lib/sys/src/apple/metal.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const apple = @import("root.zig");
  3 const sys = @import("../root.zig");
  4 
  5 pub const available = sys.capabilities.current.supportsAppleMetal();
  6 
  7 pub const Device = *opaque {};
  8 pub const CommandQueue = *opaque {};
  9 pub const CommandBuffer = *opaque {};
 10 pub const ComputeCommandEncoder = *opaque {};
 11 pub const SharedEvent = *opaque {};
 12 pub const Buffer = *opaque {};
 13 pub const Library = *opaque {};
 14 pub const Function = *opaque {};
 15 pub const ComputePipelineState = *opaque {};
 16 pub const CompileOptions = *opaque {};
 17 pub const Texture = *opaque {};
 18 pub const TextureDescriptor = *opaque {};
 19 pub const SamplerDescriptor = *opaque {};
 20 pub const SamplerState = *opaque {};
 21 pub const VertexDescriptor = *opaque {};
 22 pub const RenderPipelineDescriptor = *opaque {};
 23 pub const RenderPipelineState = *opaque {};
 24 pub const DepthStencilState = *opaque {};
 25 pub const RenderPassDescriptor = *opaque {};
 26 pub const RenderCommandEncoder = *opaque {};
 27 pub const BlitCommandEncoder = *opaque {};
 28 pub const IndirectCommandBufferDescriptor = *opaque {};
 29 pub const IndirectCommandBuffer = *opaque {};
 30 pub const IndirectRenderCommand = *opaque {};
 31 pub const Drawable = *opaque {};
 32 /// A `CAMetalLayer`.
 33 pub const Layer = *opaque {};
 34 
 35 pub const ResourceOptions = apple.foundation.NSUInteger;
 36 pub const resource_storage_mode_shared: ResourceOptions = 0;
 37 
 38 pub const Size = extern struct {
 39     width: apple.foundation.NSUInteger,
 40     height: apple.foundation.NSUInteger,
 41     depth: apple.foundation.NSUInteger,
 42 };
 43 
 44 pub const Origin = extern struct {
 45     x: apple.foundation.NSUInteger = 0,
 46     y: apple.foundation.NSUInteger = 0,
 47     z: apple.foundation.NSUInteger = 0,
 48 };
 49 
 50 /// `MTLViewport`: a pixel rectangle with its origin at the top left, and the depth range.
 51 pub const Viewport = extern struct {
 52     origin_x: f64,
 53     origin_y: f64,
 54     width: f64,
 55     height: f64,
 56     znear: f64,
 57     zfar: f64,
 58 };
 59 
 60 pub const ScissorRect = extern struct {
 61     x: apple.foundation.NSUInteger,
 62     y: apple.foundation.NSUInteger,
 63     width: apple.foundation.NSUInteger,
 64     height: apple.foundation.NSUInteger,
 65 };
 66 
 67 pub const ClearColor = extern struct {
 68     red: f64,
 69     green: f64,
 70     blue: f64,
 71     alpha: f64,
 72 };
 73 
 74 /// `NSRange`.
 75 pub const Range = extern struct {
 76     location: apple.foundation.NSUInteger,
 77     length: apple.foundation.NSUInteger,
 78 };
 79 
 80 pub const PixelFormat = enum(apple.foundation.NSUInteger) {
 81     rgba8_unorm = 70,
 82     rgba8_unorm_srgb = 71,
 83     bgra8_unorm = 80,
 84     bgra8_unorm_srgb = 81,
 85     depth32_float = 252,
 86     _,
 87 };
 88 
 89 pub const VertexFormat = enum(apple.foundation.NSUInteger) {
 90     float = 28,
 91     float2 = 29,
 92     float3 = 30,
 93     float4 = 31,
 94     uint = 36,
 95     uint2 = 37,
 96     uint3 = 38,
 97     uint4 = 39,
 98     _,
 99 };
100 
101 pub const VertexStepFunction = enum(apple.foundation.NSUInteger) {
102     per_vertex = 1,
103     per_instance = 2,
104     _,
105 };
106 
107 pub const PrimitiveType = enum(apple.foundation.NSUInteger) {
108     line = 1,
109     line_strip = 2,
110     triangle = 3,
111     triangle_strip = 4,
112     _,
113 };
114 
115 pub const IndexType = enum(apple.foundation.NSUInteger) {
116     uint16 = 0,
117     uint32 = 1,
118     _,
119 };
120 
121 /// `MTLCompareFunction`, which applies as `fragment op stored`.
122 pub const CompareFunction = enum(apple.foundation.NSUInteger) {
123     never = 0,
124     less = 1,
125     equal = 2,
126     less_equal = 3,
127     greater = 4,
128     not_equal = 5,
129     greater_equal = 6,
130     always = 7,
131     _,
132 };
133 
134 pub const BlendFactor = enum(apple.foundation.NSUInteger) {
135     zero = 0,
136     one = 1,
137     source_alpha = 4,
138     one_minus_source_alpha = 5,
139     _,
140 };
141 
142 /// The factors of one additive blend, `source * s + destination * d` per channel group.
143 pub const Blend = struct {
144     source_rgb: BlendFactor,
145     destination_rgb: BlendFactor,
146     source_alpha: BlendFactor,
147     destination_alpha: BlendFactor,
148 };
149 
150 pub const LoadAction = enum(apple.foundation.NSUInteger) {
151     dont_care = 0,
152     load = 1,
153     clear = 2,
154     _,
155 };
156 
157 pub const StorageMode = enum(apple.foundation.NSUInteger) {
158     shared = 0,
159     managed = 1,
160     private = 2,
161     _,
162 };
163 
164 pub const TextureUsage = apple.foundation.NSUInteger;
165 pub const texture_usage_shader_read: TextureUsage = 1 << 0;
166 pub const texture_usage_render_target: TextureUsage = 1 << 2;
167 
168 pub const SamplerFilter = enum(apple.foundation.NSUInteger) {
169     nearest = 0,
170     linear = 1,
171     _,
172 };
173 
174 pub const SamplerAddressMode = enum(apple.foundation.NSUInteger) {
175     clamp_to_edge = 0,
176     repeat = 2,
177     _,
178 };
179 
180 pub const IndirectCommandType = apple.foundation.NSUInteger;
181 pub const indirect_command_draw: IndirectCommandType = 1 << 0;
182 pub const indirect_command_draw_indexed: IndirectCommandType = 1 << 1;
183 
184 pub const ResourceUsage = apple.foundation.NSUInteger;
185 pub const resource_usage_read: ResourceUsage = 1 << 0;
186 
187 pub const RenderStages = apple.foundation.NSUInteger;
188 pub const render_stage_vertex: RenderStages = 1 << 0;
189 pub const render_stage_fragment: RenderStages = 1 << 1;
190 
191 /// How a library compiled from source treats floating point.
192 pub const MathSetting = enum {
193     /// `mathMode` is `MTLMathModeSafe`, which macOS 15 introduced.
194     math_mode_safe,
195     /// `fastMathEnabled` is `NO`, the older property the safe mode replaces.
196     fast_math_disabled,
197 };
198 
199 const math_mode_safe: apple.foundation.NSInteger = 0;
200 
201 pub const CommandBufferStatus = enum(apple.foundation.NSUInteger) {
202     not_enqueued = 0,
203     enqueued = 1,
204     committed = 2,
205     scheduled = 3,
206     completed = 4,
207     failed = 5,
208     _,
209 };
210 
211 pub const CommandBufferError = enum(apple.foundation.NSUInteger) {
212     none = 0,
213     internal = 1,
214     timeout = 2,
215     page_fault = 3,
216     access_revoked = 4,
217     not_permitted = 7,
218     out_of_memory = 8,
219     invalid_resource = 9,
220     memoryless = 10,
221     device_removed = 11,
222     stack_overflow = 12,
223     _,
224 };
225 
226 extern fn MTLCreateSystemDefaultDevice() ?Device;
227 
228 pub fn systemDefaultDevice() ?Device {
229     return MTLCreateSystemDefaultDevice();
230 }
231 
232 pub fn newCommandQueue(device: Device) ?CommandQueue {
233     return apple.objc.send(?CommandQueue, @ptrCast(device), apple.objc.selector("newCommandQueue"), .{});
234 }
235 
236 pub fn newSharedEvent(device: Device) ?SharedEvent {
237     return apple.objc.send(?SharedEvent, @ptrCast(device), apple.objc.selector("newSharedEvent"), .{});
238 }
239 
240 pub fn newBuffer(device: Device, byte_size: usize) ?Buffer {
241     return apple.objc.send(
242         ?Buffer,
243         @ptrCast(device),
244         apple.objc.selector("newBufferWithLength:options:"),
245         .{ byte_size, resource_storage_mode_shared },
246     );
247 }
248 
249 pub fn newLibraryWithSource(
250     device: Device,
251     source: apple.foundation.String,
252     options: ?CompileOptions,
253     out_error: *?apple.foundation.Error,
254 ) ?Library {
255     return apple.objc.send(
256         ?Library,
257         @ptrCast(device),
258         apple.objc.selector("newLibraryWithSource:options:error:"),
259         .{ source, options, out_error },
260     );
261 }
262 
263 pub fn newLibraryWithData(
264     device: Device,
265     data: apple.dispatch.Data,
266     out_error: *?apple.foundation.Error,
267 ) ?Library {
268     return apple.objc.send(
269         ?Library,
270         @ptrCast(device),
271         apple.objc.selector("newLibraryWithData:error:"),
272         .{ data, out_error },
273     );
274 }
275 
276 pub fn newFunction(library: Library, name: apple.foundation.String) ?Function {
277     return apple.objc.send(?Function, @ptrCast(library), apple.objc.selector("newFunctionWithName:"), .{name});
278 }
279 
280 pub fn newComputePipelineState(
281     device: Device,
282     function: Function,
283     out_error: *?apple.foundation.Error,
284 ) ?ComputePipelineState {
285     return apple.objc.send(
286         ?ComputePipelineState,
287         @ptrCast(device),
288         apple.objc.selector("newComputePipelineStateWithFunction:error:"),
289         .{ function, out_error },
290     );
291 }
292 
293 pub fn commandBuffer(queue: CommandQueue) ?CommandBuffer {
294     return apple.objc.send(?CommandBuffer, @ptrCast(queue), apple.objc.selector("commandBuffer"), .{});
295 }
296 
297 pub fn computeCommandEncoder(command_buffer: CommandBuffer) ?ComputeCommandEncoder {
298     return apple.objc.send(
299         ?ComputeCommandEncoder,
300         @ptrCast(command_buffer),
301         apple.objc.selector("computeCommandEncoder"),
302         .{},
303     );
304 }
305 
306 pub fn setSignaledValue(event: SharedEvent, value: u64) void {
307     apple.objc.send(void, @ptrCast(event), apple.objc.selector("setSignaledValue:"), .{value});
308 }
309 
310 pub fn signaledValue(event: SharedEvent) u64 {
311     return apple.objc.send(u64, @ptrCast(event), apple.objc.selector("signaledValue"), .{});
312 }
313 
314 pub fn encodeSignalEvent(command_buffer: CommandBuffer, event: SharedEvent, value: u64) void {
315     apple.objc.send(
316         void,
317         @ptrCast(command_buffer),
318         apple.objc.selector("encodeSignalEvent:value:"),
319         .{ event, value },
320     );
321 }
322 
323 pub fn encodeWaitForEvent(command_buffer: CommandBuffer, event: SharedEvent, value: u64) void {
324     apple.objc.send(
325         void,
326         @ptrCast(command_buffer),
327         apple.objc.selector("encodeWaitForEvent:value:"),
328         .{ event, value },
329     );
330 }
331 
332 pub fn setComputePipelineState(encoder: ComputeCommandEncoder, pipeline: ComputePipelineState) void {
333     apple.objc.send(
334         void,
335         @ptrCast(encoder),
336         apple.objc.selector("setComputePipelineState:"),
337         .{pipeline},
338     );
339 }
340 
341 pub fn setBuffer(
342     encoder: ComputeCommandEncoder,
343     buffer: Buffer,
344     offset: apple.foundation.NSUInteger,
345     index: apple.foundation.NSUInteger,
346 ) void {
347     apple.objc.send(
348         void,
349         @ptrCast(encoder),
350         apple.objc.selector("setBuffer:offset:atIndex:"),
351         .{ buffer, offset, index },
352     );
353 }
354 
355 pub fn setBytes(
356     encoder: ComputeCommandEncoder,
357     bytes: *const anyopaque,
358     length: apple.foundation.NSUInteger,
359     index: apple.foundation.NSUInteger,
360 ) void {
361     apple.objc.send(
362         void,
363         @ptrCast(encoder),
364         apple.objc.selector("setBytes:length:atIndex:"),
365         .{ bytes, length, index },
366     );
367 }
368 
369 pub fn dispatchThreadgroups(
370     encoder: ComputeCommandEncoder,
371     threadgroups: Size,
372     threads_per_threadgroup: Size,
373 ) void {
374     apple.objc.send(
375         void,
376         @ptrCast(encoder),
377         apple.objc.selector("dispatchThreadgroups:threadsPerThreadgroup:"),
378         .{ threadgroups, threads_per_threadgroup },
379     );
380 }
381 
382 pub fn endEncoding(encoder: ComputeCommandEncoder) void {
383     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("endEncoding"), .{});
384 }
385 
386 pub fn commit(command_buffer: CommandBuffer) void {
387     apple.objc.send(void, @ptrCast(command_buffer), apple.objc.selector("commit"), .{});
388 }
389 
390 pub fn waitUntilCompleted(command_buffer: CommandBuffer) void {
391     apple.objc.send(void, @ptrCast(command_buffer), apple.objc.selector("waitUntilCompleted"), .{});
392 }
393 
394 pub fn commandBufferStatus(command_buffer: CommandBuffer) CommandBufferStatus {
395     const raw = apple.objc.send(
396         apple.foundation.NSUInteger,
397         @ptrCast(command_buffer),
398         apple.objc.selector("status"),
399         .{},
400     );
401     return @fromBackingInt(@intCast(raw));
402 }
403 
404 pub fn commandBufferError(command_buffer: CommandBuffer) ?apple.foundation.Error {
405     return apple.objc.send(
406         ?apple.foundation.Error,
407         @ptrCast(command_buffer),
408         apple.objc.selector("error"),
409         .{},
410     );
411 }
412 
413 pub fn bufferContents(buffer: Buffer) ?*anyopaque {
414     return apple.objc.send(?*anyopaque, @ptrCast(buffer), apple.objc.selector("contents"), .{});
415 }
416 
417 /// Compile options with fast math off: no reassociation, no finite-math assumption and no flush of
418 /// subnormals beyond what the device does itself. The caller releases the options.
419 pub fn newSafeCompileOptions(setting: *MathSetting) ?CompileOptions {
420     const options = newObject(CompileOptions, "MTLCompileOptions") orelse return null;
421     if (apple.objc.respondsTo(@ptrCast(options), apple.objc.selector("setMathMode:"))) {
422         apple.objc.send(void, @ptrCast(options), apple.objc.selector("setMathMode:"), .{math_mode_safe});
423         setting.* = .math_mode_safe;
424     } else {
425         apple.objc.send(void, @ptrCast(options), apple.objc.selector("setFastMathEnabled:"), .{apple.objc.boolean(false)});
426         setting.* = .fast_math_disabled;
427     }
428     return options;
429 }
430 
431 pub fn deviceName(device: Device) ?apple.foundation.String {
432     return apple.objc.send(?apple.foundation.String, @ptrCast(device), apple.objc.selector("name"), .{});
433 }
434 
435 /// A private 2D texture without mipmaps. The caller releases it.
436 pub fn newTexture2D(
437     device: Device,
438     format: PixelFormat,
439     width: u32,
440     height: u32,
441     usage: TextureUsage,
442     storage: StorageMode,
443 ) ?Texture {
444     const descriptor_class = apple.objc.class("MTLTextureDescriptor") orelse return null;
445     const descriptor = apple.objc.send(
446         ?TextureDescriptor,
447         descriptor_class,
448         apple.objc.selector("texture2DDescriptorWithPixelFormat:width:height:mipmapped:"),
449         .{ @backingInt(format), @as(apple.foundation.NSUInteger, width), @as(apple.foundation.NSUInteger, height), apple.objc.boolean(false) },
450     ) orelse return null;
451     apple.objc.send(void, @ptrCast(descriptor), apple.objc.selector("setUsage:"), .{usage});
452     apple.objc.send(void, @ptrCast(descriptor), apple.objc.selector("setStorageMode:"), .{@backingInt(storage)});
453     return apple.objc.send(?Texture, @ptrCast(device), apple.objc.selector("newTextureWithDescriptor:"), .{descriptor});
454 }
455 
456 /// A sampler without mipmaps. The caller releases it.
457 pub fn newSamplerState(device: Device, filter: SamplerFilter, address: SamplerAddressMode) ?SamplerState {
458     const descriptor = newObject(SamplerDescriptor, "MTLSamplerDescriptor") orelse return null;
459     defer apple.objc.release(descriptor);
460     const target: apple.objc.Id = @ptrCast(descriptor);
461     apple.objc.send(void, target, apple.objc.selector("setMinFilter:"), .{@backingInt(filter)});
462     apple.objc.send(void, target, apple.objc.selector("setMagFilter:"), .{@backingInt(filter)});
463     apple.objc.send(void, target, apple.objc.selector("setSAddressMode:"), .{@backingInt(address)});
464     apple.objc.send(void, target, apple.objc.selector("setTAddressMode:"), .{@backingInt(address)});
465     return apple.objc.send(?SamplerState, @ptrCast(device), apple.objc.selector("newSamplerStateWithDescriptor:"), .{descriptor});
466 }
467 
468 /// An empty vertex descriptor, autoreleased.
469 pub fn vertexDescriptor() ?VertexDescriptor {
470     const descriptor_class = apple.objc.class("MTLVertexDescriptor") orelse return null;
471     return apple.objc.send(?VertexDescriptor, descriptor_class, apple.objc.selector("vertexDescriptor"), .{});
472 }
473 
474 pub fn setVertexAttribute(
475     descriptor: VertexDescriptor,
476     index: u32,
477     format: VertexFormat,
478     offset: u32,
479     buffer_index: u32,
480 ) void {
481     const attributes = apple.objc.send(apple.objc.Id, @ptrCast(descriptor), apple.objc.selector("attributes"), .{});
482     const attribute = subscript(attributes, index);
483     apple.objc.send(void, attribute, apple.objc.selector("setFormat:"), .{@backingInt(format)});
484     apple.objc.send(void, attribute, apple.objc.selector("setOffset:"), .{@as(apple.foundation.NSUInteger, offset)});
485     apple.objc.send(void, attribute, apple.objc.selector("setBufferIndex:"), .{@as(apple.foundation.NSUInteger, buffer_index)});
486 }
487 
488 pub fn setVertexLayout(
489     descriptor: VertexDescriptor,
490     buffer_index: u32,
491     stride: u32,
492     step: VertexStepFunction,
493 ) void {
494     const layouts = apple.objc.send(apple.objc.Id, @ptrCast(descriptor), apple.objc.selector("layouts"), .{});
495     const layout = subscript(layouts, buffer_index);
496     apple.objc.send(void, layout, apple.objc.selector("setStride:"), .{@as(apple.foundation.NSUInteger, stride)});
497     apple.objc.send(void, layout, apple.objc.selector("setStepFunction:"), .{@backingInt(step)});
498     apple.objc.send(void, layout, apple.objc.selector("setStepRate:"), .{@as(apple.foundation.NSUInteger, 1)});
499 }
500 
501 /// The description of a render pipeline with one color attachment. The caller releases it.
502 pub const RenderPipelineDesc = struct {
503     vertex: Function,
504     fragment: Function,
505     vertex_descriptor: ?VertexDescriptor,
506     color_format: PixelFormat,
507     blend: ?Blend,
508     depth_format: ?PixelFormat,
509     /// Whether an indirect command buffer may set the pipeline.
510     indirect: bool,
511 };
512 
513 pub fn newRenderPipelineState(
514     device: Device,
515     desc: RenderPipelineDesc,
516     out_error: *?apple.foundation.Error,
517 ) ?RenderPipelineState {
518     const descriptor = newObject(RenderPipelineDescriptor, "MTLRenderPipelineDescriptor") orelse return null;
519     defer apple.objc.release(descriptor);
520     const target: apple.objc.Id = @ptrCast(descriptor);
521     apple.objc.send(void, target, apple.objc.selector("setVertexFunction:"), .{desc.vertex});
522     apple.objc.send(void, target, apple.objc.selector("setFragmentFunction:"), .{desc.fragment});
523     apple.objc.send(void, target, apple.objc.selector("setVertexDescriptor:"), .{desc.vertex_descriptor});
524     if (desc.depth_format) |format| {
525         apple.objc.send(void, target, apple.objc.selector("setDepthAttachmentPixelFormat:"), .{@backingInt(format)});
526     }
527     apple.objc.send(void, target, apple.objc.selector("setSupportIndirectCommandBuffers:"), .{apple.objc.boolean(desc.indirect)});
528     const attachments = apple.objc.send(apple.objc.Id, target, apple.objc.selector("colorAttachments"), .{});
529     const color = subscript(attachments, 0);
530     apple.objc.send(void, color, apple.objc.selector("setPixelFormat:"), .{@backingInt(desc.color_format)});
531     if (desc.blend) |blend| {
532         apple.objc.send(void, color, apple.objc.selector("setBlendingEnabled:"), .{apple.objc.boolean(true)});
533         apple.objc.send(void, color, apple.objc.selector("setSourceRGBBlendFactor:"), .{@backingInt(blend.source_rgb)});
534         apple.objc.send(void, color, apple.objc.selector("setDestinationRGBBlendFactor:"), .{@backingInt(blend.destination_rgb)});
535         apple.objc.send(void, color, apple.objc.selector("setSourceAlphaBlendFactor:"), .{@backingInt(blend.source_alpha)});
536         apple.objc.send(void, color, apple.objc.selector("setDestinationAlphaBlendFactor:"), .{@backingInt(blend.destination_alpha)});
537     }
538     return apple.objc.send(
539         ?RenderPipelineState,
540         @ptrCast(device),
541         apple.objc.selector("newRenderPipelineStateWithDescriptor:error:"),
542         .{ descriptor, out_error },
543     );
544 }
545 
546 /// A depth test without stencil. The caller releases it.
547 pub fn newDepthStencilState(device: Device, compare: CompareFunction, write: bool) ?DepthStencilState {
548     const descriptor = newObject(apple.objc.Id, "MTLDepthStencilDescriptor") orelse return null;
549     defer apple.objc.release(descriptor);
550     apple.objc.send(void, descriptor, apple.objc.selector("setDepthCompareFunction:"), .{@backingInt(compare)});
551     apple.objc.send(void, descriptor, apple.objc.selector("setDepthWriteEnabled:"), .{apple.objc.boolean(write)});
552     return apple.objc.send(?DepthStencilState, @ptrCast(device), apple.objc.selector("newDepthStencilStateWithDescriptor:"), .{descriptor});
553 }
554 
555 /// An empty render pass descriptor, autoreleased.
556 pub fn renderPassDescriptor() ?RenderPassDescriptor {
557     const descriptor_class = apple.objc.class("MTLRenderPassDescriptor") orelse return null;
558     return apple.objc.send(?RenderPassDescriptor, descriptor_class, apple.objc.selector("renderPassDescriptor"), .{});
559 }
560 
561 /// Makes `texture` color attachment 0, stored at the end of the pass.
562 pub fn setColorAttachment(descriptor: RenderPassDescriptor, texture: Texture, load: LoadAction, clear: ClearColor) void {
563     const attachments = apple.objc.send(apple.objc.Id, @ptrCast(descriptor), apple.objc.selector("colorAttachments"), .{});
564     const color = subscript(attachments, 0);
565     setAttachment(color, @ptrCast(texture), load);
566     apple.objc.send(void, color, apple.objc.selector("setClearColor:"), .{clear});
567 }
568 
569 /// Makes `texture` the depth attachment, stored at the end of the pass.
570 pub fn setDepthAttachment(descriptor: RenderPassDescriptor, texture: Texture, load: LoadAction, clear: f64) void {
571     const depth = apple.objc.send(apple.objc.Id, @ptrCast(descriptor), apple.objc.selector("depthAttachment"), .{});
572     setAttachment(depth, @ptrCast(texture), load);
573     apple.objc.send(void, depth, apple.objc.selector("setClearDepth:"), .{clear});
574 }
575 
576 fn setAttachment(attachment: apple.objc.Id, texture: apple.objc.Id, load: LoadAction) void {
577     const store_action_store: apple.foundation.NSUInteger = 1;
578     apple.objc.send(void, attachment, apple.objc.selector("setTexture:"), .{texture});
579     apple.objc.send(void, attachment, apple.objc.selector("setLoadAction:"), .{@backingInt(load)});
580     apple.objc.send(void, attachment, apple.objc.selector("setStoreAction:"), .{store_action_store});
581 }
582 
583 pub fn renderCommandEncoder(command_buffer: CommandBuffer, descriptor: RenderPassDescriptor) ?RenderCommandEncoder {
584     return apple.objc.send(
585         ?RenderCommandEncoder,
586         @ptrCast(command_buffer),
587         apple.objc.selector("renderCommandEncoderWithDescriptor:"),
588         .{descriptor},
589     );
590 }
591 
592 pub fn blitCommandEncoder(command_buffer: CommandBuffer) ?BlitCommandEncoder {
593     return apple.objc.send(?BlitCommandEncoder, @ptrCast(command_buffer), apple.objc.selector("blitCommandEncoder"), .{});
594 }
595 
596 pub fn presentDrawable(command_buffer: CommandBuffer, drawable: Drawable) void {
597     apple.objc.send(void, @ptrCast(command_buffer), apple.objc.selector("presentDrawable:"), .{drawable});
598 }
599 
600 pub fn setRenderPipelineState(encoder: RenderCommandEncoder, pipeline: RenderPipelineState) void {
601     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setRenderPipelineState:"), .{pipeline});
602 }
603 
604 pub fn setDepthStencilState(encoder: RenderCommandEncoder, state: ?DepthStencilState) void {
605     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setDepthStencilState:"), .{state});
606 }
607 
608 pub fn setDepthBias(encoder: RenderCommandEncoder, constant: f32, slope: f32, clamp: f32) void {
609     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setDepthBias:slopeScale:clamp:"), .{ constant, slope, clamp });
610 }
611 
612 pub fn setViewport(encoder: RenderCommandEncoder, viewport: Viewport) void {
613     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setViewport:"), .{viewport});
614 }
615 
616 pub fn setScissorRect(encoder: RenderCommandEncoder, rect: ScissorRect) void {
617     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setScissorRect:"), .{rect});
618 }
619 
620 /// `MTLWinding`: the order, as seen on screen, of a front-facing triangle's vertices.
621 pub const Winding = enum(apple.foundation.NSUInteger) {
622     clockwise = 0,
623     counter_clockwise = 1,
624 };
625 
626 pub fn setFrontFacingWinding(encoder: RenderCommandEncoder, winding: Winding) void {
627     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setFrontFacingWinding:"), .{@backingInt(winding)});
628 }
629 
630 pub fn setVertexBuffer(encoder: RenderCommandEncoder, buffer: Buffer, offset: u64, index: u32) void {
631     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setVertexBuffer:offset:atIndex:"), .{ buffer, offset, @as(apple.foundation.NSUInteger, index) });
632 }
633 
634 pub fn setFragmentBuffer(encoder: RenderCommandEncoder, buffer: Buffer, offset: u64, index: u32) void {
635     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setFragmentBuffer:offset:atIndex:"), .{ buffer, offset, @as(apple.foundation.NSUInteger, index) });
636 }
637 
638 pub fn setVertexBytes(encoder: RenderCommandEncoder, bytes: []const u8, index: u32) void {
639     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setVertexBytes:length:atIndex:"), .{ @as(*const anyopaque, bytes.ptr), @as(apple.foundation.NSUInteger, bytes.len), @as(apple.foundation.NSUInteger, index) });
640 }
641 
642 pub fn setFragmentBytes(encoder: RenderCommandEncoder, bytes: []const u8, index: u32) void {
643     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setFragmentBytes:length:atIndex:"), .{ @as(*const anyopaque, bytes.ptr), @as(apple.foundation.NSUInteger, bytes.len), @as(apple.foundation.NSUInteger, index) });
644 }
645 
646 pub fn setVertexTexture(encoder: RenderCommandEncoder, texture: Texture, sampler: SamplerState, index: u32) void {
647     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setVertexTexture:atIndex:"), .{ texture, @as(apple.foundation.NSUInteger, index) });
648     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setVertexSamplerState:atIndex:"), .{ sampler, @as(apple.foundation.NSUInteger, index) });
649 }
650 
651 pub fn setFragmentTexture(encoder: RenderCommandEncoder, texture: Texture, sampler: SamplerState, index: u32) void {
652     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setFragmentTexture:atIndex:"), .{ texture, @as(apple.foundation.NSUInteger, index) });
653     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("setFragmentSamplerState:atIndex:"), .{ sampler, @as(apple.foundation.NSUInteger, index) });
654 }
655 
656 pub fn drawPrimitives(
657     encoder: RenderCommandEncoder,
658     primitive: PrimitiveType,
659     first_vertex: u32,
660     vertex_count: u32,
661     instance_count: u32,
662     first_instance: u32,
663 ) void {
664     apple.objc.send(
665         void,
666         @ptrCast(encoder),
667         apple.objc.selector("drawPrimitives:vertexStart:vertexCount:instanceCount:baseInstance:"),
668         drawArguments(primitive, first_vertex, vertex_count, instance_count, first_instance),
669     );
670 }
671 
672 pub fn drawIndexedPrimitives(
673     encoder: RenderCommandEncoder,
674     primitive: PrimitiveType,
675     index_count: u32,
676     index_type: IndexType,
677     index_buffer: Buffer,
678     index_offset: u64,
679     instance_count: u32,
680     base_vertex: i32,
681     first_instance: u32,
682 ) void {
683     apple.objc.send(
684         void,
685         @ptrCast(encoder),
686         apple.objc.selector("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:baseVertex:baseInstance:"),
687         drawIndexedArguments(primitive, index_count, index_type, index_buffer, index_offset, instance_count, base_vertex, first_instance),
688     );
689 }
690 
691 /// Makes `buffers` resident for the commands of an indirect command buffer that read them.
692 pub fn useResources(encoder: RenderCommandEncoder, buffers: []const Buffer) void {
693     if (buffers.len == 0) return;
694     apple.objc.send(
695         void,
696         @ptrCast(encoder),
697         apple.objc.selector("useResources:count:usage:stages:"),
698         .{ @as(*const anyopaque, @ptrCast(buffers.ptr)), @as(apple.foundation.NSUInteger, buffers.len), resource_usage_read, render_stage_vertex | render_stage_fragment },
699     );
700 }
701 
702 pub fn executeCommands(encoder: RenderCommandEncoder, commands: IndirectCommandBuffer, range: Range) void {
703     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("executeCommandsInBuffer:withRange:"), .{ commands, range });
704 }
705 
706 pub fn endRenderEncoding(encoder: RenderCommandEncoder) void {
707     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("endEncoding"), .{});
708 }
709 
710 pub fn endBlitEncoding(encoder: BlitCommandEncoder) void {
711     apple.objc.send(void, @ptrCast(encoder), apple.objc.selector("endEncoding"), .{});
712 }
713 
714 /// Copies rows of `bytes_per_row` from `buffer` into the whole of mip 0 of `texture`.
715 pub fn copyBufferToTexture(
716     encoder: BlitCommandEncoder,
717     buffer: Buffer,
718     bytes_per_row: u64,
719     texture: Texture,
720     size: Size,
721 ) void {
722     apple.objc.send(
723         void,
724         @ptrCast(encoder),
725         apple.objc.selector("copyFromBuffer:sourceOffset:sourceBytesPerRow:sourceBytesPerImage:sourceSize:toTexture:destinationSlice:destinationLevel:destinationOrigin:"),
726         .{ buffer, @as(apple.foundation.NSUInteger, 0), bytes_per_row, bytes_per_row * size.height, size, texture, @as(apple.foundation.NSUInteger, 0), @as(apple.foundation.NSUInteger, 0), Origin{} },
727     );
728 }
729 
730 /// Copies the whole of mip 0 of `texture` into `buffer`, rows of `bytes_per_row` from the top.
731 pub fn copyTextureToBuffer(
732     encoder: BlitCommandEncoder,
733     texture: Texture,
734     size: Size,
735     buffer: Buffer,
736     bytes_per_row: u64,
737 ) void {
738     apple.objc.send(
739         void,
740         @ptrCast(encoder),
741         apple.objc.selector("copyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toBuffer:destinationOffset:destinationBytesPerRow:destinationBytesPerImage:"),
742         .{ texture, @as(apple.foundation.NSUInteger, 0), @as(apple.foundation.NSUInteger, 0), Origin{}, size, buffer, @as(apple.foundation.NSUInteger, 0), bytes_per_row, bytes_per_row * size.height },
743     );
744 }
745 
746 /// An indirect command buffer of up to `max_commands` draws, each of which sets its own pipeline
747 /// and buffers and inherits every other state from the encoder that executes it. The caller
748 /// releases it.
749 pub fn newIndirectCommandBuffer(device: Device, max_commands: u32) ?IndirectCommandBuffer {
750     const descriptor = newObject(IndirectCommandBufferDescriptor, "MTLIndirectCommandBufferDescriptor") orelse return null;
751     defer apple.objc.release(descriptor);
752     const target: apple.objc.Id = @ptrCast(descriptor);
753     const bind_count: apple.foundation.NSUInteger = max_indirect_buffer_bindings;
754     apple.objc.send(void, target, apple.objc.selector("setCommandTypes:"), .{indirect_command_draw | indirect_command_draw_indexed});
755     apple.objc.send(void, target, apple.objc.selector("setInheritPipelineState:"), .{apple.objc.boolean(false)});
756     apple.objc.send(void, target, apple.objc.selector("setInheritBuffers:"), .{apple.objc.boolean(false)});
757     apple.objc.send(void, target, apple.objc.selector("setMaxVertexBufferBindCount:"), .{bind_count});
758     apple.objc.send(void, target, apple.objc.selector("setMaxFragmentBufferBindCount:"), .{bind_count});
759     return apple.objc.send(
760         ?IndirectCommandBuffer,
761         @ptrCast(device),
762         apple.objc.selector("newIndirectCommandBufferWithDescriptor:maxCommandCount:options:"),
763         .{ descriptor, @as(apple.foundation.NSUInteger, max_commands), resource_storage_mode_shared },
764     );
765 }
766 
767 /// Buffer indices an indirect command may bind, in each stage: all 31 of them.
768 pub const max_indirect_buffer_bindings = 31;
769 
770 pub fn indirectRenderCommand(commands: IndirectCommandBuffer, index: u32) IndirectRenderCommand {
771     return apple.objc.send(IndirectRenderCommand, @ptrCast(commands), apple.objc.selector("indirectRenderCommandAtIndex:"), .{@as(apple.foundation.NSUInteger, index)});
772 }
773 
774 /// Whether an indirect render command can set its own depth-stencil state and depth bias, as
775 /// macOS 14 lets it, rather than inheriting both from the encoder.
776 pub fn indirectCommandsCarryDepthState(command: IndirectRenderCommand) bool {
777     return apple.objc.respondsTo(@ptrCast(command), apple.objc.selector("setDepthStencilState:")) and
778         apple.objc.respondsTo(@ptrCast(command), apple.objc.selector("setDepthBias:slopeScale:clamp:"));
779 }
780 
781 pub fn setIndirectPipelineState(command: IndirectRenderCommand, pipeline: RenderPipelineState) void {
782     apple.objc.send(void, @ptrCast(command), apple.objc.selector("setRenderPipelineState:"), .{pipeline});
783 }
784 
785 pub fn setIndirectVertexBuffer(command: IndirectRenderCommand, buffer: Buffer, offset: u64, index: u32) void {
786     apple.objc.send(void, @ptrCast(command), apple.objc.selector("setVertexBuffer:offset:atIndex:"), .{ buffer, offset, @as(apple.foundation.NSUInteger, index) });
787 }
788 
789 pub fn setIndirectFragmentBuffer(command: IndirectRenderCommand, buffer: Buffer, offset: u64, index: u32) void {
790     apple.objc.send(void, @ptrCast(command), apple.objc.selector("setFragmentBuffer:offset:atIndex:"), .{ buffer, offset, @as(apple.foundation.NSUInteger, index) });
791 }
792 
793 pub fn indirectDrawPrimitives(
794     command: IndirectRenderCommand,
795     primitive: PrimitiveType,
796     first_vertex: u32,
797     vertex_count: u32,
798     instance_count: u32,
799     first_instance: u32,
800 ) void {
801     apple.objc.send(
802         void,
803         @ptrCast(command),
804         apple.objc.selector("drawPrimitives:vertexStart:vertexCount:instanceCount:baseInstance:"),
805         drawArguments(primitive, first_vertex, vertex_count, instance_count, first_instance),
806     );
807 }
808 
809 pub fn indirectDrawIndexedPrimitives(
810     command: IndirectRenderCommand,
811     primitive: PrimitiveType,
812     index_count: u32,
813     index_type: IndexType,
814     index_buffer: Buffer,
815     index_offset: u64,
816     instance_count: u32,
817     base_vertex: i32,
818     first_instance: u32,
819 ) void {
820     apple.objc.send(
821         void,
822         @ptrCast(command),
823         apple.objc.selector("drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount:baseVertex:baseInstance:"),
824         drawIndexedArguments(primitive, index_count, index_type, index_buffer, index_offset, instance_count, base_vertex, first_instance),
825     );
826 }
827 
828 fn drawArguments(
829     primitive: PrimitiveType,
830     first_vertex: u32,
831     vertex_count: u32,
832     instance_count: u32,
833     first_instance: u32,
834 ) struct { apple.foundation.NSUInteger, apple.foundation.NSUInteger, apple.foundation.NSUInteger, apple.foundation.NSUInteger, apple.foundation.NSUInteger } {
835     return .{ @backingInt(primitive), first_vertex, vertex_count, instance_count, first_instance };
836 }
837 
838 fn drawIndexedArguments(
839     primitive: PrimitiveType,
840     index_count: u32,
841     index_type: IndexType,
842     index_buffer: Buffer,
843     index_offset: u64,
844     instance_count: u32,
845     base_vertex: i32,
846     first_instance: u32,
847 ) struct { apple.foundation.NSUInteger, apple.foundation.NSUInteger, apple.foundation.NSUInteger, Buffer, apple.foundation.NSUInteger, apple.foundation.NSUInteger, apple.foundation.NSInteger, apple.foundation.NSUInteger } {
848     return .{ @backingInt(primitive), index_count, @backingInt(index_type), index_buffer, index_offset, instance_count, base_vertex, first_instance };
849 }
850 
851 /// A `CAMetalLayer` that draws with `device` into drawables of `format`, readable by blits since
852 /// it is not framebuffer-only. The caller releases it.
853 pub fn newLayer(device: Device, format: PixelFormat, max_drawables: u32, display_sync: bool) ?Layer {
854     const layer = newObject(Layer, "CAMetalLayer") orelse return null;
855     const target: apple.objc.Id = @ptrCast(layer);
856     apple.objc.send(void, target, apple.objc.selector("setDevice:"), .{device});
857     apple.objc.send(void, target, apple.objc.selector("setPixelFormat:"), .{@backingInt(format)});
858     apple.objc.send(void, target, apple.objc.selector("setFramebufferOnly:"), .{apple.objc.boolean(false)});
859     apple.objc.send(void, target, apple.objc.selector("setMaximumDrawableCount:"), .{@as(apple.foundation.NSUInteger, max_drawables)});
860     apple.objc.send(void, target, apple.objc.selector("setDisplaySyncEnabled:"), .{apple.objc.boolean(display_sync)});
861     return layer;
862 }
863 
864 /// Places `layer` over the whole of `parent`, with drawables of `width` by `height` pixels.
865 pub fn attachLayer(layer: Layer, parent: apple.objc.Id, width: u32, height: u32) void {
866     const target: apple.objc.Id = @ptrCast(layer);
867     const bounds = apple.objc.send(apple.cocoa.NSRect, parent, apple.objc.selector("bounds"), .{});
868     apple.objc.send(void, target, apple.objc.selector("setFrame:"), .{bounds});
869     const scale: apple.coregraphics.CGFloat = if (bounds.size.width > 0)
870         @as(apple.coregraphics.CGFloat, @floatFromInt(width)) / bounds.size.width
871     else
872         1;
873     apple.objc.send(void, target, apple.objc.selector("setContentsScale:"), .{scale});
874     const drawable_size = apple.cocoa.NSSize{ .width = @floatFromInt(width), .height = @floatFromInt(height) };
875     apple.objc.send(void, target, apple.objc.selector("setDrawableSize:"), .{drawable_size});
876     apple.objc.send(void, parent, apple.objc.selector("addSublayer:"), .{target});
877 }
878 
879 pub fn detachLayer(layer: Layer) void {
880     apple.objc.send(void, @ptrCast(layer), apple.objc.selector("removeFromSuperlayer"), .{});
881 }
882 
883 /// The layer's next drawable, autoreleased, or null when none frees up within the layer's
884 /// one-second timeout.
885 pub fn nextDrawable(layer: Layer) ?Drawable {
886     return apple.objc.send(?Drawable, @ptrCast(layer), apple.objc.selector("nextDrawable"), .{});
887 }
888 
889 pub fn drawableTexture(drawable: Drawable) ?Texture {
890     return apple.objc.send(?Texture, @ptrCast(drawable), apple.objc.selector("texture"), .{});
891 }
892 
893 fn newObject(comptime T: type, class_name: [*:0]const u8) ?T {
894     const object_class = apple.objc.class(class_name) orelse return null;
895     const allocated = apple.objc.send(?apple.objc.Id, object_class, apple.objc.selector("alloc"), .{}) orelse return null;
896     return apple.objc.send(?T, allocated, apple.objc.selector("init"), .{});
897 }
898 
899 fn subscript(array: apple.objc.Id, index: u32) apple.objc.Id {
900     return apple.objc.send(apple.objc.Id, array, apple.objc.selector("objectAtIndexedSubscript:"), .{@as(apple.foundation.NSUInteger, index)});
901 }
902 
903 test "Metal ABI uses nominal objects and exact 64-bit aggregate layout" {
904     try std.testing.expectEqual(@sizeOf(usize), @sizeOf(Device));
905     try std.testing.expectEqual(@sizeOf(usize), @sizeOf(CommandBuffer));
906     try std.testing.expectEqual(@as(usize, 24), @sizeOf(Size));
907     try std.testing.expectEqual(@as(usize, 8), @alignOf(Size));
908     try std.testing.expectEqual(@as(usize, 0), @offsetOf(Size, "width"));
909     try std.testing.expectEqual(@as(usize, 8), @offsetOf(Size, "height"));
910     try std.testing.expectEqual(@as(usize, 16), @offsetOf(Size, "depth"));
911     try std.testing.expectEqual(@as(usize, 48), @sizeOf(Viewport));
912     try std.testing.expectEqual(@as(usize, 32), @sizeOf(ScissorRect));
913     try std.testing.expectEqual(@as(usize, 32), @sizeOf(ClearColor));
914     try std.testing.expectEqual(@as(usize, 24), @sizeOf(Origin));
915     try std.testing.expectEqual(@as(usize, 16), @sizeOf(Range));
916 }