Skip to content

Commit

Permalink
add more robust text drawing without texture bleeding
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCandianVendingMachine committed Dec 24, 2023
1 parent 86b17ac commit 7952f4a
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 107 deletions.
2 changes: 1 addition & 1 deletion include/lib/hashmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions include/ui/glyph.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include "lib/math.h"

typedef struct GlyphMetrics {
Vector2f max;
Vector2f min;
Vector2f bearing;
float advance;
} GlyphMetrics;
Expand Down
9 changes: 6 additions & 3 deletions shaders/text_test_shader.frag
Original file line number Diff line number Diff line change
@@ -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);
}

2 changes: 2 additions & 0 deletions shaders/text_test_shader.vert
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,4 +16,5 @@ void main()
{
gl_Position = projection * view * model * vec4(vPos.x, vPos.y, vPos.z, 1.0);
vFragColor = vColor;
vTexCoord = vUV;
}
3 changes: 2 additions & 1 deletion src/engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 11 additions & 7 deletions src/game/ui_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand All @@ -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,
Expand All @@ -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) {
Expand Down
32 changes: 6 additions & 26 deletions src/ui/font_engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,33 +72,19 @@ 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;
glyph->vertical_metrics.bearing = (Vector2f) {
.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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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++) {
Expand All @@ -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) {
Expand Down
144 changes: 77 additions & 67 deletions src/ui/text.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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);
}

0 comments on commit 7952f4a

Please sign in to comment.