#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_2d, 0); for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 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) { 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 = { .rect = rect, .color = color, }; struct primitive_2d primitive = { .type = PRIMITIVE_2D_RECT, .rect = rectangle, }; arrput(ctx.render_queue_2d, primitive); } /* 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]; 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); 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) { 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_2d(void) { for (size_t i = 0; i < arrlenu(ctx.render_queue_2d); ++i) { struct primitive_2d *current = &ctx.render_queue_2d[i]; switch (current->type) { case PRIMITIVE_2D_SPRITE: render_sprite(¤t->sprite); break; case PRIMITIVE_2D_RECT: render_rectangle(¤t->rect); break; case PRIMITIVE_2D_CIRCLE: render_circle(¤t->circle); break; } } } 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_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); /* solid white, no modulation */ glColor4f(1.0f, 1.0f, 1.0f, 1.0f); for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) { draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value, ctx.uncolored_mesh_batches[i].key); } } void render(void) { textures_update_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_2d(); } { glMatrixMode(GL_PROJECTION); glLoadIdentity(); 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(); } SDL_GL_SwapWindow(ctx.window); }