#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 /* interleaved vertex array data */ /* TODO: use int16_t for uvs */ /* TODO: use packed types? */ /* TODO: int16_t could be used for positioning, but we would need to have more CPU calcs */ 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 enum { PIPELINE_NO, PIPELINE_SPACE, PIPELINE_2D, } Pipeline; static Pipeline pipeline_last_used = PIPELINE_NO; #define CIRCLE_VERTICES_MAX 2048 void 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); pipeline_last_used = PIPELINE_SPACE; } void 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) glDisable(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, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); pipeline_last_used = PIPELINE_2D; } void render_circle(const CirclePrimitive *circle) { static SDL_Vertex vertices[CIRCLE_VERTICES_MAX]; static int indices[CIRCLE_VERTICES_MAX * 3]; int num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX-1); 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); } void use_texture_mode(TextureMode mode) { 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_ALWAYS); 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); } } 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; SDL_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) { (void)buffer; 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); } /* vertex specification */ glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, off, (void *)(size_t)voff); if (batch.textured) { glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE0); glTexCoordPointer(2, GL_FLOAT, off, (void *)(size_t)uvoff); } if (!batch.constant_colored) { glEnableClientState(GL_COLOR_ARRAY); glColorPointer(4, GL_UNSIGNED_BYTE, off, (void *)(size_t)coff); } else glColor4ub(primitives[0].sprite.color.r, primitives[0].sprite.color.g, primitives[0].sprite.color.b, primitives[0].sprite.color.a); if (batch.textured) { if (!batch.repeat) textures_bind(&ctx.texture_cache, primitives->sprite.texture_key); else textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key); } bind_quad_element_buffer(); glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, GL_UNSIGNED_SHORT, NULL); /* clear the state */ { glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); if (batch.textured) glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); if (!batch.constant_colored) glDisableClientState(GL_COLOR_ARRAY); if (batch.textured) glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } } 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 (ElementIndexedQuadWithoutTexture); else if (!batch.constant_colored && !batch.textured) return sizeof (ElementIndexedQuadWithoutColorWithoutTexture); 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) { const size_t primitives_len = arrlenu(batch->primitives); textures_bind(&ctx.texture_cache, texture_key); use_texture_mode(textures_get_mode(&ctx.texture_cache, texture_key)); glBindBuffer(GL_ARRAY_BUFFER, buffer); /* vertex specification*/ glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, offsetof(struct UncoloredSpaceTrianglePayload, v1), (void *)offsetof(struct UncoloredSpaceTrianglePayload, v0)); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE0); glTexCoordPointer(2, GL_FLOAT, offsetof(struct UncoloredSpaceTrianglePayload, v1), (void *)offsetof(struct UncoloredSpaceTrianglePayload, uv0)); /* commit for drawing */ glDrawArrays(GL_TRIANGLES, 0, 3 * (GLint)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); } 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) { (void)buffer; /* vertex specification */ glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, offsetof(ElementIndexedQuadWithoutColor, v1), (void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, v0)); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTexture(GL_TEXTURE0); glTexCoordPointer(2, GL_FLOAT, offsetof(ElementIndexedQuadWithoutColor, v1), (void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0)); bind_quad_element_buffer(); use_texture_mode(TEXTURE_MODE_GHOSTLY); glBindTexture(GL_TEXTURE_2D, font_data->texture); glColor4ub(color.r, color.g, color.b, color.a); glDrawElements(GL_TRIANGLES, 6 * (GLsizei)len, GL_UNSIGNED_SHORT, NULL); /* clear the state */ glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glBindTexture(GL_TEXTURE_2D, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); /* 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 finally_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); 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(); glDepthMask(GL_TRUE); glDisable(GL_TEXTURE_CUBE_MAP); } glEndList(); } glCallList(list); } void finally_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) { glDisable(GL_FOG); }