package zephr import "core:fmt" import gl "vendor:OpenGL" import FT "../freetype" Character :: struct { size: Vec2, bearing: Vec2, advance: u32, tex_coords: [4]Vec2, } Font :: struct { atlas_tex_id: TextureId, characters: [128]Character, } // GlyphInstance needs to be 100 bytes (meaning no padding) otherwise the data we send to the shader // is misaligned and we get rendering errors #assert(size_of(GlyphInstance) == 100) GlyphInstance :: struct #packed { pos: Vec4, tex_coords_idx: u32, color: Vec4, model: Mat4, } GlyphInstanceList :: [dynamic]GlyphInstance @private FONT_PIXEL_SIZE :: 100 @private LINE_HEIGHT :: 2.0 @private font_shader : Shader @private font_vao : u32 @private font_instance_vbo : u32 init_freetype :: proc(font_path: cstring) -> i32 { ft: FT.Library; if (FT.Init_FreeType(&ft) != 0) { return -1; } face: FT.Face; if (FT.New_Face(ft, font_path, 0, &face) != 0) { return -2; } // sets the variable font to be bold /* if ((face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { */ /* printf("[INFO] Got a variable font\n"); */ /* FT_MM_Var *mm; */ /* FT_Get_MM_Var(face, &mm); */ /* FT_Set_Var_Design_Coordinates(face, mm->num_namedstyles, mm->namedstyle[mm->num_namedstyles - 4].coords); */ /* FT_Done_MM_Var(ft, mm); */ /* } */ FT.Set_Pixel_Sizes(face, 0, FONT_PIXEL_SIZE); gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1); pen_x, pen_y: u32 /* FT_UInt glyph_idx; */ /* FT_ULong c = FT_Get_First_Char(face, &glyph_idx); */ tex_width, tex_height: u32; for i in 32..<128 { if (FT.Load_Char(face, cast(u64)i, .RENDER) != 0) { fmt.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); } pixels := make([dynamic]u8, tex_width * tex_height); defer delete(pixels) for i in 32..<128 { /* while (glyph_idx) { */ if (FT.Load_Char(face, cast(u64)i, .RENDER) != 0) { fmt.printf("[ERROR]: failed to load glyph for char '0x%x'\n", i); } /* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */ bmp := &face.glyph.bitmap; if (pen_x + bmp.width >= tex_width) { pen_x = 0; pen_y += cast(u32)(1 + (face.size.metrics.height >> 6)); } for row in 0.. i32 { font_vbo, font_ebo: u32; res := init_freetype(font_path); if (res != 0) { return res; } l_font_shader, success := create_shader("shaders/font.vert", "shaders/font.frag"); if (!success) { fmt.println("[FATAL] Failed to create font shader") return -2; } font_shader = l_font_shader; gl.GenVertexArrays(1, &font_vao); gl.GenBuffers(1, &font_vbo); gl.GenBuffers(1, &font_instance_vbo); gl.GenBuffers(1, &font_ebo); quad_vertices := [4][2]f32 { {0.0, 1.0}, // top left {1.0, 1.0}, // top right {1.0, 0.0}, // bottom right {0.0, 0.0}, // bottom left }; quad_indices := [6]u32 { 0, 1, 2, 2, 3, 0, }; gl.BindVertexArray(font_vao); // font quad vbo gl.BindBuffer(gl.ARRAY_BUFFER, font_vbo); gl.BufferData(gl.ARRAY_BUFFER, size_of(quad_vertices), &quad_vertices, gl.STATIC_DRAW); gl.EnableVertexAttribArray(0); gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 2 * size_of(f32), 0); // font instance vbo gl.BindBuffer(gl.ARRAY_BUFFER, font_instance_vbo); gl.EnableVertexAttribArray(1); gl.VertexAttribPointer(1, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), 0); gl.VertexAttribDivisor(1, 1); gl.EnableVertexAttribArray(2); gl.VertexAttribIPointer(2, 1, gl.INT, size_of(GlyphInstance), size_of(Vec4)); gl.VertexAttribDivisor(2, 1); gl.EnableVertexAttribArray(3); gl.VertexAttribPointer(3, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) + size_of(i32))); gl.VertexAttribDivisor(3, 1); gl.EnableVertexAttribArray(4); gl.EnableVertexAttribArray(5); gl.EnableVertexAttribArray(6); gl.EnableVertexAttribArray(7); gl.VertexAttribPointer(4, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 2 + size_of(i32))); gl.VertexAttribPointer(5, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 3 + size_of(i32))); gl.VertexAttribPointer(6, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 4 + size_of(i32))); gl.VertexAttribPointer(7, 4, gl.FLOAT, gl.FALSE, size_of(GlyphInstance), (size_of(Vec4) * 5 + size_of(i32))); gl.VertexAttribDivisor(4, 1); gl.VertexAttribDivisor(5, 1); gl.VertexAttribDivisor(6, 1); gl.VertexAttribDivisor(7, 1); // font ebo gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, font_ebo); gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size_of(quad_indices), &quad_indices, gl.STATIC_DRAW); use_shader(font_shader); for i in 32..<128 { for j in 0..<4 { buf: [24]byte text := fmt.bprintf(buf[:], "texcoords[%d]", (i - 32) * 4 + j); set_vec2f(font_shader, cstring(raw_data(text)), zephr_ctx.font.characters[i].tex_coords[j].x, zephr_ctx.font.characters[i].tex_coords[j].y); } } gl.BindBuffer(gl.ARRAY_BUFFER, 0); gl.BindVertexArray(0); return 0; } calculate_text_size :: proc(text: string, font_size: u32) -> Vec2 { scale := cast(f32)font_size / FONT_PIXEL_SIZE; size : Vec2 = --- w: f32 = 0; h: f32 = 0; max_bearing_h: f32 = 0; // NOTE: I don't like looping through the characters twice, but it's fine for now for i in 0.. 1) { w -= ch.bearing.x; } // remove the trailing width of the last character if (i == len(text) - 1) { w -= ((cast(f32)ch.advance / 64) - (ch.bearing.x + ch.size.x)); } // if we only have one character in the text, then remove the bearing width if (len(text) == 1) { w -= (ch.bearing.x); } h = max(h, max_bearing_h - ch.bearing.y + ch.size.y); if (text[i] == '\n') { w = 0; h += max_bearing_h; } } size.x = w * scale; size.y = h * scale; return size; } draw_text :: proc(text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) { glyph_instance_list := get_glyph_instance_list_from_text(text, font_size, constraints, color, alignment); gl.ActiveTexture(gl.TEXTURE0); gl.BindTexture(gl.TEXTURE_2D, zephr_ctx.font.atlas_tex_id); gl.BindVertexArray(font_vao); gl.BindBuffer(gl.ARRAY_BUFFER, font_instance_vbo); gl.BufferData(gl.ARRAY_BUFFER, size_of(GlyphInstance) * len(glyph_instance_list), raw_data(glyph_instance_list), gl.DYNAMIC_DRAW); gl.DrawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil, cast(i32)len(glyph_instance_list)) gl.BindVertexArray(0); gl.BindTexture(gl.TEXTURE_2D, 0); delete(glyph_instance_list) } draw_text_batch :: proc(batch: ^GlyphInstanceList) { gl.ActiveTexture(gl.TEXTURE0); gl.BindTexture(gl.TEXTURE_2D, zephr_ctx.font.atlas_tex_id); gl.BindVertexArray(font_vao); gl.BindBuffer(gl.ARRAY_BUFFER, font_instance_vbo); gl.BufferData(gl.ARRAY_BUFFER, size_of(GlyphInstance) * len(batch), raw_data(batch^), gl.DYNAMIC_DRAW); gl.DrawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_INT, nil, cast(i32)len(batch)) gl.BindVertexArray(0); gl.BindTexture(gl.TEXTURE_2D, 0); delete(batch^) } get_glyph_instance_list_from_text :: proc(text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) -> GlyphInstanceList { constraints := constraints use_shader(font_shader); text_color := Vec4{ cast(f32)color.r / 255, cast(f32)color.g / 255, cast(f32)color.b / 255, cast(f32)color.a / 255 }; set_mat4f(font_shader, "projection", zephr_ctx.projection) rect: Rect apply_constraints(&constraints, &rect.pos, &rect.size); text_size := calculate_text_size(text, FONT_PIXEL_SIZE); font_scale := cast(f32)font_size / FONT_PIXEL_SIZE * rect.size.x; apply_alignment(alignment, &constraints, Vec2{ text_size.x * font_scale, text_size.y * font_scale }, &rect.pos); model := identity() scale(&model, Vec3{font_scale, font_scale, 1}); // rotate around the center point of the text translate(&model, Vec3{-text_size.x * font_scale / 2, -text_size.y * font_scale / 2, 0}); rotate(&model, constraints.rotation, Vec3{0, 0, 1}); translate(&model, Vec3{text_size.x *font_scale / 2, text_size.y * font_scale / 2, 0}); translate(&model, Vec3{rect.pos.x, rect.pos.y, 0}); max_bearing_h: f32 = 0; for i in 0..> 6); c += 1; } return glyph_instance_list; } add_text_instance :: proc(batch: ^GlyphInstanceList, text: string, font_size: u32, constraints: UiConstraints, color: Color, alignment: Alignment) { glyph_instance_list := get_glyph_instance_list_from_text(text, font_size, constraints, color, alignment); append(batch, ..glyph_instance_list[:]) delete(glyph_instance_list); }