improve sw renderer

This commit is contained in:
asrael 2026-01-21 23:19:50 -06:00
parent 415d424057
commit 39ee0fefb7
89 changed files with 9380 additions and 2307 deletions

View file

@ -6,7 +6,6 @@
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <unistd.h>
@ -14,6 +13,7 @@
#include "pxl8_hal.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_mem.h"
#include "pxl8_repl.h"
#include "pxl8_replay.h"
#include "pxl8_script.h"
@ -39,23 +39,23 @@ static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32
#endif
pxl8* pxl8_create(const pxl8_hal* hal) {
pxl8* sys = (pxl8*)calloc(1, sizeof(pxl8));
pxl8* sys = (pxl8*)pxl8_calloc(1, sizeof(pxl8));
if (!sys) return NULL;
pxl8_log_init(&sys->log);
if (!hal) {
pxl8_error("hal cannot be null");
free(sys);
pxl8_free(sys);
return NULL;
}
sys->hal = hal;
sys->game = (pxl8_game*)calloc(1, sizeof(pxl8_game));
sys->game = (pxl8_game*)pxl8_calloc(1, sizeof(pxl8_game));
if (!sys->game) {
pxl8_error("failed to allocate game");
free(sys);
pxl8_free(sys);
return NULL;
}
@ -65,11 +65,11 @@ pxl8* pxl8_create(const pxl8_hal* hal) {
void pxl8_destroy(pxl8* sys) {
if (!sys) return;
if (sys->game) free(sys->game);
if (sys->game) pxl8_free(sys->game);
if (sys->cart) pxl8_cart_destroy(sys->cart);
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
free(sys);
pxl8_free(sys);
}
static void pxl8_print_help(void) {
@ -171,7 +171,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
cart_path = ".";
} else {
pxl8_error("no main.fnl or main.lua found in current directory");
free(original_cwd);
pxl8_free(original_cwd);
return PXL8_ERROR_INITIALIZATION_FAILED;
}
}
@ -190,7 +190,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
if (sys->cart) pxl8_cart_destroy(sys->cart);
sys->cart = NULL;
free(original_cwd);
pxl8_free(original_cwd);
return PXL8_ERROR_INITIALIZATION_FAILED;
}
@ -203,7 +203,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
} else if (script_arg) {
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
}
free(original_cwd);
pxl8_free(original_cwd);
const char* window_title = pxl8_cart_get_title(sys->cart);
if (!window_title) window_title = "pxl8";
@ -310,7 +310,7 @@ pxl8_result pxl8_update(pxl8* sys) {
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
const char* err_msg = pxl8_script_get_last_error(game->script);
pxl8_error("failed to setup pxl8 global: %s", err_msg);
pxl8_error("failed to load pxl8 global: %s", err_msg);
}
sys->repl = pxl8_repl_create();

View file

@ -1,35 +1,35 @@
#include "pxl8_bytes.h"
#include <string.h>
void pxl8_pack_u8(u8* buf, size_t offset, u8 val) {
void pxl8_pack_u8(u8* buf, usize offset, u8 val) {
buf[offset] = val;
}
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) {
void pxl8_pack_u16_le(u8* buf, usize offset, u16 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
}
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) {
void pxl8_pack_u16_be(u8* buf, usize offset, u16 val) {
buf[offset] = (u8)(val >> 8);
buf[offset + 1] = (u8)(val);
}
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) {
void pxl8_pack_u32_le(u8* buf, usize offset, u32 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16);
buf[offset + 3] = (u8)(val >> 24);
}
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) {
void pxl8_pack_u32_be(u8* buf, usize offset, u32 val) {
buf[offset] = (u8)(val >> 24);
buf[offset + 1] = (u8)(val >> 16);
buf[offset + 2] = (u8)(val >> 8);
buf[offset + 3] = (u8)(val);
}
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
void pxl8_pack_u64_le(u8* buf, usize offset, u64 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16);
@ -40,7 +40,7 @@ void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
buf[offset + 7] = (u8)(val >> 56);
}
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
void pxl8_pack_u64_be(u8* buf, usize offset, u64 val) {
buf[offset] = (u8)(val >> 56);
buf[offset + 1] = (u8)(val >> 48);
buf[offset + 2] = (u8)(val >> 40);
@ -51,85 +51,85 @@ void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
buf[offset + 7] = (u8)(val);
}
void pxl8_pack_i8(u8* buf, size_t offset, i8 val) {
void pxl8_pack_i8(u8* buf, usize offset, i8 val) {
buf[offset] = (u8)val;
}
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) {
void pxl8_pack_i16_le(u8* buf, usize offset, i16 val) {
pxl8_pack_u16_le(buf, offset, (u16)val);
}
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) {
void pxl8_pack_i16_be(u8* buf, usize offset, i16 val) {
pxl8_pack_u16_be(buf, offset, (u16)val);
}
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) {
void pxl8_pack_i32_le(u8* buf, usize offset, i32 val) {
pxl8_pack_u32_le(buf, offset, (u32)val);
}
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) {
void pxl8_pack_i32_be(u8* buf, usize offset, i32 val) {
pxl8_pack_u32_be(buf, offset, (u32)val);
}
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) {
void pxl8_pack_i64_le(u8* buf, usize offset, i64 val) {
pxl8_pack_u64_le(buf, offset, (u64)val);
}
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) {
void pxl8_pack_i64_be(u8* buf, usize offset, i64 val) {
pxl8_pack_u64_be(buf, offset, (u64)val);
}
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) {
void pxl8_pack_f32_le(u8* buf, usize offset, f32 val) {
u32 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_le(buf, offset, bits);
}
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) {
void pxl8_pack_f32_be(u8* buf, usize offset, f32 val) {
u32 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_be(buf, offset, bits);
}
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) {
void pxl8_pack_f64_le(u8* buf, usize offset, f64 val) {
u64 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_le(buf, offset, bits);
}
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) {
void pxl8_pack_f64_be(u8* buf, usize offset, f64 val) {
u64 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_be(buf, offset, bits);
}
u8 pxl8_unpack_u8(const u8* buf, size_t offset) {
u8 pxl8_unpack_u8(const u8* buf, usize offset) {
return buf[offset];
}
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) {
u16 pxl8_unpack_u16_le(const u8* buf, usize offset) {
return (u16)buf[offset] | ((u16)buf[offset + 1] << 8);
}
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) {
u16 pxl8_unpack_u16_be(const u8* buf, usize offset) {
return ((u16)buf[offset] << 8) | (u16)buf[offset + 1];
}
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) {
u32 pxl8_unpack_u32_le(const u8* buf, usize offset) {
return (u32)buf[offset] |
((u32)buf[offset + 1] << 8) |
((u32)buf[offset + 2] << 16) |
((u32)buf[offset + 3] << 24);
}
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) {
u32 pxl8_unpack_u32_be(const u8* buf, usize offset) {
return ((u32)buf[offset] << 24) |
((u32)buf[offset + 1] << 16) |
((u32)buf[offset + 2] << 8) |
(u32)buf[offset + 3];
}
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
u64 pxl8_unpack_u64_le(const u8* buf, usize offset) {
return (u64)buf[offset] |
((u64)buf[offset + 1] << 8) |
((u64)buf[offset + 2] << 16) |
@ -140,7 +140,7 @@ u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
((u64)buf[offset + 7] << 56);
}
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
u64 pxl8_unpack_u64_be(const u8* buf, usize offset) {
return ((u64)buf[offset] << 56) |
((u64)buf[offset + 1] << 48) |
((u64)buf[offset + 2] << 40) |
@ -151,56 +151,56 @@ u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
(u64)buf[offset + 7];
}
i8 pxl8_unpack_i8(const u8* buf, size_t offset) {
i8 pxl8_unpack_i8(const u8* buf, usize offset) {
return (i8)buf[offset];
}
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) {
i16 pxl8_unpack_i16_le(const u8* buf, usize offset) {
return (i16)pxl8_unpack_u16_le(buf, offset);
}
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) {
i16 pxl8_unpack_i16_be(const u8* buf, usize offset) {
return (i16)pxl8_unpack_u16_be(buf, offset);
}
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) {
i32 pxl8_unpack_i32_le(const u8* buf, usize offset) {
return (i32)pxl8_unpack_u32_le(buf, offset);
}
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) {
i32 pxl8_unpack_i32_be(const u8* buf, usize offset) {
return (i32)pxl8_unpack_u32_be(buf, offset);
}
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) {
i64 pxl8_unpack_i64_le(const u8* buf, usize offset) {
return (i64)pxl8_unpack_u64_le(buf, offset);
}
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) {
i64 pxl8_unpack_i64_be(const u8* buf, usize offset) {
return (i64)pxl8_unpack_u64_be(buf, offset);
}
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) {
f32 pxl8_unpack_f32_le(const u8* buf, usize offset) {
u32 bits = pxl8_unpack_u32_le(buf, offset);
f32 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) {
f32 pxl8_unpack_f32_be(const u8* buf, usize offset) {
u32 bits = pxl8_unpack_u32_be(buf, offset);
f32 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) {
f64 pxl8_unpack_f64_le(const u8* buf, usize offset) {
u64 bits = pxl8_unpack_u64_le(buf, offset);
f64 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) {
f64 pxl8_unpack_f64_be(const u8* buf, usize offset) {
u64 bits = pxl8_unpack_u64_be(buf, offset);
f64 result;
memcpy(&result, &bits, sizeof(result));

View file

@ -10,43 +10,43 @@ void pxl8_bit_set(u32* val, u8 bit);
bool pxl8_bit_test(u32 val, u8 bit);
void pxl8_bit_toggle(u32* val, u8 bit);
void pxl8_pack_u8(u8* buf, size_t offset, u8 val);
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);
void pxl8_pack_i8(u8* buf, size_t offset, i8 val);
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);
void pxl8_pack_u8(u8* buf, usize offset, u8 val);
void pxl8_pack_u16_be(u8* buf, usize offset, u16 val);
void pxl8_pack_u16_le(u8* buf, usize offset, u16 val);
void pxl8_pack_u32_be(u8* buf, usize offset, u32 val);
void pxl8_pack_u32_le(u8* buf, usize offset, u32 val);
void pxl8_pack_u64_be(u8* buf, usize offset, u64 val);
void pxl8_pack_u64_le(u8* buf, usize offset, u64 val);
void pxl8_pack_i8(u8* buf, usize offset, i8 val);
void pxl8_pack_i16_be(u8* buf, usize offset, i16 val);
void pxl8_pack_i16_le(u8* buf, usize offset, i16 val);
void pxl8_pack_i32_be(u8* buf, usize offset, i32 val);
void pxl8_pack_i32_le(u8* buf, usize offset, i32 val);
void pxl8_pack_i64_be(u8* buf, usize offset, i64 val);
void pxl8_pack_i64_le(u8* buf, usize offset, i64 val);
void pxl8_pack_f32_be(u8* buf, usize offset, f32 val);
void pxl8_pack_f32_le(u8* buf, usize offset, f32 val);
void pxl8_pack_f64_be(u8* buf, usize offset, f64 val);
void pxl8_pack_f64_le(u8* buf, usize offset, f64 val);
u8 pxl8_unpack_u8(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);
i8 pxl8_unpack_i8(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);
u8 pxl8_unpack_u8(const u8* buf, usize offset);
u16 pxl8_unpack_u16_be(const u8* buf, usize offset);
u16 pxl8_unpack_u16_le(const u8* buf, usize offset);
u32 pxl8_unpack_u32_be(const u8* buf, usize offset);
u32 pxl8_unpack_u32_le(const u8* buf, usize offset);
u64 pxl8_unpack_u64_be(const u8* buf, usize offset);
u64 pxl8_unpack_u64_le(const u8* buf, usize offset);
i8 pxl8_unpack_i8(const u8* buf, usize offset);
i16 pxl8_unpack_i16_be(const u8* buf, usize offset);
i16 pxl8_unpack_i16_le(const u8* buf, usize offset);
i32 pxl8_unpack_i32_be(const u8* buf, usize offset);
i32 pxl8_unpack_i32_le(const u8* buf, usize offset);
i64 pxl8_unpack_i64_be(const u8* buf, usize offset);
i64 pxl8_unpack_i64_le(const u8* buf, usize offset);
f32 pxl8_unpack_f32_be(const u8* buf, usize offset);
f32 pxl8_unpack_f32_le(const u8* buf, usize offset);
f64 pxl8_unpack_f64_be(const u8* buf, usize offset);
f64 pxl8_unpack_f64_le(const u8* buf, usize offset);
typedef struct {
const u8* bytes;

37
src/core/pxl8_game.h Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_rng.h"
#include "pxl8_script.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
typedef struct pxl8_replay pxl8_replay;
typedef struct pxl8_game {
pxl8_gfx* gfx;
pxl8_script* script;
pxl8_sfx_mixer* mixer;
pxl8_rng rng;
i32 frame_count;
u64 last_time;
f32 time;
f32 fps_accumulator;
i32 fps_frame_count;
f32 fps;
pxl8_input_state input;
pxl8_input_state prev_input;
#ifndef NDEBUG
pxl8_replay* debug_replay;
#endif
bool repl_mode;
bool repl_started;
bool running;
bool script_loaded;
char script_path[256];
} pxl8_game;

View file

@ -1,4 +1,5 @@
#include "pxl8_io.h"
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
@ -10,7 +11,7 @@ static inline char pxl8_to_lower(char c) {
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
}
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, usize* size) {
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
pxl8_cart* cart = pxl8_get_cart();
@ -19,7 +20,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
u32 cart_size = 0;
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
if (result == PXL8_OK) {
*content = realloc(data, cart_size + 1);
*content = pxl8_realloc(data, cart_size + 1);
if (!*content) {
pxl8_cart_free_file(data);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -44,13 +45,13 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
return PXL8_ERROR_SYSTEM_FAILURE;
}
*content = malloc(file_size + 1);
*content = pxl8_malloc(file_size + 1);
if (!*content) {
fclose(file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
size_t bytes_read = fread(*content, 1, file_size, file);
usize bytes_read = fread(*content, 1, file_size, file);
(*content)[bytes_read] = '\0';
*size = bytes_read;
@ -58,7 +59,7 @@ pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
return PXL8_OK;
}
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size) {
pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size) {
if (!path || !content) return PXL8_ERROR_NULL_POINTER;
FILE* file = fopen(path, "wb");
@ -66,17 +67,17 @@ pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t siz
return PXL8_ERROR_SYSTEM_FAILURE;
}
size_t bytes_written = fwrite(content, 1, size, file);
usize bytes_written = fwrite(content, 1, size, file);
fclose(file);
return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
}
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size) {
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size) {
return pxl8_io_read_file(path, (char**)data, size);
}
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size) {
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size) {
return pxl8_io_write_file(path, (const char*)data, size);
}
@ -112,13 +113,13 @@ pxl8_result pxl8_io_create_directory(const char* path) {
void pxl8_io_free_file_content(char* content) {
if (content) {
free(content);
pxl8_free(content);
}
}
void pxl8_io_free_binary_data(u8* data) {
if (data) {
free(data);
pxl8_free(data);
}
}
@ -144,7 +145,7 @@ static i32 pxl8_key_code(const char* key_name) {
};
char lower_name[64];
size_t i;
usize i;
for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) {
lower_name[i] = pxl8_to_lower(key_name[i]);
}

View file

@ -15,10 +15,10 @@ bool pxl8_io_file_exists(const char* path);
void pxl8_io_free_binary_data(u8* data);
void pxl8_io_free_file_content(char* content);
f64 pxl8_io_get_file_modified_time(const char* path);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size);
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);

View file

@ -34,7 +34,7 @@ void pxl8_log_set_level(pxl8_log_level level) {
if (g_log) g_log->level = level;
}
static void log_timestamp(char* buffer, size_t size) {
static void log_timestamp(char* buffer, usize size) {
time_t now = time(NULL);
struct tm* tm_info = localtime(&now);
strftime(buffer, size, "%H:%M:%S", tm_info);

620
src/core/pxl8_replay.c Normal file
View file

@ -0,0 +1,620 @@
#include "pxl8_replay.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_sys.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct pxl8_replay_chunk {
u8 type;
u32 size;
u8* data;
struct pxl8_replay_chunk* next;
} pxl8_replay_chunk;
typedef struct pxl8_keyframe_entry {
pxl8_keyframe keyframe;
pxl8_replay_chunk* input_deltas;
struct pxl8_keyframe_entry* next;
struct pxl8_keyframe_entry* prev;
} pxl8_keyframe_entry;
struct pxl8_replay {
FILE* file;
pxl8_replay_header header;
bool recording;
bool playing;
u32 current_frame;
pxl8_keyframe_entry* keyframes;
pxl8_keyframe_entry* current_keyframe;
u32 keyframe_count;
u32 max_keyframes;
pxl8_replay_chunk* pending_inputs;
pxl8_replay_chunk* pending_inputs_tail;
pxl8_replay_chunk* audio_events;
pxl8_replay_chunk* audio_events_tail;
};
static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) {
while (chunk) {
pxl8_replay_chunk* next = chunk->next;
pxl8_free(chunk->data);
pxl8_free(chunk);
chunk = next;
}
}
static void pxl8_replay_keyframe_entry_free(pxl8_keyframe_entry* entry) {
while (entry) {
pxl8_keyframe_entry* next = entry->next;
pxl8_replay_chunk_free(entry->input_deltas);
pxl8_free(entry);
entry = next;
}
}
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
FILE* f = fopen(path, "wb");
if (!f) {
pxl8_error("Failed to create replay file: %s", path);
return NULL;
}
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) {
fclose(f);
return NULL;
}
r->file = f;
r->recording = true;
r->playing = false;
r->header.magic = PXL8_REPLAY_MAGIC;
r->header.version = PXL8_REPLAY_VERSION;
r->header.keyframe_interval = keyframe_interval;
fwrite(&r->header, sizeof(pxl8_replay_header), 1, f);
fflush(f);
pxl8_info("Created replay file: %s (keyframe interval: %u)", path, keyframe_interval);
return r;
}
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) {
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) return NULL;
r->recording = true;
r->playing = false;
r->max_keyframes = max_keyframes;
r->header.magic = PXL8_REPLAY_MAGIC;
r->header.version = PXL8_REPLAY_VERSION;
r->header.keyframe_interval = keyframe_interval;
pxl8_debug("Created replay buffer (keyframe interval: %u, max: %u)", keyframe_interval, max_keyframes);
return r;
}
pxl8_replay* pxl8_replay_open(const char* path) {
FILE* f = fopen(path, "rb");
if (!f) {
pxl8_error("Failed to open replay file: %s", path);
return NULL;
}
pxl8_replay_header header;
if (fread(&header, sizeof(header), 1, f) != 1) {
pxl8_error("Failed to read replay header");
fclose(f);
return NULL;
}
if (header.magic != PXL8_REPLAY_MAGIC) {
pxl8_error("Invalid replay magic: 0x%08X (expected 0x%08X)", header.magic, PXL8_REPLAY_MAGIC);
fclose(f);
return NULL;
}
if (header.version > PXL8_REPLAY_VERSION) {
pxl8_error("Unsupported replay version: %u (max supported: %u)", header.version, PXL8_REPLAY_VERSION);
fclose(f);
return NULL;
}
pxl8_replay* r = pxl8_calloc(1, sizeof(pxl8_replay));
if (!r) {
fclose(f);
return NULL;
}
r->file = f;
r->header = header;
r->recording = false;
r->playing = true;
while (!feof(f)) {
u8 chunk_type;
if (fread(&chunk_type, 1, 1, f) != 1) break;
if (chunk_type == PXL8_REPLAY_CHUNK_END) break;
u8 size_bytes[3];
if (fread(size_bytes, 3, 1, f) != 1) break;
u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16);
u8* data = pxl8_malloc(size);
if (!data || fread(data, size, 1, f) != 1) {
pxl8_free(data);
break;
}
if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) {
pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
if (entry && size >= sizeof(pxl8_keyframe)) {
memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe));
entry->prev = r->current_keyframe;
if (r->current_keyframe) {
r->current_keyframe->next = entry;
}
r->current_keyframe = entry;
if (!r->keyframes) {
r->keyframes = entry;
}
r->keyframe_count++;
}
pxl8_free(data);
} else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) {
if (r->current_keyframe) {
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (chunk) {
chunk->type = chunk_type;
chunk->size = size;
chunk->data = data;
data = NULL;
if (!r->current_keyframe->input_deltas) {
r->current_keyframe->input_deltas = chunk;
} else {
pxl8_replay_chunk* tail = r->current_keyframe->input_deltas;
while (tail->next) tail = tail->next;
tail->next = chunk;
}
}
}
pxl8_free(data);
} else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) {
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (chunk) {
chunk->type = chunk_type;
chunk->size = size;
chunk->data = data;
data = NULL;
if (!r->audio_events) {
r->audio_events = chunk;
r->audio_events_tail = chunk;
} else {
r->audio_events_tail->next = chunk;
r->audio_events_tail = chunk;
}
}
pxl8_free(data);
} else {
pxl8_free(data);
}
}
r->current_keyframe = r->keyframes;
pxl8_info("Opened replay: %u frames, %u keyframes", r->header.total_frames, r->keyframe_count);
return r;
}
void pxl8_replay_destroy(pxl8_replay* r) {
if (!r) return;
if (r->file) {
if (r->recording) {
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
fwrite(&end_chunk, 1, 1, r->file);
fseek(r->file, 0, SEEK_SET);
fwrite(&r->header, sizeof(pxl8_replay_header), 1, r->file);
}
fclose(r->file);
}
pxl8_replay_keyframe_entry_free(r->keyframes);
pxl8_replay_chunk_free(r->pending_inputs);
pxl8_replay_chunk_free(r->audio_events);
pxl8_free(r);
}
bool pxl8_replay_is_recording(pxl8_replay* r) {
return r && r->recording;
}
bool pxl8_replay_is_playing(pxl8_replay* r) {
return r && r->playing;
}
u32 pxl8_replay_get_frame(pxl8_replay* r) {
return r ? r->current_frame : 0;
}
u32 pxl8_replay_get_total_frames(pxl8_replay* r) {
return r ? r->header.total_frames : 0;
}
static void write_chunk(FILE* f, u8 type, const void* data, u32 size) {
fwrite(&type, 1, 1, f);
u8 size_bytes[3] = {
(u8)(size & 0xFF),
(u8)((size >> 8) & 0xFF),
(u8)((size >> 16) & 0xFF)
};
fwrite(size_bytes, 3, 1, f);
fwrite(data, size, 1, f);
}
static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) {
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (!chunk) return;
chunk->type = type;
chunk->size = size;
chunk->data = pxl8_malloc(size);
if (!chunk->data) {
pxl8_free(chunk);
return;
}
memcpy(chunk->data, data, size);
if (!r->pending_inputs) {
r->pending_inputs = chunk;
r->pending_inputs_tail = chunk;
} else {
r->pending_inputs_tail->next = chunk;
r->pending_inputs_tail = chunk;
}
}
static void pack_keys(const bool* keys, u8* packed) {
memset(packed, 0, 32);
for (int i = 0; i < 256; i++) {
if (keys[i]) {
packed[i / 8] |= (1 << (i % 8));
}
}
}
static void unpack_keys(const u8* packed, bool* keys) {
for (int i = 0; i < 256; i++) {
keys[i] = (packed[i / 8] >> (i % 8)) & 1;
}
}
static u8 pack_mouse_buttons(const bool* buttons) {
return (buttons[0] ? 1 : 0) | (buttons[1] ? 2 : 0) | (buttons[2] ? 4 : 0);
}
static void unpack_mouse_buttons(u8 packed, bool* buttons) {
buttons[0] = (packed & 1) != 0;
buttons[1] = (packed & 2) != 0;
buttons[2] = (packed & 4) != 0;
}
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input) {
if (!r || !r->recording) return;
pxl8_keyframe kf = {0};
kf.frame_number = frame;
kf.time = time;
kf.rng_state = rng ? rng->state : 0;
if (input) {
pack_keys(input->keys_down, kf.keys_down);
kf.mouse_buttons = pack_mouse_buttons(input->mouse_buttons_down);
kf.mouse_x = (i16)input->mouse_x;
kf.mouse_y = (i16)input->mouse_y;
}
if (r->file) {
write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf));
fflush(r->file);
} else {
pxl8_keyframe_entry* entry = pxl8_calloc(1, sizeof(pxl8_keyframe_entry));
if (!entry) return;
entry->keyframe = kf;
entry->input_deltas = r->pending_inputs;
r->pending_inputs = NULL;
r->pending_inputs_tail = NULL;
if (r->keyframe_count >= r->max_keyframes && r->keyframes) {
pxl8_keyframe_entry* oldest = r->keyframes;
r->keyframes = oldest->next;
if (r->keyframes) {
r->keyframes->prev = NULL;
}
pxl8_replay_chunk_free(oldest->input_deltas);
pxl8_free(oldest);
r->keyframe_count--;
}
entry->prev = r->current_keyframe;
if (r->current_keyframe) {
r->current_keyframe->next = entry;
}
r->current_keyframe = entry;
if (!r->keyframes) {
r->keyframes = entry;
}
r->keyframe_count++;
}
r->current_frame = frame;
r->header.total_frames = frame;
}
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr) {
if (!r || !r->recording || !prev || !curr) return;
u8 buffer[256];
u32 offset = 0;
buffer[offset++] = (u8)(frame & 0xFF);
buffer[offset++] = (u8)((frame >> 8) & 0xFF);
buffer[offset++] = (u8)((frame >> 16) & 0xFF);
buffer[offset++] = (u8)((frame >> 24) & 0xFF);
u8 key_event_count = 0;
u8 key_events[64];
u32 key_offset = 0;
for (int i = 0; i < 256 && key_event_count < 32; i++) {
if (prev->keys_down[i] != curr->keys_down[i]) {
key_events[key_offset++] = (u8)i;
key_events[key_offset++] = curr->keys_down[i] ? 1 : 0;
key_event_count++;
}
}
buffer[offset++] = key_event_count;
memcpy(buffer + offset, key_events, key_offset);
offset += key_offset;
u8 prev_mouse_btns = pack_mouse_buttons(prev->mouse_buttons_down);
u8 curr_mouse_btns = pack_mouse_buttons(curr->mouse_buttons_down);
u8 mouse_flags = 0;
if (curr->mouse_x != prev->mouse_x || curr->mouse_y != prev->mouse_y) {
mouse_flags |= 0x01;
}
if (curr_mouse_btns != prev_mouse_btns) {
mouse_flags |= 0x02;
}
if (curr->mouse_wheel_x != 0 || curr->mouse_wheel_y != 0) {
mouse_flags |= 0x04;
}
buffer[offset++] = mouse_flags;
if (mouse_flags & 0x01) {
i16 dx = (i16)(curr->mouse_x - prev->mouse_x);
i16 dy = (i16)(curr->mouse_y - prev->mouse_y);
buffer[offset++] = (u8)(dx & 0xFF);
buffer[offset++] = (u8)((dx >> 8) & 0xFF);
buffer[offset++] = (u8)(dy & 0xFF);
buffer[offset++] = (u8)((dy >> 8) & 0xFF);
}
if (mouse_flags & 0x02) {
buffer[offset++] = curr_mouse_btns;
}
if (mouse_flags & 0x04) {
buffer[offset++] = (i8)curr->mouse_wheel_x;
buffer[offset++] = (i8)curr->mouse_wheel_y;
}
if (r->file) {
write_chunk(r->file, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
} else {
add_chunk_to_buffer(r, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
}
r->current_frame = frame;
r->header.total_frames = frame;
}
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume) {
if (!r || !r->recording) return;
pxl8_audio_event evt = {
.frame_number = frame,
.event_type = event_type,
.context_id = context_id,
.note = note,
.volume = volume
};
if (r->file) {
write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt));
} else {
pxl8_replay_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_replay_chunk));
if (!chunk) return;
chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT;
chunk->size = sizeof(evt);
chunk->data = pxl8_malloc(sizeof(evt));
if (!chunk->data) {
pxl8_free(chunk);
return;
}
memcpy(chunk->data, &evt, sizeof(evt));
if (!r->audio_events) {
r->audio_events = chunk;
r->audio_events_tail = chunk;
} else {
r->audio_events_tail->next = chunk;
r->audio_events_tail = chunk;
}
}
}
void pxl8_replay_close(pxl8_replay* r) {
pxl8_replay_destroy(r);
}
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame) {
if (!r || !r->playing) return false;
pxl8_keyframe_entry* target = NULL;
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
if (e->keyframe.frame_number <= frame) {
target = e;
} else {
break;
}
}
if (!target) return false;
r->current_keyframe = target;
r->current_frame = target->keyframe.frame_number;
return true;
}
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out) {
if (!r || !r->playing || !r->current_keyframe) return false;
u32 target_frame = r->current_frame + 1;
if (target_frame > r->header.total_frames) return false;
pxl8_replay_chunk* delta = r->current_keyframe->input_deltas;
while (delta) {
if (delta->size >= 5) {
u32 delta_frame = delta->data[0] | (delta->data[1] << 8) | (delta->data[2] << 16) | (delta->data[3] << 24);
if (delta_frame == target_frame) {
u32 offset = 4;
u8 key_event_count = delta->data[offset++];
for (u8 i = 0; i < key_event_count && offset + 1 < delta->size; i++) {
u8 scancode = delta->data[offset++];
u8 pressed = delta->data[offset++];
input_out->keys_down[scancode] = pressed != 0;
}
if (offset < delta->size) {
u8 mouse_flags = delta->data[offset++];
if (mouse_flags & 0x01 && offset + 3 < delta->size) {
i16 dx = (i16)(delta->data[offset] | (delta->data[offset + 1] << 8));
i16 dy = (i16)(delta->data[offset + 2] | (delta->data[offset + 3] << 8));
offset += 4;
input_out->mouse_x += dx;
input_out->mouse_y += dy;
}
if (mouse_flags & 0x02 && offset < delta->size) {
u8 mouse_btns = delta->data[offset++];
unpack_mouse_buttons(mouse_btns, input_out->mouse_buttons_down);
}
if (mouse_flags & 0x04 && offset + 1 < delta->size) {
input_out->mouse_wheel_x = (i8)delta->data[offset++];
input_out->mouse_wheel_y = (i8)delta->data[offset++];
}
}
r->current_frame = target_frame;
return true;
}
}
delta = delta->next;
}
if (r->current_keyframe->next && r->current_keyframe->next->keyframe.frame_number <= target_frame) {
r->current_keyframe = r->current_keyframe->next;
}
r->current_frame = target_frame;
return true;
}
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out) {
if (!r || !out) return false;
pxl8_keyframe_entry* target = NULL;
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
if (e->keyframe.frame_number <= frame) {
target = e;
} else {
break;
}
}
if (!target) return false;
*out = target->keyframe;
return true;
}
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input) {
if (!kf) return;
if (rng) {
rng->state = kf->rng_state;
}
if (input) {
unpack_keys(kf->keys_down, input->keys_down);
unpack_mouse_buttons(kf->mouse_buttons, input->mouse_buttons_down);
input->mouse_x = kf->mouse_x;
input->mouse_y = kf->mouse_y;
}
if (r) {
r->current_frame = kf->frame_number;
}
}
bool pxl8_replay_export(pxl8_replay* r, const char* path) {
if (!r || !r->keyframes) return false;
FILE* f = fopen(path, "wb");
if (!f) {
pxl8_error("Failed to create export file: %s", path);
return false;
}
pxl8_replay_header header = r->header;
fwrite(&header, sizeof(header), 1, f);
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
write_chunk(f, PXL8_REPLAY_CHUNK_KEYFRAME, &e->keyframe, sizeof(pxl8_keyframe));
for (pxl8_replay_chunk* c = e->input_deltas; c; c = c->next) {
write_chunk(f, c->type, c->data, c->size);
}
}
for (pxl8_replay_chunk* c = r->audio_events; c; c = c->next) {
write_chunk(f, c->type, c->data, c->size);
}
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
fwrite(&end_chunk, 1, 1, f);
fclose(f);
pxl8_info("Exported replay to: %s (%u frames)", path, header.total_frames);
return true;
}

84
src/core/pxl8_replay.h Normal file
View file

@ -0,0 +1,84 @@
#pragma once
#include "pxl8_io.h"
#include "pxl8_rng.h"
#include "pxl8_types.h"
#define PXL8_REPLAY_MAGIC 0x31525850
#define PXL8_REPLAY_VERSION 1
#define PXL8_REPLAY_CHUNK_KEYFRAME 0x01
#define PXL8_REPLAY_CHUNK_INPUT 0x02
#define PXL8_REPLAY_CHUNK_AUDIO_EVENT 0x03
#define PXL8_REPLAY_CHUNK_END 0xFF
#define PXL8_REPLAY_FLAG_HAS_PALETTE (1 << 0)
#define PXL8_REPLAY_FLAG_HAS_GLOBALS (1 << 1)
typedef struct pxl8_replay pxl8_replay;
typedef struct pxl8_replay_header {
u32 magic;
u32 version;
u32 flags;
u32 keyframe_interval;
u32 total_frames;
u32 width;
u32 height;
u32 reserved;
} pxl8_replay_header;
typedef struct pxl8_keyframe {
u32 frame_number;
f32 time;
u32 rng_state;
u8 keys_down[32];
u8 mouse_buttons;
i16 mouse_x;
i16 mouse_y;
u8 flags;
} pxl8_keyframe;
typedef struct pxl8_input_delta {
u32 frame_number;
u8 key_event_count;
u8 mouse_flags;
} pxl8_input_delta;
typedef struct pxl8_audio_event {
u32 frame_number;
u8 event_type;
u8 context_id;
u8 note;
f32 volume;
} pxl8_audio_event;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval);
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes);
pxl8_replay* pxl8_replay_open(const char* path);
void pxl8_replay_destroy(pxl8_replay* r);
bool pxl8_replay_is_recording(pxl8_replay* r);
bool pxl8_replay_is_playing(pxl8_replay* r);
u32 pxl8_replay_get_frame(pxl8_replay* r);
u32 pxl8_replay_get_total_frames(pxl8_replay* r);
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input);
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr);
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume);
void pxl8_replay_close(pxl8_replay* r);
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame);
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out);
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out);
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input);
bool pxl8_replay_export(pxl8_replay* r, const char* path);
#ifdef __cplusplus
}
#endif

View file

@ -23,6 +23,9 @@ typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef size_t usize;
typedef ptrdiff_t isize;
#if defined(__SIZEOF_INT128__)
typedef __int128_t i128;
typedef __uint128_t u128;