Skip to main content

mtl_gpu/texture/
texture.rs

1//! A texture resource that stores formatted image data.
2
3use std::ffi::c_void;
4use std::ptr::NonNull;
5
6use mtl_foundation::{Referencing, UInteger};
7use mtl_sys::{msg_send_0, msg_send_1, sel};
8
9use crate::enums::{
10    PixelFormat, ResourceOptions, TextureCompressionType, TextureSparseTier,
11    TextureSwizzleChannels, TextureType, TextureUsage,
12};
13use crate::types::ResourceID;
14
15use super::{SharedTextureHandle, TextureViewDescriptor};
16
17/// A texture resource that stores formatted image data.
18///
19/// C++ equivalent: `MTL::Texture`
20#[repr(transparent)]
21pub struct Texture(pub(crate) NonNull<c_void>);
22
23impl Texture {
24    /// Create a Texture from a raw pointer.
25    ///
26    /// # Safety
27    ///
28    /// The pointer must be a valid Metal texture object.
29    #[inline]
30    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
31        NonNull::new(ptr).map(Self)
32    }
33
34    /// Get the raw pointer to the texture.
35    #[inline]
36    pub fn as_raw(&self) -> *mut c_void {
37        self.0.as_ptr()
38    }
39
40    // =========================================================================
41    // Texture Properties
42    // =========================================================================
43
44    /// Get the texture type.
45    ///
46    /// C++ equivalent: `TextureType textureType() const`
47    #[inline]
48    pub fn texture_type(&self) -> TextureType {
49        unsafe { msg_send_0(self.as_ptr(), sel!(textureType)) }
50    }
51
52    /// Get the pixel format.
53    ///
54    /// C++ equivalent: `PixelFormat pixelFormat() const`
55    #[inline]
56    pub fn pixel_format(&self) -> PixelFormat {
57        unsafe { msg_send_0(self.as_ptr(), sel!(pixelFormat)) }
58    }
59
60    /// Get the width of the texture.
61    ///
62    /// C++ equivalent: `NS::UInteger width() const`
63    #[inline]
64    pub fn width(&self) -> UInteger {
65        unsafe { msg_send_0(self.as_ptr(), sel!(width)) }
66    }
67
68    /// Get the height of the texture.
69    ///
70    /// C++ equivalent: `NS::UInteger height() const`
71    #[inline]
72    pub fn height(&self) -> UInteger {
73        unsafe { msg_send_0(self.as_ptr(), sel!(height)) }
74    }
75
76    /// Get the depth of the texture.
77    ///
78    /// C++ equivalent: `NS::UInteger depth() const`
79    #[inline]
80    pub fn depth(&self) -> UInteger {
81        unsafe { msg_send_0(self.as_ptr(), sel!(depth)) }
82    }
83
84    /// Get the number of mipmap levels.
85    ///
86    /// C++ equivalent: `NS::UInteger mipmapLevelCount() const`
87    #[inline]
88    pub fn mipmap_level_count(&self) -> UInteger {
89        unsafe { msg_send_0(self.as_ptr(), sel!(mipmapLevelCount)) }
90    }
91
92    /// Get the sample count (for multisampled textures).
93    ///
94    /// C++ equivalent: `NS::UInteger sampleCount() const`
95    #[inline]
96    pub fn sample_count(&self) -> UInteger {
97        unsafe { msg_send_0(self.as_ptr(), sel!(sampleCount)) }
98    }
99
100    /// Get the array length (for texture arrays).
101    ///
102    /// C++ equivalent: `NS::UInteger arrayLength() const`
103    #[inline]
104    pub fn array_length(&self) -> UInteger {
105        unsafe { msg_send_0(self.as_ptr(), sel!(arrayLength)) }
106    }
107
108    /// Get the texture usage flags.
109    ///
110    /// C++ equivalent: `TextureUsage usage() const`
111    #[inline]
112    pub fn usage(&self) -> TextureUsage {
113        unsafe { msg_send_0(self.as_ptr(), sel!(usage)) }
114    }
115
116    /// Check if this is a shareable texture.
117    ///
118    /// C++ equivalent: `bool isShareable() const`
119    #[inline]
120    pub fn is_shareable(&self) -> bool {
121        unsafe { msg_send_0(self.as_ptr(), sel!(isShareable)) }
122    }
123
124    /// Check if this is a framebuffer-only texture.
125    ///
126    /// C++ equivalent: `bool isFramebufferOnly() const`
127    #[inline]
128    pub fn is_framebuffer_only(&self) -> bool {
129        unsafe { msg_send_0(self.as_ptr(), sel!(isFramebufferOnly)) }
130    }
131
132    /// Get the first mipmap level used in a texture view.
133    ///
134    /// C++ equivalent: `NS::UInteger firstMipmapInTail() const`
135    #[inline]
136    pub fn first_mipmap_in_tail(&self) -> UInteger {
137        unsafe { msg_send_0(self.as_ptr(), sel!(firstMipmapInTail)) }
138    }
139
140    /// Get the tail size in bytes for sparse textures.
141    ///
142    /// C++ equivalent: `NS::UInteger tailSizeInBytes() const`
143    #[inline]
144    pub fn tail_size_in_bytes(&self) -> UInteger {
145        unsafe { msg_send_0(self.as_ptr(), sel!(tailSizeInBytes)) }
146    }
147
148    /// Check if the tail is sparse.
149    ///
150    /// C++ equivalent: `bool isSparse() const`
151    #[inline]
152    pub fn is_sparse(&self) -> bool {
153        unsafe { msg_send_0(self.as_ptr(), sel!(isSparse)) }
154    }
155
156    /// Get the GPU resource ID for bindless access.
157    ///
158    /// C++ equivalent: `ResourceID gpuResourceID() const`
159    #[inline]
160    pub fn gpu_resource_id(&self) -> ResourceID {
161        unsafe { msg_send_0(self.as_ptr(), sel!(gpuResourceID)) }
162    }
163
164    /// Check if texture allows GPU-optimized contents.
165    ///
166    /// C++ equivalent: `bool allowGPUOptimizedContents() const`
167    #[inline]
168    pub fn allow_gpu_optimized_contents(&self) -> bool {
169        unsafe { msg_send_0(self.as_ptr(), sel!(allowGPUOptimizedContents)) }
170    }
171
172    /// Get the texture compression type.
173    ///
174    /// C++ equivalent: `TextureCompressionType compressionType() const`
175    #[inline]
176    pub fn compression_type(&self) -> TextureCompressionType {
177        unsafe { msg_send_0(self.as_ptr(), sel!(compressionType)) }
178    }
179
180    /// Get the texture swizzle channels.
181    ///
182    /// C++ equivalent: `TextureSwizzleChannels swizzle() const`
183    #[inline]
184    pub fn swizzle(&self) -> TextureSwizzleChannels {
185        unsafe { msg_send_0(self.as_ptr(), sel!(swizzle)) }
186    }
187
188    /// Get the parent texture (if this is a view).
189    ///
190    /// C++ equivalent: `Texture* parentTexture() const`
191    pub fn parent_texture(&self) -> Option<Texture> {
192        unsafe {
193            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(parentTexture));
194            if ptr.is_null() {
195                return None;
196            }
197            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
198            Texture::from_raw(ptr)
199        }
200    }
201
202    /// Get the parent relative level (for texture views).
203    ///
204    /// C++ equivalent: `NS::UInteger parentRelativeLevel() const`
205    #[inline]
206    pub fn parent_relative_level(&self) -> UInteger {
207        unsafe { msg_send_0(self.as_ptr(), sel!(parentRelativeLevel)) }
208    }
209
210    /// Get the parent relative slice (for texture views).
211    ///
212    /// C++ equivalent: `NS::UInteger parentRelativeSlice() const`
213    #[inline]
214    pub fn parent_relative_slice(&self) -> UInteger {
215        unsafe { msg_send_0(self.as_ptr(), sel!(parentRelativeSlice)) }
216    }
217
218    /// Get the buffer (if texture is backed by a buffer).
219    ///
220    /// C++ equivalent: `Buffer* buffer() const`
221    pub fn buffer(&self) -> Option<crate::buffer::Buffer> {
222        unsafe {
223            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(buffer));
224            if ptr.is_null() {
225                return None;
226            }
227            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
228            crate::buffer::Buffer::from_raw(ptr)
229        }
230    }
231
232    /// Get the buffer offset (if texture is backed by a buffer).
233    ///
234    /// C++ equivalent: `NS::UInteger bufferOffset() const`
235    #[inline]
236    pub fn buffer_offset(&self) -> UInteger {
237        unsafe { msg_send_0(self.as_ptr(), sel!(bufferOffset)) }
238    }
239
240    /// Get the buffer bytes per row (if texture is backed by a buffer).
241    ///
242    /// C++ equivalent: `NS::UInteger bufferBytesPerRow() const`
243    #[inline]
244    pub fn buffer_bytes_per_row(&self) -> UInteger {
245        unsafe { msg_send_0(self.as_ptr(), sel!(bufferBytesPerRow)) }
246    }
247
248    /// Get the IOSurface backing this texture, if any.
249    ///
250    /// C++ equivalent: `IOSurfaceRef iosurface() const`
251    ///
252    /// Returns an opaque pointer to the IOSurfaceRef, or None if the texture
253    /// is not backed by an IOSurface.
254    #[inline]
255    pub fn iosurface(&self) -> Option<*mut c_void> {
256        unsafe {
257            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(iosurface));
258            if ptr.is_null() { None } else { Some(ptr) }
259        }
260    }
261
262    /// Get the IOSurface plane index for this texture.
263    ///
264    /// C++ equivalent: `NS::UInteger iosurfacePlane() const`
265    #[inline]
266    pub fn iosurface_plane(&self) -> UInteger {
267        unsafe { msg_send_0(self.as_ptr(), sel!(iosurfacePlane)) }
268    }
269
270    /// Get the sparse texture tier for this texture.
271    ///
272    /// C++ equivalent: `TextureSparseTier sparseTextureTier() const`
273    #[inline]
274    pub fn sparse_texture_tier(&self) -> TextureSparseTier {
275        unsafe { msg_send_0(self.as_ptr(), sel!(sparseTextureTier)) }
276    }
277
278    /// Get the remote storage texture (for cross-process sharing).
279    ///
280    /// C++ equivalent: `Texture* remoteStorageTexture() const`
281    pub fn remote_storage_texture(&self) -> Option<Texture> {
282        unsafe {
283            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(remoteStorageTexture));
284            if ptr.is_null() {
285                return None;
286            }
287            // Retain the autoreleased object
288            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
289            Texture::from_raw(ptr)
290        }
291    }
292
293    /// Create a remote texture view for a different device.
294    ///
295    /// C++ equivalent: `Texture* newRemoteTextureViewForDevice(const Device*)`
296    pub fn new_remote_texture_view_for_device(&self, device: &crate::Device) -> Option<Texture> {
297        unsafe {
298            let ptr: *mut c_void = msg_send_1(
299                self.as_ptr(),
300                sel!(newRemoteTextureViewForDevice:),
301                device.as_ptr(),
302            );
303            Texture::from_raw(ptr)
304        }
305    }
306
307    // =========================================================================
308    // Texture View Creation
309    // =========================================================================
310
311    /// Create a texture view with a different pixel format.
312    ///
313    /// C++ equivalent: `Texture* newTextureView(PixelFormat)`
314    pub fn new_texture_view_with_pixel_format(&self, pixel_format: PixelFormat) -> Option<Texture> {
315        unsafe {
316            let ptr: *mut c_void = msg_send_1(
317                self.as_ptr(),
318                sel!(newTextureViewWithPixelFormat:),
319                pixel_format,
320            );
321            Texture::from_raw(ptr)
322        }
323    }
324
325    /// Create a texture view with different pixel format and texture type.
326    ///
327    /// C++ equivalent: `Texture* newTextureView(PixelFormat, TextureType, NS::Range levels, NS::Range slices)`
328    pub fn new_texture_view(
329        &self,
330        pixel_format: PixelFormat,
331        texture_type: TextureType,
332        level_range: mtl_foundation::Range,
333        slice_range: mtl_foundation::Range,
334    ) -> Option<Texture> {
335        unsafe {
336            let ptr: *mut c_void = mtl_sys::msg_send_4(
337                self.as_ptr(),
338                sel!(newTextureViewWithPixelFormat: textureType: levels: slices:),
339                pixel_format,
340                texture_type,
341                level_range,
342                slice_range,
343            );
344            Texture::from_raw(ptr)
345        }
346    }
347
348    /// Create a texture view with swizzle.
349    ///
350    /// C++ equivalent: `Texture* newTextureView(PixelFormat, TextureType, NS::Range, NS::Range, TextureSwizzleChannels)`
351    pub fn new_texture_view_with_swizzle(
352        &self,
353        pixel_format: PixelFormat,
354        texture_type: TextureType,
355        level_range: mtl_foundation::Range,
356        slice_range: mtl_foundation::Range,
357        swizzle: TextureSwizzleChannels,
358    ) -> Option<Texture> {
359        unsafe {
360            let ptr: *mut c_void = mtl_sys::msg_send_5(
361                self.as_ptr(),
362                sel!(newTextureViewWithPixelFormat: textureType: levels: slices: swizzle:),
363                pixel_format,
364                texture_type,
365                level_range,
366                slice_range,
367                swizzle,
368            );
369            Texture::from_raw(ptr)
370        }
371    }
372
373    /// Create a shared texture handle for cross-process sharing.
374    ///
375    /// C++ equivalent: `SharedTextureHandle* newSharedTextureHandle()`
376    pub fn new_shared_texture_handle(&self) -> Option<SharedTextureHandle> {
377        unsafe {
378            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(newSharedTextureHandle));
379            SharedTextureHandle::from_raw(ptr)
380        }
381    }
382
383    /// Create a texture view with a descriptor.
384    ///
385    /// C++ equivalent: `Texture* newTextureView(const TextureViewDescriptor*)`
386    pub fn new_texture_view_with_descriptor(
387        &self,
388        descriptor: &TextureViewDescriptor,
389    ) -> Option<Texture> {
390        unsafe {
391            let ptr: *mut c_void = msg_send_1(
392                self.as_ptr(),
393                sel!(newTextureViewWithDescriptor:),
394                descriptor.as_ptr(),
395            );
396            Texture::from_raw(ptr)
397        }
398    }
399
400    // =========================================================================
401    // Texture Data Operations
402    // =========================================================================
403
404    /// Get texture data bytes (full version with slice).
405    ///
406    /// C++ equivalent: `void getBytes(void*, NS::UInteger, NS::UInteger, MTL::Region, NS::UInteger, NS::UInteger)`
407    ///
408    /// # Safety
409    ///
410    /// The `pixel_bytes` pointer must be valid for writing the requested data.
411    pub unsafe fn get_bytes(
412        &self,
413        pixel_bytes: *mut c_void,
414        bytes_per_row: UInteger,
415        bytes_per_image: UInteger,
416        region: crate::types::Region,
417        mipmap_level: UInteger,
418        slice: UInteger,
419    ) {
420        unsafe {
421            mtl_sys::msg_send_6::<
422                (),
423                *mut c_void,
424                UInteger,
425                UInteger,
426                crate::types::Region,
427                UInteger,
428                UInteger,
429            >(
430                self.as_ptr(),
431                sel!(getBytes: bytesPerRow: bytesPerImage: fromRegion: mipmapLevel: slice:),
432                pixel_bytes,
433                bytes_per_row,
434                bytes_per_image,
435                region,
436                mipmap_level,
437                slice,
438            );
439        }
440    }
441
442    /// Get texture data bytes (simple version for 2D textures).
443    ///
444    /// C++ equivalent: `void getBytes(void*, NS::UInteger, MTL::Region, NS::UInteger)`
445    ///
446    /// # Safety
447    ///
448    /// The `pixel_bytes` pointer must be valid for writing the requested data.
449    pub unsafe fn get_bytes_simple(
450        &self,
451        pixel_bytes: *mut c_void,
452        bytes_per_row: UInteger,
453        region: crate::types::Region,
454        mipmap_level: UInteger,
455    ) {
456        unsafe {
457            mtl_sys::msg_send_4::<(), *mut c_void, UInteger, crate::types::Region, UInteger>(
458                self.as_ptr(),
459                sel!(getBytes: bytesPerRow: fromRegion: mipmapLevel:),
460                pixel_bytes,
461                bytes_per_row,
462                region,
463                mipmap_level,
464            );
465        }
466    }
467
468    /// Replace a region of the texture (full version with slice).
469    ///
470    /// C++ equivalent: `void replaceRegion(MTL::Region, NS::UInteger, NS::UInteger, const void*, NS::UInteger, NS::UInteger)`
471    ///
472    /// # Safety
473    ///
474    /// The `pixel_bytes` pointer must be valid for reading the data to copy.
475    pub unsafe fn replace_region(
476        &self,
477        region: crate::types::Region,
478        mipmap_level: UInteger,
479        slice: UInteger,
480        pixel_bytes: *const c_void,
481        bytes_per_row: UInteger,
482        bytes_per_image: UInteger,
483    ) {
484        unsafe {
485            mtl_sys::msg_send_6::<
486                (),
487                crate::types::Region,
488                UInteger,
489                UInteger,
490                *const c_void,
491                UInteger,
492                UInteger,
493            >(
494                self.as_ptr(),
495                sel!(replaceRegion: mipmapLevel: slice: withBytes: bytesPerRow: bytesPerImage:),
496                region,
497                mipmap_level,
498                slice,
499                pixel_bytes,
500                bytes_per_row,
501                bytes_per_image,
502            );
503        }
504    }
505
506    /// Replace a region of the texture (simple version for 2D textures).
507    ///
508    /// C++ equivalent: `void replaceRegion(MTL::Region, NS::UInteger, const void*, NS::UInteger)`
509    ///
510    /// # Safety
511    ///
512    /// The `pixel_bytes` pointer must be valid for reading the data to copy.
513    pub unsafe fn replace_region_simple(
514        &self,
515        region: crate::types::Region,
516        mipmap_level: UInteger,
517        pixel_bytes: *const c_void,
518        bytes_per_row: UInteger,
519    ) {
520        unsafe {
521            mtl_sys::msg_send_4::<(), crate::types::Region, UInteger, *const c_void, UInteger>(
522                self.as_ptr(),
523                sel!(replaceRegion: mipmapLevel: withBytes: bytesPerRow:),
524                region,
525                mipmap_level,
526                pixel_bytes,
527                bytes_per_row,
528            );
529        }
530    }
531
532    // =========================================================================
533    // Resource Properties (inherited from MTLResource)
534    // =========================================================================
535
536    /// Get the label for this texture.
537    ///
538    /// C++ equivalent: `NS::String* label() const`
539    pub fn label(&self) -> Option<String> {
540        unsafe {
541            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
542            if ptr.is_null() {
543                return None;
544            }
545            let utf8_ptr: *const std::ffi::c_char =
546                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
547            if utf8_ptr.is_null() {
548                return None;
549            }
550            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
551            Some(c_str.to_string_lossy().into_owned())
552        }
553    }
554
555    /// Set the label for this texture.
556    ///
557    /// C++ equivalent: `void setLabel(const NS::String*)`
558    pub fn set_label(&self, label: &str) {
559        if let Some(ns_label) = mtl_foundation::String::from_str(label) {
560            unsafe {
561                msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(setLabel:), ns_label.as_ptr());
562            }
563        }
564    }
565
566    /// Get the device that created this texture.
567    ///
568    /// C++ equivalent: `Device* device() const`
569    pub fn device(&self) -> crate::Device {
570        unsafe {
571            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(device));
572            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
573            crate::Device::from_raw(ptr).expect("texture has no device")
574        }
575    }
576
577    /// Get the resource options.
578    ///
579    /// C++ equivalent: `ResourceOptions resourceOptions() const`
580    #[inline]
581    pub fn resource_options(&self) -> ResourceOptions {
582        unsafe { msg_send_0(self.as_ptr(), sel!(resourceOptions)) }
583    }
584
585    /// Get the allocated size.
586    ///
587    /// C++ equivalent: `NS::UInteger allocatedSize() const`
588    #[inline]
589    pub fn allocated_size(&self) -> UInteger {
590        unsafe { msg_send_0(self.as_ptr(), sel!(allocatedSize)) }
591    }
592}
593
594impl Clone for Texture {
595    fn clone(&self) -> Self {
596        unsafe {
597            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
598        }
599        Self(self.0)
600    }
601}
602
603impl Drop for Texture {
604    fn drop(&mut self) {
605        unsafe {
606            msg_send_0::<()>(self.as_ptr(), sel!(release));
607        }
608    }
609}
610
611impl Referencing for Texture {
612    #[inline]
613    fn as_ptr(&self) -> *const c_void {
614        self.0.as_ptr()
615    }
616}
617
618unsafe impl Send for Texture {}
619unsafe impl Sync for Texture {}
620
621impl std::fmt::Debug for Texture {
622    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
623        f.debug_struct("Texture")
624            .field("texture_type", &self.texture_type())
625            .field("pixel_format", &self.pixel_format())
626            .field("width", &self.width())
627            .field("height", &self.height())
628            .field("depth", &self.depth())
629            .field("label", &self.label())
630            .finish()
631    }
632}