#include "private/rendering.h" #include "context.h" #include "textures.h" #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, */ /* 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_fvec2 uv0c, uv1c, uv2c; struct mesh_batch *batch_p; textures_load(&ctx.texture_cache, path); const SDL_Rect srcrect = textures_get_srcrect(&ctx.texture_cache, path); const int atlas_index = textures_get_atlas_index(&ctx.texture_cache, path); if (atlas_index == -1) { uv0c.x = (float)uv0.x / (float)srcrect.w; uv0c.y = (float)uv0.y / (float)srcrect.h; uv1c.x = (float)uv1.x / (float)srcrect.w; uv1c.y = (float)uv1.y / (float)srcrect.h; uv2c.x = (float)uv2.x / (float)srcrect.w; uv2c.y = (float)uv2.y / (float)srcrect.h; 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 float wr = (float)srcrect.w / TEXTURE_ATLAS_SIZE; const float hr = (float)srcrect.h / TEXTURE_ATLAS_SIZE; const float xr = (float)srcrect.x / TEXTURE_ATLAS_SIZE; const float yr = (float)srcrect.y / TEXTURE_ATLAS_SIZE; uv0c.x = xr + ((float)uv0.x / (float)srcrect.w) * wr; uv0c.y = yr + ((float)uv0.y / (float)srcrect.h) * hr; uv1c.x = xr + ((float)uv1.x / (float)srcrect.w) * wr; uv1c.y = yr + ((float)uv1.y / (float)srcrect.h) * hr; uv2c.x = xr + ((float)uv2.x / (float)srcrect.w) * wr; uv2c.y = yr + ((float)uv2.y / (float)srcrect.h) * hr; 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, .v1 = v1, .uv1 = uv1c, .v2 = v2, .uv2 = uv2c, }; 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); } /* TODO: not that we're SDL free we need to implement batching ourselves */ /* 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 upload_quad_vertices(t_frect rect) { /* client memory needs to be reachable on glDraw*, so */ static float vertices[6 * 2]; vertices[0] = rect.x; vertices[1] = rect.y; vertices[2] = rect.x; vertices[3] = rect.y + rect.h; vertices[4] = rect.x + rect.w; vertices[5] = rect.y + rect.h; vertices[6] = rect.x + rect.w; vertices[7] = rect.y + rect.h; vertices[8] = rect.x + rect.w; vertices[9] = rect.y; vertices[10] = rect.x; vertices[11] = rect.y; glVertexPointer(2, GL_FLOAT, 0, (void *)&vertices); } /* 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); /* loner */ if (sprite->atlas_index == -1) { textures_bind_loner(&ctx.texture_cache, sprite->path, GL_TEXTURE_2D); glTexCoordPointer(2, GL_SHORT, 0, (void *)(int16_t[6 * 2]) { 0, INT16_MAX, 0, 0, INT16_MAX, 0, INT16_MAX, 0, INT16_MAX, INT16_MAX, 0, INT16_MAX }); } else { SDL_Rect srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->path); textures_bind_atlas(&ctx.texture_cache, sprite->atlas_index, GL_TEXTURE_2D); glTexCoordPointer(2, GL_FLOAT, 0, /* TODO: try using shorts */ (void *)(float[6 * 2]) { (float)srcrect.x / TEXTURE_ATLAS_SIZE, (float)srcrect.y / TEXTURE_ATLAS_SIZE, (float)srcrect.x / TEXTURE_ATLAS_SIZE, (float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE, (float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE, (float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE, (float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE, (float)(srcrect.y + srcrect.h) / TEXTURE_ATLAS_SIZE, (float)(srcrect.x + srcrect.w) / TEXTURE_ATLAS_SIZE, (float)srcrect.y / TEXTURE_ATLAS_SIZE, (float)srcrect.x / TEXTURE_ATLAS_SIZE, (float)srcrect.y / TEXTURE_ATLAS_SIZE }); } 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) { glColor4ub(rectangle->color.r, rectangle->color.g, rectangle->color.b, rectangle->color.a); glEnableClientState(GL_VERTEX_ARRAY); upload_quad_vertices(rectangle->rect); glDrawArrays(GL_TRIANGLES, 0, 6); glDisableClientState(GL_VERTEX_ARRAY); } /* 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_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)); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE0); glTexCoordPointer(2, GL_FLOAT, offsetof(struct uncolored_space_triangle, v1), (void *)offsetof(struct uncolored_space_triangle, uv0)); /* 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) { glEnable(GL_TEXTURE_2D); 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) { textures_bind_atlas(&ctx.texture_cache, (int)i, GL_TEXTURE_2D); draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i]); } } for (size_t i = 0; i < shlenu(ctx.uncolored_mesh_batches_loners); ++i) { if (arrlenu(&ctx.uncolored_mesh_batches_loners[i].value.data) > 0) { textures_bind_loner(&ctx.texture_cache, ctx.uncolored_mesh_batches_loners[i].key, GL_TEXTURE_2D); draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches_loners[i].value); } } } void render(void) { if (ctx.texture_cache.is_dirty) textures_update_current_atlas(&ctx.texture_cache); /* 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; int w = (int)((float)RENDER_BASE_WIDTH * ratio); glViewport( ctx.window_w / 2 - w / 2, 0, w, ctx.window_h ); } else { float ratio = (float)ctx.window_w / (float)RENDER_BASE_WIDTH; int h = (int)((float)RENDER_BASE_HEIGHT * ratio); glViewport( 0, ctx.window_h / 2 - h / 2, ctx.window_w, h ); } glEnable(GL_DEPTH_TEST); glEnable(GL_ALPHA_TEST); 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); 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); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_TEXTURE_2D); render_sprites(); render_rectangles(); render_circles(); } { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(60, 1, 0, 0); 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(); } SDL_GL_SwapWindow(ctx.window); }