#include "private/rendering.h" #include "context.h" #include "textures.h" #include #include #include #include #include #include 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); }