aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/renderer/metal/Frame.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/renderer/metal/Frame.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/renderer/metal/Frame.zig')
-rw-r--r--vendor/libghostty-vt/src/renderer/metal/Frame.zig131
1 files changed, 131 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/renderer/metal/Frame.zig b/vendor/libghostty-vt/src/renderer/metal/Frame.zig
new file mode 100644
index 00000000..388b4f9e
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/metal/Frame.zig
@@ -0,0 +1,131 @@
+//! Wrapper for handling render passes.
+const Self = @This();
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const objc = @import("objc");
+
+const mtl = @import("api.zig");
+const Renderer = @import("../generic.zig").Renderer(Metal);
+const Metal = @import("../Metal.zig");
+const Target = @import("Target.zig");
+const RenderPass = @import("RenderPass.zig");
+
+const Health = @import("../../renderer.zig").Health;
+
+const log = std.log.scoped(.metal);
+
+/// Options for beginning a frame.
+pub const Options = struct {
+ /// MTLCommandQueue
+ queue: objc.Object,
+};
+
+/// MTLCommandBuffer
+buffer: objc.Object,
+
+block: CompletionBlock.Context,
+
+/// Begin encoding a frame.
+pub fn begin(
+ opts: Options,
+ /// Once the frame has been completed, the `frameCompleted` method
+ /// on the renderer is called with the health status of the frame.
+ renderer: *Renderer,
+ /// The target is presented via the provided renderer's API when completed.
+ target: *Target,
+) !Self {
+ const buffer = opts.queue.msgSend(
+ objc.Object,
+ objc.sel("commandBuffer"),
+ .{},
+ );
+
+ // Create our block to register for completion updates.
+ // The block is deallocated by the objC runtime on success.
+ const block = CompletionBlock.init(
+ .{
+ .renderer = renderer,
+ .target = target,
+ .sync = false,
+ },
+ &bufferCompleted,
+ );
+
+ return .{ .buffer = buffer, .block = block };
+}
+
+/// This is the block type used for the addCompletedHandler callback.
+const CompletionBlock = objc.Block(struct {
+ renderer: *Renderer,
+ target: *Target,
+ sync: bool,
+}, .{
+ objc.c.id, // MTLCommandBuffer
+}, void);
+
+fn bufferCompleted(
+ block: *const CompletionBlock.Context,
+ buffer_id: objc.c.id,
+) callconv(.c) void {
+ const buffer = objc.Object.fromId(buffer_id);
+
+ // Get our command buffer status to pass back to the generic renderer.
+ const status = buffer.getProperty(mtl.MTLCommandBufferStatus, "status");
+ const health: Health = switch (status) {
+ .@"error" => .unhealthy,
+ else => .healthy,
+ };
+
+ // If the frame is healthy, present it.
+ if (health == .healthy) {
+ block.renderer.api.present(
+ block.target.*,
+ block.sync,
+ ) catch |err| {
+ log.err("Failed to present render target: err={}", .{err});
+ };
+ }
+
+ block.renderer.frameCompleted(health);
+}
+
+/// Add a render pass to this frame with the provided attachments.
+/// Returns a RenderPass which allows render steps to be added.
+pub inline fn renderPass(
+ self: *const Self,
+ attachments: []const RenderPass.Options.Attachment,
+) RenderPass {
+ return RenderPass.begin(.{
+ .attachments = attachments,
+ .command_buffer = self.buffer,
+ });
+}
+
+/// Complete this frame and present the target.
+///
+/// If `sync` is true, this will block until the frame is presented.
+pub inline fn complete(self: *Self, sync: bool) void {
+ // If we don't need to complete synchronously,
+ // we add our block as a completion handler.
+ //
+ // It will be copied when we add the handler, and then the
+ // copy will be deallocated by the objc runtime on success.
+ if (!sync) {
+ self.buffer.msgSend(
+ void,
+ objc.sel("addCompletedHandler:"),
+ .{&self.block},
+ );
+ }
+
+ self.buffer.msgSend(void, objc.sel("commit"), .{});
+
+ // If we need to complete synchronously, we wait until
+ // the buffer is completed and invoke the block directly.
+ if (sync) {
+ self.buffer.msgSend(void, "waitUntilCompleted", .{});
+ self.block.sync = true;
+ CompletionBlock.invoke(&self.block, .{self.buffer.value});
+ }
+}