From e1498f538d51ee1c186211a004793a6fb8317401 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 11 Jun 2026 14:45:53 -0500 Subject: [PATCH] Optimize chunk tile mesh --- archive/rpg/rpgcamera.h | 1 - src/dusk/display/spritebatch/spritebatch.c | 67 +++++++++++++++------- src/dusk/display/spritebatch/spritebatch.h | 20 +++++++ src/dusk/engine/engine.c | 2 +- src/dusk/rpg/overworld/chunk.h | 7 ++- src/dusk/rpg/overworld/map.c | 60 +++++++++++-------- src/dusk/rpg/overworld/map.h | 4 +- src/dusk/rpg/rpg.c | 8 ++- src/dusk/rpg/rpg.h | 4 +- src/dusk/scene/overworld/sceneoverworld.c | 24 ++++---- 10 files changed, 128 insertions(+), 69 deletions(-) diff --git a/archive/rpg/rpgcamera.h b/archive/rpg/rpgcamera.h index 920a9a25..d3e49663 100644 --- a/archive/rpg/rpgcamera.h +++ b/archive/rpg/rpgcamera.h @@ -16,7 +16,6 @@ typedef enum { typedef struct { rpgcameramode_t mode; - union { worldpos_t free; struct { diff --git a/src/dusk/display/spritebatch/spritebatch.c b/src/dusk/display/spritebatch/spritebatch.c index 29084587..6fdb4774 100644 --- a/src/dusk/display/spritebatch/spritebatch.c +++ b/src/dusk/display/spritebatch/spritebatch.c @@ -50,70 +50,95 @@ errorret_t spriteBatchBuffer( } // Buffer the vertices. - for(uint32_t i = 0; i < count; i++ ){ - spritebatchsprite_t sprite = sprites[i]; + uint32_t remaining = count; + do { + uint32_t spritesBeforeFlush = ( + SPRITEBATCH_SPRITES_MAX_PER_FLUSH - SPRITEBATCH.spriteCount + ); + if(spritesBeforeFlush == 0) { + // Flush if we have no capacity before flushing. + errorChain(spriteBatchFlush()); + spritesBeforeFlush = SPRITEBATCH_SPRITES_MAX_PER_FLUSH; + } + + // Many we buffering? + const uint32_t batchCount = mathMin( + remaining, + spritesBeforeFlush + ); + + // Destination meshvertex_t *v = &SPRITEBATCH_VERTICES[ (SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH)) * QUAD_VERTEX_COUNT ]; + + // Buffer to the mesh vertices. + spriteBatchBufferToMesh( + sprites, batchCount, v, batchCount * QUAD_VERTEX_COUNT + ); + SPRITEBATCH.spriteCount += batchCount; + remaining -= batchCount; + } while(remaining > 0); + + errorOk(); +} + +void spriteBatchBufferToMesh( + const spritebatchsprite_t *sprites, + const uint32_t count, + meshvertex_t *vertices, + const uint32_t verticesSize +) { + assertNotNull(sprites, "Sprites cannot be null"); + assertTrue(count > 0, "Count must be greater than zero"); + assertNotNull(vertices, "Vertices cannot be null"); + assertTrue( + verticesSize >= count * QUAD_VERTEX_COUNT, "Vertices array too small" + ); + + for(uint32_t i = 0; i < count; i++ ){ + spritebatchsprite_t sprite = sprites[i]; + meshvertex_t *v = &vertices[i * QUAD_VERTEX_COUNT]; // Buffer the quad v[0].pos[0] = sprite.min[0]; v[0].pos[1] = sprite.min[1]; v[0].pos[2] = sprite.min[2]; - v[0].uv[0] = sprite.uvMin[0]; v[0].uv[1] = sprite.uvMin[1]; - v[1].pos[0] = sprite.max[0]; v[1].pos[1] = sprite.min[1]; v[1].pos[2] = sprite.min[2]; - v[1].uv[0] = sprite.uvMax[0]; v[1].uv[1] = sprite.uvMin[1]; - v[2].pos[0] = sprite.max[0]; v[2].pos[1] = sprite.max[1]; v[2].pos[2] = sprite.max[2]; - v[2].uv[0] = sprite.uvMax[0]; v[2].uv[1] = sprite.uvMax[1]; - v[3].pos[0] = sprite.min[0]; v[3].pos[1] = sprite.min[1]; v[3].pos[2] = sprite.min[2]; - v[3].uv[0] = sprite.uvMin[0]; v[3].uv[1] = sprite.uvMin[1]; - v[4].pos[0] = sprite.max[0]; v[4].pos[1] = sprite.max[1]; v[4].pos[2] = sprite.max[2]; - v[4].uv[0] = sprite.uvMax[0]; v[4].uv[1] = sprite.uvMax[1]; - v[5].pos[0] = sprite.min[0]; v[5].pos[1] = sprite.max[1]; v[5].pos[2] = sprite.max[2]; - v[5].uv[0] = sprite.uvMin[0]; v[5].uv[1] = sprite.uvMax[1]; - - // Do we need to flush? - SPRITEBATCH.spriteCount++; - if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) { - errorChain(spriteBatchFlush()); - } } - - errorOk(); } void spriteBatchClear() { diff --git a/src/dusk/display/spritebatch/spritebatch.h b/src/dusk/display/spritebatch/spritebatch.h index 4b397a22..88e7475d 100644 --- a/src/dusk/display/spritebatch/spritebatch.h +++ b/src/dusk/display/spritebatch/spritebatch.h @@ -63,6 +63,26 @@ errorret_t spriteBatchBuffer( const shadermaterial_t material ); +/** + * Buffers an array of sprites to a given array of mesh vertices. This is the + * internal method that is used to buffer to the internal spritebatch mesh, but + * you can use it to achieve sprite buffering to a mesh you own. + * + * verticesSize is the size of the vertices array, we use this to ensure no + * buffer overflows. + * + * @param sprites Pointer to the sprite array. + * @param count Number of sprites to buffer. + * @param vertices Pointer to the vertex array to write to. + * @param verticesSize Size of the vertex array, in number of vertices. + */ +void spriteBatchBufferToMesh( + const spritebatchsprite_t *sprites, + const uint32_t count, + meshvertex_t *vertices, + const uint32_t verticesSize +); + /** * Resets sprite and flush counters and clears the current material state. * Calling spriteBatchFlush after this renders nothing. diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 0075aac3..9ce6efd5 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -84,7 +84,7 @@ errorret_t engineDispose(void) { cutsceneDispose(); errorChain(sceneDispose()); errorChain(networkDispose()); - rpgDispose(); + errorChain(rpgDispose()); localeManagerDispose(); uiDispose(); consoleDispose(); diff --git a/src/dusk/rpg/overworld/chunk.h b/src/dusk/rpg/overworld/chunk.h index 39643f5f..fc5cbb30 100644 --- a/src/dusk/rpg/overworld/chunk.h +++ b/src/dusk/rpg/overworld/chunk.h @@ -12,16 +12,17 @@ #include "display/spritebatch/spritebatch.h" // #define CHUNK_MESH_COUNT_MAX 3 -// #define CHUNK_VERTEX_COUNT_MAX (QUAD_VERTEX_COUNT * CHUNK_MESH_COUNT_MAX) +#define CHUNK_VERTEX_COUNT (QUAD_VERTEX_COUNT * CHUNK_TILE_COUNT) #define CHUNK_ENTITY_COUNT_MAX 10 typedef struct chunk_s { chunkpos_t position; tile_t tiles[CHUNK_TILE_COUNT]; - spritebatchsprite_t sprites[CHUNK_TILE_COUNT]; + meshvertex_t vertices[CHUNK_VERTEX_COUNT]; + uint32_t vertCount; + mesh_t mesh; color_t testColor; - uint32_t spriteCount; // uint8_t meshCount; // meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX]; diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index fb01630e..78d78ca0 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -17,6 +17,17 @@ map_t MAP; errorret_t mapInit() { memoryZero(&MAP, sizeof(map_t)); + // Setup chunk meshes + for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { + chunk_t *chunk = &MAP.chunks[i]; + errorChain(meshInit( + &chunk->mesh, + MESH_PRIMITIVE_TYPE_TRIANGLES, + CHUNK_VERTEX_COUNT, + chunk->vertices + )); + } + // Perform "initial load" MAP.loaded = true; int32_t i = 0; @@ -182,10 +193,12 @@ void mapUpdate() { } -void mapDispose() { +errorret_t mapDispose() { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { mapChunkUnload(&MAP.chunks[i]); + errorChain(meshDispose(&MAP.chunks[i].mesh)); } + errorOk(); } void mapChunkUnload(chunk_t* chunk) { @@ -194,11 +207,7 @@ void mapChunkUnload(chunk_t* chunk) { entity_t *entity = &ENTITIES[chunk->entities[i]]; entity->type = ENTITY_TYPE_NULL; } - - // for(uint8_t i = 0; i < chunk->meshCount; i++) { - // if(chunk->meshes[i].vertexCount == 0) continue; - // meshDispose(&chunk->meshes[i]); - // } + chunk->vertCount = 0; } errorret_t mapChunkLoad(chunk_t* chunk) { @@ -224,52 +233,53 @@ errorret_t mapChunkLoad(chunk_t* chunk) { chunk->testColor = color; memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); - memoryZero(chunk->sprites, sizeof(chunk->sprites)); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); - chunk->spriteCount = 0; + chunk->vertCount = 0; if(chunk->position.z != 0) { errorOk(); } // Set Chunk sprites. - uint32_t i = 0; vec3 spriteMin = { chunk->position.x * CHUNK_WIDTH, chunk->position.y * CHUNK_HEIGHT, chunk->position.z * CHUNK_DEPTH }; + + spritebatchsprite_t sprites[CHUNK_TILE_COUNT]; + uint32_t i = 0; for(uint8_t x = 0; x < CHUNK_WIDTH; x++) { for(uint8_t y = 0; y < CHUNK_HEIGHT; y++) { - glm_vec3_copy(spriteMin, chunk->sprites[i].min); + glm_vec3_copy(spriteMin, sprites[i].min); glm_vec3_add( - chunk->sprites[i].min, + sprites[i].min, (vec3){ x, y, 0 }, - chunk->sprites[i].min + sprites[i].min ); - glm_vec3_copy(chunk->sprites[i].min, chunk->sprites[i].max); + glm_vec3_copy(sprites[i].min, sprites[i].max); glm_vec3_add( - chunk->sprites[i].max, + sprites[i].max, (vec3){ 1, 1, 0 }, - chunk->sprites[i].max + sprites[i].max ); - glm_vec2_copy((vec2){ 0, 0 }, chunk->sprites[i].uvMin); - glm_vec2_copy((vec2){ 1, 1 }, chunk->sprites[i].uvMax); + glm_vec2_copy((vec2){ 0, 0 }, sprites[i].uvMin); + glm_vec2_copy((vec2){ 1, 1 }, sprites[i].uvMax); - chunk->spriteCount++; i++; } } + chunk->vertCount = i * QUAD_VERTEX_COUNT; + spriteBatchBufferToMesh( + sprites, + i, + chunk->vertices, + chunk->vertCount + ); + errorChain(meshFlush(&chunk->mesh, 0, chunk->vertCount)); - // char_t buffer[64]; - // TODO: Can probably move this to asset load logic? - // chunk->meshCount = 0; - // memoryZero(chunk->meshes, sizeof(chunk->meshes)); - // memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); - - // Load. errorOk(); } diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index f97628e9..78eb5682 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -56,8 +56,10 @@ void mapUpdate(); /** * Disposes of the map. + * + * @return An error code. */ -void mapDispose(); +errorret_t mapDispose(); /** * Sets the map position and updates chunks accordingly. diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index a7604c86..776f07fd 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -42,7 +42,7 @@ errorret_t rpgInit(void) { } errorret_t rpgUpdate(void) { - #if TIME_FIXED == 0 + #ifdef DUSK_TIME_DYNAMIC if(TIME.dynamicUpdate) { errorOk(); } @@ -63,6 +63,8 @@ errorret_t rpgUpdate(void) { errorOk(); } -void rpgDispose(void) { - mapDispose(); +errorret_t rpgDispose(void) { + errorChain(mapDispose()); + + errorOk(); } \ No newline at end of file diff --git a/src/dusk/rpg/rpg.h b/src/dusk/rpg/rpg.h index 7460dfb5..fcc1449a 100644 --- a/src/dusk/rpg/rpg.h +++ b/src/dusk/rpg/rpg.h @@ -28,5 +28,7 @@ errorret_t rpgUpdate(void); /** * Dispose of the RPG subsystem. + * + * @return An error code. */ -void rpgDispose(void); \ No newline at end of file +errorret_t rpgDispose(void); \ No newline at end of file diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index 4684dd26..f5a7f778 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -37,14 +37,15 @@ errorret_t sceneOverworldUpdate(scenedata_t *sceneData) { errorret_t sceneOverworldRender(scenedata_t *sceneData) { assertNotNull(sceneData, "Scene data cannot be null"); - mat4 proj, model; - - glm_mat4_identity(model); + mat4 proj, model, eye; errorChain(shaderBind(&SHADER_UNLIT)); errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &TEXTURE_TEST)); errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE)); + + // Model + glm_mat4_identity(model); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model)); // Camera projection @@ -80,15 +81,16 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { }, worldPosVec, (vec3){ 0, -1, 0 }, // up - RPG_CAMERA.eye + eye ); - errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, RPG_CAMERA.eye)); + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye)); // Chunk Data { shadermaterial_t chunkMaterial = { .unlit = { - .color = COLOR_WHITE + .color = COLOR_WHITE, + .texture = NULL } }; @@ -97,22 +99,18 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { for(uint8_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { for(uint8_t z = 0; z < MAP_CHUNK_DEPTH; z++) { chunk_t *chunk = &MAP.chunks[i]; - if(chunk->spriteCount == 0) { + if(chunk->vertCount == 0) { i++; continue; } chunkMaterial.unlit.color = chunk->testColor; - - spriteBatchBuffer( - chunk->sprites, chunk->spriteCount, &SHADER_UNLIT, chunkMaterial - ); + errorChain(shaderSetMaterial(&SHADER_UNLIT, &chunkMaterial)); + errorChain(meshDraw(&chunk->mesh, 0, chunk->vertCount)); i++; } } } - - spriteBatchFlush(); } // Entities