Skip to main content

librashader_runtime_mtl/
texture.rs

1use crate::error::{FilterChainError, Result};
2use crate::select_optimal_pixel_format;
3use librashader_common::{FilterMode, ImageFormat, Size, WrapMode};
4use librashader_presets::Scale2D;
5use librashader_runtime::scaling::{MipmapSize, ScaleFramebuffer, ViewportSize};
6use objc2::rc::Retained;
7use objc2::runtime::ProtocolObject;
8use objc2_metal::{
9    MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLPixelFormat,
10    MTLTexture, MTLTextureDescriptor, MTLTextureUsage,
11};
12
13pub type MetalTexture = Retained<ProtocolObject<dyn MTLTexture>>;
14
15/// Alias to an `id<MTLTexture>`.
16pub type MetalTextureRef<'a> = &'a ProtocolObject<dyn MTLTexture>;
17
18pub struct OwnedTexture {
19    pub(crate) texture: MetalTexture,
20    pub(crate) max_miplevels: u32,
21    size: Size<u32>,
22}
23
24pub struct InputTexture {
25    pub texture: MetalTexture,
26    pub wrap_mode: WrapMode,
27    pub filter_mode: FilterMode,
28    pub mip_filter: FilterMode,
29}
30
31impl InputTexture {
32    pub fn try_clone(&self) -> Result<Self> {
33        Ok(Self {
34            texture: self
35                .texture
36                .newTextureViewWithPixelFormat(self.texture.pixelFormat())
37                .ok_or(FilterChainError::FailedToCreateTexture)?,
38            wrap_mode: self.wrap_mode,
39            filter_mode: self.filter_mode,
40            mip_filter: self.mip_filter,
41        })
42    }
43}
44impl AsRef<InputTexture> for InputTexture {
45    fn as_ref(&self) -> &InputTexture {
46        &self
47    }
48}
49
50impl OwnedTexture {
51    pub fn new(
52        device: &ProtocolObject<dyn MTLDevice>,
53        size: Size<u32>,
54        max_miplevels: u32,
55        format: MTLPixelFormat,
56    ) -> Result<Self> {
57        let descriptor = unsafe {
58            let descriptor =
59                MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
60                    select_optimal_pixel_format(format),
61                    size.width as usize,
62                    size.height as usize,
63                    max_miplevels > 1,
64                );
65
66            descriptor.setSampleCount(1);
67            descriptor.setMipmapLevelCount(if max_miplevels > 1 {
68                size.calculate_miplevels() as usize
69            } else {
70                1
71            });
72
73            descriptor.setUsage(
74                MTLTextureUsage::ShaderRead
75                    | MTLTextureUsage::ShaderWrite
76                    | MTLTextureUsage::RenderTarget
77                    | MTLTextureUsage::PixelFormatView,
78            );
79
80            descriptor
81        };
82
83        Ok(Self {
84            texture: device
85                .newTextureWithDescriptor(&descriptor)
86                .ok_or(FilterChainError::FailedToCreateTexture)?,
87            max_miplevels,
88            size,
89        })
90    }
91
92    pub fn scale(
93        &mut self,
94        device: &ProtocolObject<dyn MTLDevice>,
95        scaling: Scale2D,
96        format: MTLPixelFormat,
97        viewport_size: &Size<u32>,
98        source_size: &Size<u32>,
99        original_size: &Size<u32>,
100        mipmap: bool,
101    ) -> Result<Size<u32>> {
102        let size = source_size.scale_viewport(scaling, *viewport_size, *original_size, None);
103
104        if self.size != size
105            || (mipmap && self.max_miplevels == 1)
106            || (!mipmap && self.max_miplevels != 1)
107            || self.texture.pixelFormat() != select_optimal_pixel_format(format)
108        {
109            let mut new = OwnedTexture::new(
110                device,
111                size,
112                self.max_miplevels,
113                select_optimal_pixel_format(format),
114            )?;
115            std::mem::swap(self, &mut new);
116        }
117        Ok(size)
118    }
119
120    pub(crate) fn as_input(&self, filter: FilterMode, wrap_mode: WrapMode) -> Result<InputTexture> {
121        Ok(InputTexture {
122            texture: self
123                .texture
124                .newTextureViewWithPixelFormat(self.texture.pixelFormat())
125                .ok_or(FilterChainError::FailedToCreateTexture)?,
126            wrap_mode,
127            filter_mode: filter,
128            mip_filter: filter,
129        })
130    }
131
132    pub fn copy_from(
133        &self,
134        encoder: &ProtocolObject<dyn MTLBlitCommandEncoder>,
135        other: &ProtocolObject<dyn MTLTexture>,
136    ) -> Result<()> {
137        unsafe {
138            encoder.copyFromTexture_toTexture(other, &self.texture);
139        }
140
141        if self.texture.mipmapLevelCount() > 1 {
142            encoder.generateMipmapsForTexture(&self.texture);
143        }
144
145        Ok(())
146    }
147
148    pub fn generate_mipmaps(&self, cmd: &ProtocolObject<dyn MTLCommandBuffer>) -> Result<()> {
149        let mipmapper = cmd
150            .blitCommandEncoder()
151            .ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
152        if self.texture.mipmapLevelCount() > 1 {
153            mipmapper.generateMipmapsForTexture(&self.texture);
154        }
155        mipmapper.endEncoding();
156        Ok(())
157    }
158}
159
160impl ScaleFramebuffer for OwnedTexture {
161    type Error = FilterChainError;
162    type Context = ProtocolObject<dyn MTLDevice>;
163
164    fn scale(
165        &mut self,
166        scaling: Scale2D,
167        format: ImageFormat,
168        viewport_size: &Size<u32>,
169        source_size: &Size<u32>,
170        original_size: &Size<u32>,
171        should_mipmap: bool,
172        context: &Self::Context,
173    ) -> std::result::Result<Size<u32>, Self::Error> {
174        Ok(self.scale(
175            &context,
176            scaling,
177            format.into(),
178            viewport_size,
179            source_size,
180            original_size,
181            should_mipmap,
182        )?)
183    }
184}
185
186pub(crate) fn get_texture_size(texture: &ProtocolObject<dyn MTLTexture>) -> Size<u32> {
187    let height = texture.height();
188    let width = texture.width();
189    Size {
190        height: height as u32,
191        width: width as u32,
192    }
193}