veloren_voxygen/render/
texture.rs

1use super::RenderError;
2use image::DynamicImage;
3use wgpu::Extent3d;
4
5/// Represents an image that has been uploaded to the GPU.
6pub struct Texture {
7    pub tex: wgpu::Texture,
8    pub view: wgpu::TextureView,
9    pub sampler: wgpu::Sampler,
10    size: Extent3d,
11    /// TODO: consider making Texture generic over the format
12    format: wgpu::TextureFormat,
13}
14
15impl Texture {
16    pub fn new(
17        device: &wgpu::Device,
18        queue: &wgpu::Queue,
19        image: &DynamicImage,
20        filter_method: Option<wgpu::FilterMode>,
21        address_mode: Option<wgpu::AddressMode>,
22    ) -> Result<Self, RenderError> {
23        let format = match image {
24            DynamicImage::ImageLuma8(_) => wgpu::TextureFormat::R8Unorm,
25            DynamicImage::ImageLumaA8(_) => panic!("ImageLuma8 unsupported"),
26            DynamicImage::ImageRgb8(_) => panic!("ImageRgb8 unsupported"),
27            DynamicImage::ImageRgba8(_) => wgpu::TextureFormat::Rgba8UnormSrgb,
28            DynamicImage::ImageLuma16(_) => panic!("ImageLuma16 unsupported"),
29            DynamicImage::ImageLumaA16(_) => panic!("ImageLumaA16 unsupported"),
30            DynamicImage::ImageRgb16(_) => panic!("ImageRgb16 unsupported"),
31            DynamicImage::ImageRgba16(_) => panic!("ImageRgba16 unsupported"),
32            _ => panic!("unsupported format"),
33        };
34
35        // TODO: Actually handle images that aren't in rgba format properly.
36        let buffer = image.as_flat_samples_u8().ok_or_else(|| {
37            RenderError::CustomError(
38                "We currently do not support color formats using more than 4 bytes / pixel.".into(),
39            )
40        })?;
41
42        let bytes_per_pixel = u32::from(buffer.layout.channels);
43
44        let size = Extent3d {
45            width: image.width(),
46            height: image.height(),
47            depth_or_array_layers: 1,
48        };
49
50        let tex = device.create_texture(&wgpu::TextureDescriptor {
51            label: None,
52            size,
53            mip_level_count: 1,
54            sample_count: 1,
55            dimension: wgpu::TextureDimension::D2,
56            format,
57            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
58            view_formats: &[],
59        });
60
61        queue.write_texture(
62            wgpu::ImageCopyTexture {
63                texture: &tex,
64                mip_level: 0,
65                origin: wgpu::Origin3d::ZERO,
66                aspect: wgpu::TextureAspect::All,
67            },
68            buffer.as_slice(),
69            wgpu::ImageDataLayout {
70                offset: 0,
71                bytes_per_row: Some(image.width() * bytes_per_pixel),
72                rows_per_image: Some(image.height()),
73            },
74            Extent3d {
75                width: image.width(),
76                height: image.height(),
77                depth_or_array_layers: 1,
78            },
79        );
80
81        let sampler_info = wgpu::SamplerDescriptor {
82            label: None,
83            address_mode_u: address_mode.unwrap_or(wgpu::AddressMode::ClampToEdge),
84            address_mode_v: address_mode.unwrap_or(wgpu::AddressMode::ClampToEdge),
85            address_mode_w: address_mode.unwrap_or(wgpu::AddressMode::ClampToEdge),
86            mag_filter: filter_method.unwrap_or(wgpu::FilterMode::Nearest),
87            min_filter: filter_method.unwrap_or(wgpu::FilterMode::Nearest),
88            mipmap_filter: wgpu::FilterMode::Nearest,
89            ..Default::default()
90        };
91
92        let view = tex.create_view(&wgpu::TextureViewDescriptor {
93            label: None,
94            format: Some(format),
95            dimension: Some(wgpu::TextureViewDimension::D2),
96            aspect: wgpu::TextureAspect::All,
97            base_mip_level: 0,
98            mip_level_count: None,
99            base_array_layer: 0,
100            array_layer_count: None,
101        });
102
103        Ok(Self {
104            tex,
105            view,
106            sampler: device.create_sampler(&sampler_info),
107            size,
108            format,
109        })
110    }
111
112    pub fn new_dynamic(
113        device: &wgpu::Device,
114        queue: &wgpu::Queue,
115        width: u32,
116        height: u32,
117    ) -> Self {
118        let size = Extent3d {
119            width,
120            height,
121            depth_or_array_layers: 1,
122        };
123
124        let tex_info = wgpu::TextureDescriptor {
125            label: None,
126            size,
127            mip_level_count: 1,
128            sample_count: 1,
129            dimension: wgpu::TextureDimension::D2,
130            format: wgpu::TextureFormat::Rgba8UnormSrgb,
131            // TODO: nondynamic version doesn't seeem to have different usage, unify code?
132            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
133            view_formats: &[],
134        };
135
136        let sampler_info = wgpu::SamplerDescriptor {
137            label: None,
138            address_mode_u: wgpu::AddressMode::ClampToEdge,
139            address_mode_v: wgpu::AddressMode::ClampToEdge,
140            address_mode_w: wgpu::AddressMode::ClampToEdge,
141            mag_filter: wgpu::FilterMode::Linear,
142            min_filter: wgpu::FilterMode::Linear,
143            mipmap_filter: wgpu::FilterMode::Nearest,
144            ..Default::default()
145        };
146
147        let view_info = wgpu::TextureViewDescriptor {
148            label: None,
149            format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
150            dimension: Some(wgpu::TextureViewDimension::D2),
151            aspect: wgpu::TextureAspect::All,
152            base_mip_level: 0,
153            mip_level_count: None,
154            base_array_layer: 0,
155            array_layer_count: None,
156        };
157
158        let texture = Self::new_raw(device, &tex_info, &view_info, &sampler_info);
159        texture.clear(queue); // Needs to be fully initialized for partial writes to work on Dx12 AMD
160        texture
161    }
162
163    /// Note: the user is responsible for making sure the texture is fully
164    /// initialized before doing partial writes on Dx12 AMD: https://github.com/gfx-rs/wgpu/issues/1306
165    pub fn new_raw(
166        device: &wgpu::Device,
167        texture_info: &wgpu::TextureDescriptor,
168        view_info: &wgpu::TextureViewDescriptor,
169        sampler_info: &wgpu::SamplerDescriptor,
170    ) -> Self {
171        let tex = device.create_texture(texture_info);
172        let view = tex.create_view(view_info);
173
174        Self {
175            tex,
176            view,
177            sampler: device.create_sampler(sampler_info),
178            size: texture_info.size,
179            format: texture_info.format,
180        }
181    }
182
183    /// Clears the texture data to 0
184    pub fn clear(&self, queue: &wgpu::Queue) {
185        let size = self.size;
186        let byte_len = size.width as usize
187            * size.height as usize
188            * size.depth_or_array_layers as usize
189            * self.format.block_size(None).unwrap() as usize;
190        let zeros = vec![0; byte_len];
191
192        self.update(queue, [0, 0], [size.width, size.height], &zeros);
193    }
194
195    /// Update a texture with the given data (used for updating the glyph cache
196    /// texture).
197    pub fn update(&self, queue: &wgpu::Queue, offset: [u32; 2], size: [u32; 2], data: &[u8]) {
198        let bytes_per_pixel = self.format.block_size(None).unwrap();
199
200        debug_assert_eq!(
201            data.len(),
202            size[0] as usize * size[1] as usize * bytes_per_pixel as usize
203        );
204        // TODO: Only works for 2D images
205        queue.write_texture(
206            wgpu::ImageCopyTexture {
207                texture: &self.tex,
208                mip_level: 0,
209                origin: wgpu::Origin3d {
210                    x: offset[0],
211                    y: offset[1],
212                    z: 0,
213                },
214                aspect: wgpu::TextureAspect::All,
215            },
216            data,
217            wgpu::ImageDataLayout {
218                offset: 0,
219                bytes_per_row: Some(size[0] * bytes_per_pixel),
220                rows_per_image: Some(size[1]),
221            },
222            Extent3d {
223                width: size[0],
224                height: size[1],
225                depth_or_array_layers: 1,
226            },
227        );
228    }
229
230    // TODO: remove `get` from this name
231    /// Get dimensions of the represented image.
232    pub fn get_dimensions(&self) -> vek::Vec3<u32> {
233        vek::Vec3::new(
234            self.size.width,
235            self.size.height,
236            self.size.depth_or_array_layers,
237        )
238    }
239}