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 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 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}