#include "private/rendering.h" #include "context.h" #include "textures.h" #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); } /* * 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); } /* rendering */ static void render_background(void) { SDL_SetRenderDrawColor(ctx.renderer, 230, 230, 230, 255); SDL_Rect rect = { 0, 0, ctx.window_w, ctx.window_h }; SDL_RenderFillRect(ctx.renderer, &rect); SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255); } /* 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) { if (ctx.texture_cache.is_dirty) textures_update_current_atlas(&ctx.texture_cache); 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]); } } void render(void) { SDL_SetRenderDrawBlendMode(ctx.renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(ctx.renderer, 0, 0, 0, 255); SDL_RenderClear(ctx.renderer); SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255); render_background(); render_sprites(); render_rectangles(); render_circles(); SDL_RenderPresent(ctx.renderer); }