refactor SDL out of core files
This commit is contained in:
parent
82ed6b4ea9
commit
9d183341ae
21 changed files with 1419 additions and 1028 deletions
693
src/pxl8_gfx.c
693
src/pxl8_gfx.c
|
|
@ -2,62 +2,30 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_atlas.h"
|
||||
#include "pxl8_blit.h"
|
||||
#include "pxl8_font.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_atlas_entry {
|
||||
bool active;
|
||||
typedef struct pxl8_sprite_cache_entry {
|
||||
char path[256];
|
||||
u32 texture_id;
|
||||
|
||||
i32 x, y, w, h;
|
||||
} pxl8_atlas_entry;
|
||||
|
||||
typedef struct pxl8_skyline_fit {
|
||||
bool found;
|
||||
u32 node_idx;
|
||||
pxl8_point pos;
|
||||
} pxl8_skyline_fit;
|
||||
|
||||
typedef struct pxl8_skyline_node {
|
||||
i32 x, y, width;
|
||||
} pxl8_skyline_node;
|
||||
|
||||
typedef struct pxl8_skyline {
|
||||
pxl8_skyline_node* nodes;
|
||||
u32 count;
|
||||
u32 capacity;
|
||||
} pxl8_skyline;
|
||||
|
||||
struct pxl8_atlas {
|
||||
u32 height, width;
|
||||
u8* pixels;
|
||||
SDL_Texture* texture;
|
||||
|
||||
bool dirty;
|
||||
|
||||
u32 entry_capacity, entry_count;
|
||||
pxl8_atlas_entry* entries;
|
||||
|
||||
u32 free_capacity, free_count;
|
||||
u32* free_list;
|
||||
|
||||
pxl8_skyline skyline;
|
||||
};
|
||||
u32 sprite_id;
|
||||
bool active;
|
||||
} pxl8_sprite_cache_entry;
|
||||
|
||||
struct pxl8_gfx {
|
||||
SDL_Renderer* renderer;
|
||||
SDL_Texture* framebuffer_texture;
|
||||
SDL_Window* window;
|
||||
const pxl8_hal* hal;
|
||||
void* platform_data;
|
||||
|
||||
pxl8_atlas* atlas;
|
||||
pxl8_sprite_cache_entry* sprite_cache;
|
||||
u32 sprite_cache_capacity;
|
||||
u32 sprite_cache_count;
|
||||
|
||||
pxl8_color_mode color_mode;
|
||||
u8* framebuffer;
|
||||
|
|
@ -82,351 +50,6 @@ struct pxl8_gfx {
|
|||
bool affine_textures;
|
||||
};
|
||||
|
||||
static pxl8_skyline_fit pxl8_skyline_find_position(
|
||||
const pxl8_skyline* skyline,
|
||||
u32 atlas_w,
|
||||
u32 atlas_h,
|
||||
u32 rect_w,
|
||||
u32 rect_h
|
||||
) {
|
||||
pxl8_skyline_fit result = {.found = false};
|
||||
i32 best_y = INT32_MAX;
|
||||
i32 best_x = 0;
|
||||
u32 best_idx = 0;
|
||||
|
||||
for (u32 i = 0; i < skyline->count; i++) {
|
||||
i32 x = skyline->nodes[i].x;
|
||||
i32 y = skyline->nodes[i].y;
|
||||
|
||||
if (x + (i32)rect_w > (i32)atlas_w) continue;
|
||||
|
||||
i32 max_y = y;
|
||||
for (u32 j = i; j < skyline->count && skyline->nodes[j].x < x + (i32)rect_w; j++) {
|
||||
if (skyline->nodes[j].y > max_y) {
|
||||
max_y = skyline->nodes[j].y;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_y + (i32)rect_h > (i32)atlas_h) continue;
|
||||
|
||||
if (max_y < best_y || (max_y == best_y && x < best_x)) {
|
||||
best_y = max_y;
|
||||
best_x = x;
|
||||
best_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_y != INT32_MAX) {
|
||||
result.found = true;
|
||||
result.pos.x = best_x;
|
||||
result.pos.y = best_y;
|
||||
result.node_idx = best_idx;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w, u32 h) {
|
||||
u32 node_idx = 0;
|
||||
for (u32 i = 0; i < skyline->count; i++) {
|
||||
if (skyline->nodes[i].x == pos.x) {
|
||||
node_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 nodes_to_remove = 0;
|
||||
for (u32 i = node_idx; i < skyline->count; i++) {
|
||||
if (skyline->nodes[i].x < pos.x + (i32)w) {
|
||||
nodes_to_remove++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
|
||||
skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2;
|
||||
skyline->nodes = (pxl8_skyline_node*)SDL_realloc(
|
||||
skyline->nodes,
|
||||
skyline->capacity * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
}
|
||||
|
||||
if (nodes_to_remove > 0) {
|
||||
SDL_memmove(
|
||||
&skyline->nodes[node_idx + 1],
|
||||
&skyline->nodes[node_idx + nodes_to_remove],
|
||||
(skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
}
|
||||
|
||||
skyline->nodes[node_idx] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w};
|
||||
skyline->count = skyline->count - nodes_to_remove + 1;
|
||||
}
|
||||
|
||||
static void pxl8_skyline_compact(pxl8_skyline* skyline) {
|
||||
for (u32 i = 0; i < skyline->count - 1; ) {
|
||||
if (skyline->nodes[i].y == skyline->nodes[i + 1].y) {
|
||||
skyline->nodes[i].width += skyline->nodes[i + 1].width;
|
||||
SDL_memmove(
|
||||
&skyline->nodes[i + 1],
|
||||
&skyline->nodes[i + 2],
|
||||
(skyline->count - i - 2) * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
skyline->count--;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_atlas* pxl8_atlas_create(
|
||||
SDL_Renderer* renderer,
|
||||
u32 width,
|
||||
u32 height,
|
||||
pxl8_color_mode color_mode
|
||||
) {
|
||||
pxl8_atlas* atlas = (pxl8_atlas*)SDL_calloc(1, sizeof(pxl8_atlas));
|
||||
if (!atlas) return NULL;
|
||||
|
||||
atlas->height = height;
|
||||
atlas->width = width;
|
||||
|
||||
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
||||
atlas->pixels = (u8*)SDL_calloc(width * height, bytes_per_pixel);
|
||||
if (!atlas->pixels) {
|
||||
SDL_free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->texture = SDL_CreateTexture(
|
||||
renderer,
|
||||
SDL_PIXELFORMAT_RGBA32,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
if (!atlas->texture) {
|
||||
SDL_free(atlas->pixels);
|
||||
SDL_free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->entry_capacity = 64;
|
||||
atlas->entries = (pxl8_atlas_entry*)SDL_calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
|
||||
if (!atlas->entries) {
|
||||
SDL_DestroyTexture(atlas->texture);
|
||||
SDL_free(atlas->pixels);
|
||||
SDL_free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->free_capacity = 16;
|
||||
atlas->free_list = (u32*)SDL_calloc(atlas->free_capacity, sizeof(u32));
|
||||
if (!atlas->free_list) {
|
||||
SDL_free(atlas->entries);
|
||||
SDL_DestroyTexture(atlas->texture);
|
||||
SDL_free(atlas->pixels);
|
||||
SDL_free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->skyline.capacity = 16;
|
||||
atlas->skyline.nodes =
|
||||
(pxl8_skyline_node*)SDL_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
|
||||
if (!atlas->skyline.nodes) {
|
||||
SDL_free(atlas->free_list);
|
||||
SDL_free(atlas->entries);
|
||||
SDL_DestroyTexture(atlas->texture);
|
||||
SDL_free(atlas->pixels);
|
||||
SDL_free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)width};
|
||||
atlas->skyline.count = 1;
|
||||
|
||||
return atlas;
|
||||
}
|
||||
|
||||
static void pxl8_atlas_destroy(pxl8_atlas* atlas) {
|
||||
if (!atlas) return;
|
||||
|
||||
SDL_free(atlas->entries);
|
||||
SDL_free(atlas->free_list);
|
||||
SDL_free(atlas->pixels);
|
||||
SDL_free(atlas->skyline.nodes);
|
||||
if (atlas->texture) SDL_DestroyTexture(atlas->texture);
|
||||
SDL_free(atlas);
|
||||
}
|
||||
|
||||
static bool pxl8_atlas_expand(
|
||||
pxl8_atlas* atlas,
|
||||
SDL_Renderer* renderer,
|
||||
pxl8_color_mode color_mode
|
||||
) {
|
||||
if (!atlas || atlas->width >= 4096) return false;
|
||||
|
||||
u32 new_size = atlas->width * 2;
|
||||
u32 old_width = atlas->width;
|
||||
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
||||
|
||||
u8* new_pixels = (u8*)SDL_calloc(new_size * new_size, bytes_per_pixel);
|
||||
if (!new_pixels) return false;
|
||||
|
||||
SDL_Texture* new_texture = SDL_CreateTexture(
|
||||
renderer,
|
||||
SDL_PIXELFORMAT_RGBA32,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
new_size,
|
||||
new_size
|
||||
);
|
||||
|
||||
if (!new_texture) {
|
||||
SDL_free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
|
||||
pxl8_skyline new_skyline;
|
||||
new_skyline.nodes = (pxl8_skyline_node*)SDL_calloc(16, sizeof(pxl8_skyline_node));
|
||||
if (!new_skyline.nodes) {
|
||||
SDL_DestroyTexture(new_texture);
|
||||
SDL_free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
|
||||
new_skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)new_size};
|
||||
new_skyline.count = 1;
|
||||
new_skyline.capacity = 16;
|
||||
|
||||
for (u32 i = 0; i < atlas->entry_count; i++) {
|
||||
if (!atlas->entries[i].active) continue;
|
||||
|
||||
pxl8_skyline_fit fit = pxl8_skyline_find_position(
|
||||
&new_skyline,
|
||||
new_size,
|
||||
new_size,
|
||||
atlas->entries[i].w,
|
||||
atlas->entries[i].h
|
||||
);
|
||||
|
||||
if (!fit.found) {
|
||||
SDL_free(new_skyline.nodes);
|
||||
SDL_DestroyTexture(new_texture);
|
||||
SDL_free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < (u32)atlas->entries[i].h; y++) {
|
||||
for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) {
|
||||
u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x);
|
||||
u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x);
|
||||
if (bytes_per_pixel == 4) {
|
||||
((u32*)new_pixels)[dst_idx] = ((u32*)atlas->pixels)[src_idx];
|
||||
} else {
|
||||
new_pixels[dst_idx] = atlas->pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atlas->entries[i].x = fit.pos.x;
|
||||
atlas->entries[i].y = fit.pos.y;
|
||||
|
||||
pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h);
|
||||
pxl8_skyline_compact(&new_skyline);
|
||||
}
|
||||
|
||||
SDL_DestroyTexture(atlas->texture);
|
||||
SDL_free(atlas->pixels);
|
||||
SDL_free(atlas->skyline.nodes);
|
||||
|
||||
atlas->pixels = new_pixels;
|
||||
atlas->texture = new_texture;
|
||||
atlas->skyline = new_skyline;
|
||||
atlas->width = new_size;
|
||||
atlas->height = new_size;
|
||||
atlas->dirty = true;
|
||||
|
||||
pxl8_debug("Atlas expanded to %ux%u", atlas->width, atlas->height);
|
||||
return true;
|
||||
}
|
||||
|
||||
static u32 pxl8_atlas_add_texture(
|
||||
pxl8_atlas* atlas,
|
||||
const u8* pixels,
|
||||
u32 w,
|
||||
u32 h,
|
||||
const char* path,
|
||||
pxl8_color_mode color_mode
|
||||
) {
|
||||
if (!atlas || !pixels) return UINT32_MAX;
|
||||
|
||||
pxl8_skyline_fit fit =
|
||||
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
||||
if (!fit.found) {
|
||||
SDL_Renderer* renderer = atlas->texture ? SDL_GetRendererFromTexture(atlas->texture) : NULL;
|
||||
|
||||
if (!pxl8_atlas_expand(atlas, renderer, color_mode)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
||||
|
||||
if (!fit.found) return UINT32_MAX;
|
||||
}
|
||||
|
||||
u32 texture_id;
|
||||
if (atlas->free_count > 0) {
|
||||
texture_id = atlas->free_list[--atlas->free_count];
|
||||
} else {
|
||||
if (atlas->entry_count >= atlas->entry_capacity) {
|
||||
atlas->entry_capacity *= 2;
|
||||
atlas->entries = (pxl8_atlas_entry*)SDL_realloc(
|
||||
atlas->entries,
|
||||
atlas->entry_capacity * sizeof(pxl8_atlas_entry)
|
||||
);
|
||||
}
|
||||
texture_id = atlas->entry_count++;
|
||||
}
|
||||
|
||||
pxl8_atlas_entry* entry = &atlas->entries[texture_id];
|
||||
entry->active = true;
|
||||
entry->texture_id = texture_id;
|
||||
entry->x = fit.pos.x;
|
||||
entry->y = fit.pos.y;
|
||||
entry->w = w;
|
||||
entry->h = h;
|
||||
|
||||
if (path) {
|
||||
strncpy(entry->path, path, sizeof(entry->path) - 1);
|
||||
entry->path[sizeof(entry->path) - 1] = '\0';
|
||||
} else {
|
||||
snprintf(entry->path, sizeof(entry->path), "procgen_%u", texture_id);
|
||||
}
|
||||
|
||||
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
||||
for (u32 y = 0; y < h; y++) {
|
||||
for (u32 x = 0; x < w; x++) {
|
||||
u32 src_idx = y * w + x;
|
||||
u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x);
|
||||
|
||||
if (bytes_per_pixel == 4) {
|
||||
((u32*)atlas->pixels)[dst_idx] = ((const u32*)pixels)[src_idx];
|
||||
} else {
|
||||
atlas->pixels[dst_idx] = pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h);
|
||||
pxl8_skyline_compact(&atlas->skyline);
|
||||
|
||||
atlas->dirty = true;
|
||||
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
||||
*r = color & 0xFF;
|
||||
*g = (color >> 8) & 0xFF;
|
||||
|
|
@ -448,7 +71,7 @@ static u32 pxl8_get_palette_size(pxl8_color_mode mode) {
|
|||
case PXL8_COLOR_MODE_FAMI: return 64;
|
||||
case PXL8_COLOR_MODE_MEGA: return 512;
|
||||
case PXL8_COLOR_MODE_GBA: return 32768;
|
||||
case PXL8_COLOR_MODE_SUPERFAMI: return 32768;
|
||||
case PXL8_COLOR_MODE_SNES: return 32768;
|
||||
default: return 256;
|
||||
}
|
||||
}
|
||||
|
|
@ -476,11 +99,11 @@ void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width,
|
|||
|
||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
||||
pxl8_bounds bounds = {0};
|
||||
if (!gfx || !gfx->window) {
|
||||
if (!gfx) {
|
||||
return bounds;
|
||||
}
|
||||
SDL_GetWindowPosition(gfx->window, &bounds.x, &bounds.y);
|
||||
SDL_GetWindowSize(gfx->window, &bounds.w, &bounds.h);
|
||||
bounds.w = gfx->framebuffer_width;
|
||||
bounds.h = gfx->framebuffer_height;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
|
|
@ -505,18 +128,21 @@ u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) {
|
|||
}
|
||||
|
||||
pxl8_gfx* pxl8_gfx_create(
|
||||
const pxl8_hal* hal,
|
||||
pxl8_color_mode mode,
|
||||
pxl8_resolution resolution,
|
||||
const char* title,
|
||||
i32 window_width,
|
||||
i32 window_height
|
||||
) {
|
||||
pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx));
|
||||
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(*gfx));
|
||||
if (!gfx) {
|
||||
pxl8_error("Failed to allocate graphics context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gfx->hal = hal;
|
||||
|
||||
gfx->color_mode = mode;
|
||||
pxl8_gfx_get_resolution_dimensions(
|
||||
resolution,
|
||||
|
|
@ -524,60 +150,25 @@ pxl8_gfx* pxl8_gfx_create(
|
|||
&gfx->framebuffer_height
|
||||
);
|
||||
|
||||
gfx->window = SDL_CreateWindow(
|
||||
title,
|
||||
window_width, window_height,
|
||||
SDL_WINDOW_RESIZABLE
|
||||
);
|
||||
|
||||
if (!gfx->window) {
|
||||
pxl8_error("Failed to create window: %s", SDL_GetError());
|
||||
pxl8_gfx_destroy(gfx);
|
||||
gfx->platform_data = gfx->hal->create(mode, resolution, title, window_width, window_height);
|
||||
if (!gfx->platform_data) {
|
||||
pxl8_error("Failed to create platform context");
|
||||
free(gfx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gfx->renderer = SDL_CreateRenderer(gfx->window, NULL);
|
||||
if (!gfx->renderer) {
|
||||
pxl8_error("Failed to create renderer: %s", SDL_GetError());
|
||||
pxl8_gfx_destroy(gfx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_SetRenderLogicalPresentation(
|
||||
gfx->renderer,
|
||||
gfx->framebuffer_width,
|
||||
gfx->framebuffer_height,
|
||||
SDL_LOGICAL_PRESENTATION_INTEGER_SCALE
|
||||
);
|
||||
|
||||
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
||||
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
|
||||
gfx->framebuffer = (u8*)SDL_calloc(1, fb_size);
|
||||
gfx->framebuffer = (u8*)calloc(1, fb_size);
|
||||
if (!gfx->framebuffer) {
|
||||
pxl8_error("Failed to allocate framebuffer");
|
||||
pxl8_gfx_destroy(gfx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gfx->framebuffer_texture = SDL_CreateTexture(
|
||||
gfx->renderer,
|
||||
SDL_PIXELFORMAT_RGBA32,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
gfx->framebuffer_width,
|
||||
gfx->framebuffer_height
|
||||
);
|
||||
|
||||
SDL_SetTextureScaleMode(gfx->framebuffer_texture, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
if (!gfx->framebuffer_texture) {
|
||||
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
|
||||
pxl8_gfx_destroy(gfx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gfx->palette_size = pxl8_get_palette_size(mode);
|
||||
if (gfx->palette_size > 0) {
|
||||
gfx->palette = (u32*)SDL_calloc(gfx->palette_size, sizeof(u32));
|
||||
gfx->palette = (u32*)calloc(gfx->palette_size, sizeof(u32));
|
||||
if (!gfx->palette) {
|
||||
pxl8_error("Failed to allocate palette");
|
||||
pxl8_gfx_destroy(gfx);
|
||||
|
|
@ -614,27 +205,17 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
|||
if (!gfx) return;
|
||||
|
||||
pxl8_atlas_destroy(gfx->atlas);
|
||||
free(gfx->sprite_cache);
|
||||
|
||||
if (gfx->framebuffer_texture) {
|
||||
SDL_DestroyTexture(gfx->framebuffer_texture);
|
||||
gfx->framebuffer_texture = NULL;
|
||||
if (gfx->hal && gfx->platform_data) {
|
||||
gfx->hal->destroy(gfx->platform_data);
|
||||
}
|
||||
|
||||
if (gfx->renderer) {
|
||||
SDL_DestroyRenderer(gfx->renderer);
|
||||
gfx->renderer = NULL;
|
||||
}
|
||||
free(gfx->framebuffer);
|
||||
free(gfx->palette);
|
||||
free(gfx->zbuffer);
|
||||
|
||||
if (gfx->window) {
|
||||
SDL_DestroyWindow(gfx->window);
|
||||
gfx->window = NULL;
|
||||
}
|
||||
|
||||
SDL_free(gfx->framebuffer);
|
||||
SDL_free(gfx->palette);
|
||||
SDL_free(gfx->zbuffer);
|
||||
|
||||
SDL_free(gfx);
|
||||
free(gfx);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -642,41 +223,41 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width,
|
|||
if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (!gfx->atlas) {
|
||||
gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode);
|
||||
gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode);
|
||||
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u32 texture_id =
|
||||
pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode);
|
||||
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->color_mode);
|
||||
if (texture_id == UINT32_MAX) {
|
||||
pxl8_error("Texture doesn't fit in atlas");
|
||||
return PXL8_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
pxl8_debug(
|
||||
"Created texture %u: %ux%u at (%d,%d)",
|
||||
texture_id, width, height,
|
||||
gfx->atlas->entries[texture_id].x,
|
||||
gfx->atlas->entries[texture_id].y
|
||||
);
|
||||
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
||||
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (!gfx->atlas) {
|
||||
gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode);
|
||||
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
if (!gfx->sprite_cache) {
|
||||
gfx->sprite_cache_capacity = 64;
|
||||
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;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < gfx->atlas->entry_count; i++) {
|
||||
if (gfx->atlas->entries[i].active && strcmp(gfx->atlas->entries[i].path, path) == 0) {
|
||||
return gfx->atlas->entries[i].texture_id;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gfx->atlas) {
|
||||
gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode);
|
||||
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_ase_file ase_file;
|
||||
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
||||
if (result != PXL8_OK) {
|
||||
|
|
@ -690,31 +271,46 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
|||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
u32 sprite_w = ase_file.header.width;
|
||||
u32 sprite_h = ase_file.header.height;
|
||||
|
||||
u32 texture_id = pxl8_atlas_add_texture(
|
||||
u32 sprite_id = pxl8_atlas_add_texture(
|
||||
gfx->atlas,
|
||||
ase_file.frames[0].pixels,
|
||||
sprite_w,
|
||||
sprite_h,
|
||||
path,
|
||||
ase_file.header.width,
|
||||
ase_file.header.height,
|
||||
gfx->color_mode
|
||||
);
|
||||
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
|
||||
if (texture_id == UINT32_MAX) {
|
||||
if (sprite_id == UINT32_MAX) {
|
||||
pxl8_error("Sprite doesn't fit in atlas");
|
||||
return PXL8_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
pxl8_debug("Loaded sprite %u: %ux%u at (%d,%d)",
|
||||
texture_id, sprite_w, sprite_h,
|
||||
gfx->atlas->entries[texture_id].x,
|
||||
gfx->atlas->entries[texture_id].y);
|
||||
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
|
||||
gfx->sprite_cache_capacity *= 2;
|
||||
gfx->sprite_cache = (pxl8_sprite_cache_entry*)realloc(
|
||||
gfx->sprite_cache,
|
||||
gfx->sprite_cache_capacity * sizeof(pxl8_sprite_cache_entry)
|
||||
);
|
||||
}
|
||||
|
||||
return texture_id;
|
||||
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;
|
||||
|
||||
if (!gfx->atlas) {
|
||||
gfx->atlas = pxl8_atlas_create(1024, 1024, gfx->color_mode);
|
||||
}
|
||||
|
||||
return gfx->atlas;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
|
||||
|
|
@ -758,94 +354,32 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
|||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static void pxl8_upload_indexed_texture(
|
||||
SDL_Texture* texture,
|
||||
const u8* indexed,
|
||||
const u32* palette,
|
||||
u32 palette_size,
|
||||
i32 width,
|
||||
i32 height,
|
||||
u32 default_color
|
||||
) {
|
||||
static u32* rgba_buffer = NULL;
|
||||
static size_t buffer_size = 0;
|
||||
size_t needed_size = width * height;
|
||||
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->atlas) return;
|
||||
|
||||
if (buffer_size < needed_size) {
|
||||
rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4);
|
||||
buffer_size = needed_size;
|
||||
if (gfx->hal && gfx->hal->upload_atlas) {
|
||||
gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->color_mode);
|
||||
pxl8_atlas_mark_clean(gfx->atlas);
|
||||
}
|
||||
|
||||
if (!rgba_buffer) return;
|
||||
|
||||
for (i32 i = 0; i < width * height; i++) {
|
||||
u8 index = indexed[i];
|
||||
rgba_buffer[i] = (index < palette_size) ? palette[index] : default_color;
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(texture, NULL, rgba_buffer, width * 4);
|
||||
}
|
||||
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return;
|
||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
SDL_UpdateTexture(
|
||||
gfx->framebuffer_texture,
|
||||
NULL,
|
||||
gfx->framebuffer,
|
||||
gfx->framebuffer_width * 4
|
||||
);
|
||||
} else {
|
||||
pxl8_upload_indexed_texture(
|
||||
gfx->framebuffer_texture,
|
||||
gfx->framebuffer,
|
||||
gfx->palette,
|
||||
gfx->palette_size,
|
||||
gfx->framebuffer_width,
|
||||
gfx->framebuffer_height,
|
||||
0xFF000000
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->atlas || !gfx->atlas->texture || !gfx->atlas->dirty) return;
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
SDL_UpdateTexture(
|
||||
gfx->atlas->texture,
|
||||
NULL,
|
||||
gfx->atlas->pixels,
|
||||
gfx->atlas->width * 4
|
||||
);
|
||||
} else {
|
||||
pxl8_upload_indexed_texture(
|
||||
gfx->atlas->texture,
|
||||
gfx->atlas->pixels,
|
||||
gfx->palette,
|
||||
gfx->palette_size,
|
||||
gfx->atlas->width,
|
||||
gfx->atlas->height,
|
||||
0x00000000
|
||||
);
|
||||
}
|
||||
|
||||
gfx->atlas->dirty = false;
|
||||
pxl8_debug("Atlas uploaded to GPU");
|
||||
gfx->hal->upload_framebuffer(
|
||||
gfx->platform_data,
|
||||
gfx->framebuffer,
|
||||
gfx->framebuffer_width,
|
||||
gfx->framebuffer_height,
|
||||
gfx->palette,
|
||||
gfx->color_mode
|
||||
);
|
||||
}
|
||||
|
||||
void pxl8_gfx_present(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized) return;
|
||||
|
||||
SDL_SetRenderDrawColor(gfx->renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(gfx->renderer);
|
||||
|
||||
if (gfx->framebuffer_texture) {
|
||||
SDL_RenderTexture(gfx->renderer, gfx->framebuffer_texture, NULL, NULL);
|
||||
}
|
||||
|
||||
SDL_RenderPresent(gfx->renderer);
|
||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||
|
||||
gfx->hal->present(gfx->platform_data);
|
||||
}
|
||||
|
||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
|
||||
|
|
@ -1057,10 +591,10 @@ void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
|||
|
||||
|
||||
void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
||||
if (!gfx || !gfx->atlas || !gfx->framebuffer || sprite_id >= gfx->atlas->entry_count) return;
|
||||
if (!gfx->atlas->entries[sprite_id].active) return;
|
||||
if (!gfx || !gfx->atlas || !gfx->framebuffer) return;
|
||||
|
||||
pxl8_atlas_entry* entry = &gfx->atlas->entries[sprite_id];
|
||||
const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, sprite_id);
|
||||
if (!entry || !entry->active) return;
|
||||
|
||||
i32 clip_left = (x < 0) ? -x : 0;
|
||||
i32 clip_top = (y < 0) ? -y : 0;
|
||||
|
|
@ -1074,19 +608,22 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
|||
|
||||
i32 dest_x = x + clip_left;
|
||||
i32 dest_y = y + clip_top;
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
u32 atlas_width = pxl8_atlas_get_width(gfx->atlas);
|
||||
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
||||
|
||||
if (is_1to1_scale && is_unclipped && pxl8_is_simd_aligned(w)) {
|
||||
const u8* sprite_data = gfx->atlas->pixels + entry->y * gfx->atlas->width + entry->x;
|
||||
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
pxl8_blit_simd_hicolor(
|
||||
(u32*)gfx->framebuffer,
|
||||
gfx->framebuffer_width,
|
||||
(const u32*)sprite_data,
|
||||
gfx->atlas->width,
|
||||
atlas_width,
|
||||
x, y, w, h
|
||||
);
|
||||
} else {
|
||||
|
|
@ -1094,7 +631,7 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
|||
gfx->framebuffer,
|
||||
gfx->framebuffer_width,
|
||||
sprite_data,
|
||||
gfx->atlas->width,
|
||||
atlas_width,
|
||||
x, y, w, h
|
||||
);
|
||||
}
|
||||
|
|
@ -1103,16 +640,16 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
|||
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;
|
||||
i32 src_idx = src_y * gfx->atlas->width + src_x;
|
||||
i32 src_idx = src_y * atlas_width + src_x;
|
||||
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
u32 pixel = ((u32*)gfx->atlas->pixels)[src_idx];
|
||||
u32 pixel = ((const u32*)atlas_pixels)[src_idx];
|
||||
if (pixel & 0xFF000000) {
|
||||
((u32*)gfx->framebuffer)[dest_idx] = pixel;
|
||||
}
|
||||
} else {
|
||||
u8 pixel = gfx->atlas->pixels[src_idx];
|
||||
u8 pixel = atlas_pixels[src_idx];
|
||||
if (pixel != 0) {
|
||||
gfx->framebuffer[dest_idx] = pixel;
|
||||
}
|
||||
|
|
@ -1238,7 +775,7 @@ static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) {
|
|||
gfx->zbuffer_width = gfx->framebuffer_width;
|
||||
gfx->zbuffer_height = gfx->framebuffer_height;
|
||||
|
||||
gfx->zbuffer = (f32*)SDL_calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32));
|
||||
gfx->zbuffer = (f32*)calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32));
|
||||
if (!gfx->zbuffer) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1349,10 +886,10 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32
|
|||
}
|
||||
|
||||
static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 v) {
|
||||
if (!gfx->atlas || texture_id >= gfx->atlas->entry_count) return 0;
|
||||
if (!gfx->atlas->entries[texture_id].active) return 0;
|
||||
if (!gfx->atlas) return 0;
|
||||
|
||||
pxl8_atlas_entry* entry = &gfx->atlas->entries[texture_id];
|
||||
const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id);
|
||||
if (!entry || !entry->active) return 0;
|
||||
|
||||
u = u - floorf(u);
|
||||
v = v - floorf(v);
|
||||
|
|
@ -1362,12 +899,14 @@ static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32
|
|||
|
||||
i32 atlas_x = entry->x + tx;
|
||||
i32 atlas_y = entry->y + ty;
|
||||
i32 idx = atlas_y * gfx->atlas->width + atlas_x;
|
||||
u32 atlas_width = pxl8_atlas_get_width(gfx->atlas);
|
||||
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
||||
i32 idx = atlas_y * atlas_width + atlas_x;
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
return ((u32*)gfx->atlas->pixels)[idx];
|
||||
return ((const u32*)atlas_pixels)[idx];
|
||||
} else {
|
||||
return gfx->atlas->pixels[idx];
|
||||
return atlas_pixels[idx];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue