refactor SDL out of core files
This commit is contained in:
parent
82ed6b4ea9
commit
323bd2a7c1
21 changed files with 1427 additions and 1029 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
(var camera-angle 0)
|
(var camera-angle 0)
|
||||||
(local camera-height 0)
|
(local camera-height 0)
|
||||||
(local camera-distance 300)
|
(local camera-distance 450)
|
||||||
(var fps 0)
|
(var fps 0)
|
||||||
(var world nil)
|
(var world nil)
|
||||||
|
|
||||||
|
|
|
||||||
2
pxl8.sh
2
pxl8.sh
|
|
@ -383,6 +383,7 @@ case "$COMMAND" in
|
||||||
PXL8_SOURCE_FILES="
|
PXL8_SOURCE_FILES="
|
||||||
src/pxl8.c
|
src/pxl8.c
|
||||||
src/pxl8_ase.c
|
src/pxl8_ase.c
|
||||||
|
src/pxl8_atlas.c
|
||||||
src/pxl8_blit.c
|
src/pxl8_blit.c
|
||||||
src/pxl8_bsp.c
|
src/pxl8_bsp.c
|
||||||
src/pxl8_cart.c
|
src/pxl8_cart.c
|
||||||
|
|
@ -391,6 +392,7 @@ case "$COMMAND" in
|
||||||
src/pxl8_io.c
|
src/pxl8_io.c
|
||||||
src/pxl8_math.c
|
src/pxl8_math.c
|
||||||
src/pxl8_script.c
|
src/pxl8_script.c
|
||||||
|
src/pxl8_sdl3.c
|
||||||
src/pxl8_tilemap.c
|
src/pxl8_tilemap.c
|
||||||
src/pxl8_tilesheet.c
|
src/pxl8_tilesheet.c
|
||||||
src/pxl8_ui.c
|
src/pxl8_ui.c
|
||||||
|
|
|
||||||
517
src/pxl8.c
517
src/pxl8.c
|
|
@ -4,197 +4,34 @@
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <linenoise.h>
|
#include <linenoise.h>
|
||||||
|
|
||||||
#define SDL_MAIN_USE_CALLBACKS
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
#include <SDL3/SDL_main.h>
|
|
||||||
|
|
||||||
#include "pxl8_cart.h"
|
#include "pxl8_cart.h"
|
||||||
|
#include "pxl8_game.h"
|
||||||
|
#include "pxl8_hal.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_script.h"
|
#include "pxl8_script.h"
|
||||||
|
#include "pxl8_sdl3.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
#include "pxl8_ui.h"
|
#include "pxl8_ui.h"
|
||||||
|
|
||||||
#define PXL8_MAX_REPL_COMMANDS 4096
|
pxl8_game_result pxl8_init(pxl8_game* game, i32 argc, char* argv[]) {
|
||||||
|
if (!game) {
|
||||||
typedef struct pxl8_repl_command {
|
return PXL8_GAME_FAILURE;
|
||||||
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++) {
|
#ifdef PXL8_TARGET_GBA
|
||||||
if (strncmp(buf, pxl8_functions[i], buf_len) == 0) {
|
game->hal = &pxl8_hal_gba;
|
||||||
linenoiseAddCompletion(lc, pxl8_functions[i]);
|
#else
|
||||||
}
|
game->hal = &pxl8_hal_sdl3;
|
||||||
}
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
static char* pxl8_repl_hints(const char* buf, int* color, int* bold) {
|
game->color_mode = PXL8_COLOR_MODE_MEGA;
|
||||||
if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) {
|
game->resolution = PXL8_RESOLUTION_640x360;
|
||||||
*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;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.color_mode = PXL8_COLOR_MODE_MEGA;
|
|
||||||
app.resolution = PXL8_RESOLUTION_640x360;
|
|
||||||
|
|
||||||
const char* script_arg = NULL;
|
const char* script_arg = NULL;
|
||||||
bool pack_mode = false;
|
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++) {
|
for (i32 i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "--repl") == 0) {
|
if (strcmp(argv[i], "--repl") == 0) {
|
||||||
app.repl_mode = true;
|
game->repl_mode = true;
|
||||||
} else if (strcmp(argv[i], "--pack") == 0) {
|
} else if (strcmp(argv[i], "--pack") == 0) {
|
||||||
pack_mode = true;
|
pack_mode = true;
|
||||||
if (i + 2 < argc) {
|
if (i + 2 < argc) {
|
||||||
|
|
@ -211,7 +48,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||||
pack_output = argv[++i];
|
pack_output = argv[++i];
|
||||||
} else {
|
} else {
|
||||||
pxl8_error("--pack requires <folder> <output.pxc>");
|
pxl8_error("--pack requires <folder> <output.pxc>");
|
||||||
return SDL_APP_FAILURE;
|
return PXL8_GAME_FAILURE;
|
||||||
}
|
}
|
||||||
} else if (!script_arg) {
|
} else if (!script_arg) {
|
||||||
script_arg = argv[i];
|
script_arg = argv[i];
|
||||||
|
|
@ -220,39 +57,39 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||||
|
|
||||||
if (pack_mode) {
|
if (pack_mode) {
|
||||||
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
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) {
|
if (game->repl_mode) {
|
||||||
fprintf(stderr, "\033[38;2;184;187;38m[pxl8]\033[0m Starting in REPL mode with script: %s\n", app.script_path);
|
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");
|
pxl8_info("Starting up");
|
||||||
|
|
||||||
app.gfx = pxl8_gfx_create(app.color_mode, app.resolution, "pxl8", 1280, 720);
|
game->gfx = pxl8_gfx_create(game->hal, game->color_mode, game->resolution, "pxl8", 1280, 720);
|
||||||
if (!app.gfx) {
|
if (!game->gfx) {
|
||||||
pxl8_error("Failed to create graphics context");
|
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");
|
pxl8_error("Failed to load font atlas");
|
||||||
return SDL_APP_FAILURE;
|
return PXL8_GAME_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.ui = pxl8_ui_create(app.gfx);
|
game->ui = pxl8_ui_create(game->gfx);
|
||||||
if (!app.ui) {
|
if (!game->ui) {
|
||||||
pxl8_error("Failed to create UI");
|
pxl8_error("Failed to create UI");
|
||||||
pxl8_gfx_destroy(app.gfx);
|
pxl8_gfx_destroy(game->gfx);
|
||||||
return SDL_APP_FAILURE;
|
return PXL8_GAME_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.script = pxl8_script_create();
|
game->script = pxl8_script_create();
|
||||||
if (!app.script) {
|
if (!game->script) {
|
||||||
pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(app.script));
|
pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
||||||
pxl8_gfx_destroy(app.gfx);
|
pxl8_gfx_destroy(game->gfx);
|
||||||
return SDL_APP_FAILURE;
|
return PXL8_GAME_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* cart_path = script_arg ? script_arg : "demo";
|
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) {
|
if (is_cart) {
|
||||||
char* original_cwd = getcwd(NULL, 0);
|
char* original_cwd = getcwd(NULL, 0);
|
||||||
app.cart = pxl8_cart_create();
|
game->cart = pxl8_cart_create();
|
||||||
if (!app.cart) {
|
if (!game->cart) {
|
||||||
pxl8_error("Failed to create cart");
|
pxl8_error("Failed to create cart");
|
||||||
return false;
|
return PXL8_GAME_FAILURE;
|
||||||
}
|
}
|
||||||
if (pxl8_cart_load(app.cart, cart_path) == PXL8_OK) {
|
if (pxl8_cart_load(game->cart, cart_path) == PXL8_OK) {
|
||||||
pxl8_script_set_cart_path(app.script, pxl8_cart_get_base_path(app.cart), original_cwd);
|
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(game->cart), original_cwd);
|
||||||
pxl8_cart_mount(app.cart);
|
pxl8_cart_mount(game->cart);
|
||||||
strcpy(app.script_path, "main.fnl");
|
strcpy(game->script_path, "main.fnl");
|
||||||
pxl8_info("Loaded cart: %s", pxl8_cart_get_name(app.cart));
|
pxl8_info("Loaded cart: %s", pxl8_cart_get_name(game->cart));
|
||||||
} else {
|
} else {
|
||||||
pxl8_error("Failed to load cart: %s", cart_path);
|
pxl8_error("Failed to load cart: %s", cart_path);
|
||||||
return SDL_APP_FAILURE;
|
return PXL8_GAME_FAILURE;
|
||||||
}
|
}
|
||||||
free(original_cwd);
|
free(original_cwd);
|
||||||
} else if (script_arg) {
|
} else if (script_arg) {
|
||||||
strncpy(app.script_path, script_arg, sizeof(app.script_path) - 1);
|
strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1);
|
||||||
app.script_path[sizeof(app.script_path) - 1] = '\0';
|
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_script_set_gfx(app.script, app.gfx);
|
pxl8_script_set_gfx(game->script, game->gfx);
|
||||||
pxl8_script_set_input(app.script, &app.input);
|
pxl8_script_set_input(game->script, &game->input);
|
||||||
pxl8_script_set_ui(app.script, app.ui);
|
pxl8_script_set_ui(game->script, game->ui);
|
||||||
|
|
||||||
if (app.script_path[0] != '\0') {
|
if (game->script_path[0] != '\0') {
|
||||||
pxl8_result result = pxl8_script_load_main(app.script, app.script_path);
|
pxl8_result result = pxl8_script_load_main(game->script, game->script_path);
|
||||||
app.script_loaded = (result == PXL8_OK);
|
game->script_loaded = (result == PXL8_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.repl_mode) {
|
if (game->repl_mode) {
|
||||||
pxl8_repl_init(&app.repl);
|
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");
|
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) {
|
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(app.script));
|
fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", pxl8_script_get_last_error(game->script));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.last_time = SDL_GetTicksNS();
|
game->last_time = game->hal->get_ticks();
|
||||||
app.running = true;
|
game->running = true;
|
||||||
|
|
||||||
*appstate = &app;
|
|
||||||
return SDL_APP_CONTINUE;
|
return PXL8_GAME_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppIterate(void* appstate) {
|
pxl8_game_result pxl8_update(pxl8_game* game) {
|
||||||
pxl8_state* app = (pxl8_state*)appstate;
|
if (!game) {
|
||||||
pxl8_bounds bounds = pxl8_gfx_get_bounds(app->gfx);
|
return PXL8_GAME_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
u64 current_time = SDL_GetTicksNS();
|
u64 current_time = game->hal->get_ticks();
|
||||||
f32 dt = (f32)(current_time - app->last_time) / 1000000000.0f;
|
f32 dt = (f32)(current_time - game->last_time) / 1000000000.0f;
|
||||||
|
|
||||||
app->last_time = current_time;
|
game->last_time = current_time;
|
||||||
app->time += dt;
|
game->time += dt;
|
||||||
|
|
||||||
pxl8_script_check_reload(app->script);
|
pxl8_script_check_reload(game->script);
|
||||||
|
|
||||||
if (app->repl_mode) {
|
if (game->repl_mode && game->repl) {
|
||||||
pxl8_repl_command* cmd = pxl8_repl_pop_command(&app->repl);
|
pxl8_script_repl_command* cmd = pxl8_script_repl_pop_command(game->repl);
|
||||||
if (cmd) {
|
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) {
|
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) {
|
if (game->ui) {
|
||||||
pxl8_ui_input_mousemove(app->ui, app->input.mouse_x, app->input.mouse_y);
|
i32 render_width, render_height;
|
||||||
if (app->input.mouse_wheel_x != 0 || app->input.mouse_wheel_y != 0) {
|
pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height);
|
||||||
pxl8_ui_input_scroll(app->ui, app->input.mouse_wheel_x * 10, -app->input.mouse_wheel_y * 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_ui_frame_begin(app->ui);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
for (i32 i = 0; i < 3; i++) {
|
for (i32 i = 0; i < 3; i++) {
|
||||||
if (app->input.mouse_buttons_pressed[i]) {
|
if (game->input.mouse_buttons_pressed[i]) {
|
||||||
pxl8_ui_input_mousedown(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1);
|
pxl8_ui_input_mousedown(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1);
|
||||||
}
|
}
|
||||||
if (app->input.mouse_buttons_released[i]) {
|
if (game->input.mouse_buttons_released[i]) {
|
||||||
pxl8_ui_input_mouseup(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1);
|
pxl8_ui_input_mouseup(game->ui, game->input.mouse_x, game->input.mouse_y, i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i32 key = 0; key < 256; key++) {
|
for (i32 key = 0; key < 256; key++) {
|
||||||
if (app->input.keys_pressed[key]) {
|
if (game->input.keys_pressed[key]) {
|
||||||
pxl8_ui_input_keydown(app->ui, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app->script_loaded) {
|
pxl8_ui_frame_begin(game->ui);
|
||||||
pxl8_script_call_function_f32(app->script, "update", dt);
|
}
|
||||||
|
|
||||||
pxl8_result frame_result = pxl8_script_call_function(app->script, "frame");
|
if (game->script_loaded) {
|
||||||
|
pxl8_script_call_function_f32(game->script, "update", dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
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 {
|
} else {
|
||||||
pxl8_clr(app->gfx, 32);
|
pxl8_clr(game->gfx, 32);
|
||||||
|
|
||||||
i32 render_width, render_height;
|
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 y = 0; y < render_height; y += 24) {
|
||||||
for (i32 x = 0; x < render_width; x += 32) {
|
for (i32 x = 0; x < render_width; x += 32) {
|
||||||
u32 color = ((x / 32) + (y / 24) + (i32)(app->time * 2)) % 8;
|
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
|
||||||
pxl8_rect_fill(app->gfx, x, y, 31, 23, color);
|
pxl8_rect_fill(game->gfx, x, y, 31, 23, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i32 render_width, render_height;
|
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) {
|
if (game->ui) {
|
||||||
pxl8_ui_frame_end(app->ui);
|
pxl8_ui_frame_end(game->ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_gfx_set_viewport(app->gfx, pxl8_gfx_viewport(bounds, render_width, render_height));
|
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_width, render_height));
|
||||||
pxl8_gfx_upload_framebuffer(app->gfx);
|
pxl8_gfx_upload_framebuffer(game->gfx);
|
||||||
pxl8_gfx_upload_atlas(app->gfx);
|
pxl8_gfx_upload_atlas(game->gfx);
|
||||||
pxl8_gfx_present(app->gfx);
|
pxl8_gfx_present(game->gfx);
|
||||||
|
|
||||||
SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed));
|
memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed));
|
||||||
SDL_memset(app->input.keys_released, 0, sizeof(app->input.keys_released));
|
memset(game->input.keys_released, 0, sizeof(game->input.keys_released));
|
||||||
SDL_memset(app->input.mouse_buttons_pressed, 0, sizeof(app->input.mouse_buttons_pressed));
|
memset(game->input.mouse_buttons_pressed, 0, sizeof(game->input.mouse_buttons_pressed));
|
||||||
SDL_memset(app->input.mouse_buttons_released, 0, sizeof(app->input.mouse_buttons_released));
|
memset(game->input.mouse_buttons_released, 0, sizeof(game->input.mouse_buttons_released));
|
||||||
app->input.mouse_wheel_x = 0;
|
game->input.mouse_wheel_x = 0;
|
||||||
app->input.mouse_wheel_y = 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) {
|
void pxl8_quit(pxl8_game* game) {
|
||||||
pxl8_state* app = (pxl8_state*)appstate;
|
if (!game) {
|
||||||
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
|
||||||
pxl8_state* app = (pxl8_state*)appstate;
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
if (app) {
|
|
||||||
pxl8_info("Shutting down");
|
pxl8_info("Shutting down");
|
||||||
if (app->repl_mode) {
|
|
||||||
pxl8_repl_shutdown(&app->repl);
|
if (game->repl_mode && game->repl) {
|
||||||
}
|
pxl8_script_repl_shutdown(game->repl);
|
||||||
if (app->cart) {
|
pxl8_script_repl_destroy(game->repl);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
#define MINIZ_NO_DEFLATE_APIS
|
#define MINIZ_NO_DEFLATE_APIS
|
||||||
|
|
||||||
#include <miniz.h>
|
#include <miniz.h>
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_ase.h"
|
#include "pxl8_ase.h"
|
||||||
#include "pxl8_io.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->entry_count = total_colors;
|
||||||
palette->first_color = 0;
|
palette->first_color = 0;
|
||||||
palette->last_color = total_colors - 1;
|
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) {
|
if (!palette->colors) {
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
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);
|
u16 name_len = pxl8_read_u16(stream);
|
||||||
if (name_len > 0) {
|
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;
|
if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
pxl8_read_bytes(stream, layer->name, name_len);
|
pxl8_read_bytes(stream, layer->name, name_len);
|
||||||
layer->name[name_len] = '\0';
|
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;
|
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) {
|
if (!palette->colors) {
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
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 pixel_data_size = cel->width * cel->height;
|
||||||
u32 compressed_data_size = chunk_size - 20;
|
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) {
|
if (!cel->pixel_data) {
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
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);
|
i32 result = mz_uncompress(cel->pixel_data, &dest_len, compressed_data, compressed_data_size);
|
||||||
if (result != MZ_OK) {
|
if (result != MZ_OK) {
|
||||||
pxl8_error("Failed to decompress cel data: miniz error %d", result);
|
pxl8_error("Failed to decompress cel data: miniz error %d", result);
|
||||||
SDL_free(cel->pixel_data);
|
free(cel->pixel_data);
|
||||||
cel->pixel_data = NULL;
|
cel->pixel_data = NULL;
|
||||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
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->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) {
|
if (!ase_file->frames) {
|
||||||
pxl8_io_free_binary_data(file_data);
|
pxl8_io_free_binary_data(file_data);
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
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;
|
frame->duration = frame_header.duration;
|
||||||
|
|
||||||
u32 pixel_count = frame->width * frame->height;
|
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) {
|
if (!frame->pixels) {
|
||||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
break;
|
break;
|
||||||
|
|
@ -285,7 +284,7 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
|
||||||
|
|
||||||
case PXL8_ASE_CHUNK_LAYER: {
|
case PXL8_ASE_CHUNK_LAYER: {
|
||||||
ase_file->layers =
|
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));
|
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
|
||||||
if (!ase_file->layers) {
|
if (!ase_file->layers) {
|
||||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PXL8_ASE_CHUNK_PALETTE:
|
case PXL8_ASE_CHUNK_PALETTE:
|
||||||
if (ase_file->palette.colors) {
|
if (ase_file->palette.colors) {
|
||||||
SDL_free(ase_file->palette.colors);
|
free(ase_file->palette.colors);
|
||||||
ase_file->palette.colors = NULL;
|
ase_file->palette.colors = NULL;
|
||||||
}
|
}
|
||||||
result = parse_palette_chunk(&stream, &ase_file->palette);
|
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) {
|
if (ase_file->frames) {
|
||||||
for (u32 i = 0; i < ase_file->frame_count; i++) {
|
for (u32 i = 0; i < ase_file->frame_count; i++) {
|
||||||
if (ase_file->frames[i].pixels) {
|
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) {
|
if (ase_file->palette.colors) {
|
||||||
SDL_free(ase_file->palette.colors);
|
free(ase_file->palette.colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ase_file->layers) {
|
if (ase_file->layers) {
|
||||||
for (u32 i = 0; i < ase_file->layer_count; i++) {
|
for (u32 i = 0; i < ase_file->layer_count; i++) {
|
||||||
if (ase_file->layers[i].name) {
|
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));
|
memset(ase_file, 0, sizeof(pxl8_ase_file));
|
||||||
|
|
|
||||||
360
src/pxl8_atlas.c
Normal file
360
src/pxl8_atlas.c
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
#include "pxl8_atlas.h"
|
||||||
|
#include "pxl8_macros.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/pxl8_atlas.h
Normal file
26
src/pxl8_atlas.h
Normal file
|
|
@ -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);
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_bsp.h"
|
#include "pxl8_bsp.h"
|
||||||
#include "pxl8_gfx.h"
|
#include "pxl8_gfx.h"
|
||||||
#include "pxl8_io.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));
|
memset(bsp, 0, sizeof(*bsp));
|
||||||
|
|
||||||
size_t file_size;
|
FILE* f = fopen(path, "rb");
|
||||||
u8* file_data = (u8*)SDL_LoadFile(path, &file_size);
|
if (!f) {
|
||||||
if (!file_data) {
|
|
||||||
pxl8_error("Failed to load BSP file: %s", path);
|
pxl8_error("Failed to load BSP file: %s", path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return 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)) {
|
if (file_size < sizeof(pxl8_bsp_header)) {
|
||||||
pxl8_error("BSP file too small: %s", path);
|
pxl8_error("BSP file too small: %s", path);
|
||||||
SDL_free(file_data);
|
free(file_data);
|
||||||
return PXL8_ERROR_INVALID_FORMAT;
|
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) {
|
if (header.version != BSP_VERSION) {
|
||||||
pxl8_error("Invalid BSP version: %u (expected %d)", 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;
|
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;
|
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
|
||||||
bsp->num_vertices = chunk->size / 12;
|
bsp->num_vertices = chunk->size / 12;
|
||||||
if (bsp->num_vertices > 0) {
|
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;
|
if (!bsp->vertices) goto error_cleanup;
|
||||||
pxl8_stream_seek(&stream, chunk->offset);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
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;
|
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||||
bsp->num_edges = chunk->size / 4;
|
bsp->num_edges = chunk->size / 4;
|
||||||
if (bsp->num_edges > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_edges; i++) {
|
for (u32 i = 0; i < bsp->num_edges; i++) {
|
||||||
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
|
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;
|
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||||
bsp->num_surfedges = chunk->size / 4;
|
bsp->num_surfedges = chunk->size / 4;
|
||||||
if (bsp->num_surfedges > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
||||||
bsp->surfedges[i] = pxl8_read_i32(&stream);
|
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;
|
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||||
bsp->num_planes = chunk->size / 20;
|
bsp->num_planes = chunk->size / 20;
|
||||||
if (bsp->num_planes > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_planes; i++) {
|
for (u32 i = 0; i < bsp->num_planes; i++) {
|
||||||
bsp->planes[i].normal = read_vec3(&stream);
|
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;
|
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
|
||||||
bsp->num_texinfo = chunk->size / 40;
|
bsp->num_texinfo = chunk->size / 40;
|
||||||
if (bsp->num_texinfo > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
||||||
bsp->texinfo[i].u_axis = read_vec3(&stream);
|
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;
|
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||||
bsp->num_faces = chunk->size / 20;
|
bsp->num_faces = chunk->size / 20;
|
||||||
if (bsp->num_faces > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||||
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
|
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;
|
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
|
||||||
bsp->num_nodes = chunk->size / 24;
|
bsp->num_nodes = chunk->size / 24;
|
||||||
if (bsp->num_nodes > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_nodes; i++) {
|
for (u32 i = 0; i < bsp->num_nodes; i++) {
|
||||||
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
|
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;
|
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
|
||||||
bsp->num_leafs = chunk->size / 28;
|
bsp->num_leafs = chunk->size / 28;
|
||||||
if (bsp->num_leafs > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||||
bsp->leafs[i].contents = pxl8_read_i32(&stream);
|
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;
|
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
|
||||||
bsp->num_marksurfaces = chunk->size / 2;
|
bsp->num_marksurfaces = chunk->size / 2;
|
||||||
if (bsp->num_marksurfaces > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
||||||
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
|
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;
|
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
|
||||||
bsp->num_models = chunk->size / 64;
|
bsp->num_models = chunk->size / 64;
|
||||||
if (bsp->num_models > 0) {
|
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);
|
pxl8_stream_seek(&stream, chunk->offset);
|
||||||
for (u32 i = 0; i < bsp->num_models; i++) {
|
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);
|
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;
|
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||||
bsp->visdata_size = chunk->size;
|
bsp->visdata_size = chunk->size;
|
||||||
if (bsp->visdata_size > 0) {
|
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);
|
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;
|
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||||
bsp->lightdata_size = chunk->size;
|
bsp->lightdata_size = chunk->size;
|
||||||
if (bsp->lightdata_size > 0) {
|
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);
|
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",
|
pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_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:
|
error_cleanup:
|
||||||
pxl8_error("BSP chunk validation failed: %s", path);
|
pxl8_error("BSP chunk validation failed: %s", path);
|
||||||
SDL_free(file_data);
|
free(file_data);
|
||||||
pxl8_bsp_destroy(bsp);
|
pxl8_bsp_destroy(bsp);
|
||||||
return PXL8_ERROR_INVALID_FORMAT;
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
@ -266,18 +284,18 @@ error_cleanup:
|
||||||
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
|
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
|
||||||
if (!bsp) return;
|
if (!bsp) return;
|
||||||
|
|
||||||
SDL_free(bsp->edges);
|
free(bsp->edges);
|
||||||
SDL_free(bsp->faces);
|
free(bsp->faces);
|
||||||
SDL_free(bsp->leafs);
|
free(bsp->leafs);
|
||||||
SDL_free(bsp->lightdata);
|
free(bsp->lightdata);
|
||||||
SDL_free(bsp->marksurfaces);
|
free(bsp->marksurfaces);
|
||||||
SDL_free(bsp->models);
|
free(bsp->models);
|
||||||
SDL_free(bsp->nodes);
|
free(bsp->nodes);
|
||||||
SDL_free(bsp->planes);
|
free(bsp->planes);
|
||||||
SDL_free(bsp->surfedges);
|
free(bsp->surfedges);
|
||||||
SDL_free(bsp->texinfo);
|
free(bsp->texinfo);
|
||||||
SDL_free(bsp->vertices);
|
free(bsp->vertices);
|
||||||
SDL_free(bsp->visdata);
|
free(bsp->visdata);
|
||||||
|
|
||||||
memset(bsp, 0, sizeof(*bsp));
|
memset(bsp, 0, sizeof(*bsp));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_font.h"
|
#include "pxl8_font.h"
|
||||||
|
|
||||||
pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* atlas_width, i32* atlas_height) {
|
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;
|
*atlas_height = rows_needed * font->default_height;
|
||||||
|
|
||||||
i32 atlas_size = (*atlas_width) * (*atlas_height);
|
i32 atlas_size = (*atlas_width) * (*atlas_height);
|
||||||
*atlas_data = (u8*)SDL_malloc(atlas_size);
|
*atlas_data = (u8*)malloc(atlas_size);
|
||||||
if (!*atlas_data) {
|
if (!*atlas_data) {
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
src/pxl8_game.h
Normal file
49
src/pxl8_game.h
Normal file
|
|
@ -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);
|
||||||
677
src/pxl8_gfx.c
677
src/pxl8_gfx.c
|
|
@ -2,62 +2,30 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_ase.h"
|
#include "pxl8_ase.h"
|
||||||
|
#include "pxl8_atlas.h"
|
||||||
#include "pxl8_blit.h"
|
#include "pxl8_blit.h"
|
||||||
#include "pxl8_font.h"
|
#include "pxl8_font.h"
|
||||||
#include "pxl8_gfx.h"
|
#include "pxl8_gfx.h"
|
||||||
|
#include "pxl8_hal.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
typedef struct pxl8_atlas_entry {
|
typedef struct pxl8_sprite_cache_entry {
|
||||||
bool active;
|
|
||||||
char path[256];
|
char path[256];
|
||||||
u32 texture_id;
|
u32 sprite_id;
|
||||||
|
bool active;
|
||||||
i32 x, y, w, h;
|
} pxl8_sprite_cache_entry;
|
||||||
} 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct pxl8_gfx {
|
struct pxl8_gfx {
|
||||||
SDL_Renderer* renderer;
|
const pxl8_hal* hal;
|
||||||
SDL_Texture* framebuffer_texture;
|
void* platform_data;
|
||||||
SDL_Window* window;
|
|
||||||
|
|
||||||
pxl8_atlas* atlas;
|
pxl8_atlas* atlas;
|
||||||
|
pxl8_sprite_cache_entry* sprite_cache;
|
||||||
|
u32 sprite_cache_capacity;
|
||||||
|
u32 sprite_cache_count;
|
||||||
|
|
||||||
pxl8_color_mode color_mode;
|
pxl8_color_mode color_mode;
|
||||||
u8* framebuffer;
|
u8* framebuffer;
|
||||||
|
|
@ -82,351 +50,6 @@ struct pxl8_gfx {
|
||||||
bool affine_textures;
|
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) {
|
static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
||||||
*r = color & 0xFF;
|
*r = color & 0xFF;
|
||||||
*g = (color >> 8) & 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_FAMI: return 64;
|
||||||
case PXL8_COLOR_MODE_MEGA: return 512;
|
case PXL8_COLOR_MODE_MEGA: return 512;
|
||||||
case PXL8_COLOR_MODE_GBA: return 32768;
|
case PXL8_COLOR_MODE_GBA: return 32768;
|
||||||
case PXL8_COLOR_MODE_SUPERFAMI: return 32768;
|
case PXL8_COLOR_MODE_SNES: return 32768;
|
||||||
default: return 256;
|
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 pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
||||||
pxl8_bounds bounds = {0};
|
pxl8_bounds bounds = {0};
|
||||||
if (!gfx || !gfx->window) {
|
if (!gfx) {
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
SDL_GetWindowPosition(gfx->window, &bounds.x, &bounds.y);
|
bounds.w = gfx->framebuffer_width;
|
||||||
SDL_GetWindowSize(gfx->window, &bounds.w, &bounds.h);
|
bounds.h = gfx->framebuffer_height;
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -505,18 +128,21 @@ u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_gfx* pxl8_gfx_create(
|
pxl8_gfx* pxl8_gfx_create(
|
||||||
|
const pxl8_hal* hal,
|
||||||
pxl8_color_mode mode,
|
pxl8_color_mode mode,
|
||||||
pxl8_resolution resolution,
|
pxl8_resolution resolution,
|
||||||
const char* title,
|
const char* title,
|
||||||
i32 window_width,
|
i32 window_width,
|
||||||
i32 window_height
|
i32 window_height
|
||||||
) {
|
) {
|
||||||
pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx));
|
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(*gfx));
|
||||||
if (!gfx) {
|
if (!gfx) {
|
||||||
pxl8_error("Failed to allocate graphics context");
|
pxl8_error("Failed to allocate graphics context");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfx->hal = hal;
|
||||||
|
|
||||||
gfx->color_mode = mode;
|
gfx->color_mode = mode;
|
||||||
pxl8_gfx_get_resolution_dimensions(
|
pxl8_gfx_get_resolution_dimensions(
|
||||||
resolution,
|
resolution,
|
||||||
|
|
@ -524,60 +150,25 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
&gfx->framebuffer_height
|
&gfx->framebuffer_height
|
||||||
);
|
);
|
||||||
|
|
||||||
gfx->window = SDL_CreateWindow(
|
gfx->platform_data = gfx->hal->create(mode, resolution, title, window_width, window_height);
|
||||||
title,
|
if (!gfx->platform_data) {
|
||||||
window_width, window_height,
|
pxl8_error("Failed to create platform context");
|
||||||
SDL_WINDOW_RESIZABLE
|
free(gfx);
|
||||||
);
|
|
||||||
|
|
||||||
if (!gfx->window) {
|
|
||||||
pxl8_error("Failed to create window: %s", SDL_GetError());
|
|
||||||
pxl8_gfx_destroy(gfx);
|
|
||||||
return NULL;
|
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 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
||||||
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
|
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) {
|
if (!gfx->framebuffer) {
|
||||||
pxl8_error("Failed to allocate framebuffer");
|
pxl8_error("Failed to allocate framebuffer");
|
||||||
pxl8_gfx_destroy(gfx);
|
pxl8_gfx_destroy(gfx);
|
||||||
return NULL;
|
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);
|
gfx->palette_size = pxl8_get_palette_size(mode);
|
||||||
if (gfx->palette_size > 0) {
|
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) {
|
if (!gfx->palette) {
|
||||||
pxl8_error("Failed to allocate palette");
|
pxl8_error("Failed to allocate palette");
|
||||||
pxl8_gfx_destroy(gfx);
|
pxl8_gfx_destroy(gfx);
|
||||||
|
|
@ -614,27 +205,17 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
||||||
if (!gfx) return;
|
if (!gfx) return;
|
||||||
|
|
||||||
pxl8_atlas_destroy(gfx->atlas);
|
pxl8_atlas_destroy(gfx->atlas);
|
||||||
|
free(gfx->sprite_cache);
|
||||||
|
|
||||||
if (gfx->framebuffer_texture) {
|
if (gfx->hal && gfx->platform_data) {
|
||||||
SDL_DestroyTexture(gfx->framebuffer_texture);
|
gfx->hal->destroy(gfx->platform_data);
|
||||||
gfx->framebuffer_texture = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gfx->renderer) {
|
free(gfx->framebuffer);
|
||||||
SDL_DestroyRenderer(gfx->renderer);
|
free(gfx->palette);
|
||||||
gfx->renderer = NULL;
|
free(gfx->zbuffer);
|
||||||
}
|
|
||||||
|
|
||||||
if (gfx->window) {
|
free(gfx);
|
||||||
SDL_DestroyWindow(gfx->window);
|
|
||||||
gfx->window = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_free(gfx->framebuffer);
|
|
||||||
SDL_free(gfx->palette);
|
|
||||||
SDL_free(gfx->zbuffer);
|
|
||||||
|
|
||||||
SDL_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 || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
if (!gfx->atlas) {
|
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;
|
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 texture_id =
|
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->color_mode);
|
||||||
pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode);
|
|
||||||
if (texture_id == UINT32_MAX) {
|
if (texture_id == UINT32_MAX) {
|
||||||
pxl8_error("Texture doesn't fit in atlas");
|
pxl8_error("Texture doesn't fit in atlas");
|
||||||
return PXL8_ERROR_INVALID_SIZE;
|
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;
|
return texture_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
||||||
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
if (!gfx->atlas) {
|
if (!gfx->sprite_cache) {
|
||||||
gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode);
|
gfx->sprite_cache_capacity = 64;
|
||||||
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
|
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++) {
|
for (u32 i = 0; i < gfx->sprite_cache_count; i++) {
|
||||||
if (gfx->atlas->entries[i].active && strcmp(gfx->atlas->entries[i].path, path) == 0) {
|
if (gfx->sprite_cache[i].active && strcmp(gfx->sprite_cache[i].path, path) == 0) {
|
||||||
return gfx->atlas->entries[i].texture_id;
|
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_ase_file ase_file;
|
||||||
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
||||||
if (result != PXL8_OK) {
|
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;
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 sprite_w = ase_file.header.width;
|
u32 sprite_id = pxl8_atlas_add_texture(
|
||||||
u32 sprite_h = ase_file.header.height;
|
|
||||||
|
|
||||||
u32 texture_id = pxl8_atlas_add_texture(
|
|
||||||
gfx->atlas,
|
gfx->atlas,
|
||||||
ase_file.frames[0].pixels,
|
ase_file.frames[0].pixels,
|
||||||
sprite_w,
|
ase_file.header.width,
|
||||||
sprite_h,
|
ase_file.header.height,
|
||||||
path,
|
|
||||||
gfx->color_mode
|
gfx->color_mode
|
||||||
);
|
);
|
||||||
|
|
||||||
pxl8_ase_destroy(&ase_file);
|
pxl8_ase_destroy(&ase_file);
|
||||||
|
|
||||||
if (texture_id == UINT32_MAX) {
|
if (sprite_id == UINT32_MAX) {
|
||||||
pxl8_error("Sprite doesn't fit in atlas");
|
pxl8_error("Sprite doesn't fit in atlas");
|
||||||
return PXL8_ERROR_INVALID_SIZE;
|
return PXL8_ERROR_INVALID_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_debug("Loaded sprite %u: %ux%u at (%d,%d)",
|
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
|
||||||
texture_id, sprite_w, sprite_h,
|
gfx->sprite_cache_capacity *= 2;
|
||||||
gfx->atlas->entries[texture_id].x,
|
gfx->sprite_cache = (pxl8_sprite_cache_entry*)realloc(
|
||||||
gfx->atlas->entries[texture_id].y);
|
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) {
|
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;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pxl8_upload_indexed_texture(
|
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
||||||
SDL_Texture* texture,
|
if (!gfx || !gfx->initialized || !gfx->atlas) return;
|
||||||
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;
|
|
||||||
|
|
||||||
if (buffer_size < needed_size) {
|
if (gfx->hal && gfx->hal->upload_atlas) {
|
||||||
rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4);
|
gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->color_mode);
|
||||||
buffer_size = needed_size;
|
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) {
|
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) {
|
gfx->hal->upload_framebuffer(
|
||||||
SDL_UpdateTexture(
|
gfx->platform_data,
|
||||||
gfx->framebuffer_texture,
|
|
||||||
NULL,
|
|
||||||
gfx->framebuffer,
|
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_width,
|
||||||
gfx->framebuffer_height,
|
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,
|
||||||
gfx->palette_size,
|
gfx->color_mode
|
||||||
gfx->atlas->width,
|
|
||||||
gfx->atlas->height,
|
|
||||||
0x00000000
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
gfx->atlas->dirty = false;
|
|
||||||
pxl8_debug("Atlas uploaded to GPU");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_gfx_present(pxl8_gfx* gfx) {
|
void pxl8_gfx_present(pxl8_gfx* gfx) {
|
||||||
if (!gfx || !gfx->initialized) return;
|
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(gfx->renderer, 0, 0, 0, 255);
|
gfx->hal->present(gfx->platform_data);
|
||||||
SDL_RenderClear(gfx->renderer);
|
|
||||||
|
|
||||||
if (gfx->framebuffer_texture) {
|
|
||||||
SDL_RenderTexture(gfx->renderer, gfx->framebuffer_texture, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderPresent(gfx->renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
|
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) {
|
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 || !gfx->atlas || !gfx->framebuffer) return;
|
||||||
if (!gfx->atlas->entries[sprite_id].active) 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_left = (x < 0) ? -x : 0;
|
||||||
i32 clip_top = (y < 0) ? -y : 0;
|
i32 clip_top = (y < 0) ? -y : 0;
|
||||||
|
|
@ -1078,15 +612,18 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
||||||
bool is_1to1_scale = (w == entry->w && h == entry->h);
|
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);
|
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)) {
|
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) {
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||||
pxl8_blit_simd_hicolor(
|
pxl8_blit_simd_hicolor(
|
||||||
(u32*)gfx->framebuffer,
|
(u32*)gfx->framebuffer,
|
||||||
gfx->framebuffer_width,
|
gfx->framebuffer_width,
|
||||||
(const u32*)sprite_data,
|
(const u32*)sprite_data,
|
||||||
gfx->atlas->width,
|
atlas_width,
|
||||||
x, y, w, h
|
x, y, w, h
|
||||||
);
|
);
|
||||||
} else {
|
} 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,
|
||||||
gfx->framebuffer_width,
|
gfx->framebuffer_width,
|
||||||
sprite_data,
|
sprite_data,
|
||||||
gfx->atlas->width,
|
atlas_width,
|
||||||
x, y, w, h
|
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++) {
|
for (i32 px = 0; px < draw_width; px++) {
|
||||||
i32 src_x = entry->x + ((px + clip_left) * entry->w) / w;
|
i32 src_x = entry->x + ((px + clip_left) * entry->w) / w;
|
||||||
i32 src_y = entry->y + ((py + clip_top) * entry->h) / h;
|
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);
|
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
|
||||||
|
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
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) {
|
if (pixel & 0xFF000000) {
|
||||||
((u32*)gfx->framebuffer)[dest_idx] = pixel;
|
((u32*)gfx->framebuffer)[dest_idx] = pixel;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u8 pixel = gfx->atlas->pixels[src_idx];
|
u8 pixel = atlas_pixels[src_idx];
|
||||||
if (pixel != 0) {
|
if (pixel != 0) {
|
||||||
gfx->framebuffer[dest_idx] = pixel;
|
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_width = gfx->framebuffer_width;
|
||||||
gfx->zbuffer_height = gfx->framebuffer_height;
|
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) {
|
if (!gfx->zbuffer) {
|
||||||
return false;
|
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) {
|
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) return 0;
|
||||||
if (!gfx->atlas->entries[texture_id].active) 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);
|
u = u - floorf(u);
|
||||||
v = v - floorf(v);
|
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_x = entry->x + tx;
|
||||||
i32 atlas_y = entry->y + ty;
|
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) {
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||||
return ((u32*)gfx->atlas->pixels)[idx];
|
return ((const u32*)atlas_pixels)[idx];
|
||||||
} else {
|
} else {
|
||||||
return gfx->atlas->pixels[idx];
|
return atlas_pixels[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "pxl8_hal.h"
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ typedef struct pxl8_triangle {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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);
|
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
||||||
|
|
||||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
||||||
|
|
|
||||||
20
src/pxl8_hal.h
Normal file
20
src/pxl8_hal.h
Normal file
|
|
@ -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;
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <SDL3/SDL.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "pxl8_io.h"
|
#include "pxl8_io.h"
|
||||||
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
||||||
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
|
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;
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
*content = SDL_malloc(file_size + 1);
|
*content = malloc(file_size + 1);
|
||||||
if (!*content) {
|
if (!*content) {
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
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) {
|
void pxl8_io_free_file_content(char* content) {
|
||||||
if (content) {
|
if (content) {
|
||||||
SDL_free(content);
|
free(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_io_free_binary_data(u8* data) {
|
void pxl8_io_free_binary_data(u8* data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
SDL_free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static i32 pxl8_key_code(const char* key_name) {
|
static i32 pxl8_key_code(const char* key_name) {
|
||||||
if (!key_name || !key_name[0]) return 0;
|
if (!key_name || !key_name[0]) return 0;
|
||||||
|
|
||||||
SDL_Scancode scancode = SDL_GetScancodeFromName(key_name);
|
typedef struct { const char* name; i32 code; } KeyMapping;
|
||||||
return (i32)scancode;
|
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) {
|
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
#include <lua.h>
|
#include <lua.h>
|
||||||
#include <lualib.h>
|
#include <lualib.h>
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_script.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* 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;
|
if (!script) return NULL;
|
||||||
|
|
||||||
script->L = luaL_newstate();
|
script->L = luaL_newstate();
|
||||||
if (!script->L) {
|
if (!script->L) {
|
||||||
SDL_free(script);
|
free(script);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +284,7 @@ void pxl8_script_destroy(pxl8_script* script) {
|
||||||
if (script->L) {
|
if (script->L) {
|
||||||
lua_close(script->L);
|
lua_close(script->L);
|
||||||
}
|
}
|
||||||
SDL_free(script);
|
free(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <linenoise.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
#include "pxl8_ui.h"
|
#include "pxl8_ui.h"
|
||||||
|
|
||||||
typedef struct pxl8_script pxl8_script;
|
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
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
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);
|
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path);
|
||||||
bool pxl8_script_check_reload(pxl8_script* script);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
392
src/pxl8_sdl3.c
Normal file
392
src/pxl8_sdl3.c
Normal file
|
|
@ -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 <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
5
src/pxl8_sdl3.h
Normal file
5
src/pxl8_sdl3.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pxl8_hal.h"
|
||||||
|
|
||||||
|
extern const pxl8_hal pxl8_hal_sdl3;
|
||||||
|
|
@ -25,7 +25,7 @@ typedef enum pxl8_color_mode {
|
||||||
PXL8_COLOR_MODE_GBA,
|
PXL8_COLOR_MODE_GBA,
|
||||||
PXL8_COLOR_MODE_HICOLOR,
|
PXL8_COLOR_MODE_HICOLOR,
|
||||||
PXL8_COLOR_MODE_MEGA,
|
PXL8_COLOR_MODE_MEGA,
|
||||||
PXL8_COLOR_MODE_SUPERFAMI
|
PXL8_COLOR_MODE_SNES
|
||||||
} pxl8_color_mode;
|
} pxl8_color_mode;
|
||||||
|
|
||||||
typedef enum pxl8_resolution {
|
typedef enum pxl8_resolution {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include "../lib/microui/src/microui.h"
|
#include "../lib/microui/src/microui.h"
|
||||||
#include "pxl8_font.h"
|
#include "pxl8_font.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_vfx.h"
|
#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 cos_a = cosf(angle);
|
||||||
f32 sin_a = sinf(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;
|
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 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
||||||
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
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) {
|
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;
|
static f32* prev_height = NULL;
|
||||||
if (!prev_height) {
|
if (!prev_height) {
|
||||||
prev_height = (f32*)SDL_calloc(w * h, sizeof(f32));
|
prev_height = (f32*)calloc(w * h, sizeof(f32));
|
||||||
if (!prev_height) return;
|
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* 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;
|
if (!particles) return NULL;
|
||||||
|
|
||||||
particles->particles = SDL_calloc(max_count, sizeof(pxl8_particle));
|
particles->particles = calloc(max_count, sizeof(pxl8_particle));
|
||||||
if (!particles->particles) {
|
if (!particles->particles) {
|
||||||
SDL_free(particles);
|
free(particles);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,8 +187,8 @@ pxl8_particles* pxl8_particles_create(u32 max_count) {
|
||||||
|
|
||||||
void pxl8_particles_destroy(pxl8_particles* particles) {
|
void pxl8_particles_destroy(pxl8_particles* particles) {
|
||||||
if (!particles) return;
|
if (!particles) return;
|
||||||
SDL_free(particles->particles);
|
free(particles->particles);
|
||||||
SDL_free(particles);
|
free(particles);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_particles_clear(pxl8_particles* particles) {
|
void pxl8_particles_clear(pxl8_particles* particles) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
|
||||||
|
|
||||||
#include "pxl8_bsp.h"
|
#include "pxl8_bsp.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_world.h"
|
#include "pxl8_world.h"
|
||||||
|
|
@ -13,7 +12,7 @@ struct pxl8_world {
|
||||||
};
|
};
|
||||||
|
|
||||||
pxl8_world* pxl8_world_create(void) {
|
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) {
|
if (!world) {
|
||||||
pxl8_error("Failed to allocate world");
|
pxl8_error("Failed to allocate world");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -32,7 +31,7 @@ void pxl8_world_destroy(pxl8_world* world) {
|
||||||
pxl8_bsp_destroy(&world->bsp);
|
pxl8_bsp_destroy(&world->bsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(world);
|
free(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue