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