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 with_format(mut self, format: wgpu::TextureFormat) -> Self {
25        self.format = format;
26        self
27    }
28}
29
30pub struct GPUTextureArray {
31    pub texture: wgpu::Texture,
32    pub view: wgpu::TextureView,
33    pub layer_count: u32,
34}
35
36impl Asset<WGPUBackend> for GPUTextureArray {
37    type Source = TextureArrayDescriptor;
38    type Deps<'a> = Res<'a, MipmapGenerator>;
39
40    fn upload<'a>(
41        source: &TextureArrayDescriptor,
42        backend: &WGPUBackend,
43        mipmap_generator: &Res<'a, MipmapGenerator>,
44    ) -> Option<Self> {
45        let (width, height, layers): (u32, u32, Vec<Vec<u8>>) = if let Some(files) = &source.files {
46            let mut width = source.width;
47            let mut height = source.height;
48            let mut layers = Vec::with_capacity(files.len());
49            for (i, path) in files.iter().enumerate() {
50                let (w, h, data) = decode_file(path, source.format);
51                if i == 0 {
52                    width = w;
53                    height = h;
54                } else if w != width || h != height {
55                    tracing::error!(
56                        "TextureArraySpec: layer {i} ('{path}') is {w}x{h}, expected {width}x{height}"
57                    );
58                    return None;
59                }
60                layers.push(data);
61            }
62            (width, height, layers)
63        } else if let Some(data) = &source.data {
64            (source.width, source.height, data.clone())
65        } else {
66            tracing::error!("TextureArraySpec has neither `files` nor `data` set");
67            return None;
68        };
69
70        if layers.is_empty() {
71            tracing::error!("TextureArraySpec resolved to zero layers");
72            return None;
73        }
74        let layer_count = layers.len() as u32;
75
76        let mip_count = if source.generate_mips {
77            (width.max(height) as f32).log2().floor() as u32 + 1
78        } else {
79            1
80        };
81
82        let texture = backend.device.create_texture(&wgpu::TextureDescriptor {
83            label: None,
84            size: wgpu::Extent3d {
85                width,
86                height,
87                depth_or_array_layers: layer_count,
88            },
89            mip_level_count: mip_count,
90            sample_count: 1,
91            dimension: wgpu::TextureDimension::D2,
92            format: source.format,
93            usage: if mip_count > 1 {
94                wgpu::TextureUsages::TEXTURE_BINDING
95                    | wgpu::TextureUsages::COPY_DST
96                    | wgpu::TextureUsages::RENDER_ATTACHMENT
97            } else {
98                wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST
99            },
100            view_formats: &[],
101        });
102
103        for (layer, data) in layers.iter().enumerate() {
104            backend.queue.write_texture(
105                wgpu::TexelCopyTextureInfo {
106                    texture: &texture,
107                    mip_level: 0,
108                    origin: wgpu::Origin3d {
109                        x: 0,
110                        y: 0,
111                        z: layer as u32,
112                    },
113                    aspect: wgpu::TextureAspect::All,
114                },
115                data,
116                wgpu::TexelCopyBufferLayout {
117                    offset: 0,
118                    bytes_per_row: Some(bytes_per_pixel(source.format) * width),
119                    rows_per_image: Some(height),
120                },
121                wgpu::Extent3d {
122                    width,
123                    height,
124                    depth_or_array_layers: 1,
125                },
126            );
127        }
128
129        if mip_count > 1 {
130            mipmap_generator.generate_mips(
131                &backend.device,
132                &backend.queue,
133                &texture,
134                source.format,
135                mip_count,
136                layer_count,
137            );
138        }
139
140        let view = texture.create_view(&wgpu::TextureViewDescriptor {
141            dimension: Some(wgpu::TextureViewDimension::D2Array),
142            ..Default::default()
143        });
144        Some(Self {
145            texture,
146            view,
147            layer_count,
148        })
149    }
150}
151
152pub struct TextureArrayPlugin;
153impl Plugin for TextureArrayPlugin {
154    fn build(&self, app: &mut crate::prelude::App) {
155        app.add_plugin(crate::assets::singleton_asset::LazyResourcePlugin::<
156            WGPUBackend,
157            MipmapGenerator,
158        >::new());
159        app.add_plugin(AssetPlugin::<WGPUBackend, GPUTextureArray>::new());
160    }
161}