From fa138971e73a2cb02062865323a993407566976a Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 16 Jul 2026 23:52:47 -0500 Subject: [PATCH] Console optimized slightly for rendering --- src/dusk/console/console.c | 1 + src/dusk/console/console.h | 5 ++ src/dusk/ui/debug/uiconsole.c | 145 +++++++++++++++++++++++++++++++--- src/dusk/ui/debug/uiconsole.h | 22 +++++- src/dusk/ui/uielement.c | 2 +- 5 files changed, 162 insertions(+), 13 deletions(-) diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index e0acabba..1145b757 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -42,6 +42,7 @@ void consolePrint(const char_t *message, ...) { (CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX ); memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1); + CONSOLE.dirty = true; #ifdef DUSK_CONSOLE_POSIX threadMutexUnlock(&CONSOLE.printMutex); diff --git a/src/dusk/console/console.h b/src/dusk/console/console.h index 7ac6378b..2e1355a1 100644 --- a/src/dusk/console/console.h +++ b/src/dusk/console/console.h @@ -21,6 +21,11 @@ typedef struct { char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX]; bool_t visible; + // Set whenever the history changes; consumers rendering the console + // (e.g. uiConsoleDraw) check and clear this to know when their own + // cached representation of the history needs rebuilding. + bool_t dirty; + #ifdef DUSK_CONSOLE_POSIX threadmutex_t printMutex; #endif diff --git a/src/dusk/ui/debug/uiconsole.c b/src/dusk/ui/debug/uiconsole.c index 549cf000..29af32ca 100644 --- a/src/dusk/ui/debug/uiconsole.c +++ b/src/dusk/ui/debug/uiconsole.c @@ -10,19 +10,144 @@ #include "display/screen/screen.h" #include "display/text/text.h" #include "display/spritebatch/spritebatch.h" +#include "display/shader/shaderunlit.h" +#include "display/mesh/mesh.h" +#include "display/mesh/quad.h" +#include "util/memory.h" + +typedef struct { + mesh_t mesh; + bool_t built; + meshvertex_t *vertices; + int32_t capacity; + int32_t vertexCount; + int32_t cachedScanX; + int32_t cachedScanY; +} uiconsolecache_t; + +uiconsolecache_t UI_CONSOLE_CACHE; errorret_t uiConsoleDraw(void) { if(!CONSOLE.visible) errorOk(); - float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight; - for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { - errorChain(textDraw( - (float_t)SCREEN.scanX, - (float_t)SCREEN.scanY + lineH * (float_t)i, - CONSOLE.line[i], - COLOR_RED, - &FONT_DEFAULT - )); + if( + CONSOLE.dirty || + UI_CONSOLE_CACHE.cachedScanX != SCREEN.scanX || + UI_CONSOLE_CACHE.cachedScanY != SCREEN.scanY + ) { + errorChain(uiConsoleRebuild()); } - return spriteBatchFlush(); + + if(UI_CONSOLE_CACHE.vertexCount == 0) errorOk(); + + shadermaterial_t material = { + .unlit = { + .color = COLOR_RED, + .texture = FONT_DEFAULT.texture + } + }; + + errorChain(shaderBind(&SHADER_UNLIT)); + errorChain(shaderSetMaterial(&SHADER_UNLIT, &material)); + return meshDraw(&UI_CONSOLE_CACHE.mesh, 0, UI_CONSOLE_CACHE.vertexCount); +} + +errorret_t uiConsoleRebuild(void) { + int32_t spriteCount = 0; + for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { + char_t c; + int32_t j = 0; + while((c = CONSOLE.line[i][j++]) != '\0') { + if(c != ' ' && c != '\n') spriteCount++; + } + } + + const int32_t vertexCount = spriteCount * QUAD_VERTEX_COUNT; + if(vertexCount > UI_CONSOLE_CACHE.capacity) { + if(UI_CONSOLE_CACHE.built) { + errorChain(meshDispose(&UI_CONSOLE_CACHE.mesh)); + UI_CONSOLE_CACHE.built = false; + } + + if(UI_CONSOLE_CACHE.vertices != NULL) { + memoryFree(UI_CONSOLE_CACHE.vertices); + } + + UI_CONSOLE_CACHE.vertices = (meshvertex_t *)memoryAllocate( + sizeof(meshvertex_t) * vertexCount + ); + UI_CONSOLE_CACHE.capacity = vertexCount; + } + UI_CONSOLE_CACHE.vertexCount = vertexCount; + + if(vertexCount > 0) { + const float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight; + const float_t charW = (float_t)FONT_DEFAULT.tileset->tileWidth; + + int32_t index = 0; + int32_t row = 0; + for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { + float_t posX = (float_t)SCREEN.scanX; + float_t posY = (float_t)SCREEN.scanY + lineH * (float_t)row; + + char_t c; + int32_t j = 0; + while((c = CONSOLE.line[i][j++]) != '\0') { + if(c == '\n') { + posX = (float_t)SCREEN.scanX; + posY += lineH; + row++; + continue; + } + + if(c == ' ') { + posX += charW; + continue; + } + + const spritebatchsprite_t sprite = textGetSprite( + (vec2){ posX, posY }, c, &FONT_DEFAULT + ); + spriteBatchBufferToMesh( + &sprite, 1, + &UI_CONSOLE_CACHE.vertices[index], QUAD_VERTEX_COUNT + ); + + index += QUAD_VERTEX_COUNT; + posX += charW; + } + + row++; + } + + if(!UI_CONSOLE_CACHE.built) { + errorChain(meshInit( + &UI_CONSOLE_CACHE.mesh, + QUAD_PRIMITIVE_TYPE, + UI_CONSOLE_CACHE.capacity, + UI_CONSOLE_CACHE.vertices + )); + UI_CONSOLE_CACHE.built = true; + } else { + errorChain(meshFlush(&UI_CONSOLE_CACHE.mesh, 0, vertexCount)); + } + } + + UI_CONSOLE_CACHE.cachedScanX = SCREEN.scanX; + UI_CONSOLE_CACHE.cachedScanY = SCREEN.scanY; + CONSOLE.dirty = false; + errorOk(); +} + +errorret_t uiConsoleDispose(void) { + if(UI_CONSOLE_CACHE.built) { + errorChain(meshDispose(&UI_CONSOLE_CACHE.mesh)); + } + + if(UI_CONSOLE_CACHE.vertices != NULL) { + memoryFree(UI_CONSOLE_CACHE.vertices); + } + + memoryZero(&UI_CONSOLE_CACHE, sizeof(uiconsolecache_t)); + errorOk(); } diff --git a/src/dusk/ui/debug/uiconsole.h b/src/dusk/ui/debug/uiconsole.h index 2a227f74..dc031a07 100644 --- a/src/dusk/ui/debug/uiconsole.h +++ b/src/dusk/ui/debug/uiconsole.h @@ -9,9 +9,27 @@ #include "error/error.h" /** - * Renders the console history into the scan-safe area. - * No-ops when the console is not visible. + * Renders the console history into the scan-safe area, drawing a mesh + * that is only rebuilt when the history changes or the scan-safe origin + * moves, rather than rebuffering vertices every frame. No-ops when the + * console is not visible. * * @return Any error that occurs. */ errorret_t uiConsoleDraw(void); + +/** + * Rebuilds the cached mesh for the console's current history and + * scan-safe origin. Called automatically by uiConsoleDraw() whenever + * needed; only needs calling directly to force an immediate rebuild. + * + * @return Any error that occurs. + */ +errorret_t uiConsoleRebuild(void); + +/** + * Frees the mesh and vertex buffer built up by uiConsoleDraw(). + * + * @return Any error that occurs. + */ +errorret_t uiConsoleDispose(void); diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index fd3e48f1..74bda578 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -109,7 +109,7 @@ uielement_t UI_ELEMENTS[] = { }, // Debug items - { .draw = uiConsoleDraw }, + { .draw = uiConsoleDraw, .dispose = uiConsoleDispose }, { .draw = uiFPSDraw }, { .draw = uiPlayerPosDraw },