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