From 1d04c52bb2bb67207ad87ae6c477177ed8fb33ab Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Sat, 31 Aug 2024 01:05:21 +0200 Subject: [PATCH] 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. --- src/renderer/OpenGL.zig | 50 ++++++++++++++++++----------- src/renderer/opengl/CellProgram.zig | 31 ++++-------------- src/renderer/shaders/cell.f.glsl | 43 ++++++++++++------------- src/renderer/shaders/cell.v.glsl | 40 ++++++++--------------- 4 files changed, 73 insertions(+), 91 deletions(-) diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index d220cdadc..94216c3aa 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -108,6 +108,9 @@ cursor_color: ?terminal.color.RGB, /// foreground color as the cursor color. cursor_invert: bool, +/// Whether blinking cells are currently visible. Synchronized with cursor blinking. +blink_visible: bool = true, + /// Padding options padding: renderer.Options.Padding, @@ -701,7 +704,7 @@ pub fn updateFrame( self: *OpenGL, surface: *apprt.Surface, state: *renderer.State, - cursor_blink_visible: bool, + blink_visible: bool, ) !void { _ = surface; @@ -770,7 +773,7 @@ pub fn updateFrame( const cursor_style = renderer.cursorStyle( state, self.focused, - cursor_blink_visible, + blink_visible, ); // Get our preedit state @@ -854,6 +857,9 @@ pub fn updateFrame( .color_palette = state.terminal.color_palette.colors, }; }; + + self.blink_visible = blink_visible; + defer { critical.screen.deinit(); 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 x = screen.cursor.x - @intFromBool(screen_cell.wide == .spacer_tail); 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; break; } @@ -1416,7 +1422,7 @@ pub fn rebuildCells( _ = try self.addCursor(screen, cursor_style, cursor_color); 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 sty = screen.cursor.page_pin.style(screen.cursor.page_cell); 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 if (std.debug.runtime_safety) { - for (self.cells_bg.items) |cell| assert(cell.mode == .bg); - for (self.cells.items) |cell| assert(cell.mode != .bg); + for (self.cells_bg.items) |cell| assert(!cell.mode.fg); + for (self.cells.items) |cell| assert(cell.mode.fg); } } @@ -1469,7 +1475,7 @@ fn addPreeditCell( // Add our opaque background cell try self.cells_bg.append(self.alloc, .{ - .mode = .bg, + .mode = .{ .fg = false }, .grid_col = @intCast(x), .grid_row = @intCast(y), .grid_width = if (cp.wide) 2 else 1, @@ -1491,7 +1497,7 @@ fn addPreeditCell( // Add our text try self.cells.append(self.alloc, .{ - .mode = .fg, + .mode = .{ .fg = true }, .grid_col = @intCast(x), .grid_row = @intCast(y), .grid_width = if (cp.wide) 2 else 1, @@ -1558,7 +1564,7 @@ fn addCursor( }; try self.cells.append(self.alloc, .{ - .mode = .fg, + .mode = .{ .fg = true }, .grid_col = @intCast(x), .grid_row = @intCast(screen.cursor.y), .grid_width = if (wide) 2 else 1, @@ -1702,7 +1708,7 @@ fn updateCell( }; try self.cells_bg.append(self.alloc, .{ - .mode = .bg, + .mode = .{ .fg = false }, .grid_col = @intCast(x), .grid_row = @intCast(y), .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 - const mode: CellProgram.CellMode = switch (try fgMode( + switch (try fgMode( render.presentation, cell_pin, )) { - .normal => .fg, - .color => .fg_color, - .constrained => .fg_constrained, - .powerline => .fg_powerline, - }; + .normal => {}, + .color => mode.fg_color = true, + .constrained => mode.fg_constrained = true, + .powerline => mode.fg_powerline = true, + } + + mode.fg_blink = style.flags.blink; try self.cells.append(self.alloc, .{ .mode = mode, @@ -1799,7 +1809,7 @@ fn updateCell( const color = style.underlineColor(palette) orelse colors.fg; try self.cells.append(self.alloc, .{ - .mode = .fg, + .mode = .{ .fg = true }, .grid_col = @intCast(x), .grid_row = @intCast(y), .grid_width = cell.gridWidth(), @@ -1832,7 +1842,7 @@ fn updateCell( ); try self.cells.append(self.alloc, .{ - .mode = .fg, + .mode = .{ .fg = true }, .grid_col = @intCast(x), .grid_row = @intCast(y), .grid_width = cell.gridWidth(), @@ -2156,6 +2166,10 @@ fn drawCellProgram( "padding_vertical_bottom", self.padding_extend_bottom, ); + try program.program.setUniform( + "blink_visible", + self.blink_visible, + ); } // Draw background images first diff --git a/src/renderer/opengl/CellProgram.zig b/src/renderer/opengl/CellProgram.zig index 48386362e..b68ee4feb 100644 --- a/src/renderer/opengl/CellProgram.zig +++ b/src/renderer/opengl/CellProgram.zig @@ -48,31 +48,14 @@ pub const Cell = extern struct { grid_width: u8, }; -pub const CellMode = enum(u8) { - bg = 1, - fg = 2, - fg_constrained = 3, - fg_color = 7, - fg_powerline = 15, +pub const CellMode = packed struct(u8) { + fg: bool, + fg_constrained: bool = false, + fg_color: bool = false, + fg_powerline: bool = false, + fg_blink: bool = false, - // Non-exhaustive because masks change it - _, - - /// 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; - } + _padding: u3 = 0, }; pub fn init() !CellProgram { diff --git a/src/renderer/shaders/cell.f.glsl b/src/renderer/shaders/cell.f.glsl index f9c1ce2b1..9e0969daf 100644 --- a/src/renderer/shaders/cell.f.glsl +++ b/src/renderer/shaders/cell.f.glsl @@ -22,32 +22,29 @@ uniform sampler2D text_color; // Dimensions of the cell uniform vec2 cell_size; +uniform bool blink_visible; // See vertex shader -const uint MODE_BG = 1u; -const uint MODE_FG = 2u; -const uint MODE_FG_CONSTRAINED = 3u; -const uint MODE_FG_COLOR = 7u; -const uint MODE_FG_POWERLINE = 15u; +const uint MODE_FG = 1u; +const uint MODE_FG_CONSTRAINED = 2u; +const uint MODE_FG_COLOR = 4u; +const uint MODE_FG_POWERLINE = 8u; +const uint MODE_FG_BLINK = 16u; void main() { - float a; - - switch (mode) { - 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) == 0u) { + // Background + out_FragColor = color; } + 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); } diff --git a/src/renderer/shaders/cell.v.glsl b/src/renderer/shaders/cell.v.glsl index 942b7ac44..12d2d4f4e 100644 --- a/src/renderer/shaders/cell.v.glsl +++ b/src/renderer/shaders/cell.v.glsl @@ -4,11 +4,11 @@ // used to multiplex multiple render modes into a single shader. // // 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_CONSTRAINED = 3u; -const uint MODE_FG_COLOR = 7u; -const uint MODE_FG_POWERLINE = 15u; +const uint MODE_FG = 1u; +const uint MODE_FG_CONSTRAINED = 2u; +const uint MODE_FG_COLOR = 4u; +const uint MODE_FG_POWERLINE = 8u; +const uint MODE_FG_BLINK = 16u; // The grid coordinates (x, y) where x < columns and y < rows layout (location = 0) in vec2 grid_coord; @@ -170,8 +170,8 @@ void main() { vec2 cell_size_scaled = cell_size; cell_size_scaled.x = cell_size_scaled.x * grid_width; - switch (mode) { - case MODE_BG: + if ((mode & MODE_FG) == 0u) { + // Draw 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. if (grid_coord.y == 0 && padding_vertical_top) { @@ -194,12 +194,7 @@ void main() { gl_Position = projection * vec4(cell_pos, cell_z, 1.0); color = color_in / 255.0; - break; - - case MODE_FG: - case MODE_FG_CONSTRAINED: - case MODE_FG_COLOR: - case MODE_FG_POWERLINE: + } else { vec2 glyph_offset_calc = glyph_offset; // 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 // their scaled cell size exactly correct. 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) { 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); @@ -227,16 +222,10 @@ void main() { // 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; - switch(mode) { - case MODE_FG_CONSTRAINED: - case MODE_FG_POWERLINE: - case MODE_FG: - text_size = textureSize(text, 0); - break; - - case MODE_FG_COLOR: - text_size = textureSize(text_color, 0); - break; + if ((mode & MODE_FG_COLOR) != 0u) { + text_size = textureSize(text_color, 0); + } else { + text_size = textureSize(text, 0); } vec2 glyph_tex_pos = glyph_pos / 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 // have different colors as some parts are displayed via background colors). 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; color_final = contrasted_color(min_contrast, color_final, bg_color); } color = color_final; - break; } }