1use std::collections::HashMap;
4use std::ptr::NonNull;
5use std::sync::{Arc, Condvar, Mutex, PoisonError};
6
7use block2::RcBlock;
8use objc2::rc::{Retained, autoreleasepool};
9use objc2::runtime::ProtocolObject;
10use objc2_metal::{
11 MTLBarrierScope, MTLBuffer, MTLCommandBuffer, MTLCommandBufferStatus, MTLCommandEncoder,
12 MTLCommandQueue, MTLComputeCommandEncoder, MTLDevice, MTLDispatchType, MTLFence, MTLSize,
13};
14
15use super::buffer::Guard;
16use super::{Buffer, Device, Event, Pipeline};
17use crate::{GpuError, Result};
18
19const MAX_ARGS: usize = 31;
21const MAX_INLINE_BYTES: usize = 4096;
23
24#[derive(Debug)]
26pub enum Arg<'a> {
27 Read(&'a Buffer, usize),
29 Write(&'a mut Buffer, usize),
31 Bytes(&'a [u8]),
33 Same(usize, usize),
38}
39
40impl<'a> Arg<'a> {
41 pub fn read(buffer: &'a Buffer) -> Self {
43 Self::Read(buffer, 0)
44 }
45
46 pub fn write(buffer: &'a mut Buffer) -> Self {
48 Self::Write(buffer, 0)
49 }
50
51 pub fn bytes(bytes: &'a [u8]) -> Self {
53 Self::Bytes(bytes)
54 }
55
56 fn buffer(&self) -> Option<(&Buffer, usize, bool)> {
57 match self {
58 Self::Read(b, offset) => Some((b, *offset, false)),
59 Self::Write(b, offset) => Some((b, *offset, true)),
60 Self::Bytes(_) | Self::Same(..) => None,
61 }
62 }
63}
64
65pub(crate) type Fence = Retained<ProtocolObject<dyn MTLFence>>;
66
67pub struct Timeline {
76 fence: Fence,
77 started: bool,
78}
79
80impl std::fmt::Debug for Timeline {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 f.debug_struct("Timeline").finish_non_exhaustive()
83 }
84}
85
86pub struct CommandBuffer {
89 device: Device,
90 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
91 fence: Option<(Fence, bool)>,
93 own: Option<Fence>,
96 waited: Vec<Fence>,
98 encoder: Option<Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>>,
99 reads: Vec<usize>,
101 writes: Vec<usize>,
102 barriers: usize,
103 guards: Vec<(Arc<Guard>, bool)>,
105 uses: HashMap<usize, (usize, usize)>,
107 retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
108 after_wait: bool,
110 kernels: Vec<Arc<str>>,
112}
113
114impl Device {
115 pub fn command_buffer(&self) -> Result<CommandBuffer> {
121 let raw = autoreleasepool(|_| self.queue().commandBuffer())
122 .ok_or_else(|| GpuError::CommandBuffer("cannot create a command buffer".into()))?;
123 Ok(CommandBuffer {
124 device: self.clone(),
125 raw,
126 fence: None,
127 own: None,
128 waited: Vec::new(),
129 encoder: None,
130 reads: Vec::new(),
131 writes: Vec::new(),
132 barriers: 0,
133 guards: Vec::new(),
134 uses: HashMap::new(),
135 retained: Vec::new(),
136 after_wait: false,
137 kernels: Vec::new(),
138 })
139 }
140}
141
142impl Device {
143 pub fn timeline(&self) -> Result<Timeline> {
145 let fence = autoreleasepool(|_| self.raw().newFence())
146 .ok_or_else(|| GpuError::CommandBuffer("cannot create a fence".into()))?;
147 Ok(Timeline {
148 fence,
149 started: false,
150 })
151 }
152
153 pub fn ordered_command_buffer(&self) -> Result<CommandBuffer> {
162 let fence = autoreleasepool(|_| self.raw().newFence())
163 .ok_or_else(|| GpuError::CommandBuffer("cannot create a fence".into()))?;
164 let mut cmd = self.command_buffer()?;
165 cmd.own = Some(fence);
166 Ok(cmd)
167 }
168
169 pub fn command_buffer_on(&self, timeline: &mut Timeline) -> Result<CommandBuffer> {
174 let mut cmd = self.command_buffer()?;
175 cmd.fence = Some((timeline.fence.clone(), timeline.started));
176 timeline.started = true;
177 Ok(cmd)
178 }
179}
180
181fn status(raw: &ProtocolObject<dyn MTLCommandBuffer>) -> Result<()> {
183 if raw.status() == MTLCommandBufferStatus::Error {
184 let message = raw.error().map_or_else(
185 || "unknown error".to_owned(),
186 |e| e.localizedDescription().to_string(),
187 );
188 return Err(GpuError::CommandBuffer(message));
189 }
190 Ok(())
191}
192
193impl CommandBuffer {
194 pub unsafe fn dispatch(
208 &mut self,
209 pipeline: &Pipeline,
210 args: &[Arg<'_>],
211 threads: usize,
212 ) -> Result<()> {
213 if threads == 0 {
214 return self.check(args);
215 }
216 let width = pipeline.execution_width().max(1);
217 let mut group = pipeline.max_threads_per_threadgroup().min(threads).max(1);
218 if group > width {
219 group -= group % width;
220 }
221 self.encode(pipeline, args, [threads, 1, 1], |encoder| {
222 encoder
223 .dispatchThreads_threadsPerThreadgroup(size([threads, 1, 1]), size([group, 1, 1]));
224 })
225 }
226
227 pub unsafe fn dispatch_groups(
235 &mut self,
236 pipeline: &Pipeline,
237 args: &[Arg<'_>],
238 groups: [usize; 3],
239 threads: [usize; 3],
240 ) -> Result<()> {
241 let per_group: usize = threads.iter().product();
242 if per_group > pipeline.max_threads_per_threadgroup() {
243 return Err(GpuError::InvalidArgument(format!(
244 "{per_group} threads per threadgroup; `{}` allows {}",
245 pipeline.name(),
246 pipeline.max_threads_per_threadgroup()
247 )));
248 }
249 if groups.contains(&0) || per_group == 0 {
250 return self.check(args);
251 }
252 let extent = [0, 1, 2].map(|d| groups[d] * threads[d]);
253 self.encode(pipeline, args, extent, |encoder| {
254 encoder.dispatchThreadgroups_threadsPerThreadgroup(size(groups), size(threads));
255 })
256 }
257
258 pub unsafe fn dispatch_threads(
266 &mut self,
267 pipeline: &Pipeline,
268 args: &[Arg<'_>],
269 grid: [usize; 3],
270 group: [usize; 3],
271 ) -> Result<()> {
272 let per_group: usize = group.iter().product();
273 if per_group > pipeline.max_threads_per_threadgroup() {
274 return Err(GpuError::InvalidArgument(format!(
275 "{per_group} threads per threadgroup; `{}` allows {}",
276 pipeline.name(),
277 pipeline.max_threads_per_threadgroup()
278 )));
279 }
280 if grid.contains(&0) || per_group == 0 {
281 return self.check(args);
282 }
283 self.encode(pipeline, args, grid, |encoder| {
284 encoder.dispatchThreads_threadsPerThreadgroup(size(grid), size(group));
285 })
286 }
287
288 fn encode(
293 &mut self,
294 pipeline: &Pipeline,
295 args: &[Arg<'_>],
296 extent: [usize; 3],
297 dispatch: impl FnOnce(&ProtocolObject<dyn MTLComputeCommandEncoder>),
298 ) -> Result<()> {
299 autoreleasepool(|_| self.encode_now(pipeline, args, extent, dispatch))
300 }
301
302 fn encode_now(
303 &mut self,
304 pipeline: &Pipeline,
305 args: &[Arg<'_>],
306 extent: [usize; 3],
307 dispatch: impl FnOnce(&ProtocolObject<dyn MTLComputeCommandEncoder>),
308 ) -> Result<()> {
309 self.check(args)?;
310 if super::profile::enabled() {
311 self.kernels.push(match super::profile::shapes() {
312 true => format!("{} {extent:?}", pipeline.name()).into(),
313 false => pipeline.name.clone(),
314 });
315 }
316 if self.conflicts(args) {
317 self.encoder()?
318 .memoryBarrierWithScope(MTLBarrierScope::Buffers);
319 self.barriers += 1;
320 self.reads.clear();
321 self.writes.clear();
322 }
323 let encoder = self.encoder()?.clone();
324 if let Some(own) = &self.own {
325 let own = Retained::as_ptr(own) as usize;
326 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
327 for fence in buffer.pending_fences(write) {
328 let key = Retained::as_ptr(&fence) as usize;
329 let seen = self
330 .waited
331 .iter()
332 .any(|f| Retained::as_ptr(f) as usize == key);
333 if key != own && !seen {
334 encoder.waitForFence(&fence);
335 self.waited.push(fence);
336 }
337 }
338 }
339 }
340 encoder.setComputePipelineState(pipeline.raw());
341 for (index, arg) in args.iter().enumerate() {
342 match arg {
343 Arg::Bytes(bytes) => {
344 let data = NonNull::new(bytes.as_ptr().cast_mut())
345 .unwrap_or(NonNull::dangling())
346 .cast();
347 unsafe { encoder.setBytes_length_atIndex(data, bytes.len(), index) };
351 }
352 Arg::Read(..) | Arg::Write(..) => {
353 let (buffer, offset, _) = arg.buffer().expect("buffer argument");
354 unsafe { encoder.setBuffer_offset_atIndex(buffer.raw(), offset, index) };
358 }
359 Arg::Same(earlier, offset) => {
360 let (buffer, _, _) = args[*earlier].buffer().expect("validated");
361 unsafe { encoder.setBuffer_offset_atIndex(buffer.raw(), *offset, index) };
364 }
365 }
366 }
367 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
368 (if write {
369 &mut self.writes
370 } else {
371 &mut self.reads
372 })
373 .push(buffer.id());
374 let uses = self.uses.entry(buffer.id()).or_default();
375 uses.0 += 1;
376 uses.1 += usize::from(write);
377 let guard = buffer.guard();
378 guard.acquire(write);
379 self.guards.push((guard, write));
380 self.retained.extend(buffer.retained());
381 }
382 dispatch(&encoder);
383 Ok(())
384 }
385
386 fn check(&self, args: &[Arg<'_>]) -> Result<()> {
392 validate(args)?;
393 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
394 if self.own.is_none() && buffer.previous_busy() {
395 return Err(GpuError::BufferInUse);
396 }
397 let (uses, writes) = buffer.unsubmitted();
398 let (own_uses, own_writes) = self.uses.get(&buffer.id()).copied().unwrap_or_default();
399 if writes > own_writes || (write && uses > own_uses) {
400 return Err(GpuError::BufferInUse);
401 }
402 }
403 Ok(())
404 }
405
406 pub fn barriers(&self) -> usize {
408 self.barriers
409 }
410
411 fn close(&mut self) {
413 autoreleasepool(|_| self.end_encoding());
414 self.device.flights().begin();
415 }
416
417 fn end_encoding(&mut self) {
418 if let Some(encoder) = self.encoder.take() {
419 if let Some((fence, _)) = &self.fence {
420 encoder.updateFence(fence);
421 }
422 if let Some(own) = &self.own {
423 encoder.updateFence(own);
424 for (guard, write) in &self.guards {
425 guard.record(own, *write);
426 }
427 }
428 encoder.endEncoding();
429 }
430 }
431
432 pub fn signal_event(&mut self, event: &Event, value: u64) {
435 autoreleasepool(|_| self.end_encoding());
436 self.raw
437 .encodeSignalEvent_value(ProtocolObject::from_ref(event.raw()), value);
438 }
439
440 pub fn wait_event(&mut self, event: &Event, value: u64) {
443 autoreleasepool(|_| self.end_encoding());
444 self.raw
445 .encodeWaitForEvent_value(ProtocolObject::from_ref(event.raw()), value);
446 self.after_wait = true;
448 }
449
450 #[must_use = "dropping `Submitted` waits for the GPU and discards its errors; call `wait`"]
453 pub fn commit(self) -> Submitted {
454 Submitted {
455 pending: self.commit_then(|_| {}),
456 waited: false,
457 }
458 }
459
460 #[must_use = "dropping `Pending` does not wait; keep it to `wait`, or drop it explicitly"]
470 pub fn commit_then(self, on_complete: impl FnOnce(Result<()>) + Send + 'static) -> Pending {
471 self.submit(on_complete, None)
472 }
473
474 #[must_use = "dropping `Deferred` commits at once"]
481 pub fn enqueue_then(
482 self,
483 on_complete: impl FnOnce(Result<()>) + Send + 'static,
484 ) -> (Pending, Deferred) {
485 let gate = Arc::new(Gate::default());
486 let pending = self.submit(on_complete, Some(gate.clone()));
487 let deferred = Deferred {
488 raw: pending.raw.clone(),
489 gate,
490 };
491 (pending, deferred)
492 }
493
494 fn submit(
495 mut self,
496 on_complete: impl FnOnce(Result<()>) + Send + 'static,
497 gate: Option<Arc<Gate>>,
498 ) -> Pending {
499 self.close();
500 let guards = self.guards.clone();
501 let finish = Arc::new(Finish {
502 completion: Mutex::new(Some(Completion {
503 on_complete: Box::new(on_complete),
504 own: self.own.clone(),
505 kernels: std::mem::take(&mut self.kernels),
506 unfenced: self.own.is_none(),
507 guards: std::mem::take(&mut self.guards),
508 _retained: std::mem::take(&mut self.retained),
509 device: self.device.clone(),
510 })),
511 result: Mutex::new(None),
512 done: Condvar::new(),
513 });
514 let shared = finish.clone();
515 let handler = RcBlock::new(move |raw: NonNull<ProtocolObject<dyn MTLCommandBuffer>>| {
516 let completed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
519 complete(&shared, unsafe { raw.as_ref() });
521 }));
522 if completed.is_err() {
523 eprintln!("nuvec-gpu: a command buffer's completion handler panicked");
524 }
525 });
526 autoreleasepool(|_| {
527 unsafe { self.raw.addCompletedHandler(RcBlock::as_ptr(&handler)) };
531 match gate {
532 Some(_) => self.raw.enqueue(),
533 None => self.raw.commit(),
534 }
535 });
536 let sequence = super::buffer::commit_sequence();
537 for (guard, write) in guards {
538 guard.committed_as(sequence);
539 guard.submitted(write);
540 if self.own.is_some() {
543 guard.committed();
544 }
545 }
546 Pending {
547 raw: self.raw.clone(),
548 finish,
549 gate,
550 }
551 }
552
553 fn conflicts(&self, args: &[Arg<'_>]) -> bool {
556 args.iter().filter_map(Arg::buffer).any(|(b, _, write)| {
557 let id = b.id();
558 self.writes.contains(&id) || (write && self.reads.contains(&id))
559 })
560 }
561
562 fn encoder(&mut self) -> Result<&Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>> {
563 if self.encoder.is_none() {
564 let encoder = self
565 .raw
566 .computeCommandEncoderWithDispatchType(MTLDispatchType::Concurrent)
567 .ok_or_else(|| GpuError::CommandBuffer("cannot create a compute encoder".into()))?;
568 if let Some((fence, true)) = &self.fence {
569 encoder.waitForFence(fence);
570 }
571 if std::mem::take(&mut self.after_wait) {
572 if let Some((fence, _)) = &self.fence {
573 encoder.waitForFence(fence);
574 }
575 if let Some(own) = &self.own {
576 encoder.waitForFence(own);
577 }
578 self.reads.clear();
579 self.writes.clear();
580 }
581 self.encoder = Some(encoder);
582 }
583 Ok(self.encoder.as_ref().expect("just created"))
584 }
585}
586
587impl Drop for CommandBuffer {
588 fn drop(&mut self) {
591 autoreleasepool(|_| {
592 if let Some(encoder) = self.encoder.take() {
593 encoder.endEncoding();
594 }
595 for (guard, write) in self.guards.drain(..) {
596 guard.committed();
597 guard.submitted(write);
598 guard.release(write, None); }
600 self.retained.clear();
601 });
602 }
603}
604
605fn size([width, height, depth]: [usize; 3]) -> MTLSize {
606 MTLSize {
607 width,
608 height,
609 depth,
610 }
611}
612
613fn validate(args: &[Arg<'_>]) -> Result<()> {
614 if args.len() > MAX_ARGS {
615 return Err(GpuError::InvalidArgument(format!(
616 "{} arguments; Metal allows {MAX_ARGS}",
617 args.len()
618 )));
619 }
620 for (index, arg) in args.iter().enumerate() {
621 match arg {
622 Arg::Bytes(bytes) if bytes.len() > MAX_INLINE_BYTES => {
623 return Err(GpuError::InvalidArgument(format!(
624 "argument {index}: {} inline bytes; Metal allows {MAX_INLINE_BYTES}",
625 bytes.len()
626 )));
627 }
628 Arg::Write(b, _) if b.is_mapped() => return Err(GpuError::ReadOnly),
629 Arg::Same(earlier, offset) => match args.get(*earlier) {
630 Some(Arg::Write(b, _)) if *offset <= b.len() => {}
631 _ => {
632 return Err(GpuError::InvalidArgument(format!(
633 "argument {index}: `Same({earlier}, {offset})` needs a written \
634 (`Arg::Write`) argument and an offset within it"
635 )));
636 }
637 },
638 Arg::Read(..) | Arg::Write(..)
639 if arg.buffer().is_some_and(|(b, offset, _)| offset > b.len()) =>
640 {
641 let (b, offset, _) = arg.buffer().expect("buffer argument");
642 return Err(GpuError::InvalidArgument(format!(
643 "argument {index}: offset {offset} is past the end of a {}-byte buffer",
644 b.len()
645 )));
646 }
647 _ => {}
648 }
649 }
650 Ok(())
651}
652
653struct Completion {
656 on_complete: Box<dyn FnOnce(Result<()>) + Send>,
657 own: Option<Fence>,
659 kernels: Vec<Arc<str>>,
661 guards: Vec<(Arc<Guard>, bool)>,
662 unfenced: bool,
664 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
665 device: Device,
666}
667
668unsafe impl Send for Completion {}
672
673struct Finish {
676 completion: Mutex<Option<Completion>>,
677 result: Mutex<Option<Result<()>>>,
679 done: Condvar,
680}
681
682impl Finish {
683 fn wait(&self) -> Result<()> {
685 let mut result = self.result.lock().unwrap_or_else(PoisonError::into_inner);
686 loop {
687 if let Some(r) = &*result {
688 return r.clone();
689 }
690 result = self
691 .done
692 .wait(result)
693 .unwrap_or_else(PoisonError::into_inner);
694 }
695 }
696}
697
698struct Publish<'a>(&'a Finish, Option<Result<()>>);
700
701impl Drop for Publish<'_> {
702 fn drop(&mut self) {
703 let mut result = self.0.result.lock().unwrap_or_else(PoisonError::into_inner);
704 *result = self.1.take();
705 self.0.done.notify_all();
706 }
707}
708
709fn complete(finish: &Finish, raw: &ProtocolObject<dyn MTLCommandBuffer>) {
713 let Some(c) = finish
714 .completion
715 .lock()
716 .unwrap_or_else(PoisonError::into_inner)
717 .take()
718 else {
719 return;
720 };
721 let result = status(raw);
722 let _publish = Publish(finish, Some(result.clone()));
723 super::profile::record(&c.kernels, raw.GPUEndTime() - raw.GPUStartTime());
724 for (guard, write) in c.guards {
725 if c.unfenced {
726 guard.committed();
727 }
728 guard.release(write, c.own.as_ref());
729 }
730 drop(c._retained);
731 c.device.flights().end();
732 (c.on_complete)(result);
733}
734
735#[must_use = "dropping `Pending` does not wait; keep it to `wait`, or drop it explicitly"]
739#[derive(Clone)]
740pub struct Pending {
741 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
742 finish: Arc<Finish>,
743 gate: Option<Arc<Gate>>,
745}
746
747unsafe impl Send for Pending {}
750unsafe impl Sync for Pending {}
752
753impl Pending {
754 pub fn wait(&self) {
757 let _ = self.result();
758 }
759
760 pub fn wait_result(&self) -> Result<()> {
762 self.result()
763 }
764
765 fn result(&self) -> Result<()> {
766 if let Some(gate) = &self.gate {
767 gate.wait();
768 }
769 autoreleasepool(|_| {
770 self.raw.waitUntilCompleted();
771 complete(&self.finish, &self.raw);
772 });
773 self.finish.wait()
774 }
775}
776
777impl std::fmt::Debug for Pending {
778 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
779 f.debug_struct("Pending").finish_non_exhaustive()
780 }
781}
782
783#[derive(Default)]
785struct Gate {
786 committed: Mutex<bool>,
787 changed: Condvar,
788}
789
790impl Gate {
791 fn wait(&self) {
792 let mut committed = self
793 .committed
794 .lock()
795 .unwrap_or_else(PoisonError::into_inner);
796 while !*committed {
797 committed = self
798 .changed
799 .wait(committed)
800 .unwrap_or_else(PoisonError::into_inner);
801 }
802 }
803}
804
805pub struct Deferred {
809 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
810 gate: Arc<Gate>,
811}
812
813unsafe impl Send for Deferred {}
816
817impl Deferred {
818 pub fn commit(self) {}
820}
821
822impl Drop for Deferred {
823 fn drop(&mut self) {
824 autoreleasepool(|_| self.raw.commit());
825 *self
826 .gate
827 .committed
828 .lock()
829 .unwrap_or_else(PoisonError::into_inner) = true;
830 self.gate.changed.notify_all();
831 }
832}
833
834impl std::fmt::Debug for Deferred {
835 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
836 f.debug_struct("Deferred").finish_non_exhaustive()
837 }
838}
839
840#[must_use = "dropping `Submitted` waits for the GPU and discards its errors; call `wait`"]
843pub struct Submitted {
844 pending: Pending,
845 waited: bool,
846}
847
848impl Submitted {
849 pub fn wait(mut self) -> Result<()> {
852 self.waited = true;
853 self.pending.result()
854 }
855}
856
857impl std::fmt::Debug for Submitted {
858 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
859 f.debug_struct("Submitted").finish_non_exhaustive()
860 }
861}
862
863impl Drop for Submitted {
864 fn drop(&mut self) {
865 if !self.waited {
866 self.pending.wait();
867 }
868 }
869}