Skip to main content

mtl_gpu/buffer/
mod.rs

1//! Metal buffer resources.
2//!
3//! Corresponds to `Metal/MTLBuffer.hpp`.
4//!
5//! Buffers store data that can be accessed by shaders. They are the most basic
6//! type of Metal resource.
7
8use std::ffi::c_void;
9use std::ptr::NonNull;
10
11use mtl_foundation::{Referencing, UInteger};
12use mtl_sys::{msg_send_0, msg_send_1, sel};
13
14use crate::enums::{BufferSparseTier, ResourceOptions};
15use crate::types::ResourceID;
16
17/// A buffer resource that stores data for shader access.
18///
19/// C++ equivalent: `MTL::Buffer`
20///
21/// Buffers can contain any type of data: vertex data, uniform data, texture data,
22/// or any other data that shaders need to access.
23#[repr(transparent)]
24pub struct Buffer(pub(crate) NonNull<c_void>);
25
26impl Buffer {
27    /// Create a Buffer from a raw pointer.
28    ///
29    /// # Safety
30    ///
31    /// The pointer must be a valid Metal buffer object.
32    #[inline]
33    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
34        NonNull::new(ptr).map(Self)
35    }
36
37    /// Get the raw pointer to the buffer.
38    #[inline]
39    pub fn as_raw(&self) -> *mut c_void {
40        self.0.as_ptr()
41    }
42
43    // =========================================================================
44    // Buffer Properties
45    // =========================================================================
46
47    /// Get the length of the buffer in bytes.
48    ///
49    /// C++ equivalent: `NS::UInteger length() const`
50    #[inline]
51    pub fn length(&self) -> UInteger {
52        unsafe { msg_send_0(self.as_ptr(), sel!(length)) }
53    }
54
55    /// Get a CPU-accessible pointer to the buffer's contents.
56    ///
57    /// Returns `None` if the buffer is not CPU-accessible (e.g., private storage mode).
58    ///
59    /// C++ equivalent: `void* contents()`
60    #[inline]
61    pub fn contents(&self) -> Option<*mut c_void> {
62        unsafe {
63            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(contents));
64            if ptr.is_null() { None } else { Some(ptr) }
65        }
66    }
67
68    /// Notify Metal that the CPU has modified the contents of the buffer.
69    ///
70    /// Call this after modifying the buffer's contents on the CPU to ensure
71    /// the GPU sees the updated data.
72    ///
73    /// C++ equivalent: `void didModifyRange(NS::Range range)`
74    #[inline]
75    pub fn did_modify_range(&self, location: UInteger, length: UInteger) {
76        unsafe {
77            let range = mtl_foundation::Range::new(location, length);
78            msg_send_1::<(), mtl_foundation::Range>(self.as_ptr(), sel!(didModifyRange:), range);
79        }
80    }
81
82    /// Get the GPU address for bindless access.
83    ///
84    /// This returns the virtual address of the buffer in GPU memory, which
85    /// can be used for bindless resource access in shaders.
86    ///
87    /// C++ equivalent: `uint64_t gpuAddress() const`
88    #[inline]
89    pub fn gpu_address(&self) -> u64 {
90        unsafe { msg_send_0(self.as_ptr(), sel!(gpuAddress)) }
91    }
92
93    /// Create a texture that shares the buffer's storage.
94    ///
95    /// C++ equivalent: `Texture* newTexture(const TextureDescriptor*, NS::UInteger offset, NS::UInteger bytesPerRow)`
96    ///
97    /// # Safety
98    ///
99    /// The descriptor pointer must be valid.
100    pub unsafe fn new_texture(
101        &self,
102        descriptor: *const c_void,
103        offset: UInteger,
104        bytes_per_row: UInteger,
105    ) -> Option<crate::texture::Texture> {
106        unsafe {
107            let ptr: *mut c_void = mtl_sys::msg_send_3(
108                self.as_ptr(),
109                sel!(newTextureWithDescriptor: offset: bytesPerRow:),
110                descriptor,
111                offset,
112                bytes_per_row,
113            );
114            crate::texture::Texture::from_raw(ptr)
115        }
116    }
117
118    /// Create a tensor that shares the buffer's storage.
119    ///
120    /// C++ equivalent: `Tensor* newTensor(const TensorDescriptor*, NS::UInteger offset, NS::UInteger bytesPerRow)`
121    pub fn new_tensor(
122        &self,
123        descriptor: &crate::tensor::TensorDescriptor,
124        offset: UInteger,
125        bytes_per_row: UInteger,
126    ) -> Option<crate::tensor::Tensor> {
127        unsafe {
128            let ptr: *mut c_void = mtl_sys::msg_send_3(
129                self.as_ptr(),
130                sel!(newTensorWithDescriptor: offset: bytesPerRow:),
131                descriptor.as_ptr(),
132                offset,
133                bytes_per_row,
134            );
135            crate::tensor::Tensor::from_raw(ptr)
136        }
137    }
138
139    /// Add a debug marker to the buffer.
140    ///
141    /// C++ equivalent: `void addDebugMarker(const NS::String*, NS::Range)`
142    pub fn add_debug_marker(&self, marker: &str, location: UInteger, length: UInteger) {
143        if let Some(ns_marker) = mtl_foundation::String::from_str(marker) {
144            let range = mtl_foundation::Range::new(location, length);
145            unsafe {
146                mtl_sys::msg_send_2::<(), *const c_void, mtl_foundation::Range>(
147                    self.as_ptr(),
148                    sel!(addDebugMarker: range:),
149                    ns_marker.as_ptr(),
150                    range,
151                );
152            }
153        }
154    }
155
156    /// Remove all debug markers from the buffer.
157    ///
158    /// C++ equivalent: `void removeAllDebugMarkers()`
159    #[inline]
160    pub fn remove_all_debug_markers(&self) {
161        unsafe {
162            msg_send_0::<()>(self.as_ptr(), sel!(removeAllDebugMarkers));
163        }
164    }
165
166    /// Get a remote storage buffer (for cross-process sharing).
167    ///
168    /// C++ equivalent: `Buffer* remoteStorageBuffer() const`
169    #[inline]
170    pub fn remote_storage_buffer(&self) -> Option<Buffer> {
171        unsafe {
172            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(remoteStorageBuffer));
173            if ptr.is_null() {
174                return None;
175            }
176            // Retain the autoreleased object
177            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
178            Buffer::from_raw(ptr)
179        }
180    }
181
182    /// Create a remote buffer for a remote device.
183    ///
184    /// C++ equivalent: `Buffer* newRemoteBufferViewForDevice(Device*)`
185    pub fn new_remote_buffer_view_for_device(&self, device: &crate::Device) -> Option<Buffer> {
186        unsafe {
187            let ptr: *mut c_void = msg_send_1(
188                self.as_ptr(),
189                sel!(newRemoteBufferViewForDevice:),
190                device.as_ptr(),
191            );
192            Buffer::from_raw(ptr)
193        }
194    }
195
196    /// Get the GPU resource ID for bindless access.
197    ///
198    /// C++ equivalent: `ResourceID gpuResourceID() const`
199    #[inline]
200    pub fn gpu_resource_id(&self) -> ResourceID {
201        unsafe { msg_send_0(self.as_ptr(), sel!(gpuResourceID)) }
202    }
203
204    /// Get the sparse buffer tier for this buffer.
205    ///
206    /// C++ equivalent: `BufferSparseTier sparseBufferTier() const`
207    #[inline]
208    pub fn sparse_buffer_tier(&self) -> BufferSparseTier {
209        unsafe { msg_send_0(self.as_ptr(), sel!(sparseBufferTier)) }
210    }
211
212    // =========================================================================
213    // Resource Properties (inherited from MTLResource)
214    // =========================================================================
215
216    /// Get the label for this buffer.
217    ///
218    /// C++ equivalent: `NS::String* label() const`
219    pub fn label(&self) -> Option<String> {
220        unsafe {
221            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
222            if ptr.is_null() {
223                return None;
224            }
225            // Get the UTF-8 string directly from the NSString
226            let utf8_ptr: *const std::ffi::c_char =
227                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
228            if utf8_ptr.is_null() {
229                return None;
230            }
231            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
232            Some(c_str.to_string_lossy().into_owned())
233        }
234    }
235
236    /// Set the label for this buffer.
237    ///
238    /// C++ equivalent: `void setLabel(const NS::String*)`
239    pub fn set_label(&self, label: &str) {
240        if let Some(ns_label) = mtl_foundation::String::from_str(label) {
241            unsafe {
242                msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(setLabel:), ns_label.as_ptr());
243            }
244        }
245    }
246
247    /// Get the device that created this buffer.
248    ///
249    /// C++ equivalent: `Device* device() const`
250    pub fn device(&self) -> crate::Device {
251        unsafe {
252            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(device));
253            // Retain the autoreleased object
254            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
255            crate::Device::from_raw(ptr).expect("buffer has no device")
256        }
257    }
258
259    /// Get the resource options used to create this buffer.
260    ///
261    /// C++ equivalent: `ResourceOptions resourceOptions() const`
262    #[inline]
263    pub fn resource_options(&self) -> ResourceOptions {
264        unsafe { msg_send_0(self.as_ptr(), sel!(resourceOptions)) }
265    }
266
267    /// Get the allocated size of this buffer.
268    ///
269    /// C++ equivalent: `NS::UInteger allocatedSize() const`
270    #[inline]
271    pub fn allocated_size(&self) -> UInteger {
272        unsafe { msg_send_0(self.as_ptr(), sel!(allocatedSize)) }
273    }
274}
275
276impl Clone for Buffer {
277    fn clone(&self) -> Self {
278        unsafe {
279            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
280        }
281        Self(self.0)
282    }
283}
284
285impl Drop for Buffer {
286    fn drop(&mut self) {
287        unsafe {
288            msg_send_0::<()>(self.as_ptr(), sel!(release));
289        }
290    }
291}
292
293impl Referencing for Buffer {
294    #[inline]
295    fn as_ptr(&self) -> *const c_void {
296        self.0.as_ptr()
297    }
298}
299
300// SAFETY: Buffer is a reference-counted object that is thread-safe
301unsafe impl Send for Buffer {}
302unsafe impl Sync for Buffer {}
303
304impl std::fmt::Debug for Buffer {
305    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306        f.debug_struct("Buffer")
307            .field("length", &self.length())
308            .field("label", &self.label())
309            .finish()
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316
317    #[test]
318    fn test_buffer_size() {
319        assert_eq!(
320            std::mem::size_of::<Buffer>(),
321            std::mem::size_of::<*mut c_void>()
322        );
323    }
324}