Optimize chunk tile mesh

This commit is contained in:
2026-06-11 14:45:53 -05:00
parent aa246eff94
commit e1498f538d
10 changed files with 128 additions and 69 deletions
-1
View File
@@ -16,7 +16,6 @@ typedef enum {
typedef struct { typedef struct {
rpgcameramode_t mode; rpgcameramode_t mode;
union { union {
worldpos_t free; worldpos_t free;
struct { struct {
+46 -21
View File
@@ -50,70 +50,95 @@ errorret_t spriteBatchBuffer(
} }
// Buffer the vertices. // Buffer the vertices.
for(uint32_t i = 0; i < count; i++ ){ uint32_t remaining = count;
spritebatchsprite_t sprite = sprites[i]; 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[ meshvertex_t *v = &SPRITEBATCH_VERTICES[
(SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush * (SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush *
SPRITEBATCH_SPRITES_MAX_PER_FLUSH)) * QUAD_VERTEX_COUNT 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 // Buffer the quad
v[0].pos[0] = sprite.min[0]; v[0].pos[0] = sprite.min[0];
v[0].pos[1] = sprite.min[1]; v[0].pos[1] = sprite.min[1];
v[0].pos[2] = sprite.min[2]; v[0].pos[2] = sprite.min[2];
v[0].uv[0] = sprite.uvMin[0]; v[0].uv[0] = sprite.uvMin[0];
v[0].uv[1] = sprite.uvMin[1]; v[0].uv[1] = sprite.uvMin[1];
v[1].pos[0] = sprite.max[0]; v[1].pos[0] = sprite.max[0];
v[1].pos[1] = sprite.min[1]; v[1].pos[1] = sprite.min[1];
v[1].pos[2] = sprite.min[2]; v[1].pos[2] = sprite.min[2];
v[1].uv[0] = sprite.uvMax[0]; v[1].uv[0] = sprite.uvMax[0];
v[1].uv[1] = sprite.uvMin[1]; v[1].uv[1] = sprite.uvMin[1];
v[2].pos[0] = sprite.max[0]; v[2].pos[0] = sprite.max[0];
v[2].pos[1] = sprite.max[1]; v[2].pos[1] = sprite.max[1];
v[2].pos[2] = sprite.max[2]; v[2].pos[2] = sprite.max[2];
v[2].uv[0] = sprite.uvMax[0]; v[2].uv[0] = sprite.uvMax[0];
v[2].uv[1] = sprite.uvMax[1]; v[2].uv[1] = sprite.uvMax[1];
v[3].pos[0] = sprite.min[0]; v[3].pos[0] = sprite.min[0];
v[3].pos[1] = sprite.min[1]; v[3].pos[1] = sprite.min[1];
v[3].pos[2] = sprite.min[2]; v[3].pos[2] = sprite.min[2];
v[3].uv[0] = sprite.uvMin[0]; v[3].uv[0] = sprite.uvMin[0];
v[3].uv[1] = sprite.uvMin[1]; v[3].uv[1] = sprite.uvMin[1];
v[4].pos[0] = sprite.max[0]; v[4].pos[0] = sprite.max[0];
v[4].pos[1] = sprite.max[1]; v[4].pos[1] = sprite.max[1];
v[4].pos[2] = sprite.max[2]; v[4].pos[2] = sprite.max[2];
v[4].uv[0] = sprite.uvMax[0]; v[4].uv[0] = sprite.uvMax[0];
v[4].uv[1] = sprite.uvMax[1]; v[4].uv[1] = sprite.uvMax[1];
v[5].pos[0] = sprite.min[0]; v[5].pos[0] = sprite.min[0];
v[5].pos[1] = sprite.max[1]; v[5].pos[1] = sprite.max[1];
v[5].pos[2] = sprite.max[2]; v[5].pos[2] = sprite.max[2];
v[5].uv[0] = sprite.uvMin[0]; v[5].uv[0] = sprite.uvMin[0];
v[5].uv[1] = sprite.uvMax[1]; 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() { void spriteBatchClear() {
@@ -63,6 +63,26 @@ errorret_t spriteBatchBuffer(
const shadermaterial_t material 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. * Resets sprite and flush counters and clears the current material state.
* Calling spriteBatchFlush after this renders nothing. * Calling spriteBatchFlush after this renders nothing.
+1 -1
View File
@@ -84,7 +84,7 @@ errorret_t engineDispose(void) {
cutsceneDispose(); cutsceneDispose();
errorChain(sceneDispose()); errorChain(sceneDispose());
errorChain(networkDispose()); errorChain(networkDispose());
rpgDispose(); errorChain(rpgDispose());
localeManagerDispose(); localeManagerDispose();
uiDispose(); uiDispose();
consoleDispose(); consoleDispose();
+4 -3
View File
@@ -12,16 +12,17 @@
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
// #define CHUNK_MESH_COUNT_MAX 3 // #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 #define CHUNK_ENTITY_COUNT_MAX 10
typedef struct chunk_s { typedef struct chunk_s {
chunkpos_t position; chunkpos_t position;
tile_t tiles[CHUNK_TILE_COUNT]; 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; color_t testColor;
uint32_t spriteCount;
// uint8_t meshCount; // uint8_t meshCount;
// meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX]; // meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
+35 -25
View File
@@ -17,6 +17,17 @@ map_t MAP;
errorret_t mapInit() { errorret_t mapInit() {
memoryZero(&MAP, sizeof(map_t)); 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" // Perform "initial load"
MAP.loaded = true; MAP.loaded = true;
int32_t i = 0; 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++) { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]); mapChunkUnload(&MAP.chunks[i]);
errorChain(meshDispose(&MAP.chunks[i].mesh));
} }
errorOk();
} }
void mapChunkUnload(chunk_t* chunk) { void mapChunkUnload(chunk_t* chunk) {
@@ -194,11 +207,7 @@ void mapChunkUnload(chunk_t* chunk) {
entity_t *entity = &ENTITIES[chunk->entities[i]]; entity_t *entity = &ENTITIES[chunk->entities[i]];
entity->type = ENTITY_TYPE_NULL; entity->type = ENTITY_TYPE_NULL;
} }
chunk->vertCount = 0;
// for(uint8_t i = 0; i < chunk->meshCount; i++) {
// if(chunk->meshes[i].vertexCount == 0) continue;
// meshDispose(&chunk->meshes[i]);
// }
} }
errorret_t mapChunkLoad(chunk_t* chunk) { errorret_t mapChunkLoad(chunk_t* chunk) {
@@ -224,52 +233,53 @@ errorret_t mapChunkLoad(chunk_t* chunk) {
chunk->testColor = color; chunk->testColor = color;
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
memoryZero(chunk->sprites, sizeof(chunk->sprites));
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
chunk->spriteCount = 0; chunk->vertCount = 0;
if(chunk->position.z != 0) { if(chunk->position.z != 0) {
errorOk(); errorOk();
} }
// Set Chunk sprites. // Set Chunk sprites.
uint32_t i = 0;
vec3 spriteMin = { vec3 spriteMin = {
chunk->position.x * CHUNK_WIDTH, chunk->position.x * CHUNK_WIDTH,
chunk->position.y * CHUNK_HEIGHT, chunk->position.y * CHUNK_HEIGHT,
chunk->position.z * CHUNK_DEPTH 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 x = 0; x < CHUNK_WIDTH; x++) {
for(uint8_t y = 0; y < CHUNK_HEIGHT; y++) { 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( glm_vec3_add(
chunk->sprites[i].min, sprites[i].min,
(vec3){ x, y, 0 }, (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( glm_vec3_add(
chunk->sprites[i].max, sprites[i].max,
(vec3){ 1, 1, 0 }, (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){ 0, 0 }, sprites[i].uvMin);
glm_vec2_copy((vec2){ 1, 1 }, chunk->sprites[i].uvMax); glm_vec2_copy((vec2){ 1, 1 }, sprites[i].uvMax);
chunk->spriteCount++;
i++; 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(); errorOk();
} }
+3 -1
View File
@@ -56,8 +56,10 @@ void mapUpdate();
/** /**
* Disposes of the map. * Disposes of the map.
*
* @return An error code.
*/ */
void mapDispose(); errorret_t mapDispose();
/** /**
* Sets the map position and updates chunks accordingly. * Sets the map position and updates chunks accordingly.
+5 -3
View File
@@ -42,7 +42,7 @@ errorret_t rpgInit(void) {
} }
errorret_t rpgUpdate(void) { errorret_t rpgUpdate(void) {
#if TIME_FIXED == 0 #ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) { if(TIME.dynamicUpdate) {
errorOk(); errorOk();
} }
@@ -63,6 +63,8 @@ errorret_t rpgUpdate(void) {
errorOk(); errorOk();
} }
void rpgDispose(void) { errorret_t rpgDispose(void) {
mapDispose(); errorChain(mapDispose());
errorOk();
} }
+3 -1
View File
@@ -28,5 +28,7 @@ errorret_t rpgUpdate(void);
/** /**
* Dispose of the RPG subsystem. * Dispose of the RPG subsystem.
*
* @return An error code.
*/ */
void rpgDispose(void); errorret_t rpgDispose(void);
+11 -13
View File
@@ -37,14 +37,15 @@ errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
errorret_t sceneOverworldRender(scenedata_t *sceneData) { errorret_t sceneOverworldRender(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null"); assertNotNull(sceneData, "Scene data cannot be null");
mat4 proj, model; mat4 proj, model, eye;
glm_mat4_identity(model);
errorChain(shaderBind(&SHADER_UNLIT)); errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &TEXTURE_TEST)); errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &TEXTURE_TEST));
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE)); errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
// Model
glm_mat4_identity(model);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model)); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// Camera projection // Camera projection
@@ -80,15 +81,16 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) {
}, },
worldPosVec, worldPosVec,
(vec3){ 0, -1, 0 }, // up (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 // Chunk Data
{ {
shadermaterial_t chunkMaterial = { shadermaterial_t chunkMaterial = {
.unlit = { .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 y = 0; y < MAP_CHUNK_HEIGHT; y++) {
for(uint8_t z = 0; z < MAP_CHUNK_DEPTH; z++) { for(uint8_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
chunk_t *chunk = &MAP.chunks[i]; chunk_t *chunk = &MAP.chunks[i];
if(chunk->spriteCount == 0) { if(chunk->vertCount == 0) {
i++; i++;
continue; continue;
} }
chunkMaterial.unlit.color = chunk->testColor; chunkMaterial.unlit.color = chunk->testColor;
errorChain(shaderSetMaterial(&SHADER_UNLIT, &chunkMaterial));
spriteBatchBuffer( errorChain(meshDraw(&chunk->mesh, 0, chunk->vertCount));
chunk->sprites, chunk->spriteCount, &SHADER_UNLIT, chunkMaterial
);
i++; i++;
} }
} }
} }
spriteBatchFlush();
} }
// Entities // Entities