1use crate::{
2 assets::{plugin::AssetPlugin, singleton_asset::LazyResourcePlugin, 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 CubemapDescriptor {
12 pub size: u32, pub format: wgpu::TextureFormat,
14 pub faces: Option<[Vec<u8>; 6]>,
20 pub face_files: Option<[&'static str; 6]>,
23 pub generate_mips: bool,
24}
25
26impl CubemapDescriptor {
27 pub fn from_files(size: u32, files: [&'static str; 6]) -> Self {
29 Self {
30 size,
31 format: wgpu::TextureFormat::Rgba8UnormSrgb,
32 faces: None,
33 face_files: Some(files),
34 generate_mips: false,
35 }
36 }
37
38 pub fn from_faces(size: u32, format: wgpu::TextureFormat, faces: [Vec<u8>; 6]) -> Self {
40 Self {
41 size,
42 format,
43 faces: Some(faces),
44 face_files: None,
45 generate_mips: false,
46 }
47 }
48
49 pub fn empty(size: u32, format: wgpu::TextureFormat) -> Self {
51 Self {
52 size,
53 format,
54 faces: None,
55 face_files: None,
56 generate_mips: false,
57 }
58 }
59
60 pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
61 self.format = format;
62 self
63 }
64
65 pub fn with_mips(mut self) -> Self {
66 self.generate_mips = true;
67 self
68 }
69
70 fn wgpu_descriptor(&self, mip_count: u32, render_target: bool) -> wgpu::TextureDescriptor<'_> {
71 wgpu::TextureDescriptor {
72 label: None,
73 size: wgpu::Extent3d {
74 width: self.size,
75 height: self.size,
76 depth_or_array_layers: 6,
77 },
78 mip_level_count: mip_count,
79 sample_count: 1,
80 dimension: wgpu::TextureDimension::D2,
81 format: self.format,
82 usage: if render_target {
83 wgpu::TextureUsages::TEXTURE_BINDING
84 | wgpu::TextureUsages::COPY_DST
85 | wgpu::TextureUsages::RENDER_ATTACHMENT
86 } else {
87 wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST
88 },
89 view_formats: &[],
90 }
91 }
92}
93
94pub struct GPUCubemap {
95 pub texture: wgpu::Texture,
96 pub view: wgpu::TextureView,
97}
98
99impl Asset<WGPUBackend> for GPUCubemap {
100 type Source = CubemapDescriptor;
101 type Deps<'a> = Res<'a, MipmapGenerator>;
102
103 fn upload<'a>(
104 source: &CubemapDescriptor,
105 backend: &WGPUBackend,
106 mipmap_generator: &Res<'a, MipmapGenerator>,
107 ) -> Option<Self> {
108 let faces: Option<[Vec<u8>; 6]> = if let Some(files) = &source.face_files {
109 let mut out: [Vec<u8>; 6] = Default::default();
110 for (i, path) in files.iter().enumerate() {
111 let (w, h, data) = decode_file(path, source.format);
112 if w != source.size || h != source.size {
113 tracing::error!(
114 "CubemapSpec: face {i} ('{path}') is {w}x{h}, expected {0}x{0}",
115 source.size
116 );
117 return None;
118 }
119 out[i] = data;
120 }
121 Some(out)
122 } else {
123 source.faces.clone()
124 };
125
126 let mip_count = if source.generate_mips {
127 (source.size as f32).log2().floor() as u32 + 1
128 } else {
129 1
130 };
131
132 let texture = backend
133 .device
134 .create_texture(&source.wgpu_descriptor(mip_count, faces.is_none()));
135
136 if let Some(faces) = &faces {
137 for (face, data) in faces.iter().enumerate() {
138 backend.queue.write_texture(
139 wgpu::TexelCopyTextureInfo {
140 texture: &texture,
141 mip_level: 0,
142 origin: wgpu::Origin3d {
143 x: 0,
144 y: 0,
145 z: face as u32,
146 },
147 aspect: wgpu::TextureAspect::All,
148 },
149 data,
150 wgpu::TexelCopyBufferLayout {
151 offset: 0,
152 bytes_per_row: Some(bytes_per_pixel(source.format) * source.size),
153 rows_per_image: Some(source.size),
154 },
155 wgpu::Extent3d {
156 width: source.size,
157 height: source.size,
158 depth_or_array_layers: 1,
159 },
160 );
161 }
162
163 if mip_count > 1 {
164 mipmap_generator.generate_mips(
165 &backend.device,
166 &backend.queue,
167 &texture,
168 source.format,
169 mip_count,
170 6,
171 );
172 }
173 }
174
175 let view = texture.create_view(&wgpu::TextureViewDescriptor {
176 dimension: Some(wgpu::TextureViewDimension::Cube),
177 ..Default::default()
178 });
179 Some(Self { texture, view })
180 }
181}
182
183#[derive(Default)]
184pub struct CubemapPlugin;
185impl CubemapPlugin {
186 pub fn new() -> Self {
187 Self
188 }
189}
190impl Plugin for CubemapPlugin {
191 fn build(&self, app: &mut crate::prelude::App) {
192 app.add_plugin(LazyResourcePlugin::<WGPUBackend, MipmapGenerator>::new());
193 app.add_plugin(AssetPlugin::<WGPUBackend, GPUCubemap>::new());
194 }
195}