Add strided memory pushing and improved spritebatching

This commit is contained in:
2026-05-08 20:53:05 -05:00
parent 73e73d8772
commit 1ff990ff44
14 changed files with 355 additions and 126 deletions
+17 -16
View File
@@ -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;
+2 -3
View File
@@ -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();
+105 -22
View File
@@ -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() {
@@ -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
+15
View File
@@ -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;
+24 -43
View File
@@ -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;
}
}
+15 -21
View File
@@ -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
);
);
+2 -2
View File
@@ -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");
+1 -1
View File
@@ -51,7 +51,7 @@ errorret_t uiFPSDraw() {
errorChain(textDraw(
0, 0,
fpsText, textColor,
&FONT_TILESET_DEFAULT, &FONT_TEXTURE_DEFAULT
&FONT_DEFAULT
));
return spriteBatchFlush();
+13 -14
View File
@@ -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;
}
+1 -2
View File
@@ -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;
+23
View File
@@ -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.");
+23 -2
View File
@@ -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);
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
);
+82
View File
@@ -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);
}