diff --git a/shaders/text-atlas.f.glsl b/shaders/text-atlas.f.glsl new file mode 100644 index 000000000..e5802b123 --- /dev/null +++ b/shaders/text-atlas.f.glsl @@ -0,0 +1,12 @@ +#version 330 core + +in vec2 TexCoords; +in vec4 VertexColor; + +uniform sampler2D text; + +void main() +{ + float a = texture(text, TexCoords).r; + gl_FragColor = vec4(VertexColor.rgb, VertexColor.a*a); +} diff --git a/shaders/text-atlas.v.glsl b/shaders/text-atlas.v.glsl new file mode 100644 index 000000000..79c8d5613 --- /dev/null +++ b/shaders/text-atlas.v.glsl @@ -0,0 +1,17 @@ +#version 330 core + +layout (location = 0) in vec3 vertex; +layout (location = 1) in vec2 tex_coord; +layout (location = 2) in vec4 color; + +out vec2 TexCoords; +out vec4 VertexColor; + +uniform mat4 projection; + +void main() +{ + gl_Position = projection * vec4(vertex, 1.0); + TexCoords = tex_coord.xy; + VertexColor = color; +} diff --git a/src/App.zig b/src/App.zig index 0e16ea4ce..77e7f7b15 100644 --- a/src/App.zig +++ b/src/App.zig @@ -74,7 +74,8 @@ pub fn run(self: App) !void { gl.clearColor(0.2, 0.3, 0.3, 1.0); gl.clear(gl.c.GL_COLOR_BUFFER_BIT); - try self.text.render("sh $ /bin/bash -c \"echo hello\"", 25.0, 25.0, 1.0, .{ 0.5, 0.8, 0.2 }); + try self.text.render("sh $ /bin/bash -c \"echo hello\"", 25.0, 25.0, .{ 0.5, 0.8, 0.2 }); + //try self.text.render("hi", 25.0, 25.0, .{ 0.5, 0.8, 0.2 }); try self.window.swapBuffers(); try glfw.waitEvents(); diff --git a/src/TextRenderer.zig b/src/TextRenderer.zig index f93a6ef7a..87e4d9979 100644 --- a/src/TextRenderer.zig +++ b/src/TextRenderer.zig @@ -4,15 +4,15 @@ const std = @import("std"); const ftc = @import("freetype/c.zig"); const gl = @import("opengl.zig"); const gb = @import("gb_math.zig"); +const ftgl = @import("freetype-gl/c.zig"); alloc: std.mem.Allocator, -ft: ftc.FT_Library, -face: ftc.FT_Face, -chars: CharList, -vao: gl.VertexArray = undefined, -vbo: gl.Buffer = undefined, -program: gl.Program = undefined, projection: gb.gbMat4 = undefined, +font: *ftgl.texture_font_t, +atlas: *ftgl.texture_atlas_t, + +program: gl.Program, +tex: gl.Texture, const CharList = std.ArrayListUnmanaged(Char); const Char = struct { @@ -23,96 +23,58 @@ const Char = struct { }; pub fn init(alloc: std.mem.Allocator) !TextRenderer { - var ft: ftc.FT_Library = undefined; - if (ftc.FT_Init_FreeType(&ft) != 0) { - return error.FreetypeInitFailed; - } - - var face: ftc.FT_Face = undefined; - if (ftc.FT_New_Memory_Face( - ft, + const atlas = ftgl.texture_atlas_new(512, 512, 1); + if (atlas == null) return error.FontAtlasFail; + errdefer ftgl.texture_atlas_delete(atlas); + const font = ftgl.texture_font_new_from_memory( + atlas, + 48, face_ttf, face_ttf.len, - 0, - &face, - ) != 0) { - return error.FreetypeFaceFailed; - } + ); + if (font == null) return error.FontInitFail; + errdefer ftgl.texture_font_delete(font); - _ = ftc.FT_Set_Pixel_Sizes(face, 0, 48); - - // disable byte-alignment restriction - try gl.pixelStore(gl.c.GL_UNPACK_ALIGNMENT, 1); - - // Pre-render all the ASCII characters - var chars = try CharList.initCapacity(alloc, 128); - var i: usize = 0; - while (i < chars.capacity) : (i += 1) { + // Load all visible ASCII characters. + var i: u8 = 32; + while (i < 127) : (i += 1) { // Load the character - if (ftc.FT_Load_Char(face, i, ftc.FT_LOAD_RENDER) != 0) { + if (ftgl.texture_font_load_glyph(font, &i) == 0) { return error.GlyphLoadFailed; } - - // Generate the texture - const tex = try gl.Texture.create(); - var binding = try tex.bind(.@"2D"); - defer binding.unbind(); - try binding.image2D( - 0, - .Red, - @intCast(c_int, face.*.glyph.*.bitmap.width), - @intCast(c_int, face.*.glyph.*.bitmap.rows), - 0, - .Red, - .UnsignedByte, - face.*.glyph.*.bitmap.buffer, - ); - try binding.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE); - try binding.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE); - try binding.parameter(.MinFilter, gl.c.GL_LINEAR); - try binding.parameter(.MagFilter, gl.c.GL_LINEAR); - - // Store the character - chars.appendAssumeCapacity(.{ - .tex = tex, - .size = .{ - @intToFloat(f32, face.*.glyph.*.bitmap.width), - @intToFloat(f32, face.*.glyph.*.bitmap.rows), - }, - .bearing = .{ - @intToFloat(f32, face.*.glyph.*.bitmap_left), - @intToFloat(f32, face.*.glyph.*.bitmap_top), - }, - .advance = @intCast(c_uint, face.*.glyph.*.advance.x), - }); } - // Configure VAO/VBO for glyph rendering - const vao = try gl.VertexArray.create(); - const vbo = try gl.Buffer.create(); - try vao.bind(); - var binding = try vbo.bind(.ArrayBuffer); - try binding.setDataNull([6 * 4]f32, .DynamicDraw); - try binding.enableVertexAttribArray(0); - try binding.vertexAttribPointer(0, 4, gl.c.GL_FLOAT, false, 4 * @sizeOf(f32), null); - binding.unbind(); - try gl.VertexArray.unbind(); + // Build our texture + const tex = try gl.Texture.create(); + errdefer tex.destroy(); + const binding = try tex.bind(.@"2D"); + try binding.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE); + try binding.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE); + try binding.parameter(.MinFilter, gl.c.GL_LINEAR); + try binding.parameter(.MagFilter, gl.c.GL_LINEAR); + try binding.image2D( + 0, + .Red, + @intCast(c_int, atlas.*.width), + @intCast(c_int, atlas.*.height), + 0, + .Red, + .UnsignedByte, + atlas.*.data, + ); // Create our shader const program = try gl.Program.createVF( - @embedFile("../shaders/text.v.glsl"), - @embedFile("../shaders/text.f.glsl"), + @embedFile("../shaders/text-atlas.v.glsl"), + @embedFile("../shaders/text-atlas.f.glsl"), ); var res = TextRenderer{ .alloc = alloc, - .ft = ft, - .face = face, - .chars = chars, + .font = font, + .atlas = atlas, .program = program, - .vao = vao, - .vbo = vbo, - .projection = undefined, + .tex = tex, }; // Update the initialize size so we have some projection. We @@ -123,14 +85,8 @@ pub fn init(alloc: std.mem.Allocator) !TextRenderer { } pub fn deinit(self: *TextRenderer) void { - // TODO: delete textures - self.chars.deinit(self.alloc); - - if (ftc.FT_Done_Face(self.face) != 0) - std.log.err("freetype face deinitialization failed", .{}); - if (ftc.FT_Done_FreeType(self.ft) != 0) - std.log.err("freetype library deinitialization failed", .{}); - + ftgl.texture_font_delete(self.font); + ftgl.texture_atlas_delete(self.atlas); self.* = undefined; } @@ -152,44 +108,74 @@ pub fn render( text: []const u8, x: f32, y: f32, - scale: f32, color: @Vector(3, f32), ) !void { - try self.program.use(); - try self.program.setUniform("textColor", color); - try gl.Texture.active(gl.c.GL_TEXTURE0); - try self.vao.bind(); + const r = color[0]; + const g = color[1]; + const b = color[2]; + const a: f32 = 1.0; + + var vertices: std.ArrayListUnmanaged([6][9]f32) = .{}; + try vertices.ensureUnusedCapacity(self.alloc, text.len); + defer vertices.deinit(self.alloc); var curx: f32 = x; for (text) |c| { - const char = self.chars.items[c]; + if (ftgl.texture_font_get_glyph(self.font, &c)) |glyph_ptr| { + const glyph = glyph_ptr.*; + const kerning = 0; // for now + curx += kerning; - const xpos = curx + (char.bearing[0] * scale); - const ypos = y - ((char.size[1] - char.bearing[1]) * scale); - const w = char.size[0] * scale; - const h = char.size[1] * scale; + const x0 = curx + @intToFloat(f32, glyph.offset_x); + const y0 = y + @intToFloat(f32, glyph.offset_y); + const x1 = x0 + @intToFloat(f32, glyph.width); + const y1 = y0 - @intToFloat(f32, glyph.height); + const s0 = glyph.s0; + const t0 = glyph.t0; + const s1 = glyph.s1; + const t1 = glyph.t1; - const vert = [6][4]f32{ - .{ xpos, ypos + h, 0.0, 0.0 }, - .{ xpos, ypos, 0.0, 1.0 }, - .{ xpos + w, ypos, 1.0, 1.0 }, + std.log.info("CHAR ch={} x0={} y0={} x1={} y1={}", .{ c, x0, y0, x1, y1 }); - .{ xpos, ypos + h, 0.0, 0.0 }, - .{ xpos + w, ypos, 1.0, 1.0 }, - .{ xpos + w, ypos + h, 1.0, 0.0 }, - }; + const vert = [6][9]f32{ + .{ x0, y0, 0, s0, t0, r, g, b, a }, + .{ x0, y1, 0, s0, t1, r, g, b, a }, + .{ x1, y1, 0, s1, t1, r, g, b, a }, + .{ x0, y0, 0, s0, t0, r, g, b, a }, + .{ x1, y1, 0, s1, t1, r, g, b, a }, + .{ x1, y0, 0, s1, t0, r, g, b, a }, + }; - var texbind = try char.tex.bind(.@"2D"); - defer texbind.unbind(); - var bind = try self.vbo.bind(.ArrayBuffer); - try bind.setSubData(0, vert); - bind.unbind(); + vertices.appendAssumeCapacity(vert); - try gl.drawArrays(gl.c.GL_TRIANGLES, 0, 6); - - curx += @intToFloat(f32, char.advance >> 6) * scale; + curx += glyph.advance_x; + } } + try self.program.use(); + + // Bind our texture and set our data + try gl.Texture.active(gl.c.GL_TEXTURE0); + var texbind = try self.tex.bind(.@"2D"); + defer texbind.unbind(); + + // Configure VAO/VBO for glyph rendering + const vao = try gl.VertexArray.create(); + defer vao.destroy(); + try vao.bind(); + const vbo = try gl.Buffer.create(); + defer vbo.destroy(); + var binding = try vbo.bind(.ArrayBuffer); + defer binding.unbind(); + try binding.setData(vertices.items, .DynamicDraw); + try binding.enableVertexAttribArray(0); + try binding.vertexAttribPointer(0, 3, gl.c.GL_FLOAT, false, 9 * @sizeOf(f32), null); + try binding.enableVertexAttribArray(1); + try binding.vertexAttribPointer(1, 2, gl.c.GL_FLOAT, false, 9 * @sizeOf(f32), @intToPtr(*const anyopaque, 3 * @sizeOf(f32))); + try binding.enableVertexAttribArray(2); + try binding.vertexAttribPointer(2, 4, gl.c.GL_FLOAT, false, 9 * @sizeOf(f32), @intToPtr(*const anyopaque, 5 * @sizeOf(f32))); + + try gl.drawArrays(gl.c.GL_TRIANGLES, 0, @intCast(c_int, vertices.items.len * 6)); try gl.VertexArray.unbind(); } diff --git a/src/TextRenderer2.zig b/src/TextRenderer2.zig new file mode 100644 index 000000000..f93a6ef7a --- /dev/null +++ b/src/TextRenderer2.zig @@ -0,0 +1,196 @@ +const TextRenderer = @This(); + +const std = @import("std"); +const ftc = @import("freetype/c.zig"); +const gl = @import("opengl.zig"); +const gb = @import("gb_math.zig"); + +alloc: std.mem.Allocator, +ft: ftc.FT_Library, +face: ftc.FT_Face, +chars: CharList, +vao: gl.VertexArray = undefined, +vbo: gl.Buffer = undefined, +program: gl.Program = undefined, +projection: gb.gbMat4 = undefined, + +const CharList = std.ArrayListUnmanaged(Char); +const Char = struct { + tex: gl.Texture, + size: @Vector(2, f32), + bearing: @Vector(2, f32), + advance: c_uint, +}; + +pub fn init(alloc: std.mem.Allocator) !TextRenderer { + var ft: ftc.FT_Library = undefined; + if (ftc.FT_Init_FreeType(&ft) != 0) { + return error.FreetypeInitFailed; + } + + var face: ftc.FT_Face = undefined; + if (ftc.FT_New_Memory_Face( + ft, + face_ttf, + face_ttf.len, + 0, + &face, + ) != 0) { + return error.FreetypeFaceFailed; + } + + _ = ftc.FT_Set_Pixel_Sizes(face, 0, 48); + + // disable byte-alignment restriction + try gl.pixelStore(gl.c.GL_UNPACK_ALIGNMENT, 1); + + // Pre-render all the ASCII characters + var chars = try CharList.initCapacity(alloc, 128); + var i: usize = 0; + while (i < chars.capacity) : (i += 1) { + // Load the character + if (ftc.FT_Load_Char(face, i, ftc.FT_LOAD_RENDER) != 0) { + return error.GlyphLoadFailed; + } + + // Generate the texture + const tex = try gl.Texture.create(); + var binding = try tex.bind(.@"2D"); + defer binding.unbind(); + try binding.image2D( + 0, + .Red, + @intCast(c_int, face.*.glyph.*.bitmap.width), + @intCast(c_int, face.*.glyph.*.bitmap.rows), + 0, + .Red, + .UnsignedByte, + face.*.glyph.*.bitmap.buffer, + ); + try binding.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE); + try binding.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE); + try binding.parameter(.MinFilter, gl.c.GL_LINEAR); + try binding.parameter(.MagFilter, gl.c.GL_LINEAR); + + // Store the character + chars.appendAssumeCapacity(.{ + .tex = tex, + .size = .{ + @intToFloat(f32, face.*.glyph.*.bitmap.width), + @intToFloat(f32, face.*.glyph.*.bitmap.rows), + }, + .bearing = .{ + @intToFloat(f32, face.*.glyph.*.bitmap_left), + @intToFloat(f32, face.*.glyph.*.bitmap_top), + }, + .advance = @intCast(c_uint, face.*.glyph.*.advance.x), + }); + } + + // Configure VAO/VBO for glyph rendering + const vao = try gl.VertexArray.create(); + const vbo = try gl.Buffer.create(); + try vao.bind(); + var binding = try vbo.bind(.ArrayBuffer); + try binding.setDataNull([6 * 4]f32, .DynamicDraw); + try binding.enableVertexAttribArray(0); + try binding.vertexAttribPointer(0, 4, gl.c.GL_FLOAT, false, 4 * @sizeOf(f32), null); + binding.unbind(); + try gl.VertexArray.unbind(); + + // Create our shader + const program = try gl.Program.createVF( + @embedFile("../shaders/text.v.glsl"), + @embedFile("../shaders/text.f.glsl"), + ); + + var res = TextRenderer{ + .alloc = alloc, + .ft = ft, + .face = face, + .chars = chars, + .program = program, + .vao = vao, + .vbo = vbo, + .projection = undefined, + }; + + // Update the initialize size so we have some projection. We + // expect this will get updated almost immediately. + try res.setScreenSize(3000, 1666); + + return res; +} + +pub fn deinit(self: *TextRenderer) void { + // TODO: delete textures + self.chars.deinit(self.alloc); + + if (ftc.FT_Done_Face(self.face) != 0) + std.log.err("freetype face deinitialization failed", .{}); + if (ftc.FT_Done_FreeType(self.ft) != 0) + std.log.err("freetype library deinitialization failed", .{}); + + self.* = undefined; +} + +pub fn setScreenSize(self: *TextRenderer, w: i32, h: i32) !void { + gb.gb_mat4_ortho2d( + &self.projection, + 0, + @intToFloat(f32, w), + 0, + @intToFloat(f32, h), + ); + + try self.program.use(); + try self.program.setUniform("projection", self.projection); +} + +pub fn render( + self: TextRenderer, + text: []const u8, + x: f32, + y: f32, + scale: f32, + color: @Vector(3, f32), +) !void { + try self.program.use(); + try self.program.setUniform("textColor", color); + try gl.Texture.active(gl.c.GL_TEXTURE0); + try self.vao.bind(); + + var curx: f32 = x; + for (text) |c| { + const char = self.chars.items[c]; + + const xpos = curx + (char.bearing[0] * scale); + const ypos = y - ((char.size[1] - char.bearing[1]) * scale); + const w = char.size[0] * scale; + const h = char.size[1] * scale; + + const vert = [6][4]f32{ + .{ xpos, ypos + h, 0.0, 0.0 }, + .{ xpos, ypos, 0.0, 1.0 }, + .{ xpos + w, ypos, 1.0, 1.0 }, + + .{ xpos, ypos + h, 0.0, 0.0 }, + .{ xpos + w, ypos, 1.0, 1.0 }, + .{ xpos + w, ypos + h, 1.0, 0.0 }, + }; + + var texbind = try char.tex.bind(.@"2D"); + defer texbind.unbind(); + var bind = try self.vbo.bind(.ArrayBuffer); + try bind.setSubData(0, vert); + bind.unbind(); + + try gl.drawArrays(gl.c.GL_TRIANGLES, 0, 6); + + curx += @intToFloat(f32, char.advance >> 6) * scale; + } + + try gl.VertexArray.unbind(); +} + +const face_ttf = @embedFile("../fonts/Inconsolata-Regular.ttf"); diff --git a/src/freetype-gl/build.zig b/src/freetype-gl/build.zig index 5d6988a6f..e5a4f7642 100644 --- a/src/freetype-gl/build.zig +++ b/src/freetype-gl/build.zig @@ -24,6 +24,8 @@ pub fn link( var flags = std.ArrayList([]const u8).init(b.allocator); defer flags.deinit(); try flags.appendSlice(&.{ + "-std=c99", + "-O0", "-DGL_WITH_GLAD", }); diff --git a/src/freetype-gl/c.zig b/src/freetype-gl/c.zig new file mode 100644 index 000000000..978c213ea --- /dev/null +++ b/src/freetype-gl/c.zig @@ -0,0 +1,4 @@ +pub usingnamespace @cImport({ + @cInclude("texture-atlas.h"); + @cInclude("texture-font.h"); +}); diff --git a/src/opengl/Buffer.zig b/src/opengl/Buffer.zig index 3797bbb03..5f5afe070 100644 --- a/src/opengl/Buffer.zig +++ b/src/opengl/Buffer.zig @@ -40,6 +40,9 @@ pub const Binding = struct { usage: Usage, ) !void { const info = dataInfo(data); + std.log.info("SET DATA {}", .{ + info.size, + }); c.glBufferData(@enumToInt(b.target), info.size, info.ptr, @enumToInt(usage)); try errors.getError(); } @@ -83,7 +86,7 @@ pub const Binding = struct { .ptr = data, }, .Slice => .{ - .size = @sizeOf(ptr.child) * data.len, + .size = @intCast(isize, @sizeOf(ptr.child) * data.len), .ptr = data.ptr, }, else => {