1#![allow(clippy::missing_errors_doc)]
2
3use crate::{
4 ffi, storage_mode, util::take_optional_string, CommandBuffer, CommandBufferPhase, CommandQueue,
5 ComputePipelineState, CounterSampleBuffer, DepthStencilState, Event, Fence, MetalBuffer,
6 MetalTexture, RenderPipelineState, SamplerState,
7};
8use core::ffi::c_void;
9use core::ops::Range;
10use std::collections::HashSet;
11
12const MAX_BUFFER_BINDINGS: usize = 31;
13const MAX_TEXTURE_BINDINGS: usize = 128;
14const MAX_SAMPLER_BINDINGS: usize = 16;
15
16pub mod command_buffer_status {
18 pub const NOT_ENQUEUED: usize = 0;
20 pub const ENQUEUED: usize = 1;
22 pub const COMMITTED: usize = 2;
24 pub const SCHEDULED: usize = 3;
26 pub const COMPLETED: usize = 4;
28 pub const ERROR: usize = 5;
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum CommandBufferError {
35 StateLockPoisoned,
37 InvalidState {
39 operation: &'static str,
40 state: &'static str,
41 },
42 ActiveEncoder,
44 EncoderEnded,
46 EncoderCreationFailed { encoder: &'static str },
48 RangeOutOfBounds {
50 resource: &'static str,
51 offset: usize,
52 length: usize,
53 resource_length: usize,
54 },
55 InvalidRange,
57 InvalidBindingIndex {
59 binding: &'static str,
60 index: usize,
61 limit: usize,
62 },
63 IntegerOutOfRange { field: &'static str, value: usize },
65 EmptyDispatch { field: &'static str },
67 ManagedStorageRequired { storage_mode: usize },
69 FenceWaitAfterUpdate,
71 NativeRejected { operation: &'static str },
73 ExecutionFailed(String),
75}
76
77impl core::fmt::Display for CommandBufferError {
78 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79 match self {
80 Self::StateLockPoisoned => formatter.write_str("command-buffer state lock is poisoned"),
81 Self::InvalidState { operation, state } => {
82 write!(
83 formatter,
84 "{operation} is invalid while command buffer is {state}"
85 )
86 }
87 Self::ActiveEncoder => formatter.write_str("a command encoder is still active"),
88 Self::EncoderEnded => formatter.write_str("the command encoder has already ended"),
89 Self::EncoderCreationFailed { encoder } => {
90 write!(
91 formatter,
92 "Metal could not create a {encoder} command encoder"
93 )
94 }
95 Self::RangeOutOfBounds {
96 resource,
97 offset,
98 length,
99 resource_length,
100 } => write!(
101 formatter,
102 "{resource} range {offset}..{} exceeds length {resource_length}",
103 offset.saturating_add(*length)
104 ),
105 Self::InvalidRange => formatter.write_str("range end precedes range start"),
106 Self::InvalidBindingIndex {
107 binding,
108 index,
109 limit,
110 } => write!(
111 formatter,
112 "{binding} binding index {index} is outside 0..{limit}"
113 ),
114 Self::IntegerOutOfRange { field, value } => {
115 write!(formatter, "{field} value {value} exceeds native Int")
116 }
117 Self::EmptyDispatch { field } => {
118 write!(formatter, "dispatch dimension {field} must be non-zero")
119 }
120 Self::ManagedStorageRequired { storage_mode } => {
121 write!(
122 formatter,
123 "managed storage required, got mode {storage_mode}"
124 )
125 }
126 Self::FenceWaitAfterUpdate => {
127 formatter.write_str("cannot wait for a fence after updating it in the same encoder")
128 }
129 Self::NativeRejected { operation } => {
130 write!(formatter, "Metal rejected {operation}")
131 }
132 Self::ExecutionFailed(message) => write!(formatter, "GPU execution failed: {message}"),
133 }
134 }
135}
136
137impl std::error::Error for CommandBufferError {}
138
139struct EncoderCore {
140 ptr: *mut c_void,
141 command_buffer: CommandBuffer,
142 ended: bool,
143 updated_fences: HashSet<usize>,
144}
145
146impl EncoderCore {
147 fn new(ptr: *mut c_void, command_buffer: CommandBuffer) -> Self {
148 Self {
149 ptr,
150 command_buffer,
151 ended: false,
152 updated_fences: HashSet::new(),
153 }
154 }
155
156 fn with_active<T>(
157 &self,
158 operation: &'static str,
159 encode: impl FnOnce(*mut c_void) -> T,
160 ) -> Result<T, CommandBufferError> {
161 if self.ended {
162 return Err(CommandBufferError::EncoderEnded);
163 }
164 let state = self
165 .command_buffer
166 .inner
167 .state
168 .lock()
169 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
170 ensure_recording(state.phase, operation)?;
171 if !state.active_encoder {
172 return Err(CommandBufferError::EncoderEnded);
173 }
174 drop(state);
175 Ok(encode(self.ptr))
176 }
177
178 fn finish(&mut self) -> Result<(), CommandBufferError> {
179 if self.ended {
180 return Err(CommandBufferError::EncoderEnded);
181 }
182 let mut state = self
183 .command_buffer
184 .inner
185 .state
186 .lock()
187 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
188 ensure_recording(state.phase, "end_encoding")?;
189 if !state.active_encoder {
190 return Err(CommandBufferError::EncoderEnded);
191 }
192 unsafe { ffi::am_command_encoder_end_encoding(self.ptr) };
193 state.active_encoder = false;
194 drop(state);
195 self.ended = true;
196 Ok(())
197 }
198
199 fn finish_on_drop(&mut self) {
200 if self.ended {
201 return;
202 }
203 let mut state = self
204 .command_buffer
205 .inner
206 .state
207 .lock()
208 .unwrap_or_else(std::sync::PoisonError::into_inner);
209 if matches!(
210 state.phase,
211 CommandBufferPhase::Recording | CommandBufferPhase::Enqueued
212 ) && state.active_encoder
213 {
214 unsafe { ffi::am_command_encoder_end_encoding(self.ptr) };
215 state.active_encoder = false;
216 }
217 drop(state);
218 self.ended = true;
219 }
220
221 fn record_fence_update(&mut self, fence: &Fence) {
222 self.updated_fences.insert(fence.as_ptr() as usize);
223 }
224
225 fn ensure_fence_wait_allowed(&self, fence: &Fence) -> Result<(), CommandBufferError> {
226 if self.updated_fences.contains(&(fence.as_ptr() as usize)) {
227 Err(CommandBufferError::FenceWaitAfterUpdate)
228 } else {
229 Ok(())
230 }
231 }
232}
233
234impl Drop for EncoderCore {
235 fn drop(&mut self) {
236 self.finish_on_drop();
237 if !self.ptr.is_null() {
238 unsafe { ffi::am_object_release(self.ptr) };
239 self.ptr = core::ptr::null_mut();
240 }
241 }
242}
243
244macro_rules! command_encoder {
245 ($(#[$meta:meta])* pub struct $name:ident;) => {
246 $(#[$meta])*
247 pub struct $name {
248 core: EncoderCore,
249 }
250
251 impl $name {
252 fn new(ptr: *mut c_void, command_buffer: CommandBuffer) -> Self {
253 Self {
254 core: EncoderCore::new(ptr, command_buffer),
255 }
256 }
257
258 #[must_use]
263 pub fn as_ptr(&self) -> *mut c_void {
264 self.core.ptr
265 }
266
267 pub fn end_encoding(mut self) -> Result<(), CommandBufferError> {
270 self.core.finish()
271 }
272 }
273 };
274}
275
276command_encoder!(
277 pub struct BlitCommandEncoder;
279);
280command_encoder!(
281 pub struct ComputeCommandEncoder;
283);
284command_encoder!(
285 pub struct RenderCommandEncoder;
287);
288
289impl CommandQueue {
290 #[must_use]
299 pub unsafe fn new_command_buffer_with_unretained_references(&self) -> Option<CommandBuffer> {
300 let ptr =
301 ffi::am_command_queue_new_command_buffer_with_unretained_references(self.as_ptr());
302 if ptr.is_null() {
303 None
304 } else {
305 Some(CommandBuffer::from_retained_ptr(ptr))
306 }
307 }
308}
309
310impl CommandBuffer {
311 pub fn enqueue(&self) -> Result<(), CommandBufferError> {
313 let mut state = self
314 .inner
315 .state
316 .lock()
317 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
318 if state.phase != CommandBufferPhase::Recording {
319 return Err(invalid_state("enqueue", state.phase));
320 }
321 if state.active_encoder {
322 return Err(CommandBufferError::ActiveEncoder);
323 }
324 unsafe { ffi::am_command_buffer_enqueue(self.as_ptr()) };
325 state.phase = CommandBufferPhase::Enqueued;
326 drop(state);
327 Ok(())
328 }
329
330 pub fn commit(&self) -> Result<(), CommandBufferError> {
332 let mut state = self
333 .inner
334 .state
335 .lock()
336 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
337 ensure_recording(state.phase, "commit")?;
338 if state.active_encoder {
339 return Err(CommandBufferError::ActiveEncoder);
340 }
341 unsafe { ffi::am_command_buffer_commit(self.as_ptr()) };
342 state.phase = CommandBufferPhase::Committed;
343 drop(state);
344 Ok(())
345 }
346
347 pub fn wait_until_scheduled(&self) -> Result<(), CommandBufferError> {
349 let state = self
350 .inner
351 .state
352 .lock()
353 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
354 match state.phase {
355 CommandBufferPhase::Completed => return Ok(()),
356 CommandBufferPhase::Error => return Err(self.execution_error()),
357 CommandBufferPhase::Committed => {}
358 phase => return Err(invalid_state("wait_until_scheduled", phase)),
359 }
360 drop(state);
361 unsafe { ffi::am_command_buffer_wait_until_scheduled(self.as_ptr()) };
362 Ok(())
363 }
364
365 pub fn wait_until_completed(&self) -> Result<(), CommandBufferError> {
367 {
368 let state = self
369 .inner
370 .state
371 .lock()
372 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
373 match state.phase {
374 CommandBufferPhase::Completed => return Ok(()),
375 CommandBufferPhase::Error => return Err(self.execution_error()),
376 CommandBufferPhase::Committed => {}
377 phase => return Err(invalid_state("wait_until_completed", phase)),
378 }
379 }
380 unsafe { ffi::am_command_buffer_wait_until_completed(self.as_ptr()) };
381 let status = unsafe { ffi::am_command_buffer_status(self.as_ptr()) };
382 let mut state = self
383 .inner
384 .state
385 .lock()
386 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
387 if status == command_buffer_status::ERROR {
388 state.phase = CommandBufferPhase::Error;
389 drop(state);
390 Err(self.execution_error())
391 } else {
392 state.phase = CommandBufferPhase::Completed;
393 drop(state);
394 Ok(())
395 }
396 }
397
398 #[must_use]
400 pub fn status(&self) -> usize {
401 let status = unsafe { ffi::am_command_buffer_status(self.as_ptr()) };
402 let mut state = self
403 .inner
404 .state
405 .lock()
406 .unwrap_or_else(std::sync::PoisonError::into_inner);
407 match status {
408 command_buffer_status::COMPLETED => state.phase = CommandBufferPhase::Completed,
409 command_buffer_status::ERROR => state.phase = CommandBufferPhase::Error,
410 _ => {}
411 }
412 status
413 }
414
415 #[must_use]
417 pub fn error(&self) -> Option<String> {
418 unsafe { take_optional_string(ffi::am_command_buffer_error_message(self.as_ptr())) }
419 }
420
421 pub fn new_blit_command_encoder(&self) -> Result<BlitCommandEncoder, CommandBufferError> {
423 let core = self.begin_encoder("blit", || unsafe {
424 ffi::am_command_buffer_new_blit_command_encoder(self.as_ptr())
425 })?;
426 Ok(BlitCommandEncoder::new(core, self.clone()))
427 }
428
429 pub fn new_compute_command_encoder(&self) -> Result<ComputeCommandEncoder, CommandBufferError> {
431 let core = self.begin_encoder("compute", || unsafe {
432 ffi::am_command_buffer_new_compute_command_encoder(self.as_ptr())
433 })?;
434 Ok(ComputeCommandEncoder::new(core, self.clone()))
435 }
436
437 pub fn new_render_command_encoder(
439 &self,
440 texture: &MetalTexture,
441 load_action: usize,
442 store_action: usize,
443 clear_color: [f64; 4],
444 ) -> Result<RenderCommandEncoder, CommandBufferError> {
445 ensure_native_int(load_action, "load_action")?;
446 ensure_native_int(store_action, "store_action")?;
447 let core = self.begin_encoder("render", || unsafe {
448 ffi::am_command_buffer_new_render_command_encoder(
449 self.as_ptr(),
450 texture.as_ptr(),
451 load_action,
452 store_action,
453 clear_color[0],
454 clear_color[1],
455 clear_color[2],
456 clear_color[3],
457 )
458 })?;
459 Ok(RenderCommandEncoder::new(core, self.clone()))
460 }
461
462 pub fn encode_wait_for_event(
464 &self,
465 event: &Event,
466 value: u64,
467 ) -> Result<(), CommandBufferError> {
468 self.encode_without_encoder("encode_wait_for_event", || unsafe {
469 ffi::am_command_buffer_encode_wait_for_event(self.as_ptr(), event.as_ptr(), value);
470 })
471 }
472
473 pub fn encode_signal_event(&self, event: &Event, value: u64) -> Result<(), CommandBufferError> {
475 self.encode_without_encoder("encode_signal_event", || unsafe {
476 ffi::am_command_buffer_encode_signal_event(self.as_ptr(), event.as_ptr(), value);
477 })
478 }
479
480 pub fn blit_copy_buffer(
482 &self,
483 src: &MetalBuffer,
484 src_offset: usize,
485 dst: &MetalBuffer,
486 dst_offset: usize,
487 size: usize,
488 ) -> Result<(), CommandBufferError> {
489 let mut encoder = self.new_blit_command_encoder()?;
490 encoder.copy_buffer(src, src_offset, dst, dst_offset, size)?;
491 encoder.end_encoding()
492 }
493
494 pub fn dispatch_compute_1d(
496 &self,
497 pipeline: &ComputePipelineState,
498 buffers: &[&MetalBuffer],
499 threadgroups: usize,
500 threads_per_group: usize,
501 ) -> Result<(), CommandBufferError> {
502 let mut encoder = self.new_compute_command_encoder()?;
503 encoder.set_compute_pipeline_state(pipeline)?;
504 for (index, buffer) in buffers.iter().enumerate() {
505 encoder.set_buffer(buffer, 0, index)?;
506 }
507 encoder.dispatch_threadgroups((threadgroups, 1, 1), (threads_per_group, 1, 1))?;
508 encoder.end_encoding()
509 }
510
511 pub(crate) fn encode_without_encoder(
512 &self,
513 operation: &'static str,
514 encode: impl FnOnce(),
515 ) -> Result<(), CommandBufferError> {
516 let state = self
517 .inner
518 .state
519 .lock()
520 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
521 ensure_recording(state.phase, operation)?;
522 if state.active_encoder {
523 return Err(CommandBufferError::ActiveEncoder);
524 }
525 encode();
526 drop(state);
527 Ok(())
528 }
529
530 fn begin_encoder(
531 &self,
532 encoder: &'static str,
533 create: impl FnOnce() -> *mut c_void,
534 ) -> Result<*mut c_void, CommandBufferError> {
535 let mut state = self
536 .inner
537 .state
538 .lock()
539 .map_err(|_| CommandBufferError::StateLockPoisoned)?;
540 ensure_recording(state.phase, "create command encoder")?;
541 if state.active_encoder {
542 return Err(CommandBufferError::ActiveEncoder);
543 }
544 let pointer = create();
545 if pointer.is_null() {
546 return Err(CommandBufferError::EncoderCreationFailed { encoder });
547 }
548 state.active_encoder = true;
549 drop(state);
550 Ok(pointer)
551 }
552
553 fn execution_error(&self) -> CommandBufferError {
554 let message =
555 unsafe { take_optional_string(ffi::am_command_buffer_error_message(self.as_ptr())) }
556 .unwrap_or_else(|| {
557 "Metal reported an unspecified command-buffer error".to_string()
558 });
559 CommandBufferError::ExecutionFailed(message)
560 }
561}
562
563impl BlitCommandEncoder {
564 pub fn copy_buffer(
566 &mut self,
567 src: &MetalBuffer,
568 src_offset: usize,
569 dst: &MetalBuffer,
570 dst_offset: usize,
571 size: usize,
572 ) -> Result<(), CommandBufferError> {
573 checked_resource_range("source buffer", src_offset, size, src.length())?;
574 checked_resource_range("destination buffer", dst_offset, size, dst.length())?;
575 ensure_native_int(src_offset, "source offset")?;
576 ensure_native_int(dst_offset, "destination offset")?;
577 ensure_native_int(size, "copy size")?;
578 let accepted = self.core.with_active("copy_buffer", |encoder| unsafe {
579 ffi::am_blit_command_encoder_copy_buffer(
580 encoder,
581 src.as_ptr(),
582 src_offset,
583 dst.as_ptr(),
584 dst_offset,
585 size,
586 )
587 })?;
588 if accepted {
589 Ok(())
590 } else {
591 Err(CommandBufferError::NativeRejected {
592 operation: "buffer copy",
593 })
594 }
595 }
596
597 pub fn fill_buffer(
599 &mut self,
600 buffer: &MetalBuffer,
601 range: Range<usize>,
602 value: u8,
603 ) -> Result<(), CommandBufferError> {
604 if range.start > range.end {
605 return Err(CommandBufferError::InvalidRange);
606 }
607 let length = range.end - range.start;
608 checked_resource_range("buffer", range.start, length, buffer.length())?;
609 ensure_native_int(range.start, "fill offset")?;
610 ensure_native_int(length, "fill length")?;
611 let accepted = self.core.with_active("fill_buffer", |encoder| unsafe {
612 ffi::am_blit_command_encoder_fill_buffer(
613 encoder,
614 buffer.as_ptr(),
615 range.start,
616 length,
617 value,
618 )
619 })?;
620 if accepted {
621 Ok(())
622 } else {
623 Err(CommandBufferError::NativeRejected {
624 operation: "buffer fill",
625 })
626 }
627 }
628
629 pub fn sample_counters(
631 &mut self,
632 sample_buffer: &CounterSampleBuffer,
633 sample_index: usize,
634 barrier: bool,
635 ) -> Result<(), CommandBufferError> {
636 if sample_index >= sample_buffer.sample_count() {
637 return Err(CommandBufferError::InvalidBindingIndex {
638 binding: "counter sample",
639 index: sample_index,
640 limit: sample_buffer.sample_count(),
641 });
642 }
643 ensure_native_int(sample_index, "sample index")?;
644 let accepted = self.core.with_active("sample_counters", |encoder| unsafe {
645 ffi::am_blit_command_encoder_sample_counters(
646 encoder,
647 sample_buffer.as_ptr(),
648 sample_index,
649 barrier,
650 )
651 })?;
652 if accepted {
653 Ok(())
654 } else {
655 Err(CommandBufferError::NativeRejected {
656 operation: "counter sampling",
657 })
658 }
659 }
660
661 pub fn synchronize_resource(&mut self, buffer: &MetalBuffer) -> Result<(), CommandBufferError> {
663 synchronize_resource(&self.core, buffer.as_ptr(), buffer.storage_mode())
664 }
665
666 pub fn synchronize_texture(
668 &mut self,
669 texture: &MetalTexture,
670 ) -> Result<(), CommandBufferError> {
671 synchronize_resource(&self.core, texture.as_ptr(), texture.storage_mode())
672 }
673
674 pub fn update_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
676 self.core.with_active("update_fence", |encoder| unsafe {
677 ffi::am_blit_command_encoder_update_fence(encoder, fence.as_ptr());
678 })?;
679 self.core.record_fence_update(fence);
680 Ok(())
681 }
682
683 pub fn wait_for_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
685 self.core.ensure_fence_wait_allowed(fence)?;
686 self.core.with_active("wait_for_fence", |encoder| unsafe {
687 ffi::am_blit_command_encoder_wait_for_fence(encoder, fence.as_ptr());
688 })
689 }
690}
691
692impl ComputeCommandEncoder {
693 pub fn set_compute_pipeline_state(
695 &mut self,
696 pipeline: &ComputePipelineState,
697 ) -> Result<(), CommandBufferError> {
698 self.core
699 .with_active("set_compute_pipeline_state", |encoder| unsafe {
700 ffi::am_compute_command_encoder_set_pipeline_state(encoder, pipeline.as_ptr());
701 })
702 }
703
704 pub fn set_buffer(
706 &mut self,
707 buffer: &MetalBuffer,
708 offset: usize,
709 index: usize,
710 ) -> Result<(), CommandBufferError> {
711 validate_binding_index("buffer", index, MAX_BUFFER_BINDINGS)?;
712 checked_resource_range("buffer", offset, 0, buffer.length())?;
713 ensure_native_int(offset, "buffer offset")?;
714 self.core.with_active("set_buffer", |encoder| unsafe {
715 ffi::am_compute_command_encoder_set_buffer(encoder, buffer.as_ptr(), offset, index);
716 })
717 }
718
719 pub fn set_texture(
721 &mut self,
722 texture: &MetalTexture,
723 index: usize,
724 ) -> Result<(), CommandBufferError> {
725 validate_binding_index("texture", index, MAX_TEXTURE_BINDINGS)?;
726 self.core.with_active("set_texture", |encoder| unsafe {
727 ffi::am_compute_command_encoder_set_texture(encoder, texture.as_ptr(), index);
728 })
729 }
730
731 pub fn set_sampler_state(
733 &mut self,
734 sampler: &SamplerState,
735 index: usize,
736 ) -> Result<(), CommandBufferError> {
737 validate_binding_index("sampler", index, MAX_SAMPLER_BINDINGS)?;
738 self.core
739 .with_active("set_sampler_state", |encoder| unsafe {
740 ffi::am_compute_command_encoder_set_sampler_state(encoder, sampler.as_ptr(), index);
741 })
742 }
743
744 pub fn set_visible_function_table(
746 &mut self,
747 table: &crate::VisibleFunctionTable,
748 index: usize,
749 ) -> Result<(), CommandBufferError> {
750 validate_binding_index("visible function table", index, MAX_BUFFER_BINDINGS)?;
751 self.core
752 .with_active("set_visible_function_table", |encoder| unsafe {
753 ffi::am_compute_command_encoder_set_visible_function_table(
754 encoder,
755 table.as_ptr(),
756 index,
757 );
758 })
759 }
760
761 pub fn set_intersection_function_table(
763 &mut self,
764 table: &crate::IntersectionFunctionTable,
765 index: usize,
766 ) -> Result<(), CommandBufferError> {
767 validate_binding_index("intersection function table", index, MAX_BUFFER_BINDINGS)?;
768 self.core
769 .with_active("set_intersection_function_table", |encoder| unsafe {
770 ffi::am_compute_command_encoder_set_intersection_function_table(
771 encoder,
772 table.as_ptr(),
773 index,
774 );
775 })
776 }
777
778 pub fn set_acceleration_structure(
780 &mut self,
781 acceleration_structure: &crate::AccelerationStructure,
782 index: usize,
783 ) -> Result<(), CommandBufferError> {
784 validate_binding_index("acceleration structure", index, MAX_BUFFER_BINDINGS)?;
785 self.core
786 .with_active("set_acceleration_structure", |encoder| unsafe {
787 ffi::am_compute_command_encoder_set_acceleration_structure(
788 encoder,
789 acceleration_structure.as_ptr(),
790 index,
791 );
792 })
793 }
794
795 pub fn dispatch_threadgroups(
797 &mut self,
798 threadgroups: (usize, usize, usize),
799 threads_per_threadgroup: (usize, usize, usize),
800 ) -> Result<(), CommandBufferError> {
801 validate_size(threadgroups, "threadgroup")?;
802 validate_size(threads_per_threadgroup, "threads-per-threadgroup")?;
803 self.core
804 .with_active("dispatch_threadgroups", |encoder| unsafe {
805 ffi::am_compute_command_encoder_dispatch_threadgroups(
806 encoder,
807 threadgroups.0,
808 threadgroups.1,
809 threadgroups.2,
810 threads_per_threadgroup.0,
811 threads_per_threadgroup.1,
812 threads_per_threadgroup.2,
813 );
814 })
815 }
816
817 pub fn dispatch_threads(
819 &mut self,
820 threads: (usize, usize, usize),
821 threads_per_threadgroup: (usize, usize, usize),
822 ) -> Result<(), CommandBufferError> {
823 validate_size(threads, "thread grid")?;
824 validate_size(threads_per_threadgroup, "threads-per-threadgroup")?;
825 self.core.with_active("dispatch_threads", |encoder| unsafe {
826 ffi::am_compute_command_encoder_dispatch_threads(
827 encoder,
828 threads.0,
829 threads.1,
830 threads.2,
831 threads_per_threadgroup.0,
832 threads_per_threadgroup.1,
833 threads_per_threadgroup.2,
834 );
835 })
836 }
837
838 pub fn update_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
840 self.core.with_active("update_fence", |encoder| unsafe {
841 ffi::am_compute_command_encoder_update_fence(encoder, fence.as_ptr());
842 })?;
843 self.core.record_fence_update(fence);
844 Ok(())
845 }
846
847 pub fn wait_for_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
849 self.core.ensure_fence_wait_allowed(fence)?;
850 self.core.with_active("wait_for_fence", |encoder| unsafe {
851 ffi::am_compute_command_encoder_wait_for_fence(encoder, fence.as_ptr());
852 })
853 }
854}
855
856impl RenderCommandEncoder {
857 pub fn set_render_pipeline_state(
859 &mut self,
860 pipeline: &RenderPipelineState,
861 ) -> Result<(), CommandBufferError> {
862 self.core
863 .with_active("set_render_pipeline_state", |encoder| unsafe {
864 ffi::am_render_command_encoder_set_render_pipeline_state(
865 encoder,
866 pipeline.as_ptr(),
867 );
868 })
869 }
870
871 pub fn set_vertex_buffer(
873 &mut self,
874 buffer: &MetalBuffer,
875 offset: usize,
876 index: usize,
877 ) -> Result<(), CommandBufferError> {
878 validate_binding_index("vertex buffer", index, MAX_BUFFER_BINDINGS)?;
879 checked_resource_range("vertex buffer", offset, 0, buffer.length())?;
880 ensure_native_int(offset, "vertex buffer offset")?;
881 self.core
882 .with_active("set_vertex_buffer", |encoder| unsafe {
883 ffi::am_render_command_encoder_set_vertex_buffer(
884 encoder,
885 buffer.as_ptr(),
886 offset,
887 index,
888 );
889 })
890 }
891
892 pub fn set_fragment_sampler_state(
894 &mut self,
895 sampler: &SamplerState,
896 index: usize,
897 ) -> Result<(), CommandBufferError> {
898 validate_binding_index("fragment sampler", index, MAX_SAMPLER_BINDINGS)?;
899 self.core
900 .with_active("set_fragment_sampler_state", |encoder| unsafe {
901 ffi::am_render_command_encoder_set_fragment_sampler_state(
902 encoder,
903 sampler.as_ptr(),
904 index,
905 );
906 })
907 }
908
909 pub fn set_depth_stencil_state(
911 &mut self,
912 state: &DepthStencilState,
913 ) -> Result<(), CommandBufferError> {
914 self.core
915 .with_active("set_depth_stencil_state", |encoder| unsafe {
916 ffi::am_render_command_encoder_set_depth_stencil_state(encoder, state.as_ptr());
917 })
918 }
919
920 pub fn draw_primitives(
922 &mut self,
923 primitive_type: usize,
924 vertex_start: usize,
925 vertex_count: usize,
926 ) -> Result<(), CommandBufferError> {
927 ensure_native_int(primitive_type, "primitive type")?;
928 ensure_native_int(vertex_start, "vertex start")?;
929 ensure_native_int(vertex_count, "vertex count")?;
930 vertex_start
931 .checked_add(vertex_count)
932 .filter(|end| isize::try_from(*end).is_ok())
933 .ok_or_else(|| CommandBufferError::IntegerOutOfRange {
934 field: "vertex range end",
935 value: vertex_start.saturating_add(vertex_count),
936 })?;
937 self.core.with_active("draw_primitives", |encoder| unsafe {
938 ffi::am_render_command_encoder_draw_primitives(
939 encoder,
940 primitive_type,
941 vertex_start,
942 vertex_count,
943 );
944 })
945 }
946
947 pub fn update_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
949 self.core.with_active("update_fence", |encoder| unsafe {
950 ffi::am_render_command_encoder_update_fence(encoder, fence.as_ptr());
951 })?;
952 self.core.record_fence_update(fence);
953 Ok(())
954 }
955
956 pub fn wait_for_fence(&mut self, fence: &Fence) -> Result<(), CommandBufferError> {
958 self.core.ensure_fence_wait_allowed(fence)?;
959 self.core.with_active("wait_for_fence", |encoder| unsafe {
960 ffi::am_render_command_encoder_wait_for_fence(encoder, fence.as_ptr());
961 })
962 }
963}
964
965fn ensure_recording(
966 phase: CommandBufferPhase,
967 operation: &'static str,
968) -> Result<(), CommandBufferError> {
969 if matches!(
970 phase,
971 CommandBufferPhase::Recording | CommandBufferPhase::Enqueued
972 ) {
973 Ok(())
974 } else {
975 Err(invalid_state(operation, phase))
976 }
977}
978
979fn invalid_state(operation: &'static str, phase: CommandBufferPhase) -> CommandBufferError {
980 CommandBufferError::InvalidState {
981 operation,
982 state: match phase {
983 CommandBufferPhase::Recording => "recording",
984 CommandBufferPhase::Enqueued => "enqueued",
985 CommandBufferPhase::Committed => "committed",
986 CommandBufferPhase::Completed => "completed",
987 CommandBufferPhase::Error => "failed",
988 },
989 }
990}
991
992fn checked_resource_range(
993 resource: &'static str,
994 offset: usize,
995 length: usize,
996 resource_length: usize,
997) -> Result<(), CommandBufferError> {
998 let end = offset
999 .checked_add(length)
1000 .ok_or(CommandBufferError::RangeOutOfBounds {
1001 resource,
1002 offset,
1003 length,
1004 resource_length,
1005 })?;
1006 if end > resource_length {
1007 Err(CommandBufferError::RangeOutOfBounds {
1008 resource,
1009 offset,
1010 length,
1011 resource_length,
1012 })
1013 } else {
1014 Ok(())
1015 }
1016}
1017
1018fn validate_binding_index(
1019 binding: &'static str,
1020 index: usize,
1021 limit: usize,
1022) -> Result<(), CommandBufferError> {
1023 if index < limit {
1024 Ok(())
1025 } else {
1026 Err(CommandBufferError::InvalidBindingIndex {
1027 binding,
1028 index,
1029 limit,
1030 })
1031 }
1032}
1033
1034fn ensure_native_int(value: usize, field: &'static str) -> Result<(), CommandBufferError> {
1035 if isize::try_from(value).is_ok() {
1036 Ok(())
1037 } else {
1038 Err(CommandBufferError::IntegerOutOfRange { field, value })
1039 }
1040}
1041
1042fn validate_size(
1043 size: (usize, usize, usize),
1044 field: &'static str,
1045) -> Result<(), CommandBufferError> {
1046 for (axis, value) in [("width", size.0), ("height", size.1), ("depth", size.2)] {
1047 if value == 0 {
1048 return Err(CommandBufferError::EmptyDispatch { field: axis });
1049 }
1050 ensure_native_int(value, field)?;
1051 }
1052 Ok(())
1053}
1054
1055fn synchronize_resource(
1056 core: &EncoderCore,
1057 resource: *mut c_void,
1058 resource_storage_mode: usize,
1059) -> Result<(), CommandBufferError> {
1060 if resource_storage_mode != storage_mode::MANAGED {
1061 return Err(CommandBufferError::ManagedStorageRequired {
1062 storage_mode: resource_storage_mode,
1063 });
1064 }
1065 let accepted = core.with_active("synchronize_resource", |encoder| unsafe {
1066 ffi::am_blit_command_encoder_synchronize_resource(encoder, resource)
1067 })?;
1068 if accepted {
1069 Ok(())
1070 } else {
1071 Err(CommandBufferError::NativeRejected {
1072 operation: "managed resource synchronization",
1073 })
1074 }
1075}