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