From 81d8ca5b9c518954f8444829a37efddc84c1a897 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Apr 2022 17:53:19 -0700 Subject: [PATCH] render text from our terminal! --- shaders/cell.f.glsl | 13 +++++++- shaders/cell.v.glsl | 50 ++++++++++++++++++++++++++--- src/FontAtlas.zig | 1 + src/Grid.zig | 78 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 130 insertions(+), 12 deletions(-) diff --git a/shaders/cell.f.glsl b/shaders/cell.f.glsl index 9541056f6..beed2d4e2 100644 --- a/shaders/cell.f.glsl +++ b/shaders/cell.f.glsl @@ -1,8 +1,19 @@ #version 330 core +in vec2 glyph_tex_coords; + /// The background color for this cell. flat in vec4 bg_color; +/// Font texture +uniform sampler2D text; + void main() { - gl_FragColor = bg_color; + int background = 0; + if (background == 1) { + gl_FragColor = bg_color; + } else { + float a = texture(text, glyph_tex_coords).r; + gl_FragColor = vec4(bg_color.rgb, bg_color.a*a); + } } diff --git a/shaders/cell.v.glsl b/shaders/cell.v.glsl index e1fa65c68..dc86e92cb 100644 --- a/shaders/cell.v.glsl +++ b/shaders/cell.v.glsl @@ -3,17 +3,31 @@ // The grid coordinates (x, y) where x < columns and y < rows layout (location = 0) in vec2 grid_coord; +// Position of the glyph in the texture. +layout (location = 1) in vec2 glyph_pos; + +// Width/height of the glyph +layout (location = 2) in vec2 glyph_size; + +// Offset of the top-left corner of the glyph when rendered in a rect. +layout (location = 3) in vec2 glyph_offset; + // The background color for this cell in RGBA (0 to 1.0) -layout (location = 1) in vec4 bg_color_in; +layout (location = 4) in vec4 bg_color_in; // The background color for this cell in RGBA (0 to 1.0) flat out vec4 bg_color; +// The x/y coordinate for the glyph representing the font. +out vec2 glyph_tex_coords; + +uniform sampler2D text; uniform vec2 cell_size; uniform mat4 projection; void main() { // Top-left cell coordinates converted to world space + // Example: (1,0) with a 30 wide cell is converted to (30,0) vec2 cell_pos = cell_size * grid_coord; // Turn the cell position into a vertex point depending on the @@ -21,11 +35,39 @@ void main() { // for each corner of the cell. We can use gl_VertexID to determine // which one we're looking at. Using this, we can use 1 or 0 to keep // or discard the value for the vertex. + // + // 0 = top-right + // 1 = bot-right + // 2 = bot-left + // 3 = top-left vec2 position; position.x = (gl_VertexID == 0 || gl_VertexID == 1) ? 1. : 0.; position.y = (gl_VertexID == 0 || gl_VertexID == 3) ? 0. : 1.; - cell_pos = cell_pos + cell_size * position; - gl_Position = projection * vec4(cell_pos, 1.0, 1.0); - bg_color = vec4(bg_color_in.rgb / 255.0, 1.0); + int background = 0; + if (background == 1) { + // Calculate the final position of our cell in world space. + // We have to add our cell size since our vertices are offset + // one cell up and to the left. (Do the math to verify yourself) + cell_pos = cell_pos + cell_size * position; + + gl_Position = projection * vec4(cell_pos, 0.0, 1.0); + bg_color = vec4(bg_color_in.rgb / 255.0, 1.0); + } else { + // TODO: why? + vec2 glyph_offset_calc = glyph_offset; + glyph_offset_calc.y = cell_size.y - glyph_offset.y; + + // Calculate the final position of the cell. + cell_pos = cell_pos + glyph_size * position + glyph_offset_calc; + gl_Position = projection * vec4(cell_pos, 0.0, 1.0); + + // Calculate our texture coordinate + ivec2 text_size = textureSize(text, 0); + vec2 glyph_tex_size = glyph_size / text_size.xy; + glyph_tex_coords = glyph_pos + glyph_tex_size * position; + + // This is used to color the font for now. + bg_color = vec4(bg_color_in.rgb / 255.0, 1.0); + } } diff --git a/src/FontAtlas.zig b/src/FontAtlas.zig index fb56aabf9..fdf6f691f 100644 --- a/src/FontAtlas.zig +++ b/src/FontAtlas.zig @@ -191,6 +191,7 @@ fn f26dot6ToFloat(v: ftc.FT_F26Dot6) f32 { fn codepoint(v: anytype) u32 { // We need a UTF32 codepoint for freetype return switch (@TypeOf(v)) { + u32 => v, comptime_int, u8 => @intCast(u32, v), []const u8 => @intCast(u32, try std.unicode.utfDecode(v)), else => @compileError("invalid codepoint type"), diff --git a/src/Grid.zig b/src/Grid.zig index 726ae6868..aa75c78ac 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -29,6 +29,10 @@ program: gl.Program, vao: gl.VertexArray, ebo: gl.Buffer, vbo: gl.Buffer, +texture: gl.Texture, + +/// The font atlas. +font_atlas: FontAtlas, /// The raw structure that maps directly to the buffer sent to the vertex shader. const GPUCell = struct { @@ -36,6 +40,18 @@ const GPUCell = struct { grid_col: u16, grid_row: u16, + /// vec2 glyph_pos + glyph_x: f32, + glyph_y: f32, + + /// vec2 glyph_size + glyph_width: u32, + glyph_height: u32, + + /// vec2 glyph_size + glyph_offset_x: i32, + glyph_offset_y: i32, + /// vec4 bg_color_in bg_r: u8, bg_g: u8, @@ -47,9 +63,9 @@ pub fn init(alloc: Allocator) !Grid { // Initialize our font atlas. We will initially populate the // font atlas with all the visible ASCII characters since they are common. var atlas = try Atlas.init(alloc, 512); - defer atlas.deinit(alloc); + errdefer atlas.deinit(alloc); var font = try FontAtlas.init(atlas); - defer font.deinit(alloc); + errdefer font.deinit(alloc); try font.loadFaceFromMemory(face_ttf, 30); // Load all visible ASCII characters and build our cell width based on @@ -117,11 +133,42 @@ pub fn init(alloc: Allocator) !Grid { var offset: usize = 0; try vbobind.attributeAdvanced(0, 2, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(GPUCell), offset); offset += 2 * @sizeOf(u16); - try vbobind.attributeAdvanced(1, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset); + try vbobind.attributeAdvanced(1, 2, gl.c.GL_FLOAT, false, @sizeOf(GPUCell), offset); + offset += 2 * @sizeOf(f32); + try vbobind.attributeAdvanced(2, 2, gl.c.GL_UNSIGNED_INT, false, @sizeOf(GPUCell), offset); + offset += 2 * @sizeOf(u32); + try vbobind.attributeAdvanced(3, 2, gl.c.GL_INT, false, @sizeOf(GPUCell), offset); + offset += 2 * @sizeOf(i32); + try vbobind.attributeAdvanced(4, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset); try vbobind.enableAttribArray(0); try vbobind.enableAttribArray(1); + try vbobind.enableAttribArray(2); + try vbobind.enableAttribArray(3); + try vbobind.enableAttribArray(4); try vbobind.attributeDivisor(0, 1); try vbobind.attributeDivisor(1, 1); + try vbobind.attributeDivisor(2, 1); + try vbobind.attributeDivisor(3, 1); + try vbobind.attributeDivisor(4, 1); + + // Build our texture + const tex = try gl.Texture.create(); + errdefer tex.destroy(); + const texbind = try tex.bind(.@"2D"); + try texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE); + try texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE); + try texbind.parameter(.MinFilter, gl.c.GL_LINEAR); + try texbind.parameter(.MagFilter, gl.c.GL_LINEAR); + try texbind.image2D( + 0, + .Red, + @intCast(c_int, atlas.size), + @intCast(c_int, atlas.size), + 0, + .Red, + .UnsignedByte, + atlas.data.ptr, + ); return Grid{ .alloc = alloc, @@ -132,10 +179,15 @@ pub fn init(alloc: Allocator) !Grid { .vao = vao, .ebo = ebo, .vbo = vbo, + .texture = tex, + .font_atlas = font, }; } pub fn deinit(self: *Grid) void { + self.font_atlas.atlas.deinit(self.alloc); + self.font_atlas.deinit(self.alloc); + self.texture.destroy(); self.vbo.destroy(); self.ebo.destroy(); self.vao.destroy(); @@ -179,14 +231,21 @@ pub fn updateCells(self: *Grid, term: Terminal) !void { for (term.screen.items) |line, y| { for (line.items) |cell, x| { - _ = cell; + // Get our glyph + const glyph = try self.font_atlas.addGlyph(self.alloc, cell.char); self.cells.appendAssumeCapacity(.{ .grid_col = @intCast(u16, x), .grid_row = @intCast(u16, y), - .bg_r = @intCast(u8, @mod(x * y, 255)), - .bg_g = @intCast(u8, @mod(x, 255)), - .bg_b = @intCast(u8, 255 - @mod(x, 255)), + .glyph_x = glyph.s0, + .glyph_y = glyph.t0, + .glyph_width = glyph.width, + .glyph_height = glyph.height, + .glyph_offset_x = glyph.offset_x, + .glyph_offset_y = glyph.offset_y, + .bg_r = 0xFF, + .bg_g = 0xA5, + .bg_b = 0, .bg_a = 255, }); } @@ -237,6 +296,11 @@ pub fn render(self: Grid) !void { defer binding.unbind(); try binding.setData(self.cells.items, .StaticDraw); + // Bind our texture + try gl.Texture.active(gl.c.GL_TEXTURE0); + var texbind = try self.texture.bind(.@"2D"); + defer texbind.unbind(); + try gl.drawElementsInstanced( gl.c.GL_TRIANGLES, 6,