mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +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
|
screen_texture: objc.Object, // MTLTexture
|
||||||
sampler: mtl_sampler.Sampler,
|
sampler: mtl_sampler.Sampler,
|
||||||
uniforms: mtl_shaders.PostUniforms,
|
uniforms: mtl_shaders.PostUniforms,
|
||||||
|
last_frame_time: std.time.Instant,
|
||||||
|
|
||||||
pub fn deinit(self: *CustomShaderState) void {
|
pub fn deinit(self: *CustomShaderState) void {
|
||||||
deinitMTLResource(self.screen_texture);
|
deinitMTLResource(self.screen_texture);
|
||||||
@ -325,6 +326,8 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.date = .{ 0, 0, 0, 0 },
|
.date = .{ 0, 0, 0, 0 },
|
||||||
.sample_rate = 1,
|
.sample_rate = 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.last_frame_time = try std.time.Instant.now(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
errdefer if (custom_shader_state) |*state| state.deinit();
|
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.
|
// 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
|
/// Returns the grid size for a given screen size. This is safe to call
|
||||||
/// on any thread.
|
/// on any thread.
|
||||||
fn gridSize(self: *Metal) ?renderer.GridSize {
|
fn gridSize(self: *Metal) ?renderer.GridSize {
|
||||||
@ -653,6 +662,14 @@ pub fn updateFrame(
|
|||||||
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||||
_ = surface;
|
_ = 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 {}
|
// @autoreleasepool {}
|
||||||
const pool = objc.AutoreleasePool.init();
|
const pool = objc.AutoreleasePool.init();
|
||||||
defer pool.deinit();
|
defer pool.deinit();
|
||||||
|
@ -15,6 +15,7 @@ const App = @import("../App.zig");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.renderer_thread);
|
const log = std.log.scoped(.renderer_thread);
|
||||||
|
|
||||||
|
const DRAW_INTERVAL = 33; // 30 FPS
|
||||||
const CURSOR_BLINK_INTERVAL = 600;
|
const CURSOR_BLINK_INTERVAL = 600;
|
||||||
|
|
||||||
/// The type used for sending messages to the IO thread. For now this is
|
/// 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_h: xev.Timer,
|
||||||
render_c: xev.Completion = .{},
|
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
|
/// The timer used for cursor blinking
|
||||||
cursor_h: xev.Timer,
|
cursor_h: xev.Timer,
|
||||||
cursor_c: xev.Completion = .{},
|
cursor_c: xev.Completion = .{},
|
||||||
@ -100,6 +108,10 @@ pub fn init(
|
|||||||
var render_h = try xev.Timer.init();
|
var render_h = try xev.Timer.init();
|
||||||
errdefer render_h.deinit();
|
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
|
// Setup a timer for blinking the cursor
|
||||||
var cursor_timer = try xev.Timer.init();
|
var cursor_timer = try xev.Timer.init();
|
||||||
errdefer cursor_timer.deinit();
|
errdefer cursor_timer.deinit();
|
||||||
@ -114,6 +126,7 @@ pub fn init(
|
|||||||
.wakeup = wakeup_h,
|
.wakeup = wakeup_h,
|
||||||
.stop = stop_h,
|
.stop = stop_h,
|
||||||
.render_h = render_h,
|
.render_h = render_h,
|
||||||
|
.draw_h = draw_h,
|
||||||
.cursor_h = cursor_timer,
|
.cursor_h = cursor_timer,
|
||||||
.surface = surface,
|
.surface = surface,
|
||||||
.renderer = renderer_impl,
|
.renderer = renderer_impl,
|
||||||
@ -129,6 +142,7 @@ pub fn deinit(self: *Thread) void {
|
|||||||
self.stop.deinit();
|
self.stop.deinit();
|
||||||
self.wakeup.deinit();
|
self.wakeup.deinit();
|
||||||
self.render_h.deinit();
|
self.render_h.deinit();
|
||||||
|
self.draw_h.deinit();
|
||||||
self.cursor_h.deinit();
|
self.cursor_h.deinit();
|
||||||
self.loop.deinit();
|
self.loop.deinit();
|
||||||
|
|
||||||
@ -172,27 +186,8 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
cursorTimerCallback,
|
cursorTimerCallback,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we are using tracy, then we setup a prepare handle so that
|
// Start the draw timer
|
||||||
// we can mark the frame.
|
self.startDrawTimer();
|
||||||
// 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 {};
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
log.debug("starting renderer thread", .{});
|
log.debug("starting renderer thread", .{});
|
||||||
@ -200,6 +195,34 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
_ = try self.loop.run(.until_done);
|
_ = 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.
|
/// Drain the mailbox.
|
||||||
fn drainMailbox(self: *Thread) !void {
|
fn drainMailbox(self: *Thread) !void {
|
||||||
const zone = trace(@src());
|
const zone = trace(@src());
|
||||||
@ -213,6 +236,9 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
try self.renderer.setFocus(v);
|
try self.renderer.setFocus(v);
|
||||||
|
|
||||||
if (!v) {
|
if (!v) {
|
||||||
|
// Stop the draw timer
|
||||||
|
self.stopDrawTimer();
|
||||||
|
|
||||||
// If we're not focused, then we stop the cursor blink
|
// If we're not focused, then we stop the cursor blink
|
||||||
if (self.cursor_c.state() == .active and
|
if (self.cursor_c.state() == .active and
|
||||||
self.cursor_c_cancel.state() == .dead)
|
self.cursor_c_cancel.state() == .dead)
|
||||||
@ -227,6 +253,9 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Start the draw timer
|
||||||
|
self.startDrawTimer();
|
||||||
|
|
||||||
// If we're focused, we immediately show the cursor again
|
// If we're focused, we immediately show the cursor again
|
||||||
// and then restart the timer.
|
// and then restart the timer.
|
||||||
if (self.cursor_c.state() != .active) {
|
if (self.cursor_c.state() != .active) {
|
||||||
@ -325,6 +354,41 @@ fn wakeupCallback(
|
|||||||
return .rearm;
|
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(
|
fn renderCallback(
|
||||||
self_: ?*Thread,
|
self_: ?*Thread,
|
||||||
_: *xev.Loop,
|
_: *xev.Loop,
|
||||||
|
@ -129,6 +129,9 @@ pub const Uniforms = extern struct {
|
|||||||
|
|
||||||
/// The uniforms used for custom postprocess shaders.
|
/// The uniforms used for custom postprocess shaders.
|
||||||
pub const PostUniforms = extern struct {
|
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),
|
resolution: [3]f32 align(16),
|
||||||
time: f32 align(4),
|
time: f32 align(4),
|
||||||
time_delta: f32 align(4),
|
time_delta: f32 align(4),
|
||||||
|
Reference in New Issue
Block a user