Skip to main content

wgpu_hal/metal/
command.rs

1use objc2::{
2    rc::{autoreleasepool, Retained},
3    runtime::ProtocolObject,
4};
5use objc2_foundation::{NSRange, NSString, NSUInteger};
6use objc2_metal::{
7    MTLAccelerationStructure, MTLAccelerationStructureCommandEncoder, MTLBlitCommandEncoder,
8    MTLBlitPassDescriptor, MTLBuffer, MTLCommandBuffer, MTLCommandBufferStatus, MTLCommandEncoder,
9    MTLCommandQueue, MTLComputeCommandEncoder, MTLComputePassDescriptor, MTLCounterDontSample,
10    MTLDevice, MTLLoadAction, MTLPrimitiveType, MTLRenderCommandEncoder, MTLRenderPassDescriptor,
11    MTLResidencySet, MTLResidencySetDescriptor, MTLSamplerState, MTLScissorRect, MTLSize,
12    MTLStoreAction, MTLTexture, MTLVertexAmplificationViewMapping, MTLViewport,
13    MTLVisibilityResultMode,
14};
15
16use super::{adapter, conv, TimestampQuerySupport};
17use crate::CommandEncoder as _;
18use alloc::{
19    borrow::{Cow, ToOwned as _},
20    sync::Arc,
21    vec::Vec,
22};
23use core::{ops::Range, ptr::NonNull, sync::atomic};
24use smallvec::SmallVec;
25
26// has to match `Temp::binding_sizes`
27const WORD_SIZE: usize = 4;
28
29impl Default for super::CommandState {
30    fn default() -> Self {
31        Self {
32            blit: None,
33            acceleration_structure_builder: None,
34            render: None,
35            compute: None,
36            raw_primitive_type: MTLPrimitiveType::Point,
37            index: None,
38            stage_infos: Default::default(),
39            storage_buffer_length_map: Default::default(),
40            vertex_buffer_size_map: Default::default(),
41            immediates: Vec::new(),
42            pending_timer_queries: Vec::new(),
43        }
44    }
45}
46
47/// Helper for passing encoders to `update_bind_group_state`.
48///
49/// Combines [`naga::ShaderStage`] and an encoder of the appropriate type for
50/// that stage.
51enum Encoder<'e> {
52    Vertex(&'e ProtocolObject<dyn MTLRenderCommandEncoder>),
53    Fragment(&'e ProtocolObject<dyn MTLRenderCommandEncoder>),
54    Task(&'e ProtocolObject<dyn MTLRenderCommandEncoder>),
55    Mesh(&'e ProtocolObject<dyn MTLRenderCommandEncoder>),
56    Compute(&'e ProtocolObject<dyn MTLComputeCommandEncoder>),
57}
58
59impl Encoder<'_> {
60    fn stage(&self) -> naga::ShaderStage {
61        match self {
62            Self::Vertex(_) => naga::ShaderStage::Vertex,
63            Self::Fragment(_) => naga::ShaderStage::Fragment,
64            Self::Task(_) => naga::ShaderStage::Task,
65            Self::Mesh(_) => naga::ShaderStage::Mesh,
66            Self::Compute(_) => naga::ShaderStage::Compute,
67        }
68    }
69
70    fn set_buffer(
71        &self,
72        buffer: Option<&ProtocolObject<dyn MTLBuffer>>,
73        offset: NSUInteger,
74        index: NSUInteger,
75    ) {
76        unsafe {
77            match *self {
78                Self::Vertex(enc) => enc.setVertexBuffer_offset_atIndex(buffer, offset, index),
79                Self::Fragment(enc) => enc.setFragmentBuffer_offset_atIndex(buffer, offset, index),
80                Self::Task(enc) => enc.setObjectBuffer_offset_atIndex(buffer, offset, index),
81                Self::Mesh(enc) => enc.setMeshBuffer_offset_atIndex(buffer, offset, index),
82                Self::Compute(enc) => enc.setBuffer_offset_atIndex(buffer, offset, index),
83            }
84        }
85    }
86
87    fn set_acceleration_structure(
88        &self,
89        buffer: Option<&ProtocolObject<dyn MTLAccelerationStructure>>,
90        index: NSUInteger,
91    ) {
92        unsafe {
93            match *self {
94                Self::Vertex(enc) => {
95                    enc.setVertexAccelerationStructure_atBufferIndex(buffer, index)
96                }
97                Self::Fragment(enc) => {
98                    enc.setFragmentAccelerationStructure_atBufferIndex(buffer, index)
99                }
100                Self::Task(_) => {
101                    unreachable!("Acceleration structures are not allowed in task shaders")
102                }
103                Self::Mesh(_) => {
104                    unreachable!("Acceleration structures are not allowed in mesh shaders")
105                }
106                Self::Compute(enc) => enc.setAccelerationStructure_atBufferIndex(buffer, index),
107            }
108        }
109    }
110
111    fn set_bytes(&self, bytes: NonNull<core::ffi::c_void>, length: NSUInteger, index: NSUInteger) {
112        unsafe {
113            match *self {
114                Self::Vertex(enc) => enc.setVertexBytes_length_atIndex(bytes, length, index),
115                Self::Fragment(enc) => enc.setFragmentBytes_length_atIndex(bytes, length, index),
116                Self::Task(enc) => enc.setObjectBytes_length_atIndex(bytes, length, index),
117                Self::Mesh(enc) => enc.setMeshBytes_length_atIndex(bytes, length, index),
118                Self::Compute(enc) => enc.setBytes_length_atIndex(bytes, length, index),
119            }
120        }
121    }
122
123    fn set_sampler_state(
124        &self,
125        state: Option<&ProtocolObject<dyn MTLSamplerState>>,
126        index: NSUInteger,
127    ) {
128        unsafe {
129            match *self {
130                Self::Vertex(enc) => enc.setVertexSamplerState_atIndex(state, index),
131                Self::Fragment(enc) => enc.setFragmentSamplerState_atIndex(state, index),
132                Self::Task(enc) => enc.setObjectSamplerState_atIndex(state, index),
133                Self::Mesh(enc) => enc.setMeshSamplerState_atIndex(state, index),
134                Self::Compute(enc) => enc.setSamplerState_atIndex(state, index),
135            }
136        }
137    }
138
139    fn set_texture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>, index: NSUInteger) {
140        unsafe {
141            match *self {
142                Self::Vertex(enc) => enc.setVertexTexture_atIndex(texture, index),
143                Self::Fragment(enc) => enc.setFragmentTexture_atIndex(texture, index),
144                Self::Task(enc) => enc.setObjectTexture_atIndex(texture, index),
145                Self::Mesh(enc) => enc.setMeshTexture_atIndex(texture, index),
146                Self::Compute(enc) => enc.setTexture_atIndex(texture, index),
147            }
148        }
149    }
150}
151
152impl super::CommandEncoder {
153    pub fn raw_command_buffer(&self) -> Option<&ProtocolObject<dyn MTLCommandBuffer>> {
154        self.raw_cmd_buf.as_deref()
155    }
156
157    fn enter_blit(&mut self) -> Retained<ProtocolObject<dyn MTLBlitCommandEncoder>> {
158        if self.state.blit.is_none() {
159            self.leave_acceleration_structure_builder();
160            debug_assert!(self.state.render.is_none() && self.state.compute.is_none());
161            let cmd_buf = self.raw_cmd_buf.as_ref().unwrap();
162
163            // Take care of pending timer queries.
164            // If we can't use `sample_counters_in_buffer` we have to create a dummy blit encoder!
165            //
166            // There is a known bug in Metal where blit encoders won't write timestamps if they don't have a blit operation.
167            // See https://github.com/gpuweb/gpuweb/issues/2046#issuecomment-1205793680 & https://source.chromium.org/chromium/chromium/src/+/006c4eb70c96229834bbaf271290f40418144cd3:third_party/dawn/src/dawn/native/metal/BackendMTL.mm;l=350
168            //
169            // To make things worse:
170            // * what counts as a blit operation is a bit unclear, experimenting seemed to indicate that resolve_counters doesn't count.
171            // * in some cases (when?) using `set_start_of_encoder_sample_index` doesn't work, so we have to use `set_end_of_encoder_sample_index` instead
172            //
173            // All this means that pretty much the only *reliable* thing as of writing is to:
174            // * create a dummy blit encoder using set_end_of_encoder_sample_index
175            // * do a dummy write that is known to be not optimized out.
176            // * close the encoder since we used set_end_of_encoder_sample_index and don't want to get any extra stuff in there.
177            // * create another encoder for whatever we actually had in mind.
178            let supports_sample_counters_in_buffer = self
179                .shared
180                .private_caps
181                .timestamp_query_support
182                .contains(TimestampQuerySupport::ON_BLIT_ENCODER);
183
184            if !self.state.pending_timer_queries.is_empty() && !supports_sample_counters_in_buffer {
185                autoreleasepool(|_| {
186                    let descriptor = MTLBlitPassDescriptor::new();
187                    let mut last_query = None;
188                    for (i, (set, index)) in self.state.pending_timer_queries.drain(..).enumerate()
189                    {
190                        let sba_descriptor = unsafe {
191                            descriptor
192                                .sampleBufferAttachments()
193                                .objectAtIndexedSubscript(i)
194                        };
195                        sba_descriptor
196                            .setSampleBuffer(Some(set.counter_sample_buffer.as_ref().unwrap()));
197
198                        // Here be dragons:
199                        // As mentioned above, for some reasons using the start of the encoder won't yield any results sometimes!
200                        unsafe {
201                            sba_descriptor.setStartOfEncoderSampleIndex(MTLCounterDontSample)
202                        };
203                        unsafe { sba_descriptor.setEndOfEncoderSampleIndex(index as _) };
204
205                        last_query = Some((set, index));
206                    }
207                    let encoder = cmd_buf
208                        .blitCommandEncoderWithDescriptor(&descriptor)
209                        .unwrap();
210
211                    // As explained above, we need to do some write:
212                    // Conveniently, we have a buffer with every query set, that we can use for this for a dummy write,
213                    // since we know that it is going to be overwritten again on timer resolve and HAL doesn't define its state before that.
214                    let raw_range = NSRange {
215                        location: last_query.as_ref().unwrap().1 as usize
216                            * crate::QUERY_SIZE as usize,
217                        length: 1,
218                    };
219                    encoder.fillBuffer_range_value(
220                        &last_query.as_ref().unwrap().0.raw_buffer,
221                        raw_range,
222                        255, // Don't write 0, so it's easier to identify if something went wrong.
223                    );
224
225                    encoder.endEncoding();
226                });
227            }
228
229            autoreleasepool(|_| {
230                self.state.blit = Some(cmd_buf.blitCommandEncoder().unwrap());
231            });
232
233            // Clippy 1.93 hates this (it was patched in 1.93.1)
234            #[allow(clippy::panicking_unwrap, reason = "false positive")]
235            let encoder = self.state.blit.as_ref().unwrap();
236
237            // UNTESTED:
238            // If the above described issue with empty blit encoder applies to `sample_counters_in_buffer` as well, we should use the same workaround instead!
239            for (set, index) in self.state.pending_timer_queries.drain(..) {
240                debug_assert!(supports_sample_counters_in_buffer);
241                unsafe {
242                    encoder.sampleCountersInBuffer_atSampleIndex_withBarrier(
243                        set.counter_sample_buffer.as_ref().unwrap(),
244                        index as _,
245                        true,
246                    )
247                };
248            }
249        }
250        self.state.blit.as_ref().unwrap().clone()
251    }
252
253    pub(super) fn leave_blit(&mut self) {
254        if let Some(encoder) = self.state.blit.take() {
255            encoder.endEncoding();
256        }
257    }
258
259    fn enter_acceleration_structure_builder(
260        &mut self,
261    ) -> Retained<ProtocolObject<dyn MTLAccelerationStructureCommandEncoder>> {
262        if self.state.acceleration_structure_builder.is_none() {
263            self.leave_blit();
264            debug_assert!(
265                self.state.render.is_none()
266                    && self.state.compute.is_none()
267                    && self.state.blit.is_none()
268            );
269            let cmd_buf = self.raw_cmd_buf.as_ref().unwrap();
270            autoreleasepool(|_| {
271                self.state.acceleration_structure_builder =
272                    cmd_buf.accelerationStructureCommandEncoder().to_owned();
273            });
274        }
275        self.state.acceleration_structure_builder.clone().unwrap()
276    }
277
278    pub(super) fn leave_acceleration_structure_builder(&mut self) {
279        if let Some(encoder) = self.state.acceleration_structure_builder.take() {
280            encoder.endEncoding();
281        }
282    }
283
284    fn active_encoder(&mut self) -> Option<&ProtocolObject<dyn MTLCommandEncoder>> {
285        if let Some(ref encoder) = self.state.render {
286            Some(ProtocolObject::from_ref(&**encoder))
287        } else if let Some(ref encoder) = self.state.acceleration_structure_builder {
288            Some(ProtocolObject::from_ref(&**encoder))
289        } else if let Some(ref encoder) = self.state.compute {
290            Some(ProtocolObject::from_ref(&**encoder))
291        } else if let Some(ref encoder) = self.state.blit {
292            Some(ProtocolObject::from_ref(&**encoder))
293        } else {
294            None
295        }
296    }
297
298    fn begin_pass(&mut self) {
299        self.state.reset();
300        self.leave_blit();
301        self.leave_acceleration_structure_builder();
302    }
303
304    /// Updates the bindings for a single shader stage, called in `set_bind_group`.
305    fn update_bind_group_state(
306        &mut self,
307        encoder: Encoder<'_>,
308        index_base: super::ResourceData<u32>,
309        bg_info: &super::BindGroupLayoutInfo,
310        dynamic_offsets: &[wgt::DynamicOffset],
311        group_index: u32,
312        group: &super::BindGroup,
313    ) {
314        use naga::ShaderStage as S;
315        let resource_indices = match encoder.stage() {
316            S::Vertex => &bg_info.base_resource_indices.vs,
317            S::Fragment => &bg_info.base_resource_indices.fs,
318            S::Task => &bg_info.base_resource_indices.ts,
319            S::Mesh => &bg_info.base_resource_indices.ms,
320            S::Compute => &bg_info.base_resource_indices.cs,
321            S::RayGeneration | S::AnyHit | S::ClosestHit | S::Miss => unimplemented!(),
322        };
323        let buffers = match encoder.stage() {
324            S::Vertex => group.counters.vs.buffers,
325            S::Fragment => group.counters.fs.buffers,
326            S::Task => group.counters.ts.buffers,
327            S::Mesh => group.counters.ms.buffers,
328            S::Compute => group.counters.cs.buffers,
329            S::RayGeneration | S::AnyHit | S::ClosestHit | S::Miss => unimplemented!(),
330        };
331        let mut changes_sizes_buffer = false;
332        for index in 0..buffers {
333            let res = &group.buffers[(index_base.buffers + index) as usize];
334            match res {
335                super::BufferLikeResource::Buffer {
336                    ptr,
337                    mut offset,
338                    dynamic_index,
339                    binding_size,
340                    binding_location,
341                } => {
342                    let buffer = Some(unsafe { ptr.as_ref() });
343                    if let Some(dyn_index) = dynamic_index {
344                        offset += dynamic_offsets[*dyn_index as usize] as wgt::BufferAddress;
345                    }
346                    let index = (resource_indices.buffers + index) as usize;
347                    encoder.set_buffer(buffer, offset as usize, index);
348                    if let Some(size) = binding_size {
349                        let br = naga::ResourceBinding {
350                            group: group_index,
351                            binding: *binding_location,
352                        };
353                        self.state.storage_buffer_length_map.insert(br, *size);
354                        changes_sizes_buffer = true;
355                    }
356                }
357                super::BufferLikeResource::AccelerationStructure(ptr) => {
358                    let buffer = Some(unsafe { ptr.as_ref() });
359                    let index = (resource_indices.buffers + index) as usize;
360                    encoder.set_acceleration_structure(buffer, index);
361                }
362            }
363        }
364        if changes_sizes_buffer {
365            if let Some((index, sizes)) = self
366                .state
367                .make_sizes_buffer_update(encoder.stage(), &mut self.temp.binding_sizes)
368            {
369                let bytes_ptr = NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap();
370                let length = sizes.len() * WORD_SIZE;
371                let index = index as _;
372                encoder.set_bytes(bytes_ptr, length, index);
373            }
374        }
375        let samplers = match encoder.stage() {
376            S::Vertex => group.counters.vs.samplers,
377            S::Fragment => group.counters.fs.samplers,
378            S::Task => group.counters.ts.samplers,
379            S::Mesh => group.counters.ms.samplers,
380            S::Compute => group.counters.cs.samplers,
381            S::RayGeneration | S::AnyHit | S::ClosestHit | S::Miss => unimplemented!(),
382        };
383        for index in 0..samplers {
384            let res = group.samplers[(index_base.samplers + index) as usize];
385            let index = (resource_indices.samplers + index) as usize;
386            let state = Some(unsafe { res.as_ref() });
387            encoder.set_sampler_state(state, index);
388        }
389
390        let textures = match encoder.stage() {
391            S::Vertex => group.counters.vs.textures,
392            S::Fragment => group.counters.fs.textures,
393            S::Task => group.counters.ts.textures,
394            S::Mesh => group.counters.ms.textures,
395            S::Compute => group.counters.cs.textures,
396            S::RayGeneration | S::AnyHit | S::ClosestHit | S::Miss => unimplemented!(),
397        };
398        for index in 0..textures {
399            let res = group.textures[(index_base.textures + index) as usize];
400            let index = (resource_indices.textures + index) as usize;
401            let texture = Some(unsafe { res.as_ref() });
402            encoder.set_texture(texture, index);
403        }
404    }
405}
406
407impl super::CommandState {
408    fn reset(&mut self) {
409        self.storage_buffer_length_map.clear();
410        self.vertex_buffer_size_map.clear();
411        self.stage_infos.vs.clear();
412        self.stage_infos.fs.clear();
413        self.stage_infos.cs.clear();
414        self.stage_infos.ts.clear();
415        self.stage_infos.ms.clear();
416        self.immediates.clear();
417    }
418
419    fn make_sizes_buffer_update<'a>(
420        &self,
421        stage: naga::ShaderStage,
422        result_sizes: &'a mut Vec<u32>,
423    ) -> Option<(u32, &'a [u32])> {
424        let stage_info = &self.stage_infos[stage];
425        let slot = stage_info.sizes_slot?;
426
427        result_sizes.clear();
428        result_sizes.extend(stage_info.sized_bindings.iter().map(|br| {
429            self.storage_buffer_length_map
430                .get(br)
431                .map(|size| u32::try_from(size.get()).unwrap_or(u32::MAX))
432                .unwrap_or_default()
433        }));
434
435        // Extend with the sizes of the mapped vertex buffers, in the order
436        // they were added to the map.
437        result_sizes.extend(stage_info.vertex_buffer_mappings.iter().map(|vbm| {
438            self.vertex_buffer_size_map
439                .get(&(vbm.id as u64))
440                .map(|size| u32::try_from(size.get()).unwrap_or(u32::MAX))
441                .unwrap_or_default()
442        }));
443
444        if !result_sizes.is_empty() {
445            Some((slot as _, result_sizes))
446        } else {
447            None
448        }
449    }
450}
451
452impl crate::CommandEncoder for super::CommandEncoder {
453    type A = super::Api;
454
455    unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> {
456        let queue = &self.queue_shared.raw;
457        let retain_references = self.shared.settings.retain_command_buffer_references;
458
459        // Guard against exhausting Metal's command buffer budget. Use the hard
460        // limit (`MAX_COMMAND_BUFFERS`) so we fail before Metal can hang inside
461        // `new_command_buffer`.
462        let previous = self
463            .queue_shared
464            .command_buffer_created_not_submitted
465            .fetch_add(1, atomic::Ordering::AcqRel);
466        if previous >= adapter::MAX_COMMAND_BUFFERS {
467            let current = previous + 1;
468            log::warn!(
469                "metal: refusing to create new command buffer; {current} outstanding command \
470                 buffers exceeds the limit of {}. Treating this as device lost. \
471                 Ensure command encoders are submitted or dropped rather than kept alive \
472                 to avoid exhausting Metal's command buffer budget.",
473                adapter::MAX_COMMAND_BUFFERS
474            );
475            return Err(crate::DeviceError::Lost);
476        }
477
478        let raw = autoreleasepool(move |_| {
479            let cmd_buf_ref = if retain_references {
480                queue.commandBuffer()
481            } else {
482                queue.commandBufferWithUnretainedReferences()
483            }
484            .unwrap();
485            if let Some(label) = label {
486                cmd_buf_ref.setLabel(Some(&NSString::from_str(label)));
487            }
488            cmd_buf_ref.to_owned()
489        });
490
491        self.raw_cmd_buf = Some(raw);
492
493        Ok(())
494    }
495
496    unsafe fn discard_encoding(&mut self) {
497        self.leave_blit();
498        self.leave_acceleration_structure_builder();
499        // when discarding, we don't have a guarantee that
500        // everything is in a good state, so check carefully
501        if let Some(encoder) = self.state.render.take() {
502            encoder.endEncoding();
503        }
504        if let Some(encoder) = self.state.compute.take() {
505            encoder.endEncoding();
506        }
507        let had_command_buffer = self.raw_cmd_buf.is_some();
508        // Clear the Option first so the underlying `metal::CommandBuffer` is
509        // dropped before we update the counter.
510        self.raw_cmd_buf = None;
511        if had_command_buffer {
512            self.queue_shared
513                .command_buffer_created_not_submitted
514                .fetch_sub(1, atomic::Ordering::AcqRel);
515        }
516    }
517
518    unsafe fn end_encoding(&mut self) -> Result<super::CommandBuffer, crate::DeviceError> {
519        // Handle pending timer query if any.
520        if !self.state.pending_timer_queries.is_empty() {
521            self.leave_blit();
522            self.enter_blit();
523        }
524
525        self.leave_blit();
526        self.leave_acceleration_structure_builder();
527        debug_assert!(self.state.render.is_none());
528        debug_assert!(self.state.compute.is_none());
529        debug_assert!(self.state.pending_timer_queries.is_empty());
530
531        Ok(super::CommandBuffer {
532            raw: self.raw_cmd_buf.take().unwrap(),
533            queue_shared: Arc::clone(&self.queue_shared),
534        })
535    }
536
537    unsafe fn reset_all<I>(&mut self, _cmd_bufs: I)
538    where
539        I: Iterator<Item = super::CommandBuffer>,
540    {
541        //do nothing
542    }
543
544    unsafe fn transition_buffers<'a, T>(&mut self, _barriers: T)
545    where
546        T: Iterator<Item = crate::BufferBarrier<'a, super::Buffer>>,
547    {
548    }
549
550    unsafe fn transition_textures<'a, T>(&mut self, _barriers: T)
551    where
552        T: Iterator<Item = crate::TextureBarrier<'a, super::Texture>>,
553    {
554    }
555
556    unsafe fn clear_buffer(&mut self, buffer: &super::Buffer, range: crate::MemoryRange) {
557        let encoder = self.enter_blit();
558        encoder.fillBuffer_range_value(&buffer.raw, conv::map_range(&range), 0);
559    }
560
561    unsafe fn copy_buffer_to_buffer<T>(
562        &mut self,
563        src: &super::Buffer,
564        dst: &super::Buffer,
565        regions: T,
566    ) where
567        T: Iterator<Item = crate::BufferCopy>,
568    {
569        let encoder = self.enter_blit();
570        for copy in regions {
571            unsafe {
572                encoder.copyFromBuffer_sourceOffset_toBuffer_destinationOffset_size(
573                    &src.raw,
574                    copy.src_offset as usize,
575                    &dst.raw,
576                    copy.dst_offset as usize,
577                    copy.size.get() as usize,
578                )
579            };
580        }
581    }
582
583    unsafe fn copy_texture_to_texture<T>(
584        &mut self,
585        src: &super::Texture,
586        _src_usage: wgt::TextureUses,
587        dst: &super::Texture,
588        regions: T,
589    ) where
590        T: Iterator<Item = crate::TextureCopy>,
591    {
592        let dst_texture = if src.format != dst.format {
593            let raw_format = self
594                .shared
595                .private_texture_format_caps
596                .map_format(src.format);
597            Cow::Owned(autoreleasepool(|_| {
598                dst.raw.newTextureViewWithPixelFormat(raw_format).unwrap()
599            }))
600        } else {
601            Cow::Borrowed(&dst.raw)
602        };
603        let encoder = self.enter_blit();
604        for copy in regions {
605            let src_origin = conv::map_origin(&copy.src_base.origin);
606            let dst_origin = conv::map_origin(&copy.dst_base.origin);
607            // no clamping is done: Metal expects physical sizes here
608            let extent = conv::map_copy_extent(&copy.size);
609            unsafe {
610                encoder.copyFromTexture_sourceSlice_sourceLevel_sourceOrigin_sourceSize_toTexture_destinationSlice_destinationLevel_destinationOrigin(
611                    &src.raw,
612                    copy.src_base.array_layer as usize,
613                    copy.src_base.mip_level as usize,
614                    src_origin,
615                    extent,
616                    &dst_texture,
617                    copy.dst_base.array_layer as usize,
618                    copy.dst_base.mip_level as usize,
619                    dst_origin,
620                )
621            };
622        }
623    }
624
625    unsafe fn copy_buffer_to_texture<T>(
626        &mut self,
627        src: &super::Buffer,
628        dst: &super::Texture,
629        regions: T,
630    ) where
631        T: Iterator<Item = crate::BufferTextureCopy>,
632    {
633        let encoder = self.enter_blit();
634        for copy in regions {
635            let dst_origin = conv::map_origin(&copy.texture_base.origin);
636            // Metal expects buffer-texture copies in virtual sizes
637            let extent = copy
638                .texture_base
639                .max_copy_size(&dst.copy_size)
640                .min(&copy.size);
641            let bytes_per_row = copy.buffer_layout.bytes_per_row.unwrap_or(0) as u64;
642            let image_byte_stride = if extent.depth > 1 {
643                copy.buffer_layout
644                    .rows_per_image
645                    .map_or(0, |v| v as u64 * bytes_per_row)
646            } else {
647                // Don't pass a stride when updating a single layer, otherwise metal validation
648                // fails when updating a subset of the image due to the stride being larger than
649                // the amount of data to copy.
650                0
651            };
652            unsafe {
653                encoder.copyFromBuffer_sourceOffset_sourceBytesPerRow_sourceBytesPerImage_sourceSize_toTexture_destinationSlice_destinationLevel_destinationOrigin_options(
654                    &src.raw,
655                    copy.buffer_layout.offset as usize,
656                    bytes_per_row as usize,
657                    image_byte_stride as usize,
658                    conv::map_copy_extent(&extent),
659                    &dst.raw,
660                    copy.texture_base.array_layer as usize,
661                    copy.texture_base.mip_level as usize,
662                    dst_origin,
663                    conv::get_blit_option(dst.format, copy.texture_base.aspect),
664                )
665            };
666        }
667    }
668
669    unsafe fn copy_texture_to_buffer<T>(
670        &mut self,
671        src: &super::Texture,
672        _src_usage: wgt::TextureUses,
673        dst: &super::Buffer,
674        regions: T,
675    ) where
676        T: Iterator<Item = crate::BufferTextureCopy>,
677    {
678        let encoder = self.enter_blit();
679        for copy in regions {
680            let src_origin = conv::map_origin(&copy.texture_base.origin);
681            // Metal expects texture-buffer copies in virtual sizes
682            let extent = copy
683                .texture_base
684                .max_copy_size(&src.copy_size)
685                .min(&copy.size);
686            let bytes_per_row = copy.buffer_layout.bytes_per_row.unwrap_or(0) as u64;
687            let bytes_per_image = copy
688                .buffer_layout
689                .rows_per_image
690                .map_or(0, |v| v as u64 * bytes_per_row);
691            unsafe {
692                encoder.copyFromTexture_sourceSlice_sourceLevel_sourceOrigin_sourceSize_toBuffer_destinationOffset_destinationBytesPerRow_destinationBytesPerImage_options(
693                    &src.raw,
694                    copy.texture_base.array_layer as usize,
695                    copy.texture_base.mip_level as usize,
696                    src_origin,
697                    conv::map_copy_extent(&extent),
698                    &dst.raw,
699                    copy.buffer_layout.offset as usize,
700                    bytes_per_row as usize,
701                    bytes_per_image as usize,
702                    conv::get_blit_option(src.format, copy.texture_base.aspect),
703                )
704            };
705        }
706    }
707
708    unsafe fn copy_acceleration_structure_to_acceleration_structure(
709        &mut self,
710        src: &super::AccelerationStructure,
711        dst: &super::AccelerationStructure,
712        copy: wgt::AccelerationStructureCopy,
713    ) {
714        let command_encoder = self.enter_acceleration_structure_builder();
715        match copy {
716            wgt::AccelerationStructureCopy::Clone => unsafe {
717                command_encoder
718                    .copyAccelerationStructure_toAccelerationStructure(&src.raw, &dst.raw);
719            },
720            wgt::AccelerationStructureCopy::Compact => {
721                command_encoder.copyAndCompactAccelerationStructure_toAccelerationStructure(
722                    &src.raw, &dst.raw,
723                );
724            }
725        };
726    }
727
728    unsafe fn begin_query(&mut self, set: &super::QuerySet, index: u32) {
729        match set.ty {
730            wgt::QueryType::Occlusion => {
731                self.state
732                    .render
733                    .as_ref()
734                    .unwrap()
735                    .setVisibilityResultMode_offset(
736                        MTLVisibilityResultMode::Boolean,
737                        index as usize * crate::QUERY_SIZE as usize,
738                    );
739            }
740            _ => {}
741        }
742    }
743    unsafe fn end_query(&mut self, set: &super::QuerySet, _index: u32) {
744        match set.ty {
745            wgt::QueryType::Occlusion => {
746                self.state
747                    .render
748                    .as_ref()
749                    .unwrap()
750                    .setVisibilityResultMode_offset(MTLVisibilityResultMode::Disabled, 0);
751            }
752            _ => {}
753        }
754    }
755    unsafe fn write_timestamp(&mut self, set: &super::QuerySet, index: u32) {
756        let support = self.shared.private_caps.timestamp_query_support;
757        debug_assert!(
758            support.contains(TimestampQuerySupport::STAGE_BOUNDARIES),
759            "Timestamp queries are not supported"
760        );
761        let sample_buffer = set.counter_sample_buffer.as_ref().unwrap();
762        let with_barrier = true;
763
764        // Try to use an existing encoder for timestamp query if possible.
765        // This works only if it's supported for the active encoder.
766        if let (true, Some(encoder)) = (
767            support.contains(TimestampQuerySupport::ON_BLIT_ENCODER),
768            self.state.blit.as_ref(),
769        ) {
770            unsafe {
771                encoder.sampleCountersInBuffer_atSampleIndex_withBarrier(
772                    sample_buffer,
773                    index as _,
774                    with_barrier,
775                )
776            };
777        } else if let (true, Some(encoder)) = (
778            support.contains(TimestampQuerySupport::ON_RENDER_ENCODER),
779            self.state.render.as_ref(),
780        ) {
781            unsafe {
782                encoder.sampleCountersInBuffer_atSampleIndex_withBarrier(
783                    sample_buffer,
784                    index as _,
785                    with_barrier,
786                )
787            };
788        } else if let (true, Some(encoder)) = (
789            support.contains(TimestampQuerySupport::ON_COMPUTE_ENCODER),
790            self.state.compute.as_ref(),
791        ) {
792            unsafe {
793                encoder.sampleCountersInBuffer_atSampleIndex_withBarrier(
794                    sample_buffer,
795                    index as _,
796                    with_barrier,
797                )
798            };
799        } else {
800            // If we're here it means we either have no encoder open, or it's not supported to sample within them.
801            // If this happens with render/compute open, this is an invalid usage!
802            debug_assert!(self.state.render.is_none() && self.state.compute.is_none());
803
804            // But otherwise it means we'll put defer this to the next created encoder.
805            self.state.pending_timer_queries.push((set.clone(), index));
806
807            // Ensure we didn't already have a blit open.
808            self.leave_blit();
809        };
810    }
811
812    unsafe fn reset_queries(&mut self, set: &super::QuerySet, range: Range<u32>) {
813        let encoder = self.enter_blit();
814        let raw_range = NSRange {
815            location: range.start as usize * crate::QUERY_SIZE as usize,
816            length: (range.end - range.start) as usize * crate::QUERY_SIZE as usize,
817        };
818        encoder.fillBuffer_range_value(&set.raw_buffer, raw_range, 0);
819    }
820
821    unsafe fn copy_query_results(
822        &mut self,
823        set: &super::QuerySet,
824        range: Range<u32>,
825        buffer: &super::Buffer,
826        offset: wgt::BufferAddress,
827        _: wgt::BufferSize, // Metal doesn't support queries that are bigger than a single element are not supported
828    ) {
829        let encoder = self.enter_blit();
830        match set.ty {
831            wgt::QueryType::Occlusion => {
832                let size = (range.end - range.start) as u64 * crate::QUERY_SIZE;
833                unsafe {
834                    encoder.copyFromBuffer_sourceOffset_toBuffer_destinationOffset_size(
835                        &set.raw_buffer,
836                        range.start as usize * crate::QUERY_SIZE as usize,
837                        &buffer.raw,
838                        offset as usize,
839                        size as usize,
840                    )
841                };
842            }
843            wgt::QueryType::Timestamp => {
844                unsafe {
845                    encoder.resolveCounters_inRange_destinationBuffer_destinationOffset(
846                        set.counter_sample_buffer.as_ref().unwrap(),
847                        NSRange::new(range.start as usize, (range.end - range.start) as usize),
848                        &buffer.raw,
849                        offset as usize,
850                    )
851                };
852            }
853            wgt::QueryType::PipelineStatistics(_) => todo!(),
854        }
855    }
856
857    // render
858
859    unsafe fn begin_render_pass(
860        &mut self,
861        desc: &crate::RenderPassDescriptor<super::QuerySet, super::TextureView>,
862    ) -> Result<(), crate::DeviceError> {
863        self.begin_pass();
864        self.state.index = None;
865
866        assert!(self.state.blit.is_none());
867        assert!(self.state.compute.is_none());
868        assert!(self.state.render.is_none());
869
870        autoreleasepool(|_| {
871            let descriptor = MTLRenderPassDescriptor::new();
872
873            for (i, at) in desc.color_attachments.iter().enumerate() {
874                if let Some(at) = at.as_ref() {
875                    let at_descriptor =
876                        unsafe { descriptor.colorAttachments().objectAtIndexedSubscript(i) };
877                    at_descriptor.setTexture(Some(&at.target.view.raw));
878                    if let Some(depth_slice) = at.depth_slice {
879                        at_descriptor.setDepthPlane(depth_slice as usize);
880                    }
881                    if let Some(ref resolve) = at.resolve_target {
882                        //Note: the selection of levels and slices is already handled by `TextureView`
883                        at_descriptor.setResolveTexture(Some(&resolve.view.raw));
884                    }
885                    let load_action = if at.ops.contains(crate::AttachmentOps::LOAD) {
886                        MTLLoadAction::Load
887                    } else if at.ops.contains(crate::AttachmentOps::LOAD_DONT_CARE) {
888                        MTLLoadAction::DontCare
889                    } else if at.ops.contains(crate::AttachmentOps::LOAD_CLEAR) {
890                        at_descriptor.setClearColor(conv::map_clear_color(&at.clear_value));
891                        MTLLoadAction::Clear
892                    } else {
893                        unreachable!()
894                    };
895                    let store_action = conv::map_store_action(
896                        at.ops.contains(crate::AttachmentOps::STORE),
897                        at.resolve_target.is_some(),
898                    );
899                    at_descriptor.setLoadAction(load_action);
900                    at_descriptor.setStoreAction(store_action);
901                }
902            }
903
904            if let Some(ref at) = desc.depth_stencil_attachment {
905                if at.target.view.aspects.contains(crate::FormatAspects::DEPTH) {
906                    let at_descriptor = descriptor.depthAttachment();
907                    at_descriptor.setTexture(Some(&at.target.view.raw));
908
909                    let load_action = if at.depth_ops.contains(crate::AttachmentOps::LOAD) {
910                        MTLLoadAction::Load
911                    } else if at.depth_ops.contains(crate::AttachmentOps::LOAD_DONT_CARE) {
912                        MTLLoadAction::DontCare
913                    } else if at.depth_ops.contains(crate::AttachmentOps::LOAD_CLEAR) {
914                        at_descriptor.setClearDepth(at.clear_value.0 as f64);
915                        MTLLoadAction::Clear
916                    } else {
917                        unreachable!();
918                    };
919                    let store_action = if at.depth_ops.contains(crate::AttachmentOps::STORE) {
920                        MTLStoreAction::Store
921                    } else {
922                        MTLStoreAction::DontCare
923                    };
924                    at_descriptor.setLoadAction(load_action);
925                    at_descriptor.setStoreAction(store_action);
926                }
927                if at
928                    .target
929                    .view
930                    .aspects
931                    .contains(crate::FormatAspects::STENCIL)
932                {
933                    let at_descriptor = descriptor.stencilAttachment();
934                    at_descriptor.setTexture(Some(&at.target.view.raw));
935
936                    let load_action = if at.stencil_ops.contains(crate::AttachmentOps::LOAD) {
937                        MTLLoadAction::Load
938                    } else if at
939                        .stencil_ops
940                        .contains(crate::AttachmentOps::LOAD_DONT_CARE)
941                    {
942                        MTLLoadAction::DontCare
943                    } else if at.stencil_ops.contains(crate::AttachmentOps::LOAD_CLEAR) {
944                        at_descriptor.setClearStencil(at.clear_value.1);
945                        MTLLoadAction::Clear
946                    } else {
947                        unreachable!()
948                    };
949                    let store_action = if at.stencil_ops.contains(crate::AttachmentOps::STORE) {
950                        MTLStoreAction::Store
951                    } else {
952                        MTLStoreAction::DontCare
953                    };
954                    at_descriptor.setLoadAction(load_action);
955                    at_descriptor.setStoreAction(store_action);
956                }
957            }
958
959            let mut sba_index = 0;
960            let mut next_sba_descriptor = || {
961                let sba_descriptor = unsafe {
962                    descriptor
963                        .sampleBufferAttachments()
964                        .objectAtIndexedSubscript(sba_index)
965                };
966
967                unsafe { sba_descriptor.setEndOfVertexSampleIndex(MTLCounterDontSample) };
968                unsafe { sba_descriptor.setStartOfFragmentSampleIndex(MTLCounterDontSample) };
969
970                sba_index += 1;
971                sba_descriptor
972            };
973
974            for (set, index) in self.state.pending_timer_queries.drain(..) {
975                let sba_descriptor = next_sba_descriptor();
976                sba_descriptor.setSampleBuffer(Some(set.counter_sample_buffer.as_ref().unwrap()));
977                unsafe { sba_descriptor.setStartOfVertexSampleIndex(index as _) };
978                unsafe { sba_descriptor.setEndOfFragmentSampleIndex(MTLCounterDontSample) };
979            }
980
981            if let Some(ref timestamp_writes) = desc.timestamp_writes {
982                let sba_descriptor = next_sba_descriptor();
983                sba_descriptor.setSampleBuffer(Some(
984                    timestamp_writes
985                        .query_set
986                        .counter_sample_buffer
987                        .as_ref()
988                        .unwrap(),
989                ));
990
991                unsafe {
992                    sba_descriptor.setStartOfVertexSampleIndex(
993                        timestamp_writes
994                            .beginning_of_pass_write_index
995                            .map_or(MTLCounterDontSample, |i| i as _),
996                    )
997                };
998                unsafe {
999                    sba_descriptor.setEndOfFragmentSampleIndex(
1000                        timestamp_writes
1001                            .end_of_pass_write_index
1002                            .map_or(MTLCounterDontSample, |i| i as _),
1003                    )
1004                };
1005            }
1006
1007            if let Some(occlusion_query_set) = desc.occlusion_query_set {
1008                descriptor.setVisibilityResultBuffer(Some(occlusion_query_set.raw_buffer.as_ref()))
1009            }
1010            // This strangely isn't mentioned in https://developer.apple.com/documentation/metal/improving-rendering-performance-with-vertex-amplification.
1011            // The docs for [`renderTargetArrayLength`](https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor/rendertargetarraylength)
1012            // also say "The number of active layers that all attachments must have for layered rendering," implying it is only for layered rendering.
1013            // However, when I don't set this, I get undefined behavior in nonzero layers, and all non-apple examples of vertex amplification set it.
1014            // So this is just one of those undocumented requirements.
1015            if let Some(mv) = desc.multiview_mask {
1016                descriptor.setRenderTargetArrayLength(32 - mv.leading_zeros() as usize);
1017            }
1018            let raw = self.raw_cmd_buf.as_ref().unwrap();
1019            let encoder = raw.renderCommandEncoderWithDescriptor(&descriptor).unwrap();
1020            if let Some(mv) = desc.multiview_mask {
1021                // Most likely the API just wasn't thought about enough. It's not like they ever allow you
1022                // to use enough views to overflow a 32-bit bitmask.
1023                let mv = mv.get();
1024                let msb = 32 - mv.leading_zeros();
1025                let mut maps: SmallVec<[MTLVertexAmplificationViewMapping; 32]> = SmallVec::new();
1026                for i in 0..msb {
1027                    if (mv & (1 << i)) != 0 {
1028                        maps.push(MTLVertexAmplificationViewMapping {
1029                            renderTargetArrayIndexOffset: i,
1030                            viewportArrayIndexOffset: i,
1031                        });
1032                    }
1033                }
1034                unsafe {
1035                    encoder.setVertexAmplificationCount_viewMappings(
1036                        mv.count_ones() as usize,
1037                        maps.as_ptr(),
1038                    )
1039                };
1040            }
1041            if let Some(label) = desc.label {
1042                encoder.setLabel(Some(&NSString::from_str(label)));
1043            }
1044            self.state.render = Some(encoder);
1045        });
1046
1047        Ok(())
1048    }
1049
1050    unsafe fn end_render_pass(&mut self) {
1051        self.state.render.take().unwrap().endEncoding();
1052    }
1053
1054    unsafe fn set_bind_group(
1055        &mut self,
1056        layout: &super::PipelineLayout,
1057        group_index: u32,
1058        group: &super::BindGroup,
1059        dynamic_offsets: &[wgt::DynamicOffset],
1060    ) {
1061        let bg_info = layout.bind_group_infos[group_index as usize]
1062            .as_ref()
1063            .unwrap();
1064        let render_encoder = self.state.render.clone();
1065        let compute_encoder = self.state.compute.clone();
1066        if let Some(encoder) = render_encoder {
1067            self.update_bind_group_state(
1068                Encoder::Vertex(&encoder),
1069                // All zeros, as vs comes first
1070                super::ResourceData::default(),
1071                bg_info,
1072                dynamic_offsets,
1073                group_index,
1074                group,
1075            );
1076            self.update_bind_group_state(
1077                Encoder::Task(&encoder),
1078                // All zeros, as ts comes first
1079                super::ResourceData::default(),
1080                bg_info,
1081                dynamic_offsets,
1082                group_index,
1083                group,
1084            );
1085            self.update_bind_group_state(
1086                Encoder::Mesh(&encoder),
1087                group.counters.ts.clone(),
1088                bg_info,
1089                dynamic_offsets,
1090                group_index,
1091                group,
1092            );
1093            self.update_bind_group_state(
1094                Encoder::Fragment(&encoder),
1095                super::ResourceData {
1096                    buffers: group.counters.vs.buffers
1097                        + group.counters.ts.buffers
1098                        + group.counters.ms.buffers,
1099                    textures: group.counters.vs.textures
1100                        + group.counters.ts.textures
1101                        + group.counters.ms.textures,
1102                    samplers: group.counters.vs.samplers
1103                        + group.counters.ts.samplers
1104                        + group.counters.ms.samplers,
1105                },
1106                bg_info,
1107                dynamic_offsets,
1108                group_index,
1109                group,
1110            );
1111            // Call useResource on all textures and buffers used indirectly so they are alive
1112            for (resource, use_info) in group.resources_to_use.iter() {
1113                encoder.useResource_usage_stages(
1114                    unsafe { resource.as_ref() },
1115                    use_info.uses,
1116                    use_info.stages,
1117                );
1118            }
1119        }
1120        if let Some(encoder) = compute_encoder {
1121            self.update_bind_group_state(
1122                Encoder::Compute(&encoder),
1123                super::ResourceData {
1124                    buffers: group.counters.vs.buffers
1125                        + group.counters.ts.buffers
1126                        + group.counters.ms.buffers
1127                        + group.counters.fs.buffers,
1128                    textures: group.counters.vs.textures
1129                        + group.counters.ts.textures
1130                        + group.counters.ms.textures
1131                        + group.counters.fs.textures,
1132                    samplers: group.counters.vs.samplers
1133                        + group.counters.ts.samplers
1134                        + group.counters.ms.samplers
1135                        + group.counters.fs.samplers,
1136                },
1137                bg_info,
1138                dynamic_offsets,
1139                group_index,
1140                group,
1141            );
1142            // Call useResource on all textures and buffers used indirectly so they are alive
1143            for (resource, use_info) in group.resources_to_use.iter() {
1144                if !use_info.visible_in_compute {
1145                    continue;
1146                }
1147                encoder.useResource_usage(unsafe { resource.as_ref() }, use_info.uses);
1148            }
1149        }
1150    }
1151
1152    unsafe fn set_immediates(
1153        &mut self,
1154        layout: &super::PipelineLayout,
1155        offset_bytes: u32,
1156        data: &[u32],
1157    ) {
1158        let state_pc = &mut self.state.immediates;
1159        if state_pc.len() < layout.total_immediates as usize {
1160            state_pc.resize(layout.total_immediates as usize, 0);
1161        }
1162        debug_assert_eq!(offset_bytes as usize % WORD_SIZE, 0);
1163
1164        let offset_words = offset_bytes as usize / WORD_SIZE;
1165        state_pc[offset_words..offset_words + data.len()].copy_from_slice(data);
1166
1167        let bytes = NonNull::new(state_pc.as_ptr().cast_mut().cast()).unwrap();
1168        if let Some(ref compute) = self.state.compute {
1169            unsafe {
1170                compute.setBytes_length_atIndex(
1171                    bytes,
1172                    layout.total_immediates as usize * WORD_SIZE,
1173                    layout.immediates_infos.cs.unwrap().buffer_index as usize,
1174                )
1175            };
1176        }
1177        if let Some(ref render) = self.state.render {
1178            if let Some(vs) = layout.immediates_infos.vs {
1179                unsafe {
1180                    render.setVertexBytes_length_atIndex(
1181                        bytes,
1182                        layout.total_immediates as usize * WORD_SIZE,
1183                        vs.buffer_index as _,
1184                    )
1185                }
1186            }
1187            if let Some(fs) = layout.immediates_infos.fs {
1188                unsafe {
1189                    render.setFragmentBytes_length_atIndex(
1190                        bytes,
1191                        layout.total_immediates as usize * WORD_SIZE,
1192                        fs.buffer_index as _,
1193                    )
1194                }
1195            }
1196            if let Some(ts) = layout.immediates_infos.ts {
1197                if self.shared.private_caps.mesh_shaders {
1198                    unsafe {
1199                        render.setObjectBytes_length_atIndex(
1200                            bytes,
1201                            layout.total_immediates as usize * WORD_SIZE,
1202                            ts.buffer_index as _,
1203                        )
1204                    }
1205                }
1206            }
1207            if let Some(ms) = layout.immediates_infos.ms {
1208                if self.shared.private_caps.mesh_shaders {
1209                    unsafe {
1210                        render.setMeshBytes_length_atIndex(
1211                            bytes,
1212                            layout.total_immediates as usize * WORD_SIZE,
1213                            ms.buffer_index as _,
1214                        )
1215                    }
1216                }
1217            }
1218        }
1219    }
1220
1221    unsafe fn insert_debug_marker(&mut self, label: &str) {
1222        if let Some(encoder) = self.active_encoder() {
1223            encoder.insertDebugSignpost(&NSString::from_str(label));
1224        }
1225    }
1226    unsafe fn begin_debug_marker(&mut self, group_label: &str) {
1227        if let Some(encoder) = self.active_encoder() {
1228            encoder.pushDebugGroup(&NSString::from_str(group_label));
1229        } else if let Some(ref buf) = self.raw_cmd_buf {
1230            buf.pushDebugGroup(&NSString::from_str(group_label));
1231        }
1232    }
1233    unsafe fn end_debug_marker(&mut self) {
1234        if let Some(encoder) = self.active_encoder() {
1235            encoder.popDebugGroup();
1236        } else if let Some(ref buf) = self.raw_cmd_buf {
1237            buf.popDebugGroup();
1238        }
1239    }
1240
1241    unsafe fn set_render_pipeline(&mut self, pipeline: &super::RenderPipeline) {
1242        self.state.raw_primitive_type = pipeline.raw_primitive_type;
1243        match pipeline.vs_info {
1244            Some(ref info) => self.state.stage_infos.vs.assign_from(info),
1245            None => self.state.stage_infos.vs.clear(),
1246        }
1247        match pipeline.fs_info {
1248            Some(ref info) => self.state.stage_infos.fs.assign_from(info),
1249            None => self.state.stage_infos.fs.clear(),
1250        }
1251        match pipeline.ts_info {
1252            Some(ref info) => self.state.stage_infos.ts.assign_from(info),
1253            None => self.state.stage_infos.ts.clear(),
1254        }
1255        match pipeline.ms_info {
1256            Some(ref info) => self.state.stage_infos.ms.assign_from(info),
1257            None => self.state.stage_infos.ms.clear(),
1258        }
1259
1260        let encoder = self.state.render.as_ref().unwrap();
1261        encoder.setRenderPipelineState(&pipeline.raw);
1262        encoder.setFrontFacingWinding(pipeline.raw_front_winding);
1263        encoder.setCullMode(pipeline.raw_cull_mode);
1264        encoder.setTriangleFillMode(pipeline.raw_triangle_fill_mode);
1265        if let Some(depth_clip) = pipeline.raw_depth_clip_mode {
1266            encoder.setDepthClipMode(depth_clip);
1267        }
1268        if let Some((ref state, bias)) = pipeline.depth_stencil {
1269            encoder.setDepthStencilState(Some(state));
1270            encoder.setDepthBias_slopeScale_clamp(
1271                bias.constant as f32,
1272                bias.slope_scale,
1273                bias.clamp,
1274            );
1275        }
1276
1277        if pipeline.vs_info.is_some() {
1278            if let Some((index, sizes)) = self
1279                .state
1280                .make_sizes_buffer_update(naga::ShaderStage::Vertex, &mut self.temp.binding_sizes)
1281            {
1282                unsafe {
1283                    encoder.setVertexBytes_length_atIndex(
1284                        NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap(),
1285                        sizes.len() * WORD_SIZE,
1286                        index as _,
1287                    )
1288                };
1289            }
1290        }
1291        if pipeline.fs_info.is_some() {
1292            if let Some((index, sizes)) = self
1293                .state
1294                .make_sizes_buffer_update(naga::ShaderStage::Fragment, &mut self.temp.binding_sizes)
1295            {
1296                unsafe {
1297                    encoder.setFragmentBytes_length_atIndex(
1298                        NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap(),
1299                        sizes.len() * WORD_SIZE,
1300                        index as _,
1301                    )
1302                };
1303            }
1304        }
1305        if let Some(ts_info) = &pipeline.ts_info {
1306            // update the threadgroup memory sizes
1307            while self.state.stage_infos.ms.work_group_memory_sizes.len()
1308                < ts_info.work_group_memory_sizes.len()
1309            {
1310                self.state.stage_infos.ms.work_group_memory_sizes.push(0);
1311            }
1312            for (index, (cur_size, pipeline_size)) in self
1313                .state
1314                .stage_infos
1315                .ms
1316                .work_group_memory_sizes
1317                .iter_mut()
1318                .zip(ts_info.work_group_memory_sizes.iter())
1319                .enumerate()
1320            {
1321                let size = pipeline_size.next_multiple_of(16);
1322                if *cur_size != size {
1323                    *cur_size = size;
1324                    unsafe { encoder.setObjectThreadgroupMemoryLength_atIndex(size as _, index) };
1325                }
1326            }
1327            if let Some((index, sizes)) = self
1328                .state
1329                .make_sizes_buffer_update(naga::ShaderStage::Task, &mut self.temp.binding_sizes)
1330            {
1331                unsafe {
1332                    encoder.setObjectBytes_length_atIndex(
1333                        NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap(),
1334                        sizes.len() * WORD_SIZE,
1335                        index as _,
1336                    )
1337                };
1338            }
1339        }
1340        if let Some(_ms_info) = &pipeline.ms_info {
1341            // So there isn't an equivalent to
1342            // https://developer.apple.com/documentation/metal/mtlrendercommandencoder/setthreadgroupmemorylength(_:offset:index:)
1343            // for mesh shaders. This is probably because the CPU has less control over the dispatch sizes and such. Interestingly
1344            // it also affects mesh shaders without task/object shaders, even though none of compute, task or fragment shaders
1345            // behave this way.
1346            if let Some((index, sizes)) = self
1347                .state
1348                .make_sizes_buffer_update(naga::ShaderStage::Mesh, &mut self.temp.binding_sizes)
1349            {
1350                unsafe {
1351                    encoder.setMeshBytes_length_atIndex(
1352                        NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap(),
1353                        sizes.len() * WORD_SIZE,
1354                        index as _,
1355                    )
1356                };
1357            }
1358        }
1359    }
1360
1361    unsafe fn set_index_buffer<'a>(
1362        &mut self,
1363        binding: crate::BufferBinding<'a, super::Buffer>,
1364        format: wgt::IndexFormat,
1365    ) {
1366        let (stride, raw_type) = conv::map_index_format(format);
1367        self.state.index = Some(super::IndexState {
1368            buffer_ptr: NonNull::from(&*binding.buffer.raw),
1369            offset: binding.offset,
1370            stride,
1371            raw_type,
1372        });
1373    }
1374
1375    unsafe fn set_vertex_buffer<'a>(
1376        &mut self,
1377        index: u32,
1378        binding: crate::BufferBinding<'a, super::Buffer>,
1379    ) {
1380        let buffer_index = self.shared.private_caps.max_vertex_buffers as u64 - 1 - index as u64;
1381        let encoder = self.state.render.as_ref().unwrap();
1382        unsafe {
1383            encoder.setVertexBuffer_offset_atIndex(
1384                Some(&binding.buffer.raw),
1385                binding.offset as usize,
1386                buffer_index as usize,
1387            )
1388        };
1389
1390        let buffer_size = binding.resolve_size();
1391        if buffer_size > 0 {
1392            self.state.vertex_buffer_size_map.insert(
1393                buffer_index,
1394                core::num::NonZeroU64::new(buffer_size).unwrap(),
1395            );
1396        } else {
1397            self.state.vertex_buffer_size_map.remove(&buffer_index);
1398        }
1399
1400        if let Some((index, sizes)) = self
1401            .state
1402            .make_sizes_buffer_update(naga::ShaderStage::Vertex, &mut self.temp.binding_sizes)
1403        {
1404            unsafe {
1405                encoder.setVertexBytes_length_atIndex(
1406                    NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap(),
1407                    sizes.len() * WORD_SIZE,
1408                    index as _,
1409                )
1410            };
1411        }
1412    }
1413
1414    unsafe fn set_viewport(&mut self, rect: &crate::Rect<f32>, depth_range: Range<f32>) {
1415        let zfar = if self.shared.disabilities.broken_viewport_near_depth {
1416            depth_range.end - depth_range.start
1417        } else {
1418            depth_range.end
1419        };
1420        let encoder = self.state.render.as_ref().unwrap();
1421        encoder.setViewport(MTLViewport {
1422            originX: rect.x as _,
1423            originY: rect.y as _,
1424            width: rect.w as _,
1425            height: rect.h as _,
1426            znear: depth_range.start as _,
1427            zfar: zfar as _,
1428        });
1429    }
1430    unsafe fn set_scissor_rect(&mut self, rect: &crate::Rect<u32>) {
1431        //TODO: support empty scissors by modifying the viewport
1432        let scissor = MTLScissorRect {
1433            x: rect.x as _,
1434            y: rect.y as _,
1435            width: rect.w as _,
1436            height: rect.h as _,
1437        };
1438        let encoder = self.state.render.as_ref().unwrap();
1439        encoder.setScissorRect(scissor);
1440    }
1441    unsafe fn set_stencil_reference(&mut self, value: u32) {
1442        let encoder = self.state.render.as_ref().unwrap();
1443        encoder.setStencilFrontReferenceValue_backReferenceValue(value, value);
1444    }
1445    unsafe fn set_blend_constants(&mut self, color: &[f32; 4]) {
1446        let encoder = self.state.render.as_ref().unwrap();
1447        encoder.setBlendColorRed_green_blue_alpha(color[0], color[1], color[2], color[3]);
1448    }
1449
1450    unsafe fn draw(
1451        &mut self,
1452        first_vertex: u32,
1453        vertex_count: u32,
1454        first_instance: u32,
1455        instance_count: u32,
1456    ) {
1457        let encoder = self.state.render.as_ref().unwrap();
1458        if first_instance != 0 {
1459            unsafe {
1460                encoder.drawPrimitives_vertexStart_vertexCount_instanceCount_baseInstance(
1461                    self.state.raw_primitive_type,
1462                    first_vertex as _,
1463                    vertex_count as _,
1464                    instance_count as _,
1465                    first_instance as _,
1466                )
1467            };
1468        } else if instance_count != 1 {
1469            unsafe {
1470                encoder.drawPrimitives_vertexStart_vertexCount_instanceCount(
1471                    self.state.raw_primitive_type,
1472                    first_vertex as _,
1473                    vertex_count as _,
1474                    instance_count as _,
1475                )
1476            };
1477        } else {
1478            unsafe {
1479                encoder.drawPrimitives_vertexStart_vertexCount(
1480                    self.state.raw_primitive_type,
1481                    first_vertex as _,
1482                    vertex_count as _,
1483                )
1484            };
1485        }
1486    }
1487
1488    unsafe fn draw_indexed(
1489        &mut self,
1490        first_index: u32,
1491        index_count: u32,
1492        base_vertex: i32,
1493        first_instance: u32,
1494        instance_count: u32,
1495    ) {
1496        let encoder = self.state.render.as_ref().unwrap();
1497        let index = self.state.index.as_ref().unwrap();
1498        let offset = (index.offset + index.stride * first_index as wgt::BufferAddress) as usize;
1499        if base_vertex != 0 || first_instance != 0 {
1500            unsafe {
1501                encoder.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset_instanceCount_baseVertex_baseInstance(
1502                    self.state.raw_primitive_type,
1503                    index_count as _,
1504                    index.raw_type,
1505                    index.buffer_ptr.as_ref(),
1506                    offset,
1507                    instance_count as _,
1508                    base_vertex as _,
1509                    first_instance as _,
1510                )
1511            };
1512        } else if instance_count != 1 {
1513            unsafe {
1514                encoder.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset_instanceCount(
1515                    self.state.raw_primitive_type,
1516                    index_count as _,
1517                    index.raw_type,
1518                    index.buffer_ptr.as_ref(),
1519                    offset,
1520                    instance_count as _,
1521                )
1522            };
1523        } else {
1524            unsafe {
1525                encoder.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset(
1526                    self.state.raw_primitive_type,
1527                    index_count as _,
1528                    index.raw_type,
1529                    index.buffer_ptr.as_ref(),
1530                    offset,
1531                )
1532            };
1533        }
1534    }
1535
1536    unsafe fn draw_mesh_tasks(
1537        &mut self,
1538        group_count_x: u32,
1539        group_count_y: u32,
1540        group_count_z: u32,
1541    ) {
1542        let encoder = self.state.render.as_ref().unwrap();
1543        let size = MTLSize {
1544            width: group_count_x as usize,
1545            height: group_count_y as usize,
1546            depth: group_count_z as usize,
1547        };
1548        encoder.drawMeshThreadgroups_threadsPerObjectThreadgroup_threadsPerMeshThreadgroup(
1549            size,
1550            self.state.stage_infos.ts.raw_wg_size,
1551            self.state.stage_infos.ms.raw_wg_size,
1552        );
1553    }
1554
1555    unsafe fn draw_indirect(
1556        &mut self,
1557        buffer: &super::Buffer,
1558        mut offset: wgt::BufferAddress,
1559        draw_count: u32,
1560    ) {
1561        let encoder = self.state.render.as_ref().unwrap();
1562        for _ in 0..draw_count {
1563            unsafe {
1564                encoder.drawPrimitives_indirectBuffer_indirectBufferOffset(
1565                    self.state.raw_primitive_type,
1566                    &buffer.raw,
1567                    offset as usize,
1568                )
1569            };
1570            offset += size_of::<wgt::DrawIndirectArgs>() as wgt::BufferAddress;
1571        }
1572    }
1573
1574    unsafe fn draw_indexed_indirect(
1575        &mut self,
1576        buffer: &super::Buffer,
1577        mut offset: wgt::BufferAddress,
1578        draw_count: u32,
1579    ) {
1580        let encoder = self.state.render.as_ref().unwrap();
1581        let index = self.state.index.as_ref().unwrap();
1582        for _ in 0..draw_count {
1583            unsafe {
1584                encoder.drawIndexedPrimitives_indexType_indexBuffer_indexBufferOffset_indirectBuffer_indirectBufferOffset(
1585                    self.state.raw_primitive_type,
1586                    index.raw_type,
1587                    index.buffer_ptr.as_ref(),
1588                    index.offset as usize,
1589                    &buffer.raw,
1590                    offset as usize,
1591                )
1592            };
1593            offset += size_of::<wgt::DrawIndexedIndirectArgs>() as wgt::BufferAddress;
1594        }
1595    }
1596
1597    unsafe fn draw_mesh_tasks_indirect(
1598        &mut self,
1599        buffer: &<Self::A as crate::Api>::Buffer,
1600        mut offset: wgt::BufferAddress,
1601        draw_count: u32,
1602    ) {
1603        let encoder = self.state.render.as_ref().unwrap();
1604        for _ in 0..draw_count {
1605            unsafe {
1606                encoder.drawMeshThreadgroupsWithIndirectBuffer_indirectBufferOffset_threadsPerObjectThreadgroup_threadsPerMeshThreadgroup(
1607                    &buffer.raw,
1608                    offset as usize,
1609                    self.state.stage_infos.ts.raw_wg_size,
1610                    self.state.stage_infos.ms.raw_wg_size,
1611                )
1612            };
1613            offset += size_of::<wgt::DispatchIndirectArgs>() as wgt::BufferAddress;
1614        }
1615    }
1616
1617    unsafe fn draw_indirect_count(
1618        &mut self,
1619        _buffer: &super::Buffer,
1620        _offset: wgt::BufferAddress,
1621        _count_buffer: &super::Buffer,
1622        _count_offset: wgt::BufferAddress,
1623        _max_count: u32,
1624    ) {
1625        //TODO
1626    }
1627    unsafe fn draw_indexed_indirect_count(
1628        &mut self,
1629        _buffer: &super::Buffer,
1630        _offset: wgt::BufferAddress,
1631        _count_buffer: &super::Buffer,
1632        _count_offset: wgt::BufferAddress,
1633        _max_count: u32,
1634    ) {
1635        //TODO
1636    }
1637
1638    unsafe fn draw_mesh_tasks_indirect_count(
1639        &mut self,
1640        _buffer: &<Self::A as crate::Api>::Buffer,
1641        _offset: wgt::BufferAddress,
1642        _count_buffer: &<Self::A as crate::Api>::Buffer,
1643        _count_offset: wgt::BufferAddress,
1644        _max_count: u32,
1645    ) {
1646        unreachable!()
1647    }
1648
1649    // compute
1650
1651    unsafe fn begin_compute_pass(&mut self, desc: &crate::ComputePassDescriptor<super::QuerySet>) {
1652        self.begin_pass();
1653
1654        debug_assert!(self.state.blit.is_none());
1655        debug_assert!(self.state.compute.is_none());
1656        debug_assert!(self.state.render.is_none());
1657
1658        let raw = self.raw_cmd_buf.as_ref().unwrap();
1659
1660        autoreleasepool(|_| {
1661            // TimeStamp Queries and ComputePassDescriptor were both introduced in Metal 2.3 (macOS 11, iOS 14)
1662            // and we currently only need ComputePassDescriptor for timestamp queries
1663            let encoder = if self.shared.private_caps.timestamp_query_support.is_empty() {
1664                raw.computeCommandEncoder().unwrap()
1665            } else {
1666                let descriptor = MTLComputePassDescriptor::new();
1667
1668                let mut sba_index = 0;
1669                let mut next_sba_descriptor = || {
1670                    let sba_descriptor = unsafe {
1671                        descriptor
1672                            .sampleBufferAttachments()
1673                            .objectAtIndexedSubscript(sba_index)
1674                    };
1675                    sba_index += 1;
1676                    sba_descriptor
1677                };
1678
1679                for (set, index) in self.state.pending_timer_queries.drain(..) {
1680                    let sba_descriptor = next_sba_descriptor();
1681                    sba_descriptor
1682                        .setSampleBuffer(Some(set.counter_sample_buffer.as_ref().unwrap()));
1683                    unsafe { sba_descriptor.setStartOfEncoderSampleIndex(index as _) };
1684                    unsafe { sba_descriptor.setEndOfEncoderSampleIndex(MTLCounterDontSample) };
1685                }
1686
1687                if let Some(timestamp_writes) = desc.timestamp_writes.as_ref() {
1688                    let sba_descriptor = next_sba_descriptor();
1689                    sba_descriptor.setSampleBuffer(Some(
1690                        timestamp_writes
1691                            .query_set
1692                            .counter_sample_buffer
1693                            .as_ref()
1694                            .unwrap(),
1695                    ));
1696
1697                    unsafe {
1698                        sba_descriptor.setStartOfEncoderSampleIndex(
1699                            timestamp_writes
1700                                .beginning_of_pass_write_index
1701                                .map_or(MTLCounterDontSample, |i| i as _),
1702                        )
1703                    };
1704                    unsafe {
1705                        sba_descriptor.setEndOfEncoderSampleIndex(
1706                            timestamp_writes
1707                                .end_of_pass_write_index
1708                                .map_or(MTLCounterDontSample, |i| i as _),
1709                        )
1710                    };
1711                }
1712
1713                raw.computeCommandEncoderWithDescriptor(&descriptor)
1714                    .unwrap()
1715            };
1716
1717            if let Some(label) = desc.label {
1718                encoder.setLabel(Some(&NSString::from_str(label)));
1719            }
1720
1721            self.state.compute = Some(encoder.to_owned());
1722        });
1723    }
1724    unsafe fn end_compute_pass(&mut self) {
1725        self.state.compute.take().unwrap().endEncoding();
1726    }
1727
1728    unsafe fn set_compute_pipeline(&mut self, pipeline: &super::ComputePipeline) {
1729        let previous_sizes =
1730            core::mem::take(&mut self.state.stage_infos.cs.work_group_memory_sizes);
1731        self.state.stage_infos.cs.assign_from(&pipeline.cs_info);
1732
1733        let encoder = self.state.compute.as_ref().unwrap();
1734        encoder.setComputePipelineState(&pipeline.raw);
1735
1736        if let Some((index, sizes)) = self
1737            .state
1738            .make_sizes_buffer_update(naga::ShaderStage::Compute, &mut self.temp.binding_sizes)
1739        {
1740            unsafe {
1741                encoder.setBytes_length_atIndex(
1742                    NonNull::new(sizes.as_ptr().cast_mut().cast()).unwrap(),
1743                    sizes.len() * WORD_SIZE,
1744                    index as _,
1745                )
1746            };
1747        }
1748
1749        // update the threadgroup memory sizes
1750        for (i, current_size) in self
1751            .state
1752            .stage_infos
1753            .cs
1754            .work_group_memory_sizes
1755            .iter_mut()
1756            .enumerate()
1757        {
1758            let prev_size = if i < previous_sizes.len() {
1759                previous_sizes[i]
1760            } else {
1761                u32::MAX
1762            };
1763            let size: u32 = current_size.next_multiple_of(16);
1764            *current_size = size;
1765            if size != prev_size {
1766                unsafe { encoder.setThreadgroupMemoryLength_atIndex(size as _, i) };
1767            }
1768        }
1769    }
1770
1771    unsafe fn dispatch(&mut self, count: [u32; 3]) {
1772        if count[0] > 0 && count[1] > 0 && count[2] > 0 {
1773            let encoder = self.state.compute.as_ref().unwrap();
1774            let raw_count = MTLSize {
1775                width: count[0] as usize,
1776                height: count[1] as usize,
1777                depth: count[2] as usize,
1778            };
1779            encoder.dispatchThreadgroups_threadsPerThreadgroup(
1780                raw_count,
1781                self.state.stage_infos.cs.raw_wg_size,
1782            );
1783        }
1784    }
1785
1786    unsafe fn dispatch_indirect(&mut self, buffer: &super::Buffer, offset: wgt::BufferAddress) {
1787        let encoder = self.state.compute.as_ref().unwrap();
1788        unsafe {
1789            encoder
1790                .dispatchThreadgroupsWithIndirectBuffer_indirectBufferOffset_threadsPerThreadgroup(
1791                    &buffer.raw,
1792                    offset as usize,
1793                    self.state.stage_infos.cs.raw_wg_size,
1794                )
1795        };
1796    }
1797
1798    unsafe fn build_acceleration_structures<'a, T>(
1799        &mut self,
1800        _descriptor_count: u32,
1801        descriptors: T,
1802    ) where
1803        super::Api: 'a,
1804        T: IntoIterator<
1805            Item = crate::BuildAccelerationStructureDescriptor<
1806                'a,
1807                super::Buffer,
1808                super::AccelerationStructure,
1809            >,
1810        >,
1811    {
1812        let command_encoder = self.enter_acceleration_structure_builder();
1813        for descriptor in descriptors {
1814            let acceleration_structure_descriptor =
1815                conv::map_acceleration_structure_descriptor(descriptor.entries, descriptor.flags);
1816            match descriptor.mode {
1817                crate::AccelerationStructureBuildMode::Build => {
1818                    command_encoder
1819                        .buildAccelerationStructure_descriptor_scratchBuffer_scratchBufferOffset(
1820                            &descriptor.destination_acceleration_structure.raw,
1821                            &acceleration_structure_descriptor,
1822                            &descriptor.scratch_buffer.raw,
1823                            descriptor.scratch_buffer_offset as usize,
1824                        );
1825                }
1826                crate::AccelerationStructureBuildMode::Update => unsafe {
1827                    command_encoder.refitAccelerationStructure_descriptor_destination_scratchBuffer_scratchBufferOffset(
1828                        &descriptor.source_acceleration_structure.unwrap().raw,
1829                        &acceleration_structure_descriptor,
1830                        Some(&descriptor.destination_acceleration_structure.raw),
1831                        Some(&descriptor.scratch_buffer.raw),
1832                        descriptor.scratch_buffer_offset as usize,
1833                    );
1834                },
1835            }
1836        }
1837    }
1838
1839    unsafe fn place_acceleration_structure_barrier(
1840        &mut self,
1841        _barriers: crate::AccelerationStructureBarrier,
1842    ) {
1843    }
1844
1845    unsafe fn read_acceleration_structure_compact_size(
1846        &mut self,
1847        acceleration_structure: &super::AccelerationStructure,
1848        buffer: &super::Buffer,
1849    ) {
1850        let command_encoder = self.enter_acceleration_structure_builder();
1851        command_encoder.writeCompactedAccelerationStructureSize_toBuffer_offset(
1852            &acceleration_structure.raw,
1853            &buffer.raw,
1854            0,
1855        );
1856    }
1857
1858    unsafe fn set_acceleration_structure_dependencies(
1859        command_buffers: &[&super::CommandBuffer],
1860        dependencies: &[&super::AccelerationStructure],
1861    ) {
1862        let Some(first_command_buffer) = command_buffers.first() else {
1863            return;
1864        };
1865        let desc = MTLResidencySetDescriptor::new();
1866        desc.setLabel(first_command_buffer.raw.label().as_deref());
1867        let residency_set = first_command_buffer
1868            .raw
1869            .device()
1870            .newResidencySetWithDescriptor_error(&desc)
1871            .unwrap();
1872        for command_buffer in command_buffers {
1873            command_buffer.raw.useResidencySet(&residency_set);
1874        }
1875        for dependency in dependencies {
1876            residency_set.addAllocation(ProtocolObject::from_ref(&*dependency.raw));
1877        }
1878        residency_set.commit();
1879    }
1880}
1881
1882impl Drop for super::CommandEncoder {
1883    fn drop(&mut self) {
1884        // Metal raises an assert when a MTLCommandEncoder is deallocated without a call
1885        // to endEncoding. This isn't documented in the general case at
1886        // https://developer.apple.com/documentation/metal/mtlcommandencoder, but for the
1887        // more-specific MTLComputeCommandEncoder it is stated as a requirement at
1888        // https://developer.apple.com/documentation/metal/mtlcomputecommandencoder. It
1889        // appears to be a requirement for all MTLCommandEncoder objects. Failing to call
1890        // endEncoding causes a crash with the message 'Command encoder released without
1891        // endEncoding'. To prevent this, we explicitiy call discard_encoding, which
1892        // calls endEncoding on any still-held MTLCommandEncoders.
1893        unsafe {
1894            self.discard_encoding();
1895        }
1896        self.counters.command_encoders.sub(1);
1897    }
1898}
1899
1900impl Drop for super::CommandBuffer {
1901    fn drop(&mut self) {
1902        // `command_buffer_created_not_submitted` is usually decremented when the command
1903        // buffer is submitted. But if we're dropping a command buffer that was never
1904        // submitted, we need to decrement the count here.
1905        let status = self.raw.status();
1906        if status == MTLCommandBufferStatus::NotEnqueued
1907            || status == MTLCommandBufferStatus::Enqueued
1908        {
1909            let previous = self
1910                .queue_shared
1911                .command_buffer_created_not_submitted
1912                .fetch_sub(1, atomic::Ordering::AcqRel);
1913            debug_assert!(previous > 0);
1914        }
1915    }
1916}