Skip to main content

libdqg/renderer/
texture.rs

1use crate::renderer::Renderer;
2use image::GenericImageView;
3use std::path::Path;
4
5pub struct Texture {
6    pub(crate) bind_group: wgpu::BindGroup,
7    pub width: u32,
8    pub height: u32,
9}
10
11impl Texture {
12    fn new(img: image::DynamicImage, renderer: &Renderer) -> Result<Self, String> {
13        let rgba = img.to_rgba8();
14        let dimensions = img.dimensions();
15
16        let size = wgpu::Extent3d {
17            width: dimensions.0,
18            height: dimensions.1,
19            depth_or_array_layers: 1,
20        };
21
22        let texture = renderer.device.create_texture(&wgpu::TextureDescriptor {
23            label: Some("Texture"),
24            size,
25            mip_level_count: 1,
26            sample_count: 1,
27            dimension: wgpu::TextureDimension::D2,
28            format: wgpu::TextureFormat::Rgba8UnormSrgb,
29            view_formats: &[],
30            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
31        });
32        renderer.queue.write_texture(
33            // Tells wgpu where to copy the pixel data
34            wgpu::TexelCopyTextureInfo {
35                texture: &texture,
36                mip_level: 0,
37                origin: wgpu::Origin3d::ZERO,
38                aspect: wgpu::TextureAspect::All,
39            },
40            // The actual pixel data
41            &rgba,
42            // The layout of the texture
43            wgpu::TexelCopyBufferLayout {
44                offset: 0,
45                bytes_per_row: Some(4 * dimensions.0),
46                rows_per_image: Some(dimensions.1),
47            },
48            size,
49        );
50        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
51
52        let bind_group = renderer.device.create_bind_group(&wgpu::BindGroupDescriptor {
53            layout: &renderer.texture_bind_group_layout,
54            entries: &[
55                wgpu::BindGroupEntry {
56                    binding: 0,
57                    resource: wgpu::BindingResource::TextureView(&view),
58                },
59                wgpu::BindGroupEntry {
60                    binding: 1,
61                    resource: wgpu::BindingResource::Sampler(&renderer.sampler),
62                },
63            ],
64            label: Some("diffuse_bind_group"),
65        });
66
67        Ok(Self {
68            bind_group,
69            width: dimensions.0,
70            height: dimensions.1
71        })
72    }
73
74    /// Creates a solid-color 1x1 texture. Used as a fallback for materials that don't
75    /// specify a diffuse texture.
76    pub fn from_color(renderer: &Renderer, color: [u8; 4]) -> Result<Self, String> {
77        let img = image::RgbaImage::from_pixel(1, 1, image::Rgba(color));
78        Self::new(image::DynamicImage::ImageRgba8(img), renderer)
79    }
80
81    pub fn from_bytes(renderer: &Renderer, bytes: &[u8]) -> Result<Self, String> {
82        let img = image::load_from_memory(bytes).map_err(|e| format!("Failed to load image: {}", e))?;
83        Self::new(img, renderer)
84    }
85    pub fn from_path(renderer: &Renderer, path: impl AsRef<Path>) -> Result<Self, String> {
86        let path = crate::util::resolve_resource_path(path);
87        let img = image::open(&path)
88            .map_err(|e| format!("Failed to load image {}: {}", path.display(), e))?;
89        Self::new(img, renderer)
90    }
91}