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}
34
35impl<'a> Arg<'a> {
36 pub fn read(buffer: &'a Buffer) -> Self {
38 Self::Read(buffer, 0)
39 }
40
41 pub fn write(buffer: &'a mut Buffer) -> Self {
43 Self::Write(buffer, 0)
44 }
45
46 pub fn bytes(bytes: &'a [u8]) -> Self {
48 Self::Bytes(bytes)
49 }
50
51 fn buffer(&self) -> Option<(&Buffer, usize, bool)> {
52 match self {
53 Self::Read(b, offset) => Some((b, *offset, false)),
54 Self::Write(b, offset) => Some((b, *offset, true)),
55 Self::Bytes(_) => None,
56 }
57 }
58}
59
60pub(crate) type Fence = Retained<ProtocolObject<dyn MTLFence>>;
61
62pub struct Timeline {
71 fence: Fence,
72 started: bool,
73}
74
75impl std::fmt::Debug for Timeline {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("Timeline").finish_non_exhaustive()
78 }
79}
80
81pub struct CommandBuffer {
84 device: Device,
85 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
86 fence: Option<(Fence, bool)>,
88 own: Option<Fence>,
91 waited: Vec<usize>,
92 encoder: Option<Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>>,
93 reads: Vec<usize>,
95 writes: Vec<usize>,
96 barriers: usize,
97 guards: Vec<(Arc<Guard>, bool)>,
99 retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
100 kernels: Vec<Arc<str>>,
102}
103
104impl Device {
105 pub fn command_buffer(&self) -> Result<CommandBuffer> {
107 let raw = self
108 .queue()
109 .commandBuffer()
110 .ok_or_else(|| GpuError::CommandBuffer("cannot create a command buffer".into()))?;
111 Ok(CommandBuffer {
112 device: self.clone(),
113 raw,
114 fence: None,
115 own: None,
116 waited: Vec::new(),
117 encoder: None,
118 reads: Vec::new(),
119 writes: Vec::new(),
120 barriers: 0,
121 guards: Vec::new(),
122 retained: Vec::new(),
123 kernels: Vec::new(),
124 })
125 }
126}
127
128impl Device {
129 pub fn timeline(&self) -> Result<Timeline> {
131 let fence = self
132 .raw()
133 .newFence()
134 .ok_or_else(|| GpuError::CommandBuffer("cannot create a fence".into()))?;
135 Ok(Timeline {
136 fence,
137 started: false,
138 })
139 }
140
141 pub fn ordered_command_buffer(&self) -> Result<CommandBuffer> {
150 let fence = self
151 .raw()
152 .newFence()
153 .ok_or_else(|| GpuError::CommandBuffer("cannot create a fence".into()))?;
154 let mut cmd = self.command_buffer()?;
155 cmd.own = Some(fence);
156 Ok(cmd)
157 }
158
159 pub fn command_buffer_on(&self, timeline: &mut Timeline) -> Result<CommandBuffer> {
162 let mut cmd = self.command_buffer()?;
163 cmd.fence = Some((timeline.fence.clone(), timeline.started));
164 timeline.started = true;
165 Ok(cmd)
166 }
167}
168
169fn status(raw: &ProtocolObject<dyn MTLCommandBuffer>) -> Result<()> {
171 if raw.status() == MTLCommandBufferStatus::Error {
172 let message = raw.error().map_or_else(
173 || "unknown error".to_owned(),
174 |e| e.localizedDescription().to_string(),
175 );
176 return Err(GpuError::CommandBuffer(message));
177 }
178 Ok(())
179}
180
181impl CommandBuffer {
182 pub fn dispatch(
185 &mut self,
186 pipeline: &Pipeline,
187 args: &[Arg<'_>],
188 threads: usize,
189 ) -> Result<()> {
190 if threads == 0 {
191 return validate(args);
192 }
193 let width = pipeline.execution_width().max(1);
194 let mut group = pipeline.max_threads_per_threadgroup().min(threads).max(1);
195 if group > width {
196 group -= group % width;
197 }
198 self.encode(pipeline, args, [threads, 1, 1], |encoder| {
199 encoder
200 .dispatchThreads_threadsPerThreadgroup(size([threads, 1, 1]), size([group, 1, 1]));
201 })
202 }
203
204 pub fn dispatch_groups(
207 &mut self,
208 pipeline: &Pipeline,
209 args: &[Arg<'_>],
210 groups: [usize; 3],
211 threads: [usize; 3],
212 ) -> Result<()> {
213 let per_group: usize = threads.iter().product();
214 if per_group > pipeline.max_threads_per_threadgroup() {
215 return Err(GpuError::InvalidArgument(format!(
216 "{per_group} threads per threadgroup; `{}` allows {}",
217 pipeline.name(),
218 pipeline.max_threads_per_threadgroup()
219 )));
220 }
221 if groups.contains(&0) || per_group == 0 {
222 return validate(args);
223 }
224 let extent = [0, 1, 2].map(|d| groups[d] * threads[d]);
225 self.encode(pipeline, args, extent, |encoder| {
226 encoder.dispatchThreadgroups_threadsPerThreadgroup(size(groups), size(threads));
227 })
228 }
229
230 pub fn dispatch_threads(
233 &mut self,
234 pipeline: &Pipeline,
235 args: &[Arg<'_>],
236 grid: [usize; 3],
237 group: [usize; 3],
238 ) -> Result<()> {
239 let per_group: usize = group.iter().product();
240 if per_group > pipeline.max_threads_per_threadgroup() {
241 return Err(GpuError::InvalidArgument(format!(
242 "{per_group} threads per threadgroup; `{}` allows {}",
243 pipeline.name(),
244 pipeline.max_threads_per_threadgroup()
245 )));
246 }
247 if grid.contains(&0) || per_group == 0 {
248 return validate(args);
249 }
250 self.encode(pipeline, args, grid, |encoder| {
251 encoder.dispatchThreads_threadsPerThreadgroup(size(grid), size(group));
252 })
253 }
254
255 fn encode(
258 &mut self,
259 pipeline: &Pipeline,
260 args: &[Arg<'_>],
261 extent: [usize; 3],
262 dispatch: impl FnOnce(&ProtocolObject<dyn MTLComputeCommandEncoder>),
263 ) -> Result<()> {
264 if super::profile::enabled() {
265 self.kernels.push(match super::profile::shapes() {
266 true => format!("{} {extent:?}", pipeline.name()).into(),
267 false => pipeline.name.clone(),
268 });
269 }
270 validate(args)?;
271 if self.conflicts(args) {
272 self.encoder()?
273 .memoryBarrierWithScope(MTLBarrierScope::Buffers);
274 self.barriers += 1;
275 self.reads.clear();
276 self.writes.clear();
277 }
278 let encoder = self.encoder()?.clone();
279 if let Some(own) = &self.own {
280 let own = Retained::as_ptr(own) as usize;
281 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
282 for fence in buffer.pending_fences(write) {
283 let key = Retained::as_ptr(&fence) as usize;
284 if key != own && !self.waited.contains(&key) {
285 encoder.waitForFence(&fence);
286 self.waited.push(key);
287 }
288 }
289 }
290 }
291 encoder.setComputePipelineState(pipeline.raw());
292 for (index, arg) in args.iter().enumerate() {
293 match arg {
294 Arg::Bytes(bytes) => {
295 let data = NonNull::new(bytes.as_ptr().cast_mut())
296 .unwrap_or(NonNull::dangling())
297 .cast();
298 unsafe { encoder.setBytes_length_atIndex(data, bytes.len(), index) };
302 }
303 Arg::Read(..) | Arg::Write(..) => {
304 let (buffer, offset, _) = arg.buffer().expect("buffer argument");
305 unsafe { encoder.setBuffer_offset_atIndex(buffer.raw(), offset, index) };
309 }
310 }
311 }
312 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
313 (if write {
314 &mut self.writes
315 } else {
316 &mut self.reads
317 })
318 .push(buffer.id());
319 let guard = buffer.guard();
320 guard.acquire(write);
321 self.guards.push((guard, write));
322 self.retained.extend(buffer.retained());
323 }
324 dispatch(&encoder);
325 Ok(())
326 }
327
328 pub fn barriers(&self) -> usize {
330 self.barriers
331 }
332
333 fn close(&mut self) {
335 if let Some(encoder) = self.encoder.take() {
336 if let Some((fence, _)) = &self.fence {
337 encoder.updateFence(fence);
338 }
339 if let Some(own) = &self.own {
340 encoder.updateFence(own);
341 for (guard, write) in &self.guards {
342 guard.record(own, *write);
343 }
344 }
345 encoder.endEncoding();
346 }
347 self.device.flights().begin();
348 }
349
350 pub fn commit(mut self) -> Submitted {
352 self.close();
353 self.raw.commit();
354 self.mark_committed();
355 Submitted {
356 device: self.device.clone(),
357 raw: self.raw.clone(),
358 unfenced: self.own.is_none(),
359 own: self.own.clone(),
360 guards: std::mem::take(&mut self.guards),
361 _retained: std::mem::take(&mut self.retained),
362 done: false,
363 }
364 }
365
366 pub fn commit_then(mut self, on_complete: impl FnOnce(Result<()>) + Send + 'static) -> Pending {
370 self.close();
371 let guards: Vec<Arc<Guard>> = match self.own {
373 Some(_) => self.guards.iter().map(|(g, _)| g.clone()).collect(),
374 None => Vec::new(),
375 };
376 let finish: Finish = Arc::new(Mutex::new(Some(Completion {
377 on_complete: Box::new(on_complete),
378 own: self.own.clone(),
379 kernels: std::mem::take(&mut self.kernels),
380 unfenced: self.own.is_none(),
381 guards: std::mem::take(&mut self.guards),
382 _retained: std::mem::take(&mut self.retained),
383 device: self.device.clone(),
384 })));
385 let shared = finish.clone();
386 let handler = RcBlock::new(move |raw: NonNull<ProtocolObject<dyn MTLCommandBuffer>>| {
387 complete(&shared, unsafe { raw.as_ref() });
389 });
390 unsafe { self.raw.addCompletedHandler(RcBlock::as_ptr(&handler)) };
394 self.raw.commit();
395 for guard in guards {
396 guard.committed();
397 }
398 Pending {
399 raw: self.raw.clone(),
400 finish,
401 }
402 }
403
404 fn mark_committed(&self) {
407 if self.own.is_some() {
408 for (guard, _) in &self.guards {
409 guard.committed();
410 }
411 }
412 }
413
414 fn conflicts(&self, args: &[Arg<'_>]) -> bool {
417 args.iter().filter_map(Arg::buffer).any(|(b, _, write)| {
418 let id = b.id();
419 self.writes.contains(&id) || (write && self.reads.contains(&id))
420 })
421 }
422
423 fn encoder(&mut self) -> Result<&Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>> {
424 if self.encoder.is_none() {
425 let encoder = self
426 .raw
427 .computeCommandEncoderWithDispatchType(MTLDispatchType::Concurrent)
428 .ok_or_else(|| GpuError::CommandBuffer("cannot create a compute encoder".into()))?;
429 if let Some((fence, true)) = &self.fence {
430 encoder.waitForFence(fence);
431 }
432 self.encoder = Some(encoder);
433 }
434 Ok(self.encoder.as_ref().expect("just created"))
435 }
436}
437
438impl Drop for CommandBuffer {
439 fn drop(&mut self) {
441 if let Some(encoder) = self.encoder.take() {
442 encoder.endEncoding();
443 }
444 for (guard, write) in self.guards.drain(..) {
445 guard.committed();
446 guard.release(write, None); }
448 }
449}
450
451fn size([width, height, depth]: [usize; 3]) -> MTLSize {
452 MTLSize {
453 width,
454 height,
455 depth,
456 }
457}
458
459fn validate(args: &[Arg<'_>]) -> Result<()> {
460 if args.len() > MAX_ARGS {
461 return Err(GpuError::InvalidArgument(format!(
462 "{} arguments; Metal allows {MAX_ARGS}",
463 args.len()
464 )));
465 }
466 for (index, arg) in args.iter().enumerate() {
467 match arg {
468 Arg::Bytes(bytes) if bytes.len() > MAX_INLINE_BYTES => {
469 return Err(GpuError::InvalidArgument(format!(
470 "argument {index}: {} inline bytes; Metal allows {MAX_INLINE_BYTES}",
471 bytes.len()
472 )));
473 }
474 Arg::Write(b, _) if b.is_mapped() => return Err(GpuError::ReadOnly),
475 Arg::Read(..) | Arg::Write(..)
476 if arg.buffer().is_some_and(|(b, offset, _)| offset > b.len()) =>
477 {
478 let (b, offset, _) = arg.buffer().expect("buffer argument");
479 return Err(GpuError::InvalidArgument(format!(
480 "argument {index}: offset {offset} is past the end of a {}-byte buffer",
481 b.len()
482 )));
483 }
484 _ => {}
485 }
486 }
487 Ok(())
488}
489
490struct Completion {
493 on_complete: Box<dyn FnOnce(Result<()>) + Send>,
494 own: Option<Fence>,
496 kernels: Vec<Arc<str>>,
498 guards: Vec<(Arc<Guard>, bool)>,
499 unfenced: bool,
501 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
502 device: Device,
503}
504
505unsafe impl Send for Completion {}
509
510type Finish = Arc<Mutex<Option<Completion>>>;
511
512fn complete(finish: &Finish, raw: &ProtocolObject<dyn MTLCommandBuffer>) {
514 let Some(c) = finish.lock().unwrap_or_else(PoisonError::into_inner).take() else {
515 return;
516 };
517 let result = status(raw);
518 super::profile::record(&c.kernels, raw.GPUEndTime() - raw.GPUStartTime());
519 for (guard, write) in c.guards {
520 if c.unfenced {
521 guard.committed();
522 }
523 guard.release(write, c.own.as_ref());
524 }
525 drop(c._retained);
526 c.device.flights().end();
527 (c.on_complete)(result);
528}
529
530pub struct Pending {
534 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
535 finish: Finish,
536}
537
538impl Pending {
539 pub fn wait(&self) {
541 self.raw.waitUntilCompleted();
542 complete(&self.finish, &self.raw);
543 }
544}
545
546impl std::fmt::Debug for Pending {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 f.debug_struct("Pending").finish_non_exhaustive()
549 }
550}
551
552pub struct Submitted {
554 device: Device,
555 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
556 unfenced: bool,
559 own: Option<Fence>,
561 guards: Vec<(Arc<Guard>, bool)>,
562 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
563 done: bool,
564}
565
566impl Submitted {
567 pub fn wait(mut self) -> Result<()> {
569 self.finish()
570 }
571
572 fn finish(&mut self) -> Result<()> {
573 if self.done {
574 return Ok(());
575 }
576 self.raw.waitUntilCompleted();
577 self.done = true;
578 for (guard, write) in self.guards.drain(..) {
579 if self.unfenced {
580 guard.committed();
581 }
582 guard.release(write, self.own.as_ref());
583 }
584 self.device.flights().end();
585 status(&self.raw)
586 }
587}
588
589impl Drop for Submitted {
590 fn drop(&mut self) {
591 let _ = self.finish();
592 }
593}