Skip to main content

pebble/wgpu/
texture_array.rs

1use crate::{
2    assets::{plugin::AssetPlugin, upload::Asset},
3    ecs::{plugin::Plugin, system::Res},
4    wgpu::{
5        backend::WGPUBackend,
6        mipmap::MipmapGenerator,
7        textures::{bytes_per_pixel, decode_file},
8    },
9};
10
11pub struct TextureArrayDescriptor {
12    /// One file path per layer. Every layer must decode to the same
13    /// `width`/`height`.
14    pub files: Option<Vec<&'static str>>,
15    pub width: u32,
16    pub height: u32,
17    pub format: wgpu::TextureFormat,
18    /// Raw pixel bytes per layer, used when `files` is `None`.
19    pub data: Option<Vec<Vec<u8>>>,
20    pub generate_mips: bool,
21}
22
23impl TextureArrayDescriptor {
24    pub fn from_files(files: Vec<&'static str>) -> Self {
25        Self {
26            files: Some(files),
27            width: 0,
28            height: 0,
29            format: wgpu::TextureFormat::Rgba8UnormSrgb,
30            data: None,
31            generate_mips: false,
32        }
33    }
34
35    pub fn from_data(width: u32, height: u32, format: wgpu::TextureFormat, layers: Vec<Vec<u8>>) -> Self {
36        Self {
37            files: None,
38            width,
39            height,
40            format,
41            data: Some(layers),
42            generate_mips: false,
43        }
44    }
45
46    pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
47        self.format = format;
48        self
49    }
50
51    pub fn with_mips(mut self) -> Self {
52        self.generate_mips = true;
53        self
54    }
55}
56
57pub struct GPUTextureArray {
58    pub texture: wgpu::Texture,
59    pub view: wgpu::TextureView,
60    pub layer_count: u32,
61}
62
63impl Asset<WGPUBackend> for GPUTextureArray {
64    type Source = TextureArrayDescriptor;
65    type Deps<'a> = Res<'a, MipmapGenerator>;
66
67    fn upload<'a>(
68        source: &TextureArrayDescriptor,
69        backend: &WGPUBackend,
70        mipmap_generator: &Res<'a, MipmapGenerator>,
71    ) -> Option<Self> {
72        let (width, height, layers): (u32, u32, Vec<Vec<u8>>) = if let Some(files) = &source.files {
73            let mut width = source.width;
74            let mut height = source.height;
75            let mut layers = Vec::with_capacity(files.len());
76            for (i, path) in files.iter().enumerate() {
77                let (w, h, data) = decode_file(path, source.format);
78                if i == 0 {
79                    width = w;
80                    height = h;
81                } else if w != width || h != height {
82                    tracing::error!(
83                        "TextureArraySpec: layer {i} ('{path}') is {w}x{h}, expected {width}x{height}"
84                    );
85                    return None;
86                }
87                layers.push(data);
88            }
89            (width, height, layers)
90        } else if let Some(data) = &source.data {
91            (source.width, source.height, data.clone())
92        } else {
93            tracing::error!("TextureArraySpec has neither `files` nor `data` set");
94            return None;
95        };
96
97        if layers.is_empty() {
98            tracing::error!("TextureArraySpec resolved to zero layers");
99            return None;
100        }
101        let layer_count = layers.len() as u32;
102
103        let mip_count = if source.generate_mips {
104            (width.max(height) as f32).log2().floor() as u32 + 1
105        } else {
106            1
107        };
108
109        let texture = backend.device.create_texture(&wgpu::TextureDescriptor {
110            label: None,
111            size: wgpu::Extent3d {
112                width,
113                height,
114                depth_or_array_layers: layer_count,
115            },
116            mip_level_count: mip_count,
117            sample_count: 1,
118            dimension: wgpu::TextureDimension::D2,
119            format: source.format,
120            usage: if mip_count > 1 {
121                wgpu::TextureUsages::TEXTURE_BINDING
122                    | wgpu::TextureUsages::COPY_DST
123                    | wgpu::TextureUsages::RENDER_ATTACHMENT
124            } else {
125                wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST
126            },
127            view_formats: &[],
128        });
129
130        for (layer, data) in layers.iter().enumerate() {
131            backend.queue.write_texture(
132                wgpu::TexelCopyTextureInfo {
133                    texture: &texture,
134                    mip_level: 0,
135                    origin: wgpu::Origin3d {
136                        x: 0,
137                        y: 0,
138                        z: layer as u32,
139                    },
140                    aspect: wgpu::TextureAspect::All,
141                },
142                data,
143                wgpu::TexelCopyBufferLayout {
144                    offset: 0,
145                    bytes_per_row: Some(bytes_per_pixel(source.format) * width),
146                    rows_per_image: Some(height),
147                },
148                wgpu::Extent3d {
149                    width,
150                    height,
151                    depth_or_array_layers: 1,
152                },
153            );
154        }
155
156        if mip_count > 1 {
157            mipmap_generator.generate_mips(
158                &backend.device,
159                &backend.queue,
160                &texture,
161                source.format,
162                mip_count,
163                layer_count,
164            );
165        }
166
167        let view = texture.create_view(&wgpu::TextureViewDescriptor {
168            dimension: Some(wgpu::TextureViewDimension::D2Array),
169            ..Default::default()
170        });
171        Some(Self {
172            texture,
173            view,
174            layer_count,
175        })
176    }
177}
178
179#[derive(Default)]
180pub struct TextureArrayPlugin;
181impl TextureArrayPlugin {
182    pub fn new() -> Self {
183        Self
184    }
185}
186impl Plugin for TextureArrayPlugin {
187    fn build(&self, app: &mut crate::prelude::App) {
188        app.add_plugin(crate::assets::singleton_asset::LazyResourcePlugin::<
189            WGPUBackend,
190            MipmapGenerator,
191        >::new());
192        app.add_plugin(AssetPlugin::<WGPUBackend, GPUTextureArray>::new());
193    }
194}