renderer: added cursor style and visibility uniforms

Specifically:
iCurrentCursorStyle
iPreviousCursorStyle
iCurrentCursorVisible
iPreviousCursorVisible

Visibility calculated and updated independently from the typical cursor
unifrom updates to preserve cursor style even when not in the viewport
or set to be hidden
pull/9572/head
ClearAspect 2025-12-08 22:11:36 -05:00
parent 049b8826f6
commit a8ccfe3911
4 changed files with 126 additions and 67 deletions

View File

@ -2652,6 +2652,22 @@ 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 iCurrentCursorVisible` - Visibility of the terminal cursor.
///
/// * `vec4 iPreviousCursorVisible` - Visibility of the previous terminal cursor.
///
/// * `float iTimeCursorChange` - Timestamp of terminal cursor change.
///
/// When the terminal cursor changes position or color, this is set to

View File

@ -740,6 +740,10 @@ 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,
.current_cursor_visible = 0,
.previous_cursor_visible = 0,
.cursor_change_time = 0,
},
.bg_image_buffer = undefined,
@ -2239,6 +2243,8 @@ 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 now = try std.time.Instant.now();
defer self.last_frame_time = now;
const first_frame_time = self.first_frame_time orelse t: {
@ -2248,99 +2254,122 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
const last_frame_time = self.last_frame_time orelse now;
const since_ns: f32 = @floatFromInt(now.since(first_frame_time));
self.custom_shader_uniforms.time = since_ns / std.time.ns_per_s;
uniforms.time = since_ns / std.time.ns_per_s;
const delta_ns: f32 = @floatFromInt(now.since(last_frame_time));
self.custom_shader_uniforms.time_delta = delta_ns / std.time.ns_per_s;
uniforms.time_delta = delta_ns / std.time.ns_per_s;
self.custom_shader_uniforms.frame += 1;
uniforms.frame += 1;
const screen = self.size.screen;
const padding = self.size.padding;
const cell = self.size.cell;
self.custom_shader_uniforms.resolution = .{
uniforms.resolution = .{
@floatFromInt(screen.width),
@floatFromInt(screen.height),
1,
};
self.custom_shader_uniforms.channel_resolution[0] = .{
uniforms.channel_resolution[0] = .{
@floatFromInt(screen.width),
@floatFromInt(screen.height),
1,
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]);
// Always check the visibility
const current_cursor_visible: bool = self.terminal_state.cursor.visible;
// Left edge of the cell the cursor is in.
var pixel_x: f32 = @floatFromInt(
cursor.grid_pos[0] * cell.width + padding.left,
);
// Top edge, relative to the top of the
// screen, of the cell the cursor is in.
var pixel_y: f32 = @floatFromInt(
cursor.grid_pos[1] * cell.height + padding.top,
);
// Always update the visibility.
// If we check for a change then the current and previous uniforms
// will always be the opposite of one another
//
// This allows for cases where the use might want to know the exact frame
// where the visibility changes
uniforms.previous_cursor_visible = uniforms.current_cursor_visible;
uniforms.current_cursor_visible = @intFromBool(current_cursor_visible);
// If +Y is up in our shaders, we need to flip the coordinate
// so that it's instead the top edge of the cell relative to
// the *bottom* of the screen.
if (!GraphicsAPI.custom_shader_y_is_down) {
pixel_y = @as(f32, @floatFromInt(screen.height)) - pixel_y;
}
if (current_cursor_visible) {
// If we have a cursor in the fg cells.
//
// * Similar check to see if the cursor does in fact exist
if (self.cells.getCursorGlyph()) |cursor| {
const cursor_width: f32 = @floatFromInt(cursor.glyph_size[0]);
const cursor_height: f32 = @floatFromInt(cursor.glyph_size[1]);
// Add the X bearing to get the -X (left) edge of the cursor.
pixel_x += @floatFromInt(cursor.bearings[0]);
// Left edge of the cell the cursor is in.
var pixel_x: f32 = @floatFromInt(
cursor.grid_pos[0] * cell.width + padding.left,
);
// Top edge, relative to the top of the
// screen, of the cell the cursor is in.
var pixel_y: f32 = @floatFromInt(
cursor.grid_pos[1] * cell.height + padding.top,
);
// How we deal with the Y bearing depends on which direction
// is "up", since we want our final `pixel_y` value to be the
// +Y edge of the cursor.
if (GraphicsAPI.custom_shader_y_is_down) {
// As a reminder, the Y bearing is the distance from the
// bottom of the cell to the top of the glyph, so to get
// the +Y edge we need to add the cell height, subtract
// the Y bearing, and add the glyph height to get the +Y
// (bottom) edge of the cursor.
pixel_y += @floatFromInt(cell.height);
pixel_y -= @floatFromInt(cursor.bearings[1]);
pixel_y += @floatFromInt(cursor.glyph_size[1]);
} else {
// If the Y direction is reversed though, we instead want
// the *top* edge of the cursor, which means we just need
// to subtract the cell height and add the Y bearing.
pixel_y -= @floatFromInt(cell.height);
pixel_y += @floatFromInt(cursor.bearings[1]);
}
// If +Y is up in our shaders, we need to flip the coordinate
// so that it's instead the top edge of the cell relative to
// the *bottom* of the screen.
if (!GraphicsAPI.custom_shader_y_is_down) {
pixel_y = @as(f32, @floatFromInt(screen.height)) - pixel_y;
}
const new_cursor: [4]f32 = .{
pixel_x,
pixel_y,
cursor_width,
cursor_height,
};
const cursor_color: [4]f32 = .{
@as(f32, @floatFromInt(cursor.color[0])) / 255.0,
@as(f32, @floatFromInt(cursor.color[1])) / 255.0,
@as(f32, @floatFromInt(cursor.color[2])) / 255.0,
@as(f32, @floatFromInt(cursor.color[3])) / 255.0,
};
// Add the X bearing to get the -X (left) edge of the cursor.
pixel_x += @floatFromInt(cursor.bearings[0]);
const uniforms = &self.custom_shader_uniforms;
// How we deal with the Y bearing depends on which direction
// is "up", since we want our final `pixel_y` value to be the
// +Y edge of the cursor.
if (GraphicsAPI.custom_shader_y_is_down) {
// As a reminder, the Y bearing is the distance from the
// bottom of the cell to the top of the glyph, so to get
// the +Y edge we need to add the cell height, subtract
// the Y bearing, and add the glyph height to get the +Y
// (bottom) edge of the cursor.
pixel_y += @floatFromInt(cell.height);
pixel_y -= @floatFromInt(cursor.bearings[1]);
pixel_y += @floatFromInt(cursor.glyph_size[1]);
} else {
// If the Y direction is reversed though, we instead want
// the *top* edge of the cursor, which means we just need
// to subtract the cell height and add the Y bearing.
pixel_y -= @floatFromInt(cell.height);
pixel_y += @floatFromInt(cursor.bearings[1]);
}
const cursor_changed: bool =
!std.meta.eql(new_cursor, uniforms.current_cursor) or
!std.meta.eql(cursor_color, uniforms.current_cursor_color);
const new_cursor: [4]f32 = .{
pixel_x,
pixel_y,
cursor_width,
cursor_height,
};
const cursor_color: [4]f32 = .{
@as(f32, @floatFromInt(cursor.color[0])) / 255.0,
@as(f32, @floatFromInt(cursor.color[1])) / 255.0,
@as(f32, @floatFromInt(cursor.color[2])) / 255.0,
@as(f32, @floatFromInt(cursor.color[3])) / 255.0,
};
if (cursor_changed) {
uniforms.previous_cursor = uniforms.current_cursor;
uniforms.previous_cursor_color = uniforms.current_cursor_color;
uniforms.current_cursor = new_cursor;
uniforms.current_cursor_color = cursor_color;
uniforms.cursor_change_time = uniforms.time;
// Update custom cursor uniforms
const cursor_style = renderer.CursorStyle.fromTerminal(self.terminal_state.cursor.visual_style).?;
const current_cursor_style = @as(i32, @intFromEnum(cursor_style));
const cursor_changed: bool =
!std.meta.eql(new_cursor, uniforms.current_cursor) or
!std.meta.eql(cursor_color, uniforms.current_cursor_color) or
current_cursor_style != uniforms.current_cursor_style;
if (cursor_changed) {
uniforms.previous_cursor = uniforms.current_cursor;
uniforms.previous_cursor_color = uniforms.current_cursor_color;
uniforms.current_cursor = new_cursor;
uniforms.current_cursor_color = cursor_color;
uniforms.previous_cursor_style = uniforms.current_cursor_style;
uniforms.current_cursor_style = current_cursor_style;
uniforms.cursor_change_time = uniforms.time;
}
}
}
}

View File

@ -15,9 +15,19 @@ layout(binding = 1, std140) uniform Globals {
uniform vec4 iPreviousCursor;
uniform vec4 iCurrentCursorColor;
uniform vec4 iPreviousCursorColor;
uniform int iCurrentCursorStyle;
uniform int iPreviousCursorStyle;
uniform int iCurrentCursorVisible;
uniform int iPreviousCursorVisible;
uniform float iTimeCursorChange;
};
#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,10 @@ 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),
current_cursor_visible: i32 align(4),
previous_cursor_visible: i32 align(4),
cursor_change_time: f32 align(4),
};