diff --git a/shaders/cell.f.glsl b/shaders/cell.f.glsl index 9f17883d1..db2d4cda4 100644 --- a/shaders/cell.f.glsl +++ b/shaders/cell.f.glsl @@ -18,6 +18,7 @@ layout(location = 0) out vec4 out_FragColor; // Font texture uniform sampler2D text; +uniform sampler2D text_color; // Dimensions of the cell uniform vec2 cell_size; @@ -25,22 +26,29 @@ uniform vec2 cell_size; // See vertex shader const uint MODE_BG = 1u; const uint MODE_FG = 2u; +const uint MODE_FG_COLOR = 7u; const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_BAR = 5u; const uint MODE_UNDERLINE = 6u; void main() { + float a; + switch (mode) { case MODE_BG: out_FragColor = color; break; case MODE_FG: - float a = texture(text, glyph_tex_coords).r; + a = texture(text, glyph_tex_coords).r; out_FragColor = vec4(color.rgb, color.a*a); break; + case MODE_FG_COLOR: + out_FragColor = texture(text_color, glyph_tex_coords); + break; + case MODE_CURSOR_RECT: out_FragColor = color; break; diff --git a/shaders/cell.v.glsl b/shaders/cell.v.glsl index 629f1ffb0..bda8ee44a 100644 --- a/shaders/cell.v.glsl +++ b/shaders/cell.v.glsl @@ -6,6 +6,7 @@ // NOTE: this must be kept in sync with the fragment shader const uint MODE_BG = 1u; const uint MODE_FG = 2u; +const uint MODE_FG_COLOR = 7u; const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_BAR = 5u; @@ -50,6 +51,7 @@ flat out vec2 screen_cell_pos; flat out uint mode; uniform sampler2D text; +uniform sampler2D text_color; uniform vec2 cell_size; uniform mat4 projection; uniform float glyph_baseline; @@ -113,20 +115,41 @@ void main() { break; case MODE_FG: + case MODE_FG_COLOR: + vec2 glyph_offset_calc = glyph_offset; + + // If the glyph is larger than our cell, we need to downsample it + // TODO: for now, we assume this means it is a full width character + // TODO: in the future, use unicode libs to verify this. + vec2 glyph_size_downsampled = glyph_size; + if (glyph_size.x > cell_size.x) { + glyph_size_downsampled.x = cell_size.x * 2; + glyph_size_downsampled.y = glyph_size.y * (glyph_size_downsampled.x / glyph_size.x); + glyph_offset_calc.y = glyph_offset.y * (glyph_size_downsampled.x / glyph_size.x); + } + // The glyph_offset.y is the y bearing, a y value that when added // to the baseline is the offset (+y is up). Our grid goes down. // So we flip it with `cell_size.y - glyph_offset.y`. The glyph_baseline // uniform sets our line baseline where characters "sit". - vec2 glyph_offset_calc = glyph_offset; - glyph_offset_calc.y = cell_size.y - glyph_offset.y - glyph_baseline; + glyph_offset_calc.y = cell_size.y - glyph_offset_calc.y - glyph_baseline; // Calculate the final position of the cell. - cell_pos = cell_pos + glyph_size * position + glyph_offset_calc; + cell_pos = cell_pos + glyph_size_downsampled * position + glyph_offset_calc; gl_Position = projection * vec4(cell_pos, cell_z, 1.0); // We need to convert our texture position and size to normalized // device coordinates (0 to 1.0) by dividing by the size of the texture. - ivec2 text_size = textureSize(text, 0); + ivec2 text_size; + switch(mode_in) { + case MODE_FG: + text_size = textureSize(text, 0); + break; + + case MODE_FG_COLOR: + text_size = textureSize(text_color, 0); + break; + } vec2 glyph_tex_pos = glyph_pos / text_size; vec2 glyph_tex_size = glyph_size / text_size; glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position; diff --git a/src/Grid.zig b/src/Grid.zig index 3dadae3e2..cf8459049 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -42,9 +42,11 @@ vao: gl.VertexArray, ebo: gl.Buffer, vbo: gl.Buffer, texture: gl.Texture, +texture_color: gl.Texture, /// The font atlas. font_atlas: font.Family, +font_emoji: font.Family, atlas_dirty: bool, /// Whether the cursor is visible or not. This is used to control cursor @@ -160,6 +162,13 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { ); log.debug("cell dimensions w={d} h={d} baseline={d}", .{ cell_width, cell_height, cell_baseline }); + // Load our emoji font + var atlas_color = try Atlas.init(alloc, 512, .rgba); + errdefer atlas_color.deinit(alloc); + var fam_emoji = try font.Family.init(atlas_color); + errdefer fam_emoji.deinit(alloc); + try fam_emoji.loadFaceFromMemory(.regular, face_emoji_ttf, config.@"font-size"); + // Create our shader const program = try gl.Program.createVF( @embedFile("../shaders/cell.v.glsl"), @@ -172,6 +181,10 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { try program.setUniform("cell_size", @Vector(2, f32){ cell_width, cell_height }); try program.setUniform("glyph_baseline", cell_baseline); + // Set all of our texture indexes + try program.setUniform("text", 0); + try program.setUniform("text_emoji", 1); + // Setup our VAO const vao = try gl.VertexArray.create(); errdefer vao.destroy(); @@ -225,21 +238,44 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { // 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, - ); + { + 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, + ); + } + + // Build our color texture + const tex_color = try gl.Texture.create(); + errdefer tex_color.destroy(); + { + const texbind = try tex_color.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, + .RGBA, + @intCast(c_int, atlas_color.size), + @intCast(c_int, atlas_color.size), + 0, + .BGRA, + .UnsignedByte, + atlas_color.data.ptr, + ); + } return Grid{ .alloc = alloc, @@ -251,7 +287,9 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { .ebo = ebo, .vbo = vbo, .texture = tex, + .texture_color = tex_color, .font_atlas = fam, + .font_emoji = fam_emoji, .atlas_dirty = false, .cursor_visible = true, .cursor_style = .box, @@ -263,7 +301,10 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid { pub fn deinit(self: *Grid) void { self.font_atlas.atlas.deinit(self.alloc); self.font_atlas.deinit(self.alloc); + self.font_emoji.atlas.deinit(self.alloc); + self.font_emoji.deinit(self.alloc); self.texture.destroy(); + self.texture_color.destroy(); self.vbo.destroy(); self.ebo.destroy(); self.vao.destroy(); @@ -456,17 +497,32 @@ pub fn updateCell( else .regular; - // Get our glyph - // TODO: if we add a glyph, I think we need to rerender the texture. + var mode: u8 = 2; // MODE_FG + + // Get our glyph. Try our normal font atlas first. const glyph = if (self.font_atlas.getGlyph(cell.char, style)) |glyph| glyph else glyph: { self.atlas_dirty = true; - break :glyph try self.font_atlas.addGlyph(self.alloc, cell.char, style); + break :glyph self.font_atlas.addGlyph( + self.alloc, + cell.char, + style, + ) catch |err| switch (err) { + error.GlyphNotFound => not_found: { + mode = 7; // MODE_FG_COLOR + break :not_found try self.font_emoji.addGlyph( + self.alloc, + cell.char, + style, + ); + }, + else => return err, + }; }; self.cells.appendAssumeCapacity(.{ - .mode = 2, + .mode = mode, .grid_col = @intCast(u16, x), .grid_row = @intCast(u16, y), .glyph_x = glyph.atlas_x, @@ -537,18 +593,35 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void { /// Updates the font texture atlas if it is dirty. fn flushAtlas(self: *Grid) !void { - var texbind = try self.texture.bind(.@"2D"); - defer texbind.unbind(); - try texbind.subImage2D( - 0, - 0, - 0, - @intCast(c_int, self.font_atlas.atlas.size), - @intCast(c_int, self.font_atlas.atlas.size), - .Red, - .UnsignedByte, - self.font_atlas.atlas.data.ptr, - ); + { + var texbind = try self.texture.bind(.@"2D"); + defer texbind.unbind(); + try texbind.subImage2D( + 0, + 0, + 0, + @intCast(c_int, self.font_atlas.atlas.size), + @intCast(c_int, self.font_atlas.atlas.size), + .Red, + .UnsignedByte, + self.font_atlas.atlas.data.ptr, + ); + } + + { + var texbind = try self.texture_color.bind(.@"2D"); + defer texbind.unbind(); + try texbind.subImage2D( + 0, + 0, + 0, + @intCast(c_int, self.font_emoji.atlas.size), + @intCast(c_int, self.font_emoji.atlas.size), + .BGRA, + .UnsignedByte, + self.font_emoji.atlas.data.ptr, + ); + } } /// Render renders the current cell state. This will not modify any of @@ -603,11 +676,15 @@ pub fn render(self: *Grid) !void { assert(self.gl_cells_written <= self.cells.items.len); } - // Bind our texture + // Bind our textures try gl.Texture.active(gl.c.GL_TEXTURE0); var texbind = try self.texture.bind(.@"2D"); defer texbind.unbind(); + try gl.Texture.active(gl.c.GL_TEXTURE1); + var texbind1 = try self.texture_color.bind(.@"2D"); + defer texbind1.unbind(); + try gl.drawElementsInstanced( gl.c.GL_TRIANGLES, 6, @@ -686,3 +763,4 @@ test "GridSize update rounding" { const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf"); const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf"); +const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf"); diff --git a/src/font/Face.zig b/src/font/Face.zig index 3250ef58f..1f6cb770b 100644 --- a/src/font/Face.zig +++ b/src/font/Face.zig @@ -98,9 +98,7 @@ pub fn loadGlyph(self: Face, alloc: Allocator, atlas: *Atlas, cp: u32) !Glyph { // Unknown glyph. log.warn("glyph not found: {x}", .{cp}); - - // TODO: render something more identifiable than a space - break :glyph_index ftc.FT_Get_Char_Index(self.ft_face, ' '); + return error.GlyphNotFound; }; //log.warn("glyph index: {}", .{glyph_index}); diff --git a/src/opengl/Texture.zig b/src/opengl/Texture.zig index 810a15c3c..09bbe043f 100644 --- a/src/opengl/Texture.zig +++ b/src/opengl/Texture.zig @@ -47,6 +47,7 @@ pub const Parameter = enum(c_uint) { /// Internal format enum for texture images. pub const InternalFormat = enum(c_int) { Red = c.GL_RED, + RGBA = c.GL_RGBA, // There are so many more that I haven't filled in. _, @@ -55,6 +56,7 @@ pub const InternalFormat = enum(c_int) { /// Format for texture images pub const Format = enum(c_uint) { Red = c.GL_RED, + BGRA = c.GL_BGRA, // There are so many more that I haven't filled in. _,