mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
really hacked in emoji support, time to clean it up
This commit is contained in:
@ -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;
|
||||
|
@ -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;
|
||||
|
88
src/Grid.zig
88
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,6 +238,7 @@ 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);
|
||||
@ -240,6 +254,28 @@ pub fn init(alloc: Allocator, config: *const Config) !Grid {
|
||||
.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,6 +593,7 @@ 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(
|
||||
@ -551,6 +608,22 @@ fn flushAtlas(self: *Grid) !void {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
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
|
||||
/// the cells.
|
||||
pub fn render(self: *Grid) !void {
|
||||
@ -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");
|
||||
|
@ -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});
|
||||
|
||||
|
@ -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.
|
||||
_,
|
||||
|
Reference in New Issue
Block a user