From ea1e99f022cea7c141a5a0258716e78602a594c9 Mon Sep 17 00:00:00 2001 From: odweta Date: Sat, 10 Oct 2026 10:33:56 +0200 Subject: [PATCH] add an overdraw buffer to allow for layered rendering --- include/dangerzone/engine.h | 15 +++++++-- include/dangerzone/render/anim.h | 18 +++++++++++ include/dangerzone/render/misc.h | 6 +++- include/dangerzone/render/rect.h | 6 +++- include/dangerzone/render/text.h | 6 +++- src/engine/engine.c | 52 ++++++++++++++++++++++++++------ src/engine/engine_init.c | 21 ++++++++++--- src/engine/render/anim.c | 22 ++++++++++++++ src/engine/render/misc.c | 29 ++++++++++++++++-- src/engine/render/rect.c | 8 +++-- src/engine/render/text.c | 5 ++- src/examples/2048.c | 10 +++--- src/examples/CMakeLists.txt | 8 ++--- src/internal/engine_internal.h | 8 +++-- 14 files changed, 176 insertions(+), 38 deletions(-) create mode 100644 include/dangerzone/render/anim.h create mode 100644 src/engine/render/anim.c diff --git a/include/dangerzone/engine.h b/include/dangerzone/engine.h index 114481d..e532845 100644 --- a/include/dangerzone/engine.h +++ b/include/dangerzone/engine.h @@ -4,13 +4,22 @@ #include #include +typedef enum { + BASE, + OVERDRAW +} framebuffer_e; + typedef struct { SDL_Event event; bool is_running; - bool framebuffer_dirty; + bool framebuffer_base_dirty; + bool framebuffer_overdraw_dirty; SDL_Rect dirty_rect; uint16_t width, height; - uint32_t *framebuffer; + // base layer ~ layer 0 + uint32_t *framebuffer_base; + // layer on top of that ~ layer 1 + uint32_t *framebuffer_overdraw; } game_state_t; extern game_state_t *game_state; @@ -28,4 +37,4 @@ void engine_start(); void -engine_stop(); \ No newline at end of file +engine_stop(); diff --git a/include/dangerzone/render/anim.h b/include/dangerzone/render/anim.h new file mode 100644 index 0000000..4b5919a --- /dev/null +++ b/include/dangerzone/render/anim.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +/** + * animate a rectangle's position changing over time + * => x0 -> x1, y0 -> y1, all else stays the same + */ +void +anim_rect_pos( + uint32_t x0, + uint32_t y0, + uint32_t x1, + uint32_t y1, + uint32_t w, + uint32_t h, + uint32_t color +); diff --git a/include/dangerzone/render/misc.h b/include/dangerzone/render/misc.h index 10a9b46..5662a46 100644 --- a/include/dangerzone/render/misc.h +++ b/include/dangerzone/render/misc.h @@ -1,9 +1,12 @@ #pragma once +#include + #include void put_pixel( + framebuffer_e framebuffer, unsigned int x, unsigned int y, uint32_t color @@ -11,5 +14,6 @@ put_pixel( void clear_framebuffer( + framebuffer_e framebuffer, uint32_t color -); \ No newline at end of file +); diff --git a/include/dangerzone/render/rect.h b/include/dangerzone/render/rect.h index e5ea19b..bebcae8 100644 --- a/include/dangerzone/render/rect.h +++ b/include/dangerzone/render/rect.h @@ -1,9 +1,12 @@ #pragma once +#include + #include void put_rect( + framebuffer_e framebuffer, uint32_t x, uint32_t y, uint32_t w, @@ -11,8 +14,9 @@ put_rect( uint32_t color ); +// TODO: remove, deprecated, you can use put_rect for the same thing void clear_rect(int x, int y, int w, int h, uint32_t color); void -mark_dirty_rect(int x, int y, int w, int h); \ No newline at end of file +mark_dirty_rect(int x, int y, int w, int h); diff --git a/include/dangerzone/render/text.h b/include/dangerzone/render/text.h index a7e767d..e73cce7 100644 --- a/include/dangerzone/render/text.h +++ b/include/dangerzone/render/text.h @@ -1,9 +1,12 @@ #pragma once +#include + #include void put_char( + framebuffer_e framebuffer, char c, uint32_t x, uint32_t y, @@ -14,6 +17,7 @@ put_char( void put_string( + framebuffer_e framebuffer, const char *text, uint32_t x, uint32_t y, @@ -22,4 +26,4 @@ put_string( uint32_t line_spacing, uint32_t fg_color, uint32_t bg_color -); \ No newline at end of file +); diff --git a/src/engine/engine.c b/src/engine/engine.c index 2a46828..77aabf6 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -42,9 +42,15 @@ mark_dirty_rect(int x, int y, int w, int h) .h = h }; - if (!game_state->framebuffer_dirty) { + if (!game_state->framebuffer_base_dirty) { game_state->dirty_rect = rect; - game_state->framebuffer_dirty = true; + game_state->framebuffer_base_dirty = true; + return; + } + + if (!game_state->framebuffer_overdraw_dirty) { + game_state->dirty_rect = rect; + game_state->framebuffer_overdraw_dirty = true; return; } @@ -83,9 +89,24 @@ mark_dirty_rect(int x, int y, int w, int h) } bool -upload_dirty_texture(void) -{ - if (!game_state->framebuffer_dirty) { +upload_dirty_texture( + framebuffer_e framebuffer +) { + uint32_t *current_framebuffer; + switch (framebuffer) { + case BASE: + current_framebuffer = game_state->framebuffer_base; + break; + case OVERDRAW: + current_framebuffer = game_state->framebuffer_overdraw; + break; + default: + break; + } + + if (!game_state->framebuffer_base_dirty && + !game_state->framebuffer_overdraw_dirty + ) { return true; } @@ -117,7 +138,7 @@ upload_dirty_texture(void) (size_t)rect.w * sizeof(uint32_t); const uint8_t *source = - (const uint8_t *)game_state->framebuffer + + (const uint8_t *)current_framebuffer + (size_t)rect.y * source_pitch + (size_t)rect.x * sizeof(uint32_t); @@ -134,7 +155,16 @@ upload_dirty_texture(void) SDL_UnlockTexture(texture); - game_state->framebuffer_dirty = false; + switch (framebuffer) { + case BASE: + game_state->framebuffer_base_dirty = false; + break; + case OVERDRAW: + game_state->framebuffer_overdraw_dirty = false; + break; + default: + break; + } return true; } @@ -152,7 +182,8 @@ engine_start() { internal_state->engine_update_framebuffer(); // update the texture with the modified framebuffer - upload_dirty_texture(); + upload_dirty_texture(BASE); + upload_dirty_texture(OVERDRAW); SDL_RenderClear( internal_state->sdl_state->renderer @@ -174,6 +205,7 @@ void engine_stop() { engine_sdl_destroy(internal_state->sdl_state); free(internal_state); - free(game_state->framebuffer); + free(game_state->framebuffer_base); + free(game_state->framebuffer_overdraw); free(game_state); -} \ No newline at end of file +} diff --git a/src/engine/engine_init.c b/src/engine/engine_init.c index 1a360c8..ab9dba6 100644 --- a/src/engine/engine_init.c +++ b/src/engine/engine_init.c @@ -160,14 +160,23 @@ engine_init( return RCODE_FAILURE; } - game_state->framebuffer = (uint32_t *) + game_state->framebuffer_base = (uint32_t *) malloc(sizeof(uint32_t) * window_width * window_height); - if (game_state->framebuffer == NULL) { + if (game_state->framebuffer_base == NULL) { free(internal_state); free(game_state); return RCODE_FAILURE; } + game_state->framebuffer_overdraw = (uint32_t *) + malloc(sizeof(uint32_t) * window_width * window_height); + if (game_state->framebuffer_overdraw == NULL) { + free(internal_state); + free(game_state->framebuffer_base); + free(game_state); + return RCODE_FAILURE; + } + internal_state->engine_poll_events = engine_poll_events; @@ -181,13 +190,15 @@ engine_init( ); if (internal_state->sdl_state == NULL) { free(internal_state); - free(game_state->framebuffer); + free(game_state->framebuffer_base); + free(game_state->framebuffer_overdraw); free(game_state); return RCODE_FAILURE; } game_state->is_running = true; - game_state->framebuffer_dirty = true; + game_state->framebuffer_base_dirty = true; + game_state->framebuffer_overdraw_dirty = true; game_state->dirty_rect = (SDL_Rect) { .x = 0, .y = 0, @@ -198,4 +209,4 @@ engine_init( game_state->height = internal_state->sdl_state->height; return RCODE_SUCCESS; -} \ No newline at end of file +} diff --git a/src/engine/render/anim.c b/src/engine/render/anim.c new file mode 100644 index 0000000..7a8409d --- /dev/null +++ b/src/engine/render/anim.c @@ -0,0 +1,22 @@ +#include + +void +anim_rect_pos( + uint32_t x0, + uint32_t y0, + uint32_t x1, + uint32_t y1, + uint32_t w, + uint32_t h, + uint32_t color +) { + // TODO: I THINK I NEED LAYERED BUFFERS + + // one step is one pixel + uint32_t stepsx = x1 - x0; + uint32_t stepsy = y1 - y0; + + /** + * 1. get the row / column in the direction of the movement + */ +} diff --git a/src/engine/render/misc.c b/src/engine/render/misc.c index 2e8159a..88dbe8e 100644 --- a/src/engine/render/misc.c +++ b/src/engine/render/misc.c @@ -2,16 +2,36 @@ #include #include +#include "internal/engine_internal.h" #include "internal/util/error.h" + #include +// helper function to return the correct pointer to a framebuffer for drawing selection +uint32_t* +get_framebuffer( + framebuffer_e framebuffer +) { + switch (framebuffer) { + case BASE: + return game_state->framebuffer_base; + case OVERDRAW: + return game_state->framebuffer_overdraw; + default: + return game_state->framebuffer_base; + } +} + // alter a pixel at an imaginary x & y coordinates void put_pixel( + framebuffer_e framebuffer, unsigned int x, unsigned int y, uint32_t color ) { + uint32_t *current_framebuffer = get_framebuffer(framebuffer); + uint16_t w = game_state->width; uint16_t h = game_state->height; @@ -21,7 +41,7 @@ put_pixel( "at x = %d, y = %d", x, y ); } else { - game_state->framebuffer[game_state->width * y + x] + current_framebuffer[game_state->width * y + x] = color; mark_dirty_rect(x, y, 1, 1); @@ -31,12 +51,15 @@ put_pixel( // fill the entire framebuffer with a given color void clear_framebuffer( + framebuffer_e framebuffer, uint32_t color ) { + uint32_t *current_framebuffer = get_framebuffer(framebuffer); + for (size_t i = 0; i < (size_t)game_state->width * game_state->height; ++i) { - game_state->framebuffer[i] = color; + current_framebuffer[i] = color; } mark_dirty_rect( @@ -45,4 +68,4 @@ clear_framebuffer( game_state->width, game_state->height ); -} \ No newline at end of file +} diff --git a/src/engine/render/rect.c b/src/engine/render/rect.c index ab6b74c..1946471 100644 --- a/src/engine/render/rect.c +++ b/src/engine/render/rect.c @@ -10,20 +10,22 @@ */ void put_rect( + framebuffer_e framebuffer, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color -) { +) { for (int i = x; i < x + w; i++) { for (int j = y; j < y + h; j++) { - put_pixel(i, j, color); + put_pixel(framebuffer, i, j, color); } } mark_dirty_rect(x, y, w, h); } +// TODO: remove, see rect.h void clear_rect(int x, int y, int w, int h, uint32_t color) { @@ -33,7 +35,7 @@ clear_rect(int x, int y, int w, int h, uint32_t color) px < game_state->width && py >= 0 && py < game_state->height) { - game_state->framebuffer[ + game_state->framebuffer_base[ py * game_state->width + px ] = color; } diff --git a/src/engine/render/text.c b/src/engine/render/text.c index 3a660c8..7d0b225 100644 --- a/src/engine/render/text.c +++ b/src/engine/render/text.c @@ -81,6 +81,7 @@ static const uint8_t font[128][FONT_ROWS] = { */ void put_char( + framebuffer_e framebuffer, char c, uint32_t x, uint32_t y, @@ -107,6 +108,7 @@ put_char( ? fg_color : bg_color; put_rect( + framebuffer, x + col * size, y + row * size, size, @@ -121,6 +123,7 @@ put_char( void put_string( + framebuffer_e framebuffer, const char *text, uint32_t x, uint32_t y, @@ -137,7 +140,7 @@ put_string( x = start_x; y += (FONT_ROWS + line_spacing) * size; } else { - put_char(*text, x, y, size, fg_color, bg_color); + put_char(framebuffer, *text, x, y, size, fg_color, bg_color); x += (FONT_COLS + spacing) * size; } diff --git a/src/examples/2048.c b/src/examples/2048.c index 9a06621..f864200 100644 --- a/src/examples/2048.c +++ b/src/examples/2048.c @@ -358,7 +358,8 @@ client_update_framebuffer() // 20, 20, 2, 2, 3, 0x000000, 0xFFFF00 // ); - clear_framebuffer(0x000000); + // FIXME: why? + //clear_framebuffer(BASE, 0x000000); uint32_t tile_size = game_state->width / SIZE; uint8_t border_width = 10; @@ -373,7 +374,8 @@ client_update_framebuffer() uint32_t w = tile_size - border_width; uint32_t h = tile_size - border_width; uint32_t background_color = colors(game.area[j][i]); - put_rect(x, y, w, h, background_color); + put_rect(BASE, x, y, w, h, 0xFFFFFF); + put_rect(OVERDRAW, x, y, w, h, background_color); // number in foreground uint32_t foreground_color = 0x000000; @@ -389,7 +391,7 @@ client_update_framebuffer() uint32_t n_rev = numrev(n); uint32_t digit_offset = 1; if (n != 0) { - put_string(n_str, x + border_width, y + border_width, font_size, digit_offset, 12, foreground_color, background_color); + put_string(OVERDRAW, n_str, x + border_width, y + border_width, font_size, digit_offset, 12, foreground_color, background_color); } } } @@ -402,7 +404,7 @@ client_update_framebuffer() alloced = true; sprintf(score_text, "score: %6d", game.score); - put_string(score_text, border_width / 2, tile_size * SIZE + border_width / 2, 6, 1, 12, 0xFFFFFF, 0x000000); + put_string(OVERDRAW, score_text, border_width / 2, tile_size * SIZE + border_width / 2, 6, 1, 12, 0xFFFFFF, 0x000000); if (alloced) { free(n_str); diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index dce4e40..81d3f48 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -1,8 +1,8 @@ -add_executable(game_snake snake.c) -target_link_libraries(game_snake PRIVATE dangerzone_engine) +#add_executable(game_snake snake.c) +#target_link_libraries(game_snake PRIVATE dangerzone_engine) add_executable(game_2048 2048.c) target_link_libraries(game_2048 PRIVATE dangerzone_engine) -add_executable(game_window window.c) -target_link_libraries(game_window PRIVATE dangerzone_engine) +#add_executable(game_window window.c) +#target_link_libraries(game_window PRIVATE dangerzone_engine) diff --git a/src/internal/engine_internal.h b/src/internal/engine_internal.h index bc4cd0b..d4898aa 100644 --- a/src/internal/engine_internal.h +++ b/src/internal/engine_internal.h @@ -1,9 +1,9 @@ #pragma once +#include + #include #include -#include -#include #include #define WINDOW_NAME_MAX_LENGTH 128 @@ -47,3 +47,7 @@ engine_sdl_destroy( engine_sdl_state_t *state ); +uint32_t* +get_framebuffer( + framebuffer_e framebuffer +);