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::TexelCopyTextureInfo {
63                texture: &tex,
64                mip_level: 0,
65                origin: wgpu::Origin3d::ZERO,
66                aspect: wgpu::TextureAspect::All,
67            },
68            buffer.as_slice(),
69            wgpu::TexelCopyBufferLayout {
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            usage: None,
97            aspect: wgpu::TextureAspect::All,
98            base_mip_level: 0,
99            mip_level_count: None,
100            base_array_layer: 0,
101            array_layer_count: None,
102        });
103
104        Ok(Self {
105            tex,
106            view,
107            sampler: device.create_sampler(&sampler_info),
108            size,
109            format,
110        })
111    }
112
113    pub fn new_dynamic(
114        device: &wgpu::Device,
115        queue: &wgpu::Queue,
116        width: u32,
117        height: u32,
118    ) -> Self {
119        let size = Extent3d {
120            width,
121            height,
122            depth_or_array_layers: 1,
123        };
124
125        let tex_info = wgpu::TextureDescriptor {
126            label: None,
127            size,
128            mip_level_count: 1,
129            sample_count: 1,
130            dimension: wgpu::TextureDimension::D2,
131            format: wgpu::TextureFormat::Rgba8UnormSrgb,
132            // TODO: nondynamic version doesn't seeem to have different usage, unify code?
133            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
134            view_formats: &[],
135        };
136
137        let sampler_info = wgpu::SamplerDescriptor {
138            label: None,
139            address_mode_u: wgpu::AddressMode::ClampToEdge,
140            address_mode_v: wgpu::AddressMode::ClampToEdge,
141            address_mode_w: wgpu::AddressMode::ClampToEdge,
142            mag_filter: wgpu::FilterMode::Linear,
143            min_filter: wgpu::FilterMode::Linear,
144            mipmap_filter: wgpu::FilterMode::Nearest,
145            ..Default::default()
146        };
147
148        let view_info = wgpu::TextureViewDescriptor {
149            label: None,
150            format: Some(wgpu::TextureFormat::Rgba8UnormSrgb),
151            dimension: Some(wgpu::TextureViewDimension::D2),
152            usage: None,
153            aspect: wgpu::TextureAspect::All,
154            base_mip_level: 0,
155            mip_level_count: None,
156            base_array_layer: 0,
157            array_layer_count: None,
158        };
159
160        let texture = Self::new_raw(device, &tex_info, &view_info, &sampler_info);
161        texture.clear(queue); // Needs to be fully initialized for partial writes to work on Dx12 AMD
162        texture
163    }
164
165    /// Note: the user is responsible for making sure the texture is fully
166    /// initialized before doing partial writes on Dx12 AMD: <https://github.com/gfx-rs/wgpu/issues/1306>
167    pub fn new_raw(
168        device: &wgpu::Device,
169        texture_info: &wgpu::TextureDescriptor,
170        view_info: &wgpu::TextureViewDescriptor,
171        sampler_info: &wgpu::SamplerDescriptor,
172    ) -> Self {
173        let tex = device.create_texture(texture_info);
174        let view = tex.create_view(view_info);
175
176        Self {
177            tex,
178            view,
179            sampler: device.create_sampler(sampler_info),
180            size: texture_info.size,
181            format: texture_info.format,
182        }
183    }
184
185    /// Clears the texture data to 0
186    pub fn clear(&self, queue: &wgpu::Queue) {
187        let size = self.size;
188        let byte_len = size.width as usize
189            * size.height as usize
190            * size.depth_or_array_layers as usize
191            * self.format.block_copy_size(None).unwrap() as usize;
192        let zeros = vec![0; byte_len];
193
194        self.update(queue, [0, 0], [size.width, size.height], &zeros);
195    }
196
197    /// Update a texture with the given data (used for updating the glyph cache
198    /// texture).
199    pub fn update(&self, queue: &wgpu::Queue, offset: [u32; 2], size: [u32; 2], data: &[u8]) {
200        let bytes_per_pixel = self.format.block_copy_size(None).unwrap();
201
202        debug_assert_eq!(
203            data.len(),
204            size[0] as usize * size[1] as usize * bytes_per_pixel as usize
205        );
206        // TODO: Only works for 2D images
207        queue.write_texture(
208            wgpu::TexelCopyTextureInfo {
209                texture: &self.tex,
210                mip_level: 0,
211                origin: wgpu::Origin3d {
212                    x: offset[0],
213                    y: offset[1],
214                    z: 0,
215                },
216                aspect: wgpu::TextureAspect::All,
217            },
218            data,
219            wgpu::TexelCopyBufferLayout {
220                offset: 0,
221                bytes_per_row: Some(size[0] * bytes_per_pixel),
222                rows_per_image: Some(size[1]),
223            },
224            Extent3d {
225                width: size[0],
226                height: size[1],
227                depth_or_array_layers: 1,
228            },
229        );
230    }
231
232    // TODO: remove `get` from this name
233    /// Get dimensions of the represented image.
234    pub fn get_dimensions(&self) -> vek::Vec3<u32> {
235        vek::Vec3::new(
236            self.size.width,
237            self.size.height,
238            self.size.depth_or_array_layers,
239        )
240    }
241}