#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; }