aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/libghostty-vt/src/renderer/metal/Pipeline.zig
blob: 9ba25c350af9ff78a40645be10f89b6e3a11a7cc (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
//! Wrapper for handling render pipelines.
const Self = @This();

const std = @import("std");
const Allocator = std.mem.Allocator;
const macos = @import("macos");
const objc = @import("objc");

const mtl = @import("api.zig");

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

/// Options for initializing a render pipeline.
pub const Options = struct {
    /// MTLDevice
    device: objc.Object,

    /// Name of the vertex function
    vertex_fn: []const u8,
    /// Name of the fragment function
    fragment_fn: []const u8,

    /// MTLLibrary to get the vertex function from
    vertex_library: objc.Object,
    /// MTLLibrary to get the fragment function from
    fragment_library: objc.Object,

    /// Vertex step function
    step_fn: mtl.MTLVertexStepFunction = .per_vertex,

    /// Info about the color attachments used by this render pipeline.
    attachments: []const Attachment,

    /// Describes a color attachment.
    pub const Attachment = struct {
        pixel_format: mtl.MTLPixelFormat,
        blending_enabled: bool = true,
    };
};

/// MTLRenderPipelineState
state: objc.Object,

pub fn init(comptime VertexAttributes: ?type, opts: Options) !Self {
    // Create our descriptor
    const desc = init: {
        const Class = objc.getClass("MTLRenderPipelineDescriptor").?;
        const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
        const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
        break :init id_init;
    };
    defer desc.msgSend(void, objc.sel("release"), .{});

    // Get our vertex and fragment functions and add them to the descriptor.
    {
        const str = try macos.foundation.String.createWithBytes(
            opts.vertex_fn,
            .utf8,
            false,
        );
        defer str.release();

        const ptr = opts.vertex_library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
        const func_vert = objc.Object.fromId(ptr.?);
        defer func_vert.msgSend(void, objc.sel("release"), .{});

        desc.setProperty("vertexFunction", func_vert);
    }
    {
        const str = try macos.foundation.String.createWithBytes(
            opts.fragment_fn,
            .utf8,
            false,
        );
        defer str.release();

        const ptr = opts.fragment_library.msgSend(?*anyopaque, objc.sel("newFunctionWithName:"), .{str});
        const func_frag = objc.Object.fromId(ptr.?);
        defer func_frag.msgSend(void, objc.sel("release"), .{});

        desc.setProperty("fragmentFunction", func_frag);
    }

    // If we have vertex attributes, create and add a vertex descriptor.
    if (VertexAttributes) |V| {
        const vertex_desc = init: {
            const Class = objc.getClass("MTLVertexDescriptor").?;
            const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
            const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
            break :init id_init;
        };
        defer vertex_desc.msgSend(void, objc.sel("release"), .{});

        // Our attributes are the fields of the input
        const attrs = objc.Object.fromId(vertex_desc.getProperty(?*anyopaque, "attributes"));
        autoAttribute(V, attrs);

        // The layout describes how and when we fetch the next vertex input.
        const layouts = objc.Object.fromId(vertex_desc.getProperty(?*anyopaque, "layouts"));
        {
            const layout = layouts.msgSend(
                objc.Object,
                objc.sel("objectAtIndexedSubscript:"),
                .{@as(c_ulong, 0)},
            );

            layout.setProperty("stepFunction", @intFromEnum(opts.step_fn));
            layout.setProperty("stride", @as(c_ulong, @sizeOf(V)));
        }

        desc.setProperty("vertexDescriptor", vertex_desc);
    }

    // Set our color attachment
    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("pixelFormat", @intFromEnum(at.pixel_format));

        attachment.setProperty("blendingEnabled", at.blending_enabled);
        // We always use premultiplied alpha blending for now.
        if (at.blending_enabled) {
            attachment.setProperty("rgbBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
            attachment.setProperty("alphaBlendOperation", @intFromEnum(mtl.MTLBlendOperation.add));
            attachment.setProperty("sourceRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
            attachment.setProperty("sourceAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one));
            attachment.setProperty("destinationRGBBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
            attachment.setProperty("destinationAlphaBlendFactor", @intFromEnum(mtl.MTLBlendFactor.one_minus_source_alpha));
        }
    }

    // Make our state
    var err: ?*anyopaque = null;
    const pipeline_state = opts.device.msgSend(
        objc.Object,
        objc.sel("newRenderPipelineStateWithDescriptor:error:"),
        .{ desc, &err },
    );
    try checkError(err);
    errdefer pipeline_state.release();

    return .{ .state = pipeline_state };
}

pub fn deinit(self: *const Self) void {
    self.state.release();
}

fn autoAttribute(T: type, attrs: objc.Object) void {
    inline for (@typeInfo(T).@"struct".fields, 0..) |field, i| {
        const offset = @offsetOf(T, field.name);

        const FT = switch (@typeInfo(field.type)) {
            .@"struct" => |e| e.backing_integer.?,
            .@"enum" => |e| e.tag_type,
            else => field.type,
        };

        // Very incomplete list, expand as necessary.
        const format = switch (FT) {
            [4]u8 => mtl.MTLVertexFormat.uchar4,
            [2]u16 => mtl.MTLVertexFormat.ushort2,
            [2]i16 => mtl.MTLVertexFormat.short2,
            f32 => mtl.MTLVertexFormat.float,
            [2]f32 => mtl.MTLVertexFormat.float2,
            [4]f32 => mtl.MTLVertexFormat.float4,
            i32 => mtl.MTLVertexFormat.int,
            [2]i32 => mtl.MTLVertexFormat.int2,
            [4]i32 => mtl.MTLVertexFormat.int2,
            u32 => mtl.MTLVertexFormat.uint,
            [2]u32 => mtl.MTLVertexFormat.uint2,
            [4]u32 => mtl.MTLVertexFormat.uint4,
            u8 => mtl.MTLVertexFormat.uchar,
            i8 => mtl.MTLVertexFormat.char,
            else => comptime unreachable,
        };

        const attr = attrs.msgSend(
            objc.Object,
            objc.sel("objectAtIndexedSubscript:"),
            .{@as(c_ulong, i)},
        );

        attr.setProperty("format", @intFromEnum(format));
        attr.setProperty("offset", @as(c_ulong, offset));
        attr.setProperty("bufferIndex", @as(c_ulong, 0));
    }
}

fn checkError(err_: ?*anyopaque) !void {
    const nserr = objc.Object.fromId(err_ orelse return);
    const str = @as(
        *macos.foundation.String,
        @ptrCast(nserr.getProperty(?*anyopaque, "localizedDescription").?),
    );

    log.err("metal error={s}", .{str.cstringPtr(.ascii).?});
    return error.MetalFailed;
}