Skip to main content

apple_metal/
pipeline.rs

1use crate::{
2    ffi, util::take_optional_string, ComputePipelineState, MetalDevice, MetalFunction,
3    RenderPipelineState,
4};
5
6/// `MTLBlendFactor` enum values.
7pub mod blend_factor {
8    /// Mirrors the `Metal` framework constant `ZERO`.
9    pub const ZERO: usize = 0;
10    /// Mirrors the `Metal` framework constant `ONE`.
11    pub const ONE: usize = 1;
12    /// Mirrors the `Metal` framework constant `SOURCE_COLOR`.
13    pub const SOURCE_COLOR: usize = 2;
14    /// Mirrors the `Metal` framework constant `ONE_MINUS_SOURCE_COLOR`.
15    pub const ONE_MINUS_SOURCE_COLOR: usize = 3;
16    /// Mirrors the `Metal` framework constant `SOURCE_ALPHA`.
17    pub const SOURCE_ALPHA: usize = 4;
18    /// Mirrors the `Metal` framework constant `ONE_MINUS_SOURCE_ALPHA`.
19    pub const ONE_MINUS_SOURCE_ALPHA: usize = 5;
20    /// Mirrors the `Metal` framework constant `DESTINATION_COLOR`.
21    pub const DESTINATION_COLOR: usize = 6;
22    /// Mirrors the `Metal` framework constant `ONE_MINUS_DESTINATION_COLOR`.
23    pub const ONE_MINUS_DESTINATION_COLOR: usize = 7;
24    /// Mirrors the `Metal` framework constant `DESTINATION_ALPHA`.
25    pub const DESTINATION_ALPHA: usize = 8;
26    /// Mirrors the `Metal` framework constant `ONE_MINUS_DESTINATION_ALPHA`.
27    pub const ONE_MINUS_DESTINATION_ALPHA: usize = 9;
28    /// Mirrors the `Metal` framework constant `SOURCE_ALPHA_SATURATED`.
29    pub const SOURCE_ALPHA_SATURATED: usize = 10;
30    /// Mirrors the `Metal` framework constant `BLEND_COLOR`.
31    pub const BLEND_COLOR: usize = 11;
32    /// Mirrors the `Metal` framework constant `ONE_MINUS_BLEND_COLOR`.
33    pub const ONE_MINUS_BLEND_COLOR: usize = 12;
34    /// Mirrors the `Metal` framework constant `BLEND_ALPHA`.
35    pub const BLEND_ALPHA: usize = 13;
36    /// Mirrors the `Metal` framework constant `ONE_MINUS_BLEND_ALPHA`.
37    pub const ONE_MINUS_BLEND_ALPHA: usize = 14;
38    /// Mirrors the `Metal` framework constant `SOURCE1_COLOR`.
39    pub const SOURCE1_COLOR: usize = 15;
40    /// Mirrors the `Metal` framework constant `ONE_MINUS_SOURCE1_COLOR`.
41    pub const ONE_MINUS_SOURCE1_COLOR: usize = 16;
42    /// Mirrors the `Metal` framework constant `SOURCE1_ALPHA`.
43    pub const SOURCE1_ALPHA: usize = 17;
44    /// Mirrors the `Metal` framework constant `ONE_MINUS_SOURCE1_ALPHA`.
45    pub const ONE_MINUS_SOURCE1_ALPHA: usize = 18;
46    /// Mirrors the `Metal` framework constant `UNSPECIALIZED`.
47    pub const UNSPECIALIZED: usize = 19;
48}
49
50/// `MTLBlendOperation` enum values.
51pub mod blend_operation {
52    /// Mirrors the `Metal` framework constant `ADD`.
53    pub const ADD: usize = 0;
54    /// Mirrors the `Metal` framework constant `SUBTRACT`.
55    pub const SUBTRACT: usize = 1;
56    /// Mirrors the `Metal` framework constant `REVERSE_SUBTRACT`.
57    pub const REVERSE_SUBTRACT: usize = 2;
58    /// Mirrors the `Metal` framework constant `MIN`.
59    pub const MIN: usize = 3;
60    /// Mirrors the `Metal` framework constant `MAX`.
61    pub const MAX: usize = 4;
62    /// Mirrors the `Metal` framework constant `UNSPECIALIZED`.
63    pub const UNSPECIALIZED: usize = 5;
64}
65
66/// `MTLColorWriteMask` bitmask values.
67pub mod color_write_mask {
68    /// Mirrors the `Metal` framework constant `NONE`.
69    pub const NONE: usize = 0;
70    /// Mirrors the `Metal` framework constant `RED`.
71    pub const RED: usize = 0x1 << 3;
72    /// Mirrors the `Metal` framework constant `GREEN`.
73    pub const GREEN: usize = 0x1 << 2;
74    /// Mirrors the `Metal` framework constant `BLUE`.
75    pub const BLUE: usize = 0x1 << 1;
76    /// Mirrors the `Metal` framework constant `ALPHA`.
77    pub const ALPHA: usize = 0x1 << 0;
78    /// Mirrors the `Metal` framework constant `ALL`.
79    pub const ALL: usize = 0xf;
80    /// Mirrors the `Metal` framework constant `UNSPECIALIZED`.
81    pub const UNSPECIALIZED: usize = 0x10;
82}
83
84/// Safe Rust description of `MTLComputePipelineDescriptor`.
85#[derive(Clone, Copy)]
86pub struct ComputePipelineDescriptor<'a> {
87    /// Mirrors the `Metal` framework property for `label`.
88    pub label: Option<&'a str>,
89    /// Mirrors the `Metal` framework property for `compute_function`.
90    pub compute_function: &'a MetalFunction,
91    /// Mirrors the `Metal` framework property for `thread_group_size_is_multiple_of_thread_execution_width`.
92    pub thread_group_size_is_multiple_of_thread_execution_width: bool,
93    /// Mirrors the `Metal` framework property for `max_total_threads_per_threadgroup`.
94    pub max_total_threads_per_threadgroup: usize,
95    /// Mirrors the `Metal` framework property for `support_indirect_command_buffers`.
96    pub support_indirect_command_buffers: bool,
97}
98
99impl<'a> ComputePipelineDescriptor<'a> {
100    /// Create a descriptor for a single compute function with default tuning flags.
101    #[must_use]
102    pub const fn new(compute_function: &'a MetalFunction) -> Self {
103        Self {
104            label: None,
105            compute_function,
106            thread_group_size_is_multiple_of_thread_execution_width: false,
107            max_total_threads_per_threadgroup: 0,
108            support_indirect_command_buffers: false,
109        }
110    }
111}
112
113/// Safe Rust description of `MTLRenderPipelineColorAttachmentDescriptor`.
114#[derive(Debug, Clone, Copy)]
115pub struct RenderPipelineColorAttachmentDescriptor {
116    /// Mirrors the `Metal` framework property for `pixel_format`.
117    pub pixel_format: usize,
118    /// Mirrors the `Metal` framework property for `blending_enabled`.
119    pub blending_enabled: bool,
120    /// Mirrors the `Metal` framework property for `source_rgb_blend_factor`.
121    pub source_rgb_blend_factor: usize,
122    /// Mirrors the `Metal` framework property for `destination_rgb_blend_factor`.
123    pub destination_rgb_blend_factor: usize,
124    /// Mirrors the `Metal` framework property for `rgb_blend_operation`.
125    pub rgb_blend_operation: usize,
126    /// Mirrors the `Metal` framework property for `source_alpha_blend_factor`.
127    pub source_alpha_blend_factor: usize,
128    /// Mirrors the `Metal` framework property for `destination_alpha_blend_factor`.
129    pub destination_alpha_blend_factor: usize,
130    /// Mirrors the `Metal` framework property for `alpha_blend_operation`.
131    pub alpha_blend_operation: usize,
132    /// Mirrors the `Metal` framework property for `write_mask`.
133    pub write_mask: usize,
134}
135
136impl Default for RenderPipelineColorAttachmentDescriptor {
137    fn default() -> Self {
138        Self {
139            pixel_format: 0,
140            blending_enabled: false,
141            source_rgb_blend_factor: blend_factor::ONE,
142            destination_rgb_blend_factor: blend_factor::ZERO,
143            rgb_blend_operation: blend_operation::ADD,
144            source_alpha_blend_factor: blend_factor::ONE,
145            destination_alpha_blend_factor: blend_factor::ZERO,
146            alpha_blend_operation: blend_operation::ADD,
147            write_mask: color_write_mask::ALL,
148        }
149    }
150}
151
152impl RenderPipelineColorAttachmentDescriptor {
153    /// Create a color attachment descriptor targeting the given pixel format.
154    #[must_use]
155    pub const fn new(pixel_format: usize) -> Self {
156        Self {
157            pixel_format,
158            blending_enabled: false,
159            source_rgb_blend_factor: blend_factor::ONE,
160            destination_rgb_blend_factor: blend_factor::ZERO,
161            rgb_blend_operation: blend_operation::ADD,
162            source_alpha_blend_factor: blend_factor::ONE,
163            destination_alpha_blend_factor: blend_factor::ZERO,
164            alpha_blend_operation: blend_operation::ADD,
165            write_mask: color_write_mask::ALL,
166        }
167    }
168
169    const fn as_words(self) -> [usize; 9] {
170        [
171            self.pixel_format,
172            self.blending_enabled as usize,
173            self.source_rgb_blend_factor,
174            self.destination_rgb_blend_factor,
175            self.rgb_blend_operation,
176            self.source_alpha_blend_factor,
177            self.destination_alpha_blend_factor,
178            self.alpha_blend_operation,
179            self.write_mask,
180        ]
181    }
182}
183
184/// Safe Rust description of `MTLRenderPipelineDescriptor`.
185#[allow(clippy::struct_excessive_bools)]
186#[derive(Clone, Copy)]
187pub struct RenderPipelineDescriptor<'a> {
188    /// Mirrors the `Metal` framework property for `label`.
189    pub label: Option<&'a str>,
190    /// Mirrors the `Metal` framework property for `vertex_function`.
191    pub vertex_function: &'a MetalFunction,
192    /// Mirrors the `Metal` framework property for `fragment_function`.
193    pub fragment_function: Option<&'a MetalFunction>,
194    /// Mirrors the `Metal` framework property for `color_attachments`.
195    pub color_attachments: &'a [RenderPipelineColorAttachmentDescriptor],
196    /// Mirrors the `Metal` framework property for `raster_sample_count`.
197    pub raster_sample_count: usize,
198    /// Mirrors the `Metal` framework property for `alpha_to_coverage_enabled`.
199    pub alpha_to_coverage_enabled: bool,
200    /// Mirrors the `Metal` framework property for `alpha_to_one_enabled`.
201    pub alpha_to_one_enabled: bool,
202    /// Mirrors the `Metal` framework property for `rasterization_enabled`.
203    pub rasterization_enabled: bool,
204    /// Mirrors the `Metal` framework property for `support_indirect_command_buffers`.
205    pub support_indirect_command_buffers: bool,
206    /// Mirrors the `Metal` framework property for `depth_attachment_pixel_format`.
207    pub depth_attachment_pixel_format: usize,
208    /// Mirrors the `Metal` framework property for `stencil_attachment_pixel_format`.
209    pub stencil_attachment_pixel_format: usize,
210}
211
212impl<'a> RenderPipelineDescriptor<'a> {
213    /// Create a descriptor for a simple render pipeline with the given attachment list.
214    #[must_use]
215    pub const fn new(
216        vertex_function: &'a MetalFunction,
217        fragment_function: Option<&'a MetalFunction>,
218        color_attachments: &'a [RenderPipelineColorAttachmentDescriptor],
219    ) -> Self {
220        Self {
221            label: None,
222            vertex_function,
223            fragment_function,
224            color_attachments,
225            raster_sample_count: 1,
226            alpha_to_coverage_enabled: false,
227            alpha_to_one_enabled: false,
228            rasterization_enabled: true,
229            support_indirect_command_buffers: false,
230            depth_attachment_pixel_format: 0,
231            stencil_attachment_pixel_format: 0,
232        }
233    }
234}
235
236/// Safe Rust description of `MTLTileRenderPipelineColorAttachmentDescriptor`.
237#[derive(Debug, Clone, Copy)]
238pub struct TileRenderPipelineColorAttachmentDescriptor {
239    /// Mirrors the `Metal` framework property for `pixel_format`.
240    pub pixel_format: usize,
241}
242
243impl TileRenderPipelineColorAttachmentDescriptor {
244    /// Create a tile color attachment descriptor for the given pixel format.
245    #[must_use]
246    pub const fn new(pixel_format: usize) -> Self {
247        Self { pixel_format }
248    }
249}
250
251/// Safe Rust description of `MTLTileRenderPipelineDescriptor`.
252#[derive(Clone, Copy)]
253pub struct TileRenderPipelineDescriptor<'a> {
254    /// Mirrors the `Metal` framework property for `label`.
255    pub label: Option<&'a str>,
256    /// Mirrors the `Metal` framework property for `tile_function`.
257    pub tile_function: &'a MetalFunction,
258    /// Mirrors the `Metal` framework property for `color_attachments`.
259    pub color_attachments: &'a [TileRenderPipelineColorAttachmentDescriptor],
260    /// Mirrors the `Metal` framework property for `raster_sample_count`.
261    pub raster_sample_count: usize,
262    /// Mirrors the `Metal` framework property for `threadgroup_size_matches_tile_size`.
263    pub threadgroup_size_matches_tile_size: bool,
264    /// Mirrors the `Metal` framework property for `max_total_threads_per_threadgroup`.
265    pub max_total_threads_per_threadgroup: usize,
266}
267
268impl<'a> TileRenderPipelineDescriptor<'a> {
269    /// Create a tile-pipeline descriptor with the given tile function and attachments.
270    #[must_use]
271    pub const fn new(
272        tile_function: &'a MetalFunction,
273        color_attachments: &'a [TileRenderPipelineColorAttachmentDescriptor],
274    ) -> Self {
275        Self {
276            label: None,
277            tile_function,
278            color_attachments,
279            raster_sample_count: 1,
280            threadgroup_size_matches_tile_size: false,
281            max_total_threads_per_threadgroup: 0,
282        }
283    }
284}
285
286fn flatten_render_color_attachments(
287    color_attachments: &[RenderPipelineColorAttachmentDescriptor],
288) -> Result<Vec<usize>, String> {
289    if color_attachments.len() > 8 {
290        return Err("Metal render pipelines support at most 8 color attachments".to_string());
291    }
292
293    let mut flat = Vec::with_capacity(color_attachments.len().saturating_mul(9));
294    for attachment in color_attachments {
295        flat.extend_from_slice(&attachment.as_words());
296    }
297    Ok(flat)
298}
299
300fn flatten_tile_color_attachments(
301    color_attachments: &[TileRenderPipelineColorAttachmentDescriptor],
302) -> Result<Vec<usize>, String> {
303    if color_attachments.len() > 8 {
304        return Err("Metal tile pipelines support at most 8 color attachments".to_string());
305    }
306
307    let mut flat = Vec::with_capacity(color_attachments.len());
308    for attachment in color_attachments {
309        flat.push(attachment.pixel_format);
310    }
311    Ok(flat)
312}
313
314impl MetalDevice {
315    /// Compile a compute pipeline from a public `MTLComputePipelineDescriptor` wrapper.
316    ///
317    /// # Errors
318    ///
319    /// Returns Metal's localized pipeline compiler error on failure.
320    pub fn new_compute_pipeline_state_with_descriptor(
321        &self,
322        descriptor: &ComputePipelineDescriptor<'_>,
323    ) -> Result<ComputePipelineState, String> {
324        let label = descriptor
325            .label
326            .and_then(|value| std::ffi::CString::new(value).ok());
327        let label_ptr = label
328            .as_deref()
329            .map_or(core::ptr::null(), core::ffi::CStr::as_ptr);
330        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
331        let ptr = unsafe {
332            ffi::am_device_new_compute_pipeline_state_with_descriptor(
333                self.as_ptr(),
334                descriptor.compute_function.as_ptr(),
335                label_ptr,
336                descriptor.thread_group_size_is_multiple_of_thread_execution_width,
337                descriptor.max_total_threads_per_threadgroup,
338                descriptor.support_indirect_command_buffers,
339                &mut err,
340            )
341        };
342        if ptr.is_null() {
343            Err(unsafe {
344                take_optional_string(err).unwrap_or_else(|| {
345                    "MTLDevice.makeComputePipelineState(descriptor:) returned nil".to_string()
346                })
347            })
348        } else {
349            Ok(unsafe { ComputePipelineState::from_retained_ptr(ptr) })
350        }
351    }
352
353    /// Compile a render pipeline from a public `MTLRenderPipelineDescriptor` wrapper.
354    ///
355    /// # Errors
356    ///
357    /// Returns Metal's localized pipeline compiler error on failure.
358    pub fn new_render_pipeline_state_with_descriptor(
359        &self,
360        descriptor: &RenderPipelineDescriptor<'_>,
361    ) -> Result<RenderPipelineState, String> {
362        let color_attachments = flatten_render_color_attachments(descriptor.color_attachments)?;
363        let label = descriptor
364            .label
365            .and_then(|value| std::ffi::CString::new(value).ok());
366        let label_ptr = label
367            .as_deref()
368            .map_or(core::ptr::null(), core::ffi::CStr::as_ptr);
369        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
370        let ptr = unsafe {
371            ffi::am_device_new_render_pipeline_state_with_descriptor(
372                self.as_ptr(),
373                descriptor.vertex_function.as_ptr(),
374                descriptor
375                    .fragment_function
376                    .map_or(core::ptr::null_mut(), MetalFunction::as_ptr),
377                label_ptr,
378                descriptor.raster_sample_count,
379                descriptor.alpha_to_coverage_enabled,
380                descriptor.alpha_to_one_enabled,
381                descriptor.rasterization_enabled,
382                descriptor.support_indirect_command_buffers,
383                descriptor.depth_attachment_pixel_format,
384                descriptor.stencil_attachment_pixel_format,
385                color_attachments.as_ptr(),
386                descriptor.color_attachments.len(),
387                &mut err,
388            )
389        };
390        if ptr.is_null() {
391            Err(unsafe {
392                take_optional_string(err).unwrap_or_else(|| {
393                    "MTLDevice.makeRenderPipelineState(descriptor:) returned nil".to_string()
394                })
395            })
396        } else {
397            Ok(unsafe { RenderPipelineState::from_retained_ptr(ptr) })
398        }
399    }
400
401    /// Compile a tile render pipeline from a public `MTLTileRenderPipelineDescriptor` wrapper.
402    ///
403    /// # Errors
404    ///
405    /// Returns Metal's localized pipeline compiler error on failure.
406    pub fn new_tile_render_pipeline_state(
407        &self,
408        descriptor: &TileRenderPipelineDescriptor<'_>,
409    ) -> Result<RenderPipelineState, String> {
410        let color_attachments = flatten_tile_color_attachments(descriptor.color_attachments)?;
411        let label = descriptor
412            .label
413            .and_then(|value| std::ffi::CString::new(value).ok());
414        let label_ptr = label
415            .as_deref()
416            .map_or(core::ptr::null(), core::ffi::CStr::as_ptr);
417        let mut err: *mut core::ffi::c_char = core::ptr::null_mut();
418        let ptr = unsafe {
419            ffi::am_device_new_tile_render_pipeline_state(
420                self.as_ptr(),
421                descriptor.tile_function.as_ptr(),
422                label_ptr,
423                descriptor.raster_sample_count,
424                descriptor.threadgroup_size_matches_tile_size,
425                descriptor.max_total_threads_per_threadgroup,
426                color_attachments.as_ptr(),
427                descriptor.color_attachments.len(),
428                &mut err,
429            )
430        };
431        if ptr.is_null() {
432            Err(unsafe {
433                take_optional_string(err).unwrap_or_else(|| {
434                    "MTLDevice.makeRenderPipelineState(tileDescriptor:) returned nil".to_string()
435                })
436            })
437        } else {
438            Ok(unsafe { RenderPipelineState::from_retained_ptr(ptr) })
439        }
440    }
441}