pxl8/src/pxl8_sdl3.c

419 lines
12 KiB
C
Raw Normal View History

2025-10-17 17:54:33 -05:00
#include "pxl8_sdl3.h"
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
2025-11-13 07:15:41 -06:00
#include "pxl8_atlas.h"
2025-11-28 14:41:35 -06:00
#include "pxl8_color.h"
2025-11-13 07:15:41 -06:00
#include "pxl8_macros.h"
2025-11-28 14:41:35 -06:00
#include "pxl8_rec.h"
2025-11-18 23:50:02 -06:00
#include "pxl8_sys.h"
2025-11-13 07:15:41 -06:00
2025-10-17 17:54:33 -05:00
typedef struct pxl8_sdl3_context {
SDL_Texture* atlas_texture;
2025-10-19 01:04:42 -05:00
SDL_Texture* framebuffer_texture;
SDL_Renderer* renderer;
SDL_Window* window;
2025-10-17 17:54:33 -05:00
u32* rgba_buffer;
size_t rgba_buffer_size;
u32 atlas_width;
u32 atlas_height;
} pxl8_sdl3_context;
static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution,
const char* title, i32 win_w, i32 win_h) {
(void)mode;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context));
2025-10-17 17:54:33 -05:00
if (!ctx) {
pxl8_error("Failed to allocate SDL3 context");
return NULL;
}
2025-11-19 22:18:08 -06:00
pxl8_size fb_size = pxl8_get_resolution_dimensions(resolution);
2025-10-17 17:54:33 -05:00
ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE);
if (!ctx->window) {
pxl8_error("Failed to create window: %s", SDL_GetError());
SDL_free(ctx);
return NULL;
}
ctx->renderer = SDL_CreateRenderer(ctx->window, NULL);
if (!ctx->renderer) {
pxl8_error("Failed to create renderer: %s", SDL_GetError());
SDL_DestroyWindow(ctx->window);
SDL_free(ctx);
return NULL;
}
2025-11-11 21:35:34 -06:00
if (!SDL_SetRenderVSync(ctx->renderer, 1)) {
pxl8_error("Failed to set vsync: %s", SDL_GetError());
}
2025-10-17 17:54:33 -05:00
ctx->framebuffer_texture = SDL_CreateTexture(ctx->renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
2025-11-19 22:18:08 -06:00
fb_size.w, fb_size.h);
2025-10-17 17:54:33 -05:00
if (!ctx->framebuffer_texture) {
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
SDL_DestroyRenderer(ctx->renderer);
SDL_DestroyWindow(ctx->window);
SDL_free(ctx);
return NULL;
}
SDL_SetTextureScaleMode(ctx->framebuffer_texture, SDL_SCALEMODE_NEAREST);
ctx->rgba_buffer = NULL;
ctx->rgba_buffer_size = 0;
return ctx;
}
static void sdl3_destroy(void* platform_data) {
if (!platform_data) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
if (ctx->rgba_buffer) {
SDL_free(ctx->rgba_buffer);
}
if (ctx->atlas_texture) {
SDL_DestroyTexture(ctx->atlas_texture);
}
if (ctx->framebuffer_texture) {
SDL_DestroyTexture(ctx->framebuffer_texture);
}
if (ctx->renderer) {
SDL_DestroyRenderer(ctx->renderer);
}
if (ctx->window) {
SDL_DestroyWindow(ctx->window);
}
SDL_free(ctx);
}
static u64 sdl3_get_ticks(void) {
return SDL_GetTicksNS();
}
static void sdl3_present(void* platform_data) {
if (!platform_data) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255);
SDL_RenderClear(ctx->renderer);
if (ctx->framebuffer_texture) {
SDL_RenderTexture(ctx->renderer, ctx->framebuffer_texture, NULL, NULL);
}
SDL_RenderPresent(ctx->renderer);
}
static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
i32 w, i32 h, const u32* palette,
pxl8_color_mode mode) {
if (!platform_data || !fb) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
2025-11-28 14:41:35 -06:00
size_t needed_size = w * h;
2025-10-17 17:54:33 -05:00
2025-11-28 14:41:35 -06:00
if (ctx->rgba_buffer_size < needed_size) {
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
if (!new_buffer) return;
ctx->rgba_buffer = new_buffer;
ctx->rgba_buffer_size = needed_size;
}
2025-10-17 17:54:33 -05:00
2025-11-28 14:41:35 -06:00
if (mode == PXL8_COLOR_MODE_HICOLOR) {
const u16* fb16 = (const u16*)fb;
for (i32 i = 0; i < w * h; i++) {
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(fb16[i]);
2025-10-17 17:54:33 -05:00
}
2025-11-28 14:41:35 -06:00
} else {
2025-11-19 22:18:08 -06:00
u32 palette_size = pxl8_get_palette_size(mode);
2025-10-17 17:54:33 -05:00
for (i32 i = 0; i < w * h; i++) {
u8 index = fb[i];
ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0xFF000000;
}
}
2025-11-28 14:41:35 -06:00
SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->rgba_buffer, w * 4);
2025-10-17 17:54:33 -05:00
}
static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
const u32* palette, pxl8_color_mode mode) {
if (!platform_data || !atlas) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
if (!pxl8_atlas_is_dirty(atlas)) return;
u32 atlas_w = pxl8_atlas_get_width(atlas);
u32 atlas_h = pxl8_atlas_get_height(atlas);
const u8* atlas_pixels = pxl8_atlas_get_pixels(atlas);
if (!ctx->atlas_texture || ctx->atlas_width != atlas_w || ctx->atlas_height != atlas_h) {
if (ctx->atlas_texture) {
SDL_DestroyTexture(ctx->atlas_texture);
}
ctx->atlas_texture = SDL_CreateTexture(
ctx->renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
atlas_w,
atlas_h
);
if (!ctx->atlas_texture) {
pxl8_error("Failed to create atlas texture: %s", SDL_GetError());
return;
}
SDL_SetTextureScaleMode(ctx->atlas_texture, SDL_SCALEMODE_NEAREST);
SDL_SetTextureBlendMode(ctx->atlas_texture, SDL_BLENDMODE_BLEND);
ctx->atlas_width = atlas_w;
ctx->atlas_height = atlas_h;
}
2025-11-28 14:41:35 -06:00
size_t needed_size = atlas_w * atlas_h;
2025-10-17 17:54:33 -05:00
2025-11-28 14:41:35 -06:00
if (ctx->rgba_buffer_size < needed_size) {
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
if (!new_buffer) return;
ctx->rgba_buffer = new_buffer;
ctx->rgba_buffer_size = needed_size;
}
2025-10-17 17:54:33 -05:00
2025-11-28 14:41:35 -06:00
if (mode == PXL8_COLOR_MODE_HICOLOR) {
const u16* atlas16 = (const u16*)atlas_pixels;
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
u16 pixel = atlas16[i];
ctx->rgba_buffer[i] = (pixel != 0) ? pxl8_rgb565_to_rgba32(pixel) : 0x00000000;
}
} else {
2025-11-19 22:18:08 -06:00
u32 palette_size = pxl8_get_palette_size(mode);
2025-10-17 17:54:33 -05:00
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
u8 index = atlas_pixels[i];
ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0x00000000;
}
}
2025-11-28 14:41:35 -06:00
SDL_UpdateTexture(ctx->atlas_texture, NULL, ctx->rgba_buffer, atlas_w * 4);
2025-10-17 17:54:33 -05:00
}
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
pxl8_error("SDL_Init failed: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
2025-11-18 23:50:02 -06:00
pxl8* sys = pxl8_create(&pxl8_hal_sdl3);
if (!sys) {
pxl8_error("Failed to create pxl8 system");
2025-10-17 17:54:33 -05:00
SDL_Quit();
return SDL_APP_FAILURE;
}
2025-11-18 23:50:02 -06:00
pxl8_result result = pxl8_init(sys, argc, argv);
if (result != PXL8_OK) {
pxl8_destroy(sys);
2025-10-17 17:54:33 -05:00
SDL_Quit();
return SDL_APP_FAILURE;
}
2025-11-18 23:50:02 -06:00
*appstate = sys;
2025-10-17 17:54:33 -05:00
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppIterate(void* appstate) {
2025-11-18 23:50:02 -06:00
pxl8* sys = (pxl8*)appstate;
if (!sys) {
2025-10-17 17:54:33 -05:00
return SDL_APP_FAILURE;
}
2025-11-18 23:50:02 -06:00
pxl8_result update_result = pxl8_update(sys);
if (update_result != PXL8_OK) {
2025-10-17 17:54:33 -05:00
return SDL_APP_FAILURE;
}
2025-11-18 23:50:02 -06:00
pxl8_result frame_result = pxl8_frame(sys);
if (frame_result != PXL8_OK) {
2025-10-17 17:54:33 -05:00
return SDL_APP_FAILURE;
}
2025-11-18 23:50:02 -06:00
return pxl8_is_running(sys) ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
2025-10-17 17:54:33 -05:00
}
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
2025-11-18 23:50:02 -06:00
pxl8* sys = (pxl8*)appstate;
if (!sys) {
return SDL_APP_CONTINUE;
}
2025-10-17 17:54:33 -05:00
2025-11-18 23:50:02 -06:00
pxl8_input_state* input = pxl8_get_input(sys);
if (!input) {
2025-10-17 17:54:33 -05:00
return SDL_APP_CONTINUE;
}
switch (event->type) {
case SDL_EVENT_QUIT:
2025-11-18 23:50:02 -06:00
pxl8_set_running(sys, false);
2025-10-17 17:54:33 -05:00
break;
case SDL_EVENT_KEY_DOWN: {
SDL_Scancode scancode = event->key.scancode;
2025-11-28 14:41:35 -06:00
if (scancode < PXL8_MAX_KEYS) {
2025-11-18 23:50:02 -06:00
if (!input->keys_down[scancode]) {
input->keys_pressed[scancode] = true;
2025-10-17 17:54:33 -05:00
}
2025-11-18 23:50:02 -06:00
input->keys_down[scancode] = true;
input->keys_released[scancode] = false;
2025-10-17 17:54:33 -05:00
}
break;
}
case SDL_EVENT_KEY_UP: {
SDL_Scancode scancode = event->key.scancode;
2025-11-28 14:41:35 -06:00
if (scancode < PXL8_MAX_KEYS) {
2025-11-18 23:50:02 -06:00
input->keys_down[scancode] = false;
input->keys_pressed[scancode] = false;
input->keys_released[scancode] = true;
2025-10-17 17:54:33 -05:00
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
u8 button = event->button.button - 1;
if (button < 3) {
2025-11-18 23:50:02 -06:00
if (!input->mouse_buttons_down[button]) {
input->mouse_buttons_pressed[button] = true;
2025-10-17 17:54:33 -05:00
}
2025-11-18 23:50:02 -06:00
input->mouse_buttons_down[button] = true;
input->mouse_buttons_released[button] = false;
2025-10-17 17:54:33 -05:00
}
break;
}
case SDL_EVENT_MOUSE_BUTTON_UP: {
u8 button = event->button.button - 1;
if (button < 3) {
2025-11-18 23:50:02 -06:00
input->mouse_buttons_down[button] = false;
input->mouse_buttons_pressed[button] = false;
input->mouse_buttons_released[button] = true;
2025-10-17 17:54:33 -05:00
}
break;
}
case SDL_EVENT_MOUSE_MOTION: {
2025-11-18 23:50:02 -06:00
pxl8_gfx* gfx = pxl8_get_gfx(sys);
if (!gfx) break;
2025-10-17 17:54:33 -05:00
2025-11-19 22:18:08 -06:00
input->mouse_dx = (i32)event->motion.xrel;
input->mouse_dy = (i32)event->motion.yrel;
2025-10-17 17:54:33 -05:00
i32 window_mouse_x = (i32)event->motion.x;
i32 window_mouse_y = (i32)event->motion.y;
SDL_Window* window = SDL_GetWindowFromID(event->motion.windowID);
if (!window) break;
i32 window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
2025-11-18 23:50:02 -06:00
pxl8_resolution resolution = pxl8_get_resolution(sys);
2025-11-19 22:18:08 -06:00
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
2025-10-17 17:54:33 -05:00
pxl8_bounds window_bounds = {0, 0, window_width, window_height};
2025-11-19 22:18:08 -06:00
pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_size.w, render_size.h);
2025-10-17 17:54:33 -05:00
2025-11-18 23:50:02 -06:00
input->mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
input->mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
2025-10-17 17:54:33 -05:00
break;
}
case SDL_EVENT_MOUSE_WHEEL: {
2025-11-18 23:50:02 -06:00
input->mouse_wheel_x = (i32)event->wheel.x;
input->mouse_wheel_y = (i32)event->wheel.y;
2025-10-17 17:54:33 -05:00
break;
}
}
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
(void)result;
2025-11-18 23:50:02 -06:00
pxl8* sys = (pxl8*)appstate;
if (sys) {
pxl8_quit(sys);
pxl8_destroy(sys);
2025-10-17 17:54:33 -05:00
}
SDL_Quit();
}
2025-11-19 22:18:08 -06:00
static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) {
if (!platform_data) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
if (!SDL_SetWindowRelativeMouseMode(ctx->window, enabled)) {
pxl8_error("Failed to set relative mouse mode: %s", SDL_GetError());
}
}
static void sdl3_set_cursor(void* platform_data, pxl8_cursor cursor) {
if (!platform_data) return;
SDL_SystemCursor sdl_cursor;
switch (cursor) {
case PXL8_CURSOR_ARROW:
sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT;
break;
case PXL8_CURSOR_HAND:
sdl_cursor = SDL_SYSTEM_CURSOR_POINTER;
break;
default:
sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT;
break;
}
SDL_Cursor* cursor_obj = SDL_CreateSystemCursor(sdl_cursor);
if (cursor_obj) {
SDL_SetCursor(cursor_obj);
}
}
static void sdl3_center_cursor(void* platform_data) {
if (!platform_data) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
i32 w, h;
if (SDL_GetWindowSize(ctx->window, &w, &h)) {
SDL_WarpMouseInWindow(ctx->window, w / 2, h / 2);
}
}
2025-10-17 17:54:33 -05:00
const pxl8_hal pxl8_hal_sdl3 = {
.create = sdl3_create,
.destroy = sdl3_destroy,
.get_ticks = sdl3_get_ticks,
.center_cursor = sdl3_center_cursor,
2025-10-17 17:54:33 -05:00
.present = sdl3_present,
.set_cursor = sdl3_set_cursor,
2025-10-17 17:54:33 -05:00
.upload_atlas = sdl3_upload_atlas,
.upload_framebuffer = sdl3_upload_framebuffer,
2025-11-19 22:18:08 -06:00
.set_relative_mouse_mode = sdl3_set_relative_mouse_mode,
2025-10-17 17:54:33 -05:00
};