mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
renderer(opengl): implement blinking cells, make render modes a bitfield
The render modes in the vertex shader has always been... strange. It kind of functioned like an enum and a bitfield at the same time. To comfortably accommodate adding blink information on cells, I've refactored it to make it into a true bitfield -- this made the logic on the Zig side much simpler, with the tradeoff being that logic is slightly harder to read in the shader code as GLSL does not support implicitly casting integers after bitmasking to booleans. The blink is currently synchronized to the cursor blinking (which is what most other terminal emulators do), though we should be able to have a separate cell blink timer in the future should the need appear.
This commit is contained in:
@ -108,6 +108,9 @@ cursor_color: ?terminal.color.RGB,
|
|||||||
/// foreground color as the cursor color.
|
/// foreground color as the cursor color.
|
||||||
cursor_invert: bool,
|
cursor_invert: bool,
|
||||||
|
|
||||||
|
/// Whether blinking cells are currently visible. Synchronized with cursor blinking.
|
||||||
|
blink_visible: bool = true,
|
||||||
|
|
||||||
/// Padding options
|
/// Padding options
|
||||||
padding: renderer.Options.Padding,
|
padding: renderer.Options.Padding,
|
||||||
|
|
||||||
@ -701,7 +704,7 @@ pub fn updateFrame(
|
|||||||
self: *OpenGL,
|
self: *OpenGL,
|
||||||
surface: *apprt.Surface,
|
surface: *apprt.Surface,
|
||||||
state: *renderer.State,
|
state: *renderer.State,
|
||||||
cursor_blink_visible: bool,
|
blink_visible: bool,
|
||||||
) !void {
|
) !void {
|
||||||
_ = surface;
|
_ = surface;
|
||||||
|
|
||||||
@ -770,7 +773,7 @@ pub fn updateFrame(
|
|||||||
const cursor_style = renderer.cursorStyle(
|
const cursor_style = renderer.cursorStyle(
|
||||||
state,
|
state,
|
||||||
self.focused,
|
self.focused,
|
||||||
cursor_blink_visible,
|
blink_visible,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get our preedit state
|
// Get our preedit state
|
||||||
@ -854,6 +857,9 @@ pub fn updateFrame(
|
|||||||
.color_palette = state.terminal.color_palette.colors,
|
.color_palette = state.terminal.color_palette.colors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.blink_visible = blink_visible;
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
critical.screen.deinit();
|
critical.screen.deinit();
|
||||||
if (critical.preedit) |p| p.deinit(self.alloc);
|
if (critical.preedit) |p| p.deinit(self.alloc);
|
||||||
@ -1279,7 +1285,7 @@ pub fn rebuildCells(
|
|||||||
const screen_cell = row.cells(.all)[screen.cursor.x];
|
const screen_cell = row.cells(.all)[screen.cursor.x];
|
||||||
const x = screen.cursor.x - @intFromBool(screen_cell.wide == .spacer_tail);
|
const x = screen.cursor.x - @intFromBool(screen_cell.wide == .spacer_tail);
|
||||||
for (self.cells.items[start_i..]) |cell| {
|
for (self.cells.items[start_i..]) |cell| {
|
||||||
if (cell.grid_col == x and cell.mode.isFg()) {
|
if (cell.grid_col == x and cell.mode.fg) {
|
||||||
cursor_cell = cell;
|
cursor_cell = cell;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1416,7 +1422,7 @@ pub fn rebuildCells(
|
|||||||
|
|
||||||
_ = try self.addCursor(screen, cursor_style, cursor_color);
|
_ = try self.addCursor(screen, cursor_style, cursor_color);
|
||||||
if (cursor_cell) |*cell| {
|
if (cursor_cell) |*cell| {
|
||||||
if (cell.mode.isFg() and cell.mode != .fg_color) {
|
if (cell.mode.fg and !cell.mode.fg_color) {
|
||||||
const cell_color = if (self.cursor_invert) blk: {
|
const cell_color = if (self.cursor_invert) blk: {
|
||||||
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
const sty = screen.cursor.page_pin.style(screen.cursor.page_cell);
|
||||||
break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color;
|
break :blk sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color;
|
||||||
@ -1436,8 +1442,8 @@ pub fn rebuildCells(
|
|||||||
|
|
||||||
// Some debug mode safety checks
|
// Some debug mode safety checks
|
||||||
if (std.debug.runtime_safety) {
|
if (std.debug.runtime_safety) {
|
||||||
for (self.cells_bg.items) |cell| assert(cell.mode == .bg);
|
for (self.cells_bg.items) |cell| assert(!cell.mode.fg);
|
||||||
for (self.cells.items) |cell| assert(cell.mode != .bg);
|
for (self.cells.items) |cell| assert(cell.mode.fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1469,7 +1475,7 @@ fn addPreeditCell(
|
|||||||
|
|
||||||
// Add our opaque background cell
|
// Add our opaque background cell
|
||||||
try self.cells_bg.append(self.alloc, .{
|
try self.cells_bg.append(self.alloc, .{
|
||||||
.mode = .bg,
|
.mode = .{ .fg = false },
|
||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(y),
|
.grid_row = @intCast(y),
|
||||||
.grid_width = if (cp.wide) 2 else 1,
|
.grid_width = if (cp.wide) 2 else 1,
|
||||||
@ -1491,7 +1497,7 @@ fn addPreeditCell(
|
|||||||
|
|
||||||
// Add our text
|
// Add our text
|
||||||
try self.cells.append(self.alloc, .{
|
try self.cells.append(self.alloc, .{
|
||||||
.mode = .fg,
|
.mode = .{ .fg = true },
|
||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(y),
|
.grid_row = @intCast(y),
|
||||||
.grid_width = if (cp.wide) 2 else 1,
|
.grid_width = if (cp.wide) 2 else 1,
|
||||||
@ -1558,7 +1564,7 @@ fn addCursor(
|
|||||||
};
|
};
|
||||||
|
|
||||||
try self.cells.append(self.alloc, .{
|
try self.cells.append(self.alloc, .{
|
||||||
.mode = .fg,
|
.mode = .{ .fg = true },
|
||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(screen.cursor.y),
|
.grid_row = @intCast(screen.cursor.y),
|
||||||
.grid_width = if (wide) 2 else 1,
|
.grid_width = if (wide) 2 else 1,
|
||||||
@ -1702,7 +1708,7 @@ fn updateCell(
|
|||||||
};
|
};
|
||||||
|
|
||||||
try self.cells_bg.append(self.alloc, .{
|
try self.cells_bg.append(self.alloc, .{
|
||||||
.mode = .bg,
|
.mode = .{ .fg = false },
|
||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(y),
|
.grid_row = @intCast(y),
|
||||||
.grid_width = cell.gridWidth(),
|
.grid_width = cell.gridWidth(),
|
||||||
@ -1743,16 +1749,20 @@ fn updateCell(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var mode: CellProgram.CellMode = .{ .fg = true };
|
||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
// If we're rendering a color font, we use the color atlas
|
||||||
const mode: CellProgram.CellMode = switch (try fgMode(
|
switch (try fgMode(
|
||||||
render.presentation,
|
render.presentation,
|
||||||
cell_pin,
|
cell_pin,
|
||||||
)) {
|
)) {
|
||||||
.normal => .fg,
|
.normal => {},
|
||||||
.color => .fg_color,
|
.color => mode.fg_color = true,
|
||||||
.constrained => .fg_constrained,
|
.constrained => mode.fg_constrained = true,
|
||||||
.powerline => .fg_powerline,
|
.powerline => mode.fg_powerline = true,
|
||||||
};
|
}
|
||||||
|
|
||||||
|
mode.fg_blink = style.flags.blink;
|
||||||
|
|
||||||
try self.cells.append(self.alloc, .{
|
try self.cells.append(self.alloc, .{
|
||||||
.mode = mode,
|
.mode = mode,
|
||||||
@ -1799,7 +1809,7 @@ fn updateCell(
|
|||||||
const color = style.underlineColor(palette) orelse colors.fg;
|
const color = style.underlineColor(palette) orelse colors.fg;
|
||||||
|
|
||||||
try self.cells.append(self.alloc, .{
|
try self.cells.append(self.alloc, .{
|
||||||
.mode = .fg,
|
.mode = .{ .fg = true },
|
||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(y),
|
.grid_row = @intCast(y),
|
||||||
.grid_width = cell.gridWidth(),
|
.grid_width = cell.gridWidth(),
|
||||||
@ -1832,7 +1842,7 @@ fn updateCell(
|
|||||||
);
|
);
|
||||||
|
|
||||||
try self.cells.append(self.alloc, .{
|
try self.cells.append(self.alloc, .{
|
||||||
.mode = .fg,
|
.mode = .{ .fg = true },
|
||||||
.grid_col = @intCast(x),
|
.grid_col = @intCast(x),
|
||||||
.grid_row = @intCast(y),
|
.grid_row = @intCast(y),
|
||||||
.grid_width = cell.gridWidth(),
|
.grid_width = cell.gridWidth(),
|
||||||
@ -2156,6 +2166,10 @@ fn drawCellProgram(
|
|||||||
"padding_vertical_bottom",
|
"padding_vertical_bottom",
|
||||||
self.padding_extend_bottom,
|
self.padding_extend_bottom,
|
||||||
);
|
);
|
||||||
|
try program.program.setUniform(
|
||||||
|
"blink_visible",
|
||||||
|
self.blink_visible,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw background images first
|
// Draw background images first
|
||||||
|
@ -48,31 +48,14 @@ pub const Cell = extern struct {
|
|||||||
grid_width: u8,
|
grid_width: u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CellMode = enum(u8) {
|
pub const CellMode = packed struct(u8) {
|
||||||
bg = 1,
|
fg: bool,
|
||||||
fg = 2,
|
fg_constrained: bool = false,
|
||||||
fg_constrained = 3,
|
fg_color: bool = false,
|
||||||
fg_color = 7,
|
fg_powerline: bool = false,
|
||||||
fg_powerline = 15,
|
fg_blink: bool = false,
|
||||||
|
|
||||||
// Non-exhaustive because masks change it
|
_padding: u3 = 0,
|
||||||
_,
|
|
||||||
|
|
||||||
/// Apply a mask to the mode.
|
|
||||||
pub fn mask(self: CellMode, m: CellMode) CellMode {
|
|
||||||
return @enumFromInt(@intFromEnum(self) | @intFromEnum(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isFg(self: CellMode) bool {
|
|
||||||
// Since we use bit tricks below, we want to ensure the enum
|
|
||||||
// doesn't change without us looking at this logic again.
|
|
||||||
comptime {
|
|
||||||
const info = @typeInfo(CellMode).Enum;
|
|
||||||
std.debug.assert(info.fields.len == 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return @intFromEnum(self) & @intFromEnum(@as(CellMode, .fg)) != 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init() !CellProgram {
|
pub fn init() !CellProgram {
|
||||||
|
@ -22,32 +22,29 @@ uniform sampler2D text_color;
|
|||||||
|
|
||||||
// Dimensions of the cell
|
// Dimensions of the cell
|
||||||
uniform vec2 cell_size;
|
uniform vec2 cell_size;
|
||||||
|
uniform bool blink_visible;
|
||||||
|
|
||||||
// See vertex shader
|
// See vertex shader
|
||||||
const uint MODE_BG = 1u;
|
const uint MODE_FG = 1u;
|
||||||
const uint MODE_FG = 2u;
|
const uint MODE_FG_CONSTRAINED = 2u;
|
||||||
const uint MODE_FG_CONSTRAINED = 3u;
|
const uint MODE_FG_COLOR = 4u;
|
||||||
const uint MODE_FG_COLOR = 7u;
|
const uint MODE_FG_POWERLINE = 8u;
|
||||||
const uint MODE_FG_POWERLINE = 15u;
|
const uint MODE_FG_BLINK = 16u;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float a;
|
if ((mode & MODE_FG) == 0u) {
|
||||||
|
// Background
|
||||||
switch (mode) {
|
out_FragColor = color;
|
||||||
case MODE_BG:
|
|
||||||
out_FragColor = color;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MODE_FG:
|
|
||||||
case MODE_FG_CONSTRAINED:
|
|
||||||
case MODE_FG_POWERLINE:
|
|
||||||
a = texture(text, glyph_tex_coords).r;
|
|
||||||
vec3 premult = color.rgb * color.a;
|
|
||||||
out_FragColor = vec4(premult.rgb*a, a);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MODE_FG_COLOR:
|
|
||||||
out_FragColor = texture(text_color, glyph_tex_coords);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if ((mode & MODE_FG_BLINK) != 0u && !blink_visible) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
if ((mode & MODE_FG_COLOR) != 0u) {
|
||||||
|
out_FragColor = texture(text_color, glyph_tex_coords);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float a = texture(text, glyph_tex_coords).r;
|
||||||
|
vec3 premult = color.rgb * color.a;
|
||||||
|
out_FragColor = vec4(premult.rgb*a, a);
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
// used to multiplex multiple render modes into a single shader.
|
// used to multiplex multiple render modes into a single shader.
|
||||||
//
|
//
|
||||||
// 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_FG = 1u;
|
||||||
const uint MODE_FG = 2u;
|
const uint MODE_FG_CONSTRAINED = 2u;
|
||||||
const uint MODE_FG_CONSTRAINED = 3u;
|
const uint MODE_FG_COLOR = 4u;
|
||||||
const uint MODE_FG_COLOR = 7u;
|
const uint MODE_FG_POWERLINE = 8u;
|
||||||
const uint MODE_FG_POWERLINE = 15u;
|
const uint MODE_FG_BLINK = 16u;
|
||||||
|
|
||||||
// The grid coordinates (x, y) where x < columns and y < rows
|
// The grid coordinates (x, y) where x < columns and y < rows
|
||||||
layout (location = 0) in vec2 grid_coord;
|
layout (location = 0) in vec2 grid_coord;
|
||||||
@ -170,8 +170,8 @@ void main() {
|
|||||||
vec2 cell_size_scaled = cell_size;
|
vec2 cell_size_scaled = cell_size;
|
||||||
cell_size_scaled.x = cell_size_scaled.x * grid_width;
|
cell_size_scaled.x = cell_size_scaled.x * grid_width;
|
||||||
|
|
||||||
switch (mode) {
|
if ((mode & MODE_FG) == 0u) {
|
||||||
case MODE_BG:
|
// Draw background
|
||||||
// If we're at the edge of the grid, we add our padding to the background
|
// If we're at the edge of the grid, we add our padding to the background
|
||||||
// to extend it. Note: grid_padding is top/right/bottom/left.
|
// to extend it. Note: grid_padding is top/right/bottom/left.
|
||||||
if (grid_coord.y == 0 && padding_vertical_top) {
|
if (grid_coord.y == 0 && padding_vertical_top) {
|
||||||
@ -194,12 +194,7 @@ void main() {
|
|||||||
|
|
||||||
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
gl_Position = projection * vec4(cell_pos, cell_z, 1.0);
|
||||||
color = color_in / 255.0;
|
color = color_in / 255.0;
|
||||||
break;
|
} else {
|
||||||
|
|
||||||
case MODE_FG:
|
|
||||||
case MODE_FG_CONSTRAINED:
|
|
||||||
case MODE_FG_COLOR:
|
|
||||||
case MODE_FG_POWERLINE:
|
|
||||||
vec2 glyph_offset_calc = glyph_offset;
|
vec2 glyph_offset_calc = glyph_offset;
|
||||||
|
|
||||||
// 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
|
||||||
@ -211,7 +206,7 @@ void main() {
|
|||||||
// We also always constrain colored glyphs since we should have
|
// We also always constrain colored glyphs since we should have
|
||||||
// their scaled cell size exactly correct.
|
// their scaled cell size exactly correct.
|
||||||
vec2 glyph_size_calc = glyph_size;
|
vec2 glyph_size_calc = glyph_size;
|
||||||
if (mode == MODE_FG_CONSTRAINED || mode == MODE_FG_COLOR) {
|
if ((mode & (MODE_FG_CONSTRAINED | MODE_FG_COLOR)) != 0u) {
|
||||||
if (glyph_size.x > cell_size_scaled.x) {
|
if (glyph_size.x > cell_size_scaled.x) {
|
||||||
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
|
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
|
||||||
glyph_offset_calc.y = glyph_offset_calc.y + ((glyph_size.y - new_y) / 2);
|
glyph_offset_calc.y = glyph_offset_calc.y + ((glyph_size.y - new_y) / 2);
|
||||||
@ -227,16 +222,10 @@ void main() {
|
|||||||
// 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;
|
ivec2 text_size;
|
||||||
switch(mode) {
|
if ((mode & MODE_FG_COLOR) != 0u) {
|
||||||
case MODE_FG_CONSTRAINED:
|
text_size = textureSize(text_color, 0);
|
||||||
case MODE_FG_POWERLINE:
|
} else {
|
||||||
case MODE_FG:
|
text_size = textureSize(text, 0);
|
||||||
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;
|
||||||
@ -250,11 +239,10 @@ void main() {
|
|||||||
// and Powerline glyphs to be unaffected (else parts of the line would
|
// and Powerline glyphs to be unaffected (else parts of the line would
|
||||||
// have different colors as some parts are displayed via background colors).
|
// have different colors as some parts are displayed via background colors).
|
||||||
vec4 color_final = color_in / 255.0;
|
vec4 color_final = color_in / 255.0;
|
||||||
if (min_contrast > 1.0 && mode == MODE_FG) {
|
if (min_contrast > 1.0 && (mode & ~(MODE_FG | MODE_FG_BLINK)) != 0u) {
|
||||||
vec4 bg_color = bg_color_in / 255.0;
|
vec4 bg_color = bg_color_in / 255.0;
|
||||||
color_final = contrasted_color(min_contrast, color_final, bg_color);
|
color_final = contrasted_color(min_contrast, color_final, bg_color);
|
||||||
}
|
}
|
||||||
color = color_final;
|
color = color_final;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user