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