comfy_wgpu/
texture.rs

1use crate::*;
2
3use image::GenericImageView;
4use image::ImageResult;
5
6#[derive(Debug)]
7pub struct TextureCreationParams<'a> {
8    pub label: Option<&'a str>,
9    pub width: u32,
10    pub height: u32,
11    pub format: wgpu::TextureFormat,
12    pub mip_level_count: u32,
13    pub filter_mode: wgpu::FilterMode,
14    pub render_scale: f32,
15    pub view_formats: &'a [wgpu::TextureFormat],
16}
17
18impl Default for TextureCreationParams<'_> {
19    fn default() -> Self {
20        Self {
21            label: None,
22            width: 0,
23            height: 0,
24            format: wgpu::TextureFormat::Rgba16Float,
25            mip_level_count: 1,
26            filter_mode: wgpu::FilterMode::Linear,
27            render_scale: 1.0,
28            view_formats: &[],
29        }
30    }
31}
32
33#[derive(Debug)]
34pub struct BindableTexture {
35    pub texture: Texture,
36    pub bind_group: wgpu::BindGroup,
37}
38
39impl BindableTexture {
40    pub fn new(
41        device: &wgpu::Device,
42        layout: &wgpu::BindGroupLayout,
43        params: &TextureCreationParams,
44    ) -> Self {
45        let texture = Texture::create_with_params(device, params);
46
47        let label = params.label.map(|x| format!("{} Bind Group", x));
48
49        let bind_group =
50            device.simple_bind_group(label.as_deref(), &texture, layout);
51
52        Self { texture, bind_group }
53    }
54}
55
56#[derive(Debug)]
57pub struct Texture {
58    pub texture: wgpu::Texture,
59    pub view: wgpu::TextureView,
60    pub sampler: wgpu::Sampler,
61}
62
63impl Texture {
64    pub const DEPTH_FORMAT: wgpu::TextureFormat =
65        wgpu::TextureFormat::Depth32Float;
66
67    pub fn handle(&self) -> TextureHandle {
68        TextureHandle::Raw(default_hash(&self.texture.global_id()))
69    }
70
71    pub fn create_depth_texture(
72        device: &wgpu::Device,
73        config: &wgpu::SurfaceConfiguration,
74        label: &str,
75    ) -> Self {
76        let size = wgpu::Extent3d {
77            width: config.width,
78            height: config.height,
79            depth_or_array_layers: 1,
80        };
81
82        let desc = wgpu::TextureDescriptor {
83            label: Some(label),
84            size,
85            mip_level_count: 1,
86            sample_count: 1,
87            dimension: wgpu::TextureDimension::D2,
88            format: Self::DEPTH_FORMAT,
89            usage: wgpu::TextureUsages::RENDER_ATTACHMENT |
90                wgpu::TextureUsages::TEXTURE_BINDING,
91            view_formats: &[],
92        };
93
94        let texture = device.create_texture(&desc);
95
96        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
97        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
98            address_mode_u: wgpu::AddressMode::ClampToEdge,
99            address_mode_v: wgpu::AddressMode::ClampToEdge,
100            address_mode_w: wgpu::AddressMode::ClampToEdge,
101            mag_filter: wgpu::FilterMode::Linear,
102            min_filter: wgpu::FilterMode::Linear,
103            mipmap_filter: wgpu::FilterMode::Nearest,
104            compare: Some(wgpu::CompareFunction::LessEqual),
105            lod_min_clamp: 0.0,
106            lod_max_clamp: 100.0,
107            ..Default::default()
108        });
109
110        Self { texture, view, sampler }
111    }
112
113    pub fn create_with_params(
114        device: &wgpu::Device,
115        params: &TextureCreationParams,
116    ) -> Self {
117        let size = wgpu::Extent3d {
118            width: ((params.width as f32) * params.render_scale.sqrt()).round()
119                as u32,
120            height: ((params.height as f32) * params.render_scale.sqrt())
121                .round() as u32,
122            depth_or_array_layers: 1,
123        };
124
125        let texture = device.create_texture(&wgpu::TextureDescriptor {
126            label: params.label,
127            size,
128            mip_level_count: params.mip_level_count,
129            sample_count: 1,
130            dimension: wgpu::TextureDimension::D2,
131            format: params.format,
132            usage: wgpu::TextureUsages::TEXTURE_BINDING |
133                wgpu::TextureUsages::COPY_DST |
134                wgpu::TextureUsages::RENDER_ATTACHMENT,
135            view_formats: params.view_formats,
136        });
137
138        let view_label = params.label.map(|x| format!("{} View", x));
139
140        let view = texture.create_view(&wgpu::TextureViewDescriptor {
141            label: view_label.as_deref(),
142            // TODO: fix this and move it to the pp layer instead
143            mip_level_count: if params.mip_level_count > 0 {
144                Some(1)
145            } else {
146                None
147            },
148            ..Default::default()
149        });
150
151        let sampler_label = params.label.map(|x| format!("{} Sampler", x));
152
153        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
154            label: sampler_label.as_deref(),
155            address_mode_u: wgpu::AddressMode::ClampToEdge,
156            address_mode_v: wgpu::AddressMode::ClampToEdge,
157            address_mode_w: wgpu::AddressMode::ClampToEdge,
158            mag_filter: params.filter_mode,
159            min_filter: params.filter_mode,
160            mipmap_filter: wgpu::FilterMode::Nearest,
161            ..Default::default()
162        });
163
164        Self {
165            texture,
166            view,
167            sampler,
168            // size,
169        }
170    }
171
172    pub fn create_scaled_mip_filter_surface_texture(
173        device: &wgpu::Device,
174        config: &wgpu::SurfaceConfiguration,
175        format: wgpu::TextureFormat,
176        render_scale: f32,
177        mip_level_count: u32,
178        filter_mode: wgpu::FilterMode,
179        label: &str,
180    ) -> Self {
181        Self::create_with_params(device, &TextureCreationParams {
182            label: Some(label),
183            width: config.width,
184            height: config.height,
185            format,
186            mip_level_count,
187            filter_mode,
188            render_scale,
189            view_formats: &[],
190        })
191    }
192
193    pub fn from_bytes(
194        device: &wgpu::Device,
195        queue: &wgpu::Queue,
196        bytes: &[u8],
197        label: &str,
198        is_normal_map: bool,
199    ) -> ImageResult<(DynamicImage, Self)> {
200        let img = image::load_from_memory(bytes)?;
201        let tex =
202            Self::from_image(device, queue, &img, Some(label), is_normal_map)?;
203
204        Ok((img, tex))
205    }
206
207    pub fn from_image(
208        device: &wgpu::Device,
209        queue: &wgpu::Queue,
210        img: &image::DynamicImage,
211        label: Option<&str>,
212        is_normal_map: bool,
213    ) -> ImageResult<Self> {
214        Self::from_image_ex(
215            device,
216            queue,
217            img,
218            label,
219            is_normal_map,
220            wgpu::AddressMode::Repeat,
221        )
222    }
223
224    pub fn from_image_ex(
225        device: &wgpu::Device,
226        queue: &wgpu::Queue,
227        img: &image::DynamicImage,
228        label: Option<&str>,
229        is_normal_map: bool,
230        address_mode: wgpu::AddressMode,
231    ) -> ImageResult<Self> {
232        let format = if is_normal_map {
233            wgpu::TextureFormat::Rgba8Unorm
234        } else {
235            wgpu::TextureFormat::Rgba8UnormSrgb
236        };
237
238        Self::from_image_with_format(
239            device,
240            queue,
241            img,
242            label,
243            address_mode,
244            format,
245        )
246    }
247
248    pub fn from_image_with_format(
249        device: &wgpu::Device,
250        queue: &wgpu::Queue,
251        img: &image::DynamicImage,
252        label: Option<&str>,
253        address_mode: wgpu::AddressMode,
254        format: wgpu::TextureFormat,
255    ) -> ImageResult<Self> {
256        let img = img.flipv();
257        let rgba = img.to_rgba8();
258        let dimensions = img.dimensions();
259
260        Self::from_image_data_with_format(
261            device,
262            queue,
263            &rgba,
264            label,
265            address_mode,
266            format,
267            dimensions,
268            4,
269        )
270    }
271
272    pub fn from_image_data_with_format(
273        device: &wgpu::Device,
274        queue: &wgpu::Queue,
275        img_data: &[u8],
276        label: Option<&str>,
277        address_mode: wgpu::AddressMode,
278        format: wgpu::TextureFormat,
279        dimensions: (u32, u32),
280        bytes_per_pixel: u32,
281    ) -> ImageResult<Self> {
282        let size = wgpu::Extent3d {
283            width: dimensions.0,
284            height: dimensions.1,
285            depth_or_array_layers: 1,
286        };
287
288        let texture = device.create_texture(&wgpu::TextureDescriptor {
289            label,
290            size,
291            mip_level_count: 1,
292            sample_count: 1,
293            dimension: wgpu::TextureDimension::D2,
294            format,
295            usage: wgpu::TextureUsages::TEXTURE_BINDING |
296                wgpu::TextureUsages::COPY_DST,
297            view_formats: &[],
298        });
299
300        queue.write_texture(
301            wgpu::ImageCopyTexture {
302                aspect: wgpu::TextureAspect::All,
303                texture: &texture,
304                mip_level: 0,
305                origin: wgpu::Origin3d::ZERO,
306            },
307            img_data,
308            wgpu::ImageDataLayout {
309                offset: 0,
310                bytes_per_row: Some(bytes_per_pixel * dimensions.0),
311                rows_per_image: Some(dimensions.1),
312            },
313            size,
314        );
315
316        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
317
318        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
319            address_mode_u: address_mode,
320            address_mode_v: address_mode,
321            address_mode_w: address_mode,
322            // TODO: configure this
323            mag_filter: wgpu::FilterMode::Nearest,
324            min_filter: wgpu::FilterMode::Nearest,
325            mipmap_filter: wgpu::FilterMode::Nearest,
326            ..Default::default()
327        });
328
329        Ok(Self { texture, view, sampler })
330    }
331
332    pub fn from_image_uninit(
333        device: &wgpu::Device,
334        img: &image::DynamicImage,
335        label: Option<&str>,
336    ) -> ImageResult<Self> {
337        let dimensions = img.dimensions();
338        assert!(dimensions.0 > 0 && dimensions.1 > 0);
339        Self::create_uninit(device, dimensions.0, dimensions.1, label)
340    }
341
342    pub fn create_uninit(
343        device: &wgpu::Device,
344        width: u32,
345        height: u32,
346        label: Option<&str>,
347    ) -> ImageResult<Self> {
348        let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1 };
349
350        let format = wgpu::TextureFormat::Rgba8UnormSrgb;
351
352        let texture = device.create_texture(&wgpu::TextureDescriptor {
353            label,
354            size,
355            mip_level_count: 1,
356            sample_count: 1,
357            dimension: wgpu::TextureDimension::D2,
358            format,
359            usage: wgpu::TextureUsages::TEXTURE_BINDING |
360                wgpu::TextureUsages::COPY_DST,
361            view_formats: &[],
362        });
363
364        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
365
366        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
367            address_mode_u: wgpu::AddressMode::ClampToEdge,
368            address_mode_v: wgpu::AddressMode::ClampToEdge,
369            address_mode_w: wgpu::AddressMode::ClampToEdge,
370            mag_filter: wgpu::FilterMode::Nearest,
371            min_filter: wgpu::FilterMode::Nearest,
372            mipmap_filter: wgpu::FilterMode::Nearest,
373            ..Default::default()
374        });
375
376        Ok(Self { texture, view, sampler })
377    }
378}