pebble/graphics/pipeline/
texture_array.rs1use crate::{
2 assets::{handle::Handle, storage::Assets, upload::{Asset, AssetSource}},
3 ecs::resources::Read,
4 graphics::{
5 pipeline::{
6 mipmap::{MipLevels, MipmapGenerator},
7 texture_view::TextureView,
8 textures::{bytes_per_pixel, check_texture_array_layers, check_texture_dimensions, decode_file},
9 },
10 render::{Backend, gpu_context::GpuContext},
11 types::TextureFormat,
12 },
13};
14
15pub struct TextureArray {
19 files: Option<Vec<&'static str>>,
20 width: u32,
21 height: u32,
22 format: TextureFormat,
23 data: Option<Vec<Vec<u8>>>,
24 layer_count: u32,
25 mip_levels: MipLevels,
26}
27
28impl TextureArray {
29 pub fn from_files(files: Vec<&'static str>) -> Self {
30 Self { files: Some(files), width: 0, height: 0, format: TextureFormat::Rgba8UnormSrgb, data: None, layer_count: 0, mip_levels: MipLevels::None }
31 }
32
33 pub fn from_data(width: u32, height: u32, format: TextureFormat, layers: Vec<Vec<u8>>) -> Self {
34 Self { files: None, width, height, format, data: Some(layers), layer_count: 0, mip_levels: MipLevels::None }
35 }
36
37 pub fn empty(width: u32, height: u32, format: TextureFormat, layer_count: u32) -> Self {
39 Self { files: None, width, height, format, data: None, layer_count, mip_levels: MipLevels::None }
40 }
41
42 pub fn with_format(mut self, format: TextureFormat) -> Self {
43 self.format = format;
44 self
45 }
46
47 pub fn with_mips(mut self) -> Self {
48 self.mip_levels = MipLevels::Full;
49 self
50 }
51
52 pub fn with_mip_count(mut self, count: u32) -> Self {
53 self.mip_levels = MipLevels::Fixed(count);
54 self
55 }
56
57 fn validate(&self) {
58 if self.data.is_some() && (self.width == 0 || self.height == 0) {
59 tracing::warn!(
60 "TextureArray::from_data(): width/height is 0 ({}x{}) — did you swap the \
61 argument order, or forget to pass the real dimensions?",
62 self.width,
63 self.height,
64 );
65 }
66 }
67
68 pub fn build_asset(self, name: &str, assets: &mut Assets<TextureArray>) -> Handle<TextureArray> {
69 self.validate();
70 assets.insert(name, self)
71 }
72
73 pub fn data(&self) -> Option<&[Vec<u8>]> {
76 self.data.as_deref()
77 }
78
79 pub fn release_cpu_data(&mut self) {
82 self.data = None;
83 }
84}
85
86pub struct GPUTextureArray {
88 texture: wgpu::Texture,
89 view: wgpu::TextureView,
90 layer_count: u32,
91 width: u32,
92 height: u32,
93 format: TextureFormat,
94 ctx: GpuContext,
95}
96
97impl GPUTextureArray {
98 pub fn write_layer(&self, layer: u32, mip_level: u32, pixels: &[u8]) {
100 crate::graphics::pipeline::textures::write_texture_mip(
101 self.ctx.queue(),
102 &self.texture,
103 layer,
104 mip_level,
105 self.format.into(),
106 self.width,
107 self.height,
108 pixels,
109 );
110 }
111
112 pub fn layer_count(&self) -> u32 {
113 self.layer_count
114 }
115
116 pub fn width(&self) -> u32 {
117 self.width
118 }
119
120 pub fn height(&self) -> u32 {
121 self.height
122 }
123
124 pub fn get_view(&self, layer: u32, mip_level: u32) -> TextureView {
126 assert!(layer < self.layer_count, "GPUTextureArray::get_view: layer {layer} out of range (0..{})", self.layer_count);
127 let view = self.texture.create_view(&wgpu::TextureViewDescriptor {
128 dimension: Some(wgpu::TextureViewDimension::D2),
129 base_mip_level: mip_level,
130 mip_level_count: Some(1),
131 base_array_layer: layer,
132 array_layer_count: Some(1),
133 ..Default::default()
134 });
135 TextureView::from_raw(view, self.texture.clone())
136 }
137
138 pub(crate) fn view(&self) -> &wgpu::TextureView {
139 &self.view
140 }
141}
142
143impl AssetSource for TextureArray {
144 type Processed = GPUTextureArray;
145}
146
147impl Asset<Backend> for TextureArray {
148 type Deps<'a> = Read<'a, MipmapGenerator>;
149
150 fn upload<'a>(&self, backend: &Backend, mipmap_generator: &Read<'a, MipmapGenerator>) -> Option<GPUTextureArray> {
151 let (width, height, layer_count, layers): (u32, u32, u32, Option<Vec<Vec<u8>>>) =
152 if let Some(files) = &self.files {
153 let mut width = self.width;
154 let mut height = self.height;
155 let mut layers = Vec::with_capacity(files.len());
156 for (i, path) in files.iter().enumerate() {
157 let (w, h, data) = decode_file(path, self.format.into())?;
158 if i == 0 {
159 width = w;
160 height = h;
161 } else if w != width || h != height {
162 tracing::error!(
163 "TextureArraySpec: layer {i} ('{path}') is {w}x{h}, expected {width}x{height}"
164 );
165 return None;
166 }
167 layers.push(data);
168 }
169 let count = layers.len() as u32;
170 (width, height, count, Some(layers))
171 } else if let Some(data) = &self.data {
172 let count = data.len() as u32;
173 (self.width, self.height, count, Some(data.clone()))
174 } else {
175 (self.width, self.height, self.layer_count, None)
176 };
177
178 if layer_count == 0 {
179 tracing::error!("TextureArraySpec resolved to zero layers");
180 return None;
181 }
182
183 check_texture_dimensions(&backend.device, "GPUTextureArray", width, height);
184 check_texture_array_layers(&backend.device, "GPUTextureArray", layer_count);
185
186 let mip_count = crate::graphics::pipeline::mipmap::mip_count(width.max(height), self.mip_levels);
187 let usage = crate::graphics::pipeline::mipmap::texture_usage_for(mip_count, layers.is_some());
188
189 let texture = backend.device.create_texture(&wgpu::TextureDescriptor {
190 label: None,
191 size: wgpu::Extent3d { width, height, depth_or_array_layers: layer_count },
192 mip_level_count: mip_count,
193 sample_count: 1,
194 dimension: wgpu::TextureDimension::D2,
195 format: self.format.into(),
196 usage,
197 view_formats: &[],
198 });
199
200 if let Some(layers) = &layers {
201 for (layer, data) in layers.iter().enumerate() {
202 backend.queue.write_texture(
203 wgpu::TexelCopyTextureInfo {
204 texture: &texture,
205 mip_level: 0,
206 origin: wgpu::Origin3d { x: 0, y: 0, z: layer as u32 },
207 aspect: wgpu::TextureAspect::All,
208 },
209 data,
210 wgpu::TexelCopyBufferLayout {
211 offset: 0,
212 bytes_per_row: Some(bytes_per_pixel(self.format.into()) * width),
213 rows_per_image: Some(height),
214 },
215 wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
216 );
217 }
218
219 if mip_count > 1 {
220 mipmap_generator.generate_mips(backend, &texture, self.format.into(), mip_count, layer_count);
221 }
222 }
223
224 let view = texture.create_view(&wgpu::TextureViewDescriptor {
225 dimension: Some(wgpu::TextureViewDimension::D2Array),
226 ..Default::default()
227 });
228 Some(GPUTextureArray { texture, view, layer_count, width, height, format: self.format, ctx: GpuContext::from_backend(backend) })
229 }
230}