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/string.h"
#include "asset/asset.h"
#include "display/shader/shaderunlit.h"
#include "display/shader/shaderlist.h"
#include "time/time.h"
display_t DISPLAY = { 0 };
@@ -53,31 +53,8 @@ errorret_t displayInit(void) {
errorChain(screenInit());
// 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(
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));
errorChain(shaderListInit());
errorOk();
}
@@ -119,7 +96,7 @@ errorret_t displaySetState(displaystate_t state) {
}
errorret_t displayDispose(void) {
errorChain(shaderDispose(&SHADER_UNLIT));
errorChain(shaderListDispose());
errorChain(spriteBatchDispose());
screenDispose();
errorChain(textDispose());
+1
View File
@@ -7,5 +7,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
shader.c
shaderlist.c
shaderunlit.c
)
+2
View File
@@ -8,6 +8,7 @@
#include "shader.h"
#include "shadermaterial.h"
#include "assert/assert.h"
#include "log/log.h"
shader_t *bound = NULL;
@@ -57,6 +58,7 @@ errorret_t shaderSetColor(
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertTrue(bound == shader, "Shader must be bound.");
errorChain(shaderSetColorPlatform(shader, name, color));
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
#include "display/shader/shaderunlit.h"
#include "display/shader/shaderlist.h"
typedef union shadermaterial_u {
shaderunlitmaterial_t unlit;
+75 -110
View File
@@ -9,14 +9,13 @@
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
#include "display/shader/shaderunlit.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,
@@ -28,137 +27,100 @@ errorret_t spriteBatchInit() {
errorret_t spriteBatchBuffer(
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++) {
const spritebatchsprite_t *s = &sprites[i];
assertNotNull(sprites, "Sprites cannot be null");
assertTrue(count > 0, "Count must be greater than zero");
assertNotNull(shader, "Shader cannot be null");
// if(s->texture != SPRITEBATCH.currentTexture) {
// errorChain(spriteBatchFlush());
// }
// 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;
}
if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) {
errorChain(spriteBatchFlush());
}
// Buffer the vertices.
for(uint32_t i = 0; i < count; i++ ){
spritebatchsprite_t sprite = sprites[i];
meshvertex_t *v = &SPRITEBATCH_VERTICES[
(SPRITEBATCH.spriteCount + (SPRITEBATCH.spriteFlush *
SPRITEBATCH_SPRITES_MAX_PER_FLUSH)) * 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].pos[0] = s->min[0]; v[0].pos[1] = s->min[1]; v[0].pos[2] = s->min[2];
v[0].uv[0] = s->uvMin[0]; v[0].uv[1] = s->uvMin[1];
v[0].uv[0] = sprite.uvMin[0];
v[0].uv[1] = sprite.uvMin[1];
v[1].pos[0] = s->max[0]; v[1].pos[1] = s->min[1]; v[1].pos[2] = s->min[2];
v[1].uv[0] = s->uvMax[0]; v[1].uv[1] = s->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[1].pos[0] = sprite.max[0];
v[1].pos[1] = sprite.min[1];
v[1].pos[2] = sprite.min[2];
v[3].pos[0] = s->min[0]; v[3].pos[1] = s->min[1]; v[3].pos[2] = s->min[2];
v[3].uv[0] = s->uvMin[0]; v[3].uv[1] = s->uvMin[1];
v[1].uv[0] = sprite.uvMax[0];
v[1].uv[1] = sprite.uvMin[1];
v[4].pos[0] = s->max[0]; v[4].pos[1] = s->max[1]; v[4].pos[2] = s->min[2];
v[4].uv[0] = s->uvMax[0]; v[4].uv[1] = s->uvMax[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];
v[2].pos[0] = sprite.max[0];
v[2].pos[1] = sprite.max[1];
v[2].pos[2] = sprite.max[2];
#if MESH_ENABLE_COLOR
for(uint8_t vi = 0; vi < QUAD_VERTEX_COUNT; vi++) {
v[vi].color = s->color;
}
#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++;
if(SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX_PER_FLUSH) {
errorChain(spriteBatchFlush());
}
}
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() {
SPRITEBATCH.spriteCount = 0;
SPRITEBATCH.spriteFlush = 0;
SPRITEBATCH.currentTexture = NULL;
SPRITEBATCH.shader = NULL;
memoryZero(&SPRITEBATCH.material, sizeof(shadermaterial_t));
}
errorret_t spriteBatchFlush() {
@@ -171,6 +133,9 @@ errorret_t spriteBatchFlush() {
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));
@@ -186,4 +151,4 @@ errorret_t spriteBatchFlush() {
errorret_t spriteBatchDispose() {
errorChain(meshDispose(&SPRITEBATCH.mesh));
errorOk();
}
}
+26 -105
View File
@@ -8,6 +8,7 @@
#pragma once
#include "display/mesh/quad.h"
#include "display/texture/texture.h"
#include "display/shader/shadermaterial.h"
#define SPRITEBATCH_SPRITES_MAX 512
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
@@ -21,146 +22,66 @@ typedef struct {
vec3 max;
vec2 uvMin;
vec2 uvMax;
texture_t *texture;
#if MESH_ENABLE_COLOR
color_t color;
#endif
} spritebatchsprite_t;
typedef struct {
mesh_t mesh;
int32_t spriteCount;
int32_t spriteFlush;
texture_t *currentTexture;
shader_t *shader;
shadermaterial_t material;
} 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 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();
/**
* Buffers an array of sprites. Flushes automatically when the texture changes
* or the per-flush limit is reached.
* Lowest-level buffer function. Writes sprites into the internal vertex buffer.
* 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 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(
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.
*
* @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.
* Resets sprite and flush counters and clears the current material state.
* Calling spriteBatchFlush after this renders nothing.
*/
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();
/**
* 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();
+28 -25
View File
@@ -34,15 +34,12 @@ errorret_t textDispose(void) {
errorOk();
}
errorret_t textDrawChar(
const float_t x,
const float_t y,
const char_t c,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
font_t *font
spritebatchsprite_t textGetSprite(
const vec2 pos, const char_t c, const 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;
if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) {
tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
@@ -52,19 +49,22 @@ errorret_t textDrawChar(
"Character is out of bounds for font tiles"
);
// Create sprite.
vec4 uv;
tilesetTileGetUV(font->tileset, tileIndex, uv);
errorChain(spriteBatchPush(
x, y,
x + font->tileset->tileWidth,
y + font->tileset->tileHeight,
#if MESH_ENABLE_COLOR
color,
#endif
uv[0], uv[1], uv[2], uv[3]
));
errorOk();
spritebatchsprite_t sprite;
sprite.min[0] = pos[0];
sprite.min[1] = pos[1];
sprite.min[2] = 0.0f;
sprite.max[0] = pos[0] + font->tileset->tileWidth;
sprite.max[1] = pos[1] + font->tileset->tileHeight;
sprite.max[2] = 0.0f;
sprite.uvMin[0] = uv[0];
sprite.uvMin[1] = uv[1];
sprite.uvMax[0] = uv[2];
sprite.uvMax[1] = uv[3];
return sprite;
}
errorret_t textDraw(
@@ -76,6 +76,14 @@ errorret_t textDraw(
) {
assertNotNull(text, "Text cannot be NULL");
spritebatchsprite_t sprite;
shadermaterial_t material = {
.unlit = {
.color = color,
.texture = font->texture
}
};
float_t posX = x;
float_t posY = y;
@@ -100,13 +108,8 @@ errorret_t textDraw(
continue;
}
errorChain(textDrawChar(
posX, posY, c,
#if MESH_ENABLE_COLOR
color,
#endif
font
));
sprite = textGetSprite((vec2){posX, posY}, c, font);
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
posX += font->tileset->tileWidth;
}
errorOk();
+9 -14
View File
@@ -8,6 +8,7 @@
#pragma once
#include "asset/asset.h"
#include "display/text/font.h"
#include "display/spritebatch/spritebatch.h"
#define TEXT_CHAR_START '!'
@@ -28,23 +29,17 @@ errorret_t textInit(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 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 font Font to use for rendering.
* @return Either an error or success result.
* @param pos The (x, y) position of the character in screen/world space.
* @param c The character to build a sprite for.
* @param font Font to use for tile lookup.
* @return The populated sprite ready for spriteBatchBuffer.
*/
errorret_t textDrawChar(
const float_t x,
const float_t y,
spritebatchsprite_t textGetSprite(
const vec2 pos,
const char_t c,
#if MESH_ENABLE_COLOR
const color_t color,
#endif
font_t *font
const font_t *font
);
/**
@@ -7,22 +7,12 @@
#include "entityrenderable.h"
#include "entity/entitymanager.h"
#include "display/shader/shadermaterial.h"
#include "display/shader/shaderunlit.h"
#include "display/display.h"
#include "display/mesh/cube.h"
#include "display/spritebatch/spritebatch.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);
}
#include "util/memory.h"
#include "assert/assert.h"
void entityRenderableInit(
const entityid_t entityId,
@@ -31,15 +21,44 @@ void entityRenderableInit(
entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
r->draw = entityRenderableDrawDefault;
r->drawUser = NULL;
memoryZero(r, sizeof(entityrenderable_t));
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(
const entityid_t entityId,
const componentid_t componentId
) {
}
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(
@@ -52,11 +71,13 @@ void entityRenderableSetDraw(
),
void *user
) {
assertNotNull(draw, "Draw callback cannot be null");
entityrenderable_t *r = componentGetData(
entityId, componentId, COMPONENT_TYPE_RENDERABLE
);
r->draw = draw;
r->drawUser = user;
r->type = ENTITY_RENDERABLE_TYPE_CUSTOM;
r->data.custom.draw = draw;
r->data.custom.drawUser = user;
}
errorret_t entityRenderableDraw(
@@ -66,5 +87,42 @@ errorret_t entityRenderableDraw(
entityrenderable_t *r = componentGetData(
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/shader/shadermaterial.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 {
errorret_t (*draw)(
@@ -18,12 +44,30 @@ typedef struct {
void *user
);
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;
/**
* 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 componentId The renderable component of the entity.
*/
@@ -33,8 +77,8 @@ 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 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 componentId The renderable component of the entity.
* @param draw The draw callback to assign.
* @param user Userdata passed to the callback.
*/
void entityRenderableSetDraw(
const entityid_t entityId,
+1
View File
@@ -7,6 +7,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
scene.c
scenerenderpipeline.c
)
# Subdirectories
+1 -20
View File
@@ -7,40 +7,21 @@
#include "overworldground.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityrenderable.h"
#include "entity/component/physics/entityphysics.h"
#include "display/mesh/plane.h"
#include "display/shader/shaderunlit.h"
#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) {
ground->entityId = entityManagerAdd();
ground->posCompId = entityAddComponent(
ground->entityId, COMPONENT_TYPE_POSITION
);
componentid_t renderComp = entityAddComponent(
ground->entityId, COMPONENT_TYPE_RENDERABLE
);
(void)entityAddComponent(ground->entityId, COMPONENT_TYPE_RENDERABLE);
vec3 pos = { -OVERWORLD_GROUND_SIZE, 0.0f, -OVERWORLD_GROUND_SIZE };
vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f };
entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos);
entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale);
entityRenderableSetDraw(ground->entityId, renderComp, overworldGroundDraw, NULL);
// Separate physics entity centered on the finite ground area.
// 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 "console/console.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/entityoverworldcamera.h"
#include "display/mesh/cube.h"
#include "display/mesh/plane.h"
#include "display/displaystate.h"
#include "scene/scene.h"
#include "assert/assert.h"
#define OVERWORLD (SCENE.data.overworld)
@@ -26,14 +32,72 @@ static void overworldSceneNpcInteract(
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) {
consolePrint("Overworld scene initialized");
overworldGroundAdd(&OVERWORLD.ground);
overworldSceneConfigureShaderMaterial(
OVERWORLD.ground.entityId, COLOR_MAGENTA, &PLANE_MESH_SIMPLE
);
overworldPlayerAdd(&OVERWORLD.player);
overworldSceneConfigureSprite(OVERWORLD.player.entityId, COLOR_GREEN, NULL);
vec3 npcPos = { 3.0f, 0.5f, 3.0f };
overworldNpcAdd(&OVERWORLD.npc, npcPos);
overworldSceneConfigureSprite(OVERWORLD.npc.entityId, COLOR_BLUE, NULL);
OVERWORLD.cameraEntityId = entityManagerAdd();
(void)entityAddComponent(OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION);
@@ -48,7 +112,7 @@ void overworldSceneInit(void) {
entityoverworldcamera_t *camData = entityOverworldCameraGet(
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);
camData->scale = 32.0f;
entityInteractableSetCallback(
+45
View File
@@ -8,6 +8,9 @@
#pragma once
#include "error/error.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/overworldground.h"
#include "scene/overworld/overworldnpc.h"
@@ -20,6 +23,48 @@ typedef struct {
overworldnpc_t npc;
} 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);
/**
* Updates the overworld scene each frame.
*
* @return Error state.
*/
errorret_t overworldSceneUpdate(void);
/**
* Disposes the overworld scene, invalidating all entity and component IDs.
*/
void overworldSceneDispose(void);
+6 -57
View File
@@ -10,13 +10,12 @@
#include "time/time.h"
#include "display/screen/screen.h"
#include "entity/entitymanager.h"
#include "entity/component/display/entityrenderable.h"
#include "display/shader/shaderunlit.h"
#include "display/spritebatch/spritebatch.h"
#include "display/screen/screen.h"
#include "display/display.h"
#include "console/console.h"
#include "util/string.h"
#include "ui/ui.h"
#include "scene/scenerenderpipeline.h"
scenefuncs_t SCENE_FUNCTIONS[SCENE_TYPE_COUNT] = {
{ 0 },
@@ -69,63 +68,13 @@ errorret_t sceneUpdate(void) {
}
errorret_t sceneRender(void) {
mat4 proj, view, model, ident;
mat4 proj, view, ident;
glm_mat4_identity(ident);
errorChain(shaderBind(&SHADER_UNLIT));
// 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));
}
}
errorChain(sceneRenderPipeline(entityCameraGetCurrent()));
// UI Rendering
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident));
glm_ortho(
@@ -143,7 +92,7 @@ errorret_t sceneRender(void) {
view
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_BLEND
}));
+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->texture, "frame texture must not be NULL");
errorChain(shaderSetTexture(
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, frame->texture
));
shadermaterial_t material = {
.unlit = {
.color = COLOR_WHITE,
.texture = frame->texture
}
};
spritebatchsprite_t sprites[9];
float_t tileW = (float_t)frame->tileset.tileWidth;
float_t tileH = (float_t)frame->tileset.tileHeight;
vec4 uv;
tilesetPositionGetUV(&frame->tileset, 0, 0, uv);
errorChain(spriteBatchPush(
x, y, x + tileW, y + tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[0].min[0] = x;
sprites[0].min[1] = y;
sprites[0].min[2] = 0.0f;
sprites[0].max[0] = x + tileW;
sprites[0].max[1] = y + tileH;
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);
errorChain(spriteBatchPush(
x + tileW, y, x + width - tileW, y + tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[1].min[0] = x + tileW;
sprites[1].min[1] = y;
sprites[1].min[2] = 0.0f;
sprites[1].max[0] = x + width - tileW;
sprites[1].max[1] = y + tileH;
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);
errorChain(spriteBatchPush(
x + width - tileW, y, x + width, y + tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[2].min[0] = x + width - tileW;
sprites[2].min[1] = y;
sprites[2].min[2] = 0.0f;
sprites[2].max[0] = x + width;
sprites[2].max[1] = y + tileH;
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);
errorChain(spriteBatchPush(
x, y + tileH, x + tileW, y + height - tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[3].min[0] = x;
sprites[3].min[1] = y + tileH;
sprites[3].min[2] = 0.0f;
sprites[3].max[0] = x + tileW;
sprites[3].max[1] = y + height - tileH;
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);
errorChain(spriteBatchPush(
x + tileW, y + tileH, x + width - tileW, y + height - tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[4].min[0] = x + tileW;
sprites[4].min[1] = y + tileH;
sprites[4].min[2] = 0.0f;
sprites[4].max[0] = x + width - tileW;
sprites[4].max[1] = y + height - tileH;
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);
errorChain(spriteBatchPush(
x + width - tileW, y + tileH, x + width, y + height - tileH,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[5].min[0] = x + width - tileW;
sprites[5].min[1] = y + tileH;
sprites[5].min[2] = 0.0f;
sprites[5].max[0] = x + width;
sprites[5].max[1] = y + height - tileH;
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);
errorChain(spriteBatchPush(
x, y + height - tileH, x + tileW, y + height,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[6].min[0] = x;
sprites[6].min[1] = y + height - tileH;
sprites[6].min[2] = 0.0f;
sprites[6].max[0] = x + tileW;
sprites[6].max[1] = y + height;
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);
errorChain(spriteBatchPush(
x + tileW, y + height - tileH, x + width - tileW, y + height,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[7].min[0] = x + tileW;
sprites[7].min[1] = y + height - tileH;
sprites[7].min[2] = 0.0f;
sprites[7].max[0] = x + width - tileW;
sprites[7].max[1] = y + height;
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);
errorChain(spriteBatchPush(
x + width - tileW, y + height - tileH, x + width, y + height,
#if MESH_ENABLE_COLOR
COLOR_WHITE,
#endif
uv[0], uv[1], uv[2], uv[3]
));
sprites[8].min[0] = x + width - tileW;
sprites[8].min[1] = y + height - tileH;
sprites[8].min[2] = 0.0f;
sprites[8].max[0] = x + width;
sprites[8].max[1] = y + height;
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) {
+8 -10
View File
@@ -58,16 +58,14 @@ errorret_t uiFullboxDraw(uifullbox_t *fullbox) {
errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, color));
#endif
errorChain(spriteBatchPush(
0.0f, 0.0f,
(float_t)SCREEN.width, (float_t)SCREEN.height,
#if MESH_ENABLE_COLOR
color,
#endif
0.0f, 0.0f, 1.0f, 1.0f
));
errorChain(spriteBatchFlush());
errorOk();
spritebatchsprite_t sprite;
shadermaterial_t material = {
.unlit = {
.color = color,
.texture = &TEXTURE_WHITE
}
};
return spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material);
}
void uiFullboxTransition(
+12 -18
View File
@@ -12,6 +12,7 @@
#include "event/event.h"
#include "display/screen/screen.h"
#include "display/texture/texture.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
#include "display/shader/shaderunlit.h"
@@ -216,15 +217,12 @@ errorret_t uiTextboxDraw(void) {
));
errorChain(spriteBatchFlush());
errorChain(shaderSetTexture(
&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, UI_TEXTBOX.font->texture
));
#if MESH_ENABLE_COLOR
#else
errorChain(shaderSetColor(
&SHADER_UNLIT, SHADER_UNLIT_COLOR, UI_TEXTBOX.textColor
));
#endif
shadermaterial_t textMaterial = {
.unlit = {
.color = UI_TEXTBOX.textColor,
.texture = UI_TEXTBOX.font->texture
}
};
float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth;
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++) {
uitextboxline_t *line = &UI_TEXTBOX.lines[li];
int32_t visible = line->count < charsLeft ? line->count : charsLeft;
float_t lineY = contentY;
lineY += (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
float_t lineY = contentY + (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
for(int32_t ci = 0; ci < visible; ci++) {
char_t c = UI_TEXTBOX.text[line->start + ci];
if(c == ' ') continue;
errorChain(textDrawChar(
contentX + (float_t)ci * fontW,
lineY,
spritebatchsprite_t sprite = textGetSprite(
(vec2){ contentX + (float_t)ci * fontW, lineY },
c,
#if MESH_ENABLE_COLOR
UI_TEXTBOX.textColor,
#endif
UI_TEXTBOX.font
));
);
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, textMaterial));
}
charsLeft -= visible;