renderer/metal: setup display link

This commit is contained in:
Mitchell Hashimoto
2024-05-03 20:45:56 -07:00
parent cc72e6de23
commit a40ffad218
2 changed files with 71 additions and 2 deletions

View File

@ -11,6 +11,7 @@ const objc = @import("objc");
const macos = @import("macos"); const macos = @import("macos");
const imgui = @import("imgui"); const imgui = @import("imgui");
const glslang = @import("glslang"); const glslang = @import("glslang");
const xev = @import("xev");
const apprt = @import("../apprt.zig"); const apprt = @import("../apprt.zig");
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");
@ -42,6 +43,11 @@ const InstanceBuffer = mtl_buffer.Buffer(u16);
const ImagePlacementList = std.ArrayListUnmanaged(mtl_image.Placement); const ImagePlacementList = std.ArrayListUnmanaged(mtl_image.Placement);
const DisplayLink = switch (builtin.os.tag) {
.macos => *macos.video.DisplayLink,
else => void,
};
// Get native API access on certain platforms so we can do more customization. // Get native API access on certain platforms so we can do more customization.
const glfwNative = glfw.Native(.{ const glfwNative = glfw.Native(.{
.cocoa = builtin.os.tag == .macos, .cocoa = builtin.os.tag == .macos,
@ -115,6 +121,11 @@ shaders: Shaders, // Compiled shaders
/// Metal objects /// Metal objects
layer: objc.Object, // CAMetalLayer layer: objc.Object, // CAMetalLayer
/// The CVDisplayLink used to drive the rendering loop in sync
/// with the display. This is void on platforms that don't support
/// a display link.
display_link: ?DisplayLink = null,
/// Custom shader state. This is only set if we have custom shaders. /// Custom shader state. This is only set if we have custom shaders.
custom_shader_state: ?CustomShaderState = null, custom_shader_state: ?CustomShaderState = null,
@ -545,9 +556,15 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
}; };
}; };
const cells = try mtl_cell.Contents.init(alloc); var cells = try mtl_cell.Contents.init(alloc);
errdefer cells.deinit(alloc); errdefer cells.deinit(alloc);
const display_link: ?DisplayLink = switch (builtin.os.tag) {
.macos => try macos.video.DisplayLink.createWithActiveCGDisplays(),
else => null,
};
errdefer if (display_link) |v| v.release();
return Metal{ return Metal{
.alloc = alloc, .alloc = alloc,
.config = options.config, .config = options.config,
@ -581,6 +598,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
// Metal stuff // Metal stuff
.layer = layer, .layer = layer,
.display_link = display_link,
.custom_shader_state = custom_shader_state, .custom_shader_state = custom_shader_state,
.gpu_state = gpu_state, .gpu_state = gpu_state,
}; };
@ -589,6 +607,13 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
pub fn deinit(self: *Metal) void { pub fn deinit(self: *Metal) void {
self.gpu_state.deinit(); self.gpu_state.deinit();
if (DisplayLink != void) {
if (self.display_link) |display_link| {
display_link.stop() catch {};
display_link.release();
}
}
self.cells.deinit(self.alloc); self.cells.deinit(self.alloc);
self.font_shaper.deinit(); self.font_shaper.deinit();
@ -635,6 +660,45 @@ pub fn threadExit(self: *const Metal) void {
// Metal requires no per-thread state. // Metal requires no per-thread state.
} }
/// Called by renderer.Thread when it starts the main loop.
pub fn loopEnter(self: *Metal, thr: *renderer.Thread) !void {
// If we don't support a display link we have no work to do.
if (comptime DisplayLink == void) return;
// This is when we know our "self" pointer is stable so we can
// setup the display link. To setup the display link we set our
// callback and we can start it immediately.
const display_link = self.display_link orelse return;
try display_link.setOutputCallback(
xev.Async,
&displayLinkCallback,
&thr.draw_now,
);
display_link.start() catch {};
}
/// Called by renderer.Thread when it exits the main loop.
pub fn loopExit(self: *const Metal) void {
// If we don't support a display link we have no work to do.
if (comptime DisplayLink == void) return;
// Stop our display link. If this fails its okay it just means
// that we either never started it or the view its attached to
// is gone which is fine.
const display_link = self.display_link orelse return;
display_link.stop() catch {};
}
fn displayLinkCallback(
_: *macos.video.DisplayLink,
ud: ?*xev.Async,
) void {
const draw_now = ud orelse return;
draw_now.notify() catch |err| {
log.err("error notifying draw_now err={}", .{err});
};
}
/// True if our renderer has animations so that a higher frequency /// True if our renderer has animations so that a higher frequency
/// timer is used. /// timer is used.
pub fn hasAnimations(self: *const Metal) bool { pub fn hasAnimations(self: *const Metal) bool {

View File

@ -146,7 +146,7 @@ pub fn init(
var mailbox = try Mailbox.create(alloc); var mailbox = try Mailbox.create(alloc);
errdefer mailbox.destroy(alloc); errdefer mailbox.destroy(alloc);
return Thread{ return .{
.alloc = alloc, .alloc = alloc,
.config = DerivedConfig.init(config), .config = DerivedConfig.init(config),
.loop = loop, .loop = loop,
@ -191,6 +191,11 @@ pub fn threadMain(self: *Thread) void {
fn threadMain_(self: *Thread) !void { fn threadMain_(self: *Thread) !void {
defer log.debug("renderer thread exited", .{}); defer log.debug("renderer thread exited", .{});
// Run our loop start/end callbacks if the renderer cares.
const has_loop = @hasDecl(renderer.Renderer, "loopEnter");
if (has_loop) try self.renderer.loopEnter(self);
defer if (has_loop) self.renderer.loopExit();
// Run our thread start/end callbacks. This is important because some // Run our thread start/end callbacks. This is important because some
// renderers have to do per-thread setup. For example, OpenGL has to set // renderers have to do per-thread setup. For example, OpenGL has to set
// some thread-local state since that is how it works. // some thread-local state since that is how it works.