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(mut self, on_complete: impl FnOnce(Result<()>) + Send + 'static) -> Pending {
471 self.close();
472 let guards = self.guards.clone();
473 let finish = Arc::new(Finish {
474 completion: Mutex::new(Some(Completion {
475 on_complete: Box::new(on_complete),
476 own: self.own.clone(),
477 kernels: std::mem::take(&mut self.kernels),
478 unfenced: self.own.is_none(),
479 guards: std::mem::take(&mut self.guards),
480 _retained: std::mem::take(&mut self.retained),
481 device: self.device.clone(),
482 })),
483 result: Mutex::new(None),
484 done: Condvar::new(),
485 });
486 let shared = finish.clone();
487 let handler = RcBlock::new(move |raw: NonNull<ProtocolObject<dyn MTLCommandBuffer>>| {
488 let completed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
491 complete(&shared, unsafe { raw.as_ref() });
493 }));
494 if completed.is_err() {
495 eprintln!("nuvec-gpu: a command buffer's completion handler panicked");
496 }
497 });
498 autoreleasepool(|_| {
499 unsafe { self.raw.addCompletedHandler(RcBlock::as_ptr(&handler)) };
503 self.raw.commit();
504 });
505 let sequence = super::buffer::commit_sequence();
506 for (guard, write) in guards {
507 guard.committed_as(sequence);
508 guard.submitted(write);
509 if self.own.is_some() {
512 guard.committed();
513 }
514 }
515 Pending {
516 raw: self.raw.clone(),
517 finish,
518 }
519 }
520
521 fn conflicts(&self, args: &[Arg<'_>]) -> bool {
524 args.iter().filter_map(Arg::buffer).any(|(b, _, write)| {
525 let id = b.id();
526 self.writes.contains(&id) || (write && self.reads.contains(&id))
527 })
528 }
529
530 fn encoder(&mut self) -> Result<&Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>> {
531 if self.encoder.is_none() {
532 let encoder = self
533 .raw
534 .computeCommandEncoderWithDispatchType(MTLDispatchType::Concurrent)
535 .ok_or_else(|| GpuError::CommandBuffer("cannot create a compute encoder".into()))?;
536 if let Some((fence, true)) = &self.fence {
537 encoder.waitForFence(fence);
538 }
539 if std::mem::take(&mut self.after_wait) {
540 if let Some((fence, _)) = &self.fence {
541 encoder.waitForFence(fence);
542 }
543 if let Some(own) = &self.own {
544 encoder.waitForFence(own);
545 }
546 self.reads.clear();
547 self.writes.clear();
548 }
549 self.encoder = Some(encoder);
550 }
551 Ok(self.encoder.as_ref().expect("just created"))
552 }
553}
554
555impl Drop for CommandBuffer {
556 fn drop(&mut self) {
559 autoreleasepool(|_| {
560 if let Some(encoder) = self.encoder.take() {
561 encoder.endEncoding();
562 }
563 for (guard, write) in self.guards.drain(..) {
564 guard.committed();
565 guard.submitted(write);
566 guard.release(write, None); }
568 self.retained.clear();
569 });
570 }
571}
572
573fn size([width, height, depth]: [usize; 3]) -> MTLSize {
574 MTLSize {
575 width,
576 height,
577 depth,
578 }
579}
580
581fn validate(args: &[Arg<'_>]) -> Result<()> {
582 if args.len() > MAX_ARGS {
583 return Err(GpuError::InvalidArgument(format!(
584 "{} arguments; Metal allows {MAX_ARGS}",
585 args.len()
586 )));
587 }
588 for (index, arg) in args.iter().enumerate() {
589 match arg {
590 Arg::Bytes(bytes) if bytes.len() > MAX_INLINE_BYTES => {
591 return Err(GpuError::InvalidArgument(format!(
592 "argument {index}: {} inline bytes; Metal allows {MAX_INLINE_BYTES}",
593 bytes.len()
594 )));
595 }
596 Arg::Write(b, _) if b.is_mapped() => return Err(GpuError::ReadOnly),
597 Arg::Same(earlier, offset) => match args.get(*earlier) {
598 Some(Arg::Write(b, _)) if *offset <= b.len() => {}
599 _ => {
600 return Err(GpuError::InvalidArgument(format!(
601 "argument {index}: `Same({earlier}, {offset})` needs a written \
602 (`Arg::Write`) argument and an offset within it"
603 )));
604 }
605 },
606 Arg::Read(..) | Arg::Write(..)
607 if arg.buffer().is_some_and(|(b, offset, _)| offset > b.len()) =>
608 {
609 let (b, offset, _) = arg.buffer().expect("buffer argument");
610 return Err(GpuError::InvalidArgument(format!(
611 "argument {index}: offset {offset} is past the end of a {}-byte buffer",
612 b.len()
613 )));
614 }
615 _ => {}
616 }
617 }
618 Ok(())
619}
620
621struct Completion {
624 on_complete: Box<dyn FnOnce(Result<()>) + Send>,
625 own: Option<Fence>,
627 kernels: Vec<Arc<str>>,
629 guards: Vec<(Arc<Guard>, bool)>,
630 unfenced: bool,
632 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
633 device: Device,
634}
635
636unsafe impl Send for Completion {}
640
641struct Finish {
644 completion: Mutex<Option<Completion>>,
645 result: Mutex<Option<Result<()>>>,
647 done: Condvar,
648}
649
650impl Finish {
651 fn wait(&self) -> Result<()> {
653 let mut result = self.result.lock().unwrap_or_else(PoisonError::into_inner);
654 loop {
655 if let Some(r) = &*result {
656 return r.clone();
657 }
658 result = self
659 .done
660 .wait(result)
661 .unwrap_or_else(PoisonError::into_inner);
662 }
663 }
664}
665
666struct Publish<'a>(&'a Finish, Option<Result<()>>);
668
669impl Drop for Publish<'_> {
670 fn drop(&mut self) {
671 let mut result = self.0.result.lock().unwrap_or_else(PoisonError::into_inner);
672 *result = self.1.take();
673 self.0.done.notify_all();
674 }
675}
676
677fn complete(finish: &Finish, raw: &ProtocolObject<dyn MTLCommandBuffer>) {
681 let Some(c) = finish
682 .completion
683 .lock()
684 .unwrap_or_else(PoisonError::into_inner)
685 .take()
686 else {
687 return;
688 };
689 let result = status(raw);
690 let _publish = Publish(finish, Some(result.clone()));
691 super::profile::record(&c.kernels, raw.GPUEndTime() - raw.GPUStartTime());
692 for (guard, write) in c.guards {
693 if c.unfenced {
694 guard.committed();
695 }
696 guard.release(write, c.own.as_ref());
697 }
698 drop(c._retained);
699 c.device.flights().end();
700 (c.on_complete)(result);
701}
702
703#[must_use = "dropping `Pending` does not wait; keep it to `wait`, or drop it explicitly"]
707#[derive(Clone)]
708pub struct Pending {
709 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
710 finish: Arc<Finish>,
711}
712
713unsafe impl Send for Pending {}
716unsafe impl Sync for Pending {}
718
719impl Pending {
720 pub fn wait(&self) {
723 let _ = self.result();
724 }
725
726 pub fn wait_result(&self) -> Result<()> {
728 self.result()
729 }
730
731 fn result(&self) -> Result<()> {
732 autoreleasepool(|_| {
733 self.raw.waitUntilCompleted();
734 complete(&self.finish, &self.raw);
735 });
736 self.finish.wait()
737 }
738}
739
740impl std::fmt::Debug for Pending {
741 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
742 f.debug_struct("Pending").finish_non_exhaustive()
743 }
744}
745
746#[must_use = "dropping `Submitted` waits for the GPU and discards its errors; call `wait`"]
749pub struct Submitted {
750 pending: Pending,
751 waited: bool,
752}
753
754impl Submitted {
755 pub fn wait(mut self) -> Result<()> {
758 self.waited = true;
759 self.pending.result()
760 }
761}
762
763impl std::fmt::Debug for Submitted {
764 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
765 f.debug_struct("Submitted").finish_non_exhaustive()
766 }
767}
768
769impl Drop for Submitted {
770 fn drop(&mut self) {
771 if !self.waited {
772 self.pending.wait();
773 }
774 }
775}