really hacked in emoji support, time to clean it up

This commit is contained in:
Mitchell Hashimoto
2022-08-19 21:03:04 -07:00
parent 3d6ca0e423
commit 22ed65a818
5 changed files with 149 additions and 40 deletions

View File

@ -18,6 +18,7 @@ layout(location = 0) out vec4 out_FragColor;
// Font texture // Font texture
uniform sampler2D text; uniform sampler2D text;
uniform sampler2D text_color;
// Dimensions of the cell // Dimensions of the cell
uniform vec2 cell_size; uniform vec2 cell_size;
@ -25,22 +26,29 @@ uniform vec2 cell_size;
// See vertex shader // See vertex shader
const uint MODE_BG = 1u; const uint MODE_BG = 1u;
const uint MODE_FG = 2u; const uint MODE_FG = 2u;
const uint MODE_FG_COLOR = 7u;
const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT = 3u;
const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_RECT_HOLLOW = 4u;
const uint MODE_CURSOR_BAR = 5u; const uint MODE_CURSOR_BAR = 5u;
const uint MODE_UNDERLINE = 6u; const uint MODE_UNDERLINE = 6u;
void main() { void main() {
float a;
switch (mode) { switch (mode) {
case MODE_BG: case MODE_BG:
out_FragColor = color; out_FragColor = color;
break; break;
case MODE_FG: 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); out_FragColor = vec4(color.rgb, color.a*a);
break; break;
case MODE_FG_COLOR:
out_FragColor = texture(text_color, glyph_tex_coords);
break;
case MODE_CURSOR_RECT: case MODE_CURSOR_RECT:
out_FragColor = color; out_FragColor = color;
break; break;

View File

@ -6,6 +6,7 @@
// NOTE: this must be kept in sync with the fragment shader // NOTE: this must be kept in sync with the fragment shader
const uint MODE_BG = 1u; const uint MODE_BG = 1u;
const uint MODE_FG = 2u; const uint MODE_FG = 2u;
const uint MODE_FG_COLOR = 7u;
const uint MODE_CURSOR_RECT = 3u; const uint MODE_CURSOR_RECT = 3u;
const uint MODE_CURSOR_RECT_HOLLOW = 4u; const uint MODE_CURSOR_RECT_HOLLOW = 4u;
const uint MODE_CURSOR_BAR = 5u; const uint MODE_CURSOR_BAR = 5u;
@ -50,6 +51,7 @@ flat out vec2 screen_cell_pos;
flat out uint mode; flat out uint mode;
uniform sampler2D text; uniform sampler2D text;
uniform sampler2D text_color;
uniform vec2 cell_size; uniform vec2 cell_size;
uniform mat4 projection; uniform mat4 projection;
uniform float glyph_baseline; uniform float glyph_baseline;
@ -113,20 +115,41 @@ void main() {
break; break;
case MODE_FG: 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 // 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. // 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 // So we flip it with `cell_size.y - glyph_offset.y`. The glyph_baseline
// uniform sets our line baseline where characters "sit". // uniform sets our line baseline where characters "sit".
vec2 glyph_offset_calc = glyph_offset; glyph_offset_calc.y = cell_size.y - glyph_offset_calc.y - glyph_baseline;
glyph_offset_calc.y = cell_size.y - glyph_offset.y - glyph_baseline;
// Calculate the final position of the cell. // 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); gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
// We need to convert our texture position and size to normalized // 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. // 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_pos = glyph_pos / text_size;
vec2 glyph_tex_size = glyph_size / text_size; vec2 glyph_tex_size = glyph_size / text_size;
glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position; glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position;

View File

@ -42,9 +42,11 @@ vao: gl.VertexArray,
ebo: gl.Buffer, ebo: gl.Buffer,
vbo: gl.Buffer, vbo: gl.Buffer,
texture: gl.Texture, texture: gl.Texture,
texture_color: gl.Texture,
/// The font atlas. /// The font atlas.
font_atlas: font.Family, font_atlas: font.Family,
font_emoji: font.Family,
atlas_dirty: bool, atlas_dirty: bool,
/// Whether the cursor is visible or not. This is used to control cursor /// 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 }); 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 // Create our shader
const program = try gl.Program.createVF( const program = try gl.Program.createVF(
@embedFile("../shaders/cell.v.glsl"), @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("cell_size", @Vector(2, f32){ cell_width, cell_height });
try program.setUniform("glyph_baseline", cell_baseline); 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 // Setup our VAO
const vao = try gl.VertexArray.create(); const vao = try gl.VertexArray.create();
errdefer vao.destroy(); errdefer vao.destroy();
@ -225,6 +238,7 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid {
// Build our texture // Build our texture
const tex = try gl.Texture.create(); const tex = try gl.Texture.create();
errdefer tex.destroy(); errdefer tex.destroy();
{
const texbind = try tex.bind(.@"2D"); const texbind = try tex.bind(.@"2D");
try texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE); try texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE);
try texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE); try texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE);
@ -240,6 +254,28 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid {
.UnsignedByte, .UnsignedByte,
atlas.data.ptr, 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{ return Grid{
.alloc = alloc, .alloc = alloc,
@ -251,7 +287,9 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid {
.ebo = ebo, .ebo = ebo,
.vbo = vbo, .vbo = vbo,
.texture = tex, .texture = tex,
.texture_color = tex_color,
.font_atlas = fam, .font_atlas = fam,
.font_emoji = fam_emoji,
.atlas_dirty = false, .atlas_dirty = false,
.cursor_visible = true, .cursor_visible = true,
.cursor_style = .box, .cursor_style = .box,
@ -263,7 +301,10 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid {
pub fn deinit(self: *Grid) void { pub fn deinit(self: *Grid) void {
self.font_atlas.atlas.deinit(self.alloc); self.font_atlas.atlas.deinit(self.alloc);
self.font_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.destroy();
self.texture_color.destroy();
self.vbo.destroy(); self.vbo.destroy();
self.ebo.destroy(); self.ebo.destroy();
self.vao.destroy(); self.vao.destroy();
@ -456,17 +497,32 @@ pub fn updateCell(
else else
.regular; .regular;
// Get our glyph var mode: u8 = 2; // MODE_FG
// TODO: if we add a glyph, I think we need to rerender the texture.
// Get our glyph. Try our normal font atlas first.
const glyph = if (self.font_atlas.getGlyph(cell.char, style)) |glyph| const glyph = if (self.font_atlas.getGlyph(cell.char, style)) |glyph|
glyph glyph
else glyph: { else glyph: {
self.atlas_dirty = true; 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(.{ self.cells.appendAssumeCapacity(.{
.mode = 2, .mode = mode,
.grid_col = @intCast(u16, x), .grid_col = @intCast(u16, x),
.grid_row = @intCast(u16, y), .grid_row = @intCast(u16, y),
.glyph_x = glyph.atlas_x, .glyph_x = glyph.atlas_x,
@ -537,6 +593,7 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void {
/// Updates the font texture atlas if it is dirty. /// Updates the font texture atlas if it is dirty.
fn flushAtlas(self: *Grid) !void { fn flushAtlas(self: *Grid) !void {
{
var texbind = try self.texture.bind(.@"2D"); var texbind = try self.texture.bind(.@"2D");
defer texbind.unbind(); defer texbind.unbind();
try texbind.subImage2D( try texbind.subImage2D(
@ -549,6 +606,22 @@ fn flushAtlas(self: *Grid) !void {
.UnsignedByte, .UnsignedByte,
self.font_atlas.atlas.data.ptr, 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 /// 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); assert(self.gl_cells_written <= self.cells.items.len);
} }
// Bind our texture // Bind our textures
try gl.Texture.active(gl.c.GL_TEXTURE0); try gl.Texture.active(gl.c.GL_TEXTURE0);
var texbind = try self.texture.bind(.@"2D"); var texbind = try self.texture.bind(.@"2D");
defer texbind.unbind(); 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( try gl.drawElementsInstanced(
gl.c.GL_TRIANGLES, gl.c.GL_TRIANGLES,
6, 6,
@ -686,3 +763,4 @@ test "GridSize update rounding" {
const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf"); const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf");
const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf"); const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf");
const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");

View File

@ -98,9 +98,7 @@ pub fn loadGlyph(self: Face, alloc: Allocator, atlas: *Atlas, cp: u32) !Glyph {
// Unknown glyph. // Unknown glyph.
log.warn("glyph not found: {x}", .{cp}); log.warn("glyph not found: {x}", .{cp});
return error.GlyphNotFound;
// TODO: render something more identifiable than a space
break :glyph_index ftc.FT_Get_Char_Index(self.ft_face, ' ');
}; };
//log.warn("glyph index: {}", .{glyph_index}); //log.warn("glyph index: {}", .{glyph_index});

View File

@ -47,6 +47,7 @@ pub const Parameter = enum(c_uint) {
/// Internal format enum for texture images. /// Internal format enum for texture images.
pub const InternalFormat = enum(c_int) { pub const InternalFormat = enum(c_int) {
Red = c.GL_RED, Red = c.GL_RED,
RGBA = c.GL_RGBA,
// There are so many more that I haven't filled in. // 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 /// Format for texture images
pub const Format = enum(c_uint) { pub const Format = enum(c_uint) {
Red = c.GL_RED, Red = c.GL_RED,
BGRA = c.GL_BGRA,
// There are so many more that I haven't filled in. // There are so many more that I haven't filled in.
_, _,