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