custom shader: added cursor style and visibility uniforms (#9572)

Fixes: #9416

Specifically:
  iCurrentCursorStyle
  iPreviousCursorStyle
  iCurrentCursorVisible
  iPreviousCursorVisible

Visibility calculated and updated independently from the typical cursor
uniform updates to preserve cursor style even when not in the viewport
or set to be hidden

I used Claude-Code to initially navigate and gauge an understanding of
the rendering system. Otherwise I authored the rest of the PR
This commit is contained in:
Mitchell Hashimoto
2026-02-23 14:17:23 -08:00
committed by GitHub
5 changed files with 47 additions and 10 deletions

View File

@@ -2946,6 +2946,20 @@ keybind: Keybinds = .{},
///
/// * `vec4 iPreviousCursorColor` - Color of the previous terminal cursor.
///
/// * `vec4 iCurrentCursorStyle` - Style of the terminal cursor
///
/// Macros simplified use are defined for the various cursor styles:
///
/// - `CURSORSTYLE_BLOCK` or `0`
/// - `CURSORSTYLE_BLOCK_HOLLOW` or `1`
/// - `CURSORSTYLE_BAR` or `2`
/// - `CURSORSTYLE_UNDERLINE` or `3`
/// - `CURSORSTYLE_LOCK` or `4`
///
/// * `vec4 iPreviousCursorStyle` - Style of the previous terminal cursor
///
/// * `vec4 iCursorVisible` - Visibility of the terminal cursor.
///
/// * `float iTimeCursorChange` - Timestamp of terminal cursor change.
///
/// When the terminal cursor changes position or color, this is set to

View File

@@ -15,7 +15,7 @@ pub const Style = enum {
lock,
/// Create a cursor style from the terminal style request.
pub fn fromTerminal(term: terminal.CursorStyle) ?Style {
pub fn fromTerminal(term: terminal.CursorStyle) Style {
return switch (term) {
.bar => .bar,
.block => .block,

View File

@@ -756,6 +756,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
.previous_cursor = @splat(0),
.current_cursor_color = @splat(0),
.previous_cursor_color = @splat(0),
.current_cursor_style = 0,
.previous_cursor_style = 0,
.cursor_visible = 0,
.cursor_change_time = 0,
.time_focus = 0,
.focus = 1, // assume focused initially
@@ -2011,11 +2014,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Only update when terminal state is dirty.
if (self.terminal_state.dirty == .false) return;
const uniforms: *shadertoy.Uniforms = &self.custom_shader_uniforms;
const colors: *const terminal.RenderState.Colors = &self.terminal_state.colors;
// 256-color palette
for (colors.palette, 0..) |color, i| {
self.custom_shader_uniforms.palette[i] = .{
uniforms.palette[i] = .{
@as(f32, @floatFromInt(color.r)) / 255.0,
@as(f32, @floatFromInt(color.g)) / 255.0,
@as(f32, @floatFromInt(color.b)) / 255.0,
@@ -2024,7 +2028,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
}
// Background color
self.custom_shader_uniforms.background_color = .{
uniforms.background_color = .{
@as(f32, @floatFromInt(colors.background.r)) / 255.0,
@as(f32, @floatFromInt(colors.background.g)) / 255.0,
@as(f32, @floatFromInt(colors.background.b)) / 255.0,
@@ -2032,7 +2036,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
};
// Foreground color
self.custom_shader_uniforms.foreground_color = .{
uniforms.foreground_color = .{
@as(f32, @floatFromInt(colors.foreground.r)) / 255.0,
@as(f32, @floatFromInt(colors.foreground.g)) / 255.0,
@as(f32, @floatFromInt(colors.foreground.b)) / 255.0,
@@ -2041,7 +2045,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Cursor color
if (colors.cursor) |cursor_color| {
self.custom_shader_uniforms.cursor_color = .{
uniforms.cursor_color = .{
@as(f32, @floatFromInt(cursor_color.r)) / 255.0,
@as(f32, @floatFromInt(cursor_color.g)) / 255.0,
@as(f32, @floatFromInt(cursor_color.b)) / 255.0,
@@ -2055,7 +2059,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Cursor text color
if (self.config.cursor_text) |cursor_text| {
self.custom_shader_uniforms.cursor_text = .{
uniforms.cursor_text = .{
@as(f32, @floatFromInt(cursor_text.color.r)) / 255.0,
@as(f32, @floatFromInt(cursor_text.color.g)) / 255.0,
@as(f32, @floatFromInt(cursor_text.color.b)) / 255.0,
@@ -2065,7 +2069,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Selection background color
if (self.config.selection_background) |selection_bg| {
self.custom_shader_uniforms.selection_background_color = .{
uniforms.selection_background_color = .{
@as(f32, @floatFromInt(selection_bg.color.r)) / 255.0,
@as(f32, @floatFromInt(selection_bg.color.g)) / 255.0,
@as(f32, @floatFromInt(selection_bg.color.b)) / 255.0,
@@ -2075,13 +2079,21 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Selection foreground color
if (self.config.selection_foreground) |selection_fg| {
self.custom_shader_uniforms.selection_foreground_color = .{
uniforms.selection_foreground_color = .{
@as(f32, @floatFromInt(selection_fg.color.r)) / 255.0,
@as(f32, @floatFromInt(selection_fg.color.g)) / 255.0,
@as(f32, @floatFromInt(selection_fg.color.b)) / 255.0,
1.0,
};
}
// Cursor visibility
uniforms.cursor_visible = @intFromBool(self.terminal_state.cursor.visible);
// Cursor style
const cursor_style: renderer.CursorStyle = .fromTerminal(self.terminal_state.cursor.visual_style);
uniforms.previous_cursor_style = uniforms.current_cursor_style;
uniforms.current_cursor_style = @as(i32, @intFromEnum(cursor_style));
}
/// Update per-frame custom shader uniforms.
@@ -2091,7 +2103,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// We only need to do this if we have custom shaders.
if (!self.has_custom_shaders) return;
const uniforms = &self.custom_shader_uniforms;
const uniforms: *shadertoy.Uniforms = &self.custom_shader_uniforms;
const now = try std.time.Instant.now();
defer self.last_frame_time = now;
@@ -2125,7 +2137,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
0,
};
// Update custom cursor uniforms, if we have a cursor.
if (self.cells.getCursorGlyph()) |cursor| {
const cursor_width: f32 = @floatFromInt(cursor.glyph_size[0]);
const cursor_height: f32 = @floatFromInt(cursor.glyph_size[1]);

View File

@@ -15,6 +15,9 @@ layout(binding = 1, std140) uniform Globals {
uniform vec4 iPreviousCursor;
uniform vec4 iCurrentCursorColor;
uniform vec4 iPreviousCursorColor;
uniform int iCurrentCursorStyle;
uniform int iPreviousCursorStyle;
uniform int iCursorVisible;
uniform float iTimeCursorChange;
uniform float iTimeFocus;
uniform int iFocus;
@@ -27,6 +30,12 @@ layout(binding = 1, std140) uniform Globals {
uniform vec3 iSelectionBackgroundColor;
};
#define CURSORSTYLE_BLOCK 0
#define CURSORSTYLE_BLOCK_HOLLOW 1
#define CURSORSTYLE_BAR 2
#define CURSORSTYLE_UNDERLINE 3
#define CURSORSTYLE_LOCK 4
layout(binding = 0) uniform sampler2D iChannel0;
// These are unused currently by Ghostty:

View File

@@ -24,6 +24,9 @@ pub const Uniforms = extern struct {
previous_cursor: [4]f32 align(16),
current_cursor_color: [4]f32 align(16),
previous_cursor_color: [4]f32 align(16),
current_cursor_style: i32 align(4),
previous_cursor_style: i32 align(4),
cursor_visible: i32 align(4),
cursor_change_time: f32 align(4),
time_focus: f32 align(4),
focus: i32 align(4),