refactor SDL out of core files
This commit is contained in:
parent
82ed6b4ea9
commit
9d183341ae
21 changed files with 1419 additions and 1028 deletions
392
src/pxl8_sdl3.c
Normal file
392
src/pxl8_sdl3.c
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#include "pxl8_sdl3.h"
|
||||
#include "pxl8_atlas.h"
|
||||
#include "pxl8_game.h"
|
||||
#include "pxl8_macros.h"
|
||||
|
||||
#define SDL_MAIN_USE_CALLBACKS
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
typedef struct pxl8_sdl3_context {
|
||||
SDL_Window* window;
|
||||
SDL_Renderer* renderer;
|
||||
SDL_Texture* framebuffer_texture;
|
||||
SDL_Texture* atlas_texture;
|
||||
|
||||
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(*ctx));
|
||||
if (!ctx) {
|
||||
pxl8_error("Failed to allocate SDL3 context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i32 fb_w, fb_h;
|
||||
switch (resolution) {
|
||||
case PXL8_RESOLUTION_240x160: fb_w = 240; fb_h = 160; break;
|
||||
case PXL8_RESOLUTION_320x180: fb_w = 320; fb_h = 180; break;
|
||||
case PXL8_RESOLUTION_320x240: fb_w = 320; fb_h = 240; break;
|
||||
case PXL8_RESOLUTION_640x360: fb_w = 640; fb_h = 360; break;
|
||||
case PXL8_RESOLUTION_640x480: fb_w = 640; fb_h = 480; break;
|
||||
case PXL8_RESOLUTION_800x600: fb_w = 800; fb_h = 600; break;
|
||||
case PXL8_RESOLUTION_960x540: fb_w = 960; fb_h = 540; break;
|
||||
default: fb_w = 640; fb_h = 360; break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SDL_SetRenderLogicalPresentation(ctx->renderer, fb_w, fb_h,
|
||||
SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
||||
|
||||
ctx->framebuffer_texture = SDL_CreateTexture(ctx->renderer,
|
||||
SDL_PIXELFORMAT_RGBA32,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
fb_w, fb_h);
|
||||
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;
|
||||
|
||||
if (mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
SDL_UpdateTexture(ctx->framebuffer_texture, NULL, fb, w * 4);
|
||||
} else {
|
||||
size_t needed_size = w * h;
|
||||
|
||||
if (ctx->rgba_buffer_size < needed_size) {
|
||||
ctx->rgba_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
|
||||
ctx->rgba_buffer_size = needed_size;
|
||||
}
|
||||
|
||||
if (!ctx->rgba_buffer) return;
|
||||
|
||||
u32 palette_size;
|
||||
switch (mode) {
|
||||
case PXL8_COLOR_MODE_FAMI: palette_size = 64; break;
|
||||
case PXL8_COLOR_MODE_MEGA: palette_size = 512; break;
|
||||
case PXL8_COLOR_MODE_GBA: palette_size = 32768; break;
|
||||
case PXL8_COLOR_MODE_SNES: palette_size = 32768; break;
|
||||
default: palette_size = 256; break;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < w * h; i++) {
|
||||
u8 index = fb[i];
|
||||
ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0xFF000000;
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->rgba_buffer, w * 4);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
SDL_UpdateTexture(ctx->atlas_texture, NULL, atlas_pixels, atlas_w * 4);
|
||||
} else {
|
||||
size_t needed_size = atlas_w * atlas_h;
|
||||
|
||||
if (ctx->rgba_buffer_size < needed_size) {
|
||||
ctx->rgba_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
|
||||
ctx->rgba_buffer_size = needed_size;
|
||||
}
|
||||
|
||||
if (!ctx->rgba_buffer) return;
|
||||
|
||||
u32 palette_size;
|
||||
switch (mode) {
|
||||
case PXL8_COLOR_MODE_FAMI: palette_size = 64; break;
|
||||
case PXL8_COLOR_MODE_MEGA: palette_size = 512; break;
|
||||
case PXL8_COLOR_MODE_GBA: palette_size = 32768; break;
|
||||
case PXL8_COLOR_MODE_SNES: palette_size = 32768; break;
|
||||
default: palette_size = 256; break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(ctx->atlas_texture, NULL, ctx->rgba_buffer, atlas_w * 4);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pxl8_game* game = (pxl8_game*)SDL_calloc(1, sizeof(pxl8_game));
|
||||
if (!game) {
|
||||
pxl8_error("Failed to allocate game instance");
|
||||
SDL_Quit();
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
game->hal = &pxl8_hal_sdl3;
|
||||
|
||||
pxl8_game_result result = pxl8_init(game, argc, argv);
|
||||
if (result != PXL8_GAME_CONTINUE) {
|
||||
SDL_free(game);
|
||||
SDL_Quit();
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
*appstate = game;
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppIterate(void* appstate) {
|
||||
pxl8_game* game = (pxl8_game*)appstate;
|
||||
|
||||
if (!game) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_game_result update_result = pxl8_update(game);
|
||||
if (update_result == PXL8_GAME_FAILURE) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
if (update_result == PXL8_GAME_SUCCESS) {
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
|
||||
pxl8_game_result frame_result = pxl8_frame(game);
|
||||
if (frame_result == PXL8_GAME_FAILURE) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
return (frame_result == PXL8_GAME_SUCCESS) ? SDL_APP_SUCCESS : SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
||||
pxl8_game* game = (pxl8_game*)appstate;
|
||||
|
||||
if (!game) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
game->running = false;
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
if (event->key.key == SDLK_ESCAPE) {
|
||||
game->running = false;
|
||||
}
|
||||
|
||||
SDL_Scancode scancode = event->key.scancode;
|
||||
if (scancode < 256) {
|
||||
if (!game->input.keys_down[scancode]) {
|
||||
game->input.keys_pressed[scancode] = true;
|
||||
}
|
||||
game->input.keys_down[scancode] = true;
|
||||
game->input.keys_released[scancode] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
SDL_Scancode scancode = event->key.scancode;
|
||||
if (scancode < 256) {
|
||||
game->input.keys_down[scancode] = false;
|
||||
game->input.keys_pressed[scancode] = false;
|
||||
game->input.keys_released[scancode] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
if (!game->input.mouse_buttons_down[button]) {
|
||||
game->input.mouse_buttons_pressed[button] = true;
|
||||
}
|
||||
game->input.mouse_buttons_down[button] = true;
|
||||
game->input.mouse_buttons_released[button] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
game->input.mouse_buttons_down[button] = false;
|
||||
game->input.mouse_buttons_pressed[button] = false;
|
||||
game->input.mouse_buttons_released[button] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION: {
|
||||
if (!game->gfx) break;
|
||||
|
||||
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);
|
||||
|
||||
i32 render_width, render_height;
|
||||
pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height);
|
||||
|
||||
pxl8_bounds window_bounds = {0, 0, window_width, window_height};
|
||||
pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_width, render_height);
|
||||
|
||||
game->input.mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
|
||||
game->input.mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_WHEEL: {
|
||||
game->input.mouse_wheel_x = (i32)event->wheel.x;
|
||||
game->input.mouse_wheel_y = (i32)event->wheel.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
||||
(void)result;
|
||||
|
||||
pxl8_game* game = (pxl8_game*)appstate;
|
||||
if (game) {
|
||||
pxl8_quit(game);
|
||||
SDL_free(game);
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
const pxl8_hal pxl8_hal_sdl3 = {
|
||||
.create = sdl3_create,
|
||||
.destroy = sdl3_destroy,
|
||||
.get_ticks = sdl3_get_ticks,
|
||||
.present = sdl3_present,
|
||||
.upload_atlas = sdl3_upload_atlas,
|
||||
.upload_framebuffer = sdl3_upload_framebuffer,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue