add ui module

This commit is contained in:
asrael 2025-10-04 11:55:04 -05:00
parent 1744e689b5
commit 6008ebf5ed
12 changed files with 703 additions and 215 deletions

View file

@ -6,11 +6,37 @@
#include "pxl8_ase.h"
#include "pxl8_blit.h"
#include "pxl8_font.h"
#include "pxl8_gfx.h"
#include "pxl8_macros.h"
#include "pxl8_math.h"
#include "pxl8_types.h"
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_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
pxl8_viewport vp = {0};
vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height);
vp.scaled_width = (i32)(width * vp.scale);
vp.scaled_height = (i32)(height * vp.scale);
vp.offset_x = (bounds.w - vp.scaled_width) / 2;
vp.offset_y = (bounds.h - vp.scaled_height) / 2;
return vp;
}
typedef struct pxl8_atlas_entry {
char path[256];
u32 sprite_id;
@ -44,10 +70,7 @@ struct pxl8_gfx {
u32 sprite_frame_width;
u32 sprite_frames_per_row;
i32 viewport_height;
i32 viewport_width;
i32 viewport_x;
i32 viewport_y;
pxl8_viewport viewport;
bool backface_culling;
pxl8_mat4 model;
@ -191,10 +214,11 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons
}
}
gfx->viewport_x = 0;
gfx->viewport_y = 0;
gfx->viewport_width = gfx->framebuffer_width;
gfx->viewport_height = gfx->framebuffer_height;
gfx->viewport.offset_x = 0;
gfx->viewport.offset_y = 0;
gfx->viewport.scaled_width = gfx->framebuffer_width;
gfx->viewport.scaled_height = gfx->framebuffer_height;
gfx->viewport.scale = 1.0f;
gfx->backface_culling = true;
gfx->model = pxl8_mat4_identity();
@ -241,7 +265,6 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
SDL_free(gfx);
}
// resource loading
pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height) {
if (!gfx || !gfx->initialized) return PXL8_ERROR_INVALID_ARGUMENT;
@ -417,55 +440,53 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
return PXL8_OK;
}
// rendering pipeline
static void pxl8_upload_indexed_texture(SDL_Texture* texture, const u8* indexed, const u32* palette, u32 palette_size, i32 width, i32 height, u32 default_color) {
static u32* rgba_buffer = NULL;
static size_t buffer_size = 0;
size_t needed_size = width * height;
if (buffer_size < needed_size) {
rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4);
buffer_size = needed_size;
}
if (!rgba_buffer) return;
for (i32 i = 0; i < width * height; i++) {
u8 index = indexed[i];
rgba_buffer[i] = (index < palette_size) ? palette[index] : default_color;
}
SDL_UpdateTexture(texture, NULL, rgba_buffer, width * 4);
}
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer,
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer,
gfx->framebuffer_width * 4);
} else {
static u32* rgba_buffer = NULL;
static size_t buffer_size = 0;
size_t needed_size = gfx->framebuffer_width * gfx->framebuffer_height;
if (buffer_size < needed_size) {
rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4);
buffer_size = needed_size;
}
if (!rgba_buffer) return;
for (i32 i = 0; i < gfx->framebuffer_width * gfx->framebuffer_height; i++) {
u8 index = gfx->framebuffer[i];
rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0xFF000000;
}
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, rgba_buffer,
gfx->framebuffer_width * 4);
pxl8_upload_indexed_texture(gfx->framebuffer_texture, gfx->framebuffer,
gfx->palette, gfx->palette_size,
gfx->framebuffer_width, gfx->framebuffer_height,
0xFF000000);
}
}
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->sprite_atlas_texture || !gfx->atlas_dirty) return;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas,
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas,
gfx->sprite_atlas_width * 4);
} else {
u32* rgba_buffer = (u32*)SDL_malloc(gfx->sprite_atlas_width * gfx->sprite_atlas_height * 4);
if (!rgba_buffer) return;
for (u32 i = 0; i < gfx->sprite_atlas_width * gfx->sprite_atlas_height; i++) {
u8 index = gfx->atlas[i];
rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0x00000000;
}
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, rgba_buffer,
gfx->sprite_atlas_width * 4);
SDL_free(rgba_buffer);
pxl8_upload_indexed_texture(gfx->sprite_atlas_texture, gfx->atlas,
gfx->palette, gfx->palette_size,
gfx->sprite_atlas_width, gfx->sprite_atlas_height,
0x00000000);
}
gfx->atlas_dirty = false;
pxl8_debug("Atlas uploaded to GPU");
}
@ -483,19 +504,15 @@ void pxl8_gfx_present(pxl8_gfx* gfx) {
SDL_RenderPresent(gfx->renderer);
}
void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height) {
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) {
if (!gfx) return;
gfx->viewport_x = x;
gfx->viewport_y = y;
gfx->viewport_width = width;
gfx->viewport_height = height;
gfx->viewport = vp;
}
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
(void)gfx; (void)left; (void)right; (void)top; (void)bottom;
}
// drawing primitives
void pxl8_clr(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->framebuffer) return;
@ -571,12 +588,26 @@ void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
pxl8_line(gfx, x, y + h - 1, x, y, 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;
} else {
gfx->framebuffer[idx] = color & 0xFF;
}
}
void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
if (!gfx) return;
for (i32 py = y; py < y + h; py++) {
for (i32 px = x; px < x + w; px++) {
pxl8_pixel(gfx, px, py, color);
if (!gfx || !gfx->framebuffer) return;
i32 x0 = (x < 0) ? 0 : x;
i32 y0 = (y < 0) ? 0 : y;
i32 x1 = (x + w > gfx->framebuffer_width) ? gfx->framebuffer_width : x + w;
i32 y1 = (y + h > gfx->framebuffer_height) ? gfx->framebuffer_height : y + h;
for (i32 py = y0; py < y1; py++) {
for (i32 px = x0; px < x1; px++) {
pxl8_pixel_unchecked(gfx, px, py, color);
}
}
}
@ -609,20 +640,65 @@ void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
}
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
if (!gfx) return;
for (i32 y = -radius; y <= radius; y++) {
for (i32 x = -radius; x <= radius; x++) {
if (!gfx || !gfx->framebuffer) return;
i32 x0 = (cx - radius < 0) ? -cx : -radius;
i32 y0 = (cy - radius < 0) ? -cy : -radius;
i32 x1 = (cx + radius >= gfx->framebuffer_width) ? gfx->framebuffer_width - cx - 1 : radius;
i32 y1 = (cy + radius >= gfx->framebuffer_height) ? gfx->framebuffer_height - cy - 1 : radius;
for (i32 y = y0; y <= y1; y++) {
for (i32 x = x0; x <= x1; x++) {
if (x * x + y * y <= radius * radius) {
pxl8_pixel(gfx, cx + x, cy + y, color);
pxl8_pixel_unchecked(gfx, cx + x, cy + y, color);
}
}
}
}
void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
if (!gfx || !text) return;
(void)x; (void)y; (void)color;
if (!gfx || !text || !gfx->framebuffer) return;
const pxl8_font* font = &pxl8_default_font;
i32 cursor_x = x;
i32 cursor_y = y;
for (const char* c = text; *c; c++) {
const pxl8_glyph* glyph = pxl8_font_find_glyph(font, (u32)*c);
if (!glyph) continue;
for (i32 gy = 0; gy < glyph->height; gy++) {
for (i32 gx = 0; gx < glyph->width; gx++) {
i32 px = cursor_x + gx;
i32 py = cursor_y + gy;
if (px < 0 || px >= gfx->framebuffer_width ||
py < 0 || py >= gfx->framebuffer_height) continue;
u8 pixel_bit = 0;
if (glyph->format == PXL8_FONT_FORMAT_INDEXED) {
u8 pixel_byte = glyph->data.indexed[gy];
pixel_bit = (pixel_byte >> gx) & 1;
} else {
i32 glyph_idx = gy * 8 + gx;
u32 rgba_pixel = glyph->data.rgba[glyph_idx];
pixel_bit = ((rgba_pixel >> 24) & 0xFF) > 128 ? 1 : 0;
}
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;
} else {
gfx->framebuffer[fb_idx] = (u8)color;
}
}
}
}
cursor_x += font->default_width;
}
}
@ -681,7 +757,6 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
}
}
// palette effects
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step) {
if (!gfx || !gfx->palette || count == 0) return;
@ -707,82 +782,63 @@ void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors) {
void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color) {
if (!gfx || !gfx->palette || count == 0) return;
if (amount < 0.0f) amount = 0.0f;
if (amount > 1.0f) amount = 1.0f;
u8 target_r = target_color & 0xFF;
u8 target_g = (target_color >> 8) & 0xFF;
u8 target_b = (target_color >> 16) & 0xFF;
u8 target_a = (target_color >> 24) & 0xFF;
u8 target_r, target_g, target_b, target_a;
pxl8_color_unpack(target_color, &target_r, &target_g, &target_b, &target_a);
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
u32 current = gfx->palette[start + i];
u8 cur_r = current & 0xFF;
u8 cur_g = (current >> 8) & 0xFF;
u8 cur_b = (current >> 16) & 0xFF;
u8 cur_a = (current >> 24) & 0xFF;
u8 new_r = cur_r + (i32)((target_r - cur_r) * amount);
u8 new_g = cur_g + (i32)((target_g - cur_g) * amount);
u8 new_b = cur_b + (i32)((target_b - cur_b) * amount);
u8 new_a = cur_a + (i32)((target_a - cur_a) * amount);
gfx->palette[start + i] = new_r | (new_g << 8) | (new_b << 16) | (new_a << 24);
u8 cur_r, cur_g, cur_b, cur_a;
pxl8_color_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);
}
}
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t) {
if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return;
if (t < 0.0f) t = 0.0f;
if (t > 1.0f) t = 1.0f;
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
u32 col1 = palette1[i];
u32 col2 = palette2[i];
u8 r1 = col1 & 0xFF;
u8 g1 = (col1 >> 8) & 0xFF;
u8 b1 = (col1 >> 16) & 0xFF;
u8 a1 = (col1 >> 24) & 0xFF;
u8 r2 = col2 & 0xFF;
u8 g2 = (col2 >> 8) & 0xFF;
u8 b2 = (col2 >> 16) & 0xFF;
u8 a2 = (col2 >> 24) & 0xFF;
u8 r = r1 + (i32)((r2 - r1) * t);
u8 g = g1 + (i32)((g2 - g1) * t);
u8 b = b1 + (i32)((b2 - b1) * t);
u8 a = a1 + (i32)((a2 - a1) * t);
gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24);
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);
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);
}
}
void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color) {
if (!gfx || !gfx->palette || count <= 1) return;
u8 from_r = from_color & 0xFF;
u8 from_g = (from_color >> 8) & 0xFF;
u8 from_b = (from_color >> 16) & 0xFF;
u8 from_a = (from_color >> 24) & 0xFF;
u8 to_r = to_color & 0xFF;
u8 to_g = (to_color >> 8) & 0xFF;
u8 to_b = (to_color >> 16) & 0xFF;
u8 to_a = (to_color >> 24) & 0xFF;
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);
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
f32 t = (f32)i / (f32)(count - 1);
u8 r = from_r + (i32)((to_r - from_r) * t);
u8 g = from_g + (i32)((to_g - from_g) * t);
u8 b = from_b + (i32)((to_b - from_b) * t);
u8 a = from_a + (i32)((to_a - from_a) * t);
gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24);
u8 r = pxl8_color_lerp_channel(from_r, to_r, t);
u8 g = pxl8_color_lerp_channel(from_g, to_g, t);
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);
}
}
@ -895,6 +951,26 @@ void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color)
pxl8_line(gfx, x0, y0, x1, y1, color);
}
static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) {
if (y < 0 || y >= gfx->framebuffer_height) return;
if (xs > xe) {
i32 tmp = xs; xs = xe; xe = tmp;
}
for (i32 x = xs; x <= xe; x++) {
if (x >= 0 && x < gfx->framebuffer_width) {
f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs);
f32 z = z0 + t * (z1 - z0);
i32 idx = y * gfx->zbuffer_width + x;
if (z <= gfx->zbuffer[idx]) {
gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color);
}
}
}
}
static void pxl8_draw_flat_bottom_triangle(
pxl8_gfx* gfx,
i32 x0, i32 y0, f32 z0,
@ -912,28 +988,7 @@ static void pxl8_draw_flat_bottom_triangle(
f32 cur_x2 = (f32)x0;
for (i32 y = y0; y <= y1; y++) {
if (y >= 0 && y < gfx->framebuffer_height) {
i32 xs = (i32)cur_x1;
i32 xe = (i32)cur_x2;
if (xs > xe) {
i32 tmp = xs; xs = xe; xe = tmp;
}
for (i32 x = xs; x <= xe; x++) {
if (x >= 0 && x < gfx->framebuffer_width) {
f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs);
f32 z = z0 + t * (z1 - z0);
i32 idx = y * gfx->zbuffer_width + x;
if (z <= gfx->zbuffer[idx]) {
gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color);
}
}
}
}
pxl8_fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color);
cur_x1 += inv_slope_1;
cur_x2 += inv_slope_2;
}
@ -956,28 +1011,7 @@ static void pxl8_draw_flat_top_triangle(
f32 cur_x2 = (f32)x2;
for (i32 y = y2; y > y0; y--) {
if (y >= 0 && y < gfx->framebuffer_height) {
i32 xs = (i32)cur_x1;
i32 xe = (i32)cur_x2;
if (xs > xe) {
i32 tmp = xs; xs = xe; xe = tmp;
}
for (i32 x = xs; x <= xe; x++) {
if (x >= 0 && x < gfx->framebuffer_width) {
f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs);
f32 z = z0 + t * (z1 - z0);
i32 idx = y * gfx->zbuffer_width + x;
if (z <= gfx->zbuffer[idx]) {
gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color);
}
}
}
}
pxl8_fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color);
cur_x1 -= inv_slope_1;
cur_x2 -= inv_slope_2;
}