From 323bd2a7c1775d4f0dd5f117a177a762d094234f Mon Sep 17 00:00:00 2001 From: asrael Date: Fri, 17 Oct 2025 17:54:33 -0500 Subject: [PATCH] refactor SDL out of core files --- demo/mod/bsp_world.fnl | 2 +- pxl8.sh | 2 + src/pxl8.c | 523 +++++++++---------------------- src/pxl8_ase.c | 31 +- src/pxl8_atlas.c | 360 +++++++++++++++++++++ src/pxl8_atlas.h | 26 ++ src/pxl8_bsp.c | 84 +++-- src/pxl8_font.c | 5 +- src/pxl8_game.h | 49 +++ src/pxl8_gfx.c | 693 +++++++---------------------------------- src/pxl8_gfx.h | 3 +- src/pxl8_hal.h | 20 ++ src/pxl8_io.c | 41 ++- src/pxl8_script.c | 179 ++++++++++- src/pxl8_script.h | 10 + src/pxl8_sdl3.c | 392 +++++++++++++++++++++++ src/pxl8_sdl3.h | 5 + src/pxl8_types.h | 2 +- src/pxl8_ui.c | 1 - src/pxl8_vfx.c | 21 +- src/pxl8_world.c | 7 +- 21 files changed, 1427 insertions(+), 1029 deletions(-) create mode 100644 src/pxl8_atlas.c create mode 100644 src/pxl8_atlas.h create mode 100644 src/pxl8_game.h create mode 100644 src/pxl8_hal.h create mode 100644 src/pxl8_sdl3.c create mode 100644 src/pxl8_sdl3.h diff --git a/demo/mod/bsp_world.fnl b/demo/mod/bsp_world.fnl index 31cb77b..8c87791 100644 --- a/demo/mod/bsp_world.fnl +++ b/demo/mod/bsp_world.fnl @@ -2,7 +2,7 @@ (var camera-angle 0) (local camera-height 0) -(local camera-distance 300) +(local camera-distance 450) (var fps 0) (var world nil) diff --git a/pxl8.sh b/pxl8.sh index ef7db2a..9d22e65 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -383,6 +383,7 @@ case "$COMMAND" in PXL8_SOURCE_FILES=" src/pxl8.c src/pxl8_ase.c + src/pxl8_atlas.c src/pxl8_blit.c src/pxl8_bsp.c src/pxl8_cart.c @@ -391,6 +392,7 @@ case "$COMMAND" in src/pxl8_io.c src/pxl8_math.c src/pxl8_script.c + src/pxl8_sdl3.c src/pxl8_tilemap.c src/pxl8_tilesheet.c src/pxl8_ui.c diff --git a/src/pxl8.c b/src/pxl8.c index 6f7b565..20ff15d 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -4,197 +4,34 @@ #include #include +#include #include #include #include -#define SDL_MAIN_USE_CALLBACKS -#include -#include - #include "pxl8_cart.h" +#include "pxl8_game.h" +#include "pxl8_hal.h" #include "pxl8_macros.h" #include "pxl8_script.h" +#include "pxl8_sdl3.h" #include "pxl8_types.h" #include "pxl8_ui.h" -#define PXL8_MAX_REPL_COMMANDS 4096 - -typedef struct pxl8_repl_command { - char buffer[PXL8_MAX_REPL_COMMANDS]; - struct pxl8_repl_command* next; -} pxl8_repl_command; - -typedef struct pxl8_repl_state { - pthread_t thread; - pthread_mutex_t mutex; - pxl8_repl_command* queue_head; - pxl8_repl_command* queue_tail; - bool running; -} pxl8_repl_state; - -typedef struct pxl8_state { - pxl8_cart* cart; - pxl8_color_mode color_mode; - pxl8_gfx* gfx; - pxl8_repl_state repl; - pxl8_resolution resolution; - pxl8_script* script; - pxl8_ui* ui; - - i32 frame_count; - u64 last_time; - f32 time; - - bool repl_mode; - bool running; - bool script_loaded; - char script_path[256]; - - pxl8_input_state input; -} pxl8_state; - -static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) { - const char* fennel_keywords[] = { - "fn", "let", "var", "set", "global", "local", - "if", "when", "do", "while", "for", "each", - "lambda", "λ", "partial", "macro", "macros", - "require", "include", "import-macros", - "values", "select", "table", "length", - ".", "..", ":", "->", "->>", "-?>", "-?>>", - "doto", "match", "case", "pick-values", - "collect", "icollect", "accumulate" - }; - - const char* pxl8_functions[] = { - "pxl8.clr", "pxl8.pixel", "pxl8.get_pixel", - "pxl8.line", "pxl8.rect", "pxl8.rect_fill", - "pxl8.circle", "pxl8.circle_fill", "pxl8.text", - "pxl8.get_screen", "pxl8.info", "pxl8.warn", - "pxl8.error", "pxl8.debug", "pxl8.trace" - }; - - auto buf_len = strlen(buf); - - for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) { - if (strncmp(buf, fennel_keywords[i], buf_len) == 0) { - linenoiseAddCompletion(lc, fennel_keywords[i]); - } - } - - for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) { - if (strncmp(buf, pxl8_functions[i], buf_len) == 0) { - linenoiseAddCompletion(lc, pxl8_functions[i]); - } - } -} - -static char* pxl8_repl_hints(const char* buf, int* color, int* bold) { - if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) { - *color = 35; - *bold = 0; - return "clr|pixel|line|rect|circle|text|get_screen"; - } - - if (strcmp(buf, "(fn") == 0) { - *color = 36; - *bold = 0; - return " [args] body)"; - } - - if (strcmp(buf, "(let") == 0) { - *color = 36; - *bold = 0; - return " [bindings] body)"; - } - - return NULL; -} - -static void* pxl8_repl_stdin_thread(void* user_data) { - pxl8_repl_state* repl = (pxl8_repl_state*)user_data; - char* line; - const char* history_file = ".pxl8_history"; - - linenoiseHistorySetMaxLen(100); - linenoiseSetMultiLine(1); - linenoiseSetCompletionCallback(pxl8_repl_completion); - linenoiseSetHintsCallback(pxl8_repl_hints); - linenoiseHistoryLoad(history_file); - - while (repl->running && (line = linenoise(">> "))) { - if (strlen(line) > 0) { - linenoiseHistoryAdd(line); - linenoiseHistorySave(history_file); - - pxl8_repl_command* cmd = (pxl8_repl_command*)SDL_malloc(sizeof(pxl8_repl_command)); - if (cmd) { - strncpy(cmd->buffer, line, PXL8_MAX_REPL_COMMANDS - 1); - cmd->buffer[PXL8_MAX_REPL_COMMANDS - 1] = '\0'; - cmd->next = NULL; - - pthread_mutex_lock(&repl->mutex); - if (repl->queue_tail) { - repl->queue_tail->next = cmd; - repl->queue_tail = cmd; - } else { - repl->queue_head = repl->queue_tail = cmd; - } - pthread_mutex_unlock(&repl->mutex); - } - } - linenoiseFree(line); - } - - return NULL; -} - -static void pxl8_repl_init(pxl8_repl_state* repl) { - repl->queue_head = NULL; - repl->queue_tail = NULL; - repl->running = true; - pthread_mutex_init(&repl->mutex, NULL); - pthread_create(&repl->thread, NULL, pxl8_repl_stdin_thread, repl); -} - -static void pxl8_repl_shutdown(pxl8_repl_state* repl) { - repl->running = false; - pthread_join(repl->thread, NULL); - pthread_mutex_destroy(&repl->mutex); - - pxl8_repl_command* cmd = repl->queue_head; - while (cmd) { - pxl8_repl_command* next = cmd->next; - SDL_free(cmd); - cmd = next; - } -} - -static pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl_state* repl) { - pthread_mutex_lock(&repl->mutex); - pxl8_repl_command* cmd = repl->queue_head; - if (cmd) { - repl->queue_head = cmd->next; - if (!repl->queue_head) { - repl->queue_tail = NULL; - } - } - pthread_mutex_unlock(&repl->mutex); - return cmd; -} - - -SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { - static pxl8_state app = {0}; - - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { - pxl8_error("SDL_Init failed: %s", SDL_GetError()); - return SDL_APP_FAILURE; +pxl8_game_result pxl8_init(pxl8_game* game, i32 argc, char* argv[]) { + if (!game) { + return PXL8_GAME_FAILURE; } - app.color_mode = PXL8_COLOR_MODE_MEGA; - app.resolution = PXL8_RESOLUTION_640x360; + #ifdef PXL8_TARGET_GBA + game->hal = &pxl8_hal_gba; + #else + game->hal = &pxl8_hal_sdl3; + #endif + + game->color_mode = PXL8_COLOR_MODE_MEGA; + game->resolution = PXL8_RESOLUTION_640x360; const char* script_arg = NULL; bool pack_mode = false; @@ -203,7 +40,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { for (i32 i = 1; i < argc; i++) { if (strcmp(argv[i], "--repl") == 0) { - app.repl_mode = true; + game->repl_mode = true; } else if (strcmp(argv[i], "--pack") == 0) { pack_mode = true; if (i + 2 < argc) { @@ -211,7 +48,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { pack_output = argv[++i]; } else { pxl8_error("--pack requires "); - return SDL_APP_FAILURE; + return PXL8_GAME_FAILURE; } } else if (!script_arg) { script_arg = argv[i]; @@ -220,39 +57,39 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { if (pack_mode) { pxl8_result result = pxl8_cart_pack(pack_input, pack_output); - return (result == PXL8_OK) ? SDL_APP_SUCCESS : SDL_APP_FAILURE; + return (result == PXL8_OK) ? PXL8_GAME_SUCCESS : PXL8_GAME_FAILURE; } - if (app.repl_mode) { - fprintf(stderr, "\033[38;2;184;187;38m[pxl8]\033[0m Starting in REPL mode with script: %s\n", app.script_path); + if (game->repl_mode) { + fprintf(stderr, "\033[38;2;184;187;38m[pxl8]\033[0m Starting in REPL mode with script: %s\n", game->script_path); } pxl8_info("Starting up"); - app.gfx = pxl8_gfx_create(app.color_mode, app.resolution, "pxl8", 1280, 720); - if (!app.gfx) { + game->gfx = pxl8_gfx_create(game->hal, game->color_mode, game->resolution, "pxl8", 1280, 720); + if (!game->gfx) { pxl8_error("Failed to create graphics context"); - return SDL_APP_FAILURE; + return PXL8_GAME_FAILURE; } - if (pxl8_gfx_load_font_atlas(app.gfx) != PXL8_OK) { + if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) { pxl8_error("Failed to load font atlas"); - return SDL_APP_FAILURE; + return PXL8_GAME_FAILURE; } - app.ui = pxl8_ui_create(app.gfx); - if (!app.ui) { + game->ui = pxl8_ui_create(game->gfx); + if (!game->ui) { pxl8_error("Failed to create UI"); - pxl8_gfx_destroy(app.gfx); - return SDL_APP_FAILURE; + pxl8_gfx_destroy(game->gfx); + return PXL8_GAME_FAILURE; } - app.script = pxl8_script_create(); - if (!app.script) { - pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(app.script)); - pxl8_gfx_destroy(app.gfx); - return SDL_APP_FAILURE; + game->script = pxl8_script_create(); + if (!game->script) { + pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(game->script)); + pxl8_gfx_destroy(game->gfx); + return PXL8_GAME_FAILURE; } const char* cart_path = script_arg ? script_arg : "demo"; @@ -263,244 +100,188 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { if (is_cart) { char* original_cwd = getcwd(NULL, 0); - app.cart = pxl8_cart_create(); - if (!app.cart) { + game->cart = pxl8_cart_create(); + if (!game->cart) { pxl8_error("Failed to create cart"); - return false; + return PXL8_GAME_FAILURE; } - if (pxl8_cart_load(app.cart, cart_path) == PXL8_OK) { - pxl8_script_set_cart_path(app.script, pxl8_cart_get_base_path(app.cart), original_cwd); - pxl8_cart_mount(app.cart); - strcpy(app.script_path, "main.fnl"); - pxl8_info("Loaded cart: %s", pxl8_cart_get_name(app.cart)); + if (pxl8_cart_load(game->cart, cart_path) == PXL8_OK) { + pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(game->cart), original_cwd); + pxl8_cart_mount(game->cart); + strcpy(game->script_path, "main.fnl"); + pxl8_info("Loaded cart: %s", pxl8_cart_get_name(game->cart)); } else { pxl8_error("Failed to load cart: %s", cart_path); - return SDL_APP_FAILURE; + return PXL8_GAME_FAILURE; } free(original_cwd); } else if (script_arg) { - strncpy(app.script_path, script_arg, sizeof(app.script_path) - 1); - app.script_path[sizeof(app.script_path) - 1] = '\0'; + strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1); + game->script_path[sizeof(game->script_path) - 1] = '\0'; } - pxl8_script_set_gfx(app.script, app.gfx); - pxl8_script_set_input(app.script, &app.input); - pxl8_script_set_ui(app.script, app.ui); + pxl8_script_set_gfx(game->script, game->gfx); + pxl8_script_set_input(game->script, &game->input); + pxl8_script_set_ui(game->script, game->ui); - if (app.script_path[0] != '\0') { - pxl8_result result = pxl8_script_load_main(app.script, app.script_path); - app.script_loaded = (result == PXL8_OK); + if (game->script_path[0] != '\0') { + pxl8_result result = pxl8_script_load_main(game->script, game->script_path); + game->script_loaded = (result == PXL8_OK); } - if (app.repl_mode) { - pxl8_repl_init(&app.repl); - fprintf(stderr, "\033[38;2;184;187;38m[pxl8 REPL]\033[0m Fennel %s - Tab for completions, Ctrl-C to exit\n", "1.5.1"); + if (game->repl_mode) { + game->repl = pxl8_script_repl_create(); + if (game->repl) { + pxl8_script_repl_init(game->repl); + fprintf(stderr, "\033[38;2;184;187;38m[pxl8 REPL]\033[0m Fennel %s - Tab for completions, Ctrl-C to exit\n", "1.5.1"); - if (pxl8_script_load_module(app.script, "pxl8") != PXL8_OK) { - fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", pxl8_script_get_last_error(app.script)); + if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) { + fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", pxl8_script_get_last_error(game->script)); + } } } - app.last_time = SDL_GetTicksNS(); - app.running = true; + game->last_time = game->hal->get_ticks(); + game->running = true; - *appstate = &app; - return SDL_APP_CONTINUE; + + return PXL8_GAME_CONTINUE; } -SDL_AppResult SDL_AppIterate(void* appstate) { - pxl8_state* app = (pxl8_state*)appstate; - pxl8_bounds bounds = pxl8_gfx_get_bounds(app->gfx); +pxl8_game_result pxl8_update(pxl8_game* game) { + if (!game) { + return PXL8_GAME_FAILURE; + } - u64 current_time = SDL_GetTicksNS(); - f32 dt = (f32)(current_time - app->last_time) / 1000000000.0f; + u64 current_time = game->hal->get_ticks(); + f32 dt = (f32)(current_time - game->last_time) / 1000000000.0f; - app->last_time = current_time; - app->time += dt; - - pxl8_script_check_reload(app->script); - - if (app->repl_mode) { - pxl8_repl_command* cmd = pxl8_repl_pop_command(&app->repl); + game->last_time = current_time; + game->time += dt; + + pxl8_script_check_reload(game->script); + + if (game->repl_mode && game->repl) { + pxl8_script_repl_command* cmd = pxl8_script_repl_pop_command(game->repl); if (cmd) { - pxl8_result result = pxl8_script_eval(app->script, cmd->buffer); + pxl8_result result = pxl8_script_eval(game->script, pxl8_script_repl_command_buffer(cmd)); if (result != PXL8_OK) { - pxl8_error("%s", pxl8_script_get_last_error(app->script)); + pxl8_error("%s", pxl8_script_get_last_error(game->script)); } - SDL_free(cmd); + pxl8_script_repl_command_free(cmd); } } - if (app->ui) { - pxl8_ui_input_mousemove(app->ui, app->input.mouse_x, app->input.mouse_y); - if (app->input.mouse_wheel_x != 0 || app->input.mouse_wheel_y != 0) { - pxl8_ui_input_scroll(app->ui, app->input.mouse_wheel_x * 10, -app->input.mouse_wheel_y * 10); + if (game->ui) { + i32 render_width, render_height; + pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); + + pxl8_ui_input_mousemove(game->ui, game->input.mouse_x, game->input.mouse_y); + + if (game->input.mouse_wheel_x != 0 || game->input.mouse_wheel_y != 0) { + pxl8_ui_input_scroll(game->ui, game->input.mouse_wheel_x * 10, -game->input.mouse_wheel_y * 10); } - pxl8_ui_frame_begin(app->ui); - for (i32 i = 0; i < 3; i++) { - if (app->input.mouse_buttons_pressed[i]) { - pxl8_ui_input_mousedown(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1); + if (game->input.mouse_buttons_pressed[i]) { + pxl8_ui_input_mousedown(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1); } - if (app->input.mouse_buttons_released[i]) { - pxl8_ui_input_mouseup(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1); + if (game->input.mouse_buttons_released[i]) { + pxl8_ui_input_mouseup(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1); } } for (i32 key = 0; key < 256; key++) { - if (app->input.keys_pressed[key]) { - pxl8_ui_input_keydown(app->ui, key); + if (game->input.keys_pressed[key]) { + pxl8_ui_input_keydown(game->ui, key); } - if (!app->input.keys_down[key] && app->input.keys_pressed[key]) { - pxl8_ui_input_keyup(app->ui, key); + if (!game->input.keys_down[key] && game->input.keys_pressed[key]) { + pxl8_ui_input_keyup(game->ui, key); } } + + pxl8_ui_frame_begin(game->ui); } - if (app->script_loaded) { - pxl8_script_call_function_f32(app->script, "update", dt); + if (game->script_loaded) { + pxl8_script_call_function_f32(game->script, "update", dt); + } - pxl8_result frame_result = pxl8_script_call_function(app->script, "frame"); + return PXL8_GAME_CONTINUE; +} + +pxl8_game_result pxl8_frame(pxl8_game* game) { + if (!game) { + return PXL8_GAME_FAILURE; + } + + pxl8_bounds bounds = pxl8_gfx_get_bounds(game->gfx); + + if (game->script_loaded) { + pxl8_result frame_result = pxl8_script_call_function(game->script, "frame"); if (frame_result == PXL8_ERROR_SCRIPT_ERROR) { - pxl8_error("Error calling frame: %s", pxl8_script_get_last_error(app->script)); + pxl8_error("Error calling frame: %s", pxl8_script_get_last_error(game->script)); } } else { - pxl8_clr(app->gfx, 32); + pxl8_clr(game->gfx, 32); i32 render_width, render_height; - pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); + pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); for (i32 y = 0; y < render_height; y += 24) { for (i32 x = 0; x < render_width; x += 32) { - u32 color = ((x / 32) + (y / 24) + (i32)(app->time * 2)) % 8; - pxl8_rect_fill(app->gfx, x, y, 31, 23, color); + u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8; + pxl8_rect_fill(game->gfx, x, y, 31, 23, color); } } } i32 render_width, render_height; - pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); + pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); - if (app->ui) { - pxl8_ui_frame_end(app->ui); + if (game->ui) { + pxl8_ui_frame_end(game->ui); } - pxl8_gfx_set_viewport(app->gfx, pxl8_gfx_viewport(bounds, render_width, render_height)); - pxl8_gfx_upload_framebuffer(app->gfx); - pxl8_gfx_upload_atlas(app->gfx); - pxl8_gfx_present(app->gfx); + pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_width, render_height)); + pxl8_gfx_upload_framebuffer(game->gfx); + pxl8_gfx_upload_atlas(game->gfx); + pxl8_gfx_present(game->gfx); - SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed)); - SDL_memset(app->input.keys_released, 0, sizeof(app->input.keys_released)); - SDL_memset(app->input.mouse_buttons_pressed, 0, sizeof(app->input.mouse_buttons_pressed)); - SDL_memset(app->input.mouse_buttons_released, 0, sizeof(app->input.mouse_buttons_released)); - app->input.mouse_wheel_x = 0; - app->input.mouse_wheel_y = 0; + memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed)); + memset(game->input.keys_released, 0, sizeof(game->input.keys_released)); + memset(game->input.mouse_buttons_pressed, 0, sizeof(game->input.mouse_buttons_pressed)); + memset(game->input.mouse_buttons_released, 0, sizeof(game->input.mouse_buttons_released)); + game->input.mouse_wheel_x = 0; + game->input.mouse_wheel_y = 0; - return app->running ? SDL_APP_CONTINUE : SDL_APP_SUCCESS; + return game->running ? PXL8_GAME_CONTINUE : PXL8_GAME_SUCCESS; } -SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { - pxl8_state* app = (pxl8_state*)appstate; - - switch (event->type) { - case SDL_EVENT_QUIT: - app->running = false; - return SDL_APP_SUCCESS; - - case SDL_EVENT_KEY_DOWN: { - if (event->key.key == SDLK_ESCAPE) { - app->running = false; - return SDL_APP_SUCCESS; - } - - SDL_Scancode scancode = event->key.scancode; - if (scancode < 256) { - if (!app->input.keys_down[scancode]) { - app->input.keys_pressed[scancode] = true; - } - app->input.keys_down[scancode] = true; - app->input.keys_released[scancode] = false; - } - break; - } - - case SDL_EVENT_KEY_UP: { - SDL_Scancode scancode = event->key.scancode; - if (scancode < 256) { - app->input.keys_down[scancode] = false; - app->input.keys_pressed[scancode] = false; - app->input.keys_released[scancode] = true; - } - break; - } - - case SDL_EVENT_MOUSE_BUTTON_DOWN: { - u8 button = event->button.button - 1; - if (button < 3) { - if (!app->input.mouse_buttons_down[button]) { - app->input.mouse_buttons_pressed[button] = true; - } - app->input.mouse_buttons_down[button] = true; - app->input.mouse_buttons_released[button] = false; - } - break; - } - - case SDL_EVENT_MOUSE_BUTTON_UP: { - u8 button = event->button.button - 1; - if (button < 3) { - app->input.mouse_buttons_down[button] = false; - app->input.mouse_buttons_pressed[button] = false; - app->input.mouse_buttons_released[button] = true; - } - break; - } - - case SDL_EVENT_MOUSE_MOTION: { - i32 window_mouse_x = (i32)event->motion.x; - i32 window_mouse_y = (i32)event->motion.y; - - i32 render_width, render_height; - pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); - pxl8_viewport vp = pxl8_gfx_viewport(pxl8_gfx_get_bounds(app->gfx), render_width, render_height); - - app->input.mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale); - app->input.mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale); - break; - } - - case SDL_EVENT_MOUSE_WHEEL: { - app->input.mouse_wheel_x = (i32)event->wheel.x; - app->input.mouse_wheel_y = (i32)event->wheel.y; - break; - } +void pxl8_quit(pxl8_game* game) { + if (!game) { + return; } - return SDL_APP_CONTINUE; -} + pxl8_info("Shutting down"); -void SDL_AppQuit(void* appstate, SDL_AppResult result) { - pxl8_state* app = (pxl8_state*)appstate; - (void)result; - - if (app) { - pxl8_info("Shutting down"); - if (app->repl_mode) { - pxl8_repl_shutdown(&app->repl); - } - if (app->cart) { - pxl8_cart_unload(app->cart); - free(app->cart); - app->cart = NULL; - } - pxl8_script_destroy(app->script); - if (app->ui) { - pxl8_ui_destroy(app->ui); - } - pxl8_gfx_destroy(app->gfx); + if (game->repl_mode && game->repl) { + pxl8_script_repl_shutdown(game->repl); + pxl8_script_repl_destroy(game->repl); } - SDL_Quit(); + if (game->cart) { + pxl8_cart_unload(game->cart); + free(game->cart); + game->cart = NULL; + } + + pxl8_script_destroy(game->script); + + if (game->ui) { + pxl8_ui_destroy(game->ui); + } + + pxl8_gfx_destroy(game->gfx); } + diff --git a/src/pxl8_ase.c b/src/pxl8_ase.c index 4696a05..39ca5b6 100644 --- a/src/pxl8_ase.c +++ b/src/pxl8_ase.c @@ -8,7 +8,6 @@ #define MINIZ_NO_DEFLATE_APIS #include -#include #include "pxl8_ase.h" #include "pxl8_io.h" @@ -57,7 +56,7 @@ static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette palette->entry_count = total_colors; palette->first_color = 0; palette->last_color = total_colors - 1; - palette->colors = (u32*)SDL_malloc(total_colors * sizeof(u32)); + palette->colors = (u32*)malloc(total_colors * sizeof(u32)); if (!palette->colors) { return PXL8_ERROR_OUT_OF_MEMORY; } @@ -96,7 +95,7 @@ static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer) u16 name_len = pxl8_read_u16(stream); if (name_len > 0) { - layer->name = (char*)SDL_malloc(name_len + 1); + layer->name = (char*)malloc(name_len + 1); if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY; pxl8_read_bytes(stream, layer->name, name_len); layer->name[name_len] = '\0'; @@ -119,7 +118,7 @@ static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* pa return PXL8_ERROR_ASE_MALFORMED_CHUNK; } - palette->colors = (u32*)SDL_malloc(color_count * sizeof(u32)); + palette->colors = (u32*)malloc(color_count * sizeof(u32)); if (!palette->colors) { return PXL8_ERROR_OUT_OF_MEMORY; } @@ -167,7 +166,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase u32 pixel_data_size = cel->width * cel->height; u32 compressed_data_size = chunk_size - 20; - cel->pixel_data = (u8*)SDL_malloc(pixel_data_size); + cel->pixel_data = (u8*)malloc(pixel_data_size); if (!cel->pixel_data) { return PXL8_ERROR_OUT_OF_MEMORY; } @@ -177,7 +176,7 @@ static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase i32 result = mz_uncompress(cel->pixel_data, &dest_len, compressed_data, compressed_data_size); if (result != MZ_OK) { pxl8_error("Failed to decompress cel data: miniz error %d", result); - SDL_free(cel->pixel_data); + free(cel->pixel_data); cel->pixel_data = NULL; return PXL8_ERROR_ASE_MALFORMED_CHUNK; } @@ -218,7 +217,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { } ase_file->frame_count = ase_file->header.frames; - ase_file->frames = (pxl8_ase_frame*)SDL_calloc(ase_file->frame_count, sizeof(pxl8_ase_frame)); + ase_file->frames = (pxl8_ase_frame*)calloc(ase_file->frame_count, sizeof(pxl8_ase_frame)); if (!ase_file->frames) { pxl8_io_free_binary_data(file_data); return PXL8_ERROR_OUT_OF_MEMORY; @@ -256,7 +255,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { frame->duration = frame_header.duration; u32 pixel_count = frame->width * frame->height; - frame->pixels = (u8*)SDL_calloc(pixel_count, sizeof(u8)); + frame->pixels = (u8*)calloc(pixel_count, sizeof(u8)); if (!frame->pixels) { result = PXL8_ERROR_OUT_OF_MEMORY; break; @@ -285,7 +284,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { case PXL8_ASE_CHUNK_LAYER: { ase_file->layers = - (pxl8_ase_layer*)SDL_realloc(ase_file->layers, + (pxl8_ase_layer*)realloc(ase_file->layers, (ase_file->layer_count + 1) * sizeof(pxl8_ase_layer)); if (!ase_file->layers) { result = PXL8_ERROR_OUT_OF_MEMORY; @@ -325,14 +324,14 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { } } } - SDL_free(cel.pixel_data); + free(cel.pixel_data); } break; } case PXL8_ASE_CHUNK_PALETTE: if (ase_file->palette.colors) { - SDL_free(ase_file->palette.colors); + free(ase_file->palette.colors); ase_file->palette.colors = NULL; } result = parse_palette_chunk(&stream, &ase_file->palette); @@ -367,23 +366,23 @@ void pxl8_ase_destroy(pxl8_ase_file* ase_file) { if (ase_file->frames) { for (u32 i = 0; i < ase_file->frame_count; i++) { if (ase_file->frames[i].pixels) { - SDL_free(ase_file->frames[i].pixels); + free(ase_file->frames[i].pixels); } } - SDL_free(ase_file->frames); + free(ase_file->frames); } if (ase_file->palette.colors) { - SDL_free(ase_file->palette.colors); + free(ase_file->palette.colors); } if (ase_file->layers) { for (u32 i = 0; i < ase_file->layer_count; i++) { if (ase_file->layers[i].name) { - SDL_free(ase_file->layers[i].name); + free(ase_file->layers[i].name); } } - SDL_free(ase_file->layers); + free(ase_file->layers); } memset(ase_file, 0, sizeof(pxl8_ase_file)); diff --git a/src/pxl8_atlas.c b/src/pxl8_atlas.c new file mode 100644 index 0000000..96b28a2 --- /dev/null +++ b/src/pxl8_atlas.c @@ -0,0 +1,360 @@ +#include "pxl8_atlas.h" +#include "pxl8_macros.h" + +#include +#include +#include +#include + +typedef struct pxl8_skyline_fit { + bool found; + u32 node_idx; + pxl8_point pos; +} pxl8_skyline_fit; + +typedef struct pxl8_skyline_node { + i32 x, y, width; +} pxl8_skyline_node; + +typedef struct pxl8_skyline { + pxl8_skyline_node* nodes; + u32 count; + u32 capacity; +} pxl8_skyline; + +struct pxl8_atlas { + u32 height, width; + u8* pixels; + + bool dirty; + + u32 entry_capacity, entry_count; + pxl8_atlas_entry* entries; + + u32 free_capacity, free_count; + u32* free_list; + + pxl8_skyline skyline; +}; + +static pxl8_skyline_fit pxl8_skyline_find_position( + const pxl8_skyline* skyline, + u32 atlas_w, + u32 atlas_h, + u32 rect_w, + u32 rect_h +) { + pxl8_skyline_fit result = {.found = false}; + i32 best_y = INT32_MAX; + i32 best_x = 0; + u32 best_idx = 0; + + for (u32 i = 0; i < skyline->count; i++) { + i32 x = skyline->nodes[i].x; + i32 y = skyline->nodes[i].y; + + if (x + (i32)rect_w > (i32)atlas_w) continue; + + i32 max_y = y; + for (u32 j = i; j < skyline->count && skyline->nodes[j].x < x + (i32)rect_w; j++) { + if (skyline->nodes[j].y > max_y) { + max_y = skyline->nodes[j].y; + } + } + + if (max_y + (i32)rect_h > (i32)atlas_h) continue; + + if (max_y < best_y || (max_y == best_y && x < best_x)) { + best_y = max_y; + best_x = x; + best_idx = i; + } + } + + if (best_y != INT32_MAX) { + result.found = true; + result.pos.x = best_x; + result.pos.y = best_y; + result.node_idx = best_idx; + } + + return result; +} + +static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w, u32 h) { + u32 node_idx = 0; + for (u32 i = 0; i < skyline->count; i++) { + if (skyline->nodes[i].x == pos.x) { + node_idx = i; + break; + } + } + + u32 nodes_to_remove = 0; + for (u32 i = node_idx; i < skyline->count; i++) { + if (skyline->nodes[i].x < pos.x + (i32)w) { + nodes_to_remove++; + } else { + break; + } + } + + if (skyline->count - nodes_to_remove + 1 > skyline->capacity) { + skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2; + skyline->nodes = (pxl8_skyline_node*)realloc( + skyline->nodes, + skyline->capacity * sizeof(pxl8_skyline_node) + ); + } + + if (nodes_to_remove > 0) { + memmove( + &skyline->nodes[node_idx + 1], + &skyline->nodes[node_idx + nodes_to_remove], + (skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node) + ); + } + + skyline->nodes[node_idx] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w}; + skyline->count = skyline->count - nodes_to_remove + 1; +} + +static void pxl8_skyline_compact(pxl8_skyline* skyline) { + for (u32 i = 0; i < skyline->count - 1; ) { + if (skyline->nodes[i].y == skyline->nodes[i + 1].y) { + skyline->nodes[i].width += skyline->nodes[i + 1].width; + memmove( + &skyline->nodes[i + 1], + &skyline->nodes[i + 2], + (skyline->count - i - 2) * sizeof(pxl8_skyline_node) + ); + skyline->count--; + } else { + i++; + } + } +} + +pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_color_mode color_mode) { + pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas)); + if (!atlas) return NULL; + + atlas->height = height; + atlas->width = width; + + i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel); + if (!atlas->pixels) { + free(atlas); + return NULL; + } + + atlas->entry_capacity = 64; + atlas->entries = (pxl8_atlas_entry*)calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry)); + if (!atlas->entries) { + free(atlas->pixels); + free(atlas); + return NULL; + } + + atlas->free_capacity = 16; + atlas->free_list = (u32*)calloc(atlas->free_capacity, sizeof(u32)); + if (!atlas->free_list) { + free(atlas->entries); + free(atlas->pixels); + free(atlas); + return NULL; + } + + atlas->skyline.capacity = 16; + atlas->skyline.nodes = + (pxl8_skyline_node*)calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node)); + if (!atlas->skyline.nodes) { + free(atlas->free_list); + free(atlas->entries); + free(atlas->pixels); + free(atlas); + return NULL; + } + + atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)width}; + atlas->skyline.count = 1; + + return atlas; +} + +void pxl8_atlas_destroy(pxl8_atlas* atlas) { + if (!atlas) return; + + free(atlas->entries); + free(atlas->free_list); + free(atlas->pixels); + free(atlas->skyline.nodes); + free(atlas); +} + +bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_color_mode color_mode) { + if (!atlas || atlas->width >= 4096) return false; + + i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + u32 new_size = atlas->width * 2; + u32 old_width = atlas->width; + + u8* new_pixels = (u8*)calloc(new_size * new_size, bytes_per_pixel); + if (!new_pixels) return false; + + pxl8_skyline new_skyline; + new_skyline.nodes = (pxl8_skyline_node*)calloc(16, sizeof(pxl8_skyline_node)); + if (!new_skyline.nodes) { + free(new_pixels); + return false; + } + + new_skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)new_size}; + new_skyline.count = 1; + new_skyline.capacity = 16; + + for (u32 i = 0; i < atlas->entry_count; i++) { + if (!atlas->entries[i].active) continue; + + pxl8_skyline_fit fit = pxl8_skyline_find_position( + &new_skyline, + new_size, + new_size, + atlas->entries[i].w, + atlas->entries[i].h + ); + + if (!fit.found) { + free(new_skyline.nodes); + free(new_pixels); + return false; + } + + for (u32 y = 0; y < (u32)atlas->entries[i].h; y++) { + for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) { + u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x); + u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x); + if (bytes_per_pixel == 4) { + ((u32*)new_pixels)[dst_idx] = ((u32*)atlas->pixels)[src_idx]; + } else { + new_pixels[dst_idx] = atlas->pixels[src_idx]; + } + } + } + + atlas->entries[i].x = fit.pos.x; + atlas->entries[i].y = fit.pos.y; + + pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h); + pxl8_skyline_compact(&new_skyline); + } + + free(atlas->pixels); + free(atlas->skyline.nodes); + + atlas->pixels = new_pixels; + atlas->skyline = new_skyline; + atlas->width = new_size; + atlas->height = new_size; + atlas->dirty = true; + + pxl8_debug("Atlas expanded to %ux%u", atlas->width, atlas->height); + return true; +} + +u32 pxl8_atlas_add_texture( + pxl8_atlas* atlas, + const u8* pixels, + u32 w, + u32 h, + pxl8_color_mode color_mode +) { + if (!atlas || !pixels) return UINT32_MAX; + + pxl8_skyline_fit fit = + pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); + if (!fit.found) { + if (!pxl8_atlas_expand(atlas, color_mode)) { + return UINT32_MAX; + } + + fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); + + if (!fit.found) return UINT32_MAX; + } + + u32 texture_id; + if (atlas->free_count > 0) { + texture_id = atlas->free_list[--atlas->free_count]; + } else { + if (atlas->entry_count >= atlas->entry_capacity) { + atlas->entry_capacity *= 2; + atlas->entries = (pxl8_atlas_entry*)realloc( + atlas->entries, + atlas->entry_capacity * sizeof(pxl8_atlas_entry) + ); + } + texture_id = atlas->entry_count++; + } + + pxl8_atlas_entry* entry = &atlas->entries[texture_id]; + entry->active = true; + entry->texture_id = texture_id; + entry->x = fit.pos.x; + entry->y = fit.pos.y; + entry->w = w; + entry->h = h; + + i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + for (u32 y = 0; y < h; y++) { + for (u32 x = 0; x < w; x++) { + u32 src_idx = y * w + x; + u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x); + + if (bytes_per_pixel == 4) { + ((u32*)atlas->pixels)[dst_idx] = ((const u32*)pixels)[src_idx]; + } else { + atlas->pixels[dst_idx] = pixels[src_idx]; + } + } + } + + pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h); + pxl8_skyline_compact(&atlas->skyline); + + atlas->dirty = true; + + return texture_id; +} + +const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id) { + if (!atlas || id >= atlas->entry_count) return NULL; + return &atlas->entries[id]; +} + +u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas) { + return atlas ? atlas->entry_count : 0; +} + +u32 pxl8_atlas_get_height(const pxl8_atlas* atlas) { + return atlas ? atlas->height : 0; +} + +const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) { + return atlas ? atlas->pixels : NULL; +} + +u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) { + return atlas ? atlas->width : 0; +} + +bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas) { + return atlas ? atlas->dirty : false; +} + +void pxl8_atlas_mark_clean(pxl8_atlas* atlas) { + if (atlas) { + atlas->dirty = false; + } +} diff --git a/src/pxl8_atlas.h b/src/pxl8_atlas.h new file mode 100644 index 0000000..842cbbf --- /dev/null +++ b/src/pxl8_atlas.h @@ -0,0 +1,26 @@ +#pragma once + +#include "pxl8_types.h" + +typedef struct pxl8_atlas pxl8_atlas; + +typedef struct pxl8_atlas_entry { + bool active; + u32 texture_id; + i32 x, y, w, h; +} pxl8_atlas_entry; + +pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_color_mode color_mode); +void pxl8_atlas_destroy(pxl8_atlas* atlas); + +u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, + pxl8_color_mode color_mode); +bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_color_mode color_mode); + +const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id); +u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas); +u32 pxl8_atlas_get_height(const pxl8_atlas* atlas); +const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas); +u32 pxl8_atlas_get_width(const pxl8_atlas* atlas); +bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas); +void pxl8_atlas_mark_clean(pxl8_atlas* atlas); diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c index b8c41cb..a61d0f8 100644 --- a/src/pxl8_bsp.c +++ b/src/pxl8_bsp.c @@ -1,7 +1,7 @@ +#include +#include #include -#include - #include "pxl8_bsp.h" #include "pxl8_gfx.h" #include "pxl8_io.h" @@ -59,16 +59,34 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { memset(bsp, 0, sizeof(*bsp)); - size_t file_size; - u8* file_data = (u8*)SDL_LoadFile(path, &file_size); - if (!file_data) { + FILE* f = fopen(path, "rb"); + if (!f) { pxl8_error("Failed to load BSP file: %s", path); return PXL8_ERROR_FILE_NOT_FOUND; } + 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)) { pxl8_error("BSP file too small: %s", path); - SDL_free(file_data); + free(file_data); return PXL8_ERROR_INVALID_FORMAT; } @@ -79,7 +97,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (header.version != BSP_VERSION) { pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION); - SDL_free(file_data); + free(file_data); return PXL8_ERROR_INVALID_FORMAT; } @@ -92,7 +110,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup; bsp->num_vertices = chunk->size / 12; if (bsp->num_vertices > 0) { - bsp->vertices = SDL_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex)); + bsp->vertices = calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex)); if (!bsp->vertices) goto error_cleanup; pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_vertices; i++) { @@ -104,7 +122,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup; bsp->num_edges = chunk->size / 4; if (bsp->num_edges > 0) { - bsp->edges = SDL_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge)); + bsp->edges = calloc(bsp->num_edges, sizeof(pxl8_bsp_edge)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_edges; i++) { bsp->edges[i].vertex[0] = pxl8_read_u16(&stream); @@ -116,7 +134,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup; bsp->num_surfedges = chunk->size / 4; if (bsp->num_surfedges > 0) { - bsp->surfedges = SDL_calloc(bsp->num_surfedges, sizeof(i32)); + bsp->surfedges = calloc(bsp->num_surfedges, sizeof(i32)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_surfedges; i++) { bsp->surfedges[i] = pxl8_read_i32(&stream); @@ -127,7 +145,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup; bsp->num_planes = chunk->size / 20; if (bsp->num_planes > 0) { - bsp->planes = SDL_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane)); + bsp->planes = calloc(bsp->num_planes, sizeof(pxl8_bsp_plane)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_planes; i++) { bsp->planes[i].normal = read_vec3(&stream); @@ -140,7 +158,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup; bsp->num_texinfo = chunk->size / 40; if (bsp->num_texinfo > 0) { - bsp->texinfo = SDL_calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo)); + bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_texinfo; i++) { bsp->texinfo[i].u_axis = read_vec3(&stream); @@ -155,7 +173,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup; bsp->num_faces = chunk->size / 20; if (bsp->num_faces > 0) { - bsp->faces = SDL_calloc(bsp->num_faces, sizeof(pxl8_bsp_face)); + bsp->faces = calloc(bsp->num_faces, sizeof(pxl8_bsp_face)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_faces; i++) { bsp->faces[i].plane_id = pxl8_read_u16(&stream); @@ -175,7 +193,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup; bsp->num_nodes = chunk->size / 24; if (bsp->num_nodes > 0) { - bsp->nodes = SDL_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node)); + bsp->nodes = calloc(bsp->num_nodes, sizeof(pxl8_bsp_node)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_nodes; i++) { bsp->nodes[i].plane_id = pxl8_read_u32(&stream); @@ -192,7 +210,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup; bsp->num_leafs = chunk->size / 28; if (bsp->num_leafs > 0) { - bsp->leafs = SDL_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf)); + bsp->leafs = calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_leafs; i++) { bsp->leafs[i].contents = pxl8_read_i32(&stream); @@ -209,7 +227,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup; bsp->num_marksurfaces = chunk->size / 2; if (bsp->num_marksurfaces > 0) { - bsp->marksurfaces = SDL_calloc(bsp->num_marksurfaces, sizeof(u16)); + bsp->marksurfaces = calloc(bsp->num_marksurfaces, sizeof(u16)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_marksurfaces; i++) { bsp->marksurfaces[i] = pxl8_read_u16(&stream); @@ -220,7 +238,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup; bsp->num_models = chunk->size / 64; if (bsp->num_models > 0) { - bsp->models = SDL_calloc(bsp->num_models, sizeof(pxl8_bsp_model)); + bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model)); pxl8_stream_seek(&stream, chunk->offset); for (u32 i = 0; i < bsp->num_models; i++) { for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream); @@ -237,7 +255,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup; bsp->visdata_size = chunk->size; if (bsp->visdata_size > 0) { - bsp->visdata = SDL_malloc(bsp->visdata_size); + bsp->visdata = malloc(bsp->visdata_size); memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size); } @@ -245,11 +263,11 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup; bsp->lightdata_size = chunk->size; if (bsp->lightdata_size > 0) { - bsp->lightdata = SDL_malloc(bsp->lightdata_size); + bsp->lightdata = malloc(bsp->lightdata_size); memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size); } - SDL_free(file_data); + free(file_data); pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs", bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs); @@ -258,7 +276,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { error_cleanup: pxl8_error("BSP chunk validation failed: %s", path); - SDL_free(file_data); + free(file_data); pxl8_bsp_destroy(bsp); return PXL8_ERROR_INVALID_FORMAT; } @@ -266,18 +284,18 @@ error_cleanup: void pxl8_bsp_destroy(pxl8_bsp* bsp) { if (!bsp) return; - SDL_free(bsp->edges); - SDL_free(bsp->faces); - SDL_free(bsp->leafs); - SDL_free(bsp->lightdata); - SDL_free(bsp->marksurfaces); - SDL_free(bsp->models); - SDL_free(bsp->nodes); - SDL_free(bsp->planes); - SDL_free(bsp->surfedges); - SDL_free(bsp->texinfo); - SDL_free(bsp->vertices); - SDL_free(bsp->visdata); + free(bsp->edges); + free(bsp->faces); + free(bsp->leafs); + free(bsp->lightdata); + free(bsp->marksurfaces); + free(bsp->models); + free(bsp->nodes); + free(bsp->planes); + free(bsp->surfedges); + free(bsp->texinfo); + free(bsp->vertices); + free(bsp->visdata); memset(bsp, 0, sizeof(*bsp)); } diff --git a/src/pxl8_font.c b/src/pxl8_font.c index f896432..f2b56ac 100644 --- a/src/pxl8_font.c +++ b/src/pxl8_font.c @@ -1,7 +1,6 @@ +#include #include -#include - #include "pxl8_font.h" pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* atlas_width, i32* atlas_height) { @@ -16,7 +15,7 @@ pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* *atlas_height = rows_needed * font->default_height; i32 atlas_size = (*atlas_width) * (*atlas_height); - *atlas_data = (u8*)SDL_malloc(atlas_size); + *atlas_data = (u8*)malloc(atlas_size); if (!*atlas_data) { return PXL8_ERROR_OUT_OF_MEMORY; } diff --git a/src/pxl8_game.h b/src/pxl8_game.h new file mode 100644 index 0000000..3f7325d --- /dev/null +++ b/src/pxl8_game.h @@ -0,0 +1,49 @@ +#pragma once + +#include "pxl8_cart.h" +#include "pxl8_gfx.h" +#include "pxl8_hal.h" +#include "pxl8_script.h" +#include "pxl8_types.h" +#include "pxl8_ui.h" + +typedef enum pxl8_game_result { + PXL8_GAME_CONTINUE, + PXL8_GAME_SUCCESS, + PXL8_GAME_FAILURE +} pxl8_game_result; + +typedef struct pxl8_game { + const pxl8_hal* hal; + + pxl8_cart* cart; + pxl8_color_mode color_mode; + pxl8_gfx* gfx; + pxl8_resolution resolution; + pxl8_script* script; + pxl8_ui* ui; + + i32 frame_count; + u64 last_time; + f32 time; + + bool repl_mode; + bool running; + bool script_loaded; + char script_path[256]; + + pxl8_input_state input; + pxl8_script_repl* repl; +} pxl8_game; + +typedef struct pxl8_game_callbacks { + pxl8_game_result (*init)(pxl8_game* game, i32 argc, char* argv[]); + pxl8_game_result (*update)(pxl8_game* game); + pxl8_game_result (*frame)(pxl8_game* game); + void (*quit)(pxl8_game* game); +} pxl8_game_callbacks; + +pxl8_game_result pxl8_init(pxl8_game* game, i32 argc, char* argv[]); +pxl8_game_result pxl8_update(pxl8_game* game); +pxl8_game_result pxl8_frame(pxl8_game* game); +void pxl8_quit(pxl8_game* game); diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 0276cb9..668dd34 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -2,62 +2,30 @@ #include #include -#include - #include "pxl8_ase.h" +#include "pxl8_atlas.h" #include "pxl8_blit.h" #include "pxl8_font.h" #include "pxl8_gfx.h" +#include "pxl8_hal.h" #include "pxl8_macros.h" #include "pxl8_math.h" #include "pxl8_types.h" -typedef struct pxl8_atlas_entry { - bool active; +typedef struct pxl8_sprite_cache_entry { char path[256]; - u32 texture_id; - - i32 x, y, w, h; -} pxl8_atlas_entry; - -typedef struct pxl8_skyline_fit { - bool found; - u32 node_idx; - pxl8_point pos; -} pxl8_skyline_fit; - -typedef struct pxl8_skyline_node { - i32 x, y, width; -} pxl8_skyline_node; - -typedef struct pxl8_skyline { - pxl8_skyline_node* nodes; - u32 count; - u32 capacity; -} pxl8_skyline; - -struct pxl8_atlas { - u32 height, width; - u8* pixels; - SDL_Texture* texture; - - bool dirty; - - u32 entry_capacity, entry_count; - pxl8_atlas_entry* entries; - - u32 free_capacity, free_count; - u32* free_list; - - pxl8_skyline skyline; -}; + u32 sprite_id; + bool active; +} pxl8_sprite_cache_entry; struct pxl8_gfx { - SDL_Renderer* renderer; - SDL_Texture* framebuffer_texture; - SDL_Window* window; + const pxl8_hal* hal; + void* platform_data; pxl8_atlas* atlas; + pxl8_sprite_cache_entry* sprite_cache; + u32 sprite_cache_capacity; + u32 sprite_cache_count; pxl8_color_mode color_mode; u8* framebuffer; @@ -82,351 +50,6 @@ struct pxl8_gfx { bool affine_textures; }; -static pxl8_skyline_fit pxl8_skyline_find_position( - const pxl8_skyline* skyline, - u32 atlas_w, - u32 atlas_h, - u32 rect_w, - u32 rect_h -) { - pxl8_skyline_fit result = {.found = false}; - i32 best_y = INT32_MAX; - i32 best_x = 0; - u32 best_idx = 0; - - for (u32 i = 0; i < skyline->count; i++) { - i32 x = skyline->nodes[i].x; - i32 y = skyline->nodes[i].y; - - if (x + (i32)rect_w > (i32)atlas_w) continue; - - i32 max_y = y; - for (u32 j = i; j < skyline->count && skyline->nodes[j].x < x + (i32)rect_w; j++) { - if (skyline->nodes[j].y > max_y) { - max_y = skyline->nodes[j].y; - } - } - - if (max_y + (i32)rect_h > (i32)atlas_h) continue; - - if (max_y < best_y || (max_y == best_y && x < best_x)) { - best_y = max_y; - best_x = x; - best_idx = i; - } - } - - if (best_y != INT32_MAX) { - result.found = true; - result.pos.x = best_x; - result.pos.y = best_y; - result.node_idx = best_idx; - } - - return result; -} - -static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w, u32 h) { - u32 node_idx = 0; - for (u32 i = 0; i < skyline->count; i++) { - if (skyline->nodes[i].x == pos.x) { - node_idx = i; - break; - } - } - - u32 nodes_to_remove = 0; - for (u32 i = node_idx; i < skyline->count; i++) { - if (skyline->nodes[i].x < pos.x + (i32)w) { - nodes_to_remove++; - } else { - break; - } - } - - if (skyline->count - nodes_to_remove + 1 > skyline->capacity) { - skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2; - skyline->nodes = (pxl8_skyline_node*)SDL_realloc( - skyline->nodes, - skyline->capacity * sizeof(pxl8_skyline_node) - ); - } - - if (nodes_to_remove > 0) { - SDL_memmove( - &skyline->nodes[node_idx + 1], - &skyline->nodes[node_idx + nodes_to_remove], - (skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node) - ); - } - - skyline->nodes[node_idx] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w}; - skyline->count = skyline->count - nodes_to_remove + 1; -} - -static void pxl8_skyline_compact(pxl8_skyline* skyline) { - for (u32 i = 0; i < skyline->count - 1; ) { - if (skyline->nodes[i].y == skyline->nodes[i + 1].y) { - skyline->nodes[i].width += skyline->nodes[i + 1].width; - SDL_memmove( - &skyline->nodes[i + 1], - &skyline->nodes[i + 2], - (skyline->count - i - 2) * sizeof(pxl8_skyline_node) - ); - skyline->count--; - } else { - i++; - } - } -} - -static pxl8_atlas* pxl8_atlas_create( - SDL_Renderer* renderer, - u32 width, - u32 height, - pxl8_color_mode color_mode -) { - pxl8_atlas* atlas = (pxl8_atlas*)SDL_calloc(1, sizeof(pxl8_atlas)); - if (!atlas) return NULL; - - atlas->height = height; - atlas->width = width; - - i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - atlas->pixels = (u8*)SDL_calloc(width * height, bytes_per_pixel); - if (!atlas->pixels) { - SDL_free(atlas); - return NULL; - } - - atlas->texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - width, - height - ); - - if (!atlas->texture) { - SDL_free(atlas->pixels); - SDL_free(atlas); - return NULL; - } - - atlas->entry_capacity = 64; - atlas->entries = (pxl8_atlas_entry*)SDL_calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry)); - if (!atlas->entries) { - SDL_DestroyTexture(atlas->texture); - SDL_free(atlas->pixels); - SDL_free(atlas); - return NULL; - } - - atlas->free_capacity = 16; - atlas->free_list = (u32*)SDL_calloc(atlas->free_capacity, sizeof(u32)); - if (!atlas->free_list) { - SDL_free(atlas->entries); - SDL_DestroyTexture(atlas->texture); - SDL_free(atlas->pixels); - SDL_free(atlas); - return NULL; - } - - atlas->skyline.capacity = 16; - atlas->skyline.nodes = - (pxl8_skyline_node*)SDL_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node)); - if (!atlas->skyline.nodes) { - SDL_free(atlas->free_list); - SDL_free(atlas->entries); - SDL_DestroyTexture(atlas->texture); - SDL_free(atlas->pixels); - SDL_free(atlas); - return NULL; - } - - atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)width}; - atlas->skyline.count = 1; - - return atlas; -} - -static void pxl8_atlas_destroy(pxl8_atlas* atlas) { - if (!atlas) return; - - SDL_free(atlas->entries); - SDL_free(atlas->free_list); - SDL_free(atlas->pixels); - SDL_free(atlas->skyline.nodes); - if (atlas->texture) SDL_DestroyTexture(atlas->texture); - SDL_free(atlas); -} - -static bool pxl8_atlas_expand( - pxl8_atlas* atlas, - SDL_Renderer* renderer, - pxl8_color_mode color_mode -) { - if (!atlas || atlas->width >= 4096) return false; - - u32 new_size = atlas->width * 2; - u32 old_width = atlas->width; - i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - - u8* new_pixels = (u8*)SDL_calloc(new_size * new_size, bytes_per_pixel); - if (!new_pixels) return false; - - SDL_Texture* new_texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - new_size, - new_size - ); - - if (!new_texture) { - SDL_free(new_pixels); - return false; - } - - pxl8_skyline new_skyline; - new_skyline.nodes = (pxl8_skyline_node*)SDL_calloc(16, sizeof(pxl8_skyline_node)); - if (!new_skyline.nodes) { - SDL_DestroyTexture(new_texture); - SDL_free(new_pixels); - return false; - } - - new_skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)new_size}; - new_skyline.count = 1; - new_skyline.capacity = 16; - - for (u32 i = 0; i < atlas->entry_count; i++) { - if (!atlas->entries[i].active) continue; - - pxl8_skyline_fit fit = pxl8_skyline_find_position( - &new_skyline, - new_size, - new_size, - atlas->entries[i].w, - atlas->entries[i].h - ); - - if (!fit.found) { - SDL_free(new_skyline.nodes); - SDL_DestroyTexture(new_texture); - SDL_free(new_pixels); - return false; - } - - for (u32 y = 0; y < (u32)atlas->entries[i].h; y++) { - for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) { - u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x); - u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x); - if (bytes_per_pixel == 4) { - ((u32*)new_pixels)[dst_idx] = ((u32*)atlas->pixels)[src_idx]; - } else { - new_pixels[dst_idx] = atlas->pixels[src_idx]; - } - } - } - - atlas->entries[i].x = fit.pos.x; - atlas->entries[i].y = fit.pos.y; - - pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h); - pxl8_skyline_compact(&new_skyline); - } - - SDL_DestroyTexture(atlas->texture); - SDL_free(atlas->pixels); - SDL_free(atlas->skyline.nodes); - - atlas->pixels = new_pixels; - atlas->texture = new_texture; - atlas->skyline = new_skyline; - atlas->width = new_size; - atlas->height = new_size; - atlas->dirty = true; - - pxl8_debug("Atlas expanded to %ux%u", atlas->width, atlas->height); - return true; -} - -static u32 pxl8_atlas_add_texture( - pxl8_atlas* atlas, - const u8* pixels, - u32 w, - u32 h, - const char* path, - pxl8_color_mode color_mode -) { - if (!atlas || !pixels) return UINT32_MAX; - - pxl8_skyline_fit fit = - pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); - if (!fit.found) { - SDL_Renderer* renderer = atlas->texture ? SDL_GetRendererFromTexture(atlas->texture) : NULL; - - if (!pxl8_atlas_expand(atlas, renderer, color_mode)) { - return UINT32_MAX; - } - - fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); - - if (!fit.found) return UINT32_MAX; - } - - u32 texture_id; - if (atlas->free_count > 0) { - texture_id = atlas->free_list[--atlas->free_count]; - } else { - if (atlas->entry_count >= atlas->entry_capacity) { - atlas->entry_capacity *= 2; - atlas->entries = (pxl8_atlas_entry*)SDL_realloc( - atlas->entries, - atlas->entry_capacity * sizeof(pxl8_atlas_entry) - ); - } - texture_id = atlas->entry_count++; - } - - pxl8_atlas_entry* entry = &atlas->entries[texture_id]; - entry->active = true; - entry->texture_id = texture_id; - entry->x = fit.pos.x; - entry->y = fit.pos.y; - entry->w = w; - entry->h = h; - - if (path) { - strncpy(entry->path, path, sizeof(entry->path) - 1); - entry->path[sizeof(entry->path) - 1] = '\0'; - } else { - snprintf(entry->path, sizeof(entry->path), "procgen_%u", texture_id); - } - - i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - for (u32 y = 0; y < h; y++) { - for (u32 x = 0; x < w; x++) { - u32 src_idx = y * w + x; - u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x); - - if (bytes_per_pixel == 4) { - ((u32*)atlas->pixels)[dst_idx] = ((const u32*)pixels)[src_idx]; - } else { - atlas->pixels[dst_idx] = pixels[src_idx]; - } - } - } - - pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h); - pxl8_skyline_compact(&atlas->skyline); - - atlas->dirty = true; - - return texture_id; -} - static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { *r = color & 0xFF; *g = (color >> 8) & 0xFF; @@ -448,7 +71,7 @@ static u32 pxl8_get_palette_size(pxl8_color_mode mode) { case PXL8_COLOR_MODE_FAMI: return 64; case PXL8_COLOR_MODE_MEGA: return 512; case PXL8_COLOR_MODE_GBA: return 32768; - case PXL8_COLOR_MODE_SUPERFAMI: return 32768; + case PXL8_COLOR_MODE_SNES: return 32768; default: return 256; } } @@ -476,11 +99,11 @@ void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { pxl8_bounds bounds = {0}; - if (!gfx || !gfx->window) { + if (!gfx) { return bounds; } - SDL_GetWindowPosition(gfx->window, &bounds.x, &bounds.y); - SDL_GetWindowSize(gfx->window, &bounds.w, &bounds.h); + bounds.w = gfx->framebuffer_width; + bounds.h = gfx->framebuffer_height; return bounds; } @@ -505,18 +128,21 @@ u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) { } pxl8_gfx* pxl8_gfx_create( + const pxl8_hal* hal, pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height ) { - pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx)); + pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(*gfx)); if (!gfx) { pxl8_error("Failed to allocate graphics context"); return NULL; } + gfx->hal = hal; + gfx->color_mode = mode; pxl8_gfx_get_resolution_dimensions( resolution, @@ -524,60 +150,25 @@ pxl8_gfx* pxl8_gfx_create( &gfx->framebuffer_height ); - gfx->window = SDL_CreateWindow( - title, - window_width, window_height, - SDL_WINDOW_RESIZABLE - ); - - if (!gfx->window) { - pxl8_error("Failed to create window: %s", SDL_GetError()); - pxl8_gfx_destroy(gfx); + gfx->platform_data = gfx->hal->create(mode, resolution, title, window_width, window_height); + if (!gfx->platform_data) { + pxl8_error("Failed to create platform context"); + free(gfx); return NULL; } - gfx->renderer = SDL_CreateRenderer(gfx->window, NULL); - if (!gfx->renderer) { - pxl8_error("Failed to create renderer: %s", SDL_GetError()); - pxl8_gfx_destroy(gfx); - return NULL; - } - - SDL_SetRenderLogicalPresentation( - gfx->renderer, - gfx->framebuffer_width, - gfx->framebuffer_height, - SDL_LOGICAL_PRESENTATION_INTEGER_SCALE - ); - i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel; - gfx->framebuffer = (u8*)SDL_calloc(1, fb_size); + gfx->framebuffer = (u8*)calloc(1, fb_size); if (!gfx->framebuffer) { pxl8_error("Failed to allocate framebuffer"); pxl8_gfx_destroy(gfx); return NULL; } - gfx->framebuffer_texture = SDL_CreateTexture( - gfx->renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - gfx->framebuffer_width, - gfx->framebuffer_height - ); - - SDL_SetTextureScaleMode(gfx->framebuffer_texture, SDL_SCALEMODE_NEAREST); - - if (!gfx->framebuffer_texture) { - pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError()); - pxl8_gfx_destroy(gfx); - return NULL; - } - gfx->palette_size = pxl8_get_palette_size(mode); if (gfx->palette_size > 0) { - gfx->palette = (u32*)SDL_calloc(gfx->palette_size, sizeof(u32)); + gfx->palette = (u32*)calloc(gfx->palette_size, sizeof(u32)); if (!gfx->palette) { pxl8_error("Failed to allocate palette"); pxl8_gfx_destroy(gfx); @@ -614,27 +205,17 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) { if (!gfx) return; pxl8_atlas_destroy(gfx->atlas); + free(gfx->sprite_cache); - if (gfx->framebuffer_texture) { - SDL_DestroyTexture(gfx->framebuffer_texture); - gfx->framebuffer_texture = NULL; + if (gfx->hal && gfx->platform_data) { + gfx->hal->destroy(gfx->platform_data); } - if (gfx->renderer) { - SDL_DestroyRenderer(gfx->renderer); - gfx->renderer = NULL; - } + free(gfx->framebuffer); + free(gfx->palette); + free(gfx->zbuffer); - if (gfx->window) { - SDL_DestroyWindow(gfx->window); - gfx->window = NULL; - } - - SDL_free(gfx->framebuffer); - SDL_free(gfx->palette); - SDL_free(gfx->zbuffer); - - SDL_free(gfx); + free(gfx); } @@ -642,41 +223,41 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT; if (!gfx->atlas) { - gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode); + gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode); if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY; } - u32 texture_id = - pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode); + u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->color_mode); if (texture_id == UINT32_MAX) { pxl8_error("Texture doesn't fit in atlas"); return PXL8_ERROR_INVALID_SIZE; } - pxl8_debug( - "Created texture %u: %ux%u at (%d,%d)", - texture_id, width, height, - gfx->atlas->entries[texture_id].x, - gfx->atlas->entries[texture_id].y - ); - return texture_id; } pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; - if (!gfx->atlas) { - gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode); - if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY; + if (!gfx->sprite_cache) { + gfx->sprite_cache_capacity = 64; + gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc( + gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry) + ); + if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY; } - for (u32 i = 0; i < gfx->atlas->entry_count; i++) { - if (gfx->atlas->entries[i].active && strcmp(gfx->atlas->entries[i].path, path) == 0) { - return gfx->atlas->entries[i].texture_id; + for (u32 i = 0; i < gfx->sprite_cache_count; i++) { + if (gfx->sprite_cache[i].active && strcmp(gfx->sprite_cache[i].path, path) == 0) { + return gfx->sprite_cache[i].sprite_id; } } + if (!gfx->atlas) { + gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode); + if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY; + } + pxl8_ase_file ase_file; pxl8_result result = pxl8_ase_load(path, &ase_file); if (result != PXL8_OK) { @@ -690,31 +271,46 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { return PXL8_ERROR_INVALID_FORMAT; } - u32 sprite_w = ase_file.header.width; - u32 sprite_h = ase_file.header.height; - - u32 texture_id = pxl8_atlas_add_texture( + u32 sprite_id = pxl8_atlas_add_texture( gfx->atlas, ase_file.frames[0].pixels, - sprite_w, - sprite_h, - path, + ase_file.header.width, + ase_file.header.height, gfx->color_mode ); pxl8_ase_destroy(&ase_file); - if (texture_id == UINT32_MAX) { + if (sprite_id == UINT32_MAX) { pxl8_error("Sprite doesn't fit in atlas"); return PXL8_ERROR_INVALID_SIZE; } - pxl8_debug("Loaded sprite %u: %ux%u at (%d,%d)", - texture_id, sprite_w, sprite_h, - gfx->atlas->entries[texture_id].x, - gfx->atlas->entries[texture_id].y); + if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) { + gfx->sprite_cache_capacity *= 2; + gfx->sprite_cache = (pxl8_sprite_cache_entry*)realloc( + gfx->sprite_cache, + gfx->sprite_cache_capacity * sizeof(pxl8_sprite_cache_entry) + ); + } - return texture_id; + pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++]; + entry->active = true; + entry->sprite_id = sprite_id; + strncpy(entry->path, path, sizeof(entry->path) - 1); + entry->path[sizeof(entry->path) - 1] = '\0'; + + return sprite_id; +} + +pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized) return NULL; + + if (!gfx->atlas) { + gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode); + } + + return gfx->atlas; } pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) { @@ -758,94 +354,32 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { return PXL8_OK; } -static void pxl8_upload_indexed_texture( - SDL_Texture* texture, - const u8* indexed, - const u32* palette, - u32 palette_size, - i32 width, - i32 height, - u32 default_color -) { - static u32* rgba_buffer = NULL; - static size_t buffer_size = 0; - size_t needed_size = width * height; +void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized || !gfx->atlas) return; - if (buffer_size < needed_size) { - rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4); - buffer_size = needed_size; + if (gfx->hal && gfx->hal->upload_atlas) { + gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->color_mode); + pxl8_atlas_mark_clean(gfx->atlas); } - - if (!rgba_buffer) return; - - for (i32 i = 0; i < width * height; i++) { - u8 index = indexed[i]; - rgba_buffer[i] = (index < palette_size) ? palette[index] : default_color; - } - - SDL_UpdateTexture(texture, NULL, rgba_buffer, width * 4); } void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return; + if (!gfx || !gfx->initialized || !gfx->hal) return; - if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - SDL_UpdateTexture( - gfx->framebuffer_texture, - NULL, - gfx->framebuffer, - gfx->framebuffer_width * 4 - ); - } else { - pxl8_upload_indexed_texture( - gfx->framebuffer_texture, - gfx->framebuffer, - gfx->palette, - gfx->palette_size, - gfx->framebuffer_width, - gfx->framebuffer_height, - 0xFF000000 - ); - } -} - -void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized || !gfx->atlas || !gfx->atlas->texture || !gfx->atlas->dirty) return; - - if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - SDL_UpdateTexture( - gfx->atlas->texture, - NULL, - gfx->atlas->pixels, - gfx->atlas->width * 4 - ); - } else { - pxl8_upload_indexed_texture( - gfx->atlas->texture, - gfx->atlas->pixels, - gfx->palette, - gfx->palette_size, - gfx->atlas->width, - gfx->atlas->height, - 0x00000000 - ); - } - - gfx->atlas->dirty = false; - pxl8_debug("Atlas uploaded to GPU"); + gfx->hal->upload_framebuffer( + gfx->platform_data, + gfx->framebuffer, + gfx->framebuffer_width, + gfx->framebuffer_height, + gfx->palette, + gfx->color_mode + ); } void pxl8_gfx_present(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized) return; - - SDL_SetRenderDrawColor(gfx->renderer, 0, 0, 0, 255); - SDL_RenderClear(gfx->renderer); - - if (gfx->framebuffer_texture) { - SDL_RenderTexture(gfx->renderer, gfx->framebuffer_texture, NULL, NULL); - } - - SDL_RenderPresent(gfx->renderer); + if (!gfx || !gfx->initialized || !gfx->hal) return; + + gfx->hal->present(gfx->platform_data); } pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) { @@ -1057,10 +591,10 @@ void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { - if (!gfx || !gfx->atlas || !gfx->framebuffer || sprite_id >= gfx->atlas->entry_count) return; - if (!gfx->atlas->entries[sprite_id].active) return; + if (!gfx || !gfx->atlas || !gfx->framebuffer) return; - pxl8_atlas_entry* entry = &gfx->atlas->entries[sprite_id]; + const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, sprite_id); + if (!entry || !entry->active) return; i32 clip_left = (x < 0) ? -x : 0; i32 clip_top = (y < 0) ? -y : 0; @@ -1074,19 +608,22 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { i32 dest_x = x + clip_left; i32 dest_y = y + clip_top; - + bool is_1to1_scale = (w == entry->w && h == entry->h); bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0); - + + u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); + const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); + if (is_1to1_scale && is_unclipped && pxl8_is_simd_aligned(w)) { - const u8* sprite_data = gfx->atlas->pixels + entry->y * gfx->atlas->width + entry->x; + const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { pxl8_blit_simd_hicolor( (u32*)gfx->framebuffer, gfx->framebuffer_width, (const u32*)sprite_data, - gfx->atlas->width, + atlas_width, x, y, w, h ); } else { @@ -1094,7 +631,7 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { gfx->framebuffer, gfx->framebuffer_width, sprite_data, - gfx->atlas->width, + atlas_width, x, y, w, h ); } @@ -1103,16 +640,16 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { for (i32 px = 0; px < draw_width; px++) { i32 src_x = entry->x + ((px + clip_left) * entry->w) / w; i32 src_y = entry->y + ((py + clip_top) * entry->h) / h; - i32 src_idx = src_y * gfx->atlas->width + src_x; + i32 src_idx = src_y * atlas_width + src_x; i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px); if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - u32 pixel = ((u32*)gfx->atlas->pixels)[src_idx]; + u32 pixel = ((const u32*)atlas_pixels)[src_idx]; if (pixel & 0xFF000000) { ((u32*)gfx->framebuffer)[dest_idx] = pixel; } } else { - u8 pixel = gfx->atlas->pixels[src_idx]; + u8 pixel = atlas_pixels[src_idx]; if (pixel != 0) { gfx->framebuffer[dest_idx] = pixel; } @@ -1238,7 +775,7 @@ static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) { gfx->zbuffer_width = gfx->framebuffer_width; gfx->zbuffer_height = gfx->framebuffer_height; - gfx->zbuffer = (f32*)SDL_calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32)); + gfx->zbuffer = (f32*)calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32)); if (!gfx->zbuffer) { return false; } @@ -1349,10 +886,10 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 } static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 v) { - if (!gfx->atlas || texture_id >= gfx->atlas->entry_count) return 0; - if (!gfx->atlas->entries[texture_id].active) return 0; + if (!gfx->atlas) return 0; - pxl8_atlas_entry* entry = &gfx->atlas->entries[texture_id]; + const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id); + if (!entry || !entry->active) return 0; u = u - floorf(u); v = v - floorf(v); @@ -1362,12 +899,14 @@ static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 i32 atlas_x = entry->x + tx; i32 atlas_y = entry->y + ty; - i32 idx = atlas_y * gfx->atlas->width + atlas_x; + u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); + const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); + i32 idx = atlas_y * atlas_width + atlas_x; if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - return ((u32*)gfx->atlas->pixels)[idx]; + return ((const u32*)atlas_pixels)[idx]; } else { - return gfx->atlas->pixels[idx]; + return atlas_pixels[idx]; } } diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index e40ebad..f989445 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -1,5 +1,6 @@ #pragma once +#include "pxl8_hal.h" #include "pxl8_math.h" #include "pxl8_types.h" @@ -56,7 +57,7 @@ typedef struct pxl8_triangle { extern "C" { #endif -pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height); +pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height); void pxl8_gfx_destroy(pxl8_gfx* gfx); pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); diff --git a/src/pxl8_hal.h b/src/pxl8_hal.h new file mode 100644 index 0000000..9868082 --- /dev/null +++ b/src/pxl8_hal.h @@ -0,0 +1,20 @@ +#pragma once + +#include "pxl8_types.h" + +typedef struct pxl8_atlas pxl8_atlas; + +typedef struct pxl8_hal { + void* (*create)(pxl8_color_mode mode, pxl8_resolution res, + const char* title, i32 win_w, i32 win_h); + void (*destroy)(void* platform_data); + + u64 (*get_ticks)(void); + + void (*present)(void* platform_data); + void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas, + const u32* palette, pxl8_color_mode mode); + void (*upload_framebuffer)(void* platform_data, const u8* fb, + i32 w, i32 h, const u32* palette, + pxl8_color_mode mode); +} pxl8_hal; diff --git a/src/pxl8_io.c b/src/pxl8_io.c index 6822913..f9a4b5b 100644 --- a/src/pxl8_io.c +++ b/src/pxl8_io.c @@ -1,7 +1,9 @@ #include -#include +#include +#include #include "pxl8_io.h" +#include "pxl8_types.h" pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) { if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER; @@ -20,7 +22,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) { return PXL8_ERROR_SYSTEM_FAILURE; } - *content = SDL_malloc(file_size + 1); + *content = malloc(file_size + 1); if (!*content) { fclose(file); return PXL8_ERROR_OUT_OF_MEMORY; @@ -88,21 +90,48 @@ pxl8_result pxl8_io_create_directory(const char* path) { void pxl8_io_free_file_content(char* content) { if (content) { - SDL_free(content); + free(content); } } void pxl8_io_free_binary_data(u8* data) { if (data) { - SDL_free(data); + free(data); } } static i32 pxl8_key_code(const char* key_name) { if (!key_name || !key_name[0]) return 0; - SDL_Scancode scancode = SDL_GetScancodeFromName(key_name); - return (i32)scancode; + typedef struct { const char* name; i32 code; } KeyMapping; + static const KeyMapping keys[] = { + {"a", 4}, {"b", 5}, {"c", 6}, {"d", 7}, {"e", 8}, {"f", 9}, {"g", 10}, {"h", 11}, + {"i", 12}, {"j", 13}, {"k", 14}, {"l", 15}, {"m", 16}, {"n", 17}, {"o", 18}, {"p", 19}, + {"q", 20}, {"r", 21}, {"s", 22}, {"t", 23}, {"u", 24}, {"v", 25}, {"w", 26}, {"x", 27}, + {"y", 28}, {"z", 29}, + {"1", 30}, {"2", 31}, {"3", 32}, {"4", 33}, {"5", 34}, + {"6", 35}, {"7", 36}, {"8", 37}, {"9", 38}, {"0", 39}, + {"return", 40}, {"escape", 41}, {"backspace", 42}, {"tab", 43}, {"space", 44}, + {"left", 80}, {"right", 79}, {"up", 82}, {"down", 81}, + {"f1", 58}, {"f2", 59}, {"f3", 60}, {"f4", 61}, {"f5", 62}, {"f6", 63}, + {"f7", 64}, {"f8", 65}, {"f9", 66}, {"f10", 67}, {"f11", 68}, {"f12", 69}, + {NULL, 0} + }; + + char lower_name[64]; + size_t i; + for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) { + lower_name[i] = (char)tolower((unsigned char)key_name[i]); + } + lower_name[i] = '\0'; + + for (i = 0; keys[i].name; i++) { + if (strcmp(lower_name, keys[i].name) == 0) { + return keys[i].code; + } + } + + return 0; } bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) { diff --git a/src/pxl8_script.c b/src/pxl8_script.c index f4390a9..816a6fb 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -7,7 +7,6 @@ #include #include #include -#include #include "pxl8_macros.h" #include "pxl8_script.h" @@ -222,12 +221,12 @@ const char* pxl8_script_get_last_error(pxl8_script* script) { } pxl8_script* pxl8_script_create(void) { - pxl8_script* script = SDL_calloc(1, sizeof(pxl8_script)); + pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script)); if (!script) return NULL; script->L = luaL_newstate(); if (!script->L) { - SDL_free(script); + free(script); return NULL; } @@ -285,7 +284,7 @@ void pxl8_script_destroy(pxl8_script* script) { if (script->L) { lua_close(script->L); } - SDL_free(script); + free(script); } void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) { @@ -600,3 +599,175 @@ bool pxl8_script_check_reload(pxl8_script* script) { return false; } + +#include +#include +#include + +#define PXL8_MAX_REPL_COMMANDS 4096 + +struct pxl8_script_repl_command { + char buffer[PXL8_MAX_REPL_COMMANDS]; + struct pxl8_script_repl_command* next; +}; + +struct pxl8_script_repl { + pthread_t thread; + pthread_mutex_t mutex; + pxl8_script_repl_command* queue_head; + pxl8_script_repl_command* queue_tail; + bool running; +}; + +static void pxl8_script_repl_completion(const char* buf, linenoiseCompletions* lc) { + const char* fennel_keywords[] = { + "fn", "let", "var", "set", "global", "local", + "if", "when", "do", "while", "for", "each", + "lambda", "λ", "partial", "macro", "macros", + "require", "include", "import-macros", + "values", "select", "table", "length", + ".", "..", ":", "->", "->>", "-?>", "-?>>", + "doto", "match", "case", "pick-values", + "collect", "icollect", "accumulate" + }; + + const char* pxl8_functions[] = { + "pxl8.clr", "pxl8.pixel", "pxl8.get_pixel", + "pxl8.line", "pxl8.rect", "pxl8.rect_fill", + "pxl8.circle", "pxl8.circle_fill", "pxl8.text", + "pxl8.get_screen", "pxl8.info", "pxl8.warn", + "pxl8.error", "pxl8.debug", "pxl8.trace" + }; + + size_t buf_len = strlen(buf); + + for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) { + if (strncmp(buf, fennel_keywords[i], buf_len) == 0) { + linenoiseAddCompletion(lc, fennel_keywords[i]); + } + } + + for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) { + if (strncmp(buf, pxl8_functions[i], buf_len) == 0) { + linenoiseAddCompletion(lc, pxl8_functions[i]); + } + } +} + +static char* pxl8_script_repl_hints(const char* buf, int* color, int* bold) { + if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) { + *color = 35; + *bold = 0; + return "clr|pixel|line|rect|circle|text|get_screen"; + } + + if (strcmp(buf, "(fn") == 0) { + *color = 36; + *bold = 0; + return " [args] body)"; + } + + if (strcmp(buf, "(let") == 0) { + *color = 36; + *bold = 0; + return " [bindings] body)"; + } + + return NULL; +} + +static void* pxl8_script_repl_stdin_thread(void* user_data) { + pxl8_script_repl* repl = (pxl8_script_repl*)user_data; + char* line; + const char* history_file = ".pxl8_history"; + + linenoiseHistorySetMaxLen(100); + linenoiseSetMultiLine(1); + linenoiseSetCompletionCallback(pxl8_script_repl_completion); + linenoiseSetHintsCallback(pxl8_script_repl_hints); + linenoiseHistoryLoad(history_file); + + while (repl->running && (line = linenoise(">> "))) { + if (strlen(line) > 0) { + linenoiseHistoryAdd(line); + linenoiseHistorySave(history_file); + + pxl8_script_repl_command* cmd = (pxl8_script_repl_command*)malloc(sizeof(pxl8_script_repl_command)); + if (cmd) { + strncpy(cmd->buffer, line, PXL8_MAX_REPL_COMMANDS - 1); + cmd->buffer[PXL8_MAX_REPL_COMMANDS - 1] = '\0'; + cmd->next = NULL; + + pthread_mutex_lock(&repl->mutex); + if (repl->queue_tail) { + repl->queue_tail->next = cmd; + repl->queue_tail = cmd; + } else { + repl->queue_head = repl->queue_tail = cmd; + } + pthread_mutex_unlock(&repl->mutex); + } + } + linenoiseFree(line); + } + + return NULL; +} + +pxl8_script_repl* pxl8_script_repl_create(void) { + pxl8_script_repl* repl = (pxl8_script_repl*)calloc(1, sizeof(pxl8_script_repl)); + return repl; +} + +void pxl8_script_repl_destroy(pxl8_script_repl* repl) { + if (!repl) return; + free(repl); +} + +void pxl8_script_repl_init(pxl8_script_repl* repl) { + if (!repl) return; + + repl->queue_head = NULL; + repl->queue_tail = NULL; + repl->running = true; + pthread_mutex_init(&repl->mutex, NULL); + pthread_create(&repl->thread, NULL, pxl8_script_repl_stdin_thread, repl); +} + +void pxl8_script_repl_shutdown(pxl8_script_repl* repl) { + if (!repl) return; + + repl->running = false; + pthread_join(repl->thread, NULL); + pthread_mutex_destroy(&repl->mutex); + + pxl8_script_repl_command* cmd = repl->queue_head; + while (cmd) { + pxl8_script_repl_command* next = cmd->next; + free(cmd); + cmd = next; + } +} + +pxl8_script_repl_command* pxl8_script_repl_pop_command(pxl8_script_repl* repl) { + if (!repl) return NULL; + + pthread_mutex_lock(&repl->mutex); + pxl8_script_repl_command* cmd = repl->queue_head; + if (cmd) { + repl->queue_head = cmd->next; + if (!repl->queue_head) { + repl->queue_tail = NULL; + } + } + pthread_mutex_unlock(&repl->mutex); + return cmd; +} + +const char* pxl8_script_repl_command_buffer(pxl8_script_repl_command* cmd) { + return cmd ? cmd->buffer : NULL; +} + +void pxl8_script_repl_command_free(pxl8_script_repl_command* cmd) { + free(cmd); +} diff --git a/src/pxl8_script.h b/src/pxl8_script.h index aa3479f..8f96a2c 100644 --- a/src/pxl8_script.h +++ b/src/pxl8_script.h @@ -5,6 +5,8 @@ #include "pxl8_ui.h" typedef struct pxl8_script pxl8_script; +typedef struct pxl8_script_repl pxl8_script_repl; +typedef struct pxl8_script_repl_command pxl8_script_repl_command; #ifdef __cplusplus extern "C" { @@ -29,6 +31,14 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename); pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path); bool pxl8_script_check_reload(pxl8_script* script); +pxl8_script_repl* pxl8_script_repl_create(void); +void pxl8_script_repl_destroy(pxl8_script_repl* repl); +void pxl8_script_repl_init(pxl8_script_repl* repl); +void pxl8_script_repl_shutdown(pxl8_script_repl* repl); +pxl8_script_repl_command* pxl8_script_repl_pop_command(pxl8_script_repl* repl); +const char* pxl8_script_repl_command_buffer(pxl8_script_repl_command* cmd); +void pxl8_script_repl_command_free(pxl8_script_repl_command* cmd); + #ifdef __cplusplus } #endif diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c new file mode 100644 index 0000000..ed0f20a --- /dev/null +++ b/src/pxl8_sdl3.c @@ -0,0 +1,392 @@ +#include "pxl8_sdl3.h" +#include "pxl8_atlas.h" +#include "pxl8_game.h" +#include "pxl8_macros.h" + +#define SDL_MAIN_USE_CALLBACKS +#include +#include + +typedef struct pxl8_sdl3_context { + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* framebuffer_texture; + SDL_Texture* atlas_texture; + + u32* rgba_buffer; + size_t rgba_buffer_size; + + u32 atlas_width; + u32 atlas_height; +} pxl8_sdl3_context; + +static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution, + const char* title, i32 win_w, i32 win_h) { + (void)mode; + + pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(*ctx)); + if (!ctx) { + pxl8_error("Failed to allocate SDL3 context"); + return NULL; + } + + i32 fb_w, fb_h; + switch (resolution) { + case PXL8_RESOLUTION_240x160: fb_w = 240; fb_h = 160; break; + case PXL8_RESOLUTION_320x180: fb_w = 320; fb_h = 180; break; + case PXL8_RESOLUTION_320x240: fb_w = 320; fb_h = 240; break; + case PXL8_RESOLUTION_640x360: fb_w = 640; fb_h = 360; break; + case PXL8_RESOLUTION_640x480: fb_w = 640; fb_h = 480; break; + case PXL8_RESOLUTION_800x600: fb_w = 800; fb_h = 600; break; + case PXL8_RESOLUTION_960x540: fb_w = 960; fb_h = 540; break; + default: fb_w = 640; fb_h = 360; break; + } + + ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE); + if (!ctx->window) { + pxl8_error("Failed to create window: %s", SDL_GetError()); + SDL_free(ctx); + return NULL; + } + + ctx->renderer = SDL_CreateRenderer(ctx->window, NULL); + if (!ctx->renderer) { + pxl8_error("Failed to create renderer: %s", SDL_GetError()); + SDL_DestroyWindow(ctx->window); + SDL_free(ctx); + return NULL; + } + + SDL_SetRenderLogicalPresentation(ctx->renderer, fb_w, fb_h, + SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); + + ctx->framebuffer_texture = SDL_CreateTexture(ctx->renderer, + SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STREAMING, + fb_w, fb_h); + if (!ctx->framebuffer_texture) { + pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError()); + SDL_DestroyRenderer(ctx->renderer); + SDL_DestroyWindow(ctx->window); + SDL_free(ctx); + return NULL; + } + + SDL_SetTextureScaleMode(ctx->framebuffer_texture, SDL_SCALEMODE_NEAREST); + + ctx->rgba_buffer = NULL; + ctx->rgba_buffer_size = 0; + + return ctx; +} + +static void sdl3_destroy(void* platform_data) { + if (!platform_data) return; + + pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; + + if (ctx->rgba_buffer) { + SDL_free(ctx->rgba_buffer); + } + if (ctx->atlas_texture) { + 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); +} + +static u64 sdl3_get_ticks(void) { + return SDL_GetTicksNS(); +} + + +static void sdl3_present(void* platform_data) { + if (!platform_data) return; + + pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; + + SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); + SDL_RenderClear(ctx->renderer); + + if (ctx->framebuffer_texture) { + SDL_RenderTexture(ctx->renderer, ctx->framebuffer_texture, NULL, NULL); + } + + SDL_RenderPresent(ctx->renderer); +} + +static void sdl3_upload_framebuffer(void* platform_data, const u8* fb, + i32 w, i32 h, const u32* palette, + pxl8_color_mode mode) { + if (!platform_data || !fb) return; + + pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; + + if (mode == PXL8_COLOR_MODE_HICOLOR) { + SDL_UpdateTexture(ctx->framebuffer_texture, NULL, fb, w * 4); + } else { + size_t needed_size = w * h; + + if (ctx->rgba_buffer_size < needed_size) { + ctx->rgba_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4); + ctx->rgba_buffer_size = needed_size; + } + + if (!ctx->rgba_buffer) return; + + u32 palette_size; + switch (mode) { + case PXL8_COLOR_MODE_FAMI: palette_size = 64; break; + case PXL8_COLOR_MODE_MEGA: palette_size = 512; break; + case PXL8_COLOR_MODE_GBA: palette_size = 32768; break; + case PXL8_COLOR_MODE_SNES: palette_size = 32768; break; + default: palette_size = 256; break; + } + + for (i32 i = 0; i < w * h; i++) { + u8 index = fb[i]; + ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0xFF000000; + } + + SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->rgba_buffer, w * 4); + } +} + +static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas, + const u32* palette, pxl8_color_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; + } + + if (mode == PXL8_COLOR_MODE_HICOLOR) { + SDL_UpdateTexture(ctx->atlas_texture, NULL, atlas_pixels, atlas_w * 4); + } else { + size_t needed_size = atlas_w * atlas_h; + + if (ctx->rgba_buffer_size < needed_size) { + ctx->rgba_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4); + ctx->rgba_buffer_size = needed_size; + } + + if (!ctx->rgba_buffer) return; + + u32 palette_size; + switch (mode) { + case PXL8_COLOR_MODE_FAMI: palette_size = 64; break; + case PXL8_COLOR_MODE_MEGA: palette_size = 512; break; + case PXL8_COLOR_MODE_GBA: palette_size = 32768; break; + case PXL8_COLOR_MODE_SNES: palette_size = 32768; break; + default: palette_size = 256; break; + } + + for (u32 i = 0; i < atlas_w * atlas_h; i++) { + u8 index = atlas_pixels[i]; + ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0x00000000; + } + + SDL_UpdateTexture(ctx->atlas_texture, NULL, ctx->rgba_buffer, atlas_w * 4); + } +} + +SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + pxl8_error("SDL_Init failed: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + pxl8_game* game = (pxl8_game*)SDL_calloc(1, sizeof(pxl8_game)); + if (!game) { + pxl8_error("Failed to allocate game instance"); + return SDL_APP_FAILURE; + } + + pxl8_game_result result = pxl8_init(game, argc, argv); + + *appstate = game; + + switch (result) { + case PXL8_GAME_CONTINUE: + case PXL8_GAME_SUCCESS: + return SDL_APP_CONTINUE; + case PXL8_GAME_FAILURE: + default: + return SDL_APP_FAILURE; + } +} + +SDL_AppResult SDL_AppIterate(void* appstate) { + pxl8_game* game = (pxl8_game*)appstate; + + if (!game) { + return SDL_APP_FAILURE; + } + + pxl8_game_result update_result = pxl8_update(game); + if (update_result == PXL8_GAME_FAILURE) { + return SDL_APP_FAILURE; + } + if (update_result == PXL8_GAME_SUCCESS) { + return SDL_APP_SUCCESS; + } + + pxl8_game_result frame_result = pxl8_frame(game); + if (frame_result == PXL8_GAME_FAILURE) { + return SDL_APP_FAILURE; + } + + return (frame_result == PXL8_GAME_SUCCESS) ? SDL_APP_SUCCESS : SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { + pxl8_game* game = (pxl8_game*)appstate; + + if (!game) { + return SDL_APP_CONTINUE; + } + + switch (event->type) { + case SDL_EVENT_QUIT: + game->running = false; + break; + + case SDL_EVENT_KEY_DOWN: { + if (event->key.key == SDLK_ESCAPE) { + game->running = false; + } + + SDL_Scancode scancode = event->key.scancode; + if (scancode < 256) { + if (!game->input.keys_down[scancode]) { + game->input.keys_pressed[scancode] = true; + } + game->input.keys_down[scancode] = true; + game->input.keys_released[scancode] = false; + } + break; + } + + case SDL_EVENT_KEY_UP: { + SDL_Scancode scancode = event->key.scancode; + if (scancode < 256) { + game->input.keys_down[scancode] = false; + game->input.keys_pressed[scancode] = false; + game->input.keys_released[scancode] = true; + } + break; + } + + case SDL_EVENT_MOUSE_BUTTON_DOWN: { + u8 button = event->button.button - 1; + if (button < 3) { + if (!game->input.mouse_buttons_down[button]) { + game->input.mouse_buttons_pressed[button] = true; + } + game->input.mouse_buttons_down[button] = true; + game->input.mouse_buttons_released[button] = false; + } + break; + } + + case SDL_EVENT_MOUSE_BUTTON_UP: { + u8 button = event->button.button - 1; + if (button < 3) { + game->input.mouse_buttons_down[button] = false; + game->input.mouse_buttons_pressed[button] = false; + game->input.mouse_buttons_released[button] = true; + } + break; + } + + case SDL_EVENT_MOUSE_MOTION: { + if (!game->gfx) break; + + i32 window_mouse_x = (i32)event->motion.x; + i32 window_mouse_y = (i32)event->motion.y; + + SDL_Window* window = SDL_GetWindowFromID(event->motion.windowID); + if (!window) break; + + i32 window_width, window_height; + SDL_GetWindowSize(window, &window_width, &window_height); + + i32 render_width, render_height; + pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); + + pxl8_bounds window_bounds = {0, 0, window_width, window_height}; + pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_width, render_height); + + game->input.mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale); + game->input.mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale); + break; + } + + case SDL_EVENT_MOUSE_WHEEL: { + game->input.mouse_wheel_x = (i32)event->wheel.x; + game->input.mouse_wheel_y = (i32)event->wheel.y; + break; + } + } + + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void* appstate, SDL_AppResult result) { + pxl8_game* game = (pxl8_game*)appstate; + (void)result; + + if (game) { + pxl8_quit(game); + SDL_free(game); + } + + SDL_Quit(); +} + +const pxl8_hal pxl8_hal_sdl3 = { + .create = sdl3_create, + .destroy = sdl3_destroy, + .get_ticks = sdl3_get_ticks, + .present = sdl3_present, + .upload_atlas = sdl3_upload_atlas, + .upload_framebuffer = sdl3_upload_framebuffer, +}; diff --git a/src/pxl8_sdl3.h b/src/pxl8_sdl3.h new file mode 100644 index 0000000..4051e9e --- /dev/null +++ b/src/pxl8_sdl3.h @@ -0,0 +1,5 @@ +#pragma once + +#include "pxl8_hal.h" + +extern const pxl8_hal pxl8_hal_sdl3; diff --git a/src/pxl8_types.h b/src/pxl8_types.h index 60094e1..e07536f 100644 --- a/src/pxl8_types.h +++ b/src/pxl8_types.h @@ -25,7 +25,7 @@ typedef enum pxl8_color_mode { PXL8_COLOR_MODE_GBA, PXL8_COLOR_MODE_HICOLOR, PXL8_COLOR_MODE_MEGA, - PXL8_COLOR_MODE_SUPERFAMI + PXL8_COLOR_MODE_SNES } pxl8_color_mode; typedef enum pxl8_resolution { diff --git a/src/pxl8_ui.c b/src/pxl8_ui.c index 77d35d6..cbac4a7 100644 --- a/src/pxl8_ui.c +++ b/src/pxl8_ui.c @@ -2,7 +2,6 @@ #include "../lib/microui/src/microui.h" #include "pxl8_font.h" -#include #include #include diff --git a/src/pxl8_vfx.c b/src/pxl8_vfx.c index f6f29a7..5605cb3 100644 --- a/src/pxl8_vfx.c +++ b/src/pxl8_vfx.c @@ -1,7 +1,6 @@ #include #include - -#include +#include #include "pxl8_vfx.h" @@ -82,10 +81,10 @@ void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) { f32 cos_a = cosf(angle); f32 sin_a = sinf(angle); - u8* temp_buffer = (u8*)SDL_malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); + u8* temp_buffer = (u8*)malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); if (!temp_buffer) return; - SDL_memcpy(temp_buffer, pxl8_gfx_get_framebuffer(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); + memcpy(temp_buffer, pxl8_gfx_get_framebuffer(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { @@ -104,7 +103,7 @@ void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) { } } - SDL_free(temp_buffer); + free(temp_buffer); } void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) { @@ -145,7 +144,7 @@ void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_ static f32* prev_height = NULL; if (!prev_height) { - prev_height = (f32*)SDL_calloc(w * h, sizeof(f32)); + prev_height = (f32*)calloc(w * h, sizeof(f32)); if (!prev_height) return; } @@ -169,12 +168,12 @@ void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_ } pxl8_particles* pxl8_particles_create(u32 max_count) { - pxl8_particles* particles = SDL_calloc(1, sizeof(pxl8_particles)); + pxl8_particles* particles = calloc(1, sizeof(pxl8_particles)); if (!particles) return NULL; - particles->particles = SDL_calloc(max_count, sizeof(pxl8_particle)); + particles->particles = calloc(max_count, sizeof(pxl8_particle)); if (!particles->particles) { - SDL_free(particles); + free(particles); return NULL; } @@ -188,8 +187,8 @@ pxl8_particles* pxl8_particles_create(u32 max_count) { void pxl8_particles_destroy(pxl8_particles* particles) { if (!particles) return; - SDL_free(particles->particles); - SDL_free(particles); + free(particles->particles); + free(particles); } void pxl8_particles_clear(pxl8_particles* particles) { diff --git a/src/pxl8_world.c b/src/pxl8_world.c index 54f3a67..7ccb9e5 100644 --- a/src/pxl8_world.c +++ b/src/pxl8_world.c @@ -1,7 +1,6 @@ +#include #include -#include - #include "pxl8_bsp.h" #include "pxl8_macros.h" #include "pxl8_world.h" @@ -13,7 +12,7 @@ struct pxl8_world { }; pxl8_world* pxl8_world_create(void) { - pxl8_world* world = (pxl8_world*)SDL_calloc(1, sizeof(pxl8_world)); + pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world)); if (!world) { pxl8_error("Failed to allocate world"); return NULL; @@ -32,7 +31,7 @@ void pxl8_world_destroy(pxl8_world* world) { pxl8_bsp_destroy(&world->bsp); } - SDL_free(world); + free(world); } pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {