partially done work on total source tree rework, separation of engine context and game context, generalization of renderer for different backends as well as web platform target
This commit is contained in:
205
src/rendering/twn_sprites.c
Normal file
205
src/rendering/twn_sprites.c
Normal file
@ -0,0 +1,205 @@
|
||||
#include "townengine/twn_rendering.h"
|
||||
#include "twn_rendering_c.h"
|
||||
#include "townengine/context.h"
|
||||
#include "townengine/util.h"
|
||||
#include "townengine/textures/internal_api.h"
|
||||
#include "twn_rendering_platform.h"
|
||||
|
||||
#include <stb_ds.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* an implementation note:
|
||||
* try to avoid doing expensive work in the push functions,
|
||||
* because they will be called multiple times in the main loop
|
||||
* before anything is really rendered
|
||||
*/
|
||||
/* TODO: it might make sense to infer alpha channel presence / meaningfulness for textures in atlas */
|
||||
/* so that they are rendered with no blend / batched in a way to reduce overdraw automatically */
|
||||
void push_sprite(const t_push_sprite_args args) {
|
||||
struct sprite_primitive sprite = {
|
||||
.rect = args.rect,
|
||||
.color = m_or(args, color, ((t_color) { 255, 255, 255, 255 })),
|
||||
.rotation = m_or(args, rotation, 0.0f),
|
||||
.texture_key = textures_get_key(&ctx.texture_cache, args.path),
|
||||
.flip_x = m_or(args, flip_x, false),
|
||||
.flip_y = m_or(args, flip_y, false),
|
||||
.repeat = !m_or(args, stretch, true),
|
||||
m_opt_from(texture_origin, args, texture_origin)
|
||||
};
|
||||
|
||||
struct primitive_2d primitive = {
|
||||
.type = PRIMITIVE_2D_SPRITE,
|
||||
.sprite = sprite,
|
||||
};
|
||||
|
||||
arrput(ctx.render_queue_2d, primitive);
|
||||
}
|
||||
|
||||
|
||||
struct sprite_batch collect_sprite_batch(const struct primitive_2d primitives[], size_t len) {
|
||||
/* assumes that first primitive is already a sprite */
|
||||
const uint16_t texture_key_id = primitives[0].sprite.texture_key.id;
|
||||
const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key);
|
||||
|
||||
struct sprite_batch batch = {
|
||||
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
|
||||
.constant_colored = true,
|
||||
.repeat = primitives[0].sprite.repeat,
|
||||
};
|
||||
|
||||
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
|
||||
|
||||
/* batch size is clamped so that reallocated short indices could be used */
|
||||
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
|
||||
len = QUAD_ELEMENT_BUFFER_LENGTH;
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
const struct primitive_2d *const current = &primitives[i];
|
||||
|
||||
/* don't touch things other than sprites */
|
||||
if (current->type != PRIMITIVE_2D_SPRITE)
|
||||
break;
|
||||
|
||||
/* only collect the same blend modes */
|
||||
const enum texture_mode mode = textures_get_mode(&ctx.texture_cache, current->sprite.texture_key);
|
||||
if (mode != batch.mode)
|
||||
break;
|
||||
|
||||
/* only collect the same texture atlases */
|
||||
if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key) != atlas_id)
|
||||
break;
|
||||
|
||||
/* repeated textures require separate handling */
|
||||
if (batch.repeat) {
|
||||
/* all must be repeated */
|
||||
if (!current->sprite.repeat)
|
||||
break;
|
||||
|
||||
/* all must be of same texture id, not just atlas id */
|
||||
if (current->sprite.texture_key.id != texture_key_id)
|
||||
break;
|
||||
}
|
||||
|
||||
/* if all are modulated the same we can skip sending the color data */
|
||||
if (*(const uint32_t *)¤t->sprite.color != uniform_color)
|
||||
batch.constant_colored = false;
|
||||
|
||||
++batch.size;
|
||||
}
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
|
||||
/* assumes that orthogonal matrix setup is done already */
|
||||
void render_sprites(const struct primitive_2d primitives[],
|
||||
const struct sprite_batch batch)
|
||||
{
|
||||
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
||||
static vertex_buffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
|
||||
use_sprite_blendmode(batch.mode);
|
||||
|
||||
const t_frect dims =
|
||||
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||
|
||||
/* vertex population over a vertex buffer builder interface */
|
||||
{
|
||||
vertex_buffer_builder payload = build_vertex_buffer(vertex_array, get_sprite_payload_size(batch) * batch.size);
|
||||
|
||||
for (size_t i = 0; i < batch.size; ++i) {
|
||||
/* render opaques front to back */
|
||||
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
|
||||
const struct sprite_primitive sprite = primitives[cur].sprite;
|
||||
|
||||
const t_frect srcrect =
|
||||
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
|
||||
|
||||
t_fvec2 uv0, uv1, uv2, uv3;
|
||||
|
||||
if (!sprite.repeat) {
|
||||
const float wr = srcrect.w / dims.w;
|
||||
const float hr = srcrect.h / dims.h;
|
||||
const float xr = srcrect.x / dims.w;
|
||||
const float yr = srcrect.y / dims.h;
|
||||
|
||||
uv0 = (t_fvec2){ xr + wr * sprite.flip_x, yr + hr * sprite.flip_y };
|
||||
uv1 = (t_fvec2){ xr + wr * sprite.flip_x, yr + hr * !sprite.flip_y };
|
||||
uv2 = (t_fvec2){ xr + wr * !sprite.flip_x, yr + hr * !sprite.flip_y };
|
||||
uv3 = (t_fvec2){ xr + wr * !sprite.flip_x, yr + hr * sprite.flip_y };
|
||||
|
||||
/* TODO: texture_origin support */
|
||||
|
||||
} else {
|
||||
/* try fitting texture into supplied destination rectangle */
|
||||
|
||||
const float rx = sprite.rect.w / srcrect.w;
|
||||
const float ry = sprite.rect.h / srcrect.h;
|
||||
|
||||
uv0 = (t_fvec2){ rx * sprite.flip_x, ry * sprite.flip_y };
|
||||
uv1 = (t_fvec2){ rx * sprite.flip_x, ry * !sprite.flip_y };
|
||||
uv2 = (t_fvec2){ rx * !sprite.flip_x, ry * !sprite.flip_y };
|
||||
uv3 = (t_fvec2){ rx * !sprite.flip_x, ry * sprite.flip_y };
|
||||
|
||||
if (m_is_set(sprite, texture_origin)) {
|
||||
/* displace origin */
|
||||
|
||||
const float ax = sprite.texture_origin_opt.x / srcrect.w;
|
||||
const float ay = sprite.texture_origin_opt.y / srcrect.h;
|
||||
|
||||
uv0.x += ax; uv1.x += ax; uv2.x += ax; uv3.x += ax;
|
||||
uv0.y += ay; uv1.y += ay; uv2.y += ay; uv3.y += ay;
|
||||
}
|
||||
}
|
||||
|
||||
t_fvec2 v0, v1, v2, v3;
|
||||
|
||||
/* todo: fast PI/2 degree divisible rotations? */
|
||||
if (sprite.rotation == 0.0f) {
|
||||
/* non-rotated case */
|
||||
|
||||
v0 = (t_fvec2){ sprite.rect.x, sprite.rect.y };
|
||||
v1 = (t_fvec2){ sprite.rect.x, sprite.rect.y + sprite.rect.h };
|
||||
v2 = (t_fvec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y + sprite.rect.h };
|
||||
v3 = (t_fvec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y };
|
||||
|
||||
} else if (sprite.rect.w == sprite.rect.h) {
|
||||
/* rotated square case */
|
||||
|
||||
const t_fvec2 c = frect_center(sprite.rect);
|
||||
const t_fvec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
|
||||
const t_fvec2 d = {
|
||||
.x = t.x * sprite.rect.w * (float)M_SQRT1_2,
|
||||
.y = t.y * sprite.rect.h * (float)M_SQRT1_2,
|
||||
};
|
||||
|
||||
v0 = (t_fvec2){ c.x - d.x, c.y - d.y };
|
||||
v1 = (t_fvec2){ c.x - d.y, c.y + d.x };
|
||||
v2 = (t_fvec2){ c.x + d.x, c.y + d.y };
|
||||
v3 = (t_fvec2){ c.x + d.y, c.y - d.x };
|
||||
|
||||
} else {
|
||||
/* rotated non-square case*/
|
||||
|
||||
const t_fvec2 c = frect_center(sprite.rect);
|
||||
const t_fvec2 t = fast_cossine(sprite.rotation);
|
||||
|
||||
const t_fvec2 h = { sprite.rect.w / 2, sprite.rect.h / 2 };
|
||||
|
||||
v0 = (t_fvec2){ c.x + t.x * -h.x - t.y * -h.y, c.y + t.y * -h.x + t.x * -h.y };
|
||||
v1 = (t_fvec2){ c.x + t.x * -h.x - t.y * +h.y, c.y + t.y * -h.x + t.x * +h.y };
|
||||
v2 = (t_fvec2){ c.x + t.x * +h.x - t.y * +h.y, c.y + t.y * +h.x + t.x * +h.y };
|
||||
v3 = (t_fvec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
|
||||
}
|
||||
|
||||
push_sprite_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
|
||||
}
|
||||
}
|
||||
|
||||
finally_render_sprites(primitives, batch, vertex_array);
|
||||
}
|
Reference in New Issue
Block a user