From 7952f4ad87e02a4f8022ba52b1540a6997bfb725 Mon Sep 17 00:00:00 2001 From: Bailey Danyluk Date: Sun, 24 Dec 2023 03:13:32 -0700 Subject: [PATCH] add more robust text drawing without texture bleeding --- include/lib/hashmap.h | 2 +- include/ui/glyph.h | 2 - shaders/text_test_shader.frag | 9 ++- shaders/text_test_shader.vert | 2 + src/engine.c | 3 +- src/game/ui_test.c | 18 +++-- src/ui/font_engine.c | 32 ++------ src/ui/text.c | 144 ++++++++++++++++++---------------- 8 files changed, 105 insertions(+), 107 deletions(-) diff --git a/include/lib/hashmap.h b/include/lib/hashmap.h index ec1b605..d099c82 100644 --- a/include/lib/hashmap.h +++ b/include/lib/hashmap.h @@ -10,7 +10,7 @@ #define _DEFAULT_HASHMAP_SIZE 16 #ifndef _VERIFY_HASHMAP_INTEGRITY -#define _VERIFY_HASHMAP_INTEGRITY 1 +#define _VERIFY_HASHMAP_INTEGRITY 0 #endif // Generic hashmap diff --git a/include/ui/glyph.h b/include/ui/glyph.h index 06d8ad5..bc0aa55 100644 --- a/include/ui/glyph.h +++ b/include/ui/glyph.h @@ -4,8 +4,6 @@ #include "lib/math.h" typedef struct GlyphMetrics { - Vector2f max; - Vector2f min; Vector2f bearing; float advance; } GlyphMetrics; diff --git a/shaders/text_test_shader.frag b/shaders/text_test_shader.frag index d01d1e0..8c1b5c7 100644 --- a/shaders/text_test_shader.frag +++ b/shaders/text_test_shader.frag @@ -1,10 +1,13 @@ #version 410 core in vec4 vFragColor; +in vec2 vTexCoord; + out vec4 FragColor; -void main() -{ - FragColor = vFragColor; +uniform sampler2D fontAtlas; + +void main() { + FragColor = texture(fontAtlas, vTexCoord); } diff --git a/shaders/text_test_shader.vert b/shaders/text_test_shader.vert index 237ae8e..92ccf91 100644 --- a/shaders/text_test_shader.vert +++ b/shaders/text_test_shader.vert @@ -6,6 +6,7 @@ layout (location = 2) in vec3 vNormal; layout (location = 3) in vec4 vColor; out vec4 vFragColor; +out vec2 vTexCoord; uniform mat4 projection; uniform mat4 view; @@ -15,4 +16,5 @@ void main() { gl_Position = projection * view * model * vec4(vPos.x, vPos.y, vPos.z, 1.0); vFragColor = vColor; + vTexCoord = vUV; } diff --git a/src/engine.c b/src/engine.c index 45b91c8..fa19fde 100644 --- a/src/engine.c +++ b/src/engine.c @@ -113,7 +113,8 @@ void graphics_init(void) { initialise_renderer(); - ColorRGB clear_colour = hex_to_rgb("0xFF00FF"); + ColorRGB clear_colour = hex_to_rgb("0x333333"); + //ColorRGB clear_colour = hex_to_rgb("0xFFFFFF"); window_set_clear_color(&ENGINE->window, clear_colour); glfwSwapInterval(0); diff --git a/src/game/ui_test.c b/src/game/ui_test.c index eecf08f..31f587e 100644 --- a/src/game/ui_test.c +++ b/src/game/ui_test.c @@ -20,10 +20,10 @@ void ui_test_push(GameState* state) { s->font_engine = new_font_engine(); - int points[] = { 16, 32, 64 }; + int points[] = { 72 }; load_font_from_disk( s->font_engine, - (Vector2i) { .x = 1000, .y = 470 }, + (Vector2i) { .x = 1200, .y = 520 }, "default", "/home/bailey/.fonts/RobotoMono/RobotoMonoNerdFont-Medium.ttf", sizeof(points) / sizeof(int), points ); @@ -38,10 +38,10 @@ void ui_test_push(GameState* state) { -100.f, 100.f ); s->view = new_camera(); - float sx = 500.f; - float sy = 100.f; - float tx = 1280 * 0.5f - sx * 0.5f; - float ty = 720 * 0.5f - sy * 0.5f; + float sx = 0.2f; + float sy = 0.2f; + float tx = 1280 * 0.02f - sx * 0.5f; + float ty = 720 * 0.05f - sy * 0.5f; float m[] = { sx, 0.f, 0.f, 0.f, 0.f, sy, 0.f, 0.f, @@ -54,7 +54,11 @@ void ui_test_push(GameState* state) { bind_primitive_to_vao(primitive_quad(), s->vao); s->text = create_text(get_font(s->font_engine, "default")); - text_set_string(&s->text, "The Quick Brown Fox Jumps Over The Lazy Dog"); + text_set_string(&s->text, "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla varius. Aenean tristique\nsem sed metus. Quisque orci turpis, euismod semper, volutpat sed, sagittis id, purus.\nPellentesque molestie. Nam vitae odio. Suspendisse elit magna, dapibus sed, fermentum vel,\nelementum eget, nulla. Integer eget ligula. Pellentesque erat. Proin elit mauris, semper\neu, feugiat sed, rhoncus et, wisi. Nulla pede ipsum_ornare eget, porttitor vel, nonummy\nac, nibh. Integer consectetuer faucibus dui. Vestibulum venenatis feugiat wisi. Praesent\nultricies. Ut aliquet ligula at dolor. In hac habitasse platea dictumst. In est nibh,\nelementum et, malesuada nec, semper vitae, tortor.\n\tSed est sem-molestie in, ultricies\nsit amet, varius non, lorem. Praesent eget dolor. Nullam sed purus eu diam venenatis\nullamcorper. Fusce semper nisl vel lectus fermentum aliquet. Nullam sem. Ut ultrices\nplacerat felis. Curabitur pulvinar. Integer egestas. Nam quam sem, tincidunt id, lacinia\nac, pharetra sit amet, tellus. Pellentesque eros justo, rutrum in, euismod eget, mollis a,\nmassa. Sed pretium mi a enim. Maecenas gravida. Suspendisse purus risus, consectetuer sed,\nvestibulum ut^bibendum vel, ligula $123.\n\tDonec et erat. In eget nunc eu ipsum commodo mattis. Mauris semper. Aliquam erat volutpat.\nNullam non sem nec augue convallis porta. Fusce interdum-quam quis pede; suspendisse\nadipiscing. In est. Cras risus mauris, commodo a, egestas non, pretium ut, risus. Nulla\nauctor volutpat augue. Donec wisi. Suspendisse quam orci, posuere et, volutpat quis,\ncommodo sit amet, lectus. Aliquam erat volutpat. Donec dignissim. Sed pretium arcu sit\namet dolor. Aenean mattis urna et nulla. Aenean tellus. Aliquam erat volutpat. Maecenas\nnec diam semper diam luctus commodo.\n\nThe Quick Brown Fox Jumps Over The Lazy Dog 0123456789"); + s->text._render_info.point = 0; + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); } void ui_test_pop(GameState* state) { diff --git a/src/ui/font_engine.c b/src/ui/font_engine.c index d5b0b48..cb43d38 100644 --- a/src/ui/font_engine.c +++ b/src/ui/font_engine.c @@ -72,18 +72,10 @@ bool get_glyph_from_char(Glyph* glyph, Font font, char c) { return false; } // Horizontal metrics - glyph->horizontal_metrics.advance = font._face->glyph->metrics.horiAdvance; + glyph->horizontal_metrics.advance = font._face->glyph->metrics.horiAdvance / 64.f; glyph->horizontal_metrics.bearing = (Vector2f) { .x = font._face->glyph->metrics.horiBearingX, - .y = font._face->glyph->metrics.horiBearingY - }; - glyph->horizontal_metrics.min = (Vector2f) { - .x = glyph->horizontal_metrics.bearing.x, - .y = glyph->horizontal_metrics.bearing.y - font._face->glyph->metrics.height, - }; - glyph->horizontal_metrics.max = (Vector2f) { - .x = glyph->horizontal_metrics.min.x + font._face->glyph->metrics.width, - .y = glyph->horizontal_metrics.bearing.y, + .y = -font._face->glyph->metrics.horiBearingY }; // Vertical metrics glyph->vertical_metrics.advance = font._face->glyph->metrics.vertAdvance; @@ -91,14 +83,8 @@ bool get_glyph_from_char(Glyph* glyph, Font font, char c) { .x = font._face->glyph->metrics.vertBearingX, .y = font._face->glyph->metrics.vertBearingY }; - glyph->vertical_metrics.min = (Vector2f) { - .x = -glyph->horizontal_metrics.bearing.x, - .y = glyph->horizontal_metrics.bearing.y + font._face->glyph->metrics.height, - }; - glyph->vertical_metrics.max = (Vector2f) { - .x = glyph->horizontal_metrics.min.x + font._face->glyph->metrics.width, - .y = glyph->horizontal_metrics.bearing.y, - }; + glyph->horizontal_metrics.bearing = mul_vector2f(glyph->horizontal_metrics.bearing, 1.f / 64.f); + glyph->vertical_metrics.bearing = mul_vector2f(glyph->vertical_metrics.bearing, 1.f / 64.f); // Divide by 64 to get from points -> pixels glyph->bounds.size.x = font._face->glyph->metrics.width / 64.f; glyph->bounds.size.y = font._face->glyph->metrics.height / 64.f; @@ -157,7 +143,7 @@ void load_font_from_disk(FontEngine engine, Vector2i atlas_size, const char* id, VECTOR_PUSH(Glyph, &point->glyphs, glyph); } - for (char c = 'A'; c <= 'z'; c++) { + for (char c = '!'; c <= '~'; c++) { log_debug_verbose(1, "Generating glyph %c for [id: %s]", c, id); Glyph glyph; if (!get_glyph_from_char(&glyph, font, c)) { @@ -195,13 +181,10 @@ void load_font_from_disk(FontEngine engine, Vector2i atlas_size, const char* id, VECTOR_PUSH(AtlasEntry, &entries, glyph_entry); } } - log_debug("0"); - log_debug("1 %d", *(size_t*)hashmap_get(&font.char_glyph_map, "A")); if (!create_atlas(&font.glyph_atlas, entries, atlas_size)) { log_warning("Could not pack all glyphs into atlas"); } - log_debug("2 %d", *(size_t*)hashmap_get(&font.char_glyph_map, "A")); // Set glyph texture info for (int i = 0; i < font.glyph_atlas.packed_entries.length; i++) { @@ -218,17 +201,14 @@ void load_font_from_disk(FontEngine engine, Vector2i atlas_size, const char* id, }; _VECTOR_SET(Glyph, &font.points[meta_glyph->point].glyphs, meta_glyph->glyph_index, glyph); } - log_debug("3 %d", *(size_t*)hashmap_get(&font.char_glyph_map, "A")); // set font GPU texture font._texture = generate_texture(); - Image atlas_image = draw_atlas(font.glyph_atlas, 3, draw_meta_glyph); + Image atlas_image = draw_atlas(font.glyph_atlas, 4, draw_meta_glyph); bind_image_to_texture(&font._texture, atlas_image); - log_debug("4 %d", *(size_t*)hashmap_get(&font.char_glyph_map, "A")); // put font into map hashmap_set(&engine.manager.font_map, id, &font); - log_debug("5 %d", *(size_t*)hashmap_get(&font.char_glyph_map, "A")); } void draw_font_atlas(FontEngine engine, const char* id, const char* out_path) { diff --git a/src/ui/text.c b/src/ui/text.c index 446b127..2e7c06f 100644 --- a/src/ui/text.c +++ b/src/ui/text.c @@ -12,7 +12,7 @@ Text create_text(Font font) { text.font = font; text._updated = true; text.string = string_from_cstr(""); - glGenBuffers(1, &text._render_info.vao_handle); + glGenVertexArrays(1, &text._render_info.vao_handle); glGenBuffers(1, &text._render_info.vbo_handle); text._render_info.point = 0; return text; @@ -25,27 +25,40 @@ void text_set_string(Text* text, const char* str) { } void draw_text(Text* text) { - glBindVertexArray(text->_render_info.vao_handle); if (text->_updated) { - log_debug("6"); - text->_render_info.vertex_count = 6 * text->string.length; + size_t char_count = 0; + for (int i = 0; i < text->string.length; i++) { + char c = text->string.buffer[i]; + if (c != '\n' && c != ' ' && c != '\t') { + char_count++; + } + } + text->_render_info.vertex_count = 6 * char_count; + Vertex* vertices = malloc(sizeof(Vertex) * text->_render_info.vertex_count); memset(vertices, 0, sizeof(Vertex) * text->_render_info.vertex_count); - Vector2f max_size = (Vector2f) { .x = 0.f, .y = 0.f }; Vector2f cursor_position = (Vector2f) { .x = 0.f, .y = 0.f }; Point point = text->font.points[text->_render_info.point]; - log_debug("5"); + + int vertex_index = 0; for (int i = 0; i < text->string.length; i++) { - log_debug("a, %d", i); char c = text->string.buffer[i]; - log_debug("char: %c", c); + if (c == '\n') { + cursor_position.x = 0.f; + cursor_position.y += point.newline_height; + continue; + } size_t glyph_index = *(size_t*)hashmap_get(&text->font.char_glyph_map, &c); - log_debug("glyph index %d", glyph_index); Glyph glyph = _VECTOR_GET(Glyph, &point.glyphs, glyph_index); - log_debug("glyph: %f", glyph.horizontal_metrics.advance); - - log_debug("b"); + if (c == ' ' || c == '\t') { + int modifier = 1; + if (c == '\t') { + modifier = 4; + } + cursor_position.x += modifier * glyph.horizontal_metrics.advance; + continue; + } const Vector2f origin = add_vector2f(cursor_position, glyph.horizontal_metrics.bearing); const Vector3f tl = (Vector3f) { .x = origin.x, @@ -68,80 +81,72 @@ void draw_text(Text* text) { .z = 0.f }; - log_debug("c"); - vertices[6 * i + 0].position = tl; - vertices[6 * i + 1].position = tr; - vertices[6 * i + 2].position = br; - vertices[6 * i + 3].position = tl; - vertices[6 * i + 4].position = br; - vertices[6 * i + 5].position = bl; - log_debug("d"); + vertices[6 * vertex_index + 0].position = tl; + vertices[6 * vertex_index + 1].position = tr; + vertices[6 * vertex_index + 2].position = br; + vertices[6 * vertex_index + 3].position = tl; + vertices[6 * vertex_index + 4].position = br; + vertices[6 * vertex_index + 5].position = bl; - vertices[6 * i + 0].uv_coordinate = (Vector2f) { - .x = glyph.uv_coordinates.x, - .y = glyph.uv_coordinates.y + Vector2f half_inv_uv_size = (Vector2f) { + .x = 0.5f / (float)text->font.glyph_atlas.size.x, + .y = 0.5f / (float)text->font.glyph_atlas.size.y }; - vertices[6 * i + 1].uv_coordinate = (Vector2f) { - .x = glyph.uv_coordinates.x + glyph.uv_size.x, - .y = glyph.uv_coordinates.y + Vector2f one_inv_uv_size = (Vector2f) { + .x = 1.f / (float)text->font.glyph_atlas.size.x, + .y = 1.f / (float)text->font.glyph_atlas.size.y }; - vertices[6 * i + 2].uv_coordinate = (Vector2f) { - .x = glyph.uv_coordinates.x + glyph.uv_size.x, - .y = glyph.uv_coordinates.y + glyph.uv_size.y + + vertices[6 * vertex_index + 0].uv_coordinate = (Vector2f) { + .x = glyph.uv_coordinates.x + half_inv_uv_size.x, + .y = glyph.uv_coordinates.y + half_inv_uv_size.y + }; + vertices[6 * vertex_index + 1].uv_coordinate = (Vector2f) { + .x = glyph.uv_coordinates.x + glyph.uv_size.x - one_inv_uv_size.x, + .y = glyph.uv_coordinates.y + half_inv_uv_size.y + }; + vertices[6 * vertex_index + 2].uv_coordinate = (Vector2f) { + .x = glyph.uv_coordinates.x + glyph.uv_size.x - one_inv_uv_size.x, + .y = glyph.uv_coordinates.y + glyph.uv_size.y - one_inv_uv_size.y }; - vertices[6 * i + 3].uv_coordinate = (Vector2f) { - .x = glyph.uv_coordinates.x, - .y = glyph.uv_coordinates.y + vertices[6 * vertex_index + 3].uv_coordinate = (Vector2f) { + .x = glyph.uv_coordinates.x + half_inv_uv_size.x, + .y = glyph.uv_coordinates.y + half_inv_uv_size.y }; - vertices[6 * i + 4].uv_coordinate = (Vector2f) { - .x = glyph.uv_coordinates.x + glyph.uv_size.x, - .y = glyph.uv_coordinates.y + glyph.uv_size.y + vertices[6 * vertex_index + 4].uv_coordinate = (Vector2f) { + .x = glyph.uv_coordinates.x + glyph.uv_size.x - one_inv_uv_size.x, + .y = glyph.uv_coordinates.y + glyph.uv_size.y - one_inv_uv_size.y }; - vertices[6 * i + 5].uv_coordinate = (Vector2f) { - .x = glyph.uv_coordinates.x, - .y = glyph.uv_coordinates.y + glyph.uv_size.y + vertices[6 * vertex_index + 5].uv_coordinate = (Vector2f) { + .x = glyph.uv_coordinates.x + half_inv_uv_size.x, + .y = glyph.uv_coordinates.y + glyph.uv_size.y - one_inv_uv_size.y }; - log_debug("e"); - vertices[6 * i + 0].color = (VertexColor) { + vertices[6 * vertex_index + 0].color = (VertexColor) { .r = 1.f, .g = 1.f, .b = 1.f, .a = 1.f }; - vertices[6 * i + 1].color = (VertexColor) { + vertices[6 * vertex_index + 1].color = (VertexColor) { .r = 1.f, .g = 1.f, .b = 1.f, .a = 1.f }; - vertices[6 * i + 2].color = (VertexColor) { + vertices[6 * vertex_index + 2].color = (VertexColor) { .r = 1.f, .g = 1.f, .b = 1.f, .a = 1.f }; - vertices[6 * i + 3].color = (VertexColor) { + vertices[6 * vertex_index + 3].color = (VertexColor) { .r = 1.f, .g = 1.f, .b = 1.f, .a = 1.f }; - vertices[6 * i + 4].color = (VertexColor) { + vertices[6 * vertex_index + 4].color = (VertexColor) { .r = 1.f, .g = 1.f, .b = 1.f, .a = 1.f }; - vertices[6 * i + 5].color = (VertexColor) { + vertices[6 * vertex_index + 5].color = (VertexColor) { .r = 1.f, .g = 1.f, .b = 1.f, .a = 1.f }; - log_debug("f"); - if (c == '\n') { - cursor_position.x = 0.f; - cursor_position.y += point.newline_height; - } else { - cursor_position.x += glyph.horizontal_metrics.advance; - } - log_debug("g"); + cursor_position.x += glyph.horizontal_metrics.advance; - max_size.x = max_size.x < br.x ? br.x : max_size.x; - max_size.y = max_size.y < br.y ? br.y : max_size.y; - log_debug("h"); + vertex_index++; } - log_debug("4"); - for (int i = 0; i < text->_render_info.vertex_count; i++) { - vertices[i].position.x /= max_size.x; - vertices[i].position.y /= max_size.y; - } - log_debug("3"); + glBindVertexArray(text->_render_info.vao_handle); glBindBuffer(GL_ARRAY_BUFFER, text->_render_info.vbo_handle); glBufferData( @@ -157,16 +162,21 @@ void draw_text(Text* text) { text->_updated = false; free(vertices); - glBindVertexArray(text->_render_info.vao_handle); - log_debug("2"); } - glDrawArrays(GL_TRIANGLES, text->_render_info.vertex_count, GL_UNSIGNED_INT); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, text->font._texture.id); + glBindVertexArray(text->_render_info.vao_handle); + glDrawArrays(GL_TRIANGLES, 0, text->_render_info.vertex_count); glBindVertexArray(0); - log_debug("1"); + glBindTexture(GL_TEXTURE_2D, 0); } void destroy_text(Text text) { del_string(text.string); - glDeleteBuffers(1, &text._render_info.vao_handle); + glDeleteVertexArrays(1, &text._render_info.vao_handle); glDeleteBuffers(1, &text._render_info.vbo_handle); }