true 16-bit color... glorious

This commit is contained in:
asrael 2025-11-28 14:41:35 -06:00
parent 3dccce8a81
commit b1e8525c3e
30 changed files with 678 additions and 652 deletions

View file

@ -1,9 +1,15 @@
# pxl8
### Requirements
- **Clang 19+** (or GCC 15+) - Required for C23 `#embed` support
- **SDL3** - System package or auto-vendored
### Quick Start
```bash
./pxl8.sh build # Build pxl8 system
./pxl8.sh install # Install pxl8 binary to ~/.local/bin
./pxl8.sh run [game.cart | project_dir] # Run pxl8 demo (or a specific game)
./pxl8.sh run [game.cart | project_dir] --repl # Run pxl8 demo (or a specific game) with a REPL
```

View file

@ -3,6 +3,7 @@
(var world nil)
(var auto-run? false)
(var auto-run-cancel-key nil)
(var cam-pitch 0)
(var cam-x 1000)
(var cam-y 64)
@ -77,7 +78,15 @@
(fn update [dt]
(when (pxl8.key_pressed "`")
(set auto-run? (not auto-run?)))
(set auto-run? (not auto-run?))
(when (and auto-run? (pxl8.key_down "w"))
(set auto-run-cancel-key "w")))
(when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s")))
(set auto-run? false)
(when (pxl8.key_down "s")
(set auto-run-cancel-key "s")))
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
(set auto-run-cancel-key nil))
(when (pxl8.world_is_loaded world)
(let [forward-x (- (math.sin cam-yaw))
@ -94,7 +103,7 @@
(when (or (pxl8.key_down "w") auto-run?)
(set move-forward (+ move-forward 1)))
(when (pxl8.key_down "s")
(when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s"))
(set move-forward (- move-forward 1)))
(when (pxl8.key_down "a")

View file

@ -2,7 +2,7 @@
set -e
CC="${CC:-gcc}"
CC="${CC:-clang}"
if command -v ccache >/dev/null 2>&1; then
CC="ccache $CC"
@ -338,16 +338,17 @@ case "$COMMAND" in
src/pxl8_bsp.c
src/pxl8_cart.c
src/pxl8_font.c
src/pxl8_gen.c
src/pxl8_gfx.c
src/pxl8_gui.c
src/pxl8_io.c
src/pxl8_math.c
src/pxl8_procgen.c
src/pxl8_rec.c
src/pxl8_script.c
src/pxl8_sdl3.c
src/pxl8_tilemap.c
src/pxl8_tilesheet.c
src/pxl8_transition.c
src/pxl8_gui.c
src/pxl8_vfx.c
src/pxl8_world.c
"

View file

@ -148,7 +148,8 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
if (pxl8_cart_load(sys->cart, cart_path) == PXL8_OK) {
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
pxl8_cart_mount(sys->cart);
strcpy(game->script_path, "main.fnl");
strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1);
game->script_path[sizeof(game->script_path) - 1] = '\0';
pxl8_info("Loaded cart: %s", pxl8_cart_get_name(sys->cart));
} else {
pxl8_error("Failed to load cart: %s", cart_path);
@ -369,7 +370,7 @@ u32 pxl8_get_palette_size(pxl8_color_mode mode) {
case PXL8_COLOR_MODE_HICOLOR: return 0;
case PXL8_COLOR_MODE_FAMI: return 64;
case PXL8_COLOR_MODE_MEGA: return 512;
case PXL8_COLOR_MODE_GBA: return 32768;
case PXL8_COLOR_MODE_GBA:
case PXL8_COLOR_MODE_SNES: return 32768;
default: return 256;
}

View file

@ -242,10 +242,7 @@ static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* ti
}
if (tileset->flags & 1) {
u32 external_file_id = pxl8_read_u32(stream);
u32 tileset_id = pxl8_read_u32(stream);
(void)external_file_id;
(void)tileset_id;
pxl8_skip_bytes(stream, 8); // external_file_id + tileset_id
}
if (tileset->flags & 2) {
@ -458,13 +455,14 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
break;
case PXL8_ASE_CHUNK_LAYER: {
ase_file->layers =
pxl8_ase_layer* new_layers =
(pxl8_ase_layer*)realloc(ase_file->layers,
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
if (!ase_file->layers) {
if (!new_layers) {
result = PXL8_ERROR_OUT_OF_MEMORY;
break;
}
ase_file->layers = new_layers;
result = parse_layer_chunk(&stream, &ase_file->layers[ase_file->layer_count]);
if (result == PXL8_OK) {
@ -477,11 +475,12 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
pxl8_ase_cel cel = {0};
result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
if (result == PXL8_OK) {
frame->cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
if (!frame->cels) {
pxl8_ase_cel* new_cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
if (!new_cels) {
result = PXL8_ERROR_OUT_OF_MEMORY;
break;
}
frame->cels = new_cels;
frame->cels[frame->cel_count] = cel;
frame->cel_count++;
@ -522,12 +521,13 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
break;
case PXL8_ASE_CHUNK_TILESET: {
ase_file->tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets,
pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets,
(ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset));
if (!ase_file->tilesets) {
if (!new_tilesets) {
result = PXL8_ERROR_OUT_OF_MEMORY;
break;
}
ase_file->tilesets = new_tilesets;
memset(&ase_file->tilesets[ase_file->tileset_count], 0, sizeof(pxl8_ase_tileset));
result = parse_tileset_chunk(&stream, &ase_file->tilesets[ase_file->tileset_count]);

View file

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include "pxl8_color.h"
#include "pxl8_macros.h"
typedef struct pxl8_skyline_fit {
@ -82,7 +83,7 @@ static pxl8_skyline_fit pxl8_skyline_find_position(
return result;
}
static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w, u32 h) {
static bool 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) {
@ -101,11 +102,14 @@ static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w,
}
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2;
skyline->nodes = (pxl8_skyline_node*)realloc(
u32 new_capacity = (skyline->count - nodes_to_remove + 1) * 2;
pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)realloc(
skyline->nodes,
skyline->capacity * sizeof(pxl8_skyline_node)
new_capacity * sizeof(pxl8_skyline_node)
);
if (!new_nodes) return false;
skyline->nodes = new_nodes;
skyline->capacity = new_capacity;
}
if (nodes_to_remove > 0) {
@ -118,6 +122,7 @@ static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w,
skyline->nodes[node_idx] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w};
skyline->count = skyline->count - nodes_to_remove + 1;
return true;
}
static void pxl8_skyline_compact(pxl8_skyline* skyline) {
@ -143,14 +148,14 @@ pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_color_mode color_mode)
atlas->height = height;
atlas->width = width;
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(color_mode);
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel);
if (!atlas->pixels) {
free(atlas);
return NULL;
}
atlas->entry_capacity = 64;
atlas->entry_capacity = PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY;
atlas->entries = (pxl8_atlas_entry*)calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
if (!atlas->entries) {
free(atlas->pixels);
@ -197,7 +202,7 @@ void pxl8_atlas_destroy(pxl8_atlas* 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;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(color_mode);
u32 new_size = atlas->width * 2;
u32 old_width = atlas->width;
@ -236,8 +241,8 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_color_mode color_mode) {
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];
if (bytes_per_pixel == 2) {
((u16*)new_pixels)[dst_idx] = ((u16*)atlas->pixels)[src_idx];
} else {
new_pixels[dst_idx] = atlas->pixels[src_idx];
}
@ -247,7 +252,11 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_color_mode color_mode) {
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);
if (!pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h)) {
free(new_skyline.nodes);
free(new_pixels);
return false;
}
pxl8_skyline_compact(&new_skyline);
}
@ -290,11 +299,14 @@ u32 pxl8_atlas_add_texture(
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(
u32 new_capacity = atlas->entry_capacity * 2;
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)realloc(
atlas->entries,
atlas->entry_capacity * sizeof(pxl8_atlas_entry)
new_capacity * sizeof(pxl8_atlas_entry)
);
if (!new_entries) return UINT32_MAX;
atlas->entries = new_entries;
atlas->entry_capacity = new_capacity;
}
texture_id = atlas->entry_count++;
}
@ -307,21 +319,24 @@ u32 pxl8_atlas_add_texture(
entry->w = w;
entry->h = h;
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(color_mode);
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];
if (bytes_per_pixel == 2) {
((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx];
} else {
atlas->pixels[dst_idx] = pixels[src_idx];
}
}
}
pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h);
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
entry->active = false;
return UINT32_MAX;
}
pxl8_skyline_compact(&atlas->skyline);
atlas->dirty = true;

View file

@ -1,16 +1,16 @@
#include "pxl8_blit.h"
void pxl8_blit_hicolor(u32* fb, u32 fb_width, const u32* sprite, u32 atlas_width,
void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width,
i32 x, i32 y, u32 w, u32 h) {
u32* dest_base = fb + y * fb_width + x;
const u32* src_base = sprite;
u16* dest_base = fb + y * fb_width + x;
const u16* src_base = sprite;
for (u32 row = 0; row < h; row++) {
u32* dest_row = dest_base + row * fb_width;
const u32* src_row = src_base + row * atlas_width;
u16* dest_row = dest_base + row * fb_width;
const u16* src_row = src_base + row * atlas_width;
for (u32 col = 0; col < w; col++) {
if (src_row[col] & 0xFF000000) {
if (src_row[col] != 0) {
dest_row[col] = src_row[col];
}
}

View file

@ -7,8 +7,8 @@ extern "C" {
#endif
void pxl8_blit_hicolor(
u32* fb, u32 fb_width,
const u32* sprite, u32 atlas_width,
u16* fb, u32 fb_width,
const u16* sprite, u32 atlas_width,
i32 x, i32 y, u32 w, u32 h
);
void pxl8_blit_indexed(

View file

@ -468,8 +468,9 @@ void pxl8_bsp_render_textured(
static u32 rendered_faces_capacity = 0;
if (rendered_faces_capacity < bsp->num_faces) {
rendered_faces = realloc(rendered_faces, bsp->num_faces);
if (!rendered_faces) return;
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
if (!new_buffer) return;
rendered_faces = new_buffer;
rendered_faces_capacity = bsp->num_faces;
pxl8_debug("Allocated face tracking buffer: %u bytes", bsp->num_faces);
}

View file

@ -21,8 +21,8 @@ struct pxl8_cart {
char* name;
};
static pxl8_cart* __pxl8_current_cart = NULL;
static char* __pxl8_original_cwd = NULL;
static pxl8_cart* pxl8_current_cart = NULL;
static char* pxl8_original_cwd = NULL;
static void pxl8_add_file_recursive(mz_zip_archive* zip, const char* dir_path, const char* prefix) {
DIR* dir = opendir(dir_path);
@ -89,7 +89,7 @@ pxl8_cart* pxl8_cart_create(void) {
}
pxl8_cart* pxl8_cart_current(void) {
return __pxl8_current_cart;
return pxl8_current_cart;
}
void pxl8_cart_destroy(pxl8_cart* cart) {
@ -254,20 +254,20 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
if (!cart || !cart->base_path) return PXL8_ERROR_NULL_POINTER;
if (cart->is_mounted) return PXL8_OK;
if (__pxl8_current_cart) {
pxl8_cart_unmount(__pxl8_current_cart);
if (pxl8_current_cart) {
pxl8_cart_unmount(pxl8_current_cart);
}
__pxl8_original_cwd = getcwd(NULL, 0);
pxl8_original_cwd = getcwd(NULL, 0);
if (chdir(cart->base_path) != 0) {
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
free(__pxl8_original_cwd);
__pxl8_original_cwd = NULL;
free(pxl8_original_cwd);
pxl8_original_cwd = NULL;
return PXL8_ERROR_FILE_NOT_FOUND;
}
cart->is_mounted = true;
__pxl8_current_cart = cart;
pxl8_current_cart = cart;
pxl8_info("Mounted cart: %s", cart->name);
return PXL8_OK;
@ -276,15 +276,15 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
void pxl8_cart_unmount(pxl8_cart* cart) {
if (!cart || !cart->is_mounted) return;
if (__pxl8_original_cwd) {
chdir(__pxl8_original_cwd);
free(__pxl8_original_cwd);
__pxl8_original_cwd = NULL;
if (pxl8_original_cwd) {
chdir(pxl8_original_cwd);
free(pxl8_original_cwd);
pxl8_original_cwd = NULL;
}
cart->is_mounted = false;
if (__pxl8_current_cart == cart) {
__pxl8_current_cart = NULL;
if (pxl8_current_cart == cart) {
pxl8_current_cart = NULL;
}
pxl8_info("Unmounted cart: %s", cart->name);

42
src/pxl8_color.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "pxl8_types.h"
static inline i32 pxl8_bytes_per_pixel(pxl8_color_mode mode) {
return (mode == PXL8_COLOR_MODE_HICOLOR) ? 2 : 1;
}
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}
static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) {
*r = (color >> 11) << 3;
*g = ((color >> 5) & 0x3F) << 2;
*b = (color & 0x1F) << 3;
}
static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) {
return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF);
}
static inline u32 pxl8_rgb565_to_rgba32(u16 color) {
u8 r, g, b;
pxl8_rgb565_unpack(color, &r, &g, &b);
return r | (g << 8) | (b << 16) | 0xFF000000;
}
static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
*r = color & 0xFF;
*g = (color >> 8) & 0xFF;
*b = (color >> 16) & 0xFF;
*a = (color >> 24) & 0xFF;
}
static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) {
return r | (g << 8) | (b << 16) | (a << 24);
}
static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) {
return c1 + (i32)((c2 - c1) * t);
}

88
src/pxl8_embed.h Normal file
View file

@ -0,0 +1,88 @@
#pragma once
#include "pxl8_types.h"
#include <string.h>
static const char embed_fennel[] = {
#embed "lib/fennel/fennel.lua"
, 0 };
static const char embed_pxl8[] = {
#embed "src/lua/pxl8.lua"
, 0 };
static const char embed_pxl8_anim[] = {
#embed "src/lua/pxl8/anim.lua"
, 0 };
static const char embed_pxl8_core[] = {
#embed "src/lua/pxl8/core.lua"
, 0 };
static const char embed_pxl8_gfx2d[] = {
#embed "src/lua/pxl8/gfx2d.lua"
, 0 };
static const char embed_pxl8_gfx3d[] = {
#embed "src/lua/pxl8/gfx3d.lua"
, 0 };
static const char embed_pxl8_gui[] = {
#embed "src/lua/pxl8/gui.lua"
, 0 };
static const char embed_pxl8_input[] = {
#embed "src/lua/pxl8/input.lua"
, 0 };
static const char embed_pxl8_math[] = {
#embed "src/lua/pxl8/math.lua"
, 0 };
static const char embed_pxl8_particles[] = {
#embed "src/lua/pxl8/particles.lua"
, 0 };
static const char embed_pxl8_tilemap[] = {
#embed "src/lua/pxl8/tilemap.lua"
, 0 };
static const char embed_pxl8_transition[] = {
#embed "src/lua/pxl8/transition.lua"
, 0 };
static const char embed_pxl8_vfx[] = {
#embed "src/lua/pxl8/vfx.lua"
, 0 };
static const char embed_pxl8_world[] = {
#embed "src/lua/pxl8/world.lua"
, 0 };
#define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1}
typedef struct { const char* name; const char* data; u32 size; } pxl8_embed;
static const pxl8_embed pxl8_embeds[] = {
PXL8_EMBED_ENTRY(embed_fennel, "fennel"),
PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"),
PXL8_EMBED_ENTRY(embed_pxl8_anim, "pxl8.anim"),
PXL8_EMBED_ENTRY(embed_pxl8_core, "pxl8.core"),
PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"),
PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"),
PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"),
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),
PXL8_EMBED_ENTRY(embed_pxl8_vfx, "pxl8.vfx"),
PXL8_EMBED_ENTRY(embed_pxl8_world, "pxl8.world"),
{0}
};
static inline const pxl8_embed* pxl8_embed_find(const char* name) {
for (const pxl8_embed* e = pxl8_embeds; e->name; e++)
if (strcmp(e->name, name) == 0) return e;
return NULL;
}

View file

@ -1,4 +1,4 @@
#include "pxl8_procgen.h"
#include "pxl8_gen.h"
#include <stdlib.h>
#include <string.h>
@ -487,7 +487,7 @@ void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
if (pattern < 0.25f) color = params->base_color;
else if (pattern < 0.50f) color = params->base_color + 1;
else if (pattern < 0.75f) color = params->base_color + 2;
else color = params->base_color + 1;
else color = params->base_color + 3;
}
// Brick pattern (wall style)
else {

View file

@ -6,6 +6,7 @@
#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_blit.h"
#include "pxl8_color.h"
#include "pxl8_font.h"
#include "pxl8_hal.h"
#include "pxl8_macros.h"
@ -55,21 +56,6 @@ struct pxl8_gfx {
};
static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
*r = color & 0xFF;
*g = (color >> 8) & 0xFF;
*b = (color >> 16) & 0xFF;
*a = (color >> 24) & 0xFF;
}
static inline u32 pxl8_color_pack(u8 r, u8 g, u8 b, u8 a) {
return r | (g << 8) | (b << 16) | (a << 24);
}
static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) {
return c1 + (i32)((c2 - c1) * t);
}
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
pxl8_bounds bounds = {0};
if (!gfx) {
@ -84,8 +70,14 @@ pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx) {
return gfx ? gfx->color_mode : PXL8_COLOR_MODE_FAMI;
}
u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer : NULL;
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
if (!gfx || gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return NULL;
return gfx->framebuffer;
}
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
if (!gfx || gfx->color_mode != PXL8_COLOR_MODE_HICOLOR) return NULL;
return (u16*)gfx->framebuffer;
}
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
@ -126,7 +118,7 @@ pxl8_gfx* pxl8_gfx_create(
return NULL;
}
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(gfx->color_mode);
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
gfx->framebuffer = (u8*)calloc(1, fb_size);
if (!gfx->framebuffer) {
@ -183,14 +175,17 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
free(gfx);
}
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
if (gfx->atlas) return PXL8_OK;
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->color_mode);
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
}
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height) {
if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT;
if (!gfx->atlas) {
gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode);
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
}
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
if (result != PXL8_OK) return result;
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->color_mode);
if (texture_id == UINT32_MAX) {
@ -205,7 +200,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
if (!gfx->sprite_cache) {
gfx->sprite_cache_capacity = 64;
gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY;
gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc(
gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry)
);
@ -218,13 +213,11 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
}
}
if (!gfx->atlas) {
gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode);
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
}
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
if (result != PXL8_OK) return result;
pxl8_ase_file ase_file;
pxl8_result result = pxl8_ase_load(path, &ase_file);
result = pxl8_ase_load(path, &ase_file);
if (result != PXL8_OK) {
pxl8_error("Failed to load ASE file: %s", path);
return result;
@ -252,11 +245,14 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
}
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
gfx->sprite_cache_capacity *= 2;
gfx->sprite_cache = (pxl8_sprite_cache_entry*)realloc(
u32 new_capacity = gfx->sprite_cache_capacity * 2;
pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc(
gfx->sprite_cache,
gfx->sprite_cache_capacity * sizeof(pxl8_sprite_cache_entry)
new_capacity * sizeof(pxl8_sprite_cache_entry)
);
if (!new_cache) return PXL8_ERROR_OUT_OF_MEMORY;
gfx->sprite_cache = new_cache;
gfx->sprite_cache_capacity = new_capacity;
}
pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++];
@ -271,10 +267,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
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);
}
if (pxl8_gfx_ensure_atlas(gfx) != PXL8_OK) return NULL;
return gfx->atlas;
}
@ -369,14 +362,13 @@ void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
void pxl8_clear(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->framebuffer) return;
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
i32 size = gfx->framebuffer_width * gfx->framebuffer_height;
if (bytes_per_pixel == 4) {
u32* fb32 = (u32*)gfx->framebuffer;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
u16* fb16 = (u16*)gfx->framebuffer;
u16 color16 = pxl8_rgba32_to_rgb565(color);
for (i32 i = 0; i < size; i++) {
fb32[i] = color;
fb16[i] = color16;
}
} else {
memset(gfx->framebuffer, color & 0xFF, size);
@ -389,7 +381,7 @@ void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
i32 idx = y * gfx->framebuffer_width + x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
((u32*)gfx->framebuffer)[idx] = color;
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
} else {
gfx->framebuffer[idx] = color & 0xFF;
}
@ -401,7 +393,7 @@ u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) {
i32 idx = y * gfx->framebuffer_width + x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
return ((u32*)gfx->framebuffer)[idx];
return pxl8_rgb565_to_rgba32(((u16*)gfx->framebuffer)[idx]);
} else {
return gfx->framebuffer[idx];
}
@ -445,7 +437,7 @@ void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
static inline void pxl8_pixel_unchecked(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
i32 idx = y * gfx->framebuffer_width + x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
((u32*)gfx->framebuffer)[idx] = color;
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
} else {
gfx->framebuffer[idx] = color & 0xFF;
}
@ -543,7 +535,7 @@ void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
if (pixel_bit) {
i32 fb_idx = py * gfx->framebuffer_width + px;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
((u32*)gfx->framebuffer)[fb_idx] = color;
((u16*)gfx->framebuffer)[fb_idx] = pxl8_rgba32_to_rgb565(color);
} else {
gfx->framebuffer[fb_idx] = (u8)color;
}
@ -582,17 +574,17 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
if (is_1to1_scale && is_unclipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
const u16* sprite_data = (const u16*)atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_hicolor(
(u32*)gfx->framebuffer,
(u16*)gfx->framebuffer,
gfx->framebuffer_width,
(const u32*)sprite_data,
sprite_data,
atlas_width,
x, y, w, h
);
} else {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_indexed(
gfx->framebuffer,
gfx->framebuffer_width,
@ -610,9 +602,9 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
u32 pixel = ((const u32*)atlas_pixels)[src_idx];
if (pixel & 0xFF000000) {
((u32*)gfx->framebuffer)[dest_idx] = pixel;
u16 pixel = ((const u16*)atlas_pixels)[src_idx];
if (pixel != 0) {
((u16*)gfx->framebuffer)[dest_idx] = pixel;
}
} else {
u8 pixel = atlas_pixels[src_idx];
@ -655,18 +647,18 @@ void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 ta
if (amount > 1.0f) amount = 1.0f;
u8 target_r, target_g, target_b, target_a;
pxl8_color_unpack(target_color, &target_r, &target_g, &target_b, &target_a);
pxl8_rgba32_unpack(target_color, &target_r, &target_g, &target_b, &target_a);
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
u8 cur_r, cur_g, cur_b, cur_a;
pxl8_color_unpack(gfx->palette[start + i], &cur_r, &cur_g, &cur_b, &cur_a);
pxl8_rgba32_unpack(gfx->palette[start + i], &cur_r, &cur_g, &cur_b, &cur_a);
u8 new_r = pxl8_color_lerp_channel(cur_r, target_r, amount);
u8 new_g = pxl8_color_lerp_channel(cur_g, target_g, amount);
u8 new_b = pxl8_color_lerp_channel(cur_b, target_b, amount);
u8 new_a = pxl8_color_lerp_channel(cur_a, target_a, amount);
gfx->palette[start + i] = pxl8_color_pack(new_r, new_g, new_b, new_a);
gfx->palette[start + i] = pxl8_rgba32_pack(new_r, new_g, new_b, new_a);
}
}
@ -685,15 +677,15 @@ void pxl8_gfx_interpolate_palettes(
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
u8 r1, g1, b1, a1, r2, g2, b2, a2;
pxl8_color_unpack(palette1[i], &r1, &g1, &b1, &a1);
pxl8_color_unpack(palette2[i], &r2, &g2, &b2, &a2);
pxl8_rgba32_unpack(palette1[i], &r1, &g1, &b1, &a1);
pxl8_rgba32_unpack(palette2[i], &r2, &g2, &b2, &a2);
u8 r = pxl8_color_lerp_channel(r1, r2, t);
u8 g = pxl8_color_lerp_channel(g1, g2, t);
u8 b = pxl8_color_lerp_channel(b1, b2, t);
u8 a = pxl8_color_lerp_channel(a1, a2, t);
gfx->palette[start + i] = pxl8_color_pack(r, g, b, a);
gfx->palette[start + i] = pxl8_rgba32_pack(r, g, b, a);
}
}
@ -702,8 +694,8 @@ void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32
u8 from_r, from_g, from_b, from_a;
u8 to_r, to_g, to_b, to_a;
pxl8_color_unpack(from_color, &from_r, &from_g, &from_b, &from_a);
pxl8_color_unpack(to_color, &to_r, &to_g, &to_b, &to_a);
pxl8_rgba32_unpack(from_color, &from_r, &from_g, &from_b, &from_a);
pxl8_rgba32_unpack(to_color, &to_r, &to_g, &to_b, &to_a);
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
f32 t = (f32)i / (f32)(count - 1);
@ -713,7 +705,7 @@ void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32
u8 b = pxl8_color_lerp_channel(from_b, to_b, t);
u8 a = pxl8_color_lerp_channel(from_a, to_a, t);
gfx->palette[start + i] = pxl8_color_pack(r, g, b, a);
gfx->palette[start + i] = pxl8_rgba32_pack(r, g, b, a);
}
}
@ -872,12 +864,13 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32
i32 fb_offset = y * gfx->framebuffer_width;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
u32* fb = (u32*)gfx->framebuffer;
u16* fb = (u16*)gfx->framebuffer;
u16 color16 = pxl8_rgba32_to_rgb565(color);
if (width == 0) {
i32 idx = zbuf_offset + xs;
if (z0 <= gfx->zbuffer[idx]) {
gfx->zbuffer[idx] = z0;
fb[fb_offset + xs] = color;
fb[fb_offset + xs] = color16;
}
return;
}
@ -889,7 +882,7 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32
i32 idx = zbuf_offset + x;
if (z <= gfx->zbuffer[idx]) {
gfx->zbuffer[idx] = z;
fb[fb_offset + x] = color;
fb[fb_offset + x] = color16;
}
z += dz;
}
@ -946,7 +939,7 @@ static inline void pxl8_fill_scanline_textured(
i32 tex_mask = tex_w - 1;
i32 atlas_x_base = entry->x;
i32 atlas_y_base = entry->y;
bool is_hicolor = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR);
bool is_hicolor = gfx->color_mode == PXL8_COLOR_MODE_HICOLOR;
bool affine = gfx->affine_textures;
i32 span = xe - xs;
@ -975,17 +968,18 @@ static inline void pxl8_fill_scanline_textured(
}
i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx);
u32 color = is_hicolor ? ((const u32*)atlas_pixels)[atlas_idx] : atlas_pixels[atlas_idx];
if (is_hicolor) {
if (color & 0xFF000000) {
gfx->zbuffer[idx] = z0;
((u32*)gfx->framebuffer)[y * gfx->framebuffer_width + xs] = color;
}
} else {
u16 color = ((const u16*)atlas_pixels)[atlas_idx];
if (color != 0) {
gfx->zbuffer[idx] = z0;
gfx->framebuffer[y * gfx->framebuffer_width + xs] = (u8)color;
((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + xs] = color;
}
} else {
u8 color = atlas_pixels[atlas_idx];
if (color != 0) {
gfx->zbuffer[idx] = z0;
gfx->framebuffer[y * gfx->framebuffer_width + xs] = color;
}
}
}
@ -1030,17 +1024,18 @@ static inline void pxl8_fill_scanline_textured(
}
i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx);
u32 color = is_hicolor ? ((const u32*)atlas_pixels)[atlas_idx] : atlas_pixels[atlas_idx];
if (is_hicolor) {
if (color & 0xFF000000) {
gfx->zbuffer[idx] = z;
((u32*)gfx->framebuffer)[y * gfx->framebuffer_width + x] = color;
}
} else {
u16 color = ((const u16*)atlas_pixels)[atlas_idx];
if (color != 0) {
gfx->zbuffer[idx] = z;
gfx->framebuffer[y * gfx->framebuffer_width + x] = (u8)color;
((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + x] = color;
}
} else {
u8 color = atlas_pixels[atlas_idx];
if (color != 0) {
gfx->zbuffer[idx] = z;
gfx->framebuffer[y * gfx->framebuffer_width + x] = color;
}
}
}

View file

@ -54,7 +54,8 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx);
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);

View file

@ -120,6 +120,8 @@ static i32 pxl8_key_code(const char* key_name) {
{"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},
{"capslock", 57}, {"lshift", 225}, {"rshift", 229},
{"lctrl", 224}, {"rctrl", 228}, {"lalt", 226}, {"ralt", 230},
{NULL, 0}
};
@ -142,21 +144,21 @@ static i32 pxl8_key_code(const char* key_name) {
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) {
if (!input) return false;
i32 key = pxl8_key_code(key_name);
if (key < 0 || key >= 256) return false;
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
return input->keys_down[key];
}
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name) {
if (!input) return false;
i32 key = pxl8_key_code(key_name);
if (key < 0 || key >= 256) return false;
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
return input->keys_pressed[key];
}
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name) {
if (!input) return false;
i32 key = pxl8_key_code(key_name);
if (key < 0 || key >= 256) return false;
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
return input->keys_released[key];
}

View file

@ -10,18 +10,24 @@ typedef struct {
const u8* bytes;
u32 offset;
u32 size;
bool overflow;
} pxl8_stream;
static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
return (pxl8_stream){
.bytes = bytes,
.offset = 0,
.size = size
.size = size,
.overflow = false
};
}
static inline bool pxl8_stream_can_read(const pxl8_stream* stream, u32 count) {
return stream->offset + count <= stream->size;
return !stream->overflow && stream->offset + count <= stream->size;
}
static inline bool pxl8_stream_has_overflow(const pxl8_stream* stream) {
return stream->overflow;
}
static inline void pxl8_stream_seek(pxl8_stream* stream, u32 offset) {
@ -33,16 +39,19 @@ static inline u32 pxl8_stream_position(const pxl8_stream* stream) {
}
static inline u8 pxl8_read_u8(pxl8_stream* stream) {
if (stream->offset + 1 > stream->size) { stream->overflow = true; return 0; }
return stream->bytes[stream->offset++];
}
static inline u16 pxl8_read_u16(pxl8_stream* stream) {
if (stream->offset + 2 > stream->size) { stream->overflow = true; return 0; }
u16 val = (u16)stream->bytes[stream->offset] | ((u16)stream->bytes[stream->offset + 1] << 8);
stream->offset += 2;
return val;
}
static inline u32 pxl8_read_u32(pxl8_stream* stream) {
if (stream->offset + 4 > stream->size) { stream->overflow = true; return 0; }
u32 val = (u32)stream->bytes[stream->offset] |
((u32)stream->bytes[stream->offset + 1] << 8) |
((u32)stream->bytes[stream->offset + 2] << 16) |
@ -67,16 +76,19 @@ static inline f32 pxl8_read_f32(pxl8_stream* stream) {
}
static inline void pxl8_read_bytes(pxl8_stream* stream, void* dest, u32 count) {
if (stream->offset + count > stream->size) { stream->overflow = true; return; }
for (u32 i = 0; i < count; i++) {
((u8*)dest)[i] = stream->bytes[stream->offset++];
}
}
static inline void pxl8_skip_bytes(pxl8_stream* stream, u32 count) {
if (stream->offset + count > stream->size) { stream->overflow = true; return; }
stream->offset += count;
}
static inline const u8* pxl8_read_ptr(pxl8_stream* stream, u32 count) {
if (stream->offset + count > stream->size) { stream->overflow = true; return NULL; }
const u8* ptr = &stream->bytes[stream->offset];
stream->offset += count;
return ptr;

137
src/pxl8_rec.c Normal file
View file

@ -0,0 +1,137 @@
#include "pxl8_rec.h"
#include "pxl8_macros.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct pxl8_recorder {
i32 height;
i32 width;
pxl8_recorder_format format;
i32 framerate;
char output_path[512];
FILE* ffmpeg_pipe;
u32 frame_count;
bool recording;
};
static void generate_default_output_path(pxl8_recorder* rec) {
time_t now = time(NULL);
struct tm* t = localtime(&now);
const char* ext = (rec->format == PXL8_RECORDER_GIF) ? "gif" : "mp4";
snprintf(rec->output_path, sizeof(rec->output_path),
"recording_%04d%02d%02d_%02d%02d%02d.%s",
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, ext);
}
pxl8_recorder* pxl8_recorder_create(i32 width, i32 height) {
pxl8_recorder* rec = (pxl8_recorder*)calloc(1, sizeof(pxl8_recorder));
if (!rec) return NULL;
rec->height = height;
rec->width = width;
rec->format = PXL8_RECORDER_MP4;
rec->framerate = 60;
rec->output_path[0] = '\0';
rec->ffmpeg_pipe = NULL;
rec->frame_count = 0;
rec->recording = false;
return rec;
}
void pxl8_recorder_destroy(pxl8_recorder* rec) {
if (!rec) return;
if (rec->recording) pxl8_recorder_stop(rec);
free(rec);
}
void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format) {
if (rec && !rec->recording) rec->format = format;
}
void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps) {
if (rec && !rec->recording) rec->framerate = fps > 0 ? fps : 60;
}
void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path) {
if (rec && !rec->recording && path) strncpy(rec->output_path, path, sizeof(rec->output_path) - 1);
}
void pxl8_recorder_start(pxl8_recorder* rec) {
if (!rec || rec->recording) return;
if (rec->output_path[0] == '\0') generate_default_output_path(rec);
char cmd[1024];
if (rec->format == PXL8_RECORDER_GIF) {
snprintf(cmd, sizeof(cmd),
"ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - "
"-vf \"split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" "
"\"%s\" 2>/dev/null",
rec->width, rec->height, rec->framerate, rec->output_path);
} else {
snprintf(cmd, sizeof(cmd),
"ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - "
"-c:v libx264 -pix_fmt yuv420p -crf 18 -preset fast "
"\"%s\" 2>/dev/null",
rec->width, rec->height, rec->framerate, rec->output_path);
}
rec->ffmpeg_pipe = popen(cmd, "w");
if (!rec->ffmpeg_pipe) {
pxl8_error("Failed to start ffmpeg. Is ffmpeg installed?");
return;
}
rec->frame_count = 0;
rec->recording = true;
pxl8_info("Recording started: %s (%dx%d @ %dfps)",
rec->output_path, rec->width, rec->height, rec->framerate);
}
void pxl8_recorder_stop(pxl8_recorder* rec) {
if (!rec || !rec->recording) return;
rec->recording = false;
if (rec->ffmpeg_pipe) {
pclose(rec->ffmpeg_pipe);
rec->ffmpeg_pipe = NULL;
}
pxl8_info("Recording stopped: %u frames -> %s", rec->frame_count, rec->output_path);
}
bool pxl8_recorder_is_recording(pxl8_recorder* rec) {
return rec && rec->recording;
}
void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels) {
if (!rec || !rec->recording || !rgba_pixels || !rec->ffmpeg_pipe) return;
size_t written = fwrite(rgba_pixels, 4, rec->width * rec->height, rec->ffmpeg_pipe);
if (written == (size_t)(rec->width * rec->height)) {
rec->frame_count++;
} else {
pxl8_error("Failed to write frame %u", rec->frame_count);
}
}
u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec) {
return rec ? rec->frame_count : 0;
}
const char* pxl8_recorder_get_output_path(pxl8_recorder* rec) {
return rec ? rec->output_path : NULL;
}

31
src/pxl8_rec.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum pxl8_recorder_format {
PXL8_RECORDER_GIF,
PXL8_RECORDER_MP4
} pxl8_recorder_format;
typedef struct pxl8_recorder pxl8_recorder;
pxl8_recorder* pxl8_recorder_create(i32 width, i32 height);
void pxl8_recorder_destroy(pxl8_recorder* rec);
void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels);
u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec);
const char* pxl8_recorder_get_output_path(pxl8_recorder* rec);
bool pxl8_recorder_is_recording(pxl8_recorder* rec);
void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format);
void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps);
void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path);
void pxl8_recorder_start(pxl8_recorder* rec);
void pxl8_recorder_stop(pxl8_recorder* rec);
#ifdef __cplusplus
}
#endif

View file

@ -12,6 +12,7 @@
#include <lua.h>
#include <lualib.h>
#include "pxl8_embed.h"
#include "pxl8_macros.h"
#include "pxl8_gui.h"
@ -19,9 +20,9 @@ struct pxl8_script {
lua_State* L;
pxl8_gfx* gfx;
pxl8_input_state* input;
char last_error[2048];
char main_path[256];
char watch_dir[256];
char last_error[PXL8_MAX_ERROR_SIZE];
char main_path[PXL8_MAX_PATH];
char watch_dir[PXL8_MAX_PATH];
time_t latest_mod_time;
};
@ -353,6 +354,39 @@ const char* pxl8_script_get_last_error(pxl8_script* script) {
return script ? script->last_error : "Invalid script context";
}
static int pxl8_embed_searcher(lua_State* L) {
const char* name = luaL_checkstring(L, 1);
const pxl8_embed* e = pxl8_embed_find(name);
if (!e) {
lua_pushfstring(L, "\n\tno embedded module '%s'", name);
return 1;
}
if (luaL_loadbuffer(L, e->data, e->size, name) != 0) {
return lua_error(L);
}
lua_pushstring(L, name);
return 2;
}
static void pxl8_install_embed_searcher(lua_State* L) {
lua_getglobal(L, "package");
lua_getfield(L, -1, "searchers");
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_getfield(L, -1, "loaders");
}
int len = (int)lua_objlen(L, -1);
for (int i = len; i >= 2; i--) {
lua_rawgeti(L, -1, i);
lua_rawseti(L, -2, i + 1);
}
lua_pushcfunction(L, pxl8_embed_searcher);
lua_rawseti(L, -2, 2);
lua_pop(L, 2);
}
pxl8_script* pxl8_script_create(void) {
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script));
if (!script) return NULL;
@ -383,29 +417,28 @@ pxl8_script* pxl8_script_create(void) {
lua_pop(script->L, 1);
lua_getglobal(script->L, "package");
lua_getfield(script->L, -1, "path");
const char* current_path = lua_tostring(script->L, -1);
lua_pop(script->L, 1);
pxl8_install_embed_searcher(script->L);
lua_pushfstring(script->L, "%s;src/lua/?.lua", current_path);
lua_setfield(script->L, -2, "path");
lua_pop(script->L, 1);
const pxl8_embed* fennel = pxl8_embed_find("fennel");
if (fennel && luaL_loadbuffer(script->L, fennel->data, fennel->size, "fennel") == 0) {
if (lua_pcall(script->L, 0, 1, 0) == 0) {
lua_setglobal(script->L, "fennel");
if (luaL_dofile(script->L, "lib/fennel/fennel.lua") == 0) {
lua_setglobal(script->L, "fennel");
lua_getglobal(script->L, "fennel");
lua_getfield(script->L, -1, "install");
if (lua_isfunction(script->L, -1)) {
if (lua_pcall(script->L, 0, 0, 0) != 0) {
pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1));
lua_getglobal(script->L, "fennel");
lua_getfield(script->L, -1, "install");
if (lua_isfunction(script->L, -1)) {
if (lua_pcall(script->L, 0, 0, 0) != 0) {
pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1));
lua_pop(script->L, 1);
}
} else {
lua_pop(script->L, 1);
}
lua_pop(script->L, 1);
} else {
pxl8_warn("Failed to execute fennel: %s", lua_tostring(script->L, -1));
lua_pop(script->L, 1);
}
lua_pop(script->L, 1);
}
script->last_error[0] = '\0';
@ -746,7 +779,8 @@ pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
strncpy(script->watch_dir, script->main_path, dir_len);
script->watch_dir[dir_len] = '\0';
} else {
strcpy(script->watch_dir, ".");
script->watch_dir[0] = '.';
script->watch_dir[1] = '\0';
}
script->latest_mod_time = get_latest_script_mod_time(script->watch_dir);

View file

@ -5,7 +5,9 @@
#include <SDL3/SDL_main.h>
#include "pxl8_atlas.h"
#include "pxl8_color.h"
#include "pxl8_macros.h"
#include "pxl8_rec.h"
#include "pxl8_sys.h"
typedef struct pxl8_sdl3_context {
@ -122,28 +124,29 @@ static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
if (!platform_data || !fb) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
size_t needed_size = w * h;
if (ctx->rgba_buffer_size < needed_size) {
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
if (!new_buffer) return;
ctx->rgba_buffer = new_buffer;
ctx->rgba_buffer_size = needed_size;
}
if (mode == PXL8_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;
const u16* fb16 = (const u16*)fb;
for (i32 i = 0; i < w * h; i++) {
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(fb16[i]);
}
if (!ctx->rgba_buffer) return;
} else {
u32 palette_size = pxl8_get_palette_size(mode);
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);
}
SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->rgba_buffer, w * 4);
}
static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
@ -183,27 +186,30 @@ static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
ctx->atlas_height = atlas_h;
}
size_t needed_size = atlas_w * atlas_h;
if (ctx->rgba_buffer_size < needed_size) {
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
if (!new_buffer) return;
ctx->rgba_buffer = new_buffer;
ctx->rgba_buffer_size = needed_size;
}
if (mode == PXL8_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;
const u16* atlas16 = (const u16*)atlas_pixels;
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
u16 pixel = atlas16[i];
ctx->rgba_buffer[i] = (pixel != 0) ? pxl8_rgb565_to_rgba32(pixel) : 0x00000000;
}
if (!ctx->rgba_buffer) return;
} else {
u32 palette_size = pxl8_get_palette_size(mode);
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_UpdateTexture(ctx->atlas_texture, NULL, ctx->rgba_buffer, atlas_w * 4);
}
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
@ -267,7 +273,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
case SDL_EVENT_KEY_DOWN: {
SDL_Scancode scancode = event->key.scancode;
if (scancode < 256) {
if (scancode < PXL8_MAX_KEYS) {
if (!input->keys_down[scancode]) {
input->keys_pressed[scancode] = true;
}
@ -279,7 +285,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
case SDL_EVENT_KEY_UP: {
SDL_Scancode scancode = event->key.scancode;
if (scancode < 256) {
if (scancode < PXL8_MAX_KEYS) {
input->keys_down[scancode] = false;
input->keys_pressed[scancode] = false;
input->keys_released[scancode] = true;

View file

@ -4,6 +4,7 @@
#include <string.h>
#include "pxl8_ase.h"
#include "pxl8_color.h"
#include "pxl8_macros.h"
#include "pxl8_tilemap.h"
@ -105,11 +106,11 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
tilesheet->color_mode = pxl8_gfx_get_color_mode(gfx);
size_t data_size = width * height;
if (pxl8_gfx_get_color_mode(gfx) == PXL8_COLOR_MODE_HICOLOR) {
data_size *= 4;
}
u32 pixel_count = width * height;
u16 ase_depth = ase_file.header.color_depth;
bool gfx_hicolor = (tilesheet->color_mode == PXL8_COLOR_MODE_HICOLOR);
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->color_mode);
tilesheet->data = malloc(data_size);
if (!tilesheet->data) {
pxl8_ase_destroy(&ase_file);
@ -117,7 +118,41 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
}
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
memcpy(tilesheet->data, ase_file.frames[0].pixels, data_size);
const u8* src = ase_file.frames[0].pixels;
if (ase_depth == 8 && !gfx_hicolor) {
memcpy(tilesheet->data, src, pixel_count);
} else if (ase_depth == 32 && gfx_hicolor) {
u16* dst = (u16*)tilesheet->data;
const u32* rgba = (const u32*)src;
for (u32 i = 0; i < pixel_count; i++) {
u32 c = rgba[i];
u8 a = (c >> 24) & 0xFF;
if (a == 0) {
dst[i] = 0;
} else {
dst[i] = pxl8_rgba32_to_rgb565(c);
}
}
} else if (ase_depth == 8 && gfx_hicolor) {
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
tilesheet->color_mode = PXL8_COLOR_MODE_FAMI;
u8* new_data = realloc(tilesheet->data, pixel_count);
if (!new_data) {
free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
tilesheet->data = new_data;
memcpy(tilesheet->data, src, pixel_count);
} else {
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_INVALID_ARGUMENT;
}
}
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool));
@ -138,15 +173,13 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
bool has_content = false;
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
u32 idx = (tile_y + py) * width + (tile_x + px);
if (is_hicolor) {
u32 idx = ((tile_y + py) * width + (tile_x + px)) * 4;
u8 alpha = tilesheet->data[idx + 3];
if (alpha > 0) {
if (((u16*)tilesheet->data)[idx] != 0) {
has_content = true;
break;
}
} else {
u32 idx = (tile_y + py) * width + (tile_x + px);
if (tilesheet->data[idx] != 0) {
has_content = true;
break;
@ -277,17 +310,17 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
u32 bytes_per_pixel = (tilesheet->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->color_mode);
for (u32 py = 0; py < tilesheet->tile_size; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
u32 src_idx = py * tilesheet->tile_size + px;
u32 dst_x = tile_x * tilesheet->tile_size + px;
u32 dst_y = tile_y * tilesheet->tile_size + py;
u32 dst_idx = (dst_y * tilesheet->width + dst_x) * bytes_per_pixel;
u32 dst_idx = dst_y * tilesheet->width + dst_x;
if (bytes_per_pixel == 4) {
((u32*)tilesheet->data)[dst_idx / 4] = pixels[src_idx];
if (bytes_per_pixel == 2) {
((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx];
} else {
tilesheet->data[dst_idx] = pixels[src_idx];
}

View file

@ -168,8 +168,11 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
i32 block_size = (i32)(max_block_size * progress);
if (block_size < 1) block_size = 1;
u8* fb = pxl8_gfx_get_framebuffer(gfx);
if (!fb) break;
pxl8_color_mode mode = pxl8_gfx_get_color_mode(gfx);
bool has_fb = (mode == PXL8_COLOR_MODE_HICOLOR)
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
if (!has_fb) break;
for (i32 y = 0; y < height; y += block_size) {
for (i32 x = 0; x < width; x += block_size) {

View file

@ -4,6 +4,14 @@
#include <stddef.h>
#include <stdint.h>
#define PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY 64
#define PXL8_DEFAULT_ATLAS_SIZE 1024
#define PXL8_DEFAULT_SPRITE_CACHE_CAPACITY 64
#define PXL8_MAX_ERROR_SIZE 2048
#define PXL8_MAX_KEYS 256
#define PXL8_MAX_PALETTE_SIZE 256
#define PXL8_MAX_PATH 256
typedef float f32;
typedef double f64;
typedef int8_t i8;
@ -69,9 +77,9 @@ typedef struct pxl8_bounds {
} pxl8_bounds;
typedef struct pxl8_input_state {
bool keys_down[256];
bool keys_pressed[256];
bool keys_released[256];
bool keys_down[PXL8_MAX_KEYS];
bool keys_pressed[PXL8_MAX_KEYS];
bool keys_released[PXL8_MAX_KEYS];
bool mouse_buttons_down[3];
bool mouse_buttons_pressed[3];

View file

@ -1,308 +0,0 @@
#include "pxl8_ui.h"
#include <stdlib.h>
#include <string.h>
#include <microui.h>
#include "pxl8_font.h"
struct pxl8_ui {
pxl8_gfx* gfx;
mu_Context mu_ctx;
i32 font_height;
i32 font_width;
};
static int mu_text_width(mu_Font font, const char* str, int len) {
const pxl8_font* f = (const pxl8_font*)font;
if (!f) return 0;
if (len < 0) {
len = (int)strlen(str);
}
i32 width = 0;
for (int i = 0; i < len; i++) {
const pxl8_glyph* glyph = pxl8_font_find_glyph(f, (u32)str[i]);
if (glyph) {
width += f->default_width;
}
}
return width;
}
static int mu_text_height(mu_Font font) {
const pxl8_font* f = (const pxl8_font*)font;
if (!f) return 8;
return f->default_height;
}
static void pxl8_ui_render_icon(pxl8_gfx* gfx, i32 id, i32 x, i32 y, i32 w, i32 h, u8 color) {
switch (id) {
case 2: {
i32 cx = x + w / 2;
i32 cy = y + h / 2;
i32 size = (w < h ? w : h) / 3;
pxl8_line(gfx, cx - size, cy, cx, cy + size, color);
pxl8_line(gfx, cx, cy + size, cx + size, cy - size, color);
break;
}
case 1: {
i32 cx = x + w / 2;
i32 cy = y + h / 2;
i32 size = (w < h ? w : h) / 4;
pxl8_line(gfx, cx - size, cy - size, cx + size, cy + size, color);
pxl8_line(gfx, cx + size, cy - size, cx - size, cy + size, color);
break;
}
}
}
static void pxl8_ui_render_commands(pxl8_ui* ui) {
mu_Command* cmd = NULL;
while (mu_next_command(&ui->mu_ctx, &cmd)) {
switch (cmd->type) {
case MU_COMMAND_RECT: {
mu_RectCommand* rc = (mu_RectCommand*)cmd;
pxl8_rect_fill(ui->gfx, rc->rect.x, rc->rect.y, rc->rect.w, rc->rect.h, rc->color.r);
break;
}
case MU_COMMAND_TEXT: {
mu_TextCommand* tc = (mu_TextCommand*)cmd;
pxl8_text(ui->gfx, tc->str, tc->pos.x, tc->pos.y, tc->color.r);
break;
}
case MU_COMMAND_CLIP: {
break;
}
case MU_COMMAND_ICON: {
mu_IconCommand* ic = (mu_IconCommand*)cmd;
pxl8_ui_render_icon(ui->gfx, ic->id, ic->rect.x, ic->rect.y, ic->rect.w, ic->rect.h, ic->color.r);
break;
}
}
}
}
static void pxl8_ui_draw_9slice(pxl8_gfx* gfx, pxl8_bounds rect, pxl8_frame_theme* theme) {
if (theme->sprite_id == 0) return;
i32 cs = theme->corner_size;
i32 es = theme->edge_size;
pxl8_sprite(gfx, theme->sprite_id, rect.x, rect.y, cs, cs);
pxl8_sprite(gfx, theme->sprite_id, rect.x + rect.w - cs, rect.y, cs, cs);
pxl8_sprite(gfx, theme->sprite_id, rect.x, rect.y + rect.h - cs, cs, cs);
pxl8_sprite(gfx, theme->sprite_id, rect.x + rect.w - cs, rect.y + rect.h - cs, cs, cs);
for (i32 x = cs; x < rect.w - cs; x += es) {
i32 w = (x + es > rect.w - cs) ? (rect.w - cs - x) : es;
pxl8_sprite(gfx, theme->sprite_id, rect.x + x, rect.y, w, es);
pxl8_sprite(gfx, theme->sprite_id, rect.x + x, rect.y + rect.h - es, w, es);
}
for (i32 y = cs; y < rect.h - cs; y += es) {
i32 h = (y + es > rect.h - cs) ? (rect.h - cs - y) : es;
pxl8_sprite(gfx, theme->sprite_id, rect.x, rect.y + y, es, h);
pxl8_sprite(gfx, theme->sprite_id, rect.x + rect.w - es, rect.y + y, es, h);
}
}
pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx) {
if (!gfx) return NULL;
pxl8_ui* ui = (pxl8_ui*)malloc(sizeof(pxl8_ui));
if (!ui) return NULL;
memset(ui, 0, sizeof(pxl8_ui));
ui->gfx = gfx;
ui->font_width = 8;
ui->font_height = 8;
mu_init(&ui->mu_ctx);
ui->mu_ctx.style->font = (mu_Font)&pxl8_default_font;
ui->mu_ctx.text_height = mu_text_height;
ui->mu_ctx.text_width = mu_text_width;
ui->mu_ctx.style->colors[0] = (mu_Color){15, 0, 0, 255};
ui->mu_ctx.style->colors[1] = (mu_Color){8, 0, 0, 255};
ui->mu_ctx.style->colors[2] = (mu_Color){1, 0, 0, 255};
ui->mu_ctx.style->colors[3] = (mu_Color){2, 0, 0, 255};
ui->mu_ctx.style->colors[4] = (mu_Color){15, 0, 0, 255};
ui->mu_ctx.style->colors[5] = (mu_Color){0, 0, 0, 0};
ui->mu_ctx.style->colors[6] = (mu_Color){7, 0, 0, 255};
ui->mu_ctx.style->colors[7] = (mu_Color){8, 0, 0, 255};
ui->mu_ctx.style->colors[8] = (mu_Color){10, 0, 0, 255};
ui->mu_ctx.style->colors[9] = (mu_Color){2, 0, 0, 255};
ui->mu_ctx.style->colors[10] = (mu_Color){3, 0, 0, 255};
ui->mu_ctx.style->colors[11] = (mu_Color){10, 0, 0, 255};
ui->mu_ctx.style->colors[12] = (mu_Color){1, 0, 0, 255};
ui->mu_ctx.style->colors[13] = (mu_Color){3, 0, 0, 255};
return ui;
}
void pxl8_ui_destroy(pxl8_ui* ui) {
if (!ui) return;
free(ui);
}
void pxl8_ui_frame_begin(pxl8_ui* ui) {
if (!ui) return;
mu_begin(&ui->mu_ctx);
}
void pxl8_ui_frame_end(pxl8_ui* ui) {
if (!ui) return;
mu_end(&ui->mu_ctx);
pxl8_ui_render_commands(ui);
}
void pxl8_ui_input_keydown(pxl8_ui* ui, i32 key) {
if (!ui) return;
mu_input_keydown(&ui->mu_ctx, key);
}
void pxl8_ui_input_keyup(pxl8_ui* ui, i32 key) {
if (!ui) return;
mu_input_keyup(&ui->mu_ctx, key);
}
void pxl8_ui_input_mousedown(pxl8_ui* ui, i32 x, i32 y, i32 button) {
if (!ui) return;
mu_input_mousedown(&ui->mu_ctx, x, y, button);
}
void pxl8_ui_input_mousemove(pxl8_ui* ui, i32 x, i32 y) {
if (!ui) return;
mu_input_mousemove(&ui->mu_ctx, x, y);
}
void pxl8_ui_input_mouseup(pxl8_ui* ui, i32 x, i32 y, i32 button) {
if (!ui) return;
mu_input_mouseup(&ui->mu_ctx, x, y, button);
}
void pxl8_ui_input_scroll(pxl8_ui* ui, i32 x, i32 y) {
if (!ui) return;
mu_input_scroll(&ui->mu_ctx, x, y);
}
void pxl8_ui_input_text(pxl8_ui* ui, const char* text) {
if (!ui || !text) return;
mu_input_text(&ui->mu_ctx, text);
}
bool pxl8_ui_has_mouse_focus(pxl8_ui* ui) {
if (!ui) return false;
return ui->mu_ctx.hover_root != NULL;
}
bool pxl8_ui_button(pxl8_ui* ui, const char* label) {
if (!ui) return false;
return mu_button(&ui->mu_ctx, label) & MU_RES_SUBMIT;
}
bool pxl8_ui_checkbox(pxl8_ui* ui, const char* label, bool* state) {
if (!ui || !state || !label) return false;
mu_Context* ctx = &ui->mu_ctx;
mu_push_id(ctx, label, (int)strlen(label));
mu_Id id = mu_get_id(ctx, label, (int)strlen(label));
mu_Rect r = mu_layout_next(ctx);
mu_Rect box = mu_rect(r.x, r.y, r.h, r.h);
int had_focus_before = (ctx->focus == id);
mu_update_control(ctx, id, r, 0);
int has_focus_after = (ctx->focus == id);
int mouseover = mu_mouse_over(ctx, r);
int res = 0;
int int_state = *state ? 1 : 0;
if (had_focus_before && !has_focus_after && !ctx->mouse_down && mouseover) {
res |= MU_RES_CHANGE;
int_state = !int_state;
}
mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0);
if (int_state) {
mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]);
}
r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h);
mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
mu_pop_id(ctx);
*state = int_state != 0;
return res & MU_RES_CHANGE;
}
void pxl8_ui_indent(pxl8_ui* ui, i32 amount) {
if (!ui) return;
mu_Layout* layout = &ui->mu_ctx.layout_stack.items[ui->mu_ctx.layout_stack.idx - 1];
layout->indent += amount;
}
void pxl8_ui_label(pxl8_ui* ui, const char* text) {
if (!ui || !text) return;
mu_label(&ui->mu_ctx, text);
}
void pxl8_ui_layout_row(pxl8_ui* ui, i32 item_count, const i32* widths, i32 height) {
if (!ui) return;
mu_layout_row(&ui->mu_ctx, item_count, widths, height);
}
i32 pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, i32 item_count) {
if (!ui || !items) return -1;
i32 selected = -1;
for (i32 i = 0; i < item_count; i++) {
if (!items[i].enabled) {
mu_label(&ui->mu_ctx, items[i].label);
} else if (mu_button(&ui->mu_ctx, items[i].label) & MU_RES_SUBMIT) {
selected = i;
}
}
return selected;
}
void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme) {
if (!ui || !theme) return;
pxl8_rect_fill(ui->gfx, rect.x, rect.y, rect.w, rect.h, theme->bg_color);
pxl8_ui_draw_9slice(ui->gfx, rect, theme);
}
bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, i32 options) {
if (!ui) return false;
mu_Rect r = { rect.x, rect.y, rect.w, rect.h };
return mu_begin_window_ex(&ui->mu_ctx, title, r, options);
}
void pxl8_ui_window_end(pxl8_ui* ui) {
if (!ui) return;
mu_end_window(&ui->mu_ctx);
}
void pxl8_ui_window_set_open(pxl8_ui* ui, const char* title, bool open) {
if (!ui || !title) return;
mu_Container* win = mu_get_container(&ui->mu_ctx, title);
if (win) {
win->open = open ? 1 : 0;
}
}
pxl8_frame_theme pxl8_ui_theme_default(void) {
pxl8_frame_theme theme = {0};
theme.bg_color = 0;
theme.corner_size = 8;
theme.edge_size = 8;
theme.padding = 4;
theme.sprite_id = 0;
return theme;
}

View file

@ -1,80 +0,0 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_types.h"
typedef struct pxl8_ui pxl8_ui;
typedef struct pxl8_ui_theme {
u8 text;
u8 border;
u8 window_bg;
u8 title_bg;
u8 title_text;
u8 panel_bg;
u8 button;
u8 button_hover;
u8 button_focus;
u8 base;
u8 base_hover;
u8 base_focus;
u8 scroll_base;
u8 scroll_thumb;
} pxl8_ui_theme;
typedef struct pxl8_frame_theme {
u8 bg_color;
u32 sprite_id;
i32 corner_size;
i32 edge_size;
i32 padding;
} pxl8_frame_theme;
typedef struct pxl8_menu_item {
bool enabled;
const char* label;
} pxl8_menu_item;
typedef enum pxl8_ui_options {
PXL8_UI_NOCLOSE = 1 << 0,
PXL8_UI_NOFRAME = 1 << 1,
PXL8_UI_NORESIZE = 1 << 2,
PXL8_UI_NOTITLE = 1 << 3
} pxl8_ui_options;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx);
void pxl8_ui_destroy(pxl8_ui* ui);
bool pxl8_ui_has_mouse_focus(pxl8_ui* ui);
void pxl8_ui_frame_begin(pxl8_ui* ui);
void pxl8_ui_frame_end(pxl8_ui* ui);
void pxl8_ui_input_keydown(pxl8_ui* ui, i32 key);
void pxl8_ui_input_keyup(pxl8_ui* ui, i32 key);
void pxl8_ui_input_mousedown(pxl8_ui* ui, i32 x, i32 y, i32 button);
void pxl8_ui_input_mousemove(pxl8_ui* ui, i32 x, i32 y);
void pxl8_ui_input_mouseup(pxl8_ui* ui, i32 x, i32 y, i32 button);
void pxl8_ui_input_scroll(pxl8_ui* ui, i32 x, i32 y);
void pxl8_ui_input_text(pxl8_ui* ui, const char* text);
bool pxl8_ui_button(pxl8_ui* ui, const char* label);
bool pxl8_ui_checkbox(pxl8_ui* ui, const char* label, bool* state);
void pxl8_ui_indent(pxl8_ui* ui, i32 amount);
void pxl8_ui_label(pxl8_ui* ui, const char* text);
void pxl8_ui_layout_row(pxl8_ui* ui, i32 item_count, const i32* widths, i32 height);
i32 pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, i32 item_count);
void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme);
bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, i32 options);
void pxl8_ui_window_end(pxl8_ui* ui);
void pxl8_ui_window_set_open(pxl8_ui* ui, const char* title, bool open);
pxl8_frame_theme pxl8_ui_theme_default(void);
#ifdef __cplusplus
}
#endif

View file

@ -3,6 +3,8 @@
#include <stdlib.h>
#include <string.h>
#define PXL8_RANDF() ((f32)rand() / (f32)RAND_MAX)
struct pxl8_particles {
pxl8_particle* particles;
u32 alive_count;
@ -26,7 +28,7 @@ struct pxl8_particles {
};
void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) {
if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return;
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
@ -51,6 +53,8 @@ void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f
for (u32 i = 0; i < bar_count; i++) {
pxl8_raster_bar* bar = &bars[i];
if (bar->height <= 1) continue;
f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase);
i32 y_int = (i32)y;
@ -75,7 +79,7 @@ void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f
}
void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return;
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
f32 cos_a = cosf(angle);
f32 sin_a = sinf(angle);
@ -83,7 +87,7 @@ void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
u8* temp_buffer = (u8*)malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
if (!temp_buffer) return;
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_indexed(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
@ -97,7 +101,7 @@ void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
i32 sy = (i32)src_y;
if (sx >= 0 && sx < pxl8_gfx_get_width(gfx) && sy >= 0 && sy < pxl8_gfx_get_height(gfx)) {
pxl8_gfx_get_framebuffer(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx];
pxl8_gfx_get_framebuffer_indexed(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx];
}
}
}
@ -106,7 +110,7 @@ void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
}
void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) {
if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return;
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
f32 cx = pxl8_gfx_get_width(gfx) / 2.0f;
f32 cy = pxl8_gfx_get_height(gfx) / 2.0f;
@ -210,8 +214,8 @@ void pxl8_particles_emit(pxl8_particles* particles, u32 count) {
pxl8_particle* p = &particles->particles[j];
p->life = 1.0f;
p->max_life = 1.0f;
p->x = particles->x + (((f32)rand() / RAND_MAX) - 0.5f) * particles->spread_x;
p->y = particles->y + (((f32)rand() / RAND_MAX) - 0.5f) * particles->spread_y;
p->x = particles->x + ((PXL8_RANDF()) - 0.5f) * particles->spread_x;
p->y = particles->y + ((PXL8_RANDF()) - 0.5f) * particles->spread_y;
p->z = 0;
p->vx = p->vy = p->vz = 0;
p->ax = particles->gravity_x;
@ -312,15 +316,15 @@ void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32
for (u32 i = 0; i < 50 && i < particles->max_count; i++) {
pxl8_particle* p = &particles->particles[i];
f32 angle = ((f32)rand() / RAND_MAX) * 6.28f;
f32 speed = force * (0.5f + ((f32)rand() / RAND_MAX) * 0.5f);
f32 angle = (PXL8_RANDF()) * 6.28f;
f32 speed = force * (0.5f + (PXL8_RANDF()) * 0.5f);
p->x = x;
p->y = y;
p->vx = cosf(angle) * speed;
p->vy = sinf(angle) * speed;
p->life = 1.0f;
p->max_life = 1.0f + ((f32)rand() / RAND_MAX);
p->max_life = 1.0f + (PXL8_RANDF());
p->color = color;
p->flags = 1;
}
@ -331,9 +335,9 @@ static void fire_spawn(pxl8_particle* p, void* userdata) {
p->start_color = palette_start + 6 + (rand() % 3);
p->end_color = palette_start;
p->color = p->start_color;
p->max_life = 1.5f + ((f32)rand() / RAND_MAX) * 1.5f;
p->vy = -80.0f - ((f32)rand() / RAND_MAX) * 120.0f;
p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 40.0f;
p->max_life = 1.5f + (PXL8_RANDF()) * 1.5f;
p->vy = -80.0f - (PXL8_RANDF()) * 120.0f;
p->vx = ((PXL8_RANDF()) - 0.5f) * 40.0f;
}
static void fire_update(pxl8_particle* p, f32 dt, void* userdata) {
@ -378,7 +382,7 @@ static void rain_spawn(pxl8_particle* p, void* userdata) {
p->end_color = 29;
p->color = p->start_color;
p->max_life = 2.0f;
p->vy = 200.0f + ((f32)rand() / RAND_MAX) * 100.0f;
p->vy = 200.0f + (PXL8_RANDF()) * 100.0f;
}
void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind) {
@ -401,10 +405,10 @@ static void smoke_spawn(pxl8_particle* p, void* userdata) {
p->start_color = base_color;
p->end_color = base_color + 4;
p->color = p->start_color;
p->max_life = 3.0f + ((f32)rand() / RAND_MAX) * 2.0f;
p->vy = -20.0f - ((f32)rand() / RAND_MAX) * 30.0f;
p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 20.0f;
p->size = 1.0f + ((f32)rand() / RAND_MAX) * 2.0f;
p->max_life = 3.0f + (PXL8_RANDF()) * 2.0f;
p->vy = -20.0f - (PXL8_RANDF()) * 30.0f;
p->vx = ((PXL8_RANDF()) - 0.5f) * 20.0f;
p->size = 1.0f + (PXL8_RANDF()) * 2.0f;
}
void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color) {
@ -429,8 +433,8 @@ static void snow_spawn(pxl8_particle* p, void* userdata) {
p->end_color = 10;
p->color = p->start_color;
p->max_life = 4.0f;
p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 20.0f;
p->vy = 30.0f + ((f32)rand() / RAND_MAX) * 20.0f;
p->vx = ((PXL8_RANDF()) - 0.5f) * 20.0f;
p->vy = 30.0f + (PXL8_RANDF()) * 20.0f;
}
void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind) {
@ -453,10 +457,10 @@ static void sparks_spawn(pxl8_particle* p, void* userdata) {
p->start_color = base_color;
p->end_color = base_color > 2 ? base_color - 2 : 0;
p->color = p->start_color;
p->max_life = 0.5f + ((f32)rand() / RAND_MAX) * 1.0f;
p->max_life = 0.5f + (PXL8_RANDF()) * 1.0f;
f32 angle = ((f32)rand() / RAND_MAX) * 6.28f;
f32 speed = 100.0f + ((f32)rand() / RAND_MAX) * 200.0f;
f32 angle = (PXL8_RANDF()) * 6.28f;
f32 speed = 100.0f + (PXL8_RANDF()) * 200.0f;
p->vx = cosf(angle) * speed;
p->vy = sinf(angle) * speed - 50.0f;
}
@ -488,9 +492,9 @@ void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread) {
for (u32 i = 0; i < particles->max_count; i++) {
pxl8_particle* p = &particles->particles[i];
p->x = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread;
p->y = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread;
p->z = ((f32)rand() / RAND_MAX) * spread;
p->x = (PXL8_RANDF()) * spread * 2.0f - spread;
p->y = (PXL8_RANDF()) * spread * 2.0f - spread;
p->z = (PXL8_RANDF()) * spread;
p->vz = -speed;
p->life = 1000.0f;
p->max_life = 1000.0f;

View file

@ -4,8 +4,9 @@
#include <string.h>
#include "pxl8_bsp.h"
#include "pxl8_gen.h"
#include "pxl8_macros.h"
#include "pxl8_procgen.h"
#include "pxl8_math.h"
struct pxl8_world {
pxl8_bsp bsp;
@ -221,22 +222,6 @@ bool pxl8_world_is_loaded(const pxl8_world* world) {
return world && world->loaded;
}
static inline f32 vec3_dot(pxl8_vec3 a, pxl8_vec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static inline pxl8_vec3 vec3_cross(pxl8_vec3 a, pxl8_vec3 b) {
return (pxl8_vec3){
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
};
}
static inline f32 vec3_length(pxl8_vec3 v) {
return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
}
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
if (!world || !world->loaded) return to;
@ -294,7 +279,7 @@ pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from,
bool is_new_plane = true;
for (u32 p = 0; p < num_planes; p++) {
if (vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
if (pxl8_vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
is_new_plane = false;
break;
}
@ -327,8 +312,8 @@ pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from,
original_velocity.z * original_velocity.z;
if (orig_vel_len_sq > 0.000001f) {
f32 vdot0 = vec3_dot(original_velocity, clip_planes[0]);
f32 vdot1 = vec3_dot(original_velocity, clip_planes[1]);
f32 vdot0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
f32 vdot1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
f32 dot0 = fabsf(vdot0);
f32 dot1 = fabsf(vdot1);
@ -350,11 +335,11 @@ pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from,
pos.y += slide_vel.y;
pos.z += slide_vel.z;
pxl8_vec3 crease_dir = vec3_cross(clip_planes[0], clip_planes[1]);
f32 crease_len = vec3_length(crease_dir);
pxl8_vec3 crease_dir = pxl8_vec3_cross(clip_planes[0], clip_planes[1]);
f32 crease_len = pxl8_vec3_length(crease_dir);
if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) {
f32 bias0 = vec3_dot(original_velocity, clip_planes[0]);
f32 bias1 = vec3_dot(original_velocity, clip_planes[1]);
f32 bias0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
f32 bias1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
if (bias0 < 0 && bias1 < 0) {
const f32 corner_push = 0.1f;

View file

@ -1,9 +1,9 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_gen.h"
#include "pxl8_gfx.h"
#include "pxl8_math.h"
#include "pxl8_procgen.h"
#include "pxl8_types.h"
typedef struct pxl8_world pxl8_world;