2025-11-13 07:15:41 -06:00
|
|
|
#include "pxl8_gfx.h"
|
|
|
|
|
|
2025-10-01 12:56:13 -05:00
|
|
|
#include <stdlib.h>
|
2025-10-04 04:13:48 -05:00
|
|
|
#include <string.h>
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_ase.h"
|
2025-10-17 17:54:33 -05:00
|
|
|
#include "pxl8_atlas.h"
|
2025-10-01 12:56:13 -05:00
|
|
|
#include "pxl8_blit.h"
|
2025-11-28 14:41:35 -06:00
|
|
|
#include "pxl8_color.h"
|
2025-10-04 11:55:04 -05:00
|
|
|
#include "pxl8_font.h"
|
2025-10-17 17:54:33 -05:00
|
|
|
#include "pxl8_hal.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_macros.h"
|
2025-10-04 04:13:48 -05:00
|
|
|
#include "pxl8_math.h"
|
2025-11-19 22:18:08 -06:00
|
|
|
#include "pxl8_sys.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_types.h"
|
|
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
typedef struct pxl8_palette_cycle {
|
|
|
|
|
bool active;
|
|
|
|
|
u8 end_index;
|
|
|
|
|
f32 speed;
|
|
|
|
|
u8 start_index;
|
|
|
|
|
f32 timer;
|
|
|
|
|
} pxl8_palette_cycle;
|
|
|
|
|
|
|
|
|
|
typedef struct pxl8_effects {
|
|
|
|
|
pxl8_palette_cycle palette_cycles[8];
|
|
|
|
|
f32 time;
|
|
|
|
|
} pxl8_effects;
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
typedef struct pxl8_sprite_cache_entry {
|
2025-10-04 04:13:48 -05:00
|
|
|
char path[256];
|
2025-10-17 17:54:33 -05:00
|
|
|
u32 sprite_id;
|
|
|
|
|
bool active;
|
|
|
|
|
} pxl8_sprite_cache_entry;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
struct pxl8_gfx {
|
2025-10-17 17:54:33 -05:00
|
|
|
const pxl8_hal* hal;
|
|
|
|
|
void* platform_data;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
pxl8_atlas* atlas;
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_sprite_cache_entry* sprite_cache;
|
|
|
|
|
u32 sprite_cache_capacity;
|
|
|
|
|
u32 sprite_cache_count;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
pxl8_pixel_mode pixel_mode;
|
2025-10-04 04:13:48 -05:00
|
|
|
u8* framebuffer;
|
|
|
|
|
i32 framebuffer_height;
|
|
|
|
|
i32 framebuffer_width;
|
|
|
|
|
bool initialized;
|
|
|
|
|
|
|
|
|
|
u32* palette;
|
|
|
|
|
u32 palette_size;
|
|
|
|
|
|
2025-10-04 11:55:04 -05:00
|
|
|
pxl8_viewport viewport;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
pxl8_effects effects;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
bool backface_culling;
|
|
|
|
|
pxl8_mat4 model;
|
|
|
|
|
pxl8_mat4 projection;
|
|
|
|
|
pxl8_mat4 view;
|
2025-11-09 06:30:17 -06:00
|
|
|
pxl8_mat4 mvp;
|
|
|
|
|
bool mvp_dirty;
|
|
|
|
|
pxl8_frustum frustum;
|
2025-10-04 04:13:48 -05:00
|
|
|
bool wireframe;
|
|
|
|
|
f32* zbuffer;
|
|
|
|
|
i32 zbuffer_height;
|
|
|
|
|
i32 zbuffer_width;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
bool affine_textures;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
|
|
|
|
pxl8_bounds bounds = {0};
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!gfx) {
|
2025-10-04 04:13:48 -05:00
|
|
|
return bounds;
|
|
|
|
|
}
|
2025-10-17 17:54:33 -05:00
|
|
|
bounds.w = gfx->framebuffer_width;
|
|
|
|
|
bounds.h = gfx->framebuffer_height;
|
2025-10-04 04:13:48 -05:00
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
|
2025-11-28 23:42:57 -06:00
|
|
|
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL;
|
2025-11-28 14:41:35 -06:00
|
|
|
return gfx->framebuffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
|
2025-11-28 23:42:57 -06:00
|
|
|
if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL;
|
2025-11-28 14:41:35 -06:00
|
|
|
return (u16*)gfx->framebuffer;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->framebuffer_height : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->framebuffer_width : 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->palette_size : 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 17:19:59 -05:00
|
|
|
pxl8_gfx* pxl8_gfx_create(
|
2025-10-17 17:54:33 -05:00
|
|
|
const pxl8_hal* hal,
|
2025-11-18 23:50:02 -06:00
|
|
|
void* platform_data,
|
2025-11-28 23:42:57 -06:00
|
|
|
pxl8_pixel_mode mode,
|
2025-11-18 23:50:02 -06:00
|
|
|
pxl8_resolution resolution
|
2025-10-05 17:19:59 -05:00
|
|
|
) {
|
2025-11-15 11:40:27 -06:00
|
|
|
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx) {
|
|
|
|
|
pxl8_error("Failed to allocate graphics context");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->hal = hal;
|
2025-11-18 23:50:02 -06:00
|
|
|
gfx->platform_data = platform_data;
|
2025-10-17 17:54:33 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
gfx->pixel_mode = mode;
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
|
|
|
|
|
gfx->framebuffer_width = size.w;
|
|
|
|
|
gfx->framebuffer_height = size.h;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!gfx->platform_data) {
|
2025-11-18 23:50:02 -06:00
|
|
|
pxl8_error("Platform data cannot be NULL");
|
2025-10-17 17:54:33 -05:00
|
|
|
free(gfx);
|
2025-10-04 04:13:48 -05:00
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
i32 bytes_per_pixel = pxl8_bytes_per_pixel(gfx->pixel_mode);
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->framebuffer = (u8*)calloc(1, fb_size);
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx->framebuffer) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to allocate framebuffer");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
gfx->palette_size = (mode == PXL8_PIXEL_HICOLOR) ? 0 : PXL8_MAX_PALETTE_SIZE;
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->palette_size > 0) {
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->palette = (u32*)calloc(gfx->palette_size, sizeof(u32));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx->palette) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to allocate palette");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
for (u32 i = 0; i < gfx->palette_size; i++) {
|
|
|
|
|
u8 gray = (u8)i;
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-04 11:55:04 -05:00
|
|
|
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;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
gfx->backface_culling = true;
|
|
|
|
|
gfx->model = pxl8_mat4_identity();
|
|
|
|
|
gfx->projection = pxl8_mat4_identity();
|
|
|
|
|
gfx->view = pxl8_mat4_identity();
|
2025-11-09 06:30:17 -06:00
|
|
|
gfx->mvp_dirty = true;
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->wireframe = false;
|
|
|
|
|
gfx->zbuffer = NULL;
|
|
|
|
|
gfx->zbuffer_height = 0;
|
|
|
|
|
gfx->zbuffer_width = 0;
|
2025-10-05 16:25:17 -05:00
|
|
|
gfx->affine_textures = false;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
gfx->initialized = true;
|
|
|
|
|
return gfx;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx) return;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
pxl8_atlas_destroy(gfx->atlas);
|
2025-10-17 17:54:33 -05:00
|
|
|
free(gfx->sprite_cache);
|
|
|
|
|
free(gfx->framebuffer);
|
|
|
|
|
free(gfx->palette);
|
|
|
|
|
free(gfx->zbuffer);
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
free(gfx);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
|
|
|
|
|
if (gfx->atlas) return PXL8_OK;
|
2025-11-28 23:42:57 -06:00
|
|
|
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode);
|
2025-11-28 14:41:35 -06:00
|
|
|
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
|
|
|
|
if (result != PXL8_OK) return result;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode);
|
2025-10-05 16:25:17 -05:00
|
|
|
if (texture_id == UINT32_MAX) {
|
|
|
|
|
pxl8_error("Texture doesn't fit in atlas");
|
|
|
|
|
return PXL8_ERROR_INVALID_SIZE;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
return texture_id;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
|
|
|
|
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!gfx->sprite_cache) {
|
2025-11-28 14:41:35 -06:00
|
|
|
gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY;
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc(
|
|
|
|
|
gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry)
|
|
|
|
|
);
|
|
|
|
|
if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
for (u32 i = 0; i < gfx->sprite_cache_count; i++) {
|
|
|
|
|
if (gfx->sprite_cache[i].active && strcmp(gfx->sprite_cache[i].path, path) == 0) {
|
|
|
|
|
return gfx->sprite_cache[i].sprite_id;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
|
|
|
|
if (result != PXL8_OK) return result;
|
2025-10-17 17:54:33 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_ase_file ase_file;
|
2025-11-28 14:41:35 -06:00
|
|
|
result = pxl8_ase_load(path, &ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
if (result != PXL8_OK) {
|
|
|
|
|
pxl8_error("Failed to load ASE file: %s", path);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
if (ase_file.frame_count == 0) {
|
|
|
|
|
pxl8_error("No frames in ASE file");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
u32 sprite_id = pxl8_atlas_add_texture(
|
2025-10-05 17:19:59 -05:00
|
|
|
gfx->atlas,
|
|
|
|
|
ase_file.frames[0].pixels,
|
2025-10-17 17:54:33 -05:00
|
|
|
ase_file.header.width,
|
|
|
|
|
ase_file.header.height,
|
2025-11-28 23:42:57 -06:00
|
|
|
gfx->pixel_mode
|
2025-10-05 17:19:59 -05:00
|
|
|
);
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (sprite_id == UINT32_MAX) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Sprite doesn't fit in atlas");
|
|
|
|
|
return PXL8_ERROR_INVALID_SIZE;
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
|
2025-11-28 14:41:35 -06:00
|
|
|
u32 new_capacity = gfx->sprite_cache_capacity * 2;
|
|
|
|
|
pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc(
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->sprite_cache,
|
2025-11-28 14:41:35 -06:00
|
|
|
new_capacity * sizeof(pxl8_sprite_cache_entry)
|
2025-10-17 17:54:33 -05:00
|
|
|
);
|
2025-11-28 14:41:35 -06:00
|
|
|
if (!new_cache) return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
gfx->sprite_cache = new_cache;
|
|
|
|
|
gfx->sprite_cache_capacity = new_capacity;
|
2025-10-17 17:54:33 -05:00
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++];
|
|
|
|
|
entry->active = true;
|
|
|
|
|
entry->sprite_id = sprite_id;
|
|
|
|
|
strncpy(entry->path, path, sizeof(entry->path) - 1);
|
|
|
|
|
entry->path[sizeof(entry->path) - 1] = '\0';
|
|
|
|
|
|
|
|
|
|
return sprite_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->initialized) return NULL;
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
if (pxl8_gfx_ensure_atlas(gfx) != PXL8_OK) return NULL;
|
2025-10-17 17:54:33 -05:00
|
|
|
return gfx->atlas;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
|
|
|
|
|
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return PXL8_OK;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
pxl8_debug("Loading palette from: %s", path);
|
2025-10-12 05:02:19 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_ase_file ase_file;
|
|
|
|
|
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
|
|
|
|
if (result != PXL8_OK) {
|
|
|
|
|
pxl8_error("Failed to load ASE file for palette: %s", path);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2025-10-12 05:02:19 -05:00
|
|
|
|
|
|
|
|
if (ase_file.palette.entry_count == 0 || !ase_file.palette.colors) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("No palette data in ASE file");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
|
|
|
}
|
2025-10-12 05:02:19 -05:00
|
|
|
|
2025-10-05 17:19:59 -05:00
|
|
|
u32 copy_size = ase_file.palette.entry_count < gfx->palette_size
|
|
|
|
|
? ase_file.palette.entry_count
|
|
|
|
|
: gfx->palette_size;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
memcpy(gfx->palette, ase_file.palette.colors, copy_size * sizeof(u32));
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
u32 last_color = (copy_size > 0) ? gfx->palette[copy_size - 1] : 0xFF000000;
|
|
|
|
|
for (u32 i = copy_size; i < gfx->palette_size; i++) {
|
|
|
|
|
gfx->palette[i] = last_color;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_debug("Loaded palette with %u colors", copy_size);
|
2025-11-11 21:24:53 -06:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
|
|
|
|
(void)gfx;
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->initialized || !gfx->atlas) return;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
if (gfx->hal && gfx->hal->upload_atlas) {
|
2025-11-28 23:42:57 -06:00
|
|
|
gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->pixel_mode);
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_atlas_mark_clean(gfx->atlas);
|
2025-10-04 11:55:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->hal->upload_framebuffer(
|
|
|
|
|
gfx->platform_data,
|
|
|
|
|
gfx->framebuffer,
|
|
|
|
|
gfx->framebuffer_width,
|
|
|
|
|
gfx->framebuffer_height,
|
|
|
|
|
gfx->palette,
|
2025-11-28 23:42:57 -06:00
|
|
|
gfx->pixel_mode
|
2025-10-17 17:54:33 -05:00
|
|
|
);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_present(pxl8_gfx* gfx) {
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
|
|
|
|
|
|
|
|
|
gfx->hal->present(gfx->platform_data);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 11:55:04 -05:00
|
|
|
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx) return;
|
2025-10-04 11:55:04 -05:00
|
|
|
gfx->viewport = vp;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-15 11:55:00 -06:00
|
|
|
void pxl8_clear(pxl8_gfx* gfx, u32 color) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx || !gfx->framebuffer) return;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 size = gfx->framebuffer_width * gfx->framebuffer_height;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
u16* fb16 = (u16*)gfx->framebuffer;
|
|
|
|
|
u16 color16 = pxl8_rgba32_to_rgb565(color);
|
2025-08-13 15:04:49 -05:00
|
|
|
for (i32 i = 0; i < size; i++) {
|
2025-11-28 14:41:35 -06:00
|
|
|
fb16[i] = color16;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
memset(gfx->framebuffer, color & 0xFF, size);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
|
|
|
|
if (!gfx || !gfx->framebuffer) return;
|
|
|
|
|
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return;
|
2025-11-28 14:41:35 -06:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 idx = y * gfx->framebuffer_width + x;
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->framebuffer[idx] = color & 0xFF;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) {
|
|
|
|
|
if (!gfx || !gfx->framebuffer) return 0;
|
|
|
|
|
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return 0;
|
2025-11-28 14:41:35 -06:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 idx = y * gfx->framebuffer_width + x;
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
return pxl8_rgb565_to_rgba32(((u16*)gfx->framebuffer)[idx]);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
return gfx->framebuffer[idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 dx = abs(x1 - x0);
|
|
|
|
|
i32 dy = abs(y1 - y0);
|
|
|
|
|
i32 sx = x0 < x1 ? 1 : -1;
|
|
|
|
|
i32 sy = y0 < y1 ? 1 : -1;
|
|
|
|
|
i32 err = dx - dy;
|
|
|
|
|
|
|
|
|
|
while (1) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_pixel(gfx, x0, y0, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (x0 == x1 && y0 == y1) break;
|
|
|
|
|
|
|
|
|
|
i32 e2 = 2 * err;
|
|
|
|
|
if (e2 > -dy) {
|
|
|
|
|
err -= dy;
|
|
|
|
|
x0 += sx;
|
|
|
|
|
}
|
|
|
|
|
if (e2 < dx) {
|
|
|
|
|
err += dx;
|
|
|
|
|
y0 += sy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_line(gfx, x, y, x + w - 1, y, color);
|
|
|
|
|
pxl8_line(gfx, x + w - 1, y, x + w - 1, y + h - 1, color);
|
|
|
|
|
pxl8_line(gfx, x + w - 1, y + h - 1, x, y + h - 1, color);
|
|
|
|
|
pxl8_line(gfx, x, y + h - 1, x, y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 11:55:04 -05:00
|
|
|
static inline void pxl8_pixel_unchecked(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
|
|
|
|
i32 idx = y * gfx->framebuffer_width + x;
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
|
2025-10-04 11:55:04 -05:00
|
|
|
} else {
|
|
|
|
|
gfx->framebuffer[idx] = color & 0xFF;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
2025-10-04 11:55:04 -05:00
|
|
|
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);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 x = radius;
|
|
|
|
|
i32 y = 0;
|
|
|
|
|
i32 err = 0;
|
|
|
|
|
|
|
|
|
|
while (x >= y) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_pixel(gfx, cx + x, cy + y, color);
|
|
|
|
|
pxl8_pixel(gfx, cx + y, cy + x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - y, cy + x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - x, cy + y, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - x, cy - y, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - y, cy - x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx + y, cy - x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx + x, cy - y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (err <= 0) {
|
|
|
|
|
y += 1;
|
|
|
|
|
err += 2 * y + 1;
|
|
|
|
|
} else {
|
|
|
|
|
x -= 1;
|
|
|
|
|
err -= 2 * x + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
2025-10-04 11:55:04 -05:00
|
|
|
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++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
if (x * x + y * y <= radius * radius) {
|
2025-10-04 11:55:04 -05:00
|
|
|
pxl8_pixel_unchecked(gfx, cx + x, cy + y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
2025-10-04 11:55:04 -05:00
|
|
|
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;
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
((u16*)gfx->framebuffer)[fb_idx] = pxl8_rgba32_to_rgb565(color);
|
2025-10-04 11:55:04 -05:00
|
|
|
} else {
|
|
|
|
|
gfx->framebuffer[fb_idx] = (u8)color;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cursor_x += font->default_width;
|
|
|
|
|
}
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
2025-10-17 17:54:33 -05:00
|
|
|
if (!gfx || !gfx->atlas || !gfx->framebuffer) return;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, sprite_id);
|
|
|
|
|
if (!entry || !entry->active) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 clip_left = (x < 0) ? -x : 0;
|
|
|
|
|
i32 clip_top = (y < 0) ? -y : 0;
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 clip_right = (x + w > gfx->framebuffer_width) ? x + w - gfx->framebuffer_width : 0;
|
|
|
|
|
i32 clip_bottom = (y + h > gfx->framebuffer_height) ? y + h - gfx->framebuffer_height : 0;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 draw_width = w - clip_left - clip_right;
|
|
|
|
|
i32 draw_height = h - clip_top - clip_bottom;
|
|
|
|
|
|
|
|
|
|
if (draw_width <= 0 || draw_height <= 0) return;
|
|
|
|
|
|
|
|
|
|
i32 dest_x = x + clip_left;
|
|
|
|
|
i32 dest_y = y + clip_top;
|
2025-10-17 17:54:33 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
bool is_1to1_scale = (w == entry->w && h == entry->h);
|
|
|
|
|
bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0);
|
2025-10-17 17:54:33 -05:00
|
|
|
|
|
|
|
|
u32 atlas_width = pxl8_atlas_get_width(gfx->atlas);
|
|
|
|
|
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
|
|
|
|
|
2025-11-11 12:26:22 -06:00
|
|
|
if (is_1to1_scale && is_unclipped) {
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
const u16* sprite_data = (const u16*)atlas_pixels + entry->y * atlas_width + entry->x;
|
2025-11-11 12:26:22 -06:00
|
|
|
pxl8_blit_hicolor(
|
2025-11-28 14:41:35 -06:00
|
|
|
(u16*)gfx->framebuffer,
|
2025-10-05 17:19:59 -05:00
|
|
|
gfx->framebuffer_width,
|
2025-11-28 14:41:35 -06:00
|
|
|
sprite_data,
|
2025-10-17 17:54:33 -05:00
|
|
|
atlas_width,
|
2025-10-05 17:19:59 -05:00
|
|
|
x, y, w, h
|
|
|
|
|
);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-11-28 14:41:35 -06:00
|
|
|
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
|
2025-11-11 12:26:22 -06:00
|
|
|
pxl8_blit_indexed(
|
2025-10-05 17:19:59 -05:00
|
|
|
gfx->framebuffer,
|
|
|
|
|
gfx->framebuffer_width,
|
|
|
|
|
sprite_data,
|
2025-10-17 17:54:33 -05:00
|
|
|
atlas_width,
|
2025-10-05 17:19:59 -05:00
|
|
|
x, y, w, h
|
|
|
|
|
);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (i32 py = 0; py < draw_height; py++) {
|
|
|
|
|
for (i32 px = 0; px < draw_width; px++) {
|
|
|
|
|
i32 src_x = entry->x + ((px + clip_left) * entry->w) / w;
|
|
|
|
|
i32 src_y = entry->y + ((py + clip_top) * entry->h) / h;
|
2025-10-17 17:54:33 -05:00
|
|
|
i32 src_idx = src_y * atlas_width + src_x;
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
u16 pixel = ((const u16*)atlas_pixels)[src_idx];
|
|
|
|
|
if (pixel != 0) {
|
|
|
|
|
((u16*)gfx->framebuffer)[dest_idx] = pixel;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-10-17 17:54:33 -05:00
|
|
|
u8 pixel = atlas_pixels[src_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
if (pixel != 0) {
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->framebuffer[dest_idx] = pixel;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step) {
|
|
|
|
|
if (!gfx || !gfx->palette || count == 0) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
u32 temp[256];
|
|
|
|
|
for (u8 i = 0; i < count; i++) {
|
2025-10-04 04:13:48 -05:00
|
|
|
temp[i] = gfx->palette[start + i];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (u8 i = 0; i < count; i++) {
|
|
|
|
|
u8 src_idx = i;
|
|
|
|
|
u8 dst_idx = (i + step) % count;
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[start + dst_idx] = temp[src_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors) {
|
|
|
|
|
if (!gfx || !gfx->palette || !new_colors) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
|
|
|
|
gfx->palette[start + i] = new_colors[i];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color) {
|
|
|
|
|
if (!gfx || !gfx->palette || count == 0) return;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
if (amount < 0.0f) amount = 0.0f;
|
|
|
|
|
if (amount > 1.0f) amount = 1.0f;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
|
|
|
|
u8 target_r, target_g, target_b, target_a;
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_rgba32_unpack(target_color, &target_r, &target_g, &target_b, &target_a);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
2025-10-04 11:55:04 -05:00
|
|
|
u8 cur_r, cur_g, cur_b, cur_a;
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_rgba32_unpack(gfx->palette[start + i], &cur_r, &cur_g, &cur_b, &cur_a);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
gfx->palette[start + i] = pxl8_rgba32_pack(new_r, new_g, new_b, new_a);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 17:19:59 -05:00
|
|
|
void pxl8_gfx_interpolate_palettes(
|
|
|
|
|
pxl8_gfx* gfx,
|
|
|
|
|
u32* palette1,
|
|
|
|
|
u32* palette2,
|
|
|
|
|
u8 start,
|
|
|
|
|
u8 count,
|
|
|
|
|
f32 t
|
|
|
|
|
) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
if (t < 0.0f) t = 0.0f;
|
|
|
|
|
if (t > 1.0f) t = 1.0f;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
2025-10-04 11:55:04 -05:00
|
|
|
u8 r1, g1, b1, a1, r2, g2, b2, a2;
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_rgba32_unpack(palette1[i], &r1, &g1, &b1, &a1);
|
|
|
|
|
pxl8_rgba32_unpack(palette2[i], &r2, &g2, &b2, &a2);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
gfx->palette[start + i] = pxl8_rgba32_pack(r, g, b, a);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
2025-10-04 11:55:04 -05:00
|
|
|
|
|
|
|
|
u8 from_r, from_g, from_b, from_a;
|
|
|
|
|
u8 to_r, to_g, to_b, to_a;
|
2025-11-28 14:41:35 -06:00
|
|
|
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);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
f32 t = (f32)i / (f32)(count - 1);
|
2025-10-04 11:55:04 -05:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
gfx->palette[start + i] = pxl8_rgba32_pack(r, g, b, a);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
static void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx || !effects) return;
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
effects->time += dt;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
for (i32 i = 0; i < 8; i++) {
|
|
|
|
|
pxl8_palette_cycle* cycle = &effects->palette_cycles[i];
|
|
|
|
|
if (!cycle->active) continue;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
cycle->timer += dt * cycle->speed;
|
|
|
|
|
if (cycle->timer >= 1.0f) {
|
|
|
|
|
cycle->timer -= 1.0f;
|
|
|
|
|
i32 count = cycle->end_index - cycle->start_index + 1;
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_cycle_palette(gfx, cycle->start_index, count, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
pxl8_gfx_process_effects(gfx, &gfx->effects, dt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed) {
|
|
|
|
|
if (!gfx) return -1;
|
|
|
|
|
if (start_index >= end_index) return -1;
|
|
|
|
|
|
|
|
|
|
for (i32 i = 0; i < 8; i++) {
|
|
|
|
|
if (!gfx->effects.palette_cycles[i].active) {
|
|
|
|
|
gfx->effects.palette_cycles[i].active = true;
|
|
|
|
|
gfx->effects.palette_cycles[i].start_index = start_index;
|
|
|
|
|
gfx->effects.palette_cycles[i].end_index = end_index;
|
|
|
|
|
gfx->effects.palette_cycles[i].speed = speed;
|
|
|
|
|
gfx->effects.palette_cycles[i].timer = 0.0f;
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id) {
|
|
|
|
|
if (!gfx || cycle_id < 0 || cycle_id >= 8) return;
|
|
|
|
|
gfx->effects.palette_cycles[cycle_id].active = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed) {
|
|
|
|
|
if (!gfx || cycle_id < 0 || cycle_id >= 8) return;
|
|
|
|
|
gfx->effects.palette_cycles[cycle_id].speed = speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
for (i32 i = 0; i < 8; i++) {
|
|
|
|
|
gfx->effects.palette_cycles[i].active = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) {
|
|
|
|
|
if (gfx->zbuffer) return true;
|
|
|
|
|
|
|
|
|
|
gfx->zbuffer_width = gfx->framebuffer_width;
|
|
|
|
|
gfx->zbuffer_height = gfx->framebuffer_height;
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
gfx->zbuffer = (f32*)calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx->zbuffer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_3d_clear_zbuffer(gfx);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->zbuffer) return;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
|
|
|
|
i32 count = gfx->zbuffer_width * gfx->zbuffer_height;
|
|
|
|
|
const f32 far_z = 1e30f;
|
|
|
|
|
|
2025-11-15 14:23:53 -06:00
|
|
|
f32* ptr = gfx->zbuffer;
|
2025-11-09 06:30:17 -06:00
|
|
|
for (i32 i = 0; i < count; i++) {
|
2025-11-15 14:23:53 -06:00
|
|
|
ptr[i] = far_z;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->backface_culling = culling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->model = mat;
|
2025-11-09 06:30:17 -06:00
|
|
|
gfx->mvp_dirty = true;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->projection = mat;
|
2025-11-09 06:30:17 -06:00
|
|
|
gfx->mvp_dirty = true;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->view = mat;
|
2025-11-09 06:30:17 -06:00
|
|
|
gfx->mvp_dirty = true;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->wireframe = wireframe;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->affine_textures = affine;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-09 06:30:17 -06:00
|
|
|
static inline void pxl8_update_mvp(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx->mvp_dirty) return;
|
|
|
|
|
|
|
|
|
|
gfx->mvp = pxl8_mat4_multiply(gfx->projection,
|
|
|
|
|
pxl8_mat4_multiply(gfx->view, gfx->model));
|
|
|
|
|
|
|
|
|
|
pxl8_mat4 vp = pxl8_mat4_multiply(gfx->projection, gfx->view);
|
|
|
|
|
gfx->frustum = pxl8_frustum_from_matrix(vp);
|
|
|
|
|
|
|
|
|
|
gfx->mvp_dirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx) return NULL;
|
|
|
|
|
pxl8_update_mvp(gfx);
|
|
|
|
|
return &gfx->frustum;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) {
|
2025-11-09 06:30:17 -06:00
|
|
|
pxl8_update_mvp(gfx);
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_vec4 v = {
|
|
|
|
|
.x = pos.x,
|
|
|
|
|
.y = pos.y,
|
|
|
|
|
.z = pos.z,
|
|
|
|
|
.w = 1.0f,
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-09 06:30:17 -06:00
|
|
|
return pxl8_mat4_multiply_vec4(gfx->mvp, v);
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void pxl8_project_to_screen(pxl8_gfx* gfx, pxl8_vec4 clip, i32* x, i32* y, f32* z) {
|
|
|
|
|
if (fabsf(clip.w) < 1e-6f) {
|
|
|
|
|
*x = *y = 0;
|
|
|
|
|
*z = 1e30f;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 inv_w = 1.0f / clip.w;
|
|
|
|
|
f32 ndc_x = clip.x * inv_w;
|
|
|
|
|
f32 ndc_y = clip.y * inv_w;
|
|
|
|
|
|
|
|
|
|
*x = (i32)((ndc_x + 1.0f) * 0.5f * gfx->framebuffer_width);
|
|
|
|
|
*y = (i32)((1.0f - ndc_y) * 0.5f * gfx->framebuffer_height);
|
|
|
|
|
*z = clip.z * inv_w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
|
|
|
|
|
pxl8_vec4 v0 = pxl8_transform_vertex(gfx, p0);
|
|
|
|
|
pxl8_vec4 v1 = pxl8_transform_vertex(gfx, p1);
|
|
|
|
|
|
|
|
|
|
i32 x0, y0, x1, y1;
|
|
|
|
|
f32 z0, z1;
|
|
|
|
|
pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0);
|
|
|
|
|
pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1);
|
|
|
|
|
|
|
|
|
|
pxl8_line(gfx, x0, y0, x1, y1, color);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) {
|
2025-10-04 11:55:04 -05:00
|
|
|
if (y < 0 || y >= gfx->framebuffer_height) return;
|
|
|
|
|
if (xs > xe) {
|
|
|
|
|
i32 tmp = xs; xs = xe; xe = tmp;
|
2025-11-09 06:30:17 -06:00
|
|
|
f32 tmpz = z0; z0 = z1; z1 = tmpz;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (xs < 0) xs = 0;
|
|
|
|
|
if (xe >= gfx->framebuffer_width) xe = gfx->framebuffer_width - 1;
|
|
|
|
|
if (xs > xe) return;
|
|
|
|
|
|
|
|
|
|
i32 width = xe - xs;
|
|
|
|
|
i32 zbuf_offset = y * gfx->zbuffer_width;
|
|
|
|
|
i32 fb_offset = y * gfx->framebuffer_width;
|
|
|
|
|
|
2025-11-28 23:42:57 -06:00
|
|
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
2025-11-28 14:41:35 -06:00
|
|
|
u16* fb = (u16*)gfx->framebuffer;
|
|
|
|
|
u16 color16 = pxl8_rgba32_to_rgb565(color);
|
2025-11-19 22:18:08 -06:00
|
|
|
if (width == 0) {
|
|
|
|
|
i32 idx = zbuf_offset + xs;
|
|
|
|
|
if (z0 <= gfx->zbuffer[idx]) {
|
|
|
|
|
gfx->zbuffer[idx] = z0;
|
2025-11-28 14:41:35 -06:00
|
|
|
fb[fb_offset + xs] = color16;
|
2025-11-19 22:18:08 -06:00
|
|
|
}
|
|
|
|
|
return;
|
2025-11-09 06:30:17 -06:00
|
|
|
}
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
f32 dz = (z1 - z0) / (f32)width;
|
|
|
|
|
f32 z = z0;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
for (i32 x = xs; x <= xe; x++) {
|
|
|
|
|
i32 idx = zbuf_offset + x;
|
|
|
|
|
if (z <= gfx->zbuffer[idx]) {
|
|
|
|
|
gfx->zbuffer[idx] = z;
|
2025-11-28 14:41:35 -06:00
|
|
|
fb[fb_offset + x] = color16;
|
2025-11-19 22:18:08 -06:00
|
|
|
}
|
|
|
|
|
z += dz;
|
2025-11-11 21:24:53 -06:00
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
} else {
|
2025-11-19 22:18:08 -06:00
|
|
|
u8 idx_color = color & 0xFF;
|
|
|
|
|
if (width == 0) {
|
|
|
|
|
i32 idx = zbuf_offset + xs;
|
2025-11-09 06:30:17 -06:00
|
|
|
if (z0 <= gfx->zbuffer[idx]) {
|
2025-11-19 22:18:08 -06:00
|
|
|
gfx->zbuffer[idx] = z0;
|
|
|
|
|
gfx->framebuffer[fb_offset + xs] = idx_color;
|
2025-11-09 06:30:17 -06:00
|
|
|
}
|
2025-11-19 22:18:08 -06:00
|
|
|
return;
|
2025-11-09 06:30:17 -06:00
|
|
|
}
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
f32 dz = (z1 - z0) / (f32)width;
|
|
|
|
|
f32 z = z0;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
for (i32 x = xs; x <= xe; x++) {
|
|
|
|
|
i32 idx = zbuf_offset + x;
|
2025-11-10 09:39:33 -06:00
|
|
|
if (z <= gfx->zbuffer[idx]) {
|
2025-11-19 22:18:08 -06:00
|
|
|
gfx->zbuffer[idx] = z;
|
|
|
|
|
gfx->framebuffer[fb_offset + x] = idx_color;
|
2025-10-05 16:25:17 -05:00
|
|
|
}
|
2025-11-19 22:18:08 -06:00
|
|
|
z += dz;
|
2025-10-05 16:25:17 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
static inline void pxl8_fill_scanline_textured(
|
2025-11-10 09:39:33 -06:00
|
|
|
pxl8_gfx* gfx, i32 y, i32 xs, i32 xe,
|
|
|
|
|
f32 z0, f32 z1,
|
|
|
|
|
f32 u0, f32 v0, f32 w0,
|
|
|
|
|
f32 u1, f32 v1, f32 w1,
|
|
|
|
|
u32 texture_id
|
2025-10-04 04:13:48 -05:00
|
|
|
) {
|
2025-11-10 09:39:33 -06:00
|
|
|
if (y < 0 || y >= gfx->framebuffer_height) return;
|
|
|
|
|
if (xs > xe) {
|
|
|
|
|
i32 tmp = xs; xs = xe; xe = tmp;
|
|
|
|
|
f32 tmpf;
|
|
|
|
|
tmpf = z0; z0 = z1; z1 = tmpf;
|
|
|
|
|
tmpf = u0; u0 = u1; u1 = tmpf;
|
|
|
|
|
tmpf = v0; v0 = v1; v1 = tmpf;
|
|
|
|
|
tmpf = w0; w0 = w1; w1 = tmpf;
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id);
|
|
|
|
|
if (!entry || !entry->active) return;
|
|
|
|
|
|
|
|
|
|
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
|
|
|
|
u32 atlas_width = pxl8_atlas_get_width(gfx->atlas);
|
|
|
|
|
i32 tex_w = entry->w;
|
|
|
|
|
i32 tex_h = entry->h;
|
|
|
|
|
bool is_pow2 = ((tex_w & (tex_w - 1)) == 0) && ((tex_h & (tex_h - 1)) == 0) && (tex_w == tex_h);
|
|
|
|
|
i32 tex_mask = tex_w - 1;
|
|
|
|
|
i32 atlas_x_base = entry->x;
|
|
|
|
|
i32 atlas_y_base = entry->y;
|
2025-11-28 23:42:57 -06:00
|
|
|
bool is_hicolor = gfx->pixel_mode == PXL8_PIXEL_HICOLOR;
|
2025-11-19 22:18:08 -06:00
|
|
|
bool affine = gfx->affine_textures;
|
|
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
i32 span = xe - xs;
|
|
|
|
|
if (span <= 0) {
|
|
|
|
|
if (xs >= 0 && xs < gfx->framebuffer_width) {
|
|
|
|
|
i32 idx = y * gfx->zbuffer_width + xs;
|
|
|
|
|
if (z0 <= gfx->zbuffer[idx]) {
|
2025-11-19 22:18:08 -06:00
|
|
|
f32 tex_u = u0, tex_v = v0;
|
|
|
|
|
if (!affine && fabsf(w0) > 1e-6f) {
|
2025-11-10 10:51:50 -06:00
|
|
|
f32 perspective_w = 1.0f / w0;
|
2025-11-19 22:18:08 -06:00
|
|
|
tex_u *= perspective_w;
|
|
|
|
|
tex_v *= perspective_w;
|
2025-11-10 09:39:33 -06:00
|
|
|
}
|
2025-11-19 22:18:08 -06:00
|
|
|
|
|
|
|
|
i32 tu = (i32)(tex_u * tex_w);
|
|
|
|
|
i32 tv = (i32)(tex_v * tex_h);
|
|
|
|
|
i32 tx, ty;
|
|
|
|
|
if (is_pow2) {
|
|
|
|
|
tx = tu & tex_mask;
|
|
|
|
|
ty = tv & tex_mask;
|
|
|
|
|
} else {
|
|
|
|
|
tx = tu % tex_w;
|
|
|
|
|
if (tx < 0) tx += tex_w;
|
|
|
|
|
ty = tv % tex_h;
|
|
|
|
|
if (ty < 0) ty += tex_h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx);
|
|
|
|
|
|
|
|
|
|
if (is_hicolor) {
|
2025-11-28 14:41:35 -06:00
|
|
|
u16 color = ((const u16*)atlas_pixels)[atlas_idx];
|
|
|
|
|
if (color != 0) {
|
2025-11-19 22:18:08 -06:00
|
|
|
gfx->zbuffer[idx] = z0;
|
2025-11-28 14:41:35 -06:00
|
|
|
((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + xs] = color;
|
2025-11-19 22:18:08 -06:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-11-28 14:41:35 -06:00
|
|
|
u8 color = atlas_pixels[atlas_idx];
|
2025-11-19 22:18:08 -06:00
|
|
|
if (color != 0) {
|
|
|
|
|
gfx->zbuffer[idx] = z0;
|
2025-11-28 14:41:35 -06:00
|
|
|
gfx->framebuffer[y * gfx->framebuffer_width + xs] = color;
|
2025-11-19 22:18:08 -06:00
|
|
|
}
|
2025-11-10 09:39:33 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 inv_span = 1.0f / (f32)span;
|
|
|
|
|
f32 dz = (z1 - z0) * inv_span;
|
|
|
|
|
f32 du = (u1 - u0) * inv_span;
|
|
|
|
|
f32 dv = (v1 - v0) * inv_span;
|
|
|
|
|
f32 dw = (w1 - w0) * inv_span;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
f32 z = z0;
|
|
|
|
|
f32 u = u0;
|
|
|
|
|
f32 v = v0;
|
|
|
|
|
f32 w = w0;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
for (i32 x = xs; x <= xe; x++) {
|
|
|
|
|
if (x >= 0 && x < gfx->framebuffer_width) {
|
|
|
|
|
i32 idx = y * gfx->zbuffer_width + x;
|
|
|
|
|
if (z <= gfx->zbuffer[idx]) {
|
2025-11-19 22:18:08 -06:00
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
f32 tex_u = u, tex_v = v;
|
2025-11-19 22:18:08 -06:00
|
|
|
if (!affine && fabsf(w) > 1e-6f) {
|
2025-11-10 10:51:50 -06:00
|
|
|
f32 perspective_w = 1.0f / w;
|
|
|
|
|
tex_u *= perspective_w;
|
|
|
|
|
tex_v *= perspective_w;
|
2025-11-10 09:39:33 -06:00
|
|
|
}
|
2025-11-19 22:18:08 -06:00
|
|
|
|
|
|
|
|
i32 tu = (i32)(tex_u * tex_w);
|
|
|
|
|
i32 tv = (i32)(tex_v * tex_h);
|
|
|
|
|
i32 tx, ty;
|
|
|
|
|
if (is_pow2) {
|
|
|
|
|
tx = tu & tex_mask;
|
|
|
|
|
ty = tv & tex_mask;
|
|
|
|
|
} else {
|
|
|
|
|
tx = tu % tex_w;
|
|
|
|
|
if (tx < 0) tx += tex_w;
|
|
|
|
|
ty = tv % tex_h;
|
|
|
|
|
if (ty < 0) ty += tex_h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx);
|
|
|
|
|
|
|
|
|
|
if (is_hicolor) {
|
2025-11-28 14:41:35 -06:00
|
|
|
u16 color = ((const u16*)atlas_pixels)[atlas_idx];
|
|
|
|
|
if (color != 0) {
|
2025-11-19 22:18:08 -06:00
|
|
|
gfx->zbuffer[idx] = z;
|
2025-11-28 14:41:35 -06:00
|
|
|
((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + x] = color;
|
2025-11-19 22:18:08 -06:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-11-28 14:41:35 -06:00
|
|
|
u8 color = atlas_pixels[atlas_idx];
|
2025-11-19 22:18:08 -06:00
|
|
|
if (color != 0) {
|
|
|
|
|
gfx->zbuffer[idx] = z;
|
2025-11-28 14:41:35 -06:00
|
|
|
gfx->framebuffer[y * gfx->framebuffer_width + x] = color;
|
2025-11-19 22:18:08 -06:00
|
|
|
}
|
2025-11-10 09:39:33 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
z += dz;
|
|
|
|
|
u += du;
|
|
|
|
|
v += dv;
|
|
|
|
|
w += dw;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
typedef void (*pxl8_scanline_func)(pxl8_gfx*, i32, i32, i32, f32, f32, u32);
|
|
|
|
|
typedef void (*pxl8_scanline_textured_func)(pxl8_gfx*, i32, i32, i32, f32, f32, f32, f32, f32, f32, f32, f32, u32);
|
|
|
|
|
|
|
|
|
|
static void pxl8_scan_triangle(
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx* gfx,
|
|
|
|
|
i32 x0, i32 y0, f32 z0,
|
|
|
|
|
i32 x1, i32 y1, f32 z1,
|
|
|
|
|
i32 x2, i32 y2, f32 z2,
|
2025-11-09 06:30:17 -06:00
|
|
|
u32 color,
|
2025-11-10 09:39:33 -06:00
|
|
|
pxl8_scanline_func fill_scanline,
|
|
|
|
|
bool is_bottom
|
2025-10-04 04:13:48 -05:00
|
|
|
) {
|
|
|
|
|
(void)z2;
|
|
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
i32 y_start, y_end, y_step;
|
|
|
|
|
f32 x_start1, x_start2, inv_slope_1, inv_slope_2;
|
|
|
|
|
|
|
|
|
|
if (is_bottom) {
|
|
|
|
|
if (y1 == y0 || y1 < y0 || y1 - y0 > gfx->framebuffer_height) return;
|
|
|
|
|
y_start = y0;
|
|
|
|
|
y_end = y1;
|
|
|
|
|
y_step = 1;
|
|
|
|
|
x_start1 = x_start2 = (f32)x0;
|
|
|
|
|
inv_slope_1 = (f32)(x1 - x0) / (f32)(y1 - y0);
|
|
|
|
|
inv_slope_2 = (f32)(x2 - x0) / (f32)(y2 - y0);
|
|
|
|
|
} else {
|
|
|
|
|
if (y2 == y0 || y2 < y0 || y2 - y0 > gfx->framebuffer_height) return;
|
|
|
|
|
y_start = y2;
|
|
|
|
|
y_end = y0;
|
|
|
|
|
y_step = -1;
|
|
|
|
|
x_start1 = x_start2 = (f32)x2;
|
|
|
|
|
inv_slope_1 = (f32)(x2 - x0) / (f32)(y2 - y0);
|
|
|
|
|
inv_slope_2 = (f32)(x2 - x1) / (f32)(y2 - y1);
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
f32 cur_x1 = x_start1;
|
|
|
|
|
f32 cur_x2 = x_start2;
|
|
|
|
|
f32 slope_step1 = inv_slope_1 * y_step;
|
|
|
|
|
f32 slope_step2 = inv_slope_2 * y_step;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
for (i32 y = y_start; y != y_end + y_step; y += y_step) {
|
2025-11-09 06:30:17 -06:00
|
|
|
fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color);
|
2025-11-10 09:39:33 -06:00
|
|
|
cur_x1 += slope_step1;
|
|
|
|
|
cur_x2 += slope_step2;
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color) {
|
|
|
|
|
pxl8_triangle tri;
|
|
|
|
|
tri.v[0].position = v0;
|
|
|
|
|
tri.v[0].color = color;
|
|
|
|
|
tri.v[1].position = v1;
|
|
|
|
|
tri.v[1].color = color;
|
|
|
|
|
tri.v[2].position = v2;
|
|
|
|
|
tri.v[2].color = color;
|
|
|
|
|
pxl8_3d_draw_triangle(gfx, tri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) {
|
|
|
|
|
if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return;
|
|
|
|
|
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_vec4 v0 = pxl8_transform_vertex(gfx, tri.v[0].position);
|
|
|
|
|
pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position);
|
|
|
|
|
pxl8_vec4 v2 = pxl8_transform_vertex(gfx, tri.v[2].position);
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_vec4 tv0 = v0;
|
|
|
|
|
pxl8_vec4 tv1 = v1;
|
|
|
|
|
pxl8_vec4 tv2 = v2;
|
|
|
|
|
u32 tc0 = tri.v[0].color;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
i32 x0, y0, x1, y1, x2, y2;
|
|
|
|
|
f32 z0, z1, z2;
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_project_to_screen(gfx, tv0, &x0, &y0, &z0);
|
|
|
|
|
pxl8_project_to_screen(gfx, tv1, &x1, &y1, &z1);
|
|
|
|
|
pxl8_project_to_screen(gfx, tv2, &x2, &y2, &z2);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
if (gfx->backface_culling) {
|
|
|
|
|
i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
|
|
|
|
|
if (cross >= 0) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gfx->wireframe) {
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_line(gfx, x0, y0, x1, y1, tc0);
|
|
|
|
|
pxl8_line(gfx, x1, y1, x2, y2, tc0);
|
|
|
|
|
pxl8_line(gfx, x2, y2, x0, y0, tc0);
|
2025-10-04 04:13:48 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (y0 > y1) {
|
|
|
|
|
i32 tmp_i = x0; x0 = x1; x1 = tmp_i;
|
|
|
|
|
tmp_i = y0; y0 = y1; y1 = tmp_i;
|
|
|
|
|
f32 tmp_f = z0; z0 = z1; z1 = tmp_f;
|
|
|
|
|
}
|
|
|
|
|
if (y0 > y2) {
|
|
|
|
|
i32 tmp_i = x0; x0 = x2; x2 = tmp_i;
|
|
|
|
|
tmp_i = y0; y0 = y2; y2 = tmp_i;
|
|
|
|
|
f32 tmp_f = z0; z0 = z2; z2 = tmp_f;
|
|
|
|
|
}
|
|
|
|
|
if (y1 > y2) {
|
|
|
|
|
i32 tmp_i = x1; x1 = x2; x2 = tmp_i;
|
|
|
|
|
tmp_i = y1; y1 = y2; y2 = tmp_i;
|
|
|
|
|
f32 tmp_f = z1; z1 = z2; z2 = tmp_f;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_scanline_func fill_scanline = pxl8_fill_scanline;
|
2025-11-09 06:30:17 -06:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (y1 == y2) {
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, tc0, fill_scanline, true);
|
2025-10-04 04:13:48 -05:00
|
|
|
} else if (y0 == y1) {
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, tc0, fill_scanline, false);
|
2025-10-04 04:13:48 -05:00
|
|
|
} else {
|
|
|
|
|
i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0));
|
|
|
|
|
i32 y3 = y1;
|
|
|
|
|
f32 z3 = z0 + ((f32)(y1 - y0) / (f32)(y2 - y0)) * (z2 - z0);
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, tc0, fill_scanline, true);
|
|
|
|
|
pxl8_scan_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, tc0, fill_scanline, false);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
typedef struct pxl8_textured_vertex {
|
|
|
|
|
i32 x, y;
|
|
|
|
|
f32 z, u, v, w;
|
|
|
|
|
} pxl8_textured_vertex;
|
|
|
|
|
|
2025-11-10 09:39:33 -06:00
|
|
|
static void pxl8_scan_triangle_textured(
|
2025-10-05 16:25:17 -05:00
|
|
|
pxl8_gfx* gfx,
|
|
|
|
|
pxl8_textured_vertex v0,
|
|
|
|
|
pxl8_textured_vertex v1,
|
|
|
|
|
pxl8_textured_vertex v2,
|
2025-11-10 09:39:33 -06:00
|
|
|
u32 texture_id,
|
|
|
|
|
pxl8_scanline_textured_func fill_scanline,
|
|
|
|
|
bool is_bottom
|
2025-10-05 16:25:17 -05:00
|
|
|
) {
|
2025-11-10 09:39:33 -06:00
|
|
|
i32 y_start, y_end, y_step;
|
|
|
|
|
f32 x_start1, x_start2;
|
|
|
|
|
f32 z_start1, z_start2, u_start1, u_start2, v_start1, v_start2, w_start1, w_start2;
|
|
|
|
|
f32 inv_slope_1, inv_slope_2, inv_slope_z1, inv_slope_z2;
|
|
|
|
|
f32 inv_slope_u1, inv_slope_u2, inv_slope_v1, inv_slope_v2, inv_slope_w1, inv_slope_w2;
|
|
|
|
|
|
|
|
|
|
if (is_bottom) {
|
2025-11-19 22:18:08 -06:00
|
|
|
if (v1.y == v0.y || v1.y < v0.y) return;
|
2025-11-10 09:39:33 -06:00
|
|
|
y_start = v0.y;
|
|
|
|
|
y_end = v1.y;
|
|
|
|
|
y_step = 1;
|
|
|
|
|
x_start1 = x_start2 = (f32)v0.x;
|
|
|
|
|
z_start1 = z_start2 = v0.z;
|
|
|
|
|
u_start1 = u_start2 = v0.u;
|
|
|
|
|
v_start1 = v_start2 = v0.v;
|
|
|
|
|
w_start1 = w_start2 = v0.w;
|
|
|
|
|
inv_slope_1 = (f32)(v1.x - v0.x) / (f32)(v1.y - v0.y);
|
|
|
|
|
inv_slope_2 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_z1 = (v1.z - v0.z) / (f32)(v1.y - v0.y);
|
|
|
|
|
inv_slope_z2 = (v2.z - v0.z) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_u1 = (v1.u - v0.u) / (f32)(v1.y - v0.y);
|
|
|
|
|
inv_slope_u2 = (v2.u - v0.u) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_v1 = (v1.v - v0.v) / (f32)(v1.y - v0.y);
|
|
|
|
|
inv_slope_v2 = (v2.v - v0.v) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_w1 = (v1.w - v0.w) / (f32)(v1.y - v0.y);
|
|
|
|
|
inv_slope_w2 = (v2.w - v0.w) / (f32)(v2.y - v0.y);
|
|
|
|
|
} else {
|
2025-11-19 22:18:08 -06:00
|
|
|
if (v2.y == v0.y || v2.y < v0.y) return;
|
2025-11-10 09:39:33 -06:00
|
|
|
y_start = v2.y;
|
|
|
|
|
y_end = v0.y;
|
|
|
|
|
y_step = -1;
|
|
|
|
|
x_start1 = x_start2 = (f32)v2.x;
|
|
|
|
|
z_start1 = z_start2 = v2.z;
|
|
|
|
|
u_start1 = u_start2 = v2.u;
|
|
|
|
|
v_start1 = v_start2 = v2.v;
|
|
|
|
|
w_start1 = w_start2 = v2.w;
|
|
|
|
|
inv_slope_1 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_2 = (f32)(v2.x - v1.x) / (f32)(v2.y - v1.y);
|
|
|
|
|
inv_slope_z1 = (v2.z - v0.z) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_z2 = (v2.z - v1.z) / (f32)(v2.y - v1.y);
|
|
|
|
|
inv_slope_u1 = (v2.u - v0.u) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_u2 = (v2.u - v1.u) / (f32)(v2.y - v1.y);
|
|
|
|
|
inv_slope_v1 = (v2.v - v0.v) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_v2 = (v2.v - v1.v) / (f32)(v2.y - v1.y);
|
|
|
|
|
inv_slope_w1 = (v2.w - v0.w) / (f32)(v2.y - v0.y);
|
|
|
|
|
inv_slope_w2 = (v2.w - v1.w) / (f32)(v2.y - v1.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 cur_x1 = x_start1, cur_x2 = x_start2;
|
|
|
|
|
f32 cur_z1 = z_start1, cur_z2 = z_start2;
|
|
|
|
|
f32 cur_u1 = u_start1, cur_u2 = u_start2;
|
|
|
|
|
f32 cur_v1 = v_start1, cur_v2 = v_start2;
|
|
|
|
|
f32 cur_w1 = w_start1, cur_w2 = w_start2;
|
|
|
|
|
|
|
|
|
|
f32 slope_step_x1 = inv_slope_1 * y_step;
|
|
|
|
|
f32 slope_step_x2 = inv_slope_2 * y_step;
|
|
|
|
|
f32 slope_step_z1 = inv_slope_z1 * y_step;
|
|
|
|
|
f32 slope_step_z2 = inv_slope_z2 * y_step;
|
|
|
|
|
f32 slope_step_u1 = inv_slope_u1 * y_step;
|
|
|
|
|
f32 slope_step_u2 = inv_slope_u2 * y_step;
|
|
|
|
|
f32 slope_step_v1 = inv_slope_v1 * y_step;
|
|
|
|
|
f32 slope_step_v2 = inv_slope_v2 * y_step;
|
|
|
|
|
f32 slope_step_w1 = inv_slope_w1 * y_step;
|
|
|
|
|
f32 slope_step_w2 = inv_slope_w2 * y_step;
|
|
|
|
|
|
|
|
|
|
for (i32 y = y_start; y != y_end + y_step; y += y_step) {
|
|
|
|
|
fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2,
|
2025-10-05 16:25:17 -05:00
|
|
|
cur_z1, cur_z2, cur_u1, cur_v1, cur_w1, cur_u2, cur_v2, cur_w2, texture_id);
|
2025-11-10 09:39:33 -06:00
|
|
|
cur_x1 += slope_step_x1; cur_x2 += slope_step_x2;
|
|
|
|
|
cur_z1 += slope_step_z1; cur_z2 += slope_step_z2;
|
|
|
|
|
cur_u1 += slope_step_u1; cur_u2 += slope_step_u2;
|
|
|
|
|
cur_v1 += slope_step_v1; cur_v2 += slope_step_v2;
|
|
|
|
|
cur_w1 += slope_step_w1; cur_w2 += slope_step_w2;
|
2025-10-05 16:25:17 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
typedef struct {
|
|
|
|
|
pxl8_vec4 clip;
|
|
|
|
|
f32 u, v;
|
|
|
|
|
} pxl8_clip_vert;
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
i32 x, y;
|
|
|
|
|
f32 z, u, v, w;
|
|
|
|
|
} pxl8_screen_vert;
|
|
|
|
|
|
|
|
|
|
static void pxl8_clip_screen_axis(pxl8_screen_vert* in, i32 in_count, pxl8_screen_vert* out, i32* out_count, i32 plane_pos, bool is_x, bool is_min) {
|
|
|
|
|
*out_count = 0;
|
|
|
|
|
|
|
|
|
|
for (i32 i = 0; i < in_count; i++) {
|
|
|
|
|
pxl8_screen_vert curr = in[i];
|
|
|
|
|
pxl8_screen_vert next = in[(i + 1) % in_count];
|
|
|
|
|
|
|
|
|
|
i32 curr_val = is_x ? curr.x : curr.y;
|
|
|
|
|
i32 next_val = is_x ? next.x : next.y;
|
|
|
|
|
|
|
|
|
|
bool curr_inside = is_min ? (curr_val >= plane_pos) : (curr_val <= plane_pos);
|
|
|
|
|
bool next_inside = is_min ? (next_val >= plane_pos) : (next_val <= plane_pos);
|
|
|
|
|
|
|
|
|
|
if (curr_inside) {
|
|
|
|
|
out[(*out_count)++] = curr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (curr_inside != next_inside) {
|
|
|
|
|
i32 denom = next_val - curr_val;
|
|
|
|
|
if (denom != 0) {
|
|
|
|
|
f32 t = (f32)(plane_pos - curr_val) / (f32)denom;
|
|
|
|
|
out[*out_count] = (pxl8_screen_vert){
|
|
|
|
|
.x = curr.x + (i32)(t * (next.x - curr.x)),
|
|
|
|
|
.y = curr.y + (i32)(t * (next.y - curr.y)),
|
|
|
|
|
.z = curr.z + t * (next.z - curr.z),
|
|
|
|
|
.u = curr.u + t * (next.u - curr.u),
|
|
|
|
|
.v = curr.v + t * (next.v - curr.v),
|
|
|
|
|
.w = curr.w + t * (next.w - curr.w)
|
|
|
|
|
};
|
|
|
|
|
(*out_count)++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void pxl8_clip_near_plane(pxl8_clip_vert* in, i32 in_count, pxl8_clip_vert* out, i32* out_count, f32 near) {
|
|
|
|
|
*out_count = 0;
|
|
|
|
|
|
|
|
|
|
for (i32 i = 0; i < in_count; i++) {
|
|
|
|
|
pxl8_clip_vert curr = in[i];
|
|
|
|
|
pxl8_clip_vert next = in[(i + 1) % in_count];
|
|
|
|
|
|
|
|
|
|
bool curr_inside = curr.clip.w >= near;
|
|
|
|
|
bool next_inside = next.clip.w >= near;
|
|
|
|
|
|
|
|
|
|
if (curr_inside) {
|
|
|
|
|
out[(*out_count)++] = curr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (curr_inside != next_inside) {
|
|
|
|
|
f32 t = (near - curr.clip.w) / (next.clip.w - curr.clip.w);
|
|
|
|
|
|
|
|
|
|
out[*out_count] = (pxl8_clip_vert){
|
|
|
|
|
.clip = {
|
|
|
|
|
curr.clip.x + t * (next.clip.x - curr.clip.x),
|
|
|
|
|
curr.clip.y + t * (next.clip.y - curr.clip.y),
|
|
|
|
|
curr.clip.z + t * (next.clip.z - curr.clip.z),
|
|
|
|
|
near
|
|
|
|
|
},
|
|
|
|
|
.u = curr.u + t * (next.u - curr.u),
|
|
|
|
|
.v = curr.v + t * (next.v - curr.v)
|
|
|
|
|
};
|
|
|
|
|
(*out_count)++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 17:19:59 -05:00
|
|
|
void pxl8_3d_draw_triangle_textured(
|
|
|
|
|
pxl8_gfx* gfx,
|
|
|
|
|
pxl8_vec3 v0,
|
|
|
|
|
pxl8_vec3 v1,
|
|
|
|
|
pxl8_vec3 v2,
|
|
|
|
|
f32 u0,
|
|
|
|
|
f32 v0f,
|
|
|
|
|
f32 u1,
|
|
|
|
|
f32 v1f,
|
|
|
|
|
f32 u2,
|
|
|
|
|
f32 v2f,
|
|
|
|
|
u32 texture_id
|
|
|
|
|
) {
|
2025-10-05 16:25:17 -05:00
|
|
|
if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return;
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0);
|
|
|
|
|
pxl8_vec4 cv1 = pxl8_transform_vertex(gfx, v1);
|
|
|
|
|
pxl8_vec4 cv2 = pxl8_transform_vertex(gfx, v2);
|
|
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_clip_vert input[3] = {
|
|
|
|
|
{cv0, u0, v0f},
|
|
|
|
|
{cv1, u1, v1f},
|
|
|
|
|
{cv2, u2, v2f}
|
|
|
|
|
};
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_clip_vert clipped[8];
|
|
|
|
|
i32 clipped_count = 0;
|
|
|
|
|
|
|
|
|
|
f32 near_clip = 0.1f;
|
|
|
|
|
pxl8_clip_near_plane(input, 3, clipped, &clipped_count, near_clip);
|
|
|
|
|
|
|
|
|
|
if (clipped_count < 3) return;
|
|
|
|
|
|
|
|
|
|
i32 guard_band = (gfx->framebuffer_width > gfx->framebuffer_height ? gfx->framebuffer_width : gfx->framebuffer_height) * 2;
|
|
|
|
|
|
|
|
|
|
for (i32 tri = 0; tri < clipped_count - 2; tri++) {
|
|
|
|
|
pxl8_vec4 c0 = clipped[0].clip;
|
|
|
|
|
pxl8_vec4 c1 = clipped[tri + 1].clip;
|
|
|
|
|
pxl8_vec4 c2 = clipped[tri + 2].clip;
|
|
|
|
|
f32 tu0 = clipped[0].u;
|
|
|
|
|
f32 tv0 = clipped[0].v;
|
|
|
|
|
f32 tu1 = clipped[tri + 1].u;
|
|
|
|
|
f32 tv1 = clipped[tri + 1].v;
|
|
|
|
|
f32 tu2 = clipped[tri + 2].u;
|
|
|
|
|
f32 tv2 = clipped[tri + 2].v;
|
|
|
|
|
|
|
|
|
|
i32 sx0, sy0, sx1, sy1, sx2, sy2;
|
|
|
|
|
f32 z0, z1, z2;
|
|
|
|
|
pxl8_project_to_screen(gfx, c0, &sx0, &sy0, &z0);
|
|
|
|
|
pxl8_project_to_screen(gfx, c1, &sx1, &sy1, &z1);
|
|
|
|
|
pxl8_project_to_screen(gfx, c2, &sx2, &sy2, &z2);
|
|
|
|
|
|
|
|
|
|
f32 w0_recip = 1.0f / c0.w;
|
|
|
|
|
f32 w1_recip = 1.0f / c1.w;
|
|
|
|
|
f32 w2_recip = 1.0f / c2.w;
|
|
|
|
|
|
|
|
|
|
pxl8_screen_vert screen_tri[3] = {
|
|
|
|
|
{sx0, sy0, z0, tu0 * w0_recip, tv0 * w0_recip, w0_recip},
|
|
|
|
|
{sx1, sy1, z1, tu1 * w1_recip, tv1 * w1_recip, w1_recip},
|
|
|
|
|
{sx2, sy2, z2, tu2 * w2_recip, tv2 * w2_recip, w2_recip}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pxl8_screen_vert temp1[8], temp2[8];
|
|
|
|
|
i32 count1, count2;
|
|
|
|
|
|
|
|
|
|
pxl8_clip_screen_axis(screen_tri, 3, temp1, &count1, -guard_band, true, true);
|
|
|
|
|
if (count1 < 3) continue;
|
|
|
|
|
pxl8_clip_screen_axis(temp1, count1, temp2, &count2, guard_band, true, false);
|
|
|
|
|
if (count2 < 3) continue;
|
|
|
|
|
pxl8_clip_screen_axis(temp2, count2, temp1, &count1, -guard_band, false, true);
|
|
|
|
|
if (count1 < 3) continue;
|
|
|
|
|
pxl8_clip_screen_axis(temp1, count1, temp2, &count2, guard_band, false, false);
|
|
|
|
|
if (count2 < 3) continue;
|
|
|
|
|
|
|
|
|
|
for (i32 i = 1; i < count2 - 1; i++) {
|
|
|
|
|
pxl8_screen_vert v0 = temp2[0];
|
|
|
|
|
pxl8_screen_vert v1 = temp2[i];
|
|
|
|
|
pxl8_screen_vert v2 = temp2[i + 1];
|
|
|
|
|
|
|
|
|
|
pxl8_textured_vertex tv[3];
|
|
|
|
|
tv[0].x = v0.x; tv[0].y = v0.y; tv[0].z = v0.z;
|
|
|
|
|
tv[0].u = v0.u; tv[0].v = v0.v; tv[0].w = v0.w;
|
|
|
|
|
tv[1].x = v1.x; tv[1].y = v1.y; tv[1].z = v1.z;
|
|
|
|
|
tv[1].u = v1.u; tv[1].v = v1.v; tv[1].w = v1.w;
|
|
|
|
|
tv[2].x = v2.x; tv[2].y = v2.y; tv[2].z = v2.z;
|
|
|
|
|
tv[2].u = v2.u; tv[2].v = v2.v; tv[2].w = v2.w;
|
|
|
|
|
|
|
|
|
|
if (gfx->backface_culling) {
|
|
|
|
|
i32 cross =
|
|
|
|
|
(tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x);
|
|
|
|
|
if (cross >= 0) continue;
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
if (tv[0].y > tv[1].y) {
|
|
|
|
|
pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[1]; tv[1] = tmp;
|
|
|
|
|
}
|
|
|
|
|
if (tv[0].y > tv[2].y) {
|
|
|
|
|
pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[2]; tv[2] = tmp;
|
|
|
|
|
}
|
|
|
|
|
if (tv[1].y > tv[2].y) {
|
|
|
|
|
pxl8_textured_vertex tmp = tv[1]; tv[1] = tv[2]; tv[2] = tmp;
|
|
|
|
|
}
|
2025-11-10 09:39:33 -06:00
|
|
|
|
2025-11-19 22:18:08 -06:00
|
|
|
pxl8_scanline_textured_func fill_scanline = pxl8_fill_scanline_textured;
|
|
|
|
|
|
|
|
|
|
if (tv[1].y == tv[2].y) {
|
|
|
|
|
pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, true);
|
|
|
|
|
} else if (tv[0].y == tv[1].y) {
|
|
|
|
|
pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, false);
|
|
|
|
|
} else {
|
|
|
|
|
f32 t = (f32)(tv[1].y - tv[0].y) / (f32)(tv[2].y - tv[0].y);
|
|
|
|
|
pxl8_textured_vertex v3;
|
|
|
|
|
v3.x = tv[0].x + (i32)(t * (tv[2].x - tv[0].x));
|
|
|
|
|
v3.y = tv[1].y;
|
|
|
|
|
v3.z = tv[0].z + t * (tv[2].z - tv[0].z);
|
|
|
|
|
v3.u = tv[0].u + t * (tv[2].u - tv[0].u);
|
|
|
|
|
v3.v = tv[0].v + t * (tv[2].v - tv[0].v);
|
|
|
|
|
v3.w = tv[0].w + t * (tv[2].w - tv[0].w);
|
|
|
|
|
|
|
|
|
|
pxl8_scan_triangle_textured(gfx, tv[0], tv[1], v3, texture_id, fill_scanline, true);
|
|
|
|
|
pxl8_scan_triangle_textured(gfx, tv[1], v3, tv[2], texture_id, fill_scanline, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 16:25:17 -05:00
|
|
|
}
|
|
|
|
|
}
|