From 638c67a99e65f8f7d58cd4078dd042cea883d1a2 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Fri, 24 Nov 2023 23:21:08 +0200 Subject: [PATCH] text instancing --- cudoku.c | 24 +++++-- helper.c | 8 --- helper.h | 3 - shader.c | 5 ++ shader.h | 1 + shaders/font.vert | 18 +++++ todo.txt | 28 +++++++- ui.c | 168 ++++++++++++++++++++++++++++++++-------------- ui.h | 11 +-- z_math.h | 8 +++ 10 files changed, 197 insertions(+), 77 deletions(-) create mode 100644 shaders/font.vert diff --git a/cudoku.c b/cudoku.c index 5ed90d8..8b2bf47 100644 --- a/cudoku.c +++ b/cudoku.c @@ -29,7 +29,7 @@ static const int quad_indices[] = { static const Color mistake_color = {1.f, 0.f, 0.f, 0.5f}; -static const wide_str *win_text = U"You won!"; +static const char *win_text = "You won!"; static const char *help_texts[HELP_TEXT_SIZE] = { "F1 - Toggle help", @@ -58,15 +58,15 @@ void draw_win() { /* set_width_constraint(&constraints, window.width, UI_CONSTRAINT_FIXED); */ /* set_height_constraint(&constraints, window.height, UI_CONSTRAINT_FIXED); */ - Alignment align = ALIGN_CENTER; - int font_size = 72.0f; + Alignment align = ALIGN_TOP_LEFT; + int font_size = 84.0f; - /* Sizef text_size = calculate_text_size(win_text, font_size); */ + Sizef text_size = calculate_text_size(win_text, font_size); set_x_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); set_y_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); - /* set_width_constraint(&constraints, text_size.width, UI_CONSTRAINT_FIXED); */ - /* set_height_constraint(&constraints, text_size.height, UI_CONSTRAINT_FIXED); */ + set_width_constraint(&constraints, text_size.width, UI_CONSTRAINT_FIXED); + set_height_constraint(&constraints, text_size.height, UI_CONSTRAINT_FIXED); /* set_rotation_constraint(&constraints, 0); */ /* UIConstraints circle_const; */ @@ -76,7 +76,7 @@ void draw_win() { /* set_height_constraint(&circle_const, 90, UI_CONSTRAINT_FIXED); */ /* draw_circle(circle_const, NULL, ALIGN_LEFT_CENTER); */ - /* draw_quad(constraints, &bg_color, 0.0, align); */ + draw_quad(constraints, &bg_color, 0.0, align); /* set_x_constraint(&constraints, -50, UI_CONSTRAINT_FIXED); */ /* draw_quad(constraints, &bg_color, 25.0, ALIGN_TOP_RIGHT); */ /* set_x_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); */ @@ -87,7 +87,17 @@ void draw_win() { set_width_constraint(&constraints, 1, UI_CONSTRAINT_FIXED); set_height_constraint(&constraints, 1, UI_CONSTRAINT_FIXED); + /* set_rotation_constraint(&constraints, 0); */ draw_text(win_text, font_size, constraints, &text_color, align); + + /* for (int i = 0; i < 100; i++) { */ + /* int x = rand() % window.width; */ + /* int y = rand() % window.height; */ + /* set_x_constraint(&constraints, x, UI_CONSTRAINT_FIXED); */ + /* set_y_constraint(&constraints, y, UI_CONSTRAINT_FIXED); */ + + /* draw_text(win_text, font_size, constraints, &text_color, align); */ + /* } */ } void set_scale_factor(int width, int height, float *x, float *y) { diff --git a/helper.c b/helper.c index df13293..1741794 100644 --- a/helper.c +++ b/helper.c @@ -70,14 +70,6 @@ Matrix4x4 orthographic_projection_2d(float left, float right, float bottom, floa return result; } -uint wide_str_len(const wide_str *text) { - uint len = 0; - for (uint i = 0; text[i] != '\0'; i++) { - len++; - } - return len; -} - int max(int a, int b) { return a > b ? a : b; } diff --git a/helper.h b/helper.h index 5ce4326..c1e0061 100644 --- a/helper.h +++ b/helper.h @@ -4,15 +4,12 @@ #include "z_math.h" -typedef unsigned int wide_str; - Matrix4x4 identity(); void mult_4x4(Matrix4x4 *multiplicant, Matrix4x4 *multiplier); void apply_scale(Matrix4x4 *matrix, Sizef scale); void apply_rotation(Matrix4x4 *model, float angle); void apply_translation(Matrix4x4 *matrix, Vec2f pos); Matrix4x4 orthographic_projection_2d(float left, float right, float bottom, float top); -uint wide_str_len(const wide_str *text); int max(int a, int b); float maxf(float a, float b); float clampf(float value, float min, float max); diff --git a/shader.c b/shader.c index 418e7f9..527cfb4 100644 --- a/shader.c +++ b/shader.c @@ -87,6 +87,11 @@ void set_float(Shader shader, const char *name, float val) { glUniform1f(loc, val); } +void set_vec2f(Shader shader, const char *name, float val1, float val2) { + int loc = glGetUniformLocation(shader.program, name); + glUniform2f(loc, val1, val2); +} + void set_vec3f(Shader shader, const char *name, float val1, float val2, float val3) { int loc = glGetUniformLocation(shader.program, name); glUniform3f(loc, val1, val2, val3); diff --git a/shader.h b/shader.h index e17db7f..c05d03b 100644 --- a/shader.h +++ b/shader.h @@ -9,5 +9,6 @@ Shader create_shader(const char *vertex_path, const char *fragment_path); void use_shader(Shader shader); void set_mat4f(Shader shader, const char *name, float *mat); void set_float(Shader shader, const char *name, float val); +void set_vec2f(Shader shader, const char *name, float val1, float val2); void set_vec3f(Shader shader, const char *name, float val1, float val2, float val3); void set_vec4f(Shader shader, const char *name, float val1, float val2, float val3, float val4); diff --git a/shaders/font.vert b/shaders/font.vert new file mode 100644 index 0000000..a1df642 --- /dev/null +++ b/shaders/font.vert @@ -0,0 +1,18 @@ +#version 330 core +layout (location = 0) in vec2 vertex; + +// per instance +layout (location = 1) in vec4 offset; +layout (location = 2) in int tex_index; + +out vec2 v_TexCoords; +uniform mat4 projection; +uniform mat4 model; +// buffer for all the 96 ascii characters' texcoords +uniform vec2 texcoords[96 * 4]; + +void main() { + vec2 pos = vec2((vertex.x) * offset.z, (vertex.y) * offset.w); + gl_Position = projection * model * vec4(pos + offset.xy, 0.0, 1.0); + v_TexCoords = texcoords[tex_index * 4 + gl_VertexID]; +} diff --git a/todo.txt b/todo.txt index e268089..6cd9151 100644 --- a/todo.txt +++ b/todo.txt @@ -4,8 +4,12 @@ and replace them with the screen's currently active resolution text rendering improvements - - [x] unicode support (allows for non-ascii characters) + - [ ] unicode support (allows for non-ascii characters) + - unicode support removed for the foreseeable future - [x] atlas texture for text glyphs (this means we only load in one texture for all text) + - [x] better estimation of atlas width and height (currently the generated atlas texture + has a lot of empty space (over 50% of the texture's height)) + - ended up just looping over the ascii glyphs and finding the max width and height - SDF generation allowing for better text scaling (slower but visually better) - SDF is too slow if we want to support unicode. it's acceptably fast for ascii only though. @@ -17,4 +21,24 @@ text rendering improvements Will probably end up using MSDF (if I can get it to work with C lol) - - instanced rendering for text (this means we only have to do one draw call for all text) + - [x] instanced rendering for text (this means we only have to do one draw call for all text) + running into problems. we got the positions to instance correctly but the texture coordinates + are more difficult to work with because we can't just send a texcoord per vertex per instance + - Ended up passing hardcoded texcoords of all glyphs to the shader using a uniform array which allows us to render infinite + instances of text in one draw call without exceeding any buffer limits. Of course this does mean that if we were to support + unicode we'd have to make the uniform array bigger and its size dynamic. So unicode is dropped for now + + +text tests / iGPU / Non-instaned rendering + - 10 texts / 72 font size + 0.8-0.9ms per frame (1200-1300fps) + + - 100 texts / 48 font size + 1.2-1.3ms per frame (770-830fps) + + - 1000 texts / 28 font size + 9-9.5ms per frame (105-110fps) + + - 10000 texts / 9 font size + 80-85ms per frame (12-13fps) + diff --git a/ui.c b/ui.c index 02b77dd..22c7f16 100644 --- a/ui.c +++ b/ui.c @@ -19,15 +19,20 @@ Display *display; Window window; +typedef struct InstanceData { + Vec4f position; + int tex_coords_index; +} InstanceData; + Context zephr_context; Shader font_shader; Shader ui_shader; unsigned int font_vao; unsigned int ui_vao; -unsigned int font_vbo; +unsigned int font_instance_vbo; unsigned int ui_vbo; -// TODO: instanced rendering of ui elements and text?? +// TODO: instanced rendering of ui elements int init_fonts(const char *font_path) { FT_Library ft; @@ -55,22 +60,38 @@ int init_fonts(const char *font_path) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - uint max_dim = (1 + (face->size->metrics.height >> 6)) * ceilf(sqrtf(face->num_glyphs)); - uint tex_width = 1; - while (tex_width < max_dim) { - tex_width <<= 1; - } - uint tex_height = tex_width; + /* uint max_dim = (1 + (face->size->metrics.height >> 6)) * ceilf(sqrtf(face->num_glyphs)); */ + /* uint tex_width = 1; */ + /* while (tex_width < max_dim) { */ + /* tex_width <<= 1; */ + /* } */ + /* uint tex_height = tex_width; */ - char *pixels = (char *)calloc(tex_width * tex_height, 1); int pen_x = 0, pen_y = 0; FT_UInt glyph_idx; FT_ULong c = FT_Get_First_Char(face, &glyph_idx); - while (glyph_idx) { - if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { - printf("[ERROR]: failed to load glyph for char '0x%lx'\n", c); + uint tex_width = 0, tex_height = 0; + for (uint i = 32; i < 128; i++) { + if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { + printf("[ERROR]: failed to load glyph for char '0x%x'\n", i); + } + + /* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */ + + tex_width += face->glyph->bitmap.width + 1; + tex_height = max(tex_height, face->glyph->bitmap.rows); + } + + printf("[INFO] Texture size: %d,%d\n", tex_width, tex_height); + + char *pixels = (char *)calloc(tex_width * tex_height, 1); + + for (uint i = 32; i < 128; i++) { + /* while (glyph_idx) { */ + if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { + printf("[ERROR]: failed to load glyph for char '0x%x'\n", i); } /* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */ @@ -110,11 +131,11 @@ int init_fonts(const char *font_path) { character.size = (Size){ .width = face->glyph->bitmap.width, .height = face->glyph->bitmap.rows }; character.bearing = (Size){ .width = face->glyph->bitmap_left, .height = face->glyph->bitmap_top }; - hmput(zephr_context.font.characters, c, character); + zephr_context.font.characters[i] = character; pen_x += bmp->width + 1; - c = FT_Get_Next_Char(face, c, &glyph_idx); + /* c = FT_Get_Next_Char(face, c, &glyph_idx); */ } unsigned int texture; @@ -140,6 +161,9 @@ int init_fonts(const char *font_path) { } int init_ui(const char* font_path, Size window_size) { + uint font_vbo; + uint font_ebo; + zephr_context.window_size = window_size; zephr_context.projection = orthographic_projection_2d(0.f, window_size.width, window_size.height, 0.f); @@ -152,19 +176,58 @@ int init_ui(const char* font_path, Size window_size) { return 1; } - font_shader = create_shader("shaders/ui.vert", "shaders/font.frag"); + font_shader = create_shader("shaders/font.vert", "shaders/font.frag"); ui_shader = create_shader("shaders/ui.vert", "shaders/ui.frag"); glGenVertexArrays(1, &font_vao); glGenVertexArrays(1, &ui_vao); glGenBuffers(1, &font_vbo); + glGenBuffers(1, &font_instance_vbo); + glGenBuffers(1, &font_ebo); glGenBuffers(1, &ui_vbo); + float quad_vertices[4][2] = { + {0.0, 1.0}, // top left + {1.0, 1.0}, // top right + {1.0, 0.0}, // bottom right + {0.0, 0.0}, // bottom left + }; + + int quad_indices[6] = { + 0, 1, 2, + 2, 3, 0 + }; + glBindVertexArray(font_vao); + + // font quad vbo glBindBuffer(GL_ARRAY_BUFFER, font_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * 2, quad_vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + + // font instance vbo + glBindBuffer(GL_ARRAY_BUFFER, font_instance_vbo); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(InstanceData), (void *)0); + glVertexAttribDivisor(1, 1); + glEnableVertexAttribArray(2); + glVertexAttribIPointer(2, 1, GL_INT, sizeof(InstanceData), (void *)sizeof(Vec4f)); + glVertexAttribDivisor(2, 1); + + // font ebo + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW); + + use_shader(font_shader); + + for (int i = 32; i < 128; i++) { + for (int j = 0; j < 4; j++) { + char text[32]; + snprintf(text, 32, "texcoords[%d]", (i - 32) * 4 + j); + set_vec2f(font_shader, text, zephr_context.font.characters[i].tex_coords[j].x, zephr_context.font.characters[i].tex_coords[j].y); + } + } glBindVertexArray(ui_vao); glBindBuffer(GL_ARRAY_BUFFER, ui_vbo); @@ -256,9 +319,16 @@ bool zephr_should_quit() { /* update_ui(); */ audio_update(); + return zephr_context.should_quit; } +// example of how to batch all text drawing in one drawcall +// this should be called at the end of the frame +/* void batch_text_draw(&zephr_context.texts) { + * do something with zephr_context.texts +}*/ + void zephr_swap_buffers() { glXSwapBuffers(display, window); } @@ -267,7 +337,7 @@ Size zephr_get_window_size() { return zephr_context.window_size; } -Sizef calculate_text_size(const wide_str *text, int font_size) { +Sizef calculate_text_size(const char *text, int font_size) { float scale = (float)font_size / FONT_PIXEL_SIZE; Sizef size = { .width = 0, .height = 0 }; int w = 0; @@ -275,29 +345,27 @@ Sizef calculate_text_size(const wide_str *text, int font_size) { int max_bearing_h = 0; // NOTE: I don't like looping through the characters twice, but it's fine for now - for (uint i = 0; (i < text[i]) != '\0'; i++) { - Character ch = hmget(zephr_context.font.characters, (uint)text[i]); - /* Character ch = characters[(uint)text[i]]; */ + for (int i = 0; text[i] != '\0'; i++) { + Character ch = zephr_context.font.characters[(uint)text[i]]; max_bearing_h = max(max_bearing_h, ch.bearing.height); } - for (uint i = 0; (i < text[i]) != '\0'; i++) { - Character ch = hmget(zephr_context.font.characters, (uint)text[i]); - /* Character ch = characters[(uint)text[i]]; */ + for (int i = 0; text[i] != '\0'; i++) { + Character ch = zephr_context.font.characters[(uint)text[i]]; w += ch.advance / 64; // remove bearing of first character - if (i == 0 && wide_str_len(text) > 1) { + if (i == 0 && strlen(text) > 1) { w -= ch.bearing.width; } // remove the trailing width of the last character - if (i == wide_str_len(text) - 1) { + if (i == strlen(text) - 1) { w -= ((ch.advance / 64) - (ch.bearing.width + ch.size.width)); } // if we only have one character in the text, then remove the bearing width - if (wide_str_len(text) == 1) { + if (strlen(text) == 1) { w -= (ch.bearing.width); } @@ -525,7 +593,7 @@ void draw_triangle(UIConstraints constraints, Color *color, Alignment align) { glBindVertexArray(0); } -void draw_text(const wide_str* text, int font_size, UIConstraints constraints, Color *color, Alignment alignment) { +void draw_text(const char* text, int font_size, UIConstraints constraints, Color *color, Alignment alignment) { use_shader(font_shader); if (color) { set_vec4f(font_shader, "textColor", color->r / 255.f, color->g / 255.f, color->b / 255.f, color->a / 255.f); @@ -556,52 +624,54 @@ void draw_text(const wide_str* text, int font_size, UIConstraints constraints, C set_mat4f(font_shader, "model", (float *)model.m); int max_bearing_h = 0; - for (uint i = 0; (i < text[i]) != '\0'; i++) { - Character ch = hmget(zephr_context.font.characters, text[i]); - /* Character ch = characters[(int)text[i]]; */ + for (int i = 0; (i < text[i]) != '\0'; i++) { + Character ch = zephr_context.font.characters[(int)text[i]]; max_bearing_h = max(max_bearing_h, ch.bearing.height); } - float first_char_bearing_w = hmget(zephr_context.font.characters, (int)text[0]).bearing.width; + float first_char_bearing_w = zephr_context.font.characters[(int)text[0]].bearing.width; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, zephr_context.font.atlas_texture_id); glBindVertexArray(font_vao); + InstanceData* instance_data = malloc(sizeof(InstanceData) * strlen(text)); + // we use the original text and character sizes in the loop and then we just // scale up or down the model matrix to get the desired font size. // this way everything works out fine and we get to transform the text using the // model matrix int c = 0; int x = 0; + /* int line = 0; */ while (text[c] != '\0') { - Character ch = hmget(zephr_context.font.characters, text[c]); - /* Character ch = characters[(int)text[c]]; */ + Character ch = zephr_context.font.characters[(int)text[c]]; // subtract the bearing width of the first character to remove the extra space // at the start of the text and move every char to the left by that width float xpos = (x + (ch.bearing.width - first_char_bearing_w)); - float ypos = (text_size.height - ch.bearing.height - (text_size.height - max_bearing_h)); + float ypos = /*(line * text_size.height) + */(text_size.height - ch.bearing.height - (text_size.height - max_bearing_h)); - float vertices[6][4] = { - {xpos, ypos + ch.size.height, ch.tex_coords[0].x, ch.tex_coords[0].y}, - {xpos, ypos, ch.tex_coords[3].x, ch.tex_coords[3].y}, - {xpos + ch.size.width, ypos, ch.tex_coords[2].x, ch.tex_coords[2].y}, + /* if (text[c] == '\n') { */ + /* line++; */ + /* x = 0; */ + /* c++; */ + /* continue; */ + /* } */ - {xpos, ypos + ch.size.height, ch.tex_coords[0].x, ch.tex_coords[0].y}, - {xpos + ch.size.width, ypos, ch.tex_coords[2].x, ch.tex_coords[2].y}, - {xpos + ch.size.width, ypos + ch.size.height, ch.tex_coords[1].x, ch.tex_coords[1].y}, - }; - - glBindBuffer(GL_ARRAY_BUFFER, font_vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glDrawArrays(GL_TRIANGLES, 0, 6); + instance_data[c].position = (Vec4f){xpos, ypos, ch.size.width, ch.size.height}; + instance_data[c].tex_coords_index = (int)text[c] - 32; x += (ch.advance >> 6); c++; } + glBindBuffer(GL_ARRAY_BUFFER, font_instance_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(InstanceData) * strlen(text), instance_data, GL_DYNAMIC_DRAW); + + glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL, strlen(text)); + + free(instance_data); + glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/ui.h b/ui.h index 4f87666..e608ea0 100644 --- a/ui.h +++ b/ui.h @@ -12,13 +12,8 @@ typedef struct Character { Vec2f tex_coords[4]; // the 4 corners of the character quad } Character; -typedef struct Characters { - uint key; - Character value; -} Characters; - typedef struct ZephFont { - Characters *characters; + Character characters[128]; unsigned int atlas_texture_id; } ZephFont; @@ -71,5 +66,5 @@ void set_rotation_constraint(UIConstraints *constraints, float angle_d); void draw_quad(UIConstraints constraints, Color *color, float border_radius, Alignment align); void draw_circle(UIConstraints constraints, Color *color, Alignment align); void draw_triangle(UIConstraints constraints, Color *color, Alignment align); -void draw_text(const uint* text, int font_size, UIConstraints constraints, Color *color, Alignment align); -Sizef calculate_text_size(const uint *text, int font_size); +void draw_text(const char* text, int font_size, UIConstraints constraints, Color *color, Alignment align); +Sizef calculate_text_size(const char *text, int font_size); diff --git a/z_math.h b/z_math.h index c0de527..f93e128 100644 --- a/z_math.h +++ b/z_math.h @@ -24,6 +24,14 @@ typedef struct Vec2f { float y; } Vec2f; +typedef struct Vec4f { + float x; + float y; + float z; + float w; +} Vec4f; + + typedef struct Color { float r; float g;