mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 21:40:29 +00:00
Add iTimeFocus shader uniform to track time since focus
This commit is contained in:
committed by
Mitchell Hashimoto
parent
bde9578adc
commit
ec2612f9ce
@@ -2783,6 +2783,22 @@ keybind: Keybinds = .{},
|
||||
/// the same time as the `iTime` uniform, allowing you to compute the
|
||||
/// time since the change by subtracting this from `iTime`.
|
||||
///
|
||||
/// * `float iTimeFocus` - Timestamp when the surface last gained iFocus.
|
||||
///
|
||||
/// When the surface gains focus, this is set to the current value of
|
||||
/// `iTime`, similar to how `iTimeCursorChange` works. This allows you
|
||||
/// to compute the time since focus was gained or lost by calculating
|
||||
/// `iTime - iTimeFocus`. Use this to create animations that restart
|
||||
/// when the terminal regains focus.
|
||||
///
|
||||
/// * `int iFocus` - Current focus state of the surface.
|
||||
///
|
||||
/// Set to 1.0 when the surface is focused, 0.0 when unfocused. This
|
||||
/// allows shaders to detect unfocused state and avoid animation artifacts
|
||||
/// from large time deltas caused by infrequent "deceptive frames"
|
||||
/// (e.g., modifier key presses, link hover events in unfocused split panes).
|
||||
/// Check `iFocus > 0` to determine if the surface is currently focused.
|
||||
///
|
||||
/// 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
|
||||
|
||||
@@ -116,6 +116,10 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
/// True if the window is focused
|
||||
focused: bool,
|
||||
|
||||
/// Flag to indicate that our focus state changed for custom
|
||||
/// shaders to update their state.
|
||||
custom_shader_focused_changed: bool = false,
|
||||
|
||||
/// The most recent scrollbar state. We use this as a cache to
|
||||
/// determine if we need to notify the apprt that there was a
|
||||
/// scrollbar change.
|
||||
@@ -746,6 +750,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
.current_cursor_color = @splat(0),
|
||||
.previous_cursor_color = @splat(0),
|
||||
.cursor_change_time = 0,
|
||||
.time_focus = 0,
|
||||
.focus = 1, // assume focused initially
|
||||
},
|
||||
.bg_image_buffer = undefined,
|
||||
|
||||
@@ -1008,8 +1014,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
///
|
||||
/// Must be called on the render thread.
|
||||
pub fn setFocus(self: *Self, focus: bool) !void {
|
||||
assert(self.focused != focus);
|
||||
|
||||
self.focused = focus;
|
||||
|
||||
// Flag that we need to update our custom shaders
|
||||
self.custom_shader_focused_changed = true;
|
||||
|
||||
// If we're not focused, then we want to stop the display link
|
||||
// because it is a waste of resources and we can move to pure
|
||||
// change-driven updates.
|
||||
@@ -2255,6 +2266,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: {
|
||||
@@ -2264,23 +2277,23 @@ 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,
|
||||
@@ -2345,8 +2358,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
@as(f32, @floatFromInt(cursor.color[3])) / 255.0,
|
||||
};
|
||||
|
||||
const uniforms = &self.custom_shader_uniforms;
|
||||
|
||||
const cursor_changed: bool =
|
||||
!std.meta.eql(new_cursor, uniforms.current_cursor) or
|
||||
!std.meta.eql(cursor_color, uniforms.current_cursor_color);
|
||||
@@ -2359,6 +2370,19 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
uniforms.cursor_change_time = uniforms.time;
|
||||
}
|
||||
}
|
||||
|
||||
// Update focus uniforms
|
||||
uniforms.focus = @intFromBool(self.focused);
|
||||
|
||||
// If we need to update the time our focus state changed
|
||||
// then update it to our current frame time. This may not be
|
||||
// exactly correct since it is frame time, not exact focus
|
||||
// time, but focus time on its own isn't exactly correct anyways
|
||||
// since it comes async from a message.
|
||||
if (self.custom_shader_focused_changed and self.focused) {
|
||||
uniforms.time_focus = uniforms.time;
|
||||
self.custom_shader_focused_changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the terminal state to GPU cells stored in CPU memory. These
|
||||
|
||||
@@ -16,6 +16,8 @@ layout(binding = 1, std140) uniform Globals {
|
||||
uniform vec4 iCurrentCursorColor;
|
||||
uniform vec4 iPreviousCursorColor;
|
||||
uniform float iTimeCursorChange;
|
||||
uniform float iTimeFocus;
|
||||
uniform int iFocus;
|
||||
};
|
||||
|
||||
layout(binding = 0) uniform sampler2D iChannel0;
|
||||
|
||||
41
src/renderer/shaders/test_shadertoy_focus.glsl
Normal file
41
src/renderer/shaders/test_shadertoy_focus.glsl
Normal file
@@ -0,0 +1,41 @@
|
||||
// Test shader for iTimeFocus and iFocus
|
||||
// Shows border when focused, green fade that restarts on each focus gain
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
|
||||
// Sample the terminal content
|
||||
vec4 terminal = texture2D(iChannel0, uv);
|
||||
vec3 color = terminal.rgb;
|
||||
|
||||
if (iFocus > 0) {
|
||||
// FOCUSED: Add border and fading green overlay
|
||||
|
||||
// Calculate time since focus was gained
|
||||
float timeSinceFocus = iTime - iTimeFocus;
|
||||
|
||||
// Green fade: starts at 1.0 (full green), fades to 0.0 over 3 seconds
|
||||
float fadeOut = max(0.0, 1.0 - (timeSinceFocus / 3.0));
|
||||
|
||||
// Add green overlay that fades out
|
||||
color = mix(color, vec3(0.0, 1.0, 0.0), fadeOut * 0.4);
|
||||
|
||||
// Add border (5 pixels)
|
||||
float borderSize = 5.0;
|
||||
vec2 pixelCoord = fragCoord;
|
||||
bool isBorder = pixelCoord.x < borderSize ||
|
||||
pixelCoord.x > iResolution.x - borderSize ||
|
||||
pixelCoord.y < borderSize ||
|
||||
pixelCoord.y > iResolution.y - borderSize;
|
||||
|
||||
if (isBorder) {
|
||||
// Bright cyan border that pulses subtly
|
||||
float pulse = sin(timeSinceFocus * 2.0) * 0.1 + 0.9;
|
||||
color = vec3(0.0, 1.0, 1.0) * pulse;
|
||||
}
|
||||
} else {
|
||||
// UNFOCUSED: Solid red overlay (no border)
|
||||
color = mix(color, vec3(1.0, 0.0, 0.0), 0.3);
|
||||
}
|
||||
|
||||
fragColor = vec4(color, 1.0);
|
||||
}
|
||||
@@ -25,6 +25,8 @@ pub const Uniforms = extern struct {
|
||||
current_cursor_color: [4]f32 align(16),
|
||||
previous_cursor_color: [4]f32 align(16),
|
||||
cursor_change_time: f32 align(4),
|
||||
time_focus: f32 align(4),
|
||||
focus: i32 align(4),
|
||||
};
|
||||
|
||||
/// The target to load shaders for.
|
||||
@@ -412,3 +414,4 @@ test "shadertoy to glsl" {
|
||||
|
||||
const test_crt = @embedFile("shaders/test_shadertoy_crt.glsl");
|
||||
const test_invalid = @embedFile("shaders/test_shadertoy_invalid.glsl");
|
||||
const test_focus = @embedFile("shaders/test_shadertoy_focus.glsl");
|
||||
|
||||
Reference in New Issue
Block a user