#include "twn_draw_c.h" #include "twn_util.h" #include "twn_util_c.h" #include "twn_engine_context_c.h" #include "twn_text_c.h" #include "twn_types.h" #include #include /* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */ /* this is only doable is we take out color attribute to separate array or a portion of it */ /* interleaved vertex array data */ typedef struct ElementIndexedQuad { /* upper-left */ Vec2 v0; Vec2 uv0; Color c0; /* bottom-left */ Vec2 v1; Vec2 uv1; Color c1; /* bottom-right */ Vec2 v2; Vec2 uv2; Color c2; /* upper-right */ Vec2 v3; Vec2 uv3; Color c3; } ElementIndexedQuad; typedef struct ElementIndexedQuadWithoutColor { /* upper-left */ Vec2 v0; Vec2 uv0; /* bottom-left */ Vec2 v1; Vec2 uv1; /* bottom-right */ Vec2 v2; Vec2 uv2; /* upper-right */ Vec2 v3; Vec2 uv3; } ElementIndexedQuadWithoutColor; typedef struct ElementIndexedQuadWithoutTexture { /* upper-left */ Vec2 v0; Color c0; /* bottom-left */ Vec2 v1; Color c1; /* bottom-right */ Vec2 v2; Color c2; /* upper-right */ Vec2 v3; Color c3; } ElementIndexedQuadWithoutTexture; typedef struct ElementIndexedQuadWithoutColorWithoutTexture { /* upper-left */ Vec2 v0; /* bottom-left */ Vec2 v1; /* bottom-right */ Vec2 v2; /* upper-right */ Vec2 v3; } ElementIndexedQuadWithoutColorWithoutTexture; typedef struct { size_t offset; GLenum type; GLsizei stride; GLuint buffer; uint8_t arity; /* leave at 0 to signal pointer as unused */ } AttributeArrayPointer; /* allows us to have generic way to issue draws as well as */ /* deferring new draw calls while previous frame is still being drawn */ typedef struct { AttributeArrayPointer vertices; AttributeArrayPointer texcoords; bool constant_colored; union { AttributeArrayPointer colors; Color color; }; bool textured, texture_repeat, uses_gpu_key; TextureKey texture_key; GPUTexture gpu_texture; GLuint element_buffer; GLsizei element_count; GLsizei range_start, range_end; /* could be either `element_count` with supplied `element_buffer`, or this, but not both */ GLsizei primitive_count; double depth_range_low, depth_range_high; } DeferredCommandDraw; typedef struct { char *paths; } DeferredCommandDrawSkybox; typedef struct { Color color; bool clear_color; bool clear_depth; bool clear_stencil; } DeferredCommandClear; typedef enum { PIPELINE_NO, PIPELINE_SPACE, PIPELINE_2D, } Pipeline; typedef struct { Pipeline pipeline; } DeferredCommandUsePipeline; typedef struct { TextureMode mode; } DeferredCommandUseTextureMode; typedef struct { double low, high; } DeferredCommandDepthRange; typedef struct { float start, end, density; Color color; } DeferredCommandApplyFog; typedef struct { enum DeferredCommandType { DEFERRED_COMMAND_TYPE_DRAW, DEFERRED_COMMAND_TYPE_DRAW_SKYBOX, DEFERRED_COMMAND_TYPE_CLEAR, DEFERRED_COMMAND_TYPE_USE_PIPIELINE, DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE, DEFERRED_COMMAND_TYPE_DEPTH_RANGE, DEFERRED_COMMAND_TYPE_APPLY_FOG, DEFERRED_COMMAND_TYPE_POP_FOG, } type; union { DeferredCommandDraw draw; DeferredCommandDrawSkybox draw_skybox; DeferredCommandClear clear; DeferredCommandUsePipeline use_pipeline; DeferredCommandUseTextureMode use_texture_mode; DeferredCommandDepthRange depth_range; DeferredCommandApplyFog apply_fog; }; } DeferredCommand; static TextureMode texture_mode_last_used = TEXTURE_MODE_UNKNOWN; static Pipeline pipeline_last_used = PIPELINE_NO; /* potentially double buffered array of vertex array handles */ /* we assume they will be refilled fully each frame */ static size_t scratch_va_front_used, scratch_va_back_used; static GLuint *front_scratch_vertex_arrays, *back_scratch_vertex_arrays; static GLuint **current_scratch_vertex_array = &front_scratch_vertex_arrays; static void restart_scratch_vertex_arrays(void) { scratch_va_front_used = 0; scratch_va_back_used = 0; if (ctx.render_double_buffered) { current_scratch_vertex_array = current_scratch_vertex_array == &front_scratch_vertex_arrays ? &back_scratch_vertex_arrays : &front_scratch_vertex_arrays; } } GLuint get_scratch_vertex_array(void) { size_t *used = current_scratch_vertex_array == &front_scratch_vertex_arrays ? &scratch_va_front_used : &scratch_va_back_used; if (arrlenu(*current_scratch_vertex_array) <= *used) { GLuint handle; glGenBuffers(1, &handle); arrpush(*current_scratch_vertex_array, handle); } (*used)++; return (*current_scratch_vertex_array)[*used - 1]; } static void finally_use_2d_pipeline(void); static void finally_use_space_pipeline(void); static void finally_use_texture_mode(TextureMode mode); static void deferred_render_skybox(char *paths); static void deferred_apply_fog(float start, float end, float density, Color color); static void deferred_pop_fog(void); static DeferredCommand *deferred_commands; static void issue_deferred_draw_commands(void) { for (size_t i = 0; i < arrlenu(deferred_commands); ++i) { switch (deferred_commands[i].type) { case DEFERRED_COMMAND_TYPE_DEPTH_RANGE: { glDepthRange(deferred_commands[i].depth_range.low, deferred_commands[i].depth_range.high); break; } case DEFERRED_COMMAND_TYPE_CLEAR: { glClearColor((1.0f / 255) * deferred_commands[i].clear.color.r, (1.0f / 255) * deferred_commands[i].clear.color.g, (1.0f / 255) * deferred_commands[i].clear.color.b, (1.0f / 255) * deferred_commands[i].clear.color.a); /* needed as we might mess with it */ glDepthRange(0.0, 1.0); glDepthMask(GL_TRUE); glClear((deferred_commands[i].clear.clear_color ? GL_COLOR_BUFFER_BIT : 0) | (deferred_commands[i].clear.clear_depth ? GL_DEPTH_BUFFER_BIT : 0) | (deferred_commands[i].clear.clear_stencil ? GL_STENCIL_BUFFER_BIT : 0) ); break; } case DEFERRED_COMMAND_TYPE_DRAW: { DeferredCommandDraw const command = deferred_commands[i].draw; /* TODO: don't assume a single vertex array ? */ SDL_assert(command.vertices.arity != 0); SDL_assert(command.vertices.buffer); SDL_assert((command.element_buffer && command.element_count != 0) || command.primitive_count != 0); glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, command.element_buffer); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(command.vertices.arity, command.vertices.type, command.vertices.stride, (void *)command.vertices.offset); if (command.texcoords.arity != 0) { SDL_assert(command.texcoords.buffer == command.vertices.buffer); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE0); glTexCoordPointer(command.texcoords.arity, command.texcoords.type, command.texcoords.stride, (void *)command.texcoords.offset); } if (command.colors.arity != 0) { SDL_assert(command.colors.buffer == command.vertices.buffer); glEnableClientState(GL_COLOR_ARRAY); glColorPointer(command.colors.arity, command.colors.type, command.colors.stride, (void *)command.colors.offset); } else if (command.constant_colored) glColor4ub(command.color.r, command.color.g, command.color.b, command.color.a); if (command.textured) { if (command.uses_gpu_key) glBindTexture(GL_TEXTURE_2D, command.gpu_texture); else if (command.texture_repeat) textures_bind_repeating(&ctx.texture_cache, command.texture_key); else textures_bind(&ctx.texture_cache, command.texture_key); } if (command.element_buffer) { SDL_assert(command.element_count != 0); if (command.range_start == command.range_end) glDrawElements(GL_TRIANGLES, command.element_count, GL_UNSIGNED_SHORT, NULL); else glDrawRangeElements(GL_TRIANGLES, command.range_start, command.range_end, command.element_count, GL_UNSIGNED_SHORT, NULL); } else { SDL_assert(command.primitive_count != 0); glDrawArrays(GL_TRIANGLES, 0, command.primitive_count); } /* state clearing */ if (command.textured) glBindTexture(GL_TEXTURE_2D, 0); if (command.colors.arity != 0) glDisableClientState(GL_COLOR_ARRAY); if (command.texcoords.arity != 0) glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); break; } case DEFERRED_COMMAND_TYPE_DRAW_SKYBOX: { deferred_render_skybox(deferred_commands[i].draw_skybox.paths); break; } case DEFERRED_COMMAND_TYPE_USE_PIPIELINE: { switch (deferred_commands[i].use_pipeline.pipeline) { case PIPELINE_2D: finally_use_2d_pipeline(); break; case PIPELINE_SPACE: finally_use_space_pipeline(); break; case PIPELINE_NO: default: SDL_assert(false); } break; } case DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE: { finally_use_texture_mode(deferred_commands[i].use_texture_mode.mode); break; } case DEFERRED_COMMAND_TYPE_APPLY_FOG: { deferred_apply_fog(deferred_commands[i].apply_fog.start, deferred_commands[i].apply_fog.end, deferred_commands[i].apply_fog.density, deferred_commands[i].apply_fog.color); break; } case DEFERRED_COMMAND_TYPE_POP_FOG: { deferred_pop_fog(); break; } default: SDL_assert(false); } } } void clear_draw_buffer(void) { /* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/ DeferredCommand command = { .type = DEFERRED_COMMAND_TYPE_CLEAR, .clear = (DeferredCommandClear) { .clear_color = true, .clear_depth = true, .clear_stencil = true, .color = (Color) { 230, 230, 230, 1 } } }; arrpush(deferred_commands, command); } void start_render_frame(void) { clear_draw_buffer(); } void end_render_frame(void) { if (!ctx.render_double_buffered || ctx.game.frame_number == 1) { issue_deferred_draw_commands(); SDL_GL_SwapWindow(ctx.window); arrsetlen(deferred_commands, 0); restart_scratch_vertex_arrays(); } else { /* instead of waiting for frame to finish for the swap, we continue */ /* while issuing new state for the next call, but deferring any fragment emitting calls for later */ /* actual swap will happen when next frame is fully finished, introducing a delay */ SDL_GL_SwapWindow(ctx.window); issue_deferred_draw_commands(); restart_scratch_vertex_arrays(); glFlush(); arrsetlen(deferred_commands, 0); } } void use_space_pipeline(void) { DeferredCommand const command = { .type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE, .use_pipeline = { PIPELINE_SPACE } }; arrpush(deferred_commands, command); } static void finally_use_space_pipeline(void) { if (pipeline_last_used == PIPELINE_SPACE) return; static GLuint list = 0; if (!list) { list = glGenLists(1); glNewList(list, GL_COMPILE); { glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glShadeModel(GL_SMOOTH); if (GLAD_GL_ARB_depth_clamp) glDisable(GL_DEPTH_CLAMP); glEnable(GL_CULL_FACE); glDepthRange(0, 1); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); /* solid white, no modulation */ glColor4ub(255, 255, 255, 255); } glEndList(); } glCallList(list); glMatrixMode(GL_PROJECTION); glLoadMatrixf(&camera_projection_matrix.row[0].x); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&camera_look_at_matrix.row[0].x); texture_mode_last_used = -1; pipeline_last_used = PIPELINE_SPACE; } void use_2d_pipeline(void) { DeferredCommand const command = { .type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE, .use_pipeline = { PIPELINE_2D } }; arrpush(deferred_commands, command); } static void finally_use_2d_pipeline(void) { if (pipeline_last_used == PIPELINE_SPACE) { glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glFlush(); } if (pipeline_last_used == PIPELINE_2D) return; static GLuint list = 0; if (!list) { list = glGenLists(1); glNewList(list, GL_COMPILE); { glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glShadeModel(GL_FLAT); /* removes near/far plane comparison and discard */ if (GLAD_GL_ARB_depth_clamp) glEnable(GL_DEPTH_CLAMP); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glDisable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); } glEndList(); } glCallList(list); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); texture_mode_last_used = -1; pipeline_last_used = PIPELINE_2D; } void use_texture_mode(TextureMode mode) { DeferredCommand const command = { .type = DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE, .use_texture_mode = { mode } }; arrpush(deferred_commands, command); } static void finally_use_texture_mode(TextureMode mode) { if (texture_mode_last_used == mode) return; static GLuint lists = 0; if (!lists) { lists = glGenLists(3); /* ghostly */ glNewList(lists + 0, GL_COMPILE); { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthFunc(GL_LESS); glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); } glEndList(); /* seethrough */ glNewList(lists + 1, GL_COMPILE); { glDisable(GL_BLEND); glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_EQUAL, 1.0f); } glEndList(); /* opaque */ glNewList(lists + 2, GL_COMPILE); { glDisable(GL_BLEND); glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); glDisable(GL_ALPHA_TEST); } glEndList(); } if (mode == TEXTURE_MODE_GHOSTLY) { glCallList(lists + 0); } else if (mode == TEXTURE_MODE_SEETHROUGH) { glCallList(lists + 1); } else { glCallList(lists + 2); } texture_mode_last_used = mode; } VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) { SDL_assert(bytes != 0); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW); void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); if (!mapping) CRY("build_vertex_buffer", "Error mapping a vertex array buffer"); return (VertexBufferBuilder) { .mapping = mapping, .bytes_left = bytes, }; } bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder, void const *bytes, size_t size) { if (builder->bytes_left == 0) return false; memcpy(builder->mapping, bytes, size); builder->bytes_left -= size; /* trigger data send */ if (builder->bytes_left == 0) { glUnmapBuffer(GL_ARRAY_BUFFER); return false; } builder->mapping = (void *)((uintptr_t)builder->mapping + size); return true; } void finally_render_quads(const Primitive2D primitives[], const struct QuadBatch batch, const VertexBuffer buffer) { DeferredCommandDraw command = {0}; GLsizei off = 0, voff = 0, uvoff = 0, coff = 0; if (!batch.constant_colored && batch.textured) { off = offsetof(ElementIndexedQuad, v1); voff = offsetof(ElementIndexedQuad, v0); uvoff = offsetof(ElementIndexedQuad, uv0); coff = offsetof(ElementIndexedQuad, c0); } else if (batch.constant_colored && batch.textured) { off = offsetof(ElementIndexedQuadWithoutColor, v1); voff = offsetof(ElementIndexedQuadWithoutColor, v0); uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0); } else if (!batch.constant_colored && !batch.textured) { off = offsetof(ElementIndexedQuadWithoutTexture, v1); voff = offsetof(ElementIndexedQuadWithoutTexture, v0); coff = offsetof(ElementIndexedQuad, c0); } else if (batch.constant_colored && !batch.textured) { off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1); voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0); } command.vertices = (AttributeArrayPointer) { .arity = 2, .type = GL_FLOAT, .stride = off, .offset = voff, .buffer = buffer }; if (batch.textured) command.texcoords = (AttributeArrayPointer) { .arity = 2, .type = GL_FLOAT, .stride = off, .offset = uvoff, .buffer = buffer }; if (!batch.constant_colored) { command.colors = (AttributeArrayPointer) { .arity = 4, .type = GL_UNSIGNED_BYTE, .stride = off, .offset = coff, .buffer = buffer }; } else { command.constant_colored = true; command.color = primitives[0].sprite.color; } if (batch.textured) { command.textured = true; command.texture_key = batch.texture_key; command.texture_repeat = batch.repeat; } command.element_buffer = get_quad_element_buffer(); command.element_count = 6 * (GLsizei)batch.size; command.range_end = 6 * (GLsizei)batch.size; use_texture_mode(batch.mode); DeferredCommand final_command = { .type = DEFERRED_COMMAND_TYPE_DRAW, .draw = command }; arrpush(deferred_commands, final_command); } size_t get_quad_payload_size(struct QuadBatch batch) { if (batch.constant_colored && batch.textured) return sizeof (ElementIndexedQuadWithoutColor); else if (!batch.constant_colored && batch.textured) return sizeof (ElementIndexedQuad); else if (batch.constant_colored && !batch.textured) return sizeof (ElementIndexedQuadWithoutColorWithoutTexture); else if (!batch.constant_colored && !batch.textured) return sizeof (ElementIndexedQuadWithoutTexture); SDL_assert(false); return 0; } bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch, VertexBufferBuilder *builder, Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, Color color) { if (!batch.constant_colored && batch.textured) { ElementIndexedQuad const buffer_element = { .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3, .uv0 = uv0, .uv1 = uv1, .uv2 = uv2, .uv3 = uv3, /* equal for all (flat shaded) */ .c0 = color, // .c1 = color, .c2 = color, // .c3 = color, }; return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); } else if (batch.constant_colored && batch.textured) { ElementIndexedQuadWithoutColor const buffer_element = { .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3, .uv0 = uv0, .uv1 = uv1, .uv2 = uv2, .uv3 = uv3, }; return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); } else if (!batch.constant_colored && !batch.textured) { ElementIndexedQuadWithoutTexture const buffer_element = { .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3, /* equal for all (flat shaded) */ .c0 = color, // .c1 = color, .c2 = color, // .c3 = color, }; return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); } else if (batch.constant_colored && !batch.textured) { ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = { .v0 = v0, .v1 = v1, .v2 = v2, .v3 = v3, }; return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); } SDL_assert(false); return false; } void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch, const TextureKey texture_key, const VertexBuffer buffer) { DeferredCommandDraw command = {0}; command.vertices = (AttributeArrayPointer) { .arity = 3, .type = GL_FLOAT, .stride = offsetof(struct UncoloredSpaceTrianglePayload, v1), .offset = offsetof(struct UncoloredSpaceTrianglePayload, v0), .buffer = buffer }; command.texcoords = (AttributeArrayPointer) { .arity = 2, .type = GL_FLOAT, .stride = offsetof(struct UncoloredSpaceTrianglePayload, v1), .offset = offsetof(struct UncoloredSpaceTrianglePayload, uv0), .buffer = buffer }; command.textured = true; command.texture_key = texture_key; const GLuint primitives_len = arrlenu(batch->primitives); command.primitive_count = 3 * primitives_len; DeferredCommand final_command = { .type = DEFERRED_COMMAND_TYPE_DRAW, .draw = command }; arrpush(deferred_commands, final_command); } bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data, VertexBufferBuilder *builder, stbtt_aligned_quad quad) { (void)font_data; ElementIndexedQuadWithoutColor buffer_element = { .v0 = (Vec2){ quad.x0, quad.y0 }, .v1 = (Vec2){ quad.x1, quad.y0 }, .v2 = (Vec2){ quad.x1, quad.y1 }, .v3 = (Vec2){ quad.x0, quad.y1 }, .uv0 = (Vec2){ quad.s0, quad.t0 }, .uv1 = (Vec2){ quad.s1, quad.t0 }, .uv2 = (Vec2){ quad.s1, quad.t1 }, .uv3 = (Vec2){ quad.s0, quad.t1 }, }; return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); } void finally_draw_text(FontData const *font_data, size_t len, Color color, VertexBuffer buffer) { DeferredCommandDraw command = {0}; command.vertices = (AttributeArrayPointer) { .arity = 2, .type = GL_FLOAT, .stride = offsetof(ElementIndexedQuadWithoutColor, v1), .offset = offsetof(ElementIndexedQuadWithoutColor, v0), .buffer = buffer }; command.texcoords = (AttributeArrayPointer) { .arity = 2, .type = GL_FLOAT, .stride = offsetof(ElementIndexedQuadWithoutColor, v1), .offset = offsetof(ElementIndexedQuadWithoutColor, uv0), .buffer = buffer }; command.constant_colored = true; command.color = color; command.gpu_texture = font_data->texture; command.uses_gpu_key = true; command.textured = true; command.element_buffer = get_quad_element_buffer(); command.element_count = 6 * (GLsizei)len; command.range_end = 6 * (GLsizei)len; use_texture_mode(TEXTURE_MODE_GHOSTLY); DeferredCommand final_command = { .type = DEFERRED_COMMAND_TYPE_DRAW, .draw = command }; arrpush(deferred_commands, final_command); /* TODO: why doesn't it get restored if not placed here? */ // glDepthMask(GL_TRUE); } size_t get_text_payload_size(void) { return sizeof (ElementIndexedQuadWithoutColor); } static void load_cubemap_side(const char *path, GLenum target) { SDL_Surface *surface = textures_load_surface(path); /* TODO: sanity check whether all of them have same dimensions? */ glTexImage2D(target, 0, GL_RGBA8, surface->w, surface->h, 0, surface->format->BytesPerPixel == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, surface->pixels); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); SDL_free(surface->pixels); SDL_FreeSurface(surface); } void render_circle(const CirclePrimitive *circle) { static Vec2 vertices[CIRCLE_VERTICES_MAX]; int num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX); create_circle_geometry(circle->position, circle->radius, num_vertices, vertices); VertexBuffer buffer = get_scratch_vertex_array(); specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices); DeferredCommandDraw command = {0}; command.vertices = (AttributeArrayPointer) { .arity = 2, .type = GL_FLOAT, .stride = sizeof (Vec2), .offset = 0, .buffer = buffer }; command.constant_colored = true; command.color = circle->color; command.element_buffer = get_circle_element_buffer(); command.element_count = (num_vertices - 2) * 3; command.range_end = (num_vertices - 2) * 3; use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY); DeferredCommand final_command = { .type = DEFERRED_COMMAND_TYPE_DRAW, .draw = command }; arrpush(deferred_commands, final_command); } void finally_render_skybox(char *paths) { DeferredCommand command = { .type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX, .draw_skybox = (DeferredCommandDrawSkybox){ .paths = paths } }; arrpush(deferred_commands, command); } static void deferred_render_skybox(char *paths) { static GLuint cubemap = 0; static char *paths_cache = NULL; bool loading_needed = false; /* drop it */ if (!paths_cache || (SDL_strcmp(paths_cache, paths) != 0)) { if (cubemap) glDeleteTextures(1, &cubemap); glGenTextures(1, &cubemap); if (paths_cache) SDL_free(paths_cache); paths_cache = paths; loading_needed = true; } else SDL_free(paths); Matrix4 camera_look_at_matrix_solipsist = camera_look_at_matrix; camera_look_at_matrix_solipsist.row[3].x = 0; camera_look_at_matrix_solipsist.row[3].y = 0; camera_look_at_matrix_solipsist.row[3].z = 0; glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&camera_look_at_matrix_solipsist.row[0].x); glDisable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_CUBE_MAP); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap); if (loading_needed) { /* load all the sides */ char *expanded = expand_asterisk(paths, "up"); load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Y); SDL_free(expanded); expanded = expand_asterisk(paths, "down"); load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y); SDL_free(expanded); expanded = expand_asterisk(paths, "east"); load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_X); SDL_free(expanded); expanded = expand_asterisk(paths, "north"); load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_POSITIVE_Z); SDL_free(expanded); expanded = expand_asterisk(paths, "west"); load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_X); SDL_free(expanded); expanded = expand_asterisk(paths, "south"); load_cubemap_side(expanded, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); SDL_free(expanded); } /* TODO: figure out which coordinates to use to not have issues with far z */ /* TODO: recalculate the list if far z requirement changes */ static GLuint list = 0; if (!list) { list = glGenLists(1); glNewList(list, GL_COMPILE); { /* note: assumes that space pipeline is applied already */ glDisable(GL_ALPHA_TEST); glDepthMask(GL_FALSE); /* removes near/far plane comparison and discard */ if (GLAD_GL_ARB_depth_clamp) glEnable(GL_DEPTH_CLAMP); glBegin(GL_QUADS); { /* up */ glTexCoord3f(50.f, 50.f, 50.f); glVertex3f(50.f, 50.f, 50.f); glTexCoord3f(-50.f, 50.f, 50.f); glVertex3f(-50.f, 50.f, 50.f); glTexCoord3f(-50.f, 50.f, -50.f); glVertex3f(-50.f, 50.f, -50.f); glTexCoord3f(50.f, 50.f, -50.f); glVertex3f(50.f, 50.f, -50.f); /* down */ glTexCoord3f(50.f, -50.f, 50.f); glVertex3f(50.f, -50.f, 50.f); glTexCoord3f(50.f, -50.f, -50.f); glVertex3f(50.f, -50.f, -50.f); glTexCoord3f(-50.f, -50.f, -50.f); glVertex3f(-50.f, -50.f, -50.f); glTexCoord3f(-50.f, -50.f, 50.f); glVertex3f(-50.f, -50.f, 50.f); /* east */ glTexCoord3f(50.f, -50.f, 50.f); glVertex3f(50.f, -50.f, 50.f); glTexCoord3f(50.f, 50.f, 50.f); glVertex3f(50.f, 50.f, 50.f); glTexCoord3f(50.f, 50.f, -50.f); glVertex3f(50.f, 50.f, -50.f); glTexCoord3f(50.f, -50.f, -50.f); glVertex3f(50.f, -50.f, -50.f); /* west */ glTexCoord3f(-50.f, -50.f, 50.f); glVertex3f(-50.f, -50.f, 50.f); glTexCoord3f(-50.f, -50.f, -50.f); glVertex3f(-50.f, -50.f, -50.f); glTexCoord3f(-50.f, 50.f, -50.f); glVertex3f(-50.f, 50.f, -50.f); glTexCoord3f(-50.f, 50.f, 50.f); glVertex3f(-50.f, 50.f, 50.f); /* north */ glTexCoord3f(-50.f, -50.f, 50.f); glVertex3f(-50.f, -50.f, 50.f); glTexCoord3f(-50.f, 50.f, 50.f); glVertex3f(-50.f, 50.f, 50.f); glTexCoord3f(50.f, 50.f, 50.f); glVertex3f(50.f, 50.f, 50.f); glTexCoord3f(50.f, -50.f, 50.f); glVertex3f(50.f, -50.f, 50.f); /* south */ glTexCoord3f(-50.f, -50.f, -50.f); glVertex3f(-50.f, -50.f, -50.f); glTexCoord3f(50.f, -50.f, -50.f); glVertex3f(50.f, -50.f, -50.f); glTexCoord3f(50.f, 50.f, -50.f); glVertex3f(50.f, 50.f, -50.f); glTexCoord3f(-50.f, 50.f, -50.f); glVertex3f(-50.f, 50.f, -50.f); } glEnd(); if (GLAD_GL_ARB_depth_clamp) glDisable(GL_DEPTH_CLAMP); glDepthMask(GL_TRUE); glDisable(GL_TEXTURE_CUBE_MAP); } glEndList(); } glCallList(list); } void finally_apply_fog(float start, float end, float density, Color color) { DeferredCommand command = { .type = DEFERRED_COMMAND_TYPE_APPLY_FOG, .apply_fog = (DeferredCommandApplyFog){ .start = start, .end = end, .density = density, .color = color } }; arrpush(deferred_commands, command); } static void deferred_apply_fog(float start, float end, float density, Color color) { if (density < 0.0f || density > 1.0f) log_warn("Invalid fog density given, should be in range [0..1]"); /* TODO: cache it for constant parameters, which is a common case */ glEnable(GL_FOG); glFogf(GL_FOG_DENSITY, density); glFogf(GL_FOG_START, start); glFogf(GL_FOG_END, end); float color_conv[4]; color_conv[0] = (float)color.r / UINT8_MAX; color_conv[1] = (float)color.g / UINT8_MAX; color_conv[2] = (float)color.b / UINT8_MAX; color_conv[3] = (float)color.a / UINT8_MAX; glFogfv(GL_FOG_COLOR, color_conv); } void finally_pop_fog(void) { DeferredCommand command = { .type = DEFERRED_COMMAND_TYPE_POP_FOG, }; arrpush(deferred_commands, command); } static void deferred_pop_fog(void) { glDisable(GL_FOG); } void set_depth_range(double low, double high) { DeferredCommand const command = { .type = DEFERRED_COMMAND_TYPE_DEPTH_RANGE, .depth_range = { .low = low, .high = high } }; arrpush(deferred_commands, command); }