From 1ff990ff447d588fc23130df074d517b1794b4b1 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 8 May 2026 20:53:05 -0500 Subject: [PATCH] Add strided memory pushing and improved spritebatching --- assets/cutscenes/MoveCubeCutscene.js | 33 ++--- src/dusk/console/console.c | 5 +- src/dusk/display/spritebatch/spritebatch.c | 127 ++++++++++++++++---- src/dusk/display/spritebatch/spritebatch.h | 32 +++++ src/dusk/display/text/font.h | 15 +++ src/dusk/display/text/text.c | 67 ++++------- src/dusk/display/text/text.h | 36 +++--- src/dusk/script/module/display/moduletext.h | 4 +- src/dusk/ui/uifps.c | 2 +- src/dusk/ui/uitextbox.c | 27 ++--- src/dusk/ui/uitextbox.h | 3 +- src/dusk/util/memory.c | 23 ++++ src/dusk/util/memory.h | 25 +++- test/util/test_memory.c | 82 +++++++++++++ 14 files changed, 355 insertions(+), 126 deletions(-) create mode 100644 src/dusk/display/text/font.h diff --git a/assets/cutscenes/MoveCubeCutscene.js b/assets/cutscenes/MoveCubeCutscene.js index a7fe1c63..6038a99b 100644 --- a/assets/cutscenes/MoveCubeCutscene.js +++ b/assets/cutscenes/MoveCubeCutscene.js @@ -7,29 +7,30 @@ function MoveCubeCutscene(params) { var startX = this.cube.position.position.x; - this.animLeft = new Animation([ - { time: 0.0, value: startX, easing: Easing.outQuad }, - { time: DURATION, value: startX - SPEED * DURATION } - ]); + // this.animLeft = new Animation([ + // { time: 0.0, value: startX, easing: Easing.outQuad }, + // { time: DURATION, value: startX - SPEED * DURATION } + // ]); - this.animRight = new Animation([ - { time: 0.0, value: startX - SPEED * DURATION, easing: Easing.inOutQuad }, - { time: DURATION, value: startX } - ]); + // this.animRight = new Animation([ + // { time: 0.0, value: startX - SPEED * DURATION, easing: Easing.inOutQuad }, + // { time: DURATION, value: startX } + // ]); } MoveCubeCutscene.prototype = Object.create(Cutscene.prototype); MoveCubeCutscene.prototype.constructor = MoveCubeCutscene; MoveCubeCutscene.prototype.update = function() { - if(!this.animLeft.complete) { - this.cube.position.position.x = this.animLeft.update(TIME.delta); - } else { - this.cube.position.position.x = this.animRight.update(TIME.delta); - if(this.animRight.complete) { - Cutscene.finish(); - } - } + Cutscene.finish(); + // if(!this.animLeft.complete) { + // this.cube.position.position.x = this.animLeft.update(TIME.delta); + // } else { + // this.cube.position.position.x = this.animRight.update(TIME.delta); + // if(this.animRight.complete) { + // Cutscene.finish(); + // } + // } }; module = MoveCubeCutscene; diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index 16bb0883..03039cba 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -129,11 +129,10 @@ errorret_t consoleDraw(void) { for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { errorChain(textDraw( - 0, FONT_TILESET_DEFAULT.tileHeight * i, + 0, FONT_DEFAULT.tileset.tileHeight * i, CONSOLE.line[i], COLOR_WHITE, - &FONT_TILESET_DEFAULT, - &FONT_TEXTURE_DEFAULT + &FONT_DEFAULT )); } return spriteBatchFlush(); diff --git a/src/dusk/display/spritebatch/spritebatch.c b/src/dusk/display/spritebatch/spritebatch.c index 1bff51f6..d46d152d 100644 --- a/src/dusk/display/spritebatch/spritebatch.c +++ b/src/dusk/display/spritebatch/spritebatch.c @@ -8,6 +8,7 @@ #include "spritebatch.h" #include "assert/assert.h" #include "util/memory.h" +#include "util/math.h" meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT]; spritebatch_t SPRITEBATCH; @@ -24,6 +25,97 @@ errorret_t spriteBatchInit() { errorOk(); } +errorret_t spriteBatchPushv( + const float_t *minX, + const float_t *minY, + const float_t *maxX, + const float_t *maxY, + const float_t *z, + #if MESH_ENABLE_COLOR + const color_t *color, + #endif + const float_t *u0, + const float_t *v0, + const float_t *u1, + const float_t *v1, + const size_t count +) { + size_t offset = 0; + while(offset < count) { + if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) { + errorChain(spriteBatchFlush()); + } + + size_t available = (size_t)( + SPRITEBATCH_SPRITES_MAX_PER_FLUSH - SPRITEBATCH.spriteCount + ); + size_t toPush = mathMin(count - offset, available); + + meshvertex_t *start = &SPRITEBATCH_VERTICES[ + (SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush * + SPRITEBATCH_SPRITES_MAX_PER_FLUSH)) * QUAD_VERTEX_COUNT + ]; + + // Fill all toPush sprites field-by-field, one memoryCopyInterleaved per + // vertex-slot per field. Vertex layout matches quadBuffer3D: + // v0=(minX,minY,z,u0,v0) v1=(maxX,minY,z,u1,v0) v2=(maxX,maxY,z,u1,v1) + // v3=(minX,minY,z,u0,v0) v4=(maxX,maxY,z,u1,v1) v5=(minX,maxY,z,u0,v1) + const size_t dstStride = sizeof(meshvertex_t) * QUAD_VERTEX_COUNT; + const size_t fSz = sizeof(float_t); + const float_t *sMinX = minX + offset; + const float_t *sMaxX = maxX + offset; + const float_t *sMinY = minY + offset; + const float_t *sMaxY = maxY + offset; + const float_t *sZ = z + offset; + const float_t *sU0 = u0 + offset; + const float_t *sU1 = u1 + offset; + const float_t *sV0 = v0 + offset; + const float_t *sV1 = v1 + offset; + + #define COPY_FIELD(vi, field, srcArr) \ + memoryCopyInterleaved(&start[vi].field, dstStride, srcArr, fSz, fSz, toPush) + + COPY_FIELD(0, pos[0], sMinX); COPY_FIELD(0, pos[1], sMinY); + COPY_FIELD(0, pos[2], sZ); COPY_FIELD(0, uv[0], sU0); + COPY_FIELD(0, uv[1], sV0); + + COPY_FIELD(1, pos[0], sMaxX); COPY_FIELD(1, pos[1], sMinY); + COPY_FIELD(1, pos[2], sZ); COPY_FIELD(1, uv[0], sU1); + COPY_FIELD(1, uv[1], sV0); + + COPY_FIELD(2, pos[0], sMaxX); COPY_FIELD(2, pos[1], sMaxY); + COPY_FIELD(2, pos[2], sZ); COPY_FIELD(2, uv[0], sU1); + COPY_FIELD(2, uv[1], sV1); + + COPY_FIELD(3, pos[0], sMinX); COPY_FIELD(3, pos[1], sMinY); + COPY_FIELD(3, pos[2], sZ); COPY_FIELD(3, uv[0], sU0); + COPY_FIELD(3, uv[1], sV0); + + COPY_FIELD(4, pos[0], sMaxX); COPY_FIELD(4, pos[1], sMaxY); + COPY_FIELD(4, pos[2], sZ); COPY_FIELD(4, uv[0], sU1); + COPY_FIELD(4, uv[1], sV1); + + COPY_FIELD(5, pos[0], sMinX); COPY_FIELD(5, pos[1], sMaxY); + COPY_FIELD(5, pos[2], sZ); COPY_FIELD(5, uv[0], sU0); + COPY_FIELD(5, uv[1], sV1); + + #undef COPY_FIELD + + #if MESH_ENABLE_COLOR + for(uint8_t vi = 0; vi < QUAD_VERTEX_COUNT; vi++) { + memoryCopyInterleaved( + &start[vi].color, dstStride, + color + offset, sizeof(color_t), sizeof(color_t), toPush + ); + } + #endif + + SPRITEBATCH.spriteCount += (int32_t)toPush; + offset += toPush; + } + errorOk(); +} + errorret_t spriteBatchPush( const float_t minX, const float_t minY, @@ -37,14 +129,13 @@ errorret_t spriteBatchPush( const float_t u1, const float_t v1 ) { - return spriteBatchPush3D( - (vec3){ minX, minY, 0 }, - (vec3){ maxX, maxY, 0 }, + return spriteBatchPushv( + &minX, &minY, &maxX, &maxY, &(float_t){0}, #if MESH_ENABLE_COLOR - color, + &color, #endif - (vec2){ u0, v0 }, - (vec2){ u1, v1 } + &u0, &v0, &u1, &v1, + 1 ); } @@ -57,25 +148,17 @@ errorret_t spriteBatchPush3D( const vec2 uv0, const vec2 uv1 ) { - // Need to flush? - if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) { - errorChain(spriteBatchFlush()); - } - - size_t vertexOffset = ( - SPRITEBATCH.spriteCount + - (SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH) - ) * QUAD_VERTEX_COUNT; - quadBuffer3D( - &SPRITEBATCH_VERTICES[vertexOffset], - min, max, - uv0, uv1 + return spriteBatchPushv( + &min[0], &min[1], + &max[0], &max[1], + &min[2], #if MESH_ENABLE_COLOR - , color + &color, #endif + &uv0[0], &uv0[1], + &uv1[0], &uv1[1], + 1 ); - SPRITEBATCH.spriteCount++; - errorOk(); } void spriteBatchClear() { diff --git a/src/dusk/display/spritebatch/spritebatch.h b/src/dusk/display/spritebatch/spritebatch.h index fddc93c9..131ffc8b 100644 --- a/src/dusk/display/spritebatch/spritebatch.h +++ b/src/dusk/display/spritebatch/spritebatch.h @@ -33,6 +33,38 @@ extern spritebatch_t SPRITEBATCH; */ errorret_t spriteBatchInit(); +/** + * Pushes multiple sprites to the batch using arrays of values. + * + * @param minX Array of minimum x coordinates. + * @param minY Array of minimum y coordinates. + * @param maxX Array of maximum x coordinates. + * @param maxY Array of maximum y coordinates. + * @param z Array of z coordinates. + * @param color Array of colors (if enabled). + * @param u0 Array of u0 texture coordinates. + * @param v0 Array of v0 texture coordinates. + * @param u1 Array of u1 texture coordinates. + * @param v1 Array of v1 texture coordinates. + * @param count Number of sprites to push. + * @return An error code indicating success or failure. + */ +errorret_t spriteBatchPushv( + const float_t *minX, + const float_t *minY, + const float_t *maxX, + const float_t *maxY, + const float_t *z, +#if MESH_ENABLE_COLOR + const color_t *color, +#endif + const float_t *u0, + const float_t *v0, + const float_t *u1, + const float_t *v1, + const size_t count +); + /** * Pushes a sprite to the batch. This basically "queues" it to render (well * technically it is buffering the vertices to the mesh at the moment, but diff --git a/src/dusk/display/text/font.h b/src/dusk/display/text/font.h new file mode 100644 index 00000000..9e15f034 --- /dev/null +++ b/src/dusk/display/text/font.h @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/texture/texture.h" +#include "display/texture/tileset.h" + +typedef struct { + texture_t texture; + tileset_t tileset; +} font_t; diff --git a/src/dusk/display/text/text.c b/src/dusk/display/text/text.c index f2eef696..b0f333b7 100644 --- a/src/dusk/display/text/text.c +++ b/src/dusk/display/text/text.c @@ -1,6 +1,6 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -13,19 +13,18 @@ #include "asset/loader/display/assettilesetloader.h" #include "display/shader/shaderunlit.h" -texture_t FONT_TEXTURE_DEFAULT; -tileset_t FONT_TILESET_DEFAULT; +font_t FONT_DEFAULT; errorret_t textInit(void) { errorChain(assetTextureLoad( - "ui/minogram.png", &FONT_TEXTURE_DEFAULT, TEXTURE_FORMAT_RGBA + "ui/minogram.png", &FONT_DEFAULT.texture, TEXTURE_FORMAT_RGBA )); - errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_TILESET_DEFAULT)); + errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_DEFAULT.tileset)); errorOk(); } errorret_t textDispose(void) { - errorChain(textureDispose(&FONT_TEXTURE_DEFAULT)); + errorChain(textureDispose(&FONT_DEFAULT.texture)); errorOk(); } @@ -36,27 +35,24 @@ errorret_t textDrawChar( #if MESH_ENABLE_COLOR const color_t color, #endif - const tileset_t *tileset, - texture_t *texture + font_t *font ) { int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START; - if(tileIndex < 0 || tileIndex >= tileset->tileCount) { + if(tileIndex < 0 || tileIndex >= font->tileset.tileCount) { tileIndex = ((int32_t)'@') - TEXT_CHAR_START; } assertTrue( - tileIndex >= 0 && tileIndex <= tileset->tileCount, + tileIndex >= 0 && tileIndex <= font->tileset.tileCount, "Character is out of bounds for font tiles" ); vec4 uv; - tilesetTileGetUV(tileset, tileIndex, uv); - + tilesetTileGetUV(&font->tileset, tileIndex, uv); errorChain(spriteBatchPush( - // texture, x, y, - x + tileset->tileWidth, - y + tileset->tileHeight, + x + font->tileset.tileWidth, + y + font->tileset.tileHeight, #if MESH_ENABLE_COLOR color, #endif @@ -65,47 +61,36 @@ errorret_t textDrawChar( errorOk(); } - errorret_t textDraw( const float_t x, const float_t y, const char_t *text, const color_t color, - const tileset_t *tileset, - texture_t *texture + font_t *font ) { assertNotNull(text, "Text cannot be NULL"); float_t posX = x; float_t posY = y; - errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture)); + errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &font->texture)); #if MESH_ENABLE_COLOR #else errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color)); #endif - // errorChain(spriteBatchPush( - // // texture, - // posX, posY, - // posX + texture->width * 1, posY + texture->height * 1, - // color, - // 0.0f, 0.0f, 1.0f, 1.0f - // )); - // errorOk(); - char_t c; int32_t i = 0; while((c = text[i++]) != '\0') { if(c == '\n') { posX = x; - posY += tileset->tileHeight; + posY += font->tileset.tileHeight; continue; } if(c == ' ') { - posX += tileset->tileWidth; + posX += font->tileset.tileWidth; continue; } @@ -114,16 +99,16 @@ errorret_t textDraw( #if MESH_ENABLE_COLOR color, #endif - tileset, texture + font )); - posX += tileset->tileWidth; + posX += font->tileset.tileWidth; } errorOk(); } void textMeasure( const char_t *text, - const tileset_t *tileset, + const font_t *font, int32_t *outWidth, int32_t *outHeight ) { @@ -132,28 +117,24 @@ void textMeasure( assertNotNull(outHeight, "Output height pointer cannot be NULL"); int32_t width = 0; - int32_t height = tileset->tileHeight; + int32_t height = font->tileset.tileHeight; int32_t lineWidth = 0; char_t c; int32_t i = 0; while((c = text[i++]) != '\0') { if(c == '\n') { - if(lineWidth > width) { - width = lineWidth; - } + if(lineWidth > width) width = lineWidth; lineWidth = 0; - height += tileset->tileHeight; + height += font->tileset.tileHeight; continue; } - lineWidth += tileset->tileWidth; + lineWidth += font->tileset.tileWidth; } - if(lineWidth > width) { - width = lineWidth; - } + if(lineWidth > width) width = lineWidth; *outWidth = width; *outHeight = height; -} \ No newline at end of file +} diff --git a/src/dusk/display/text/text.h b/src/dusk/display/text/text.h index a70b8df0..166eeeb7 100644 --- a/src/dusk/display/text/text.h +++ b/src/dusk/display/text/text.h @@ -1,43 +1,40 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #pragma once #include "asset/asset.h" -#include "display/texture/texture.h" -#include "display/texture/tileset.h" +#include "display/text/font.h" #define TEXT_CHAR_START '!' -extern texture_t FONT_TEXTURE_DEFAULT; -extern tileset_t FONT_TILESET_DEFAULT; +extern font_t FONT_DEFAULT; /** * Initializes the text system. - * + * * @return Either an error or success result. */ errorret_t textInit(void); /** * Disposes of the text system. - * + * * @return Either an error or success result. */ errorret_t textDispose(void); /** * Draws a single character at the specified position. - * + * * @param x The x-coordinate to draw the character at. * @param y The y-coordinate to draw the character at. * @param c The character to draw. * @param color The color to draw the character in. - * @param tileset Font tileset to use for rendering. - * @param texture Texture containing the font tileset image. + * @param font Font to use for rendering. * @return Either an error or success result. */ errorret_t textDrawChar( @@ -47,19 +44,17 @@ errorret_t textDrawChar( #if MESH_ENABLE_COLOR const color_t color, #endif - const tileset_t *tileset, - texture_t *texture + font_t *font ); /** * Draws a string of text at the specified position. - * + * * @param x The x-coordinate to draw the text at. * @param y The y-coordinate to draw the text at. * @param text The null-terminated string of text to draw. * @param color The color to draw the text in. - * @param tileset Font tileset to use for rendering. - * @param texture Texture containing the font tileset image. + * @param font Font to use for rendering. * @return Either an error or success result. */ errorret_t textDraw( @@ -67,21 +62,20 @@ errorret_t textDraw( const float_t y, const char_t *text, const color_t color, - const tileset_t *tileset, - texture_t *texture + font_t *font ); /** * Measures the width and height of the given text string when rendered. - * + * * @param text The null-terminated string of text to measure. - * @param tileset Font tileset to use for measurement. + * @param font Font to use for measurement. * @param outWidth Pointer to store the measured width in pixels. * @param outHeight Pointer to store the measured height in pixels. */ void textMeasure( const char_t *text, - const tileset_t *tileset, + const font_t *font, int32_t *outWidth, int32_t *outHeight -); \ No newline at end of file +); diff --git a/src/dusk/script/module/display/moduletext.h b/src/dusk/script/module/display/moduletext.h index d5e4552b..ab84561e 100644 --- a/src/dusk/script/module/display/moduletext.h +++ b/src/dusk/script/module/display/moduletext.h @@ -34,7 +34,7 @@ moduleBaseFunction(moduleTextDraw) { } errorret_t err = textDraw( - x, y, text, col, &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT + x, y, text, col, &FONT_DEFAULT ); if(err.code != ERROR_OK) { errorCatch(errorPrint(err)); @@ -54,7 +54,7 @@ moduleBaseFunction(moduleTextMeasure) { moduleBaseToString(args[0], text, sizeof(text)); int32_t w, h; - textMeasure(text, &FONT_TILESET_DEFAULT, &w, &h); + textMeasure(text, &FONT_DEFAULT, &w, &h); jerry_value_t obj = jerry_object(); jerry_value_t wKey = jerry_string_sz("width"); diff --git a/src/dusk/ui/uifps.c b/src/dusk/ui/uifps.c index afb50edc..e80f72ef 100644 --- a/src/dusk/ui/uifps.c +++ b/src/dusk/ui/uifps.c @@ -51,7 +51,7 @@ errorret_t uiFPSDraw() { errorChain(textDraw( 0, 0, fpsText, textColor, - &FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT + &FONT_DEFAULT )); return spriteBatchFlush(); diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index c0231ea5..fd7da50d 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -21,8 +21,10 @@ errorret_t uiTextboxInit(void) { UI_TEXTBOX.textColor = COLOR_WHITE; UI_TEXTBOX.advanceAction = INPUT_ACTION_ACCEPT; - float_t fontW = (float_t)FONT_TILESET_DEFAULT.tileWidth; - float_t fontH = (float_t)FONT_TILESET_DEFAULT.tileHeight; + UI_TEXTBOX.font = &FONT_DEFAULT; + + float_t fontW = (float_t)FONT_DEFAULT.tileset.tileWidth; + float_t fontH = (float_t)FONT_DEFAULT.tileset.tileHeight; float_t tbHeight = ( (float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH + (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING + @@ -36,13 +38,11 @@ errorret_t uiTextboxInit(void) { UI_TEXTBOX.frame.tileset.columns = 3; UI_TEXTBOX.frame.tileset.rows = 3; UI_TEXTBOX.frame.tileset.tileCount = 9; - UI_TEXTBOX.frame.tileset.tileWidth = FONT_TILESET_DEFAULT.tileWidth; - UI_TEXTBOX.frame.tileset.tileHeight = FONT_TILESET_DEFAULT.tileHeight; + UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset.tileWidth; + UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset.tileHeight; UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f; UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f; UI_TEXTBOX.frame.texture = &TEXTURE_WHITE; - UI_TEXTBOX.textTileset = FONT_TILESET_DEFAULT; - UI_TEXTBOX.textTexture = &FONT_TEXTURE_DEFAULT; errorOk(); } @@ -53,8 +53,8 @@ void uiTextboxBuildLayout(void) { float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; - float_t fontW = (float_t)UI_TEXTBOX.textTileset.tileWidth; - float_t fontH = (float_t)UI_TEXTBOX.textTileset.tileHeight; + float_t fontW = (float_t)UI_TEXTBOX.font->tileset.tileWidth; + float_t fontH = (float_t)UI_TEXTBOX.font->tileset.tileHeight; if(fontW <= 0.0f || fontH <= 0.0f) return; @@ -204,7 +204,7 @@ errorret_t uiTextboxDraw(void) { if(UI_TEXTBOX.lineCount == 0 || UI_TEXTBOX.text[0] == '\0') errorOk(); errorChain(shaderSetTexture( - &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, UI_TEXTBOX.textTexture + &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &UI_TEXTBOX.font->texture )); #if MESH_ENABLE_COLOR #else @@ -215,8 +215,8 @@ errorret_t uiTextboxDraw(void) { float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; - float_t fontW = (float_t)UI_TEXTBOX.textTileset.tileWidth; - float_t fontH = (float_t)UI_TEXTBOX.textTileset.tileHeight; + float_t fontW = (float_t)UI_TEXTBOX.font->tileset.tileWidth; + float_t fontH = (float_t)UI_TEXTBOX.font->tileset.tileHeight; float_t contentX = UI_TEXTBOX.x + frameTileW; float_t contentY = UI_TEXTBOX.y + frameTileH; @@ -242,8 +242,7 @@ errorret_t uiTextboxDraw(void) { #if MESH_ENABLE_COLOR UI_TEXTBOX.textColor, #endif - &UI_TEXTBOX.textTileset, - UI_TEXTBOX.textTexture + UI_TEXTBOX.font )); } @@ -256,5 +255,5 @@ errorret_t uiTextboxDraw(void) { void uiTextboxDispose(void) { uiFrameDispose(&UI_TEXTBOX.frame); - UI_TEXTBOX.textTexture = NULL; + UI_TEXTBOX.font = NULL; } diff --git a/src/dusk/ui/uitextbox.h b/src/dusk/ui/uitextbox.h index 5aa58389..9317a685 100644 --- a/src/dusk/ui/uitextbox.h +++ b/src/dusk/ui/uitextbox.h @@ -24,8 +24,7 @@ typedef struct { typedef struct { uiframe_t frame; - tileset_t textTileset; - texture_t *textTexture; + font_t *font; color_t textColor; float_t x, y, width, height; diff --git a/src/dusk/util/memory.c b/src/dusk/util/memory.c index bc112f57..878e81b1 100644 --- a/src/dusk/util/memory.c +++ b/src/dusk/util/memory.c @@ -107,6 +107,29 @@ void memoryReallocate(void **ptr, const size_t size) { *ptr = newPointer; } +void memoryCopyInterleaved( + void *dest, + const size_t destStride, + const void *src, + const size_t srcStride, + const size_t elementSize, + const size_t count +) { + assertNotNull(dest, "Cannot copy to NULL memory."); + assertNotNull(src, "Cannot copy from NULL memory."); + assertTrue(elementSize > 0, "Element size must be > 0."); + assertTrue(destStride >= elementSize, "Dest stride must be >= element size."); + assertTrue(srcStride >= elementSize, "Src stride must be >= element size."); + + uint8_t *d = (uint8_t *)dest; + const uint8_t *s = (const uint8_t *)src; + for(size_t i = 0; i < count; i++) { + memcpy(d, s, elementSize); + d += destStride; + s += srcStride; + } +} + void memoryResize(void **ptr, const size_t oldSize, const size_t newSize) { assertNotNull(ptr, "Cannot resize NULL pointer."); assertTrue(newSize > 0, "Cannot resize to 0 bytes of memory."); diff --git a/src/dusk/util/memory.h b/src/dusk/util/memory.h index 12b1dd31..f5f182c6 100644 --- a/src/dusk/util/memory.h +++ b/src/dusk/util/memory.h @@ -117,9 +117,30 @@ void memoryReallocate(void **ptr, const size_t size); /** * Reallocates memory, but copies existing data to the new memory. - * + * * @param ptr The pointer to the memory to resize. * @param oldSize The old size of the memory. * @param newSize The new size of the memory. */ -void memoryResize(void **ptr, const size_t oldSize, const size_t newSize); \ No newline at end of file +void memoryResize(void **ptr, const size_t oldSize, const size_t newSize); + +/** + * Scatter-copies elements from a contiguous source array into a strided + * destination. For each of count elements, copies elementSize bytes from + * src[i * srcStride] into dest[i * destStride]. + * + * @param dest Base destination pointer. + * @param destStride Byte stride between destination slots. + * @param src Base source pointer. + * @param srcStride Byte stride between source elements. + * @param elementSize Bytes to copy per element. + * @param count Number of elements to copy. + */ +void memoryCopyInterleaved( + void *dest, + const size_t destStride, + const void *src, + const size_t srcStride, + const size_t elementSize, + const size_t count +); \ No newline at end of file diff --git a/test/util/test_memory.c b/test/util/test_memory.c index 8cfae254..cb1e2897 100644 --- a/test/util/test_memory.c +++ b/test/util/test_memory.c @@ -423,6 +423,87 @@ static void test_memoryResize(void **state) { assert_int_equal(memoryGetAllocatedCount(), 0); } +static void test_memoryCopyInterleaved(void **state) { + (void)state; + + // Basic: copy 4 uint32_t values from contiguous source into every-other dest + // slot (stride = 2*sizeof(uint32_t)). + uint32_t src[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 }; + uint32_t dest[8]; + memoryZero(dest, sizeof(dest)); + + memoryCopyInterleaved( + dest, sizeof(uint32_t) * 2, + src, sizeof(uint32_t), + sizeof(uint32_t), 4 + ); + + assert_int_equal(dest[0], 0x11111111); + assert_int_equal(dest[2], 0x22222222); + assert_int_equal(dest[4], 0x33333333); + assert_int_equal(dest[6], 0x44444444); + // Skipped slots should be untouched. + assert_int_equal(dest[1], 0); + assert_int_equal(dest[3], 0); + assert_int_equal(dest[5], 0); + assert_int_equal(dest[7], 0); + + // Strided source: pick every second element into a contiguous destination. + uint32_t src2[8] = { 0xA, 0xFF, 0xB, 0xFF, 0xC, 0xFF, 0xD, 0xFF }; + uint32_t dest2[4]; + memoryZero(dest2, sizeof(dest2)); + + memoryCopyInterleaved( + dest2, sizeof(uint32_t), + src2, sizeof(uint32_t) * 2, + sizeof(uint32_t), 4 + ); + + assert_int_equal(dest2[0], 0xA); + assert_int_equal(dest2[1], 0xB); + assert_int_equal(dest2[2], 0xC); + assert_int_equal(dest2[3], 0xD); + + // Multi-byte element: copy structs field-by-field pattern. + typedef struct { uint32_t x; uint32_t y; } pair_t; + uint32_t yVals[3] = { 10, 20, 30 }; + pair_t pairs[3]; + memoryZero(pairs, sizeof(pairs)); + + memoryCopyInterleaved( + &pairs[0].y, sizeof(pair_t), + yVals, sizeof(uint32_t), + sizeof(uint32_t), 3 + ); + + assert_int_equal(pairs[0].y, 10); + assert_int_equal(pairs[1].y, 20); + assert_int_equal(pairs[2].y, 30); + assert_int_equal(pairs[0].x, 0); + assert_int_equal(pairs[1].x, 0); + assert_int_equal(pairs[2].x, 0); + + // Count of 0 is a no-op. + uint32_t untouched[2] = { 0xDEAD, 0xBEEF }; + memoryCopyInterleaved(untouched, 4, src, 4, 4, 0); + assert_int_equal(untouched[0], 0xDEAD); + + // NULL dest + expect_assert_failure(memoryCopyInterleaved(NULL, 8, src, 4, 4, 2)); + + // NULL src + expect_assert_failure(memoryCopyInterleaved(dest, 8, NULL, 4, 4, 2)); + + // elementSize 0 + expect_assert_failure(memoryCopyInterleaved(dest, 8, src, 4, 0, 2)); + + // destStride smaller than elementSize + expect_assert_failure(memoryCopyInterleaved(dest, 2, src, 4, 4, 2)); + + // srcStride smaller than elementSize + expect_assert_failure(memoryCopyInterleaved(dest, 8, src, 2, 4, 2)); +} + int main(int argc, char **argv) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_memoryAllocate), @@ -435,6 +516,7 @@ int main(int argc, char **argv) { cmocka_unit_test(test_memoryCompare), cmocka_unit_test(test_memoryReallocate), cmocka_unit_test(test_memoryResize), + cmocka_unit_test(test_memoryCopyInterleaved), }; return cmocka_run_group_tests(tests, NULL, NULL); } \ No newline at end of file