mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
renderer: animation timer if we have custom shaders
This commit is contained in:
@ -125,6 +125,7 @@ pub const CustomShaderState = struct {
|
||||
screen_texture: objc.Object, // MTLTexture
|
||||
sampler: mtl_sampler.Sampler,
|
||||
uniforms: mtl_shaders.PostUniforms,
|
||||
last_frame_time: std.time.Instant,
|
||||
|
||||
pub fn deinit(self: *CustomShaderState) void {
|
||||
deinitMTLResource(self.screen_texture);
|
||||
@ -325,6 +326,8 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
.date = .{ 0, 0, 0, 0 },
|
||||
.sample_rate = 1,
|
||||
},
|
||||
|
||||
.last_frame_time = try std.time.Instant.now(),
|
||||
};
|
||||
};
|
||||
errdefer if (custom_shader_state) |*state| state.deinit();
|
||||
@ -465,6 +468,12 @@ pub fn threadExit(self: *const Metal) void {
|
||||
// Metal requires no per-thread state.
|
||||
}
|
||||
|
||||
/// True if our renderer has animations so that a higher frequency
|
||||
/// timer is used.
|
||||
pub fn hasAnimations(self: *const Metal) bool {
|
||||
return self.custom_shader_state != null;
|
||||
}
|
||||
|
||||
/// Returns the grid size for a given screen size. This is safe to call
|
||||
/// on any thread.
|
||||
fn gridSize(self: *Metal) ?renderer.GridSize {
|
||||
@ -653,6 +662,14 @@ pub fn updateFrame(
|
||||
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
_ = surface;
|
||||
|
||||
// If we have custom shaders, update the animation time.
|
||||
if (self.custom_shader_state) |*state| {
|
||||
const now = std.time.Instant.now() catch state.last_frame_time;
|
||||
const since_ns: f32 = @floatFromInt(now.since(state.last_frame_time));
|
||||
state.uniforms.time = since_ns / std.time.ns_per_s;
|
||||
state.uniforms.time_delta = since_ns / std.time.ns_per_s;
|
||||
}
|
||||
|
||||
// @autoreleasepool {}
|
||||
const pool = objc.AutoreleasePool.init();
|
||||
defer pool.deinit();
|
||||
|
@ -15,6 +15,7 @@ const App = @import("../App.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const log = std.log.scoped(.renderer_thread);
|
||||
|
||||
const DRAW_INTERVAL = 33; // 30 FPS
|
||||
const CURSOR_BLINK_INTERVAL = 600;
|
||||
|
||||
/// The type used for sending messages to the IO thread. For now this is
|
||||
@ -43,6 +44,13 @@ stop_c: xev.Completion = .{},
|
||||
render_h: xev.Timer,
|
||||
render_c: xev.Completion = .{},
|
||||
|
||||
/// The timer used for draw calls. Draw calls don't update from the
|
||||
/// terminal state so they're much cheaper. They're used for animation
|
||||
/// and are paused when the terminal is not focused.
|
||||
draw_h: xev.Timer,
|
||||
draw_c: xev.Completion = .{},
|
||||
draw_active: bool = false,
|
||||
|
||||
/// The timer used for cursor blinking
|
||||
cursor_h: xev.Timer,
|
||||
cursor_c: xev.Completion = .{},
|
||||
@ -100,6 +108,10 @@ pub fn init(
|
||||
var render_h = try xev.Timer.init();
|
||||
errdefer render_h.deinit();
|
||||
|
||||
// Draw timer, see comments.
|
||||
var draw_h = try xev.Timer.init();
|
||||
errdefer draw_h.deinit();
|
||||
|
||||
// Setup a timer for blinking the cursor
|
||||
var cursor_timer = try xev.Timer.init();
|
||||
errdefer cursor_timer.deinit();
|
||||
@ -114,6 +126,7 @@ pub fn init(
|
||||
.wakeup = wakeup_h,
|
||||
.stop = stop_h,
|
||||
.render_h = render_h,
|
||||
.draw_h = draw_h,
|
||||
.cursor_h = cursor_timer,
|
||||
.surface = surface,
|
||||
.renderer = renderer_impl,
|
||||
@ -129,6 +142,7 @@ pub fn deinit(self: *Thread) void {
|
||||
self.stop.deinit();
|
||||
self.wakeup.deinit();
|
||||
self.render_h.deinit();
|
||||
self.draw_h.deinit();
|
||||
self.cursor_h.deinit();
|
||||
self.loop.deinit();
|
||||
|
||||
@ -172,27 +186,8 @@ fn threadMain_(self: *Thread) !void {
|
||||
cursorTimerCallback,
|
||||
);
|
||||
|
||||
// If we are using tracy, then we setup a prepare handle so that
|
||||
// we can mark the frame.
|
||||
// TODO
|
||||
// var frame_h: libuv.Prepare = if (!tracy.enabled) undefined else frame_h: {
|
||||
// const alloc_ptr = self.loop.getData(Allocator).?;
|
||||
// const alloc = alloc_ptr.*;
|
||||
// const h = try libuv.Prepare.init(alloc, self.loop);
|
||||
// h.setData(self);
|
||||
// try h.start(prepFrameCallback);
|
||||
//
|
||||
// break :frame_h h;
|
||||
// };
|
||||
// defer if (tracy.enabled) {
|
||||
// frame_h.close((struct {
|
||||
// fn callback(h: *libuv.Prepare) void {
|
||||
// const alloc_h = h.loop().getData(Allocator).?.*;
|
||||
// h.deinit(alloc_h);
|
||||
// }
|
||||
// }).callback);
|
||||
// _ = self.loop.run(.nowait) catch {};
|
||||
// };
|
||||
// Start the draw timer
|
||||
self.startDrawTimer();
|
||||
|
||||
// Run
|
||||
log.debug("starting renderer thread", .{});
|
||||
@ -200,6 +195,34 @@ fn threadMain_(self: *Thread) !void {
|
||||
_ = try self.loop.run(.until_done);
|
||||
}
|
||||
|
||||
fn startDrawTimer(self: *Thread) void {
|
||||
// If our renderer doesn't suppoort animations then we never run this.
|
||||
if (!@hasDecl(renderer.Renderer, "hasAnimations")) return;
|
||||
if (!self.renderer.hasAnimations()) return;
|
||||
|
||||
// Set our active state so it knows we're running. We set this before
|
||||
// even checking the active state in case we have a pending shutdown.
|
||||
self.draw_active = true;
|
||||
|
||||
// If our draw timer is already active, then we don't have to do anything.
|
||||
if (self.draw_c.state() == .active) return;
|
||||
|
||||
// Start the timer which loops
|
||||
self.draw_h.run(
|
||||
&self.loop,
|
||||
&self.draw_c,
|
||||
DRAW_INTERVAL,
|
||||
Thread,
|
||||
self,
|
||||
drawCallback,
|
||||
);
|
||||
}
|
||||
|
||||
fn stopDrawTimer(self: *Thread) void {
|
||||
// This will stop the draw on the next iteration.
|
||||
self.draw_active = false;
|
||||
}
|
||||
|
||||
/// Drain the mailbox.
|
||||
fn drainMailbox(self: *Thread) !void {
|
||||
const zone = trace(@src());
|
||||
@ -213,6 +236,9 @@ fn drainMailbox(self: *Thread) !void {
|
||||
try self.renderer.setFocus(v);
|
||||
|
||||
if (!v) {
|
||||
// Stop the draw timer
|
||||
self.stopDrawTimer();
|
||||
|
||||
// If we're not focused, then we stop the cursor blink
|
||||
if (self.cursor_c.state() == .active and
|
||||
self.cursor_c_cancel.state() == .dead)
|
||||
@ -227,6 +253,9 @@ fn drainMailbox(self: *Thread) !void {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Start the draw timer
|
||||
self.startDrawTimer();
|
||||
|
||||
// If we're focused, we immediately show the cursor again
|
||||
// and then restart the timer.
|
||||
if (self.cursor_c.state() != .active) {
|
||||
@ -325,6 +354,41 @@ fn wakeupCallback(
|
||||
return .rearm;
|
||||
}
|
||||
|
||||
fn drawCallback(
|
||||
self_: ?*Thread,
|
||||
_: *xev.Loop,
|
||||
_: *xev.Completion,
|
||||
r: xev.Timer.RunError!void,
|
||||
) xev.CallbackAction {
|
||||
_ = r catch unreachable;
|
||||
const t = self_ orelse {
|
||||
// This shouldn't happen so we log it.
|
||||
log.warn("render callback fired without data set", .{});
|
||||
return .disarm;
|
||||
};
|
||||
|
||||
// If we're doing single-threaded GPU calls then we just wake up the
|
||||
// app thread to redraw at this point.
|
||||
if (renderer.Renderer == renderer.OpenGL and
|
||||
renderer.OpenGL.single_threaded_draw)
|
||||
{
|
||||
_ = t.app_mailbox.push(
|
||||
.{ .redraw_surface = t.surface },
|
||||
.{ .instant = {} },
|
||||
);
|
||||
} else {
|
||||
t.renderer.drawFrame(t.surface) catch |err|
|
||||
log.warn("error drawing err={}", .{err});
|
||||
}
|
||||
|
||||
// Only continue if we're still active
|
||||
if (t.draw_active) {
|
||||
t.draw_h.run(&t.loop, &t.draw_c, DRAW_INTERVAL, Thread, t, drawCallback);
|
||||
}
|
||||
|
||||
return .disarm;
|
||||
}
|
||||
|
||||
fn renderCallback(
|
||||
self_: ?*Thread,
|
||||
_: *xev.Loop,
|
||||
|
@ -129,6 +129,9 @@ pub const Uniforms = extern struct {
|
||||
|
||||
/// The uniforms used for custom postprocess shaders.
|
||||
pub const PostUniforms = extern struct {
|
||||
// Note: all of the explicit aligmnments are copied from the
|
||||
// MSL developer reference just so that we can be sure that we got
|
||||
// it all exactly right.
|
||||
resolution: [3]f32 align(16),
|
||||
time: f32 align(4),
|
||||
time_delta: f32 align(4),
|
||||
|
Reference in New Issue
Block a user