Skip to main content

pebble/wgpu/
textures.rs

1pub struct TextureSpec {
2    pub width: u32,
3    pub height: u32,
4    pub format: wgpu::TextureFormat,
5    /// Base (mip 0) pixel data, tightly packed, 4 bytes per texel (matching
6    /// `format`). Required even when `format` isn't literally `Rgba8*` —
7    /// mip generation always treats it as 4 bytes/texel.
8    pub data: Vec<u8>,
9    pub generate_mips: bool,
10}
11
12impl TextureSpec {
13    /// Number of mip levels this texture will have: the full chain down to
14    /// 1x1 if `generate_mips`, otherwise just the base level.
15    pub fn mip_level_count(&self) -> u32 {
16        if self.generate_mips {
17            32 - self.width.max(self.height).max(1).leading_zeros()
18        } else {
19            1
20        }
21    }
22
23    /// Pure conversion — you call `device.create_texture(&spec.wgpu_descriptor())`
24    /// yourself, then write each mip level (see [`downsample`](Self::downsample)
25    /// for generating each one from the last).
26    pub fn wgpu_descriptor(&self) -> wgpu::TextureDescriptor<'static> {
27        wgpu::TextureDescriptor {
28            label: None,
29            size: wgpu::Extent3d {
30                width: self.width,
31                height: self.height,
32                depth_or_array_layers: 1,
33            },
34            mip_level_count: self.mip_level_count(),
35            sample_count: 1,
36            dimension: wgpu::TextureDimension::D2,
37            format: self.format,
38            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
39            view_formats: &[],
40        }
41    }
42
43    /// Box-filter downsample to half size (minimum 1x1), 4 bytes/texel.
44    /// Pure data transform — no wgpu involved. Call this once per mip level
45    /// between your own `queue.write_texture` calls to build the chain
46    /// yourself; see [`mip_level_count`](Self::mip_level_count) for how many
47    /// levels to write in total.
48    pub fn downsample(data: &[u8], width: u32, height: u32) -> (Vec<u8>, u32, u32) {
49        let next_width = (width / 2).max(1);
50        let next_height = (height / 2).max(1);
51        let mut out = vec![0u8; (next_width * next_height * 4) as usize];
52
53        for y in 0..next_height {
54            for x in 0..next_width {
55                let x0 = (x * 2).min(width - 1);
56                let x1 = (x * 2 + 1).min(width - 1);
57                let y0 = (y * 2).min(height - 1);
58                let y1 = (y * 2 + 1).min(height - 1);
59
60                for c in 0..4 {
61                    let sample = |sx: u32, sy: u32| -> u32 {
62                        data[((sy * width + sx) * 4 + c) as usize] as u32
63                    };
64                    let avg = (sample(x0, y0) + sample(x1, y0) + sample(x0, y1) + sample(x1, y1))
65                        / 4;
66                    out[((y * next_width + x) * 4 + c) as usize] = avg as u8;
67                }
68            }
69        }
70
71        (out, next_width, next_height)
72    }
73}
74
75impl TextureSpec {
76    pub fn new(width: u32, height: u32, data: Vec<u8>, generate_mips: bool) -> Self {
77        Self {
78            width,
79            height,
80            format: wgpu::TextureFormat::Rgba8Unorm,
81            data,
82            generate_mips,
83        }
84    }
85
86    pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
87        self.format = format;
88        self
89    }
90}