townengine/src/rendering.c

533 lines
16 KiB
C

#include "private/rendering.h"
#include "context.h"
#include "textures.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <glad/glad.h>
#include <stddef.h>
#include <stdlib.h>
#include <tgmath.h>
void render_queue_clear(void) {
/* since i don't intend to free the queues, */
/* it's faster and simpler to just "start over" */
/* and start overwriting the existing data */
arrsetlen(ctx.render_queue_sprites, 0);
arrsetlen(ctx.render_queue_rectangles, 0);
arrsetlen(ctx.render_queue_circles, 0);
for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i)
arrsetlen(ctx.uncolored_mesh_batches[i].data, 0);
for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i)
arrsetlen(ctx.uncolored_mesh_batches_loners[i].value.data, 0);
}
/*
* 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
*/
/* sprite */
void push_sprite(char *path, t_frect rect) {
textures_load(&ctx.texture_cache, path);
struct sprite_primitive sprite = {
.rect = rect,
.color = (t_color) { 255, 255, 255, 255 },
.path = path,
.rotation = 0.0,
.blend_mode = SDL_BLENDMODE_BLEND,
.atlas_index =
textures_get_atlas_index(&ctx.texture_cache, path),
.layer = 0,
.flip_x = false,
.flip_y = false,
};
arrput(ctx.render_queue_sprites, sprite);
}
void push_sprite_ex(t_frect rect, t_push_sprite_args args) {
textures_load(&ctx.texture_cache, args.path);
struct sprite_primitive sprite = {
.rect = rect,
.color = args.color,
.path = args.path,
.rotation = args.rotation,
.blend_mode = args.blend_mode,
.atlas_index =
textures_get_atlas_index(&ctx.texture_cache, args.path),
.layer = args.layer,
.flip_x = args.flip_x,
.flip_y = args.flip_y,
};
arrput(ctx.render_queue_sprites, sprite);
}
/* rectangle */
void push_rectangle(t_frect rect, t_color color) {
struct rect_primitive rectangle = {
.rect = rect,
.color = color,
};
arrput(ctx.render_queue_rectangles, rectangle);
}
/* circle */
void push_circle(t_fvec2 position, float radius, t_color color) {
struct circle_primitive circle = {
.radius = radius,
.color = color,
.position = position,
};
arrput(ctx.render_queue_circles, circle);
}
/* TODO: automatic handling of repeating textures */
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2)
{
/* corrected atlas texture coordinates */
t_shvec2 uv0c, uv1c, uv2c;
struct mesh_batch *batch_p;
textures_load(&ctx.texture_cache, path);
const int atlas_index = textures_get_atlas_index(&ctx.texture_cache, path);
if (atlas_index == -1) {
/* loners span whole texture i assume */
uv0c = uv0;
uv1c = uv1;
uv2c = uv2;
batch_p = &shgetp(ctx.uncolored_mesh_batches_loners, path)->value;
} else {
const size_t old_len = arrlenu(ctx.uncolored_mesh_batches);
if ((size_t)atlas_index + 1 >= old_len) {
/* grow to accommodate texture cache atlases */
arrsetlen(ctx.uncolored_mesh_batches, atlas_index + 1);
/* zero initialize it all, it's a valid state */
SDL_memset(&ctx.uncolored_mesh_batches[atlas_index],
0,
sizeof (struct mesh_batch) * ((atlas_index + 1) - old_len));
}
const SDL_Rect srcrect = ctx.texture_cache.hash[atlas_index].value.srcrect; /* TODO: does it work? */
/* fixed point galore */
const int16_t srcratx = (int16_t)srcrect.x * (INT16_MAX / TEXTURE_ATLAS_SIZE);
const int16_t srcratw = (int16_t)srcrect.w * (INT16_MAX / TEXTURE_ATLAS_SIZE);
const int16_t srcrath = (int16_t)srcrect.h * (INT16_MAX / TEXTURE_ATLAS_SIZE);
const int16_t srcraty = (int16_t)srcrect.y * (INT16_MAX / TEXTURE_ATLAS_SIZE); /* flip? */
uv0c.x = (int16_t)(srcratx + ((uint16_t)((uv0.x * (INT16_MAX / srcrect.w)) * srcratw) >> 7));
uv0c.y = (int16_t)(srcraty + ((uint16_t)((uv0.y * (INT16_MAX / srcrect.h)) * srcrath) >> 7));
uv1c.x = (int16_t)(srcratx + ((uint16_t)((uv1.x * (INT16_MAX / srcrect.w)) * srcratw) >> 7));
uv1c.y = (int16_t)(srcraty + ((uint16_t)((uv1.y * (INT16_MAX / srcrect.h)) * srcrath) >> 7));
uv2c.x = (int16_t)(srcratx + ((uint16_t)((uv2.x * (INT16_MAX / srcrect.w)) * srcratw) >> 7));
uv2c.y = (int16_t)(srcraty + ((uint16_t)((uv2.y * (INT16_MAX / srcrect.h)) * srcrath) >> 7));
batch_p = &ctx.uncolored_mesh_batches[atlas_index];
}
struct uncolored_space_triangle *data = (struct uncolored_space_triangle *)batch_p->data;
struct uncolored_space_triangle pack = {
.v0 = v0,
// .uv0 = uv0c,
.uv0 = { 0, 0 },
.v1 = v1,
// .uv1 = uv1c,
.uv1 = { INT16_MAX, 0 },
.v2 = v2,
// .uv2 = uv2c,
.uv2 = { INT16_MAX, INT16_MAX },
};
arrpush(data, pack);
batch_p->data = (uint8_t *)data;
}
/* compare functions for the sort in render_sprites */
static int cmp_atlases(const void *a, const void *b) {
int index_a = ((const struct sprite_primitive *)a)->atlas_index;
int index_b = ((const struct sprite_primitive *)b)->atlas_index;
return (index_a > index_b) - (index_a < index_b);
}
static int cmp_blend_modes(const void *a, const void *b) {
SDL_BlendMode mode_a = ((const struct sprite_primitive *)a)->blend_mode;
SDL_BlendMode mode_b = ((const struct sprite_primitive *)b)->blend_mode;
return (mode_a > mode_b) - (mode_a < mode_b);
}
static int cmp_colors(const void *a, const void *b) {
t_color color_a = ((const struct sprite_primitive *)a)->color;
t_color color_b = ((const struct sprite_primitive *)b)->color;
/* check reds */
if (color_a.r < color_b.r)
return -1;
else if (color_a.r > color_b.r)
return 1;
/* reds were equal, check greens */
else if (color_a.g < color_b.g)
return -1;
else if (color_a.g > color_b.g)
return 1;
/* greens were equal, check blues */
else if (color_a.b < color_b.b)
return -1;
else if (color_a.b > color_b.b)
return 1;
/* blues were equal, check alphas */
else if (color_a.a < color_b.a)
return -1;
else if (color_a.a > color_b.a)
return 1;
/* entirely equal */
else
return 0;
}
static int cmp_layers(const void *a, const void *b) {
int layer_a = ((const struct sprite_primitive *)a)->layer;
int layer_b = ((const struct sprite_primitive *)b)->layer;
return (layer_a > layer_b) - (layer_a < layer_b);
}
/* necessary to allow the renderer to draw in batches in the best case */
static void sort_sprites(struct sprite_primitive *sprites) {
size_t sprites_len = arrlenu(sprites);
qsort(sprites, sprites_len, sizeof *sprites, cmp_atlases);
qsort(sprites, sprites_len, sizeof *sprites, cmp_blend_modes);
qsort(sprites, sprites_len, sizeof *sprites, cmp_colors);
qsort(sprites, sprites_len, sizeof *sprites, cmp_layers);
}
static void render_sprite(struct sprite_primitive *sprite) {
SDL_Rect srcrect_value = { 0 };
SDL_Rect *srcrect = &srcrect_value;
SDL_Texture *texture = NULL;
/* loner */
if (sprite->atlas_index == -1) {
srcrect = NULL;
texture = textures_get_loner(&ctx.texture_cache, sprite->path);
} else {
*srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->path);
texture = textures_get_atlas(&ctx.texture_cache, sprite->atlas_index);
}
SDL_RendererFlip flip = SDL_FLIP_NONE;
if (sprite->flip_x)
flip |= SDL_FLIP_HORIZONTAL;
if (sprite->flip_y)
flip |= SDL_FLIP_VERTICAL;
SDL_SetTextureColorMod(texture,
sprite->color.r,
sprite->color.g,
sprite->color.b);
SDL_SetTextureAlphaMod(texture, sprite->color.a);
SDL_SetTextureBlendMode(texture, sprite->blend_mode);
SDL_Rect rect = {
(int)sprite->rect.x,
(int)sprite->rect.y,
(int)sprite->rect.w,
(int)sprite->rect.h
};
SDL_RenderCopyEx(ctx.renderer,
texture,
srcrect,
&rect,
sprite->rotation,
NULL,
flip);
}
static void render_rectangle(struct rect_primitive *rectangle) {
SDL_SetRenderDrawColor(ctx.renderer,
rectangle->color.r,
rectangle->color.g,
rectangle->color.b,
rectangle->color.a);
SDL_Rect rect = {
(int)rectangle->rect.x,
(int)rectangle->rect.y,
(int)rectangle->rect.w,
(int)rectangle->rect.h
};
SDL_RenderFillRect(ctx.renderer, &rect);
SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
}
/* vertices_out and indices_out MUST BE FREED */
static void create_circle_geometry(t_fvec2 position,
t_color color,
float radius,
size_t num_vertices,
SDL_Vertex **vertices_out,
int **indices_out)
{
SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
/* the angle (in radians) to rotate by on each iteration */
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
vertices[0].position.x = (float)position.x;
vertices[0].position.y = (float)position.y;
vertices[0].color.r = color.r;
vertices[0].color.g = color.g;
vertices[0].color.b = color.b;
vertices[0].color.a = color.a;
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
/* this point will rotate around the center */
float start_x = 0.0f - radius;
float start_y = 0.0f;
for (size_t i = 1; i < num_vertices + 1; ++i) {
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
vertices[i].position.x =
cos(final_seg_rotation_angle) * start_x -
sin(final_seg_rotation_angle) * start_y;
vertices[i].position.y =
cos(final_seg_rotation_angle) * start_y +
sin(final_seg_rotation_angle) * start_x;
vertices[i].position.x += position.x;
vertices[i].position.y += position.y;
vertices[i].color.r = color.r;
vertices[i].color.g = color.g;
vertices[i].color.b = color.b;
vertices[i].color.a = color.a;
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
size_t triangle_offset = 3 * (i - 1);
/* center point index */
indices[triangle_offset] = 0;
/* generated point index */
indices[triangle_offset + 1] = (int)i;
size_t index = (i + 1) % num_vertices;
if (index == 0)
index = num_vertices;
indices[triangle_offset + 2] = (int)index;
}
*vertices_out = vertices;
*indices_out = indices;
}
static void render_circle(struct circle_primitive *circle) {
SDL_Vertex *vertices = NULL;
int *indices = NULL;
int num_vertices = (int)circle->radius;
create_circle_geometry(circle->position,
circle->color,
circle->radius,
num_vertices,
&vertices,
&indices);
SDL_RenderGeometry(ctx.renderer, NULL,
vertices,
num_vertices + 1, /* vertices + center vertex */
indices,
num_vertices * 3);
free(vertices);
free(indices);
}
static void render_sprites(void) {
sort_sprites(ctx.render_queue_sprites);
for (size_t i = 0; i < arrlenu(ctx.render_queue_sprites); ++i) {
render_sprite(&ctx.render_queue_sprites[i]);
}
}
static void render_rectangles(void) {
for (size_t i = 0; i < arrlenu(ctx.render_queue_rectangles); ++i) {
render_rectangle(&ctx.render_queue_rectangles[i]);
}
}
static void render_circles(void) {
for (size_t i = 0; i < arrlenu(ctx.render_queue_circles); ++i) {
render_circle(&ctx.render_queue_circles[i]);
}
}
static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch) {
size_t data_len = arrlenu(batch->data);
/* create vertex array object */
if (batch->buffer == 0)
glGenBuffers(1, &batch->buffer);
glBindBuffer(GL_ARRAY_BUFFER, batch->buffer);
/* TODO: try using mapped buffers while building batches instead? */
/* this way we could skip client side copy that is kept until commitment */
/* alternatively we could commit glBufferSubData based on a threshold */
/* upload batched data */
glBufferData(GL_ARRAY_BUFFER,
data_len * sizeof (struct uncolored_space_triangle),
batch->data,
GL_STREAM_DRAW);
/* vertex specification*/
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,
GL_FLOAT,
offsetof(struct uncolored_space_triangle, v1),
(void *)offsetof(struct uncolored_space_triangle, v0));
/* note: propagates further to where texture binding is done */
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_SHORT,
offsetof(struct uncolored_space_triangle, v1),
(void *)offsetof(struct uncolored_space_triangle, uv0));
// for (size_t i = 0; i < data_len; ++i) {
// struct uncolored_space_triangle t = ((struct uncolored_space_triangle *)batch->data)[i];
// log_info("{%i, %i, %i, %i, %i, %i}\n", t.uv0.x, t.uv0.y, t.uv1.x, t.uv1.y, t.uv2.x, t.uv2.y);
// }
/* commit for drawing */
glDrawArrays(GL_TRIANGLES, 0, 3 * (int)data_len);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
/* invalidate the buffer immediately */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
static void render_space(void) {
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1);
glUseProgramObjectARB(0);
glActiveTexture(GL_TEXTURE0);
/* solid white, no modulation */
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
for (size_t i = 0; i < arrlenu(ctx.uncolored_mesh_batches); ++i) {
if (arrlenu(&ctx.uncolored_mesh_batches[i].data) > 0) {
SDL_Texture *const atlas = textures_get_atlas(&ctx.texture_cache, (int)i);
SDL_GL_BindTexture(atlas, NULL, NULL);
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i]);
SDL_GL_UnbindTexture(atlas);
}
}
for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i) {
if (arrlenu(&ctx.uncolored_mesh_batches_loners[i].value.data) > 0) {
SDL_Texture *const atlas = textures_get_loner(&ctx.texture_cache,
ctx.uncolored_mesh_batches_loners[i].key);
SDL_GL_BindTexture(atlas, NULL, NULL);
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches_loners[i].value);
SDL_GL_UnbindTexture(atlas);
}
}
glPopMatrix();
}
void render(void) {
if (ctx.texture_cache.is_dirty)
textures_update_current_atlas(&ctx.texture_cache);
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
// glDisable(GL_CULL_FACE);
// glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
/* TODO: write with no depth test, just fill it in */
render_sprites();
render_rectangles();
render_circles();
// glEnable(GL_CULL_FACE);
// glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
/* TODO: use depth test to optimize gui regions away */
render_space();
SDL_RenderFlush(ctx.renderer);
SDL_GL_SwapWindow(ctx.window);
}