1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
//! Wrapper for handling render passes.
const Self = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const builtin = @import("builtin");
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 Pipeline = @import("Pipeline.zig");
const RenderPass = @import("RenderPass.zig");
const Buffer = @import("buffer.zig").Buffer;
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});
}
}
|