refactor: add helpers for file I/O, main script detection, and safe strncpy, make hal generic
This commit is contained in:
parent
a33d4c0068
commit
9b6a3dde62
19 changed files with 323 additions and 452 deletions
4
demo/cart.fnl
Normal file
4
demo/cart.fnl
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{:title "pxl8 demo"
|
||||||
|
:resolution "640x360"
|
||||||
|
:window-size [1280 720]
|
||||||
|
:pixel-mode "indexed"}
|
||||||
|
|
@ -61,8 +61,6 @@
|
||||||
:height 64
|
:height 64
|
||||||
:base_color 4})]
|
:base_color 4})]
|
||||||
|
|
||||||
(pxl8.upload_atlas)
|
|
||||||
|
|
||||||
(let [result (pxl8.world_apply_textures world [
|
(let [result (pxl8.world_apply_textures world [
|
||||||
{:name "floor"
|
{:name "floor"
|
||||||
:texture_id floor-tex
|
:texture_id floor-tex
|
||||||
|
|
|
||||||
1
pxl8.sh
1
pxl8.sh
|
|
@ -343,7 +343,6 @@ case "$COMMAND" in
|
||||||
src/pxl8_io.c
|
src/pxl8_io.c
|
||||||
src/pxl8_log.c
|
src/pxl8_log.c
|
||||||
src/pxl8_math.c
|
src/pxl8_math.c
|
||||||
src/pxl8_rec.c
|
|
||||||
src/pxl8_repl.c
|
src/pxl8_repl.c
|
||||||
src/pxl8_save.c
|
src/pxl8_save.c
|
||||||
src/pxl8_script.c
|
src/pxl8_script.c
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ pxl8.sprite = gfx2d.sprite
|
||||||
pxl8.load_palette = gfx2d.load_palette
|
pxl8.load_palette = gfx2d.load_palette
|
||||||
pxl8.load_sprite = gfx2d.load_sprite
|
pxl8.load_sprite = gfx2d.load_sprite
|
||||||
pxl8.create_texture = gfx2d.create_texture
|
pxl8.create_texture = gfx2d.create_texture
|
||||||
pxl8.upload_atlas = gfx2d.upload_atlas
|
|
||||||
pxl8.gfx_color_ramp = gfx2d.color_ramp
|
pxl8.gfx_color_ramp = gfx2d.color_ramp
|
||||||
pxl8.gfx_fade_palette = gfx2d.fade_palette
|
pxl8.gfx_fade_palette = gfx2d.fade_palette
|
||||||
pxl8.gfx_cycle_palette = gfx2d.cycle_palette
|
pxl8.gfx_cycle_palette = gfx2d.cycle_palette
|
||||||
|
|
|
||||||
|
|
@ -70,10 +70,6 @@ function graphics.create_texture(pixels, width, height)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function graphics.upload_atlas()
|
|
||||||
C.pxl8_gfx_upload_atlas(core.gfx)
|
|
||||||
end
|
|
||||||
|
|
||||||
function graphics.color_ramp(start, count, from_color, to_color)
|
function graphics.color_ramp(start, count, from_color, to_color)
|
||||||
C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color)
|
C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
123
src/pxl8.c
123
src/pxl8.c
|
|
@ -9,14 +9,13 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "pxl8_cart.h"
|
|
||||||
#include "pxl8_game.h"
|
#include "pxl8_game.h"
|
||||||
#include "pxl8_hal.h"
|
#include "pxl8_hal.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_repl.h"
|
#include "pxl8_repl.h"
|
||||||
#include "pxl8_script.h"
|
#include "pxl8_script.h"
|
||||||
#include "pxl8_sys.h"
|
#include "pxl8_sys.h"
|
||||||
#include "pxl8_types.h"
|
|
||||||
|
|
||||||
struct pxl8 {
|
struct pxl8 {
|
||||||
pxl8_cart* cart;
|
pxl8_cart* cart;
|
||||||
|
|
@ -65,8 +64,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
|
if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
pxl8_game* game = sys->game;
|
pxl8_game* game = sys->game;
|
||||||
pxl8_pixel_mode pixel_mode = PXL8_PIXEL_INDEXED;
|
|
||||||
pxl8_resolution resolution = PXL8_RESOLUTION_640x360;
|
|
||||||
const char* script_arg = NULL;
|
const char* script_arg = NULL;
|
||||||
bool bundle_mode = false;
|
bool bundle_mode = false;
|
||||||
bool pack_mode = false;
|
bool pack_mode = false;
|
||||||
|
|
@ -116,13 +113,61 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game->repl_mode) {
|
|
||||||
pxl8_info("starting in REPL mode with script: %s", game->script_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_info("starting up");
|
pxl8_info("starting up");
|
||||||
|
|
||||||
sys->platform_data = sys->hal->create(pixel_mode, resolution, "pxl8", 1280, 720);
|
game->script = pxl8_script_create(game->repl_mode);
|
||||||
|
if (!game->script) {
|
||||||
|
pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
||||||
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_embedded = pxl8_cart_has_embedded(argv[0]);
|
||||||
|
const char* cart_path = script_arg;
|
||||||
|
char* original_cwd = getcwd(NULL, 0);
|
||||||
|
|
||||||
|
bool load_embedded = has_embedded && !script_arg;
|
||||||
|
bool load_from_path = false;
|
||||||
|
|
||||||
|
if (!load_embedded && (cart_path || !has_embedded)) {
|
||||||
|
if (!cart_path) cart_path = "demo";
|
||||||
|
struct stat st;
|
||||||
|
load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
||||||
|
(cart_path && strstr(cart_path, ".pxc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_embedded || load_from_path) {
|
||||||
|
sys->cart = pxl8_cart_create();
|
||||||
|
pxl8_result load_result = load_embedded
|
||||||
|
? pxl8_cart_load_embedded(sys->cart, argv[0])
|
||||||
|
: pxl8_cart_load(sys->cart, cart_path);
|
||||||
|
|
||||||
|
if (!sys->cart || load_result != PXL8_OK) {
|
||||||
|
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
|
||||||
|
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||||
|
sys->cart = NULL;
|
||||||
|
free(original_cwd);
|
||||||
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_cart_mount(sys->cart);
|
||||||
|
pxl8_script_load_cart_manifest(game->script, sys->cart);
|
||||||
|
if (load_from_path) {
|
||||||
|
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
|
||||||
|
}
|
||||||
|
pxl8_strncpy(game->script_path, "main.fnl", sizeof(game->script_path));
|
||||||
|
} else if (script_arg) {
|
||||||
|
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
|
||||||
|
}
|
||||||
|
free(original_cwd);
|
||||||
|
|
||||||
|
const char* window_title = pxl8_cart_get_title(sys->cart);
|
||||||
|
if (!window_title) window_title = "pxl8";
|
||||||
|
pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart);
|
||||||
|
pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart);
|
||||||
|
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
|
||||||
|
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
|
||||||
|
|
||||||
|
sys->platform_data = sys->hal->create(render_size.w, render_size.h, window_title, window_size.w, window_size.h);
|
||||||
if (!sys->platform_data) {
|
if (!sys->platform_data) {
|
||||||
pxl8_error("failed to create platform context");
|
pxl8_error("failed to create platform context");
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
|
|
@ -139,58 +184,8 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
game->script = pxl8_script_create(game->repl_mode);
|
if (game->repl_mode) {
|
||||||
if (!game->script) {
|
pxl8_info("starting in REPL mode with script: %s", game->script_path);
|
||||||
pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_embedded = pxl8_cart_has_embedded(argv[0]);
|
|
||||||
const char* cart_path = script_arg;
|
|
||||||
|
|
||||||
if (has_embedded && !script_arg) {
|
|
||||||
sys->cart = pxl8_cart_create();
|
|
||||||
if (!sys->cart) {
|
|
||||||
pxl8_error("failed to create cart");
|
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
|
||||||
}
|
|
||||||
if (pxl8_cart_load_embedded(sys->cart, argv[0]) == PXL8_OK) {
|
|
||||||
pxl8_cart_mount(sys->cart);
|
|
||||||
strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1);
|
|
||||||
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
|
||||||
pxl8_info("running embedded cart");
|
|
||||||
} else {
|
|
||||||
pxl8_error("failed to load embedded cart");
|
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
|
||||||
}
|
|
||||||
} else if (cart_path || !has_embedded) {
|
|
||||||
if (!cart_path) cart_path = "demo";
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
bool is_cart = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
|
||||||
(cart_path && strstr(cart_path, ".pxc"));
|
|
||||||
|
|
||||||
if (is_cart) {
|
|
||||||
char* original_cwd = getcwd(NULL, 0);
|
|
||||||
sys->cart = pxl8_cart_create();
|
|
||||||
if (!sys->cart) {
|
|
||||||
pxl8_error("failed to create cart");
|
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
|
||||||
}
|
|
||||||
if (pxl8_cart_load(sys->cart, cart_path) == PXL8_OK) {
|
|
||||||
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
|
|
||||||
pxl8_cart_mount(sys->cart);
|
|
||||||
strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1);
|
|
||||||
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
|
||||||
} else {
|
|
||||||
pxl8_error("failed to load cart: %s", cart_path);
|
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
|
||||||
}
|
|
||||||
free(original_cwd);
|
|
||||||
} else if (script_arg) {
|
|
||||||
strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1);
|
|
||||||
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_script_set_gfx(game->script, game->gfx);
|
pxl8_script_set_gfx(game->script, game->gfx);
|
||||||
|
|
@ -202,7 +197,10 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
game->script_loaded = (result == PXL8_OK);
|
game->script_loaded = (result == PXL8_OK);
|
||||||
|
|
||||||
if (game->script_loaded && !game->repl_mode) {
|
if (game->script_loaded && !game->repl_mode) {
|
||||||
pxl8_script_call_function(game->script, "init");
|
pxl8_result init_result = pxl8_script_call_function(game->script, "init");
|
||||||
|
if (init_result != PXL8_OK) {
|
||||||
|
pxl8_script_error("%s", pxl8_script_get_last_error(game->script));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -305,7 +303,6 @@ pxl8_result pxl8_frame(pxl8* sys) {
|
||||||
|
|
||||||
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx)));
|
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx)));
|
||||||
pxl8_gfx_upload_framebuffer(game->gfx);
|
pxl8_gfx_upload_framebuffer(game->gfx);
|
||||||
pxl8_gfx_upload_atlas(game->gfx);
|
|
||||||
pxl8_gfx_present(game->gfx);
|
pxl8_gfx_present(game->gfx);
|
||||||
|
|
||||||
memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed));
|
memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed));
|
||||||
|
|
|
||||||
|
|
@ -98,31 +98,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
|
||||||
|
|
||||||
memset(bsp, 0, sizeof(*bsp));
|
memset(bsp, 0, sizeof(*bsp));
|
||||||
|
|
||||||
FILE* f = fopen(path, "rb");
|
u8* file_data = NULL;
|
||||||
if (!f) {
|
size_t file_size = 0;
|
||||||
|
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
|
||||||
|
if (result != PXL8_OK) {
|
||||||
pxl8_error("Failed to load BSP file: %s", path);
|
pxl8_error("Failed to load BSP file: %s", path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
size_t file_size = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
|
|
||||||
u8* file_data = (u8*)malloc(file_size);
|
|
||||||
if (!file_data) {
|
|
||||||
fclose(f);
|
|
||||||
pxl8_error("Failed to allocate memory for BSP file: %s", path);
|
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fread(file_data, 1, file_size, f) != file_size) {
|
|
||||||
free(file_data);
|
|
||||||
fclose(f);
|
|
||||||
pxl8_error("Failed to read BSP file: %s", path);
|
|
||||||
return PXL8_ERROR_INVALID_FORMAT;
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
if (file_size < sizeof(pxl8_bsp_header)) {
|
if (file_size < sizeof(pxl8_bsp_header)) {
|
||||||
pxl8_error("BSP file too small: %s", path);
|
pxl8_error("BSP file too small: %s", path);
|
||||||
free(file_data);
|
free(file_data);
|
||||||
|
|
|
||||||
112
src/pxl8_cart.c
112
src/pxl8_cart.c
|
|
@ -45,7 +45,10 @@ struct pxl8_cart {
|
||||||
pxl8_cart_file* files;
|
pxl8_cart_file* files;
|
||||||
u32 file_count;
|
u32 file_count;
|
||||||
char* base_path;
|
char* base_path;
|
||||||
char* name;
|
char* title;
|
||||||
|
pxl8_resolution resolution;
|
||||||
|
pxl8_size window_size;
|
||||||
|
pxl8_pixel_mode pixel_mode;
|
||||||
bool is_folder;
|
bool is_folder;
|
||||||
bool is_mounted;
|
bool is_mounted;
|
||||||
};
|
};
|
||||||
|
|
@ -63,19 +66,12 @@ static bool is_pxc_file(const char* path) {
|
||||||
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* get_cart_name(const char* path) {
|
static bool has_main_script(const char* base_path) {
|
||||||
char* name = strdup(path);
|
char path[512];
|
||||||
size_t len = strlen(name);
|
snprintf(path, sizeof(path), "%s/main.fnl", base_path);
|
||||||
if (len > 4 && strcmp(name + len - 4, ".pxc") == 0) {
|
if (access(path, F_OK) == 0) return true;
|
||||||
name[len - 4] = '\0';
|
snprintf(path, sizeof(path), "%s/main.lua", base_path);
|
||||||
}
|
return access(path, F_OK) == 0;
|
||||||
char* last_slash = strrchr(name, '/');
|
|
||||||
if (last_slash) {
|
|
||||||
char* result = strdup(last_slash + 1);
|
|
||||||
free(name);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void collect_files_recursive(const char* dir_path, const char* prefix,
|
static void collect_files_recursive(const char* dir_path, const char* prefix,
|
||||||
|
|
@ -155,7 +151,13 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_cart* pxl8_cart_create(void) {
|
pxl8_cart* pxl8_cart_create(void) {
|
||||||
return calloc(1, sizeof(pxl8_cart));
|
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
|
||||||
|
if (cart) {
|
||||||
|
cart->resolution = PXL8_RESOLUTION_640x360;
|
||||||
|
cart->window_size = (pxl8_size){1280, 720};
|
||||||
|
cart->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||||
|
}
|
||||||
|
return cart;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_cart* pxl8_cart_current(void) {
|
pxl8_cart* pxl8_cart_current(void) {
|
||||||
|
|
@ -171,7 +173,6 @@ void pxl8_cart_destroy(pxl8_cart* cart) {
|
||||||
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||||
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
||||||
pxl8_cart_unload(cart);
|
pxl8_cart_unload(cart);
|
||||||
cart->name = get_cart_name(path);
|
|
||||||
|
|
||||||
if (is_directory(path)) {
|
if (is_directory(path)) {
|
||||||
cart->base_path = realpath(path, NULL);
|
cart->base_path = realpath(path, NULL);
|
||||||
|
|
@ -181,17 +182,12 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||||
}
|
}
|
||||||
cart->is_folder = true;
|
cart->is_folder = true;
|
||||||
|
|
||||||
char main_path[512];
|
if (!has_main_script(cart->base_path)) {
|
||||||
snprintf(main_path, sizeof(main_path), "%s/main.fnl", cart->base_path);
|
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
|
||||||
if (access(main_path, F_OK) != 0) {
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
snprintf(main_path, sizeof(main_path), "%s/main.lua", cart->base_path);
|
|
||||||
if (access(main_path, F_OK) != 0) {
|
|
||||||
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
|
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_info("Loaded cart folder: %s", cart->name);
|
pxl8_info("Loaded cart");
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,7 +214,7 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||||
free(data);
|
free(data);
|
||||||
|
|
||||||
if (result == PXL8_OK) {
|
if (result == PXL8_OK) {
|
||||||
pxl8_info("Loaded cart: %s", cart->name);
|
pxl8_info("Loaded cart");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -254,12 +250,11 @@ pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
|
||||||
}
|
}
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
cart->name = get_cart_name(exe_path);
|
|
||||||
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
|
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
|
||||||
free(data);
|
free(data);
|
||||||
|
|
||||||
if (result == PXL8_OK) {
|
if (result == PXL8_OK) {
|
||||||
pxl8_info("Loaded embedded cart: %s", cart->name);
|
pxl8_info("Loaded embedded cart");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -281,10 +276,12 @@ void pxl8_cart_unload(pxl8_cart* cart) {
|
||||||
cart->data = NULL;
|
cart->data = NULL;
|
||||||
cart->data_size = 0;
|
cart->data_size = 0;
|
||||||
|
|
||||||
if (cart->name) {
|
if (cart->title) {
|
||||||
pxl8_info("Unloaded cart: %s", cart->name);
|
pxl8_info("Unloaded cart: %s", cart->title);
|
||||||
free(cart->name);
|
free(cart->title);
|
||||||
cart->name = NULL;
|
cart->title = NULL;
|
||||||
|
} else {
|
||||||
|
pxl8_info("Unloaded cart");
|
||||||
}
|
}
|
||||||
|
|
||||||
free(cart->base_path);
|
free(cart->base_path);
|
||||||
|
|
@ -312,7 +309,11 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
||||||
|
|
||||||
cart->is_mounted = true;
|
cart->is_mounted = true;
|
||||||
pxl8_current_cart = cart;
|
pxl8_current_cart = cart;
|
||||||
pxl8_info("Mounted cart: %s", cart->name);
|
if (cart->title) {
|
||||||
|
pxl8_info("Mounted cart: %s", cart->title);
|
||||||
|
} else {
|
||||||
|
pxl8_info("Mounted cart");
|
||||||
|
}
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,8 +336,38 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart) {
|
||||||
return cart ? cart->base_path : NULL;
|
return cart ? cart->base_path : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* pxl8_cart_get_name(const pxl8_cart* cart) {
|
const char* pxl8_cart_get_title(const pxl8_cart* cart) {
|
||||||
return cart ? cart->name : NULL;
|
return cart ? cart->title : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_set_title(pxl8_cart* cart, const char* title) {
|
||||||
|
if (!cart || !title) return;
|
||||||
|
free(cart->title);
|
||||||
|
cart->title = strdup(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart) {
|
||||||
|
return cart ? cart->resolution : PXL8_RESOLUTION_640x360;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution) {
|
||||||
|
if (cart) cart->resolution = resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart) {
|
||||||
|
return cart ? cart->window_size : (pxl8_size){1280, 720};
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) {
|
||||||
|
if (cart) cart->window_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) {
|
||||||
|
return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) {
|
||||||
|
if (cart) cart->pixel_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
||||||
|
|
@ -429,14 +460,9 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
char main_path[512];
|
if (!has_main_script(folder_path)) {
|
||||||
snprintf(main_path, sizeof(main_path), "%s/main.fnl", folder_path);
|
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
|
||||||
if (access(main_path, F_OK) != 0) {
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
snprintf(main_path, sizeof(main_path), "%s/main.lua", folder_path);
|
|
||||||
if (access(main_path, F_OK) != 0) {
|
|
||||||
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
|
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char** paths = NULL;
|
char** paths = NULL;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,14 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart);
|
||||||
void pxl8_cart_unmount(pxl8_cart* cart);
|
void pxl8_cart_unmount(pxl8_cart* cart);
|
||||||
|
|
||||||
const char* pxl8_cart_get_base_path(const pxl8_cart* cart);
|
const char* pxl8_cart_get_base_path(const pxl8_cart* cart);
|
||||||
const char* pxl8_cart_get_name(const pxl8_cart* cart);
|
const char* pxl8_cart_get_title(const pxl8_cart* cart);
|
||||||
|
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart);
|
||||||
|
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart);
|
||||||
|
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart);
|
||||||
|
void pxl8_cart_set_title(pxl8_cart* cart, const char* title);
|
||||||
|
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution);
|
||||||
|
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size);
|
||||||
|
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode);
|
||||||
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
||||||
bool pxl8_cart_has_embedded(const char* exe_path);
|
bool pxl8_cart_has_embedded(const char* exe_path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "pxl8_font.h"
|
#include "pxl8_font.h"
|
||||||
#include "pxl8_hal.h"
|
#include "pxl8_hal.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_sys.h"
|
#include "pxl8_sys.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
@ -273,8 +274,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
||||||
pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++];
|
pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++];
|
||||||
entry->active = true;
|
entry->active = true;
|
||||||
entry->sprite_id = sprite_id;
|
entry->sprite_id = sprite_id;
|
||||||
strncpy(entry->path, path, sizeof(entry->path) - 1);
|
pxl8_strncpy(entry->path, path, sizeof(entry->path));
|
||||||
entry->path[sizeof(entry->path) - 1] = '\0';
|
|
||||||
|
|
||||||
return sprite_id;
|
return sprite_id;
|
||||||
}
|
}
|
||||||
|
|
@ -327,25 +327,17 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
|
||||||
if (!gfx || !gfx->initialized || !gfx->atlas) return;
|
|
||||||
|
|
||||||
if (gfx->hal && gfx->hal->upload_atlas) {
|
|
||||||
gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->pixel_mode);
|
|
||||||
pxl8_atlas_mark_clean(gfx->atlas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||||
|
|
||||||
gfx->hal->upload_framebuffer(
|
u32 bpp = (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) ? 2 : 1;
|
||||||
|
gfx->hal->upload_texture(
|
||||||
gfx->platform_data,
|
gfx->platform_data,
|
||||||
gfx->framebuffer,
|
gfx->framebuffer,
|
||||||
gfx->framebuffer_width,
|
gfx->framebuffer_width,
|
||||||
gfx->framebuffer_height,
|
gfx->framebuffer_height,
|
||||||
gfx->palette,
|
gfx->palette,
|
||||||
gfx->pixel_mode
|
bpp
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
typedef struct pxl8_atlas pxl8_atlas;
|
|
||||||
typedef struct pxl8_gfx pxl8_gfx;
|
typedef struct pxl8_gfx pxl8_gfx;
|
||||||
|
|
||||||
typedef struct pxl8_vertex {
|
typedef struct pxl8_vertex {
|
||||||
|
|
@ -42,7 +41,6 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
|
||||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
||||||
void pxl8_gfx_present(pxl8_gfx* gfx);
|
void pxl8_gfx_present(pxl8_gfx* gfx);
|
||||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
||||||
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx);
|
|
||||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
||||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
typedef struct pxl8_atlas pxl8_atlas;
|
|
||||||
typedef struct pxl8_game pxl8_game;
|
|
||||||
|
|
||||||
typedef struct pxl8_hal {
|
typedef struct pxl8_hal {
|
||||||
void* (*create)(pxl8_pixel_mode mode, pxl8_resolution res,
|
void* (*create)(i32 render_w, i32 render_h,
|
||||||
const char* title, i32 win_w, i32 win_h);
|
const char* title, i32 win_w, i32 win_h);
|
||||||
void (*destroy)(void* platform_data);
|
void (*destroy)(void* platform_data);
|
||||||
|
|
||||||
|
|
@ -14,11 +11,8 @@ typedef struct pxl8_hal {
|
||||||
|
|
||||||
void (*center_cursor)(void* platform_data);
|
void (*center_cursor)(void* platform_data);
|
||||||
void (*present)(void* platform_data);
|
void (*present)(void* platform_data);
|
||||||
void (*set_cursor)(void* platform_data, pxl8_cursor cursor);
|
void (*set_cursor)(void* platform_data, u32 cursor);
|
||||||
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
||||||
void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas,
|
void (*upload_texture)(void* platform_data, const u8* pixels, u32 w, u32 h,
|
||||||
const u32* palette, pxl8_pixel_mode mode);
|
const u32* palette, u32 bpp);
|
||||||
void (*upload_framebuffer)(void* platform_data, const u8* fb,
|
|
||||||
i32 w, i32 h, const u32* palette,
|
|
||||||
pxl8_pixel_mode mode);
|
|
||||||
} pxl8_hal;
|
} pxl8_hal;
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,4 @@ void pxl8_log_write_error(const char* file, int line, const char* fmt, ...);
|
||||||
#define pxl8_info(...) pxl8_log_write_info(__VA_ARGS__)
|
#define pxl8_info(...) pxl8_log_write_info(__VA_ARGS__)
|
||||||
#define pxl8_warn(...) pxl8_log_write_warn(__FILE__, __LINE__, __VA_ARGS__)
|
#define pxl8_warn(...) pxl8_log_write_warn(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
#define pxl8_error(...) pxl8_log_write_error(__FILE__, __LINE__, __VA_ARGS__)
|
#define pxl8_error(...) pxl8_log_write_error(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
#define pxl8_script_error(...) pxl8_log_write_error(NULL, 0, __VA_ARGS__)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#ifndef pxl8_min
|
#ifndef pxl8_min
|
||||||
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
|
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -7,3 +9,10 @@
|
||||||
#ifndef pxl8_max
|
#ifndef pxl8_max
|
||||||
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
|
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef pxl8_strncpy
|
||||||
|
#define pxl8_strncpy(dst, src, size) do { \
|
||||||
|
strncpy((dst), (src), (size) - 1); \
|
||||||
|
(dst)[(size) - 1] = '\0'; \
|
||||||
|
} while (0)
|
||||||
|
#endif
|
||||||
|
|
|
||||||
137
src/pxl8_rec.c
137
src/pxl8_rec.c
|
|
@ -1,137 +0,0 @@
|
||||||
#include "pxl8_rec.h"
|
|
||||||
|
|
||||||
#include "pxl8_log.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
struct pxl8_recorder {
|
|
||||||
i32 height;
|
|
||||||
i32 width;
|
|
||||||
|
|
||||||
pxl8_recorder_format format;
|
|
||||||
i32 framerate;
|
|
||||||
char output_path[512];
|
|
||||||
|
|
||||||
FILE* ffmpeg_pipe;
|
|
||||||
u32 frame_count;
|
|
||||||
bool recording;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void generate_default_output_path(pxl8_recorder* rec) {
|
|
||||||
time_t now = time(NULL);
|
|
||||||
struct tm* t = localtime(&now);
|
|
||||||
const char* ext = (rec->format == PXL8_RECORDER_GIF) ? "gif" : "mp4";
|
|
||||||
|
|
||||||
snprintf(rec->output_path, sizeof(rec->output_path),
|
|
||||||
"recording_%04d%02d%02d_%02d%02d%02d.%s",
|
|
||||||
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
|
||||||
t->tm_hour, t->tm_min, t->tm_sec, ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_recorder* pxl8_recorder_create(i32 width, i32 height) {
|
|
||||||
pxl8_recorder* rec = (pxl8_recorder*)calloc(1, sizeof(pxl8_recorder));
|
|
||||||
if (!rec) return NULL;
|
|
||||||
|
|
||||||
rec->height = height;
|
|
||||||
rec->width = width;
|
|
||||||
|
|
||||||
rec->format = PXL8_RECORDER_MP4;
|
|
||||||
rec->framerate = 60;
|
|
||||||
rec->output_path[0] = '\0';
|
|
||||||
|
|
||||||
rec->ffmpeg_pipe = NULL;
|
|
||||||
rec->frame_count = 0;
|
|
||||||
rec->recording = false;
|
|
||||||
|
|
||||||
return rec;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_destroy(pxl8_recorder* rec) {
|
|
||||||
if (!rec) return;
|
|
||||||
if (rec->recording) pxl8_recorder_stop(rec);
|
|
||||||
free(rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format) {
|
|
||||||
if (rec && !rec->recording) rec->format = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps) {
|
|
||||||
if (rec && !rec->recording) rec->framerate = fps > 0 ? fps : 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path) {
|
|
||||||
if (rec && !rec->recording && path) strncpy(rec->output_path, path, sizeof(rec->output_path) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_start(pxl8_recorder* rec) {
|
|
||||||
if (!rec || rec->recording) return;
|
|
||||||
if (rec->output_path[0] == '\0') generate_default_output_path(rec);
|
|
||||||
|
|
||||||
char cmd[1024];
|
|
||||||
|
|
||||||
if (rec->format == PXL8_RECORDER_GIF) {
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - "
|
|
||||||
"-vf \"split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" "
|
|
||||||
"\"%s\" 2>/dev/null",
|
|
||||||
rec->width, rec->height, rec->framerate, rec->output_path);
|
|
||||||
} else {
|
|
||||||
snprintf(cmd, sizeof(cmd),
|
|
||||||
"ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - "
|
|
||||||
"-c:v libx264 -pix_fmt yuv420p -crf 18 -preset fast "
|
|
||||||
"\"%s\" 2>/dev/null",
|
|
||||||
rec->width, rec->height, rec->framerate, rec->output_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
rec->ffmpeg_pipe = popen(cmd, "w");
|
|
||||||
if (!rec->ffmpeg_pipe) {
|
|
||||||
pxl8_error("Failed to start ffmpeg. Is ffmpeg installed?");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rec->frame_count = 0;
|
|
||||||
rec->recording = true;
|
|
||||||
|
|
||||||
pxl8_info("Recording started: %s (%dx%d @ %dfps)",
|
|
||||||
rec->output_path, rec->width, rec->height, rec->framerate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_stop(pxl8_recorder* rec) {
|
|
||||||
if (!rec || !rec->recording) return;
|
|
||||||
|
|
||||||
rec->recording = false;
|
|
||||||
|
|
||||||
if (rec->ffmpeg_pipe) {
|
|
||||||
pclose(rec->ffmpeg_pipe);
|
|
||||||
rec->ffmpeg_pipe = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_info("Recording stopped: %u frames -> %s", rec->frame_count, rec->output_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool pxl8_recorder_is_recording(pxl8_recorder* rec) {
|
|
||||||
return rec && rec->recording;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels) {
|
|
||||||
if (!rec || !rec->recording || !rgba_pixels || !rec->ffmpeg_pipe) return;
|
|
||||||
|
|
||||||
size_t written = fwrite(rgba_pixels, 4, rec->width * rec->height, rec->ffmpeg_pipe);
|
|
||||||
if (written == (size_t)(rec->width * rec->height)) {
|
|
||||||
rec->frame_count++;
|
|
||||||
} else {
|
|
||||||
pxl8_error("Failed to write frame %u", rec->frame_count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec) {
|
|
||||||
return rec ? rec->frame_count : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* pxl8_recorder_get_output_path(pxl8_recorder* rec) {
|
|
||||||
return rec ? rec->output_path : NULL;
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "pxl8_types.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum pxl8_recorder_format {
|
|
||||||
PXL8_RECORDER_GIF,
|
|
||||||
PXL8_RECORDER_MP4
|
|
||||||
} pxl8_recorder_format;
|
|
||||||
|
|
||||||
typedef struct pxl8_recorder pxl8_recorder;
|
|
||||||
|
|
||||||
pxl8_recorder* pxl8_recorder_create(i32 width, i32 height);
|
|
||||||
void pxl8_recorder_destroy(pxl8_recorder* rec);
|
|
||||||
|
|
||||||
void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels);
|
|
||||||
u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec);
|
|
||||||
const char* pxl8_recorder_get_output_path(pxl8_recorder* rec);
|
|
||||||
bool pxl8_recorder_is_recording(pxl8_recorder* rec);
|
|
||||||
void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format);
|
|
||||||
void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps);
|
|
||||||
void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path);
|
|
||||||
void pxl8_recorder_start(pxl8_recorder* rec);
|
|
||||||
void pxl8_recorder_stop(pxl8_recorder* rec);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -14,8 +14,9 @@
|
||||||
|
|
||||||
#include "pxl8_cart.h"
|
#include "pxl8_cart.h"
|
||||||
#include "pxl8_embed.h"
|
#include "pxl8_embed.h"
|
||||||
#include "pxl8_log.h"
|
|
||||||
#include "pxl8_gui.h"
|
#include "pxl8_gui.h"
|
||||||
|
#include "pxl8_log.h"
|
||||||
|
#include "pxl8_macros.h"
|
||||||
|
|
||||||
struct pxl8_script {
|
struct pxl8_script {
|
||||||
lua_State* L;
|
lua_State* L;
|
||||||
|
|
@ -193,7 +194,6 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n"
|
"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n"
|
||||||
"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n"
|
"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n"
|
||||||
"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n"
|
"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n"
|
||||||
"void pxl8_gfx_upload_atlas(pxl8_gfx* ctx);\n"
|
|
||||||
"typedef struct pxl8_input_state pxl8_input_state;\n"
|
"typedef struct pxl8_input_state pxl8_input_state;\n"
|
||||||
"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n"
|
"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n"
|
||||||
"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n"
|
"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n"
|
||||||
|
|
@ -422,17 +422,52 @@ static const char* pxl8_ffi_cdefs =
|
||||||
void pxl8_lua_log(int level, const char* file, int line, const char* msg) {
|
void pxl8_lua_log(int level, const char* file, int line, const char* msg) {
|
||||||
if (file && (file[0] == '?' || file[0] == '\0')) file = NULL;
|
if (file && (file[0] == '?' || file[0] == '\0')) file = NULL;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 0: pxl8_log_write_info("%s", msg); break;
|
case PXL8_LOG_LEVEL_TRACE: pxl8_log_write_trace(file, line, "%s", msg); break;
|
||||||
case 1: pxl8_log_write_warn(file, line, "%s", msg); break;
|
case PXL8_LOG_LEVEL_DEBUG: pxl8_log_write_debug(file, line, "%s", msg); break;
|
||||||
case 2: pxl8_log_write_error(file, line, "%s", msg); break;
|
case PXL8_LOG_LEVEL_INFO: pxl8_log_write_info("%s", msg); break;
|
||||||
case 3: pxl8_log_write_debug(file, line, "%s", msg); break;
|
case PXL8_LOG_LEVEL_WARN: pxl8_log_write_warn(file, line, "%s", msg); break;
|
||||||
case 4: pxl8_log_write_trace(file, line, "%s", msg); break;
|
case PXL8_LOG_LEVEL_ERROR: pxl8_log_write_error(file, line, "%s", msg); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pxl8_script_set_error(pxl8_script* script, const char* error) {
|
static void pxl8_script_set_error(pxl8_script* script, const char* error) {
|
||||||
if (!script) return;
|
if (!script) return;
|
||||||
snprintf(script->last_error, sizeof(script->last_error), "%s", error ? error : "Unknown error");
|
if (!error) {
|
||||||
|
snprintf(script->last_error, sizeof(script->last_error), "Unknown error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* src = error;
|
||||||
|
char* dst = script->last_error;
|
||||||
|
char* end = dst + sizeof(script->last_error) - 1;
|
||||||
|
|
||||||
|
while (*src && dst < end) {
|
||||||
|
if (strncmp(src, "[string \"", 9) == 0) {
|
||||||
|
src += 9;
|
||||||
|
const char* mod_start = src;
|
||||||
|
while (*src && !(*src == '"' && *(src+1) == ']')) src++;
|
||||||
|
size_t mod_len = src - mod_start;
|
||||||
|
|
||||||
|
if (mod_len > 4 && strncmp(mod_start, "pxl8", 4) == 0) {
|
||||||
|
const char* prefix = "src/lua/";
|
||||||
|
while (*prefix && dst < end) *dst++ = *prefix++;
|
||||||
|
for (size_t i = 0; i < mod_len && dst < end; i++) {
|
||||||
|
*dst++ = (mod_start[i] == '.') ? '/' : mod_start[i];
|
||||||
|
}
|
||||||
|
const char* suffix = ".lua";
|
||||||
|
while (*suffix && dst < end) *dst++ = *suffix++;
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < mod_len && dst < end; i++) {
|
||||||
|
*dst++ = mod_start[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*src == '"' && *(src+1) == ']') src += 2;
|
||||||
|
} else {
|
||||||
|
*dst++ = *src++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*dst = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* pxl8_script_get_last_error(pxl8_script* script) {
|
const char* pxl8_script_get_last_error(pxl8_script* script) {
|
||||||
|
|
@ -610,8 +645,7 @@ void pxl8_script_set_sys(pxl8_script* script, void* sys) {
|
||||||
|
|
||||||
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) {
|
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) {
|
||||||
char filename_copy[PATH_MAX];
|
char filename_copy[PATH_MAX];
|
||||||
strncpy(filename_copy, filename, sizeof(filename_copy) - 1);
|
pxl8_strncpy(filename_copy, filename, sizeof(filename_copy));
|
||||||
filename_copy[sizeof(filename_copy) - 1] = '\0';
|
|
||||||
|
|
||||||
char* last_slash = strrchr(filename_copy, '/');
|
char* last_slash = strrchr(filename_copy, '/');
|
||||||
|
|
||||||
|
|
@ -933,8 +967,7 @@ static time_t get_latest_script_mod_time(const char* dir_path) {
|
||||||
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
|
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
|
||||||
if (!script || !path) return PXL8_ERROR_NULL_POINTER;
|
if (!script || !path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
strncpy(script->main_path, path, sizeof(script->main_path) - 1);
|
pxl8_strncpy(script->main_path, path, sizeof(script->main_path));
|
||||||
script->main_path[sizeof(script->main_path) - 1] = '\0';
|
|
||||||
|
|
||||||
char* last_slash = strrchr(script->main_path, '/');
|
char* last_slash = strrchr(script->main_path, '/');
|
||||||
if (last_slash) {
|
if (last_slash) {
|
||||||
|
|
@ -1006,3 +1039,90 @@ bool pxl8_script_is_incomplete_input(pxl8_script* script) {
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
return pxl8_script_is_incomplete_error(script->last_error);
|
return pxl8_script_is_incomplete_error(script->last_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static pxl8_resolution parse_resolution(const char* str) {
|
||||||
|
if (strcmp(str, "240x160") == 0) return PXL8_RESOLUTION_240x160;
|
||||||
|
if (strcmp(str, "320x180") == 0) return PXL8_RESOLUTION_320x180;
|
||||||
|
if (strcmp(str, "320x240") == 0) return PXL8_RESOLUTION_320x240;
|
||||||
|
if (strcmp(str, "640x360") == 0) return PXL8_RESOLUTION_640x360;
|
||||||
|
if (strcmp(str, "640x480") == 0) return PXL8_RESOLUTION_640x480;
|
||||||
|
if (strcmp(str, "800x600") == 0) return PXL8_RESOLUTION_800x600;
|
||||||
|
if (strcmp(str, "960x540") == 0) return PXL8_RESOLUTION_960x540;
|
||||||
|
return PXL8_RESOLUTION_640x360;
|
||||||
|
}
|
||||||
|
|
||||||
|
static pxl8_pixel_mode parse_pixel_mode(const char* str) {
|
||||||
|
if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED;
|
||||||
|
if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR;
|
||||||
|
return PXL8_PIXEL_INDEXED;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) {
|
||||||
|
if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
if (!pxl8_cart_file_exists(cart, "cart.fnl")) {
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* data = NULL;
|
||||||
|
u32 size = 0;
|
||||||
|
if (pxl8_cart_read_file(cart, "cart.fnl", &data, &size) != PXL8_OK) {
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getglobal(script->L, "fennel");
|
||||||
|
if (lua_isnil(script->L, -1)) {
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
pxl8_cart_free_file(data);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(script->L, -1, "eval");
|
||||||
|
lua_pushlstring(script->L, (const char*)data, size);
|
||||||
|
lua_createtable(script->L, 0, 1);
|
||||||
|
lua_pushstring(script->L, "cart.fnl");
|
||||||
|
lua_setfield(script->L, -2, "filename");
|
||||||
|
|
||||||
|
pxl8_cart_free_file(data);
|
||||||
|
|
||||||
|
if (lua_pcall(script->L, 2, 1, 0) != 0) {
|
||||||
|
pxl8_warn("Failed to load cart.fnl: %s", lua_tostring(script->L, -1));
|
||||||
|
lua_pop(script->L, 2);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_istable(script->L, -1)) {
|
||||||
|
lua_getfield(script->L, -1, "title");
|
||||||
|
if (lua_isstring(script->L, -1)) {
|
||||||
|
pxl8_cart_set_title(cart, lua_tostring(script->L, -1));
|
||||||
|
}
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
|
||||||
|
lua_getfield(script->L, -1, "resolution");
|
||||||
|
if (lua_isstring(script->L, -1)) {
|
||||||
|
pxl8_cart_set_resolution(cart, parse_resolution(lua_tostring(script->L, -1)));
|
||||||
|
}
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
|
||||||
|
lua_getfield(script->L, -1, "pixel-mode");
|
||||||
|
if (lua_isstring(script->L, -1)) {
|
||||||
|
pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1)));
|
||||||
|
}
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
|
||||||
|
lua_getfield(script->L, -1, "window-size");
|
||||||
|
if (lua_istable(script->L, -1)) {
|
||||||
|
lua_rawgeti(script->L, -1, 1);
|
||||||
|
lua_rawgeti(script->L, -2, 2);
|
||||||
|
if (lua_isnumber(script->L, -2) && lua_isnumber(script->L, -1)) {
|
||||||
|
pxl8_size size = {(i32)lua_tonumber(script->L, -2), (i32)lua_tonumber(script->L, -1)};
|
||||||
|
pxl8_cart_set_window_size(cart, size);
|
||||||
|
}
|
||||||
|
lua_pop(script->L, 2);
|
||||||
|
}
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pop(script->L, 2);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "pxl8_cart.h"
|
||||||
#include "pxl8_gfx.h"
|
#include "pxl8_gfx.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
|
|
@ -25,6 +26,7 @@ pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name,
|
||||||
bool pxl8_script_check_reload(pxl8_script* script);
|
bool pxl8_script_check_reload(pxl8_script* script);
|
||||||
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code);
|
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code);
|
||||||
pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code);
|
pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code);
|
||||||
|
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart);
|
||||||
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path);
|
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path);
|
||||||
pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name);
|
pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name);
|
||||||
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename);
|
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename);
|
||||||
|
|
|
||||||
134
src/pxl8_sdl3.c
134
src/pxl8_sdl3.c
|
|
@ -4,37 +4,27 @@
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3/SDL_main.h>
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
#include "pxl8_atlas.h"
|
|
||||||
#include "pxl8_color.h"
|
#include "pxl8_color.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
#include "pxl8_rec.h"
|
|
||||||
#include "pxl8_sys.h"
|
#include "pxl8_sys.h"
|
||||||
|
|
||||||
typedef struct pxl8_sdl3_context {
|
typedef struct pxl8_sdl3_context {
|
||||||
SDL_Texture* atlas_texture;
|
SDL_Texture* framebuffer;
|
||||||
SDL_Texture* framebuffer_texture;
|
|
||||||
SDL_Renderer* renderer;
|
SDL_Renderer* renderer;
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
|
|
||||||
u32* rgba_buffer;
|
u32* rgba_buffer;
|
||||||
size_t rgba_buffer_size;
|
size_t rgba_buffer_size;
|
||||||
|
|
||||||
u32 atlas_width;
|
|
||||||
u32 atlas_height;
|
|
||||||
} pxl8_sdl3_context;
|
} pxl8_sdl3_context;
|
||||||
|
|
||||||
static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution,
|
static void* sdl3_create(i32 render_w, i32 render_h,
|
||||||
const char* title, i32 win_w, i32 win_h) {
|
const char* title, i32 win_w, i32 win_h) {
|
||||||
(void)mode;
|
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context));
|
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context));
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
pxl8_error("Failed to allocate SDL3 context");
|
pxl8_error("Failed to allocate SDL3 context");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_size fb_size = pxl8_get_resolution_dimensions(resolution);
|
|
||||||
|
|
||||||
ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE);
|
ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE);
|
||||||
if (!ctx->window) {
|
if (!ctx->window) {
|
||||||
pxl8_error("Failed to create window: %s", SDL_GetError());
|
pxl8_error("Failed to create window: %s", SDL_GetError());
|
||||||
|
|
@ -54,11 +44,11 @@ static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution,
|
||||||
pxl8_error("Failed to set vsync: %s", SDL_GetError());
|
pxl8_error("Failed to set vsync: %s", SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->framebuffer_texture = SDL_CreateTexture(ctx->renderer,
|
ctx->framebuffer = SDL_CreateTexture(ctx->renderer,
|
||||||
SDL_PIXELFORMAT_RGBA32,
|
SDL_PIXELFORMAT_RGBA32,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
fb_size.w, fb_size.h);
|
render_w, render_h);
|
||||||
if (!ctx->framebuffer_texture) {
|
if (!ctx->framebuffer) {
|
||||||
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
|
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
|
||||||
SDL_DestroyRenderer(ctx->renderer);
|
SDL_DestroyRenderer(ctx->renderer);
|
||||||
SDL_DestroyWindow(ctx->window);
|
SDL_DestroyWindow(ctx->window);
|
||||||
|
|
@ -66,7 +56,7 @@ static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_SetTextureScaleMode(ctx->framebuffer_texture, SDL_SCALEMODE_NEAREST);
|
SDL_SetTextureScaleMode(ctx->framebuffer, SDL_SCALEMODE_NEAREST);
|
||||||
|
|
||||||
ctx->rgba_buffer = NULL;
|
ctx->rgba_buffer = NULL;
|
||||||
ctx->rgba_buffer_size = 0;
|
ctx->rgba_buffer_size = 0;
|
||||||
|
|
@ -79,21 +69,10 @@ static void sdl3_destroy(void* platform_data) {
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||||
|
|
||||||
if (ctx->rgba_buffer) {
|
if (ctx->rgba_buffer) SDL_free(ctx->rgba_buffer);
|
||||||
SDL_free(ctx->rgba_buffer);
|
if (ctx->framebuffer) SDL_DestroyTexture(ctx->framebuffer);
|
||||||
}
|
if (ctx->renderer) SDL_DestroyRenderer(ctx->renderer);
|
||||||
if (ctx->atlas_texture) {
|
if (ctx->window) SDL_DestroyWindow(ctx->window);
|
||||||
SDL_DestroyTexture(ctx->atlas_texture);
|
|
||||||
}
|
|
||||||
if (ctx->framebuffer_texture) {
|
|
||||||
SDL_DestroyTexture(ctx->framebuffer_texture);
|
|
||||||
}
|
|
||||||
if (ctx->renderer) {
|
|
||||||
SDL_DestroyRenderer(ctx->renderer);
|
|
||||||
}
|
|
||||||
if (ctx->window) {
|
|
||||||
SDL_DestroyWindow(ctx->window);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_free(ctx);
|
SDL_free(ctx);
|
||||||
}
|
}
|
||||||
|
|
@ -111,17 +90,16 @@ static void sdl3_present(void* platform_data) {
|
||||||
SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255);
|
SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255);
|
||||||
SDL_RenderClear(ctx->renderer);
|
SDL_RenderClear(ctx->renderer);
|
||||||
|
|
||||||
if (ctx->framebuffer_texture) {
|
if (ctx->framebuffer) {
|
||||||
SDL_RenderTexture(ctx->renderer, ctx->framebuffer_texture, NULL, NULL);
|
SDL_RenderTexture(ctx->renderer, ctx->framebuffer, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderPresent(ctx->renderer);
|
SDL_RenderPresent(ctx->renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
|
static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u32 h,
|
||||||
i32 w, i32 h, const u32* palette,
|
const u32* palette, u32 bpp) {
|
||||||
pxl8_pixel_mode mode) {
|
if (!platform_data || !pixels) return;
|
||||||
if (!platform_data || !fb) return;
|
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||||
size_t needed_size = w * h;
|
size_t needed_size = w * h;
|
||||||
|
|
@ -133,81 +111,18 @@ static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
|
||||||
ctx->rgba_buffer_size = needed_size;
|
ctx->rgba_buffer_size = needed_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == PXL8_PIXEL_HICOLOR) {
|
if (bpp == 2) {
|
||||||
const u16* fb16 = (const u16*)fb;
|
const u16* pixels16 = (const u16*)pixels;
|
||||||
for (i32 i = 0; i < w * h; i++) {
|
for (u32 i = 0; i < w * h; i++) {
|
||||||
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(fb16[i]);
|
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (i32 i = 0; i < w * h; i++) {
|
for (u32 i = 0; i < w * h; i++) {
|
||||||
u8 index = fb[i];
|
ctx->rgba_buffer[i] = palette[pixels[i]];
|
||||||
ctx->rgba_buffer[i] = palette[index];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->rgba_buffer, w * 4);
|
SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4);
|
||||||
}
|
|
||||||
|
|
||||||
static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
|
|
||||||
const u32* palette, pxl8_pixel_mode mode) {
|
|
||||||
if (!platform_data || !atlas) return;
|
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
|
||||||
|
|
||||||
if (!pxl8_atlas_is_dirty(atlas)) return;
|
|
||||||
|
|
||||||
u32 atlas_w = pxl8_atlas_get_width(atlas);
|
|
||||||
u32 atlas_h = pxl8_atlas_get_height(atlas);
|
|
||||||
const u8* atlas_pixels = pxl8_atlas_get_pixels(atlas);
|
|
||||||
|
|
||||||
if (!ctx->atlas_texture || ctx->atlas_width != atlas_w || ctx->atlas_height != atlas_h) {
|
|
||||||
if (ctx->atlas_texture) {
|
|
||||||
SDL_DestroyTexture(ctx->atlas_texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->atlas_texture = SDL_CreateTexture(
|
|
||||||
ctx->renderer,
|
|
||||||
SDL_PIXELFORMAT_RGBA32,
|
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
|
||||||
atlas_w,
|
|
||||||
atlas_h
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ctx->atlas_texture) {
|
|
||||||
pxl8_error("Failed to create atlas texture: %s", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_SetTextureScaleMode(ctx->atlas_texture, SDL_SCALEMODE_NEAREST);
|
|
||||||
SDL_SetTextureBlendMode(ctx->atlas_texture, SDL_BLENDMODE_BLEND);
|
|
||||||
|
|
||||||
ctx->atlas_width = atlas_w;
|
|
||||||
ctx->atlas_height = atlas_h;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t needed_size = atlas_w * atlas_h;
|
|
||||||
|
|
||||||
if (ctx->rgba_buffer_size < needed_size) {
|
|
||||||
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
|
|
||||||
if (!new_buffer) return;
|
|
||||||
ctx->rgba_buffer = new_buffer;
|
|
||||||
ctx->rgba_buffer_size = needed_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == PXL8_PIXEL_HICOLOR) {
|
|
||||||
const u16* atlas16 = (const u16*)atlas_pixels;
|
|
||||||
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
|
|
||||||
u16 pixel = atlas16[i];
|
|
||||||
ctx->rgba_buffer[i] = (pixel != 0) ? pxl8_rgb565_to_rgba32(pixel) : 0x00000000;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
|
|
||||||
u8 index = atlas_pixels[i];
|
|
||||||
ctx->rgba_buffer[i] = palette[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_UpdateTexture(ctx->atlas_texture, NULL, ctx->rgba_buffer, atlas_w * 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||||
|
|
@ -371,7 +286,7 @@ static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sdl3_set_cursor(void* platform_data, pxl8_cursor cursor) {
|
static void sdl3_set_cursor(void* platform_data, u32 cursor) {
|
||||||
if (!platform_data) return;
|
if (!platform_data) return;
|
||||||
|
|
||||||
SDL_SystemCursor sdl_cursor;
|
SDL_SystemCursor sdl_cursor;
|
||||||
|
|
@ -410,7 +325,6 @@ const pxl8_hal pxl8_hal_sdl3 = {
|
||||||
.center_cursor = sdl3_center_cursor,
|
.center_cursor = sdl3_center_cursor,
|
||||||
.present = sdl3_present,
|
.present = sdl3_present,
|
||||||
.set_cursor = sdl3_set_cursor,
|
.set_cursor = sdl3_set_cursor,
|
||||||
.upload_atlas = sdl3_upload_atlas,
|
.upload_texture = sdl3_upload_texture,
|
||||||
.upload_framebuffer = sdl3_upload_framebuffer,
|
|
||||||
.set_relative_mouse_mode = sdl3_set_relative_mouse_mode,
|
.set_relative_mouse_mode = sdl3_set_relative_mouse_mode,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue