mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-18 15:11:25 +00:00
custom shaders: add colorscheme information to shader uniforms (#9656)
#9417 Adds palette and color scheme uniforms to custom shaders, allowing custom shaders to access terminal color information: - iPalette[256]: Full 256-color terminal palette (RGB) - iBackgroundColor, iForegroundColor: Terminal colors (RGB) - iCursorColor, iCursorText: Cursor colors (RGB) - iSelectionBackgroundColor, iSelectionForegroundColor: Selection colors (RGB) Colors are normalized to [0.0, 1.0] range and update when the palette changes via OSC sequences or configuration changes. The palette_dirty flag tracks when colors need to be refreshed, initialized to true to ensure correct colors on new surfaces.
This commit is contained in:
@@ -2883,6 +2883,24 @@ keybind: Keybinds = .{},
|
||||
/// (e.g., modifier key presses, link hover events in unfocused split panes).
|
||||
/// Check `iFocus > 0` to determine if the surface is currently focused.
|
||||
///
|
||||
/// * `vec3 iPalette[256]` - The 256-color terminal palette.
|
||||
///
|
||||
/// RGB values for all 256 colors in the terminal palette, normalized
|
||||
/// to [0.0, 1.0]. Index 0-15 are the ANSI colors, 16-231 are the 6x6x6
|
||||
/// color cube, and 232-255 are the grayscale colors.
|
||||
///
|
||||
/// * `vec3 iBackgroundColor` - Terminal background color (RGB).
|
||||
///
|
||||
/// * `vec3 iForegroundColor` - Terminal foreground color (RGB).
|
||||
///
|
||||
/// * `vec3 iCursorColor` - Terminal cursor color (RGB).
|
||||
///
|
||||
/// * `vec3 iCursorText` - Terminal cursor text color (RGB).
|
||||
///
|
||||
/// * `vec3 iSelectionBackgroundColor` - Selection background color (RGB).
|
||||
///
|
||||
/// * `vec3 iSelectionForegroundColor` - Selection foreground color (RGB).
|
||||
///
|
||||
/// If the shader fails to compile, the shader will be ignored. Any errors
|
||||
/// related to shader compilation will not show up as configuration errors
|
||||
/// and only show up in the log, since shader compilation happens after
|
||||
|
||||
@@ -231,7 +231,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
search_match,
|
||||
search_match_selected,
|
||||
};
|
||||
|
||||
/// Swap chain which maintains multiple copies of the state needed to
|
||||
/// render a frame, so that we can start building the next frame while
|
||||
/// the previous frame is still being processed on the GPU.
|
||||
@@ -752,6 +751,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
.cursor_change_time = 0,
|
||||
.time_focus = 0,
|
||||
.focus = 1, // assume focused initially
|
||||
.palette = @splat(@splat(0)),
|
||||
.background_color = @splat(0),
|
||||
.foreground_color = @splat(0),
|
||||
.cursor_color = @splat(0),
|
||||
.cursor_text = @splat(0),
|
||||
.selection_background_color = @splat(0),
|
||||
.selection_foreground_color = @splat(0),
|
||||
},
|
||||
.bg_image_buffer = undefined,
|
||||
|
||||
@@ -1276,26 +1282,25 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
// Build our GPU cells
|
||||
try self.rebuildCells(
|
||||
critical.preedit,
|
||||
renderer.cursorStyle(&self.terminal_state, .{
|
||||
.preedit = critical.preedit != null,
|
||||
.focused = self.focused,
|
||||
.blink_visible = cursor_blink_visible,
|
||||
}),
|
||||
&critical.links,
|
||||
);
|
||||
// Reset our dirty state after updating.
|
||||
defer self.terminal_state.dirty = .false;
|
||||
|
||||
// Notify our shaper we're done for the frame. For some shapers,
|
||||
// such as CoreText, this triggers off-thread cleanup logic.
|
||||
self.font_shaper.endFrame();
|
||||
|
||||
// Acquire the draw mutex because we're modifying state here.
|
||||
// Acquire the draw mutex for all remaining state updates.
|
||||
{
|
||||
self.draw_mutex.lock();
|
||||
defer self.draw_mutex.unlock();
|
||||
|
||||
// Build our GPU cells
|
||||
try self.rebuildCells(
|
||||
critical.preedit,
|
||||
renderer.cursorStyle(&self.terminal_state, .{
|
||||
.preedit = critical.preedit != null,
|
||||
.focused = self.focused,
|
||||
.blink_visible = cursor_blink_visible,
|
||||
}),
|
||||
&critical.links,
|
||||
);
|
||||
|
||||
// The scrollbar is only emitted during draws so we also
|
||||
// check the scrollbar cache here and update if needed.
|
||||
// This is pretty fast.
|
||||
@@ -1322,7 +1327,14 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
|
||||
else => {},
|
||||
};
|
||||
|
||||
// Update custom shader uniforms that depend on terminal state.
|
||||
self.updateCustomShaderUniformsFromState();
|
||||
}
|
||||
|
||||
// Notify our shaper we're done for the frame. For some shapers,
|
||||
// such as CoreText, this triggers off-thread cleanup logic.
|
||||
self.font_shaper.endFrame();
|
||||
}
|
||||
|
||||
/// Draw the frame to the screen.
|
||||
@@ -1444,8 +1456,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
// Upload the background image to the GPU as necessary.
|
||||
try self.uploadBackgroundImage();
|
||||
|
||||
// Update custom shader uniforms if necessary.
|
||||
try self.updateCustomShaderUniforms();
|
||||
// Update per-frame custom shader uniforms.
|
||||
try self.updateCustomShaderUniformsForFrame();
|
||||
|
||||
// Setup our frame data
|
||||
try frame.uniforms.sync(&.{self.uniforms});
|
||||
@@ -2259,10 +2271,93 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
self.bg_image_buffer_modified +%= 1;
|
||||
}
|
||||
|
||||
/// Update uniforms for the custom shaders, if necessary.
|
||||
/// Update custom shader uniforms that depend on terminal state.
|
||||
///
|
||||
/// This should be called in `updateFrame` when terminal state changes.
|
||||
fn updateCustomShaderUniformsFromState(self: *Self) void {
|
||||
// We only need to do this if we have custom shaders.
|
||||
if (!self.has_custom_shaders) return;
|
||||
|
||||
// Only update when terminal state is dirty.
|
||||
if (self.terminal_state.dirty == .false) return;
|
||||
|
||||
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] = .{
|
||||
@as(f32, @floatFromInt(color.r)) / 255.0,
|
||||
@as(f32, @floatFromInt(color.g)) / 255.0,
|
||||
@as(f32, @floatFromInt(color.b)) / 255.0,
|
||||
1.0,
|
||||
};
|
||||
}
|
||||
|
||||
// Background color
|
||||
self.custom_shader_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,
|
||||
1.0,
|
||||
};
|
||||
|
||||
// Foreground color
|
||||
self.custom_shader_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,
|
||||
1.0,
|
||||
};
|
||||
|
||||
// Cursor color
|
||||
if (colors.cursor) |cursor_color| {
|
||||
self.custom_shader_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,
|
||||
1.0,
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE: the following could be optimized to follow a change in
|
||||
// config for a slight optimization however this is only 12 bytes
|
||||
// each being updated and likely isn't a cause for concern
|
||||
|
||||
// Cursor text color
|
||||
if (self.config.cursor_text) |cursor_text| {
|
||||
self.custom_shader_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,
|
||||
1.0,
|
||||
};
|
||||
}
|
||||
|
||||
// Selection background color
|
||||
if (self.config.selection_background) |selection_bg| {
|
||||
self.custom_shader_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,
|
||||
1.0,
|
||||
};
|
||||
}
|
||||
|
||||
// Selection foreground color
|
||||
if (self.config.selection_foreground) |selection_fg| {
|
||||
self.custom_shader_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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Update per-frame custom shader uniforms.
|
||||
///
|
||||
/// This should be called exactly once per frame, inside `drawFrame`.
|
||||
fn updateCustomShaderUniforms(self: *Self) !void {
|
||||
fn updateCustomShaderUniformsForFrame(self: *Self) !void {
|
||||
// We only need to do this if we have custom shaders.
|
||||
if (!self.has_custom_shaders) return;
|
||||
|
||||
@@ -2388,6 +2483,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
/// Convert the terminal state to GPU cells stored in CPU memory. These
|
||||
/// are then synced to the GPU in the next frame. This only updates CPU
|
||||
/// memory and doesn't touch the GPU.
|
||||
///
|
||||
/// This requires the draw mutex.
|
||||
///
|
||||
/// Dirty state on terminal state won't be reset by this.
|
||||
fn rebuildCells(
|
||||
self: *Self,
|
||||
preedit: ?renderer.State.Preedit,
|
||||
@@ -2395,10 +2494,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
links: *const terminal.RenderState.CellSet,
|
||||
) !void {
|
||||
const state: *terminal.RenderState = &self.terminal_state;
|
||||
defer state.dirty = .false;
|
||||
|
||||
self.draw_mutex.lock();
|
||||
defer self.draw_mutex.unlock();
|
||||
|
||||
// const start = try std.time.Instant.now();
|
||||
// const start_micro = std.time.microTimestamp();
|
||||
|
||||
@@ -18,6 +18,13 @@ layout(binding = 1, std140) uniform Globals {
|
||||
uniform float iTimeCursorChange;
|
||||
uniform float iTimeFocus;
|
||||
uniform int iFocus;
|
||||
uniform vec3 iPalette[256];
|
||||
uniform vec3 iBackgroundColor;
|
||||
uniform vec3 iForegroundColor;
|
||||
uniform vec3 iCursorColor;
|
||||
uniform vec3 iCursorText;
|
||||
uniform vec3 iSelectionForegroundColor;
|
||||
uniform vec3 iSelectionBackgroundColor;
|
||||
};
|
||||
|
||||
layout(binding = 0) uniform sampler2D iChannel0;
|
||||
|
||||
@@ -27,6 +27,13 @@ pub const Uniforms = extern struct {
|
||||
cursor_change_time: f32 align(4),
|
||||
time_focus: f32 align(4),
|
||||
focus: i32 align(4),
|
||||
palette: [256][4]f32 align(16),
|
||||
background_color: [4]f32 align(16),
|
||||
foreground_color: [4]f32 align(16),
|
||||
cursor_color: [4]f32 align(16),
|
||||
cursor_text: [4]f32 align(16),
|
||||
selection_background_color: [4]f32 align(16),
|
||||
selection_foreground_color: [4]f32 align(16),
|
||||
};
|
||||
|
||||
/// The target to load shaders for.
|
||||
|
||||
Reference in New Issue
Block a user