From dfde000a3a2252648d08f60812e88ae02eab4acf Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Sat, 27 Jul 2024 15:10:19 +0300 Subject: [PATCH] rendering.c: batching for sprites (blended vs unblended), separation of rendering submodules; textures.c: textures_get_atlas_id() --- src/private/rendering.h | 26 +- src/rendering.c | 386 ++++------------------------ src/rendering.h | 4 +- src/rendering/circles.h | 132 ++++++++++ src/rendering/quad_element_buffer.h | 41 +++ src/rendering/sprites.h | 220 ++++++++++++++++ src/rendering/triangles.h | 117 +++++++++ src/textures.c | 32 ++- src/textures.h | 7 +- 9 files changed, 609 insertions(+), 356 deletions(-) create mode 100644 src/rendering/circles.h create mode 100644 src/rendering/quad_element_buffer.h create mode 100644 src/rendering/sprites.h create mode 100644 src/rendering/triangles.h diff --git a/src/private/rendering.h b/src/private/rendering.h index 73a9cc2..8257a6e 100644 --- a/src/private/rendering.h +++ b/src/private/rendering.h @@ -13,11 +13,33 @@ struct sprite_primitive { t_frect rect; t_color color; - double rotation; - SDL_BlendMode blend_mode; + float rotation; t_texture_key texture_key; bool flip_x; bool flip_y; + bool blend; /* must be explicitly stated, textures having alpha channel isn't enough */ +}; + +/* interleaved vertex array data */ +/* TODO: use int16_t for uvs */ +/* TODO: int16_t could be used for positioning, but we would need to have more CPU calcs */ +struct sprite_primitive_payload { + /* upper-left */ + t_fvec2 v0; + t_fvec2 uv0; + t_color c0; + /* bottom-left */ + t_fvec2 v1; + t_fvec2 uv1; + t_color c1; + /* bottom-right */ + t_fvec2 v2; + t_fvec2 uv2; + t_color c2; + /* upper-right */ + t_fvec2 v3; + t_fvec2 uv3; + t_color c3; }; struct rect_primitive { diff --git a/src/rendering.c b/src/rendering.c index 17d88ae..01d4312 100644 --- a/src/rendering.c +++ b/src/rendering.c @@ -1,16 +1,17 @@ #include "private/rendering.h" +#include "rendering/sprites.h" +#include "rendering/triangles.h" +#include "rendering/circles.h" #include "context.h" #include "textures.h" #include -#include #include +#include #include -#include #include -/* http://www.swiftless.com/opengltuts.html */ void render_queue_clear(void) { /* since i don't intend to free the queues, */ @@ -23,53 +24,6 @@ void render_queue_clear(void) { } -/* - * 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) { - struct sprite_primitive sprite = { - .rect = rect, - .color = (t_color) { 255, 255, 255, 255 }, - .rotation = 0.0, - .blend_mode = SDL_BLENDMODE_BLEND, - .texture_key = textures_get_key(&ctx.texture_cache, path), - .flip_x = false, - .flip_y = false, - }; - - struct primitive_2d primitive = { - .type = PRIMITIVE_2D_SPRITE, - .sprite = sprite, - }; - - arrput(ctx.render_queue_2d, primitive); -} - - -void push_sprite_ex(t_frect rect, t_push_sprite_args args) { - struct sprite_primitive sprite = { - .rect = rect, - .color = args.color, - .rotation = args.rotation, - .blend_mode = args.blend_mode, - .texture_key = textures_get_key(&ctx.texture_cache, args.path), - .flip_x = args.flip_x, - .flip_y = args.flip_y, - }; - - struct primitive_2d primitive = { - .type = PRIMITIVE_2D_SPRITE, - .sprite = sprite, - }; - - arrput(ctx.render_queue_2d, primitive); -} - - /* rectangle */ void push_rectangle(t_frect rect, t_color color) { struct rect_primitive rectangle = { @@ -86,58 +40,6 @@ void push_rectangle(t_frect rect, t_color color) { } -/* circle */ -void push_circle(t_fvec2 position, float radius, t_color color) { - struct circle_primitive circle = { - .radius = radius, - .color = color, - .position = position, - }; - - struct primitive_2d primitive = { - .type = PRIMITIVE_2D_CIRCLE, - .circle = circle, - }; - - arrput(ctx.render_queue_2d, primitive); -} - - -/* TODO: automatic handling of repeating textures */ -/* for that we could allocate a loner texture */ -void unfurl_triangle(const char *path, - t_fvec3 v0, - t_fvec3 v1, - t_fvec3 v2, - t_shvec2 uv0, - t_shvec2 uv1, - t_shvec2 uv2) -{ - const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path); - - struct mesh_batch_item *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key); - if (!batch_p) { - struct mesh_batch item = {0}; - hmput(ctx.uncolored_mesh_batches, texture_key, item); - batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */ - } - - union uncolored_space_triangle triangle = { .primitive = { - .v0 = v0, - .v1 = v1, - .v2 = v2, - .uv1 = m_to_fvec2(uv1), - .uv0 = m_to_fvec2(uv0), - .uv2 = m_to_fvec2(uv2), - }}; - - union uncolored_space_triangle *triangles = - (union uncolored_space_triangle *)batch_p->value.primitives; - arrpush(triangles, triangle); - batch_p->value.primitives = (uint8_t *)triangles; -} - - static void upload_quad_vertices(t_frect rect) { /* client memory needs to be reachable on glDraw*, so */ static float vertices[6 * 2]; @@ -153,51 +55,7 @@ static void upload_quad_vertices(t_frect rect) { } -/* TODO: texture flipping */ -/* assumes that orthogonal matrix setup is done already */ -static void render_sprite(struct sprite_primitive *sprite) { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glClientActiveTexture(GL_TEXTURE0); - - textures_bind(&ctx.texture_cache, sprite->texture_key, GL_TEXTURE_2D); - - t_rect srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->texture_key); - t_rect dims = textures_get_dims(&ctx.texture_cache, sprite->texture_key); - - glTexCoordPointer(2, - GL_FLOAT, - 0, - /* TODO: try using shorts */ - (void *)(float[6 * 2]) { - (float)srcrect.x / (float)dims.w, - (float)srcrect.y / (float)dims.h, - (float)srcrect.x / (float)dims.w, - (float)(srcrect.y + srcrect.h) / (float)dims.h, - (float)(srcrect.x + srcrect.w) / (float)dims.w, - (float)(srcrect.y + srcrect.h) / (float)dims.h, - (float)(srcrect.x + srcrect.w) / (float)dims.w, - (float)(srcrect.y + srcrect.h) / (float)dims.h, - (float)(srcrect.x + srcrect.w) / (float)dims.w, - (float)srcrect.y / (float)dims.h, - (float)srcrect.x / (float)dims.w, - (float)srcrect.y / (float)dims.h }); - - glColor4ub(sprite->color.r, sprite->color.g, sprite->color.b, sprite->color.a); - - // SDL_SetTextureBlendMode(texture, sprite->blend_mode); - - glEnableClientState(GL_VERTEX_ARRAY); - upload_quad_vertices(sprite->rect); - - glDrawArrays(GL_TRIANGLES, 0, 6); - - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - glBindTexture(GL_TEXTURE_2D, 0); -} - - -static void render_rectangle(struct rect_primitive *rectangle) { +static void render_rectangle(const struct rect_primitive *rectangle) { glColor4ub(rectangle->color.r, rectangle->color.g, rectangle->color.b, rectangle->color.a); @@ -209,116 +67,42 @@ static void render_rectangle(struct rect_primitive *rectangle) { } -/* 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); - - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, - GL_FLOAT, - sizeof (SDL_Vertex), - &vertices->position); - - glEnableClientState(GL_COLOR_ARRAY); - glColorPointer(4, - GL_UNSIGNED_BYTE, - sizeof (SDL_Vertex), - &vertices->color); - - glDrawElements(GL_TRIANGLES, - num_vertices * 3, - GL_UNSIGNED_INT, - indices); - - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - - free(vertices); - free(indices); -} - - static void render_2d(void) { - for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) { - struct primitive_2d *current = &ctx.render_queue_2d[i]; + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glDisable(GL_CULL_FACE); + glDepthFunc(GL_LESS); + + const size_t render_queue_len = arrlenu(ctx.render_queue_2d); + + size_t batch_count = 0; + + for (size_t i = 0; i < render_queue_len; ++i) { + const struct primitive_2d *current = &ctx.render_queue_2d[i]; switch (current->type) { - case PRIMITIVE_2D_SPRITE: - render_sprite(¤t->sprite); + case PRIMITIVE_2D_SPRITE: { + const struct sprite_batch batch = + collect_sprite_batch(current, render_queue_len - i); + + if (batch.blend) { + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + render_sprites(current, batch.size, false); + } else { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_DEPTH_TEST); + glDepthRange((double)batch_count / 65536, 1.0); + glDisable(GL_BLEND); + + render_sprites(current, batch.size, true); + } + + i += batch.size - 1; ++batch_count; break; + } case PRIMITIVE_2D_RECT: render_rectangle(¤t->rect); break; @@ -330,80 +114,13 @@ static void render_2d(void) { } -static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch, - t_texture_key texture_key) -{ - size_t primitives_len = arrlenu(batch->primitives); - - if (primitives_len == 0) - return; - - /* create vertex array object */ - if (batch->buffer == 0) - glGenBuffers(1, &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 */ - - /* update pixel-based uvs to correspond with texture atlases */ - for (size_t i = 0; i < primitives_len; ++i) { - struct uncolored_space_triangle_payload *payload = - &((union uncolored_space_triangle *)batch->primitives)[i].payload; - - t_rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key); - t_rect dims = textures_get_dims(&ctx.texture_cache, texture_key); - - const float wr = (float)srcrect.w / (float)dims.w; - const float hr = (float)srcrect.h / (float)dims.h; - const float xr = (float)srcrect.x / (float)dims.w; - const float yr = (float)srcrect.y / (float)dims.h; - - payload->uv0.x = xr + ((float)payload->uv0.x / (float)srcrect.w) * wr; - payload->uv0.y = yr + ((float)payload->uv0.y / (float)srcrect.h) * hr; - payload->uv1.x = xr + ((float)payload->uv1.x / (float)srcrect.w) * wr; - payload->uv1.y = yr + ((float)payload->uv1.y / (float)srcrect.h) * hr; - payload->uv2.x = xr + ((float)payload->uv2.x / (float)srcrect.w) * wr; - payload->uv2.y = yr + ((float)payload->uv2.y / (float)srcrect.h) * hr; - } - - textures_bind(&ctx.texture_cache, texture_key, GL_TEXTURE_2D); - - glBindBuffer(GL_ARRAY_BUFFER, batch->buffer); - - /* upload batched data */ - glBufferData(GL_ARRAY_BUFFER, - primitives_len * sizeof (struct uncolored_space_triangle_payload), - batch->primitives, - GL_STREAM_DRAW); - - /* vertex specification*/ - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(3, - GL_FLOAT, - offsetof(struct uncolored_space_triangle_payload, v1), - (void *)offsetof(struct uncolored_space_triangle_payload, v0)); - - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glClientActiveTexture(GL_TEXTURE0); - glTexCoordPointer(2, - GL_FLOAT, - offsetof(struct uncolored_space_triangle_payload, v1), - (void *)offsetof(struct uncolored_space_triangle_payload, uv0)); - - /* commit for drawing */ - glDrawArrays(GL_TRIANGLES, 0, 3 * (int)primitives_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) { + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glDisable(GL_ALPHA_TEST); + glDepthFunc(GL_LESS); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); @@ -420,6 +137,7 @@ static void render_space(void) { void render(void) { textures_update_atlas(&ctx.texture_cache); + /* TODO: only do this when needed */ /* fit rendering context onto the resizable screen */ if ((float)ctx.window_w / (float)ctx.window_h > RENDER_BASE_RATIO) { float ratio = (float)ctx.window_h / (float)RENDER_BASE_HEIGHT; @@ -441,9 +159,6 @@ void render(void) { ); } - glEnable(GL_DEPTH_TEST); - glEnable(GL_ALPHA_TEST); - glClearColor((1.0f / 255) * 230, (1.0f / 255) * 230, (1.0f / 255) * 230, 1); @@ -453,11 +168,6 @@ void render(void) { GL_STENCIL_BUFFER_BIT); { - glDisable(GL_CULL_FACE); - glDepthFunc(GL_ALWAYS); /* fill depth buffer with ones, 2d view is always in front */ - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1); @@ -465,8 +175,6 @@ void render(void) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glEnable(GL_TEXTURE_2D); - render_2d(); } @@ -477,12 +185,6 @@ void render(void) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glEnable(GL_CULL_FACE); - glDepthFunc(GL_LESS); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - /* TODO: use depth test to optimize gui regions away */ render_space(); } diff --git a/src/rendering.h b/src/rendering.h index 099e595..7e48cd4 100644 --- a/src/rendering.h +++ b/src/rendering.h @@ -10,10 +10,10 @@ typedef struct push_sprite_args { char *path; t_color color; - double rotation; - SDL_BlendMode blend_mode; + float rotation; bool flip_x; bool flip_y; + bool blend; } t_push_sprite_args; /* clears all render queues */ diff --git a/src/rendering/circles.h b/src/rendering/circles.h new file mode 100644 index 0000000..d7d62c9 --- /dev/null +++ b/src/rendering/circles.h @@ -0,0 +1,132 @@ +/* a rendering.c mixin */ +#ifndef CIRCLES_H +#define CIRCLES_H + +#include "../util.h" +#include "../private/rendering.h" +#include "../context.h" + +#include +#include + +#include + + +void push_circle(t_fvec2 position, float radius, t_color color) { + struct circle_primitive circle = { + .radius = radius, + .color = color, + .position = position, + }; + + struct primitive_2d primitive = { + .type = PRIMITIVE_2D_CIRCLE, + .circle = circle, + }; + + arrput(ctx.render_queue_2d, primitive); +} + +/* TODO: caching and reuse scheme */ +/* 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 = + cosf(final_seg_rotation_angle) * start_x - + sinf(final_seg_rotation_angle) * start_y; + vertices[i].position.y = + cosf(final_seg_rotation_angle) * start_y + + sinf(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(const 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); + + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, + GL_FLOAT, + sizeof (SDL_Vertex), + &vertices->position); + + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, + GL_UNSIGNED_BYTE, + sizeof (SDL_Vertex), + &vertices->color); + + glDrawElements(GL_TRIANGLES, + num_vertices * 3, + GL_UNSIGNED_INT, + indices); + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + free(vertices); + free(indices); +} + +#endif diff --git a/src/rendering/quad_element_buffer.h b/src/rendering/quad_element_buffer.h new file mode 100644 index 0000000..4f832ee --- /dev/null +++ b/src/rendering/quad_element_buffer.h @@ -0,0 +1,41 @@ +/* a rendering.c mixin */ +#ifndef QUAD_ELEMENT_BUFFER_H +#define QUAD_ELEMENT_BUFFER_H + +#include + +#include + +#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6) + +static void bind_quad_element_buffer(void) { + static GLuint buffer = 0; + + /* it's only generated once at runtime */ + if (buffer == 0) { + glGenBuffers(1, &buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + QUAD_ELEMENT_BUFFER_LENGTH * 6, + NULL, + GL_STATIC_DRAW); + + uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, + GL_WRITE_ONLY); + + for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) { + indices[i * 6 + 0] = (uint16_t)(i * 4 + 0); + indices[i * 6 + 1] = (uint16_t)(i * 4 + 1); + indices[i * 6 + 2] = (uint16_t)(i * 4 + 2); + indices[i * 6 + 3] = (uint16_t)(i * 4 + 2); + indices[i * 6 + 4] = (uint16_t)(i * 4 + 3); + indices[i * 6 + 5] = (uint16_t)(i * 4 + 0); + } + + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + + } else + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); +} + +#endif diff --git a/src/rendering/sprites.h b/src/rendering/sprites.h new file mode 100644 index 0000000..3340950 --- /dev/null +++ b/src/rendering/sprites.h @@ -0,0 +1,220 @@ +/* a rendering.c mixin */ +#ifndef SPRITES_H +#define SPRITES_H + +#include "../textures.h" +#include "../rendering.h" +#include "../context.h" +#include "quad_element_buffer.h" + +#include + +#include +#include + +/* + * 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) { + struct sprite_primitive sprite = { + .rect = rect, + .color = (t_color) { 255, 255, 255, 255 }, + .rotation = 0.0, + .texture_key = textures_get_key(&ctx.texture_cache, path), + .flip_x = false, + .flip_y = false, + .blend = true, + }; + + struct primitive_2d primitive = { + .type = PRIMITIVE_2D_SPRITE, + .sprite = sprite, + }; + + arrput(ctx.render_queue_2d, primitive); +} + + +void push_sprite_ex(t_frect rect, t_push_sprite_args args) { + struct sprite_primitive sprite = { + .rect = rect, + .color = args.color, + .rotation = args.rotation, + .texture_key = textures_get_key(&ctx.texture_cache, args.path), + .flip_x = args.flip_x, + .flip_y = args.flip_y, + .blend = args.blend, + }; + + struct primitive_2d primitive = { + .type = PRIMITIVE_2D_SPRITE, + .sprite = sprite, + }; + + arrput(ctx.render_queue_2d, primitive); +} + + +static struct sprite_batch { + int atlas_id; + size_t size; /* how many primitives are in current batch */ + bool blend; /* whether it's blended or not */ +} collect_sprite_batch(const struct primitive_2d *primitives, size_t len) { + /* assumes that first primitive is already a sprite */ + struct sprite_batch result = { + .atlas_id = + textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key), + .blend = primitives[0].sprite.blend, + }; + + /* 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 */ + if (current->sprite.blend != result.blend) + break; + + /* only collect the same texture atlases */ + if (textures_get_atlas_id(&ctx.texture_cache, current->sprite.texture_key) + != result.atlas_id) + break; + + ++result.size; + } + + return result; +} + + +/* TODO: texture flipping */ +/* assumes that orthogonal matrix setup is done already */ +static void render_sprites(const struct primitive_2d primitives[], + const size_t len, + const bool reversed) +{ + /* single vertex array is used for every batch with NULL glBufferData() trick at the end */ + static GLuint vertex_array = 0; + if (vertex_array == 0) + glGenBuffers(1, &vertex_array); + + glBindBuffer(GL_ARRAY_BUFFER, vertex_array); + glBufferData(GL_ARRAY_BUFFER, + sizeof (struct sprite_primitive_payload) * len, + NULL, + GL_STREAM_DRAW); + + const t_rect srcrect = + textures_get_srcrect(&ctx.texture_cache, primitives->sprite.texture_key); + const t_rect dims = + textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key); + + /* vertex population over a mapped buffer */ + { + /* TODO: check errors, ensure alignment ? */ + struct sprite_primitive_payload *const payload = + glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); + + for (size_t i = 0; i < len; ++i) { + const size_t cur = reversed ? len - i - 1: i; + const struct sprite_primitive sprite = primitives[cur].sprite; + + const float wr = (float)srcrect.w / (float)dims.w; + const float hr = (float)srcrect.h / (float)dims.h; + const float xr = (float)srcrect.x / (float)dims.w; + const float yr = (float)srcrect.y / (float)dims.h; + + payload[i] = (struct sprite_primitive_payload) { + /* upper-left */ + .v0 = { + sprite.rect.x, + sprite.rect.y }, + .uv0 = { + xr, + yr, }, + + /* bottom-left */ + .v1 = { + (sprite.rect.x), + (sprite.rect.y + sprite.rect.h) }, + .uv1 = { + xr, + yr + hr, }, + + /* bottom-right */ + .v2 = { + (sprite.rect.x + sprite.rect.w), + (sprite.rect.y + sprite.rect.h) }, + .uv2 = { + xr + wr, + yr + hr, }, + + /* upper-right */ + .v3 = { + (sprite.rect.x + sprite.rect.w), + (sprite.rect.y) }, + .uv3 = { + xr + wr, + yr, }, + + /* equal for all (flat shaded) */ + .c0 = sprite.color, + .c1 = sprite.color, + .c2 = sprite.color, + .c3 = sprite.color, + }; + } + + glUnmapBuffer(GL_ARRAY_BUFFER); + } + + /* vertex specification */ + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, + GL_FLOAT, + offsetof(struct sprite_primitive_payload, v1), + (void *)offsetof(struct sprite_primitive_payload, v0)); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTexture(GL_TEXTURE0); + glTexCoordPointer(2, + GL_FLOAT, + offsetof(struct sprite_primitive_payload, v1), + (void *)offsetof(struct sprite_primitive_payload, uv0)); + + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, + GL_UNSIGNED_BYTE, + offsetof(struct sprite_primitive_payload, v1), + (void *)offsetof(struct sprite_primitive_payload, c0)); + + textures_bind(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D); + + bind_quad_element_buffer(); + + glDrawElements(GL_TRIANGLES, 6 * (GLsizei)len, GL_UNSIGNED_SHORT, NULL); + + /* clear the state */ + glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +#endif diff --git a/src/rendering/triangles.h b/src/rendering/triangles.h new file mode 100644 index 0000000..8e88b11 --- /dev/null +++ b/src/rendering/triangles.h @@ -0,0 +1,117 @@ +/* a rendering.c mixin */ +#ifndef TRIANGLES_H +#define TRIANGLES_H + +#include "../textures.h" +#include "../context.h" + +#include + +/* TODO: automatic handling of repeating textures */ +/* for that we could allocate a loner texture */ +void unfurl_triangle(const char *path, + t_fvec3 v0, + t_fvec3 v1, + t_fvec3 v2, + t_shvec2 uv0, + t_shvec2 uv1, + t_shvec2 uv2) +{ + const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path); + + struct mesh_batch_item *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key); + if (!batch_p) { + struct mesh_batch item = {0}; + hmput(ctx.uncolored_mesh_batches, texture_key, item); + batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */ + } + + union uncolored_space_triangle triangle = { .primitive = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .uv1 = m_to_fvec2(uv1), + .uv0 = m_to_fvec2(uv0), + .uv2 = m_to_fvec2(uv2), + }}; + + union uncolored_space_triangle *triangles = + (union uncolored_space_triangle *)batch_p->value.primitives; + arrpush(triangles, triangle); + batch_p->value.primitives = (uint8_t *)triangles; +} + + +static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch, + t_texture_key texture_key) +{ + size_t primitives_len = arrlenu(batch->primitives); + + if (primitives_len == 0) + return; + + /* create vertex array object */ + if (batch->buffer == 0) + glGenBuffers(1, &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 */ + + /* update pixel-based uvs to correspond with texture atlases */ + for (size_t i = 0; i < primitives_len; ++i) { + struct uncolored_space_triangle_payload *payload = + &((union uncolored_space_triangle *)batch->primitives)[i].payload; + + t_rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key); + t_rect dims = textures_get_dims(&ctx.texture_cache, texture_key); + + const float wr = (float)srcrect.w / (float)dims.w; + const float hr = (float)srcrect.h / (float)dims.h; + const float xr = (float)srcrect.x / (float)dims.w; + const float yr = (float)srcrect.y / (float)dims.h; + + payload->uv0.x = xr + ((float)payload->uv0.x / (float)srcrect.w) * wr; + payload->uv0.y = yr + ((float)payload->uv0.y / (float)srcrect.h) * hr; + payload->uv1.x = xr + ((float)payload->uv1.x / (float)srcrect.w) * wr; + payload->uv1.y = yr + ((float)payload->uv1.y / (float)srcrect.h) * hr; + payload->uv2.x = xr + ((float)payload->uv2.x / (float)srcrect.w) * wr; + payload->uv2.y = yr + ((float)payload->uv2.y / (float)srcrect.h) * hr; + } + + textures_bind(&ctx.texture_cache, texture_key, GL_TEXTURE_2D); + + glBindBuffer(GL_ARRAY_BUFFER, batch->buffer); + + /* upload batched data */ + glBufferData(GL_ARRAY_BUFFER, + primitives_len * sizeof (struct uncolored_space_triangle_payload), + batch->primitives, + GL_STREAM_DRAW); + + /* vertex specification*/ + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, + GL_FLOAT, + offsetof(struct uncolored_space_triangle_payload, v1), + (void *)offsetof(struct uncolored_space_triangle_payload, v0)); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glClientActiveTexture(GL_TEXTURE0); + glTexCoordPointer(2, + GL_FLOAT, + offsetof(struct uncolored_space_triangle_payload, v1), + (void *)offsetof(struct uncolored_space_triangle_payload, uv0)); + + /* commit for drawing */ + glDrawArrays(GL_TRIANGLES, 0, 3 * (int)primitives_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); +} + +#endif diff --git a/src/textures.c b/src/textures.c index bd766e3..d912581 100644 --- a/src/textures.c +++ b/src/textures.c @@ -307,12 +307,12 @@ static t_texture_key textures_load(struct texture_cache *cache, const char *path upload_texture_from_surface(new_texture.loner_texture, surface); new_texture.srcrect = (t_rect) { .w = surface->w, .h = surface->h }; shput(cache->hash, path, new_texture); - return (t_texture_key){ (int)shgeti(cache->hash, path) }; + return (t_texture_key){ shgeti(cache->hash, path) }; } else { new_texture.atlas_index = cache->atlas_index; shput(cache->hash, path, new_texture); cache->is_dirty = true; - return (t_texture_key){ (int)shgeti(cache->hash, path) }; + return (t_texture_key){ shgeti(cache->hash, path) }; } } @@ -357,7 +357,7 @@ void textures_update_atlas(struct texture_cache *cache) { recreate_current_atlas_texture(cache); cache->is_dirty = false; - + arrfree(rects); } @@ -371,24 +371,27 @@ extern const char stop_rodata_heuristic[]; asm(".set start_rodata_address, .rodata"); asm(".set stop_rodata_heuristic, .data"); /* there's nothing in default linker script to know the size of .rodata */ +/* TODO: it might be better to contruct a new table that hashes pointers, not strings, to texture keys */ +/* this way every used texture will benefit, no matter the order of commission */ t_texture_key textures_get_key(struct texture_cache *cache, const char *path) { static const char *last_path = NULL; static t_texture_key last_texture; /* fast path */ - if (path == last_path && path >= start_rodata_address && path < stop_rodata_heuristic) + if (path == last_path) return last_texture; /* hash tables are assumed to be stable, so we just return indices */ - int texture = (int)shgeti(cache->hash, path); + ptrdiff_t texture = shgeti(cache->hash, path); /* load it if it isn't */ if (texture == -1) { last_texture = textures_load(cache, path); } else - last_texture = (t_texture_key){ texture }; + last_texture = (t_texture_key){ (uint16_t)texture }; - last_path = path; + if (path >= start_rodata_address && path < stop_rodata_heuristic) + last_path = path; return last_texture; } @@ -402,11 +405,24 @@ t_texture_key textures_get_key(struct texture_cache *cache, const char *path) { if (texture == -1) { return textures_load(cache, path); } else - return (t_texture_key){ (int)texture }; + return (t_texture_key){ (uint16_t)texture }; } #endif /* generic implementation of textures_get_key() */ +int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key) { + if (m_texture_key_is_valid(key)) { + if (cache->hash[key.id].value.loner_texture != 0) + return -cache->hash[key.id].value.loner_texture; + else + return cache->hash[key.id].value.atlas_index; + } else { + CRY("Texture lookup failed.", + "Tried to get atlas id that isn't loaded."); + return 0; + } +} + t_rect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key) { if (m_texture_key_is_valid(key)) { return cache->hash[key.id].value.srcrect; diff --git a/src/textures.h b/src/textures.h index 411d13f..bf9bf8d 100644 --- a/src/textures.h +++ b/src/textures.h @@ -8,10 +8,10 @@ #include /* type safe structure for persistent texture handles */ -typedef struct { int id; } t_texture_key; +typedef struct { uint16_t id; } t_texture_key; /* tests whether given key structure corresponds to any texture */ -#define m_texture_key_is_valid(p_key) ((p_key).id != -1) +#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1) void textures_cache_init(struct texture_cache *cache, SDL_Window *window); void textures_cache_deinit(struct texture_cache *cache); @@ -39,6 +39,9 @@ t_rect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key /* returns a rect of dimensions of the whole texture (whole atlas) */ t_rect textures_get_dims(const struct texture_cache *cache, t_texture_key key); +/* returns an identifier that is equal for all textures placed in the same atlas */ +int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key); + /* binds atlas texture in opengl state */ void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target);