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 }