1use std::ptr::NonNull;
4
5use std::sync::{Arc, Mutex, PoisonError};
6
7use block2::RcBlock;
8use objc2::rc::Retained;
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, 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),
37}
38
39impl<'a> Arg<'a> {
40 pub fn read(buffer: &'a Buffer) -> Self {
42 Self::Read(buffer, 0)
43 }
44
45 pub fn write(buffer: &'a mut Buffer) -> Self {
47 Self::Write(buffer, 0)
48 }
49
50 pub fn bytes(bytes: &'a [u8]) -> Self {
52 Self::Bytes(bytes)
53 }
54
55 fn buffer(&self) -> Option<(&Buffer, usize, bool)> {
56 match self {
57 Self::Read(b, offset) => Some((b, *offset, false)),
58 Self::Write(b, offset) => Some((b, *offset, true)),
59 Self::Bytes(_) | Self::Same(..) => None,
60 }
61 }
62}
63
64pub(crate) type Fence = Retained<ProtocolObject<dyn MTLFence>>;
65
66pub struct Timeline {
75 fence: Fence,
76 started: bool,
77}
78
79impl std::fmt::Debug for Timeline {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("Timeline").finish_non_exhaustive()
82 }
83}
84
85pub struct CommandBuffer {
88 device: Device,
89 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
90 fence: Option<(Fence, bool)>,
92 own: Option<Fence>,
95 waited: Vec<usize>,
96 encoder: Option<Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>>,
97 reads: Vec<usize>,
99 writes: Vec<usize>,
100 barriers: usize,
101 guards: Vec<(Arc<Guard>, bool)>,
103 retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
104 kernels: Vec<Arc<str>>,
106}
107
108impl Device {
109 pub fn command_buffer(&self) -> Result<CommandBuffer> {
111 let raw = self
112 .queue()
113 .commandBuffer()
114 .ok_or_else(|| GpuError::CommandBuffer("cannot create a command buffer".into()))?;
115 Ok(CommandBuffer {
116 device: self.clone(),
117 raw,
118 fence: None,
119 own: None,
120 waited: Vec::new(),
121 encoder: None,
122 reads: Vec::new(),
123 writes: Vec::new(),
124 barriers: 0,
125 guards: Vec::new(),
126 retained: Vec::new(),
127 kernels: Vec::new(),
128 })
129 }
130}
131
132impl Device {
133 pub fn timeline(&self) -> Result<Timeline> {
135 let fence = self
136 .raw()
137 .newFence()
138 .ok_or_else(|| GpuError::CommandBuffer("cannot create a fence".into()))?;
139 Ok(Timeline {
140 fence,
141 started: false,
142 })
143 }
144
145 pub fn ordered_command_buffer(&self) -> Result<CommandBuffer> {
154 let fence = self
155 .raw()
156 .newFence()
157 .ok_or_else(|| GpuError::CommandBuffer("cannot create a fence".into()))?;
158 let mut cmd = self.command_buffer()?;
159 cmd.own = Some(fence);
160 Ok(cmd)
161 }
162
163 pub fn command_buffer_on(&self, timeline: &mut Timeline) -> Result<CommandBuffer> {
166 let mut cmd = self.command_buffer()?;
167 cmd.fence = Some((timeline.fence.clone(), timeline.started));
168 timeline.started = true;
169 Ok(cmd)
170 }
171}
172
173fn status(raw: &ProtocolObject<dyn MTLCommandBuffer>) -> Result<()> {
175 if raw.status() == MTLCommandBufferStatus::Error {
176 let message = raw.error().map_or_else(
177 || "unknown error".to_owned(),
178 |e| e.localizedDescription().to_string(),
179 );
180 return Err(GpuError::CommandBuffer(message));
181 }
182 Ok(())
183}
184
185impl CommandBuffer {
186 pub fn dispatch(
189 &mut self,
190 pipeline: &Pipeline,
191 args: &[Arg<'_>],
192 threads: usize,
193 ) -> Result<()> {
194 if threads == 0 {
195 return validate(args);
196 }
197 let width = pipeline.execution_width().max(1);
198 let mut group = pipeline.max_threads_per_threadgroup().min(threads).max(1);
199 if group > width {
200 group -= group % width;
201 }
202 self.encode(pipeline, args, [threads, 1, 1], |encoder| {
203 encoder
204 .dispatchThreads_threadsPerThreadgroup(size([threads, 1, 1]), size([group, 1, 1]));
205 })
206 }
207
208 pub fn dispatch_groups(
211 &mut self,
212 pipeline: &Pipeline,
213 args: &[Arg<'_>],
214 groups: [usize; 3],
215 threads: [usize; 3],
216 ) -> Result<()> {
217 let per_group: usize = threads.iter().product();
218 if per_group > pipeline.max_threads_per_threadgroup() {
219 return Err(GpuError::InvalidArgument(format!(
220 "{per_group} threads per threadgroup; `{}` allows {}",
221 pipeline.name(),
222 pipeline.max_threads_per_threadgroup()
223 )));
224 }
225 if groups.contains(&0) || per_group == 0 {
226 return validate(args);
227 }
228 let extent = [0, 1, 2].map(|d| groups[d] * threads[d]);
229 self.encode(pipeline, args, extent, |encoder| {
230 encoder.dispatchThreadgroups_threadsPerThreadgroup(size(groups), size(threads));
231 })
232 }
233
234 pub fn dispatch_threads(
237 &mut self,
238 pipeline: &Pipeline,
239 args: &[Arg<'_>],
240 grid: [usize; 3],
241 group: [usize; 3],
242 ) -> Result<()> {
243 let per_group: usize = group.iter().product();
244 if per_group > pipeline.max_threads_per_threadgroup() {
245 return Err(GpuError::InvalidArgument(format!(
246 "{per_group} threads per threadgroup; `{}` allows {}",
247 pipeline.name(),
248 pipeline.max_threads_per_threadgroup()
249 )));
250 }
251 if grid.contains(&0) || per_group == 0 {
252 return validate(args);
253 }
254 self.encode(pipeline, args, grid, |encoder| {
255 encoder.dispatchThreads_threadsPerThreadgroup(size(grid), size(group));
256 })
257 }
258
259 fn encode(
262 &mut self,
263 pipeline: &Pipeline,
264 args: &[Arg<'_>],
265 extent: [usize; 3],
266 dispatch: impl FnOnce(&ProtocolObject<dyn MTLComputeCommandEncoder>),
267 ) -> Result<()> {
268 if super::profile::enabled() {
269 self.kernels.push(match super::profile::shapes() {
270 true => format!("{} {extent:?}", pipeline.name()).into(),
271 false => pipeline.name.clone(),
272 });
273 }
274 validate(args)?;
275 if self.conflicts(args) {
276 self.encoder()?
277 .memoryBarrierWithScope(MTLBarrierScope::Buffers);
278 self.barriers += 1;
279 self.reads.clear();
280 self.writes.clear();
281 }
282 let encoder = self.encoder()?.clone();
283 if let Some(own) = &self.own {
284 let own = Retained::as_ptr(own) as usize;
285 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
286 for fence in buffer.pending_fences(write) {
287 let key = Retained::as_ptr(&fence) as usize;
288 if key != own && !self.waited.contains(&key) {
289 encoder.waitForFence(&fence);
290 self.waited.push(key);
291 }
292 }
293 }
294 }
295 encoder.setComputePipelineState(pipeline.raw());
296 for (index, arg) in args.iter().enumerate() {
297 match arg {
298 Arg::Bytes(bytes) => {
299 let data = NonNull::new(bytes.as_ptr().cast_mut())
300 .unwrap_or(NonNull::dangling())
301 .cast();
302 unsafe { encoder.setBytes_length_atIndex(data, bytes.len(), index) };
306 }
307 Arg::Read(..) | Arg::Write(..) => {
308 let (buffer, offset, _) = arg.buffer().expect("buffer argument");
309 unsafe { encoder.setBuffer_offset_atIndex(buffer.raw(), offset, index) };
313 }
314 Arg::Same(earlier, offset) => {
315 let (buffer, _, _) = args[*earlier].buffer().expect("validated");
316 unsafe { encoder.setBuffer_offset_atIndex(buffer.raw(), *offset, index) };
319 }
320 }
321 }
322 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
323 (if write {
324 &mut self.writes
325 } else {
326 &mut self.reads
327 })
328 .push(buffer.id());
329 let guard = buffer.guard();
330 guard.acquire(write);
331 self.guards.push((guard, write));
332 self.retained.extend(buffer.retained());
333 }
334 dispatch(&encoder);
335 Ok(())
336 }
337
338 pub fn barriers(&self) -> usize {
340 self.barriers
341 }
342
343 fn close(&mut self) {
345 if let Some(encoder) = self.encoder.take() {
346 if let Some((fence, _)) = &self.fence {
347 encoder.updateFence(fence);
348 }
349 if let Some(own) = &self.own {
350 encoder.updateFence(own);
351 for (guard, write) in &self.guards {
352 guard.record(own, *write);
353 }
354 }
355 encoder.endEncoding();
356 }
357 self.device.flights().begin();
358 }
359
360 pub fn commit(mut self) -> Submitted {
362 self.close();
363 self.raw.commit();
364 self.mark_committed();
365 Submitted {
366 device: self.device.clone(),
367 raw: self.raw.clone(),
368 unfenced: self.own.is_none(),
369 own: self.own.clone(),
370 guards: std::mem::take(&mut self.guards),
371 _retained: std::mem::take(&mut self.retained),
372 done: false,
373 }
374 }
375
376 pub fn commit_then(mut self, on_complete: impl FnOnce(Result<()>) + Send + 'static) -> Pending {
380 self.close();
381 let guards: Vec<Arc<Guard>> = match self.own {
383 Some(_) => self.guards.iter().map(|(g, _)| g.clone()).collect(),
384 None => Vec::new(),
385 };
386 let finish: Finish = Arc::new(Mutex::new(Some(Completion {
387 on_complete: Box::new(on_complete),
388 own: self.own.clone(),
389 kernels: std::mem::take(&mut self.kernels),
390 unfenced: self.own.is_none(),
391 guards: std::mem::take(&mut self.guards),
392 _retained: std::mem::take(&mut self.retained),
393 device: self.device.clone(),
394 })));
395 let shared = finish.clone();
396 let handler = RcBlock::new(move |raw: NonNull<ProtocolObject<dyn MTLCommandBuffer>>| {
397 complete(&shared, unsafe { raw.as_ref() });
399 });
400 unsafe { self.raw.addCompletedHandler(RcBlock::as_ptr(&handler)) };
404 self.raw.commit();
405 for guard in guards {
406 guard.committed();
407 }
408 Pending {
409 raw: self.raw.clone(),
410 finish,
411 }
412 }
413
414 fn mark_committed(&self) {
417 if self.own.is_some() {
418 for (guard, _) in &self.guards {
419 guard.committed();
420 }
421 }
422 }
423
424 fn conflicts(&self, args: &[Arg<'_>]) -> bool {
427 args.iter().filter_map(Arg::buffer).any(|(b, _, write)| {
428 let id = b.id();
429 self.writes.contains(&id) || (write && self.reads.contains(&id))
430 })
431 }
432
433 fn encoder(&mut self) -> Result<&Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>> {
434 if self.encoder.is_none() {
435 let encoder = self
436 .raw
437 .computeCommandEncoderWithDispatchType(MTLDispatchType::Concurrent)
438 .ok_or_else(|| GpuError::CommandBuffer("cannot create a compute encoder".into()))?;
439 if let Some((fence, true)) = &self.fence {
440 encoder.waitForFence(fence);
441 }
442 self.encoder = Some(encoder);
443 }
444 Ok(self.encoder.as_ref().expect("just created"))
445 }
446}
447
448impl Drop for CommandBuffer {
449 fn drop(&mut self) {
451 if let Some(encoder) = self.encoder.take() {
452 encoder.endEncoding();
453 }
454 for (guard, write) in self.guards.drain(..) {
455 guard.committed();
456 guard.release(write, None); }
458 }
459}
460
461fn size([width, height, depth]: [usize; 3]) -> MTLSize {
462 MTLSize {
463 width,
464 height,
465 depth,
466 }
467}
468
469fn validate(args: &[Arg<'_>]) -> Result<()> {
470 if args.len() > MAX_ARGS {
471 return Err(GpuError::InvalidArgument(format!(
472 "{} arguments; Metal allows {MAX_ARGS}",
473 args.len()
474 )));
475 }
476 for (index, arg) in args.iter().enumerate() {
477 match arg {
478 Arg::Bytes(bytes) if bytes.len() > MAX_INLINE_BYTES => {
479 return Err(GpuError::InvalidArgument(format!(
480 "argument {index}: {} inline bytes; Metal allows {MAX_INLINE_BYTES}",
481 bytes.len()
482 )));
483 }
484 Arg::Write(b, _) if b.is_mapped() => return Err(GpuError::ReadOnly),
485 Arg::Same(earlier, offset) => {
486 let target = args.get(*earlier).and_then(Arg::buffer);
487 match target {
488 Some((b, ..)) if *offset <= b.len() => {}
489 _ => {
490 return Err(GpuError::InvalidArgument(format!(
491 "argument {index}: `Same({earlier}, {offset})` needs another \
492 buffer argument and an offset within it"
493 )));
494 }
495 }
496 }
497 Arg::Read(..) | Arg::Write(..)
498 if arg.buffer().is_some_and(|(b, offset, _)| offset > b.len()) =>
499 {
500 let (b, offset, _) = arg.buffer().expect("buffer argument");
501 return Err(GpuError::InvalidArgument(format!(
502 "argument {index}: offset {offset} is past the end of a {}-byte buffer",
503 b.len()
504 )));
505 }
506 _ => {}
507 }
508 }
509 Ok(())
510}
511
512struct Completion {
515 on_complete: Box<dyn FnOnce(Result<()>) + Send>,
516 own: Option<Fence>,
518 kernels: Vec<Arc<str>>,
520 guards: Vec<(Arc<Guard>, bool)>,
521 unfenced: bool,
523 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
524 device: Device,
525}
526
527unsafe impl Send for Completion {}
531
532type Finish = Arc<Mutex<Option<Completion>>>;
533
534fn complete(finish: &Finish, raw: &ProtocolObject<dyn MTLCommandBuffer>) {
536 let Some(c) = finish.lock().unwrap_or_else(PoisonError::into_inner).take() else {
537 return;
538 };
539 let result = status(raw);
540 super::profile::record(&c.kernels, raw.GPUEndTime() - raw.GPUStartTime());
541 for (guard, write) in c.guards {
542 if c.unfenced {
543 guard.committed();
544 }
545 guard.release(write, c.own.as_ref());
546 }
547 drop(c._retained);
548 c.device.flights().end();
549 (c.on_complete)(result);
550}
551
552pub struct Pending {
556 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
557 finish: Finish,
558}
559
560impl Pending {
561 pub fn wait(&self) {
563 self.raw.waitUntilCompleted();
564 complete(&self.finish, &self.raw);
565 }
566}
567
568impl std::fmt::Debug for Pending {
569 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570 f.debug_struct("Pending").finish_non_exhaustive()
571 }
572}
573
574pub struct Submitted {
576 device: Device,
577 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
578 unfenced: bool,
581 own: Option<Fence>,
583 guards: Vec<(Arc<Guard>, bool)>,
584 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
585 done: bool,
586}
587
588impl Submitted {
589 pub fn wait(mut self) -> Result<()> {
591 self.finish()
592 }
593
594 fn finish(&mut self) -> Result<()> {
595 if self.done {
596 return Ok(());
597 }
598 self.raw.waitUntilCompleted();
599 self.done = true;
600 for (guard, write) in self.guards.drain(..) {
601 if self.unfenced {
602 guard.committed();
603 }
604 guard.release(write, self.own.as_ref());
605 }
606 self.device.flights().end();
607 status(&self.raw)
608 }
609}
610
611impl Drop for Submitted {
612 fn drop(&mut self) {
613 let _ = self.finish();
614 }
615}