1use std::ptr::NonNull;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6
7use objc2::rc::Retained;
8use objc2::runtime::ProtocolObject;
9use objc2_metal::{
10 MTLBarrierScope, MTLBuffer, MTLCommandBuffer, MTLCommandBufferStatus, MTLCommandEncoder,
11 MTLCommandQueue, MTLComputeCommandEncoder, MTLDispatchType, MTLSize,
12};
13
14use super::{Buffer, Device, Pipeline};
15use crate::{GpuError, Result};
16
17const MAX_ARGS: usize = 31;
19const MAX_INLINE_BYTES: usize = 4096;
21
22#[derive(Debug)]
24pub enum Arg<'a> {
25 Read(&'a Buffer, usize),
27 Write(&'a mut Buffer, usize),
29 Bytes(&'a [u8]),
31}
32
33impl<'a> Arg<'a> {
34 pub fn read(buffer: &'a Buffer) -> Self {
36 Self::Read(buffer, 0)
37 }
38
39 pub fn write(buffer: &'a mut Buffer) -> Self {
41 Self::Write(buffer, 0)
42 }
43
44 pub fn bytes(bytes: &'a [u8]) -> Self {
46 Self::Bytes(bytes)
47 }
48
49 fn buffer(&self) -> Option<(&Buffer, usize, bool)> {
50 match self {
51 Self::Read(b, offset) => Some((b, *offset, false)),
52 Self::Write(b, offset) => Some((b, *offset, true)),
53 Self::Bytes(_) => None,
54 }
55 }
56}
57
58pub struct CommandBuffer {
61 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
62 encoder: Option<Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>>,
63 reads: Vec<usize>,
65 writes: Vec<usize>,
66 barriers: usize,
67 guards: Vec<Arc<AtomicUsize>>,
69 retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
70}
71
72impl Device {
73 pub fn command_buffer(&self) -> Result<CommandBuffer> {
75 let raw = self
76 .queue()
77 .commandBuffer()
78 .ok_or_else(|| GpuError::CommandBuffer("cannot create a command buffer".into()))?;
79 Ok(CommandBuffer {
80 raw,
81 encoder: None,
82 reads: Vec::new(),
83 writes: Vec::new(),
84 barriers: 0,
85 guards: Vec::new(),
86 retained: Vec::new(),
87 })
88 }
89}
90
91impl CommandBuffer {
92 pub fn dispatch(
95 &mut self,
96 pipeline: &Pipeline,
97 args: &[Arg<'_>],
98 threads: usize,
99 ) -> Result<()> {
100 validate(args)?;
101 if threads == 0 {
102 return Ok(());
103 }
104 if self.conflicts(args) {
105 self.encoder()?
106 .memoryBarrierWithScope(MTLBarrierScope::Buffers);
107 self.barriers += 1;
108 self.reads.clear();
109 self.writes.clear();
110 }
111 let encoder = self.encoder()?.clone();
112 encoder.setComputePipelineState(pipeline.raw());
113 for (index, arg) in args.iter().enumerate() {
114 match arg {
115 Arg::Bytes(bytes) => {
116 let data = NonNull::new(bytes.as_ptr().cast_mut())
117 .unwrap_or(NonNull::dangling())
118 .cast();
119 unsafe { encoder.setBytes_length_atIndex(data, bytes.len(), index) };
123 }
124 Arg::Read(..) | Arg::Write(..) => {
125 let (buffer, offset, _) = arg.buffer().expect("buffer argument");
126 unsafe { encoder.setBuffer_offset_atIndex(buffer.raw(), offset, index) };
130 }
131 }
132 }
133 for (buffer, _, write) in args.iter().filter_map(Arg::buffer) {
134 (if write {
135 &mut self.writes
136 } else {
137 &mut self.reads
138 })
139 .push(buffer.id());
140 let guard = buffer.guard();
141 guard.fetch_add(1, Ordering::AcqRel);
142 self.guards.push(guard);
143 self.retained.extend(buffer.retained());
144 }
145 let width = pipeline.execution_width().max(1);
146 let mut group = pipeline.max_threads_per_threadgroup().min(threads).max(1);
147 if group > width {
148 group -= group % width;
149 }
150 let size = |width| MTLSize {
151 width,
152 height: 1,
153 depth: 1,
154 };
155 encoder.dispatchThreads_threadsPerThreadgroup(size(threads), size(group));
156 Ok(())
157 }
158
159 pub fn barriers(&self) -> usize {
161 self.barriers
162 }
163
164 pub fn commit(mut self) -> Submitted {
166 if let Some(encoder) = self.encoder.take() {
167 encoder.endEncoding();
168 }
169 self.raw.commit();
170 Submitted {
171 raw: self.raw.clone(),
172 guards: std::mem::take(&mut self.guards),
173 _retained: std::mem::take(&mut self.retained),
174 done: false,
175 }
176 }
177
178 fn conflicts(&self, args: &[Arg<'_>]) -> bool {
181 args.iter().filter_map(Arg::buffer).any(|(b, _, write)| {
182 let id = b.id();
183 self.writes.contains(&id) || (write && self.reads.contains(&id))
184 })
185 }
186
187 fn encoder(&mut self) -> Result<&Retained<ProtocolObject<dyn MTLComputeCommandEncoder>>> {
188 if self.encoder.is_none() {
189 let encoder = self
190 .raw
191 .computeCommandEncoderWithDispatchType(MTLDispatchType::Concurrent)
192 .ok_or_else(|| GpuError::CommandBuffer("cannot create a compute encoder".into()))?;
193 self.encoder = Some(encoder);
194 }
195 Ok(self.encoder.as_ref().expect("just created"))
196 }
197}
198
199impl Drop for CommandBuffer {
200 fn drop(&mut self) {
202 if let Some(encoder) = self.encoder.take() {
203 encoder.endEncoding();
204 }
205 for guard in self.guards.drain(..) {
206 guard.fetch_sub(1, Ordering::AcqRel);
207 }
208 }
209}
210
211fn validate(args: &[Arg<'_>]) -> Result<()> {
212 if args.len() > MAX_ARGS {
213 return Err(GpuError::InvalidArgument(format!(
214 "{} arguments; Metal allows {MAX_ARGS}",
215 args.len()
216 )));
217 }
218 for (index, arg) in args.iter().enumerate() {
219 match arg {
220 Arg::Bytes(bytes) if bytes.len() > MAX_INLINE_BYTES => {
221 return Err(GpuError::InvalidArgument(format!(
222 "argument {index}: {} inline bytes; Metal allows {MAX_INLINE_BYTES}",
223 bytes.len()
224 )));
225 }
226 Arg::Read(..) | Arg::Write(..)
227 if arg.buffer().is_some_and(|(b, offset, _)| offset > b.len()) =>
228 {
229 let (b, offset, _) = arg.buffer().expect("buffer argument");
230 return Err(GpuError::InvalidArgument(format!(
231 "argument {index}: offset {offset} is past the end of a {}-byte buffer",
232 b.len()
233 )));
234 }
235 _ => {}
236 }
237 }
238 Ok(())
239}
240
241pub struct Submitted {
243 raw: Retained<ProtocolObject<dyn MTLCommandBuffer>>,
244 guards: Vec<Arc<AtomicUsize>>,
245 _retained: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
246 done: bool,
247}
248
249impl Submitted {
250 pub fn wait(mut self) -> Result<()> {
252 self.finish()
253 }
254
255 fn finish(&mut self) -> Result<()> {
256 if self.done {
257 return Ok(());
258 }
259 self.raw.waitUntilCompleted();
260 self.done = true;
261 for guard in self.guards.drain(..) {
262 guard.fetch_sub(1, Ordering::AcqRel);
263 }
264 if self.raw.status() == MTLCommandBufferStatus::Error {
265 let message = self.raw.error().map_or_else(
266 || "unknown error".to_owned(),
267 |e| e.localizedDescription().to_string(),
268 );
269 return Err(GpuError::CommandBuffer(message));
270 }
271 Ok(())
272 }
273}
274
275impl Drop for Submitted {
276 fn drop(&mut self) {
277 let _ = self.finish();
278 }
279}