From 99337878eca2807436bcf11d36946b90db44a2d3 Mon Sep 17 00:00:00 2001 From: pryazha Date: Mon, 4 Aug 2025 01:19:01 +0500 Subject: text rendering chapter --- debug/debug | Bin 396728 -> 0 bytes libs/common.h | 13 ++- libs/pwyazh/v2f.h | 7 ++ libs/pwyazh/v3f.h | 7 ++ text_rendering/bounds.frag | 8 ++ text_rendering/bounds.vert | 12 +++ text_rendering/build.sh | 7 ++ text_rendering/glyph.frag | 16 ++++ text_rendering/glyph.vert | 18 ++++ text_rendering/ibmvga8x16.ttf | Bin 0 -> 69760 bytes text_rendering/text_rendering.c | 199 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 282 insertions(+), 5 deletions(-) delete mode 100755 debug/debug create mode 100644 text_rendering/bounds.frag create mode 100644 text_rendering/bounds.vert create mode 100755 text_rendering/build.sh create mode 100644 text_rendering/glyph.frag create mode 100644 text_rendering/glyph.vert create mode 100644 text_rendering/ibmvga8x16.ttf create mode 100644 text_rendering/text_rendering.c diff --git a/debug/debug b/debug/debug deleted file mode 100755 index d516f24..0000000 Binary files a/debug/debug and /dev/null differ diff --git a/libs/common.h b/libs/common.h index 08e753a..a7f8985 100644 --- a/libs/common.h +++ b/libs/common.h @@ -454,16 +454,16 @@ typedef struct { #define MAX_SHADERS 8 shader_t shaders[MAX_SHADERS] = {0}; -void add_shader(const char *vert_filename, const char *frag_filename, const char *geom_filename) +U32 add_shader(const char *vert_filename, const char *frag_filename, const char *geom_filename) { if (!vert_filename) { info("vertex shader filename not specified"); - return; + return 0; } const char *dot = strrchr(vert_filename, '.'); if (!dot) { info("shader \"%s\" was not loaded .vert or .vs expected for vertex shader", vert_filename); - return; + return 0; } U64 len = dot - vert_filename; char *name = malloc(len + 1); @@ -476,10 +476,11 @@ void add_shader(const char *vert_filename, const char *frag_filename, const char if (!shaders[i].id) die("failed to load shader \"%s\"", name); shaders[i].name = name; info("shader \"%s\" loaded successfully", name); - return; + return shaders[i].id; } } info("max shaders reached, can't load \"%s\"", name); + return 0; } void remove_shader(const char *name) @@ -689,7 +690,9 @@ void gl_debug_output(GLenum source, GLenum type, GLuint id, GLenum severity, GLs /* ignore these non-significant error codes */ if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; - info("debug message (%d): %s", id, message); + if (userParam) + info("user parameters doesn't supported"); + info("debug message (%d length: %d): %s", id, length, message); switch (source) { case GL_DEBUG_SOURCE_API: info("source: api"); diff --git a/libs/pwyazh/v2f.h b/libs/pwyazh/v2f.h index bc09b45..67f9bd2 100644 --- a/libs/pwyazh/v2f.h +++ b/libs/pwyazh/v2f.h @@ -8,6 +8,13 @@ v2f(F32 x, F32 y) return(result); } +V2F +v2fa(F32 x) +{ + V2F result = { x, x }; + return(result); +} + V2F v2f_zero() { diff --git a/libs/pwyazh/v3f.h b/libs/pwyazh/v3f.h index f1b4ff7..c5a5c1b 100644 --- a/libs/pwyazh/v3f.h +++ b/libs/pwyazh/v3f.h @@ -8,6 +8,13 @@ v3f(F32 x, F32 y, F32 z) return(result); } +V3F +v3fa(F32 x) +{ + V3F result = { x, x, x }; + return(result); +} + V3F v3f_from_v4f(V4F a) { diff --git a/text_rendering/bounds.frag b/text_rendering/bounds.frag new file mode 100644 index 0000000..34646c6 --- /dev/null +++ b/text_rendering/bounds.frag @@ -0,0 +1,8 @@ +#version 330 core + +out vec4 frag_color; + +void main() +{ + frag_color = vec4(1.0); +} diff --git a/text_rendering/bounds.vert b/text_rendering/bounds.vert new file mode 100644 index 0000000..8c115a5 --- /dev/null +++ b/text_rendering/bounds.vert @@ -0,0 +1,12 @@ +#version 330 core + +layout(location = 0) in vec4 vertex; + +uniform mat4 projection; + +void main() +{ + gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); +} + + diff --git a/text_rendering/build.sh b/text_rendering/build.sh new file mode 100755 index 0000000..bfca9cc --- /dev/null +++ b/text_rendering/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh +include="-I../libs -I../libs/pwyazh -I/usr/include/freetype2" +cflags="-g -Wall -Wextra $include" +libs='-lm -lGL -lGLEW -lglfw -lfreetype' +target='text_rendering' +set -x +gcc -o $target $cflags $target.c $libs diff --git a/text_rendering/glyph.frag b/text_rendering/glyph.frag new file mode 100644 index 0000000..a6fae4b --- /dev/null +++ b/text_rendering/glyph.frag @@ -0,0 +1,16 @@ +#version 330 core + +in vert_t { + vec2 tex_coords; +} vert; + +out vec4 frag_color; + +uniform sampler2D glyph; +uniform vec3 text_color; + +void main() +{ + vec4 glyph_color = vec4(1.0, 1.0, 1.0, texture(glyph, vert.tex_coords).r); + frag_color = vec4(text_color, 1.0) * glyph_color; +} diff --git a/text_rendering/glyph.vert b/text_rendering/glyph.vert new file mode 100644 index 0000000..ebee1b5 --- /dev/null +++ b/text_rendering/glyph.vert @@ -0,0 +1,18 @@ +#version 330 core + +/* combine position and texture coordinates into vec4 */ +layout(location = 0) in vec4 vertex; + +out vert_t { + vec2 tex_coords; +} vert; + +uniform mat4 projection; + +void main() +{ + gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); + vert.tex_coords = vertex.zw; +} + + diff --git a/text_rendering/ibmvga8x16.ttf b/text_rendering/ibmvga8x16.ttf new file mode 100644 index 0000000..72231a6 Binary files /dev/null and b/text_rendering/ibmvga8x16.ttf differ diff --git a/text_rendering/text_rendering.c b/text_rendering/text_rendering.c new file mode 100644 index 0000000..398b202 --- /dev/null +++ b/text_rendering/text_rendering.c @@ -0,0 +1,199 @@ +#include +#include + +#include +#include FT_FREETYPE_H + +#include "common.h" + +typedef struct { + U32 texture_id; + V2F size; + V2F bearing; + U32 advance; +} char_t; + +char_t chars[128]; +static U32 vao, vbo; + +V2F get_text_size(const char *text, F32 scale) +{ + V2F size = {0}; + for (const char *c = text; *c; c++) { + char_t ch = chars[(U8)*c]; + F32 h = ch.size.y * scale; + if (h > size.y) + size.y = h; + size.x += (ch.advance >> 6) * scale; + } + return size; +} + +void render_text(const char *text, U32 shader, V2F pos, F32 scale, V3F color) +{ + glUseProgram(shader); + shader_set_3fv(shader, "text_color", color); + glActiveTexture(GL_TEXTURE0); + glBindVertexArray(vao); + for (const char *c = text; *c; c++) { + char_t ch = chars[(U8)*c]; + F32 x = pos.x + ch.bearing.x * scale; + F32 y = pos.y - (ch.size.y - ch.bearing.y) * scale; + F32 w = ch.size.x * scale; + F32 h = ch.size.y * scale; + F32 vertices[] = { + x, y + h, 0.0f, 0.0f, + x, y, 0.0f, 1.0f, + x + w, y, 1.0f, 1.0f, + + x, y + h, 0.0f, 0.0f, + x + w, y, 1.0f, 1.0f, + x + w, y + h, 1.0f, 0.0f, + }; + glBindTexture(GL_TEXTURE_2D, ch.texture_id); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindTexture(GL_TEXTURE_2D, 0); + pos.x += (ch.advance >> 6) * scale; + } + glBindVertexArray(0); + glUseProgram(0); +} + +void render_bounds(const char *text, U32 shader, V2F pos, F32 scale) +{ + glUseProgram(shader); + glBindVertexArray(vao); + V2F size = get_text_size(text, scale); + F32 x = pos.x; + F32 y = pos.y; + F32 w = size.x; + F32 h = size.y; + F32 vertices[] = { + x, y + h, 0.0f, 0.0f, + x, y, 0.0f, 1.0f, + x + w, y, 1.0f, 1.0f, + + x, y + h, 0.0f, 0.0f, + x + w, y, 1.0f, 1.0f, + x + w, y + h, 1.0f, 0.0f, + }; + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glDrawArrays(GL_TRIANGLES, 0, 6); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glBindVertexArray(0); + glUseProgram(0); +} + +int main(void) +{ + state_t state = init_state(1600, 800, 1); + init_glfw(&state); + init_gl(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + FT_Library ft; + if (FT_Init_FreeType(&ft)) + die("failed to initialize freetype"); + FT_Face face; + const char *font_name = "ibmvga8x16.ttf"; + if (FT_New_Face(ft, font_name, 0, &face)) + die("failed to load %s", font_name); + FT_Set_Pixel_Sizes(face, 0, 48); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + for (U32 c = 0; c < 128; c++) { + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + die("failed to load X character"); + U32 texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, + face->glyph->bitmap.width, + face->glyph->bitmap.rows, + 0, GL_RED, GL_UNSIGNED_BYTE, + face->glyph->bitmap.buffer); + 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_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + chars[c] = (char_t){ + texture, + {face->glyph->bitmap.width, face->glyph->bitmap.rows}, + {face->glyph->bitmap_left, face->glyph->bitmap_top}, + face->glyph->advance.x + }; + + } + FT_Done_Face(face); + FT_Done_FreeType(ft); + + U32 shader = add_shader("glyph.vert", "glyph.frag", 0); + MAT4 projection = ortho(0.0f, state.width, 0.0f, state.height, 0.0f, 1.0f); + glUseProgram(shader); + shader_set_mat4fv(shader, "projection", projection); + glUseProgram(0); + + U32 bounds_shader = add_shader("bounds.vert", "bounds.frag", 0); + glUseProgram(bounds_shader); + shader_set_mat4fv(bounds_shader, "projection", projection); + glUseProgram(0); + + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(F32) * 6 * 4, 0, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(F32), 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + glClearColor(0.16f, 0.16f, 0.16f, 1.0f); + F32 time = 0.0f; + + V2F pos = {state.width / 2.0f, state.height / 2.0f}; + V2F vel = {0.5f, 0.5f}; + + S32 show_bounds = 0; + const char *text = "foo, bar, nope"; + F32 scale = 1.0f; + V2F size = get_text_size(text, scale); + + while (!glfwWindowShouldClose(state.window)) { + handle_glfw_events(state.window, &state.input); + F32 dt = lock_framerate(60); + + /* update */ + F32 sin_value = 0.25f * sinf(time) + 0.75f; + F32 cos_value = 0.25f * cosf(time) + 0.75f; + V3F color = v3f(sin_value, cos_value, sin_value); + F32 speed = 256.0f; + V2F new_pos = v2f_add(pos, v2f_scalef(vel, speed * dt)); + if (new_pos.x < 0.0f || new_pos.x + size.x > state.width) + vel.x = -vel.x; + if (new_pos.y < 0.0f || new_pos.y + size.y > state.height) + vel.y = -vel.y; + pos = new_pos; + + if (key_first_press(state.input.jump)) + show_bounds = !show_bounds; + + time += dt; + + /* render */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + render_text(text, shader, pos, scale, color); + if (show_bounds) + render_bounds(text, bounds_shader, pos, scale); + + glfwSwapBuffers(state.window); + } + + return 0; +} -- cgit v1.2.3-70-g09d2