major gfx refactor

This commit is contained in:
asrael 2026-02-02 17:48:25 -06:00
parent 0c0aa792c1
commit 3c3e961995
58 changed files with 3681 additions and 2982 deletions

View file

@ -5,7 +5,6 @@
#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_backend.h"
#include "pxl8_blit.h"
#include "pxl8_color.h"
#include "pxl8_colormap.h"
@ -15,36 +14,77 @@
#include "pxl8_macros.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
#include "pxl8_render.h"
#include "pxl8_shader_registry.h"
#include "pxl8_sys.h"
#include "pxl8_types.h"
#define PXL8_MAX_TARGET_STACK 8
typedef struct pxl8_sprite_cache_entry {
char path[256];
u32 sprite_id;
bool active;
} pxl8_sprite_cache_entry;
typedef struct pxl8_target_entry {
pxl8_gfx_texture color;
pxl8_gfx_texture depth;
pxl8_gfx_texture light;
} pxl8_target_entry;
#define PXL8_MAX_FRAME_RESOURCES 512
#define PXL8_STREAM_VB_SIZE (256 * 1024)
#define PXL8_STREAM_IB_SIZE (512 * 1024)
typedef struct pxl8_frame_resources {
pxl8_gfx_texture textures[PXL8_MAX_FRAME_RESOURCES];
pxl8_gfx_buffer buffers[PXL8_MAX_FRAME_RESOURCES];
pxl8_gfx_pipeline pipelines[PXL8_MAX_FRAME_RESOURCES];
pxl8_gfx_bindings bindings[PXL8_MAX_FRAME_RESOURCES];
u32 texture_count;
u32 buffer_count;
u32 pipeline_count;
u32 bindings_count;
} pxl8_frame_resources;
struct pxl8_gfx {
pxl8_atlas* atlas;
pxl8_gfx_backend backend;
pxl8_renderer* renderer;
pxl8_gfx_texture color_target;
pxl8_gfx_texture depth_target;
pxl8_gfx_texture light_target;
pxl8_gfx_cmdbuf* cmdbuf;
pxl8_target_entry target_stack[PXL8_MAX_TARGET_STACK];
u32 target_stack_depth;
const pxl8_bsp* bsp;
pxl8_colormap* colormap;
u8* framebuffer;
i32 framebuffer_height;
i32 framebuffer_width;
pxl8_frustum frustum;
const pxl8_hal* hal;
bool initialized;
u32* output;
pxl8_palette* palette;
pxl8_palette_cube* palette_cube;
pxl8_gfx_pass frame_pass;
pxl8_frame_resources frame_res;
pxl8_pixel_mode pixel_mode;
void* platform_data;
const pxl8_sdf* sdf;
pxl8_sprite_cache_entry* sprite_cache;
u32 sprite_cache_capacity;
u32 sprite_cache_count;
pxl8_viewport viewport;
pxl8_mat4 view_proj;
pxl8_3d_frame frame;
pxl8_gfx_buffer stream_vb;
pxl8_gfx_buffer stream_ib;
u32 stream_vb_capacity;
u32 stream_ib_capacity;
u32 stream_vb_offset;
u32 stream_ib_offset;
bool wireframe;
};
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
@ -63,15 +103,12 @@ pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) {
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL;
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
return pxl8_cpu_get_framebuffer(gfx->backend.cpu);
}
return gfx->framebuffer;
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
}
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL;
return (u16*)gfx->framebuffer;
return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
}
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
@ -80,10 +117,30 @@ i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx) {
if (!gfx) return NULL;
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
return pxl8_cpu_get_light_accum(gfx->backend.cpu);
return (u32*)pxl8_texture_get_data(gfx->renderer, gfx->light_target);
}
u32* pxl8_gfx_get_output(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return gfx->output;
}
void pxl8_gfx_resolve(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized) return;
if (gfx->pixel_mode != PXL8_PIXEL_INDEXED) return;
const u32* pal = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
if (!pal) {
pxl8_error("resolve: no palette!");
return;
}
return NULL;
pxl8_resolve_to_rgba(
gfx->renderer,
gfx->color_target,
gfx->light_target,
pal,
gfx->output
);
}
const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) {
@ -93,10 +150,7 @@ const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) {
u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx) {
if (!gfx) return NULL;
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
return pxl8_cpu_get_zbuffer(gfx->backend.cpu);
}
return NULL;
return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->depth_target);
}
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
@ -123,9 +177,6 @@ u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count) {
if (!gfx || !gfx->palette || !colors) return;
pxl8_set_palette(gfx->palette, colors, count);
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR && gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
}
}
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
@ -136,9 +187,6 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
}
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
}
}
return 0;
}
@ -149,6 +197,8 @@ pxl8_gfx* pxl8_gfx_create(
pxl8_pixel_mode mode,
pxl8_resolution resolution
) {
pxl8_shader_registry_init();
pxl8_gfx* gfx = (pxl8_gfx*)pxl8_calloc(1, sizeof(pxl8_gfx));
if (!gfx) {
pxl8_error("Failed to allocate graphics context");
@ -173,29 +223,87 @@ pxl8_gfx* pxl8_gfx_create(
gfx->palette = pxl8_palette_create();
}
gfx->backend.type = PXL8_GFX_BACKEND_CPU;
gfx->backend.cpu = pxl8_cpu_create(gfx->framebuffer_width, gfx->framebuffer_height);
if (!gfx->backend.cpu) {
pxl8_error("Failed to create CPU backend");
gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height);
if (!gfx->renderer) {
pxl8_error("Failed to create renderer");
pxl8_gfx_destroy(gfx);
return NULL;
}
gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu);
pxl8_gfx_texture_desc color_desc = {
.width = (u32)gfx->framebuffer_width,
.height = (u32)gfx->framebuffer_height,
.format = PXL8_GFX_FORMAT_INDEXED8,
.render_target = true,
};
gfx->color_target = pxl8_create_texture(gfx->renderer, &color_desc);
pxl8_gfx_texture_desc depth_desc = {
.width = (u32)gfx->framebuffer_width,
.height = (u32)gfx->framebuffer_height,
.format = PXL8_GFX_FORMAT_DEPTH16,
.render_target = true,
};
gfx->depth_target = pxl8_create_texture(gfx->renderer, &depth_desc);
pxl8_gfx_texture_desc light_desc = {
.width = (u32)gfx->framebuffer_width,
.height = (u32)gfx->framebuffer_height,
.format = PXL8_GFX_FORMAT_LIGHT_ACCUM,
.render_target = true,
};
gfx->light_target = pxl8_create_texture(gfx->renderer, &light_desc);
u32 pixel_count = (u32)gfx->framebuffer_width * (u32)gfx->framebuffer_height;
gfx->output = pxl8_calloc(pixel_count, sizeof(u32));
if (!gfx->output) {
pxl8_error("Failed to allocate output buffer");
pxl8_gfx_destroy(gfx);
return NULL;
}
gfx->cmdbuf = pxl8_cmdbuf_create(1024);
if (!gfx->cmdbuf) {
pxl8_error("Failed to create command buffer");
pxl8_gfx_destroy(gfx);
return NULL;
}
if (mode != PXL8_PIXEL_HICOLOR) {
gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap));
if (gfx->colormap) {
pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap);
}
}
gfx->target_stack[0] = (pxl8_target_entry){
.color = gfx->color_target,
.depth = gfx->depth_target,
.light = gfx->light_target,
};
gfx->target_stack_depth = 1;
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;
pxl8_gfx_buffer_desc vb_desc = {
.capacity = PXL8_STREAM_VB_SIZE,
.type = PXL8_GFX_BUFFER_VERTEX,
.usage = PXL8_GFX_USAGE_STREAM,
};
gfx->stream_vb = pxl8_create_buffer(gfx->renderer, &vb_desc);
gfx->stream_vb_capacity = PXL8_STREAM_VB_SIZE;
gfx->stream_vb_offset = 0;
pxl8_gfx_buffer_desc ib_desc = {
.capacity = PXL8_STREAM_IB_SIZE,
.type = PXL8_GFX_BUFFER_INDEX,
.usage = PXL8_GFX_USAGE_STREAM,
};
gfx->stream_ib = pxl8_create_buffer(gfx->renderer, &ib_desc);
gfx->stream_ib_capacity = PXL8_STREAM_IB_SIZE;
gfx->stream_ib_offset = 0;
gfx->initialized = true;
return gfx;
}
@ -204,13 +312,13 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
if (!gfx) return;
pxl8_atlas_destroy(gfx->atlas);
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_destroy(gfx->backend.cpu);
}
pxl8_cmdbuf_destroy(gfx->cmdbuf);
pxl8_free(gfx->colormap);
pxl8_free(gfx->output);
pxl8_palette_cube_destroy(gfx->palette_cube);
pxl8_palette_destroy(gfx->palette);
pxl8_free(gfx->sprite_cache);
pxl8_renderer_destroy(gfx->renderer);
pxl8_free(gfx);
}
@ -330,26 +438,24 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->hal) return;
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU && gfx->pixel_mode == PXL8_PIXEL_INDEXED) {
pxl8_cpu_resolve(gfx->backend.cpu);
u32* output = pxl8_cpu_get_output(gfx->backend.cpu);
if (output) {
gfx->hal->upload_texture(
gfx->platform_data,
output,
gfx->framebuffer_width,
gfx->framebuffer_height,
PXL8_PIXEL_RGBA,
NULL
);
return;
}
if (gfx->pixel_mode == PXL8_PIXEL_INDEXED) {
pxl8_gfx_resolve(gfx);
gfx->hal->upload_texture(
gfx->platform_data,
gfx->output,
gfx->framebuffer_width,
gfx->framebuffer_height,
PXL8_PIXEL_RGBA,
NULL
);
return;
}
u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
gfx->hal->upload_texture(
gfx->platform_data,
gfx->framebuffer,
framebuffer,
gfx->framebuffer_width,
gfx->framebuffer_height,
gfx->pixel_mode,
@ -363,6 +469,16 @@ void pxl8_gfx_present(pxl8_gfx* gfx) {
gfx->hal->present(gfx->platform_data);
}
void pxl8_gfx_reset_stats(pxl8_gfx* gfx) {
if (!gfx || !gfx->renderer) return;
pxl8_renderer_reset_stats(gfx->renderer);
}
const pxl8_gfx_stats* pxl8_gfx_get_stats(pxl8_gfx* gfx) {
if (!gfx || !gfx->renderer) return NULL;
return pxl8_renderer_get_stats(gfx->renderer);
}
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);
@ -382,115 +498,99 @@ 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;
}
static pxl8_gfx_texture gfx_current_color(pxl8_gfx* gfx) {
if (gfx->target_stack_depth == 0) return gfx->color_target;
return gfx->target_stack[gfx->target_stack_depth - 1].color;
}
static pxl8_gfx_texture gfx_current_depth(pxl8_gfx* gfx) {
if (gfx->target_stack_depth == 0) return gfx->depth_target;
return gfx->target_stack[gfx->target_stack_depth - 1].depth;
}
static pxl8_gfx_texture gfx_current_light(pxl8_gfx* gfx) {
if (gfx->target_stack_depth == 0) return gfx->light_target;
return gfx->target_stack[gfx->target_stack_depth - 1].light;
}
static void gfx_composite_over(pxl8_gfx* gfx, const pxl8_target_entry* src, pxl8_target_entry* dst) {
if (!gfx || !src || !dst) return;
u8* src_color = (u8*)pxl8_texture_get_data(gfx->renderer, src->color);
u8* dst_color = (u8*)pxl8_texture_get_data(gfx->renderer, dst->color);
if (!src_color || !dst_color) return;
u32 width = pxl8_texture_get_width(gfx->renderer, src->color);
u32 height = pxl8_texture_get_height(gfx->renderer, src->color);
if (width == 0 || height == 0) return;
u32 dst_width = pxl8_texture_get_width(gfx->renderer, dst->color);
u32 dst_height = pxl8_texture_get_height(gfx->renderer, dst->color);
if (dst_width != width || dst_height != height) return;
u32* src_light = (u32*)pxl8_texture_get_data(gfx->renderer, src->light);
u32* dst_light = (u32*)pxl8_texture_get_data(gfx->renderer, dst->light);
u32 count = width * height;
for (u32 i = 0; i < count; i++) {
u8 sc = src_color[i];
if (sc == 0) continue;
dst_color[i] = sc;
if (dst_light) {
dst_light[i] = src_light ? src_light[i] : 0;
}
}
}
void pxl8_2d_clear(pxl8_gfx* gfx, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_clear(gfx->backend.cpu, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_clear(gfx->renderer, gfx_current_color(gfx), (u8)color);
pxl8_clear_light(gfx->renderer, gfx_current_light(gfx));
}
void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_pixel(gfx->backend.cpu, x, y, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_draw_pixel(gfx->renderer, gfx_current_color(gfx), x, y, (u8)color);
}
u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) {
if (!gfx) return 0;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
return pxl8_cpu_get_pixel(gfx->backend.cpu, x, y);
case PXL8_GFX_BACKEND_GPU:
return 0;
}
return 0;
return pxl8_get_pixel(gfx->renderer, gfx_current_color(gfx), x, y);
}
void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_line_2d(gfx->backend.cpu, x0, y0, x1, y1, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, (u8)color);
}
void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_rect(gfx->backend.cpu, x, y, w, h, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_draw_rect(gfx->renderer, gfx_current_color(gfx), x, y, w, h, (u8)color);
}
void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_rect_fill(gfx->backend.cpu, x, y, w, h, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_draw_rect_fill(gfx->renderer, gfx_current_color(gfx), x, y, w, h, (u8)color);
}
void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_circle(gfx->backend.cpu, cx, cy, radius, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_draw_circle(gfx->renderer, gfx_current_color(gfx), cx, cy, radius, (u8)color);
}
void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_circle_fill(gfx->backend.cpu, cx, cy, radius, (u8)color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_draw_circle_fill(gfx->renderer, gfx_current_color(gfx), cx, cy, radius, (u8)color);
}
void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
if (!gfx || !text) return;
u8* framebuffer = NULL;
u32* light_accum = NULL;
i32 fb_width = 0;
i32 fb_height = 0;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: {
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
if (!render_target) return;
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
light_accum = pxl8_cpu_render_target_get_light_accum(render_target);
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
break;
}
case PXL8_GFX_BACKEND_GPU:
return;
}
pxl8_gfx_texture target = gfx_current_color(gfx);
u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, target);
i32 fb_width = (i32)pxl8_texture_get_width(gfx->renderer, target);
i32 fb_height = (i32)pxl8_texture_get_height(gfx->renderer, target);
if (!framebuffer) return;
@ -523,7 +623,6 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
if (pixel_bit) {
i32 idx = py * fb_width + px;
framebuffer[idx] = (u8)color;
if (light_accum) light_accum[idx] = 0;
}
}
}
@ -536,24 +635,10 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) {
if (!gfx || !gfx->atlas) return;
u8* framebuffer = NULL;
u32* light_accum = NULL;
i32 fb_width = 0;
i32 fb_height = 0;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: {
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
if (!render_target) return;
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
light_accum = pxl8_cpu_render_target_get_light_accum(render_target);
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
break;
}
case PXL8_GFX_BACKEND_GPU:
return;
}
pxl8_gfx_texture target = gfx_current_color(gfx);
u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, target);
i32 fb_width = (i32)pxl8_texture_get_width(gfx->renderer, target);
i32 fb_height = (i32)pxl8_texture_get_height(gfx->renderer, target);
if (!framebuffer) return;
@ -583,11 +668,6 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (is_1to1_scale && is_unclipped && !is_flipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
if (light_accum) {
for (i32 py = 0; py < h; py++) {
memset(light_accum + (y + py) * fb_width + x, 0, w * sizeof(u32));
}
}
} else {
for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) {
@ -601,7 +681,6 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
if (light_accum) light_accum[dest_idx] = 0;
}
}
}
@ -616,7 +695,7 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
}
}
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) {
pxl8_3d_frame frame = {0};
if (!camera) return frame;
@ -639,32 +718,37 @@ void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp) {
gfx->bsp = bsp;
}
void pxl8_3d_set_sdf(pxl8_gfx* gfx, const pxl8_sdf* sdf) {
if (!gfx) return;
gfx->sdf = sdf;
}
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms) {
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms) {
if (!gfx || !camera) return;
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
frame.bsp = gfx->bsp;
frame.lights = lights ? pxl8_lights_data(lights) : NULL;
frame.lights_count = lights ? pxl8_lights_count(lights) : 0;
frame.sdf = gfx->sdf;
frame.uniforms.lights = lights ? pxl8_lights_data(lights) : NULL;
frame.uniforms.lights_count = lights ? pxl8_lights_count(lights) : 0;
pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view);
gfx->frustum = pxl8_frustum_from_matrix(vp);
gfx->view_proj = vp;
gfx->frame = frame;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_begin_frame(gfx->backend.cpu, &frame);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
gfx->frame_res.texture_count = 0;
gfx->frame_res.buffer_count = 0;
gfx->frame_res.pipeline_count = 0;
gfx->frame_res.bindings_count = 0;
gfx->stream_vb_offset = 0;
gfx->stream_ib_offset = 0;
pxl8_cmdbuf_reset(gfx->cmdbuf);
pxl8_gfx_pass_desc pass_desc = {
.color = { .texture = gfx_current_color(gfx), .load = PXL8_GFX_LOAD_CLEAR, .clear_value = 0 },
.depth = { .texture = gfx_current_depth(gfx), .load = PXL8_GFX_LOAD_CLEAR, .clear_value = 0xFFFF },
.light_accum = { .texture = gfx_current_light(gfx), .load = PXL8_GFX_LOAD_CLEAR },
};
gfx->frame_pass = pxl8_create_pass(gfx->renderer, &pass_desc);
pxl8_begin_pass(gfx->cmdbuf, gfx->frame_pass);
}
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) {
@ -706,90 +790,249 @@ u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_clear(gfx->backend.cpu, color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_clear(gfx->renderer, gfx_current_color(gfx), color);
pxl8_clear_light(gfx->renderer, gfx_current_light(gfx));
}
void pxl8_3d_clear_depth(pxl8_gfx* gfx) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_clear_depth(gfx->backend.cpu);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_clear_depth(gfx->renderer, gfx_current_depth(gfx));
}
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_line_3d(gfx->backend.cpu, v0, v1, color);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
pxl8_vec4 c0 = pxl8_mat4_multiply_vec4(gfx->view_proj, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f});
pxl8_vec4 c1 = pxl8_mat4_multiply_vec4(gfx->view_proj, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f});
if (c0.w <= 0.0f && c1.w <= 0.0f) return;
f32 hw = (f32)gfx->framebuffer_width * 0.5f;
f32 hh = (f32)gfx->framebuffer_height * 0.5f;
i32 x0 = (i32)(hw + c0.x / c0.w * hw);
i32 y0 = (i32)(hh - c0.y / c0.w * hh);
i32 x1 = (i32)(hw + c1.x / c1.w * hw);
i32 y1 = (i32)(hh - c1.y / c1.w * hh);
pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, color);
}
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) {
if (!gfx || !mesh || !model || !material) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas);
if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return;
pxl8_frame_resources* res = &gfx->frame_res;
bool is_wireframe = gfx->wireframe;
const char* shader_name = "lit";
if (material->emissive || (!material->dynamic_lighting && !material->per_pixel)) {
shader_name = "unlit";
}
pxl8_gfx_pipeline_desc pipe_desc = {
.blend = {
.enabled = false,
.src = PXL8_GFX_BLEND_ONE,
.dst = PXL8_GFX_BLEND_ZERO,
.alpha_test = false,
.alpha_ref = 0,
},
.depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS },
.dither = material->dither,
.double_sided = material->double_sided,
.emissive = material->emissive,
.rasterizer = { .cull = PXL8_GFX_CULL_BACK, .fill = is_wireframe ? PXL8_GFX_FILL_WIREFRAME : PXL8_GFX_FILL_SOLID },
.shader = pxl8_shader_registry_get(shader_name),
};
switch (material->blend_mode) {
case PXL8_BLEND_ALPHA_TEST:
pipe_desc.blend.alpha_test = true;
pipe_desc.blend.alpha_ref = material->alpha;
break;
case PXL8_GFX_BACKEND_GPU:
case PXL8_BLEND_ALPHA:
pipe_desc.blend.enabled = true;
pipe_desc.blend.src = PXL8_GFX_BLEND_SRC_ALPHA;
pipe_desc.blend.dst = PXL8_GFX_BLEND_INV_SRC_ALPHA;
break;
case PXL8_BLEND_ADDITIVE:
pipe_desc.blend.enabled = true;
pipe_desc.blend.src = PXL8_GFX_BLEND_ONE;
pipe_desc.blend.dst = PXL8_GFX_BLEND_ONE;
break;
case PXL8_BLEND_OPAQUE:
default:
break;
}
pxl8_gfx_pipeline pipeline = pxl8_create_pipeline(gfx->renderer, &pipe_desc);
if (res->pipeline_count < PXL8_MAX_FRAME_RESOURCES) {
res->pipelines[res->pipeline_count++] = pipeline;
}
pxl8_gfx_bindings_desc bind_desc = {
.atlas = gfx->atlas,
.colormap = gfx->colormap,
.palette = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL,
.texture_id = material->texture_id,
};
pxl8_gfx_bindings bindings = pxl8_create_bindings(gfx->renderer, &bind_desc);
if (res->bindings_count < PXL8_MAX_FRAME_RESOURCES) {
res->bindings[res->bindings_count++] = bindings;
}
u32 vb_size = mesh->vertex_count * sizeof(pxl8_vertex);
u32 ib_size = mesh->index_count * sizeof(u16);
u32 base_vertex = 0;
u32 first_index = 0;
pxl8_gfx_buffer vb, ib;
bool use_stream = pxl8_gfx_handle_valid(gfx->stream_vb) &&
pxl8_gfx_handle_valid(gfx->stream_ib) &&
(gfx->stream_vb_offset + vb_size <= gfx->stream_vb_capacity) &&
(gfx->stream_ib_offset + ib_size <= gfx->stream_ib_capacity);
if (use_stream) {
pxl8_gfx_range vb_data = { .ptr = mesh->vertices, .size = vb_size };
pxl8_gfx_range ib_data = { .ptr = mesh->indices, .size = ib_size };
i32 vb_offset = pxl8_append_buffer(gfx->renderer, gfx->stream_vb, &vb_data);
i32 ib_offset = pxl8_append_buffer(gfx->renderer, gfx->stream_ib, &ib_data);
if (vb_offset >= 0 && ib_offset >= 0) {
gfx->stream_vb_offset += vb_size;
gfx->stream_ib_offset += ib_size;
base_vertex = (u32)vb_offset / sizeof(pxl8_vertex);
first_index = (u32)ib_offset / sizeof(u16);
vb = gfx->stream_vb;
ib = gfx->stream_ib;
} else {
use_stream = false;
}
}
if (!use_stream) {
pxl8_gfx_buffer_desc vb_desc = {
.type = PXL8_GFX_BUFFER_VERTEX,
.data = { .ptr = mesh->vertices, .size = vb_size },
};
vb = pxl8_create_buffer(gfx->renderer, &vb_desc);
if (res->buffer_count < PXL8_MAX_FRAME_RESOURCES) {
res->buffers[res->buffer_count++] = vb;
}
pxl8_gfx_buffer_desc ib_desc = {
.type = PXL8_GFX_BUFFER_INDEX,
.data = { .ptr = mesh->indices, .size = ib_size },
};
ib = pxl8_create_buffer(gfx->renderer, &ib_desc);
if (res->buffer_count < PXL8_MAX_FRAME_RESOURCES) {
res->buffers[res->buffer_count++] = ib;
}
}
pxl8_gfx_cmd_draw_params draw_params = {
.model = *model,
.view = gfx->frame.view,
.projection = gfx->frame.projection,
.shader = gfx->frame.uniforms,
};
pxl8_set_pipeline(gfx->cmdbuf, pipeline);
pxl8_set_bindings(gfx->cmdbuf, bindings);
pxl8_set_draw_params(gfx->cmdbuf, &draw_params);
pxl8_draw(gfx->cmdbuf, vb, ib, first_index, mesh->index_count, base_vertex);
}
void pxl8_3d_end_frame(pxl8_gfx* gfx) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_end_frame(gfx->backend.cpu);
break;
case PXL8_GFX_BACKEND_GPU:
break;
if (!gfx) { pxl8_error("end_frame: gfx is NULL"); return; }
if (!pxl8_gfx_handle_valid(gfx->frame_pass)) { pxl8_error("end_frame: frame_pass invalid (id=%u)", gfx->frame_pass.id); return; }
pxl8_end_pass(gfx->cmdbuf);
pxl8_gfx_submit(gfx->renderer, gfx->cmdbuf);
pxl8_frame_resources* res = &gfx->frame_res;
for (u32 i = 0; i < res->buffer_count; i++) {
pxl8_destroy_buffer(gfx->renderer, res->buffers[i]);
}
for (u32 i = 0; i < res->pipeline_count; i++) {
pxl8_destroy_pipeline(gfx->renderer, res->pipelines[i]);
}
for (u32 i = 0; i < res->bindings_count; i++) {
pxl8_destroy_bindings(gfx->renderer, res->bindings[i]);
}
for (u32 i = 0; i < res->texture_count; i++) {
pxl8_destroy_texture(gfx->renderer, res->textures[i]);
}
pxl8_destroy_pass(gfx->renderer, gfx->frame_pass);
gfx->frame_pass = (pxl8_gfx_pass){PXL8_GFX_INVALID_ID};
res->buffer_count = 0;
res->pipeline_count = 0;
res->bindings_count = 0;
res->texture_count = 0;
}
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) {
if (!gfx) return NULL;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
return pxl8_cpu_get_framebuffer(gfx->backend.cpu);
case PXL8_GFX_BACKEND_GPU:
return NULL;
}
return NULL;
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx_current_color(gfx));
}
bool pxl8_gfx_push_target(pxl8_gfx* gfx) {
if (!gfx) return false;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
return pxl8_cpu_push_target(gfx->backend.cpu);
case PXL8_GFX_BACKEND_GPU:
return false;
}
return false;
if (!gfx || gfx->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false;
pxl8_gfx_texture_desc color_desc = {
.width = (u32)gfx->framebuffer_width,
.height = (u32)gfx->framebuffer_height,
.format = PXL8_GFX_FORMAT_INDEXED8,
.render_target = true,
};
pxl8_gfx_texture new_color = pxl8_create_texture(gfx->renderer, &color_desc);
pxl8_gfx_texture_desc depth_desc = {
.width = (u32)gfx->framebuffer_width,
.height = (u32)gfx->framebuffer_height,
.format = PXL8_GFX_FORMAT_DEPTH16,
.render_target = true,
};
pxl8_gfx_texture new_depth = pxl8_create_texture(gfx->renderer, &depth_desc);
pxl8_gfx_texture_desc light_desc = {
.width = (u32)gfx->framebuffer_width,
.height = (u32)gfx->framebuffer_height,
.format = PXL8_GFX_FORMAT_LIGHT_ACCUM,
.render_target = true,
};
pxl8_gfx_texture new_light = pxl8_create_texture(gfx->renderer, &light_desc);
gfx->target_stack[gfx->target_stack_depth] = (pxl8_target_entry){
.color = new_color,
.depth = new_depth,
.light = new_light,
};
gfx->target_stack_depth++;
return true;
}
void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
if (!gfx) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_pop_target(gfx->backend.cpu);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
if (!gfx || gfx->target_stack_depth <= 1) return;
u32 top = gfx->target_stack_depth - 1;
pxl8_target_entry* entry = &gfx->target_stack[top];
pxl8_target_entry* parent = &gfx->target_stack[top - 1];
gfx_composite_over(gfx, entry, parent);
gfx->target_stack_depth--;
pxl8_destroy_texture(gfx->renderer, entry->color);
pxl8_destroy_texture(gfx->renderer, entry->depth);
pxl8_destroy_texture(gfx->renderer, entry->light);
}
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
@ -797,9 +1040,6 @@ void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
if (!gfx->palette_cube) {
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
}
}
@ -810,9 +1050,6 @@ void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
if (gfx->palette_cube) {
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
}
}
@ -838,20 +1075,14 @@ u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) {
if (!gfx || !params || count == 0) return;
switch (effect) {
case PXL8_GFX_EFFECT_GLOWS: {
const pxl8_glow* glows = (const pxl8_glow*)params;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_render_glows(gfx->backend.cpu, glows, count);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
break;
}
}
void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled) {
if (gfx) gfx->wireframe = enabled;
}
bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx) {
return gfx ? gfx->wireframe : false;
}
u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx) {
return gfx ? gfx->frame.uniforms.ambient : 0;
}