/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "spritebatch.h" #include "assert/assert.h" #include "util/memory.h" #include "util/math.h" #include "display/shader/shadermaterial.h" meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT]; spritebatch_t SPRITEBATCH; errorret_t spriteBatchInit() { memoryZero(&SPRITEBATCH, sizeof(spritebatch_t)); errorChain(meshInit( &SPRITEBATCH.mesh, QUAD_PRIMITIVE_TYPE, SPRITEBATCH_VERTEX_COUNT, SPRITEBATCH_VERTICES )); errorOk(); } errorret_t spriteBatchBuffer( const spritebatchsprite_t *sprites, const uint32_t count, shader_t *shader, const shadermaterial_t material ) { assertNotNull(sprites, "Sprites cannot be null"); assertTrue(count > 0, "Count must be greater than zero"); assertNotNull(shader, "Shader cannot be null"); // Did the shader or material data change? if(shader != SPRITEBATCH.shader) { errorChain(spriteBatchFlush()); SPRITEBATCH.shader = shader; SPRITEBATCH.material = material; } else if(memoryCompare( &material, &SPRITEBATCH.material, sizeof(shadermaterial_t) ) != 0) { // Did the material data change? errorChain(spriteBatchFlush()); SPRITEBATCH.shader = shader; SPRITEBATCH.material = material; } // Buffer the vertices. 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]; } } void spriteBatchClear() { SPRITEBATCH.spriteCount = 0; SPRITEBATCH.spriteFlush = 0; SPRITEBATCH.shader = NULL; memoryZero(&SPRITEBATCH.material, sizeof(shadermaterial_t)); } errorret_t spriteBatchFlush() { if(SPRITEBATCH.spriteCount == 0) { errorOk(); } size_t vertexCount = QUAD_VERTEX_COUNT * SPRITEBATCH.spriteCount; size_t vertexOffset = ( SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH * QUAD_VERTEX_COUNT ); errorChain(shaderBind(SPRITEBATCH.shader)); errorChain(shaderSetMaterial(SPRITEBATCH.shader, &SPRITEBATCH.material)); errorChain(meshFlush(&SPRITEBATCH.mesh, vertexOffset, vertexCount)); errorChain(meshDraw(&SPRITEBATCH.mesh, vertexOffset, vertexCount)); SPRITEBATCH.spriteFlush++; if(SPRITEBATCH.spriteFlush >= SPRITEBATCH_FLUSH_COUNT) { SPRITEBATCH.spriteFlush = 0; } SPRITEBATCH.spriteCount = 0; errorOk(); } errorret_t spriteBatchDispose() { errorChain(meshDispose(&SPRITEBATCH.mesh)); errorOk(); }