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