1use crate::{
2 assets::{handle::Handle, storage::Assets, upload::Asset},
3 ecs::system::Res,
4 wgpu::{
5 backend::WGPUBackend,
6 gpu_context::GpuContext,
7 mipmap::MipmapGenerator,
8 texture_format::TextureFormat,
9 textures::{bytes_per_pixel, check_texture_array_layers, check_texture_dimensions, decode_file, write_texture_level0},
10 },
11};
12
13pub struct TextureArray {
16 files: Option<Vec<&'static str>>,
19 width: u32,
21 height: u32,
23 format: TextureFormat,
25 data: Option<Vec<Vec<u8>>>,
27 generate_mips: bool,
29 layer_count: u32,
33}
34
35pub struct TextureArrayBuilder {
41 files: Option<Vec<&'static str>>,
42 width: u32,
43 height: u32,
44 format: TextureFormat,
45 data: Option<Vec<Vec<u8>>>,
46 generate_mips: bool,
47 layer_count: u32,
48}
49
50impl TextureArrayBuilder {
51 pub fn from_files(files: Vec<&'static str>) -> Self {
54 Self {
55 files: Some(files),
56 width: 0,
57 height: 0,
58 format: TextureFormat::Rgba8UnormSrgb,
59 data: None,
60 generate_mips: false,
61 layer_count: 0,
62 }
63 }
64
65 pub fn from_data(width: u32, height: u32, format: TextureFormat, layers: Vec<Vec<u8>>) -> Self {
67 Self {
68 files: None,
69 width,
70 height,
71 format,
72 data: Some(layers),
73 generate_mips: false,
74 layer_count: 0,
75 }
76 }
77
78 pub fn empty(width: u32, height: u32, format: TextureFormat, layer_count: u32) -> Self {
81 Self {
82 files: None,
83 width,
84 height,
85 format,
86 data: None,
87 generate_mips: false,
88 layer_count,
89 }
90 }
91
92 pub fn with_format(mut self, format: TextureFormat) -> Self {
93 self.format = format;
94 self
95 }
96
97 pub fn with_mips(mut self) -> Self {
98 self.generate_mips = true;
99 self
100 }
101
102 fn validate(&self) {
106 if self.data.is_some() && (self.width == 0 || self.height == 0) {
107 tracing::warn!(
108 "TextureArrayBuilder::from_data(): width/height is 0 ({}x{}) — did you swap the \
109 argument order, or forget to pass the real dimensions?",
110 self.width,
111 self.height,
112 );
113 }
114 }
115
116 pub fn build(self) -> TextureArray {
118 self.validate();
119 TextureArray {
120 files: self.files,
121 width: self.width,
122 height: self.height,
123 format: self.format,
124 data: self.data,
125 generate_mips: self.generate_mips,
126 layer_count: self.layer_count,
127 }
128 }
129
130 pub fn build_asset(self, name: &str, assets: &mut Assets<TextureArray>) -> Handle<TextureArray> {
133 let texture_array = self.build();
134 assets.insert(name, texture_array)
135 }
136}
137
138pub struct GPUTextureArray {
143 texture: wgpu::Texture,
144 view: wgpu::TextureView,
145 layer_count: u32,
146 width: u32,
147 height: u32,
148 format: TextureFormat,
149 ctx: GpuContext,
150}
151
152impl GPUTextureArray {
153 pub fn write_layer(&self, layer: u32, pixels: &[u8]) {
157 write_texture_level0(self.ctx.queue(), &self.texture, layer, self.format.into(), self.width, self.height, pixels);
158 }
159
160 pub fn layer_count(&self) -> u32 {
161 self.layer_count
162 }
163
164 pub fn width(&self) -> u32 {
165 self.width
166 }
167
168 pub fn height(&self) -> u32 {
169 self.height
170 }
171
172 pub(crate) fn view(&self) -> &wgpu::TextureView {
173 &self.view
174 }
175}
176
177impl Asset<WGPUBackend> for GPUTextureArray {
178 type Source = TextureArray;
179 type Deps<'a> = Res<'a, MipmapGenerator>;
180
181 fn upload<'a>(
182 source: &TextureArray,
183 backend: &WGPUBackend,
184 mipmap_generator: &Res<'a, MipmapGenerator>,
185 ) -> Option<Self> {
186 let (width, height, layer_count, layers): (u32, u32, u32, Option<Vec<Vec<u8>>>) =
187 if let Some(files) = &source.files {
188 let mut width = source.width;
189 let mut height = source.height;
190 let mut layers = Vec::with_capacity(files.len());
191 for (i, path) in files.iter().enumerate() {
192 let (w, h, data) = decode_file(path, source.format.into())?;
193 if i == 0 {
194 width = w;
195 height = h;
196 } else if w != width || h != height {
197 tracing::error!(
198 "TextureArraySpec: layer {i} ('{path}') is {w}x{h}, expected {width}x{height}"
199 );
200 return None;
201 }
202 layers.push(data);
203 }
204 let count = layers.len() as u32;
205 (width, height, count, Some(layers))
206 } else if let Some(data) = &source.data {
207 let count = data.len() as u32;
208 (source.width, source.height, count, Some(data.clone()))
209 } else {
210 (source.width, source.height, source.layer_count, None)
212 };
213
214 if layer_count == 0 {
215 tracing::error!("TextureArraySpec resolved to zero layers");
216 return None;
217 }
218
219 check_texture_dimensions(&backend.device, "GPUTextureArray", width, height);
220 check_texture_array_layers(&backend.device, "GPUTextureArray", layer_count);
221
222 let mip_count = super::mipmap::mip_count(width.max(height), source.generate_mips);
223
224 let texture = backend.device.create_texture(&wgpu::TextureDescriptor {
225 label: None,
226 size: wgpu::Extent3d {
227 width,
228 height,
229 depth_or_array_layers: layer_count,
230 },
231 mip_level_count: mip_count,
232 sample_count: 1,
233 dimension: wgpu::TextureDimension::D2,
234 format: source.format.into(),
235 usage: super::mipmap::texture_usage(mip_count),
236 view_formats: &[],
237 });
238
239 if let Some(layers) = &layers {
240 for (layer, data) in layers.iter().enumerate() {
241 backend.queue.write_texture(
242 wgpu::TexelCopyTextureInfo {
243 texture: &texture,
244 mip_level: 0,
245 origin: wgpu::Origin3d {
246 x: 0,
247 y: 0,
248 z: layer as u32,
249 },
250 aspect: wgpu::TextureAspect::All,
251 },
252 data,
253 wgpu::TexelCopyBufferLayout {
254 offset: 0,
255 bytes_per_row: Some(bytes_per_pixel(source.format.into()) * width),
256 rows_per_image: Some(height),
257 },
258 wgpu::Extent3d {
259 width,
260 height,
261 depth_or_array_layers: 1,
262 },
263 );
264 }
265
266 if mip_count > 1 {
267 mipmap_generator.generate_mips(
268 &backend.device,
269 &backend.queue,
270 &texture,
271 source.format.into(),
272 mip_count,
273 layer_count,
274 );
275 }
276 }
277
278 let view = texture.create_view(&wgpu::TextureViewDescriptor {
279 dimension: Some(wgpu::TextureViewDimension::D2Array),
280 ..Default::default()
281 });
282 Some(Self {
283 texture,
284 view,
285 layer_count,
286 width,
287 height,
288 format: source.format,
289 ctx: GpuContext::from_backend(backend),
290 })
291 }
292}
293
294crate::wgpu::plugin_macros::mipmap_asset_plugin! {
295 TextureArrayPlugin, GPUTextureArray
301}