aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/libghostty-vt/src/renderer/metal/RenderPass.zig
blob: f204e17701aee83ce6140aea4ca60781888dc0e9 (plain) (blame)
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! 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 Pipeline = @import("Pipeline.zig");
const Sampler = @import("Sampler.zig");
const Texture = @import("Texture.zig");
const Target = @import("Target.zig");

const log = std.log.scoped(.metal);

/// Options for beginning a render pass.
pub const Options = struct {
    /// MTLCommandBuffer
    command_buffer: objc.Object,
    /// Color attachments for this render pass.
    attachments: []const Attachment,

    /// Describes a color attachment.
    pub const Attachment = struct {
        target: union(enum) {
            texture: Texture,
            target: Target,
        },
        clear_color: ?[4]f64 = null,
    };
};

/// Describes a step in a render pass.
pub const Step = struct {
    pipeline: Pipeline,
    /// MTLBuffer
    uniforms: ?objc.Object = null,
    /// MTLBuffer
    buffers: []const ?objc.Object = &.{},
    textures: []const ?Texture = &.{},
    /// Set of samplers to use for this step. The index maps to an index
    /// of a fragment texture, set via setFragmentSamplerState(_:index:).
    samplers: []const ?Sampler = &.{},
    draw: Draw,

    /// Describes the draw call for this step.
    pub const Draw = struct {
        type: mtl.MTLPrimitiveType,
        vertex_count: usize,
        instance_count: usize = 1,
    };
};

/// MTLRenderCommandEncoder
encoder: objc.Object,

/// Begin a render pass.
pub fn begin(
    opts: Options,
) Self {
    // Create a pass descriptor
    const desc = desc: {
        const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?;
        const desc = MTLRenderPassDescriptor.msgSend(
            objc.Object,
            objc.sel("renderPassDescriptor"),
            .{},
        );

        // Set our color attachment to be our drawable surface.
        const attachments = objc.Object.fromId(
            desc.getProperty(?*anyopaque, "colorAttachments"),
        );
        for (opts.attachments, 0..) |at, i| {
            const attachment = attachments.msgSend(
                objc.Object,
                objc.sel("objectAtIndexedSubscript:"),
                .{@as(c_ulong, i)},
            );

            attachment.setProperty(
                "loadAction",
                @intFromEnum(@as(
                    mtl.MTLLoadAction,
                    if (at.clear_color != null)
                        .clear
                    else
                        .load,
                )),
            );
            attachment.setProperty(
                "storeAction",
                @intFromEnum(mtl.MTLStoreAction.store),
            );
            attachment.setProperty("texture", switch (at.target) {
                .texture => |t| t.texture.value,
                .target => |t| t.texture.value,
            });
            if (at.clear_color) |c| attachment.setProperty(
                "clearColor",
                mtl.MTLClearColor{
                    .red = c[0],
                    .green = c[1],
                    .blue = c[2],
                    .alpha = c[3],
                },
            );
        }

        break :desc desc;
    };

    // MTLRenderCommandEncoder
    const encoder = opts.command_buffer.msgSend(
        objc.Object,
        objc.sel("renderCommandEncoderWithDescriptor:"),
        .{desc.value},
    );

    return .{ .encoder = encoder };
}

/// Add a step to this render pass.
pub fn step(self: *const Self, s: Step) void {
    if (s.draw.instance_count == 0) return;

    // Set pipeline state
    self.encoder.msgSend(
        void,
        objc.sel("setRenderPipelineState:"),
        .{s.pipeline.state.value},
    );

    if (s.buffers.len > 0) {
        // We reserve index 0 for the vertex buffer, this isn't very
        // flexible but it lines up with the API we have for OpenGL.
        if (s.buffers[0]) |buf| {
            self.encoder.msgSend(
                void,
                objc.sel("setVertexBuffer:offset:atIndex:"),
                .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 0) },
            );
            self.encoder.msgSend(
                void,
                objc.sel("setFragmentBuffer:offset:atIndex:"),
                .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 0) },
            );
        }

        // Set the rest of the buffers starting at index 2, this is
        // so that we can use index 1 for the uniforms if present.
        //
        // Also, we set buffers (and textures) for both stages.
        //
        // Again, not very flexible, but it's consistent and predictable,
        // and we need to treat the uniforms as special because of OpenGL.
        //
        // TODO: Maybe in the future add info to the pipeline struct which
        //       allows it to define a mapping between provided buffers and
        //       what index they get set at for the vertex / fragment stage.
        for (s.buffers[1..], 2..) |b, i| if (b) |buf| {
            self.encoder.msgSend(
                void,
                objc.sel("setVertexBuffer:offset:atIndex:"),
                .{ buf.value, @as(c_ulong, 0), @as(c_ulong, i) },
            );
            self.encoder.msgSend(
                void,
                objc.sel("setFragmentBuffer:offset:atIndex:"),
                .{ buf.value, @as(c_ulong, 0), @as(c_ulong, i) },
            );
        };
    }

    // Set the uniforms as buffer index 1 if present.
    if (s.uniforms) |buf| {
        self.encoder.msgSend(
            void,
            objc.sel("setVertexBuffer:offset:atIndex:"),
            .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 1) },
        );
        self.encoder.msgSend(
            void,
            objc.sel("setFragmentBuffer:offset:atIndex:"),
            .{ buf.value, @as(c_ulong, 0), @as(c_ulong, 1) },
        );
    }

    // Set textures.
    for (s.textures, 0..) |t, i| if (t) |tex| {
        self.encoder.msgSend(
            void,
            objc.sel("setVertexTexture:atIndex:"),
            .{ tex.texture.value, @as(c_ulong, i) },
        );
        self.encoder.msgSend(
            void,
            objc.sel("setFragmentTexture:atIndex:"),
            .{ tex.texture.value, @as(c_ulong, i) },
        );
    };

    // Set samplers.
    for (s.samplers, 0..) |samp, i| if (samp) |sampler| {
        self.encoder.msgSend(
            void,
            objc.sel("setFragmentSamplerState:atIndex:"),
            .{ sampler.sampler.value, @as(c_ulong, i) },
        );
    };

    // Draw!
    self.encoder.msgSend(
        void,
        objc.sel("drawPrimitives:vertexStart:vertexCount:instanceCount:"),
        .{
            @intFromEnum(s.draw.type),
            @as(c_ulong, 0),
            @as(c_ulong, s.draw.vertex_count),
            @as(c_ulong, s.draw.instance_count),
        },
    );
}

/// Complete this render pass.
/// This struct can no longer be used after calling this.
pub fn complete(self: *const Self) void {
    self.encoder.msgSend(void, objc.sel("endEncoding"), .{});
}