1use crate::{
2 assets::upload::Asset,
3 ecs::system::Res,
4 wgpu::{backend::WGPUBackend, mipmap::MipmapGenerator},
5};
6
7pub struct TextureDescriptor {
11 pub file: Option<&'static str>,
14 pub width: u32,
16 pub height: u32,
18 pub format: wgpu::TextureFormat,
20 pub data: Option<Vec<u8>>,
22 pub generate_mips: bool,
24}
25
26impl TextureDescriptor {
27 pub fn from_file(path: &'static str) -> Self {
30 Self {
31 file: Some(path),
32 width: 0,
33 height: 0,
34 format: wgpu::TextureFormat::Rgba8UnormSrgb,
35 data: None,
36 generate_mips: false,
37 }
38 }
39
40 pub fn from_data(width: u32, height: u32, format: wgpu::TextureFormat, data: Vec<u8>) -> Self {
42 Self {
43 file: None,
44 width,
45 height,
46 format,
47 data: Some(data),
48 generate_mips: false,
49 }
50 }
51
52 pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
53 self.format = format;
54 self
55 }
56
57 pub fn with_mips(mut self) -> Self {
58 self.generate_mips = true;
59 self
60 }
61}
62
63pub struct GPUTexture {
66 pub texture: wgpu::Texture,
67 pub view: wgpu::TextureView,
68}
69
70pub(crate) fn bytes_per_pixel(format: wgpu::TextureFormat) -> u32 {
72 match format {
73 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Rgba8UnormSrgb => 4,
74 wgpu::TextureFormat::Rgba16Float => 8,
75 wgpu::TextureFormat::Rgba32Float => 16,
76 other => panic!("unsupported texture format for GPUTexture: {other:?}"),
77 }
78}
79
80pub(crate) fn decode_file(path: &str, format: wgpu::TextureFormat) -> Option<(u32, u32, Vec<u8>)> {
87 let img = match image::open(path) {
88 Ok(img) => img,
89 Err(e) => {
90 tracing::error!("failed to load texture '{path}': {e}");
91 return None;
92 }
93 };
94
95 Some(match format {
96 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Rgba8UnormSrgb => {
97 let img = img.to_rgba8();
98 let (w, h) = img.dimensions();
99 (w, h, img.into_raw())
100 }
101 wgpu::TextureFormat::Rgba32Float => {
102 let img = img.to_rgba32f();
103 let (w, h) = img.dimensions();
104 let bytes = bytemuck::cast_slice(img.into_raw().as_slice()).to_vec();
105 (w, h, bytes)
106 }
107 wgpu::TextureFormat::Rgba16Float => {
108 let img = img.to_rgba32f();
109 let (w, h) = img.dimensions();
110 let bytes = img
111 .into_raw()
112 .into_iter()
113 .flat_map(|c| half::f16::from_f32(c).to_le_bytes())
114 .collect();
115 (w, h, bytes)
116 }
117 other => panic!("unsupported texture format for GPUTexture: {other:?}"),
118 })
119}
120
121impl Asset<WGPUBackend> for GPUTexture {
122 type Source = TextureDescriptor;
123 type Deps<'a> = Res<'a, MipmapGenerator>;
124
125 fn upload<'a>(
126 source: &TextureDescriptor,
127 backend: &WGPUBackend,
128 mipmap_generator: &Res<'a, MipmapGenerator>,
129 ) -> Option<Self> {
130 let (width, height, data) = if let Some(path) = source.file {
132 decode_file(path, source.format)?
133 } else if let Some(data) = &source.data {
134 (source.width, source.height, data.clone())
135 } else {
136 tracing::error!("TextureSpec has neither `file` nor `data` set");
137 return None;
138 };
139
140 let mip_count = super::mipmap::mip_count(width.max(height), source.generate_mips);
141
142 let texture = backend.device.create_texture(&wgpu::TextureDescriptor {
143 label: None,
144 size: wgpu::Extent3d {
145 width,
146 height,
147 depth_or_array_layers: 1,
148 },
149 mip_level_count: mip_count, sample_count: 1,
151 dimension: wgpu::TextureDimension::D2,
152 format: source.format,
153 usage: super::mipmap::texture_usage(mip_count),
154 view_formats: &[],
155 });
156
157 backend.queue.write_texture(
159 wgpu::TexelCopyTextureInfo {
160 texture: &texture,
161 mip_level: 0,
162 origin: wgpu::Origin3d::default(),
163 aspect: wgpu::TextureAspect::All,
164 },
165 &data,
166 wgpu::TexelCopyBufferLayout {
167 offset: 0,
168 bytes_per_row: Some(bytes_per_pixel(source.format) * width),
169 rows_per_image: Some(height),
170 },
171 wgpu::Extent3d {
172 width,
173 height,
174 depth_or_array_layers: 1,
175 },
176 );
177
178 if mip_count > 1 {
179 mipmap_generator.generate_mips(
180 &backend.device,
181 &backend.queue,
182 &texture,
183 source.format,
184 mip_count,
185 1,
186 );
187 }
188
189 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
190 Some(Self { texture, view })
191 }
192}
193
194crate::wgpu::plugin_macros::mipmap_asset_plugin! {
195 TexturePlugin, GPUTexture
201}