Spritebatch cleanup

This commit is contained in:
2026-05-26 19:07:07 -05:00
parent 382c435bac
commit 1f2657cea0
22 changed files with 842 additions and 480 deletions
+3 -26
View File
@@ -22,7 +22,7 @@
#include "util/memory.h" #include "util/memory.h"
#include "util/string.h" #include "util/string.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderlist.h"
#include "time/time.h" #include "time/time.h"
display_t DISPLAY = { 0 }; display_t DISPLAY = { 0 };
@@ -53,31 +53,8 @@ errorret_t displayInit(void) {
errorChain(screenInit()); errorChain(screenInit());
// Setup initial shader with default values // Setup initial shader with default values
mat4 view, proj, model;
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
glm_perspective( errorChain(shaderListInit());
glm_rad(45.0f),
SCREEN.aspect,
0.1f,
100.0f,
proj
);
glm_mat4_identity(model);
errorChain(shaderInit(&SHADER_UNLIT, &SHADER_UNLIT_DEFINITION));
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorOk(); errorOk();
} }
@@ -119,7 +96,7 @@ errorret_t displaySetState(displaystate_t state) {
} }
errorret_t displayDispose(void) { errorret_t displayDispose(void) {
errorChain(shaderDispose(&SHADER_UNLIT)); errorChain(shaderListDispose());
errorChain(spriteBatchDispose()); errorChain(spriteBatchDispose());
screenDispose(); screenDispose();
errorChain(textDispose()); errorChain(textDispose());
+1
View File
@@ -7,5 +7,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
shader.c shader.c
shaderlist.c
shaderunlit.c shaderunlit.c
) )
+2
View File
@@ -8,6 +8,7 @@
#include "shader.h" #include "shader.h"
#include "shadermaterial.h" #include "shadermaterial.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "log/log.h"
shader_t *bound = NULL; shader_t *bound = NULL;
@@ -57,6 +58,7 @@ errorret_t shaderSetColor(
) { ) {
assertNotNull(shader, "Shader cannot be null"); assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty"); assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertTrue(bound == shader, "Shader must be bound.");
errorChain(shaderSetColorPlatform(shader, name, color)); errorChain(shaderSetColorPlatform(shader, name, color));
errorOk(); errorOk();
} }
+87
View File
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "shaderlist.h"
#include "display/screen/screen.h"
#include "assert/assert.h"
shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT] = {
[SHADER_LIST_SHADER_UNLIT] = {
.shader = &SHADER_UNLIT,
.definition = &SHADER_UNLIT_DEFINITION
},
};
errorret_t shaderListInit() {
mat4 view, proj, model;
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
glm_perspective(
glm_rad(45.0f),
SCREEN.aspect,
0.1f,
100.0f,
proj
);
glm_mat4_identity(model);
for(shaderlistshader_t i = 0; i < SHADER_LIST_SHADER_COUNT; i++) {
if(i == SHADER_LIST_SHADER_NULL) {
continue;
}
assertNotNull(
SHADER_LIST_DEFS[i].shader, "Shader cannot be null"
);
assertNotNull(
SHADER_LIST_DEFS[i].definition, "Shader definition cannot be null"
);
errorChain(shaderInit(
SHADER_LIST_DEFS[i].shader, SHADER_LIST_DEFS[i].definition
));
errorChain(shaderBind(SHADER_LIST_DEFS[i].shader));
errorChain(shaderSetMatrix(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_PROJECTION, proj
));
errorChain(shaderSetMatrix(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_VIEW, view
));
errorChain(shaderSetMatrix(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_MODEL, model
));
errorChain(shaderSetTexture(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_TEXTURE, NULL
));
errorChain(shaderSetColor(
SHADER_LIST_DEFS[i].shader, SHADER_UNLIT_COLOR, COLOR_WHITE
));
}
errorOk();
}
errorret_t shaderListDispose(void) {
for(shaderlistshader_t i = 0; i < SHADER_LIST_SHADER_COUNT; i++) {
if(i == SHADER_LIST_SHADER_NULL) {
continue;
}
assertNotNull(
SHADER_LIST_DEFS[i].shader, "Shader cannot be null"
);
errorChain(shaderDispose(SHADER_LIST_DEFS[i].shader));
}
errorOk();
}
+40
View File
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/shader/shader.h"
#include "display/shader/shaderunlit.h"
typedef enum {
SHADER_LIST_SHADER_NULL,
SHADER_LIST_SHADER_UNLIT,
SHADER_LIST_SHADER_COUNT
} shaderlistshader_t;
typedef struct {
shader_t *shader;
shaderdefinition_t *definition;
} shaderlistdef_t;
extern shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT];
/**
* Initializes all default shaders and uploads the initial view, projection,
* and model matrices to each.
*
* @return Error state.
*/
errorret_t shaderListInit();
/**
* Disposes all default shaders.
*
* @return Error state.
*/
errorret_t shaderListDispose(void);
+1 -1
View File
@@ -6,7 +6,7 @@
*/ */
#pragma once #pragma once
#include "display/shader/shaderunlit.h" #include "display/shader/shaderlist.h"
typedef union shadermaterial_u { typedef union shadermaterial_u {
shaderunlitmaterial_t unlit; shaderunlitmaterial_t unlit;
+74 -109
View File
@@ -9,14 +9,13 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/math.h" #include "util/math.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shadermaterial.h"
meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT]; meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
spritebatch_t SPRITEBATCH; spritebatch_t SPRITEBATCH;
errorret_t spriteBatchInit() { errorret_t spriteBatchInit() {
memoryZero(&SPRITEBATCH, sizeof(spritebatch_t)); memoryZero(&SPRITEBATCH, sizeof(spritebatch_t));
errorChain(meshInit( errorChain(meshInit(
&SPRITEBATCH.mesh, &SPRITEBATCH.mesh,
QUAD_PRIMITIVE_TYPE, QUAD_PRIMITIVE_TYPE,
@@ -28,137 +27,100 @@ errorret_t spriteBatchInit() {
errorret_t spriteBatchBuffer( errorret_t spriteBatchBuffer(
const spritebatchsprite_t *sprites, const spritebatchsprite_t *sprites,
const uint32_t count const uint32_t count,
shader_t *shader,
const shadermaterial_t material
) { ) {
for(uint32_t i = 0; i < count; i++) { assertNotNull(sprites, "Sprites cannot be null");
const spritebatchsprite_t *s = &sprites[i]; assertTrue(count > 0, "Count must be greater than zero");
assertNotNull(shader, "Shader cannot be null");
// if(s->texture != SPRITEBATCH.currentTexture) { // Did the shader or material data change?
// errorChain(spriteBatchFlush()); 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;
}
if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) { // Buffer the vertices.
errorChain(spriteBatchFlush()); for(uint32_t i = 0; i < count; i++ ){
} spritebatchsprite_t sprite = sprites[i];
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
]; ];
v[0].pos[0] = s->min[0]; v[0].pos[1] = s->min[1]; v[0].pos[2] = s->min[2]; // Buffer the quad
v[0].uv[0] = s->uvMin[0]; v[0].uv[1] = s->uvMin[1]; v[0].pos[0] = sprite.min[0];
v[0].pos[1] = sprite.min[1];
v[0].pos[2] = sprite.min[2];
v[1].pos[0] = s->max[0]; v[1].pos[1] = s->min[1]; v[1].pos[2] = s->min[2]; v[0].uv[0] = sprite.uvMin[0];
v[1].uv[0] = s->uvMax[0]; v[1].uv[1] = s->uvMin[1]; v[0].uv[1] = sprite.uvMin[1];
v[2].pos[0] = s->max[0]; v[2].pos[1] = s->max[1]; v[2].pos[2] = s->min[2];
v[2].uv[0] = s->uvMax[0]; v[2].uv[1] = s->uvMax[1];
v[3].pos[0] = s->min[0]; v[3].pos[1] = s->min[1]; v[3].pos[2] = s->min[2]; v[1].pos[0] = sprite.max[0];
v[3].uv[0] = s->uvMin[0]; v[3].uv[1] = s->uvMin[1]; v[1].pos[1] = sprite.min[1];
v[1].pos[2] = sprite.min[2];
v[4].pos[0] = s->max[0]; v[4].pos[1] = s->max[1]; v[4].pos[2] = s->min[2]; v[1].uv[0] = sprite.uvMax[0];
v[4].uv[0] = s->uvMax[0]; v[4].uv[1] = s->uvMax[1]; v[1].uv[1] = sprite.uvMin[1];
v[5].pos[0] = s->min[0]; v[5].pos[1] = s->max[1]; v[5].pos[2] = s->min[2];
v[5].uv[0] = s->uvMin[0]; v[5].uv[1] = s->uvMax[1];
#if MESH_ENABLE_COLOR v[2].pos[0] = sprite.max[0];
for(uint8_t vi = 0; vi < QUAD_VERTEX_COUNT; vi++) { v[2].pos[1] = sprite.max[1];
v[vi].color = s->color; v[2].pos[2] = sprite.max[2];
}
#endif
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++; SPRITEBATCH.spriteCount++;
if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) {
errorChain(spriteBatchFlush());
}
} }
errorOk(); errorOk();
} }
errorret_t spriteBatchBufferSprite(const spritebatchsprite_t *sprite) {
return spriteBatchBuffer(sprite, 1);
}
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
) {
for(size_t i = 0; i < count; i++) {
spritebatchsprite_t sprite;
sprite.min[0] = minX[i]; sprite.min[1] = minY[i]; sprite.min[2] = z[i];
sprite.max[0] = maxX[i]; sprite.max[1] = maxY[i]; sprite.max[2] = z[i];
sprite.uvMin[0] = u0[i]; sprite.uvMin[1] = v0[i];
sprite.uvMax[0] = u1[i]; sprite.uvMax[1] = v1[i];
sprite.texture = SPRITEBATCH.currentTexture;
#if MESH_ENABLE_COLOR
sprite.color = color[i];
#endif
errorChain(spriteBatchBuffer(&sprite, 1));
}
errorOk();
}
errorret_t spriteBatchPush(
const float_t minX,
const float_t minY,
const float_t maxX,
const float_t maxY,
#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
) {
spritebatchsprite_t sprite;
sprite.min[0] = minX; sprite.min[1] = minY; sprite.min[2] = 0;
sprite.max[0] = maxX; sprite.max[1] = maxY; sprite.max[2] = 0;
sprite.uvMin[0] = u0; sprite.uvMin[1] = v0;
sprite.uvMax[0] = u1; sprite.uvMax[1] = v1;
sprite.texture = SPRITEBATCH.currentTexture;
#if MESH_ENABLE_COLOR
sprite.color = color;
#endif
return spriteBatchBuffer(&sprite, 1);
}
errorret_t spriteBatchPushZ(
const vec3 min,
const vec3 max,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
const vec2 uvMin,
const vec2 uvMax
) {
spritebatchsprite_t sprite;
sprite.min[0] = min[0]; sprite.min[1] = min[1]; sprite.min[2] = min[2];
sprite.max[0] = max[0]; sprite.max[1] = max[1]; sprite.max[2] = max[2];
sprite.uvMin[0] = uvMin[0]; sprite.uvMin[1] = uvMin[1];
sprite.uvMax[0] = uvMax[0]; sprite.uvMax[1] = uvMax[1];
sprite.texture = SPRITEBATCH.currentTexture;
#if MESH_ENABLE_COLOR
sprite.color = color;
#endif
return spriteBatchBuffer(&sprite, 1);
}
void spriteBatchClear() { void spriteBatchClear() {
SPRITEBATCH.spriteCount = 0; SPRITEBATCH.spriteCount = 0;
SPRITEBATCH.spriteFlush = 0; SPRITEBATCH.spriteFlush = 0;
SPRITEBATCH.currentTexture = NULL; SPRITEBATCH.shader = NULL;
memoryZero(&SPRITEBATCH.material, sizeof(shadermaterial_t));
} }
errorret_t spriteBatchFlush() { errorret_t spriteBatchFlush() {
@@ -171,6 +133,9 @@ errorret_t spriteBatchFlush() {
SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH * SPRITEBATCH.spriteFlush * SPRITEBATCH_SPRITES_MAX_PER_FLUSH *
QUAD_VERTEX_COUNT QUAD_VERTEX_COUNT
); );
errorChain(shaderBind(SPRITEBATCH.shader));
errorChain(shaderSetMaterial(SPRITEBATCH.shader, &SPRITEBATCH.material));
errorChain(meshFlush(&SPRITEBATCH.mesh, vertexOffset, vertexCount)); errorChain(meshFlush(&SPRITEBATCH.mesh, vertexOffset, vertexCount));
errorChain(meshDraw(&SPRITEBATCH.mesh, vertexOffset, vertexCount)); errorChain(meshDraw(&SPRITEBATCH.mesh, vertexOffset, vertexCount));
+26 -105
View File
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "display/mesh/quad.h" #include "display/mesh/quad.h"
#include "display/texture/texture.h" #include "display/texture/texture.h"
#include "display/shader/shadermaterial.h"
#define SPRITEBATCH_SPRITES_MAX 512 #define SPRITEBATCH_SPRITES_MAX 512
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT) #define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
@@ -21,146 +22,66 @@ typedef struct {
vec3 max; vec3 max;
vec2 uvMin; vec2 uvMin;
vec2 uvMax; vec2 uvMax;
texture_t *texture;
#if MESH_ENABLE_COLOR
color_t color;
#endif
} spritebatchsprite_t; } spritebatchsprite_t;
typedef struct { typedef struct {
mesh_t mesh; mesh_t mesh;
int32_t spriteCount; int32_t spriteCount;
int32_t spriteFlush; int32_t spriteFlush;
texture_t *currentTexture;
shader_t *shader;
shadermaterial_t material;
} spritebatch_t; } spritebatch_t;
// Have to define these seperately because of alignment in certain platforms. // Have to define these separately because of alignment on certain platforms.
extern meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT]; extern meshvertex_t SPRITEBATCH_VERTICES[SPRITEBATCH_VERTEX_COUNT];
extern spritebatch_t SPRITEBATCH; extern spritebatch_t SPRITEBATCH;
/** /**
* Initializes a sprite batch. * Initializes the global sprite batch and its internal mesh buffer.
* *
* @return An error code indicating success or failure. * @return Error state.
*/ */
errorret_t spriteBatchInit(); errorret_t spriteBatchInit();
/** /**
* Buffers an array of sprites. Flushes automatically when the texture changes * Lowest-level buffer function. Writes sprites into the internal vertex buffer.
* or the per-flush limit is reached. * Flushes automatically when the per-flush capacity is reached. Does not
* modify material state — call spriteBatchSetState or use a high-level push
* function before buffering.
* *
* @param sprites Pointer to the sprite array. * @param sprites Pointer to the sprite array.
* @param count Number of sprites to buffer. * @param count Number of sprites to buffer.
* @return An error code indicating success or failure. * @param shader Shader to use when flushing.
* @param material Material information passed to the shader when flushing.
* @return Error state.
*/ */
errorret_t spriteBatchBuffer( errorret_t spriteBatchBuffer(
const spritebatchsprite_t *sprites, const spritebatchsprite_t *sprites,
const uint32_t count const uint32_t count,
shader_t *shader,
const shadermaterial_t material
); );
/** /**
* Convenience wrapper — buffers a single sprite. * Resets sprite and flush counters and clears the current material state.
* * Calling spriteBatchFlush after this renders nothing.
* @param sprite The sprite to buffer.
* @return An error code indicating success or failure.
*/
errorret_t spriteBatchBufferSprite(const spritebatchsprite_t *sprite);
/**
* Pushes multiple sprites to the batch using parallel 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 2D sprite (z=0) to the batch.
*
* @param minX The minimum x coordinate of the sprite.
* @param minY The minimum y coordinate of the sprite.
* @param maxX The maximum x coordinate of the sprite.
* @param maxY The maximum y coordinate of the sprite.
* @param color The color to tint the sprite with.
* @param u0 Texture u coordinate for the top-left corner.
* @param v0 Texture v coordinate for the top-left corner.
* @param u1 Texture u coordinate for the bottom-right corner.
* @param v1 Texture v coordinate for the bottom-right corner.
* @return An error code indicating success or failure.
*/
errorret_t spriteBatchPush(
const float_t minX,
const float_t minY,
const float_t maxX,
const float_t maxY,
#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
);
/**
* Pushes a 3D sprite to the batch.
*
* @param min The minimum (x,y,z) coordinate of the sprite.
* @param max The maximum (x,y,z) coordinate of the sprite.
* @param color The color to tint the sprite with.
* @param uvMin Texture coordinate for the top-left corner.
* @param uvMax Texture coordinate for the bottom-right corner.
* @return An error code indicating success or failure.
*/
errorret_t spriteBatchPushZ(
const vec3 min,
const vec3 max,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
const vec2 uvMin,
const vec2 uvMax
);
/**
* Clears the sprite batch. Calling flush after this renders nothing.
*/ */
void spriteBatchClear(); void spriteBatchClear();
/** /**
* Flushes the sprite batch, rendering all queued sprites. * Uploads and draws all buffered sprites. If a material type has been set via
* spriteBatchSetState or spriteBatchCheckState, the shader is bound and the
* material is applied first. If matType is NULL the caller is responsible for
* having the correct shader already bound. Does nothing if the buffer is empty.
* *
* @return An error code indicating success or failure. * @return Error state.
*/ */
errorret_t spriteBatchFlush(); errorret_t spriteBatchFlush();
/** /**
* Disposes of the sprite batch, freeing any allocated resources. * Disposes of the sprite batch and frees its internal mesh buffer.
* *
* @return An error code indicating success or failure. * @return Error state.
*/ */
errorret_t spriteBatchDispose(); errorret_t spriteBatchDispose();
+28 -25
View File
@@ -34,15 +34,12 @@ errorret_t textDispose(void) {
errorOk(); errorOk();
} }
errorret_t textDrawChar( spritebatchsprite_t textGetSprite(
const float_t x, const vec2 pos, const char_t c, const font_t *font
const float_t y,
const char_t c,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
font_t *font
) { ) {
assertNotNull(font, "Font cannot be NULL");
// Change char from ASCII to a tile index.
int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START; int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START;
if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) { if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) {
tileIndex = ((int32_t)'@') - TEXT_CHAR_START; tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
@@ -52,19 +49,22 @@ errorret_t textDrawChar(
"Character is out of bounds for font tiles" "Character is out of bounds for font tiles"
); );
// Create sprite.
vec4 uv; vec4 uv;
tilesetTileGetUV(font->tileset, tileIndex, uv); tilesetTileGetUV(font->tileset, tileIndex, uv);
errorChain(spriteBatchPush( spritebatchsprite_t sprite;
x, y, sprite.min[0] = pos[0];
x + font->tileset->tileWidth, sprite.min[1] = pos[1];
y + font->tileset->tileHeight, sprite.min[2] = 0.0f;
#if MESH_ENABLE_COLOR sprite.max[0] = pos[0] + font->tileset->tileWidth;
color, sprite.max[1] = pos[1] + font->tileset->tileHeight;
#endif sprite.max[2] = 0.0f;
uv[0], uv[1], uv[2], uv[3] sprite.uvMin[0] = uv[0];
)); sprite.uvMin[1] = uv[1];
errorOk(); sprite.uvMax[0] = uv[2];
sprite.uvMax[1] = uv[3];
return sprite;
} }
errorret_t textDraw( errorret_t textDraw(
@@ -76,6 +76,14 @@ errorret_t textDraw(
) { ) {
assertNotNull(text, "Text cannot be NULL"); assertNotNull(text, "Text cannot be NULL");
spritebatchsprite_t sprite;
shadermaterial_t material = {
.unlit = {
.color = color,
.texture = font->texture
}
};
float_t posX = x; float_t posX = x;
float_t posY = y; float_t posY = y;
@@ -100,13 +108,8 @@ errorret_t textDraw(
continue; continue;
} }
errorChain(textDrawChar( sprite = textGetSprite((vec2){posX, posY}, c, font);
posX, posY, c, errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
#if MESH_ENABLE_COLOR
color,
#endif
font
));
posX += font->tileset->tileWidth; posX += font->tileset->tileWidth;
} }
errorOk(); errorOk();
+9 -14
View File
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "asset/asset.h" #include "asset/asset.h"
#include "display/text/font.h" #include "display/text/font.h"
#include "display/spritebatch/spritebatch.h"
#define TEXT_CHAR_START '!' #define TEXT_CHAR_START '!'
@@ -28,23 +29,17 @@ errorret_t textInit(void);
errorret_t textDispose(void); errorret_t textDispose(void);
/** /**
* Draws a single character at the specified position. * Builds a sprite for a single character at the given position.
* *
* @param x The x-coordinate to draw the character at. * @param pos The (x, y) position of the character in screen/world space.
* @param y The y-coordinate to draw the character at. * @param c The character to build a sprite for.
* @param c The character to draw. * @param font Font to use for tile lookup.
* @param color The color to draw the character in. * @return The populated sprite ready for spriteBatchBuffer.
* @param font Font to use for rendering.
* @return Either an error or success result.
*/ */
errorret_t textDrawChar( spritebatchsprite_t textGetSprite(
const float_t x, const vec2 pos,
const float_t y,
const char_t c, const char_t c,
#if MESH_ENABLE_COLOR const font_t *font
const color_t color,
#endif
font_t *font
); );
/** /**
@@ -7,22 +7,12 @@
#include "entityrenderable.h" #include "entityrenderable.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "display/shader/shadermaterial.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "display/display.h"
#include "display/mesh/cube.h" #include "display/mesh/cube.h"
#include "display/spritebatch/spritebatch.h" #include "util/memory.h"
#include "assert/assert.h"
errorret_t entityRenderableDrawDefault(
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
(void)entityId;
(void)componentId;
(void)user;
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_WHITE));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
return meshDraw(&CUBE_MESH_SIMPLE, 0, -1);
}
void entityRenderableInit( void entityRenderableInit(
const entityid_t entityId, const entityid_t entityId,
@@ -31,8 +21,15 @@ void entityRenderableInit(
entityrenderable_t *r = componentGetData( entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE entityId, componentId, COMPONENT_TYPE_RENDERABLE
); );
r->draw = entityRenderableDrawDefault; memoryZero(r, sizeof(entityrenderable_t));
r->drawUser = NULL; r->type = ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL;
r->data.material.shaderType = SHADER_LIST_SHADER_UNLIT;
r->data.material.material.unlit.color = COLOR_WHITE;
r->data.material.meshes[0] = &CUBE_MESH_SIMPLE;
r->data.material.meshOffsets[0] = 0;
r->data.material.meshCounts[0] = -1;
r->data.material.meshCount = 1;
r->data.material.state.flags = DISPLAY_STATE_FLAG_DEPTH_TEST;
} }
void entityRenderableDispose( void entityRenderableDispose(
@@ -42,6 +39,28 @@ void entityRenderableDispose(
} }
void entityRenderableSetType(
const entityid_t entityId,
const componentid_t componentId,
const entityrenderabletype_t type
) {
entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
r->type = type;
}
void entityRenderableSetPriority(
const entityid_t entityId,
const componentid_t componentId,
const int8_t priority
) {
entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
r->priority = priority;
}
void entityRenderableSetDraw( void entityRenderableSetDraw(
const entityid_t entityId, const entityid_t entityId,
const componentid_t componentId, const componentid_t componentId,
@@ -52,11 +71,13 @@ void entityRenderableSetDraw(
), ),
void *user void *user
) { ) {
assertNotNull(draw, "Draw callback cannot be null");
entityrenderable_t *r = componentGetData( entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE entityId, componentId, COMPONENT_TYPE_RENDERABLE
); );
r->draw = draw; r->type = ENTITY_RENDERABLE_TYPE_CUSTOM;
r->drawUser = user; r->data.custom.draw = draw;
r->data.custom.drawUser = user;
} }
errorret_t entityRenderableDraw( errorret_t entityRenderableDraw(
@@ -66,5 +87,42 @@ errorret_t entityRenderableDraw(
entityrenderable_t *r = componentGetData( entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE entityId, componentId, COMPONENT_TYPE_RENDERABLE
); );
return r->draw(entityId, componentId, r->drawUser);
switch(r->type) {
case ENTITY_RENDERABLE_TYPE_SPRITEBATCH: {
const entityrenderablespritebatch_t *sb = &r->data.spritebatch;
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_BLEND
}));
spriteBatchClear();
shadermaterial_t mat;
memoryZero(&mat, sizeof(shadermaterial_t));
mat.unlit.texture = sb->texture;
mat.unlit.color = COLOR_WHITE;
errorChain(spriteBatchBuffer(
sb->sprites, sb->spriteCount,
SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader, mat
));
return spriteBatchFlush();
}
case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL: {
const entityrenderablematerial_t *m = &r->data.material;
errorChain(displaySetState(m->state));
shader_t *shader = SHADER_LIST_DEFS[m->shaderType].shader;
assertNotNull(shader, "Shader cannot be null for material type");
errorChain(shaderBind(shader));
errorChain(shaderSetMaterial(shader, &m->material));
for(uint8_t i = 0; i < m->meshCount; i++) {
errorChain(meshDraw(m->meshes[i], m->meshOffsets[i], m->meshCounts[i]));
}
errorOk();
}
case ENTITY_RENDERABLE_TYPE_CUSTOM:
return r->data.custom.draw(entityId, componentId, r->data.custom.drawUser);
default:
assertUnreachable("Invalid renderable type");
}
} }
@@ -10,6 +10,32 @@
#include "display/mesh/mesh.h" #include "display/mesh/mesh.h"
#include "display/shader/shadermaterial.h" #include "display/shader/shadermaterial.h"
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
#include "display/displaystate.h"
#define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64
#define ENTITY_RENDERABLE_MESHES_MAX 8
typedef enum {
ENTITY_RENDERABLE_TYPE_CUSTOM = 0,
ENTITY_RENDERABLE_TYPE_SPRITEBATCH,
ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL,
} entityrenderabletype_t;
typedef struct {
spritebatchsprite_t sprites[ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX];
uint32_t spriteCount;
texture_t *texture;
} entityrenderablespritebatch_t;
typedef struct {
mesh_t *meshes[ENTITY_RENDERABLE_MESHES_MAX];
int32_t meshOffsets[ENTITY_RENDERABLE_MESHES_MAX];
int32_t meshCounts[ENTITY_RENDERABLE_MESHES_MAX];
uint8_t meshCount;
shaderlistshader_t shaderType;
shadermaterial_t material;
displaystate_t state;
} entityrenderablematerial_t;
typedef struct { typedef struct {
errorret_t (*draw)( errorret_t (*draw)(
@@ -18,11 +44,29 @@ typedef struct {
void *user void *user
); );
void *drawUser; void *drawUser;
} entityrenderablecustom_t;
typedef union entityrenderabledata_u {
entityrenderablespritebatch_t spritebatch;
entityrenderablematerial_t material;
entityrenderablecustom_t custom;
} entityrenderabledata_t;
typedef struct {
entityrenderabletype_t type;
entityrenderabledata_t data;
/**
* Render priority. 0 = auto (derived from type/flags). Higher values render
* later (on top of lower values). Range: [-128..127] with 0 is auto.
*/
int8_t priority;
} entityrenderable_t; } entityrenderable_t;
/** /**
* Initializes the entity renderable component. Defaults to * Initializes the entity renderable component. Defaults to
* ENTITY_RENDERABLE_TYPE_MATERIAL, the unlit shader, white color, no mesh. * ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL with the unlit shader, a white cube,
* and depth-test enabled.
* *
* @param entityId The entity to initialize the component for. * @param entityId The entity to initialize the component for.
* @param componentId The renderable component of the entity. * @param componentId The renderable component of the entity.
@@ -33,7 +77,7 @@ void entityRenderableInit(
); );
/** /**
* Disposes the entity renderable component, freeing any callback user data. * Disposes the entity renderable component.
* *
* @param entityId The entity to dispose the component for. * @param entityId The entity to dispose the component for.
* @param componentId The renderable component of the entity. * @param componentId The renderable component of the entity.
@@ -44,11 +88,40 @@ void entityRenderableDispose(
); );
/** /**
* Sets the draw callback for the entity's renderable component. * Sets the rendering type for the renderable component. Resets type-specific
* data to zero.
*
* @param entityId The entity to configure.
* @param componentId The renderable component.
* @param type The rendering type to use.
*/
void entityRenderableSetType(
const entityid_t entityId,
const componentid_t componentId,
const entityrenderabletype_t type
);
/**
* Sets the render priority. 0 = auto (derived from type/flags). Higher values
* render later (on top). Use non-zero to force ordering.
*
* @param entityId The entity to configure.
* @param componentId The renderable component.
* @param priority The priority value, or 0 for auto.
*/
void entityRenderableSetPriority(
const entityid_t entityId,
const componentid_t componentId,
const int8_t priority
);
/**
* Sets the draw callback, switching the type to ENTITY_RENDERABLE_TYPE_CUSTOM.
* *
* @param entityId The entity to configure. * @param entityId The entity to configure.
* @param componentId The renderable component of the entity. * @param componentId The renderable component of the entity.
* @param draw The draw callback to assign. * @param draw The draw callback to assign.
* @param user Userdata passed to the callback.
*/ */
void entityRenderableSetDraw( void entityRenderableSetDraw(
const entityid_t entityId, const entityid_t entityId,
+1
View File
@@ -7,6 +7,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
scene.c scene.c
scenerenderpipeline.c
) )
# Subdirectories # Subdirectories
+1 -20
View File
@@ -7,40 +7,21 @@
#include "overworldground.h" #include "overworldground.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "entity/component/display/entityrenderable.h"
#include "entity/component/physics/entityphysics.h" #include "entity/component/physics/entityphysics.h"
#include "display/mesh/plane.h"
#include "display/shader/shaderunlit.h"
#define OVERWORLD_GROUND_SIZE 20.0f #define OVERWORLD_GROUND_SIZE 20.0f
static errorret_t overworldGroundDraw(
const entityid_t entityId,
const componentid_t componentId,
void *user
) {
(void)entityId;
(void)componentId;
(void)user;
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_MAGENTA));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
return meshDraw(&PLANE_MESH_SIMPLE, 0, -1);
}
void overworldGroundAdd(overworldground_t *ground) { void overworldGroundAdd(overworldground_t *ground) {
ground->entityId = entityManagerAdd(); ground->entityId = entityManagerAdd();
ground->posCompId = entityAddComponent( ground->posCompId = entityAddComponent(
ground->entityId, COMPONENT_TYPE_POSITION ground->entityId, COMPONENT_TYPE_POSITION
); );
componentid_t renderComp = entityAddComponent( (void)entityAddComponent(ground->entityId, COMPONENT_TYPE_RENDERABLE);
ground->entityId, COMPONENT_TYPE_RENDERABLE
);
vec3 pos = { -OVERWORLD_GROUND_SIZE, 0.0f, -OVERWORLD_GROUND_SIZE }; vec3 pos = { -OVERWORLD_GROUND_SIZE, 0.0f, -OVERWORLD_GROUND_SIZE };
vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f }; vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f };
entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos); entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos);
entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale); entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale);
entityRenderableSetDraw(ground->entityId, renderComp, overworldGroundDraw, NULL);
// Separate physics entity centered on the finite ground area. // Separate physics entity centered on the finite ground area.
// The visual entity's position is its corner {-size, 0, -size}, not its // The visual entity's position is its corner {-size, 0, -size}, not its
+65 -1
View File
@@ -11,9 +11,15 @@
#include "overworldnpc.h" #include "overworldnpc.h"
#include "console/console.h" #include "console/console.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "entity/component/display/entityrenderable.h"
#include "display/shader/shaderlist.h"
#include "entity/component/overworld/entityinteractable.h" #include "entity/component/overworld/entityinteractable.h"
#include "entity/component/overworld/entityoverworldcamera.h" #include "entity/component/overworld/entityoverworldcamera.h"
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
#include "display/displaystate.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "assert/assert.h"
#define OVERWORLD (SCENE.data.overworld) #define OVERWORLD (SCENE.data.overworld)
@@ -26,14 +32,72 @@ static void overworldSceneNpcInteract(
consolePrint("NPC interacted with!"); consolePrint("NPC interacted with!");
} }
void overworldSceneConfigureShaderMaterial(
const entityid_t entityId,
const color_t color,
mesh_t *mesh
) {
assertNotNull(mesh, "Mesh cannot be null");
componentid_t renderComp = entityGetComponent(
entityId, COMPONENT_TYPE_RENDERABLE
);
entityRenderableSetType(entityId, renderComp, ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL);
entityrenderable_t *r = componentGetData(
entityId, renderComp, COMPONENT_TYPE_RENDERABLE
);
r->data.material.shaderType = SHADER_LIST_SHADER_UNLIT;
r->data.material.material.unlit.color = color;
r->data.material.material.unlit.texture = NULL;
r->data.material.state.flags = DISPLAY_STATE_FLAG_DEPTH_TEST;
r->data.material.meshes[0] = mesh;
r->data.material.meshOffsets[0] = 0;
r->data.material.meshCounts[0] = -1;
r->data.material.meshCount = 1;
}
void overworldSceneConfigureSprite(
const entityid_t entityId,
const color_t color,
texture_t *texture
) {
componentid_t renderComp = entityGetComponent(
entityId, COMPONENT_TYPE_RENDERABLE
);
entityRenderableSetType(entityId, renderComp, ENTITY_RENDERABLE_TYPE_SPRITEBATCH);
entityrenderable_t *r = componentGetData(
entityId, renderComp, COMPONENT_TYPE_RENDERABLE
);
r->data.spritebatch.texture = texture;
r->data.spritebatch.spriteCount = 1;
r->data.spritebatch.sprites[0].min[0] = -0.5f;
r->data.spritebatch.sprites[0].min[1] = -0.0f;
r->data.spritebatch.sprites[0].min[2] = -0.5f;
r->data.spritebatch.sprites[0].max[0] = 0.5f;
r->data.spritebatch.sprites[0].max[1] = 0.0f;
r->data.spritebatch.sprites[0].max[2] = 0.5f;
r->data.spritebatch.sprites[0].uvMin[0] = 0.0f;
r->data.spritebatch.sprites[0].uvMin[1] = 0.0f;
r->data.spritebatch.sprites[0].uvMax[0] = 1.0f;
r->data.spritebatch.sprites[0].uvMax[1] = 1.0f;
}
void overworldSceneInit(void) { void overworldSceneInit(void) {
consolePrint("Overworld scene initialized"); consolePrint("Overworld scene initialized");
overworldGroundAdd(&OVERWORLD.ground); overworldGroundAdd(&OVERWORLD.ground);
overworldSceneConfigureShaderMaterial(
OVERWORLD.ground.entityId, COLOR_MAGENTA, &PLANE_MESH_SIMPLE
);
overworldPlayerAdd(&OVERWORLD.player); overworldPlayerAdd(&OVERWORLD.player);
overworldSceneConfigureSprite(OVERWORLD.player.entityId, COLOR_GREEN, NULL);
vec3 npcPos = { 3.0f, 0.5f, 3.0f }; vec3 npcPos = { 3.0f, 0.5f, 3.0f };
overworldNpcAdd(&OVERWORLD.npc, npcPos); overworldNpcAdd(&OVERWORLD.npc, npcPos);
overworldSceneConfigureSprite(OVERWORLD.npc.entityId, COLOR_BLUE, NULL);
OVERWORLD.cameraEntityId = entityManagerAdd(); OVERWORLD.cameraEntityId = entityManagerAdd();
(void)entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION); (void)entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION);
@@ -48,7 +112,7 @@ void overworldSceneInit(void) {
entityoverworldcamera_t *camData = entityOverworldCameraGet( entityoverworldcamera_t *camData = entityOverworldCameraGet(
OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId OVERWORLD.cameraEntityId, OVERWORLD.cameraOverworldCompId
); );
glm_vec3_copy((vec3){ 0.5f, 0.5f, 0.5f }, camData->targetOffset); glm_vec3_zero(camData->targetOffset);
glm_vec3_copy((vec3){ 0.0f, 0.0f, 5.0f }, camData->eyeOffset); glm_vec3_copy((vec3){ 0.0f, 0.0f, 5.0f }, camData->eyeOffset);
camData->scale = 32.0f; camData->scale = 32.0f;
entityInteractableSetCallback( entityInteractableSetCallback(
+45
View File
@@ -8,6 +8,9 @@
#pragma once #pragma once
#include "error/error.h" #include "error/error.h"
#include "entity/entitybase.h" #include "entity/entitybase.h"
#include "display/color.h"
#include "display/mesh/mesh.h"
#include "display/texture/texture.h"
#include "scene/overworld/overworldplayer.h" #include "scene/overworld/overworldplayer.h"
#include "scene/overworld/overworldground.h" #include "scene/overworld/overworldground.h"
#include "scene/overworld/overworldnpc.h" #include "scene/overworld/overworldnpc.h"
@@ -20,6 +23,48 @@ typedef struct {
overworldnpc_t npc; overworldnpc_t npc;
} overworldscene_t; } overworldscene_t;
/**
* Configures a SHADER_MATERIAL renderable on an entity with the unlit shader,
* depth testing enabled, no blending, and a single mesh.
*
* @param entityId The entity to configure.
* @param color Unlit diffuse color.
* @param mesh Mesh to render.
*/
void overworldSceneConfigureShaderMaterial(
const entityid_t entityId,
const color_t color,
mesh_t *mesh
);
/**
* Configures a SPRITEBATCH renderable on an entity as a single 1x1 billboard
* sprite centered at the entity's origin.
*
* @param entityId The entity to configure.
* @param color Per-vertex sprite tint color.
* @param texture Texture to use, or NULL for untextured.
*/
void overworldSceneConfigureSprite(
const entityid_t entityId,
const color_t color,
texture_t *texture
);
/**
* Initializes the overworld scene, spawning all entities and configuring
* the camera, player, ground, and NPC.
*/
void overworldSceneInit(void); void overworldSceneInit(void);
/**
* Updates the overworld scene each frame.
*
* @return Error state.
*/
errorret_t overworldSceneUpdate(void); errorret_t overworldSceneUpdate(void);
/**
* Disposes the overworld scene, invalidating all entity and component IDs.
*/
void overworldSceneDispose(void); void overworldSceneDispose(void);
+5 -56
View File
@@ -10,13 +10,12 @@
#include "time/time.h" #include "time/time.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
#include "entity/entitymanager.h" #include "entity/entitymanager.h"
#include "entity/component/display/entityrenderable.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
#include "display/spritebatch/spritebatch.h" #include "display/display.h"
#include "display/screen/screen.h"
#include "console/console.h" #include "console/console.h"
#include "util/string.h" #include "util/string.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "scene/scenerenderpipeline.h"
scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT] = { scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT] = {
{ 0 }, { 0 },
@@ -69,63 +68,13 @@ errorret_t sceneUpdate(void) {
} }
errorret_t sceneRender(void) { errorret_t sceneRender(void) {
mat4 proj, view, model, ident; mat4 proj, view, ident;
glm_mat4_identity(ident); glm_mat4_identity(ident);
errorChain(shaderBind(&SHADER_UNLIT)); errorChain(sceneRenderPipeline(entityCameraGetCurrent()));
// Render scene.
entityid_t camera = entityCameraGetCurrent();
entityid_t entities[ENTITY_COUNT_MAX];
componentid_t components[ENTITY_COUNT_MAX];
entityid_t entCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_RENDERABLE,
entities, components
);
// Anything to render?
if(camera != ENTITY_ID_INVALID && entCount > 0) {
componentid_t camPos = entityGetComponent(camera,COMPONENT_TYPE_POSITION);
componentid_t camCam = entityGetComponent(camera,COMPONENT_TYPE_CAMERA);
// View
if(camPos == COMPONENT_ID_INVALID) {
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, ident));
} else {
entityPositionGetTransform(camera, camPos, view);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
}
// Projection
assertTrue(
camCam != COMPONENT_ID_INVALID,
"Current camera does not have a camera component?"
);
entityCameraGetProjection(camera, camCam, proj);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
// For each ent.
for(entityid_t i = 0; i < entCount; i++) {
entityid_t entityId = entities[i];
componentid_t renderableComp = components[i];
// Has position?
componentid_t posComp = entityGetComponent(
entityId, COMPONENT_TYPE_POSITION
);
if(posComp == COMPONENT_ID_INVALID) {
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident));
} else {
entityPositionGetTransform(entityId, posComp, model);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
}
// Render.
errorChain(entityRenderableDraw(entityId, renderableComp));
}
}
// UI Rendering // UI Rendering
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident)); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident));
glm_ortho( glm_ortho(
+120
View File
@@ -0,0 +1,120 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scenerenderpipeline.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityposition.h"
#include "entity/component/display/entitycamera.h"
#include "display/shader/shaderlist.h"
#include "display/shader/shaderunlit.h"
#include "display/displaystate.h"
#include "assert/assert.h"
int8_t sceneRenderPipelineGetPriority(const entityrenderable_t *r) {
assertNotNull(r, "Renderable cannot be null");
if(r->priority != 0) return r->priority;
switch(r->type) {
case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL:
return (r->data.material.state.flags & DISPLAY_STATE_FLAG_BLEND)
? (int8_t)10 : (int8_t)-10;
case ENTITY_RENDERABLE_TYPE_SPRITEBATCH:
return (int8_t)100;
case ENTITY_RENDERABLE_TYPE_CUSTOM:
default:
return (int8_t)0;
}
}
int_t sceneRenderPipelineCompare(const void *a, const void *b) {
assertNotNull(a, "Entry a cannot be null");
assertNotNull(b, "Entry b cannot be null");
const scenerenderpipelineentry_t *ea = (const scenerenderpipelineentry_t *)a;
const scenerenderpipelineentry_t *eb = (const scenerenderpipelineentry_t *)b;
return (int_t)ea->effectivePriority - (int_t)eb->effectivePriority;
}
shader_t *sceneRenderPipelineGetShader(const entityrenderable_t *r) {
assertNotNull(r, "Renderable cannot be null");
switch(r->type) {
case ENTITY_RENDERABLE_TYPE_SPRITEBATCH:
return SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader;
case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL:
return SHADER_LIST_DEFS[r->data.material.shaderType].shader;
case ENTITY_RENDERABLE_TYPE_CUSTOM:
default:
return SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader;
}
}
errorret_t sceneRenderPipeline(const entityid_t cameraId) {
mat4 proj, view, model, ident;
glm_mat4_identity(ident);
entityid_t entities[ENTITY_COUNT_MAX];
componentid_t components[ENTITY_COUNT_MAX];
entityid_t entCount = componentGetEntitiesWithComponent(
COMPONENT_TYPE_RENDERABLE, entities, components
);
if(cameraId == ENTITY_ID_INVALID || entCount == 0) errorOk();
scenerenderpipelineentry_t pipeline[ENTITY_COUNT_MAX];
for(entityid_t i = 0; i < entCount; i++) {
entityrenderable_t *r = componentGetData(
entities[i], components[i], COMPONENT_TYPE_RENDERABLE
);
pipeline[i].entityId = entities[i];
pipeline[i].componentId = components[i];
pipeline[i].effectivePriority = sceneRenderPipelineGetPriority(r);
}
sort(pipeline, (size_t)entCount, sizeof(scenerenderpipelineentry_t), sceneRenderPipelineCompare);
componentid_t camPos = entityGetComponent(cameraId, COMPONENT_TYPE_POSITION);
componentid_t camCam = entityGetComponent(cameraId, COMPONENT_TYPE_CAMERA);
assertTrue(
camCam != COMPONENT_ID_INVALID,
"Current camera does not have a camera component?"
);
if(camPos == COMPONENT_ID_INVALID) {
glm_mat4_copy(ident, view);
} else {
entityPositionGetTransform(cameraId, camPos, view);
}
entityCameraGetProjection(cameraId, camCam, proj);
for(shaderlistshader_t si = SHADER_LIST_SHADER_NULL + 1; si < SHADER_LIST_SHADER_COUNT; si++) {
shader_t *s = SHADER_LIST_DEFS[si].shader;
assertNotNull(s, "Shader in list cannot be null");
errorChain(shaderBind(s));
errorChain(shaderSetMatrix(s, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(s, SHADER_UNLIT_PROJECTION, proj));
}
for(entityid_t i = 0; i < entCount; i++) {
entityid_t eid = pipeline[i].entityId;
componentid_t cid = pipeline[i].componentId;
entityrenderable_t *r = componentGetData(eid, cid, COMPONENT_TYPE_RENDERABLE);
shader_t *s = sceneRenderPipelineGetShader(r);
componentid_t posComp = entityGetComponent(eid, COMPONENT_TYPE_POSITION);
if(posComp == COMPONENT_ID_INVALID) {
errorChain(shaderBind(s));
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, ident));
} else {
entityPositionGetTransform(eid, posComp, model);
errorChain(shaderBind(s));
errorChain(shaderSetMatrix(s, SHADER_UNLIT_MODEL, model));
}
errorChain(entityRenderableDraw(eid, cid));
}
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
errorOk();
}
+59
View File
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entity/entitybase.h"
#include "entity/component/display/entityrenderable.h"
#include "display/shader/shader.h"
#include "util/sort.h"
typedef struct {
entityid_t entityId;
componentid_t componentId;
int8_t effectivePriority;
} scenerenderpipelineentry_t;
/**
* Returns the effective render priority for a renderable. When the renderable's
* explicit priority is non-zero that value is returned directly; otherwise an
* automatic value is derived from the renderable type and display state flags.
*
* @param r The renderable component data.
* @return Effective priority: lower renders first, higher renders last.
*/
int8_t sceneRenderPipelineGetPriority(const entityrenderable_t *r);
/**
* sortcompare_t comparator for scenerenderpipelineentry_t. Compares by
* effectivePriority ascending so lower-priority entries sort first.
*
* @param a Pointer to the first scenerenderpipelineentry_t.
* @param b Pointer to the second scenerenderpipelineentry_t.
* @return Negative, zero, or positive.
*/
int_t sceneRenderPipelineCompare(const void *a, const void *b);
/**
* Returns the shader that will be used to render the given renderable.
* SPRITEBATCH and CUSTOM default to SHADER_UNLIT; SHADER_MATERIAL uses the
* shader indexed by the material's shaderType field in SHADER_LIST_DEFS.
*
* @param r The renderable component data.
* @return Pointer to the shader, never NULL.
*/
shader_t *sceneRenderPipelineGetShader(const entityrenderable_t *r);
/**
* Renders all entities with renderable components in priority order.
* Lower effective priority renders first (behind); higher renders last (on top).
* When priority is 0 the effective value is derived from the renderable's type
* and flags; otherwise the explicit priority is used directly.
*
* @param cameraId The entity ID of the active camera, or ENTITY_ID_INVALID.
* @return Error state.
*/
errorret_t sceneRenderPipeline(const entityid_t cameraId);
+98 -69
View File
@@ -26,97 +26,126 @@ errorret_t uiFrameDraw(
assertNotNull(frame, "frame must not be NULL"); assertNotNull(frame, "frame must not be NULL");
assertNotNull(frame->texture, "frame texture must not be NULL"); assertNotNull(frame->texture, "frame texture must not be NULL");
errorChain(shaderSetTexture( shadermaterial_t material = {
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, frame->texture .unlit = {
)); .color = COLOR_WHITE,
.texture = frame->texture
}
};
spritebatchsprite_t sprites[9];
float_t tileW = (float_t)frame->tileset.tileWidth; float_t tileW = (float_t)frame->tileset.tileWidth;
float_t tileH = (float_t)frame->tileset.tileHeight; float_t tileH = (float_t)frame->tileset.tileHeight;
vec4 uv; vec4 uv;
tilesetPositionGetUV(&frame->tileset, 0, 0, uv); tilesetPositionGetUV(&frame->tileset, 0, 0, uv);
errorChain(spriteBatchPush( sprites[0].min[0] = x;
x, y, x + tileW, y + tileH, sprites[0].min[1] = y;
#if MESH_ENABLE_COLOR sprites[0].min[2] = 0.0f;
COLOR_WHITE, sprites[0].max[0] = x + tileW;
#endif sprites[0].max[1] = y + tileH;
uv[0], uv[1], uv[2], uv[3] sprites[0].max[2] = 0.0f;
)); sprites[0].uvMin[0] = uv[0];
sprites[0].uvMin[1] = uv[1];
sprites[0].uvMax[0] = uv[2];
sprites[0].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 1, 0, uv); tilesetPositionGetUV(&frame->tileset, 1, 0, uv);
errorChain(spriteBatchPush( sprites[1].min[0] = x + tileW;
x + tileW, y, x + width - tileW, y + tileH, sprites[1].min[1] = y;
#if MESH_ENABLE_COLOR sprites[1].min[2] = 0.0f;
COLOR_WHITE, sprites[1].max[0] = x + width - tileW;
#endif sprites[1].max[1] = y + tileH;
uv[0], uv[1], uv[2], uv[3] sprites[1].max[2] = 0.0f;
)); sprites[1].uvMin[0] = uv[0];
sprites[1].uvMin[1] = uv[1];
sprites[1].uvMax[0] = uv[2];
sprites[1].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 2, 0, uv); tilesetPositionGetUV(&frame->tileset, 2, 0, uv);
errorChain(spriteBatchPush( sprites[2].min[0] = x + width - tileW;
x + width - tileW, y, x + width, y + tileH, sprites[2].min[1] = y;
#if MESH_ENABLE_COLOR sprites[2].min[2] = 0.0f;
COLOR_WHITE, sprites[2].max[0] = x + width;
#endif sprites[2].max[1] = y + tileH;
uv[0], uv[1], uv[2], uv[3] sprites[2].max[2] = 0.0f;
)); sprites[2].uvMin[0] = uv[0];
sprites[2].uvMin[1] = uv[1];
sprites[2].uvMax[0] = uv[2];
sprites[2].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 0, 1, uv); tilesetPositionGetUV(&frame->tileset, 0, 1, uv);
errorChain(spriteBatchPush( sprites[3].min[0] = x;
x, y + tileH, x + tileW, y + height - tileH, sprites[3].min[1] = y + tileH;
#if MESH_ENABLE_COLOR sprites[3].min[2] = 0.0f;
COLOR_WHITE, sprites[3].max[0] = x + tileW;
#endif sprites[3].max[1] = y + height - tileH;
uv[0], uv[1], uv[2], uv[3] sprites[3].max[2] = 0.0f;
)); sprites[3].uvMin[0] = uv[0];
sprites[3].uvMin[1] = uv[1];
sprites[3].uvMax[0] = uv[2];
sprites[3].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 1, 1, uv); tilesetPositionGetUV(&frame->tileset, 1, 1, uv);
errorChain(spriteBatchPush( sprites[4].min[0] = x + tileW;
x + tileW, y + tileH, x + width - tileW, y + height - tileH, sprites[4].min[1] = y + tileH;
#if MESH_ENABLE_COLOR sprites[4].min[2] = 0.0f;
COLOR_WHITE, sprites[4].max[0] = x + width - tileW;
#endif sprites[4].max[1] = y + height - tileH;
uv[0], uv[1], uv[2], uv[3] sprites[4].max[2] = 0.0f;
)); sprites[4].uvMin[0] = uv[0];
sprites[4].uvMin[1] = uv[1];
sprites[4].uvMax[0] = uv[2];
sprites[4].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 2, 1, uv); tilesetPositionGetUV(&frame->tileset, 2, 1, uv);
errorChain(spriteBatchPush( sprites[5].min[0] = x + width - tileW;
x + width - tileW, y + tileH, x + width, y + height - tileH, sprites[5].min[1] = y + tileH;
#if MESH_ENABLE_COLOR sprites[5].min[2] = 0.0f;
COLOR_WHITE, sprites[5].max[0] = x + width;
#endif sprites[5].max[1] = y + height - tileH;
uv[0], uv[1], uv[2], uv[3] sprites[5].max[2] = 0.0f;
)); sprites[5].uvMin[0] = uv[0];
sprites[5].uvMin[1] = uv[1];
sprites[5].uvMax[0] = uv[2];
sprites[5].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 0, 2, uv); tilesetPositionGetUV(&frame->tileset, 0, 2, uv);
errorChain(spriteBatchPush( sprites[6].min[0] = x;
x, y + height - tileH, x + tileW, y + height, sprites[6].min[1] = y + height - tileH;
#if MESH_ENABLE_COLOR sprites[6].min[2] = 0.0f;
COLOR_WHITE, sprites[6].max[0] = x + tileW;
#endif sprites[6].max[1] = y + height;
uv[0], uv[1], uv[2], uv[3] sprites[6].max[2] = 0.0f;
)); sprites[6].uvMin[0] = uv[0];
sprites[6].uvMin[1] = uv[1];
sprites[6].uvMax[0] = uv[2];
sprites[6].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 1, 2, uv); tilesetPositionGetUV(&frame->tileset, 1, 2, uv);
errorChain(spriteBatchPush( sprites[7].min[0] = x + tileW;
x + tileW, y + height - tileH, x + width - tileW, y + height, sprites[7].min[1] = y + height - tileH;
#if MESH_ENABLE_COLOR sprites[7].min[2] = 0.0f;
COLOR_WHITE, sprites[7].max[0] = x + width - tileW;
#endif sprites[7].max[1] = y + height;
uv[0], uv[1], uv[2], uv[3] sprites[7].max[2] = 0.0f;
)); sprites[7].uvMin[0] = uv[0];
sprites[7].uvMin[1] = uv[1];
sprites[7].uvMax[0] = uv[2];
sprites[7].uvMax[1] = uv[3];
tilesetPositionGetUV(&frame->tileset, 2, 2, uv); tilesetPositionGetUV(&frame->tileset, 2, 2, uv);
errorChain(spriteBatchPush( sprites[8].min[0] = x + width - tileW;
x + width - tileW, y + height - tileH, x + width, y + height, sprites[8].min[1] = y + height - tileH;
#if MESH_ENABLE_COLOR sprites[8].min[2] = 0.0f;
COLOR_WHITE, sprites[8].max[0] = x + width;
#endif sprites[8].max[1] = y + height;
uv[0], uv[1], uv[2], uv[3] sprites[8].max[2] = 0.0f;
)); sprites[8].uvMin[0] = uv[0];
sprites[8].uvMin[1] = uv[1];
sprites[8].uvMax[0] = uv[2];
sprites[8].uvMax[1] = uv[3];
errorOk(); return spriteBatchBuffer(sprites, 9, &SHADER_UNLIT, material);
} }
void uiFrameDispose(uiframe_t *frame) { void uiFrameDispose(uiframe_t *frame) {
+8 -10
View File
@@ -58,16 +58,14 @@ errorret_t uiFullboxDraw(uifullbox_t *fullbox) {
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color)); errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color));
#endif #endif
errorChain(spriteBatchPush( spritebatchsprite_t sprite;
0.0f, 0.0f, shadermaterial_t material = {
(float_t)SCREEN.width, (float_t)SCREEN.height, .unlit = {
#if MESH_ENABLE_COLOR .color = color,
color, .texture = &TEXTURE_WHITE
#endif }
0.0f, 0.0f, 1.0f, 1.0f };
)); return spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material);
errorChain(spriteBatchFlush());
errorOk();
} }
void uiFullboxTransition( void uiFullboxTransition(
+12 -18
View File
@@ -12,6 +12,7 @@
#include "event/event.h" #include "event/event.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
#include "display/texture/texture.h" #include "display/texture/texture.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
@@ -216,15 +217,12 @@ errorret_t uiTextboxDraw(void) {
)); ));
errorChain(spriteBatchFlush()); errorChain(spriteBatchFlush());
errorChain(shaderSetTexture( shadermaterial_t textMaterial = {
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, UI_TEXTBOX.font->texture .unlit = {
)); .color = UI_TEXTBOX.textColor,
#if MESH_ENABLE_COLOR .texture = UI_TEXTBOX.font->texture
#else }
errorChain(shaderSetColor( };
&SHADER_UNLIT, SHADER_UNLIT_COLOR, UI_TEXTBOX.textColor
));
#endif
float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight;
@@ -242,21 +240,17 @@ errorret_t uiTextboxDraw(void) {
for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) { for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) {
uitextboxline_t *line = &UI_TEXTBOX.lines[li]; uitextboxline_t *line = &UI_TEXTBOX.lines[li];
int32_t visible = line->count < charsLeft ? line->count : charsLeft; int32_t visible = line->count < charsLeft ? line->count : charsLeft;
float_t lineY = contentY; float_t lineY = contentY + (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
lineY += (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
for(int32_t ci = 0; ci < visible; ci++) { for(int32_t ci = 0; ci < visible; ci++) {
char_t c = UI_TEXTBOX.text[line->start + ci]; char_t c = UI_TEXTBOX.text[line->start + ci];
if(c == ' ') continue; if(c == ' ') continue;
errorChain(textDrawChar( spritebatchsprite_t sprite = textGetSprite(
contentX + (float_t)ci * fontW, (vec2){ contentX + (float_t)ci * fontW, lineY },
lineY,
c, c,
#if MESH_ENABLE_COLOR
UI_TEXTBOX.textColor,
#endif
UI_TEXTBOX.font UI_TEXTBOX.font
)); );
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial));
} }
charsLeft -= visible; charsLeft -= visible;