1use wgpu::TextureViewDimension;
2
3use myth_assets::AssetServer;
4use myth_resources::texture::TextureSource;
5
6use crate::core::gpu::ResourceState;
7
8use super::{ResourceManager, generate_gpu_resource_id};
9
10const EQUIRECT_CUBE_SIZE: u32 = 1024;
11pub const PMREM_SIZE: u32 = 512;
12pub const BRDF_LUT_SIZE: u32 = 128;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CubeSourceType {
17 Equirectangular,
19 CubeNoMipmaps,
21 CubeWithMipmaps,
23}
24
25#[derive(Debug)]
31pub struct GpuEnvironment {
32 pub source_version: u32,
34 pub needs_compute: bool,
36 pub source_type: CubeSourceType,
38 pub cube_texture: Option<wgpu::Texture>,
40 pub pmrem_texture: wgpu::Texture,
42 pub cube_view_id: u64,
44 pub pmrem_view_id: u64,
46 pub env_map_max_mip_level: f32,
48}
49
50impl ResourceManager {
51 #[allow(clippy::too_many_lines)]
62 pub fn resolve_gpu_environment(
63 &mut self,
64 assets: &AssetServer,
65 environment: &myth_scene::environment::Environment,
66 ) -> f32 {
67 let Some(source) = environment.source_env_map else {
68 return 0.0;
69 };
70
71 let mut current_version: u32 = 0;
72 let mut source_pending = false;
73 if let TextureSource::Asset(handle) = &source {
74 let state = self.prepare_texture(assets, *handle);
75 if matches!(state, ResourceState::Pending) {
76 source_pending = true;
77 }
78 if let Some(tex) = assets.textures.get(*handle) {
79 current_version = assets.images.get_version(tex.image).unwrap_or(0);
80 }
81 }
82
83 if source_pending && let Some(gpu_env) = self.environment_map_cache.get(&source) {
86 return gpu_env.env_map_max_mip_level;
87
88 }
91
92 if let Some(gpu_env) = self.environment_map_cache.get_mut(&source) {
94 if gpu_env.source_version == current_version && !gpu_env.needs_compute {
95 return gpu_env.env_map_max_mip_level;
96 }
97 if gpu_env.source_version != current_version {
98 gpu_env.source_version = current_version;
100 gpu_env.needs_compute = true;
101 self.pending_ibl_source = Some(source);
102 }
103 return gpu_env.env_map_max_mip_level;
104 }
105
106 let (is_2d_source, source_cube_size, source_mip_count) = match &source {
108 TextureSource::Asset(handle) => {
109 if let Some(binding) = self.texture_bindings.get(*handle) {
110 if let Some(img) = self.gpu_images.get(binding.image_handle) {
111 let is_2d = img.default_view_dimension == TextureViewDimension::D2;
112 (is_2d, img.size.width, img.mip_level_count)
113 } else {
114 (true, EQUIRECT_CUBE_SIZE, 1)
115 }
116 } else {
117 (true, EQUIRECT_CUBE_SIZE, 1)
118 }
119 }
120 TextureSource::Attachment(_, dim) => {
121 let is_2d = *dim == TextureViewDimension::D2;
122 (is_2d, EQUIRECT_CUBE_SIZE, if is_2d { 1 } else { 2 })
124 }
125 };
126
127 let source_type = if is_2d_source {
128 CubeSourceType::Equirectangular
129 } else if source_mip_count <= 1 {
130 CubeSourceType::CubeNoMipmaps
131 } else {
132 CubeSourceType::CubeWithMipmaps
133 };
134
135 let needs_owned_cube = matches!(
137 source_type,
138 CubeSourceType::Equirectangular | CubeSourceType::CubeNoMipmaps
139 );
140
141 let owned_cube_size = match source_type {
142 CubeSourceType::Equirectangular => EQUIRECT_CUBE_SIZE,
143 CubeSourceType::CubeNoMipmaps => source_cube_size,
144 CubeSourceType::CubeWithMipmaps => 0, };
146
147 let cube_texture = if needs_owned_cube {
148 let mip_levels = (owned_cube_size as f32).log2().floor() as u32 + 1;
149 Some(self.device.create_texture(&wgpu::TextureDescriptor {
150 label: Some("Env Cube (Owned)"),
151 size: wgpu::Extent3d {
152 width: owned_cube_size,
153 height: owned_cube_size,
154 depth_or_array_layers: 6,
155 },
156 dimension: wgpu::TextureDimension::D2,
157 format: wgpu::TextureFormat::Rgba16Float,
158 usage: wgpu::TextureUsages::STORAGE_BINDING
159 | wgpu::TextureUsages::TEXTURE_BINDING
160 | wgpu::TextureUsages::RENDER_ATTACHMENT,
161 mip_level_count: mip_levels,
162 sample_count: 1,
163 view_formats: &[],
164 }))
165 } else {
166 None
167 };
168
169 let cube_view_id = if let Some(ref cube_tex) = cube_texture {
171 let view = cube_tex.create_view(&wgpu::TextureViewDescriptor {
172 dimension: Some(TextureViewDimension::Cube),
173 ..Default::default()
174 });
175 let id = generate_gpu_resource_id();
176 self.internal_resources.insert(id, view);
177 id
178 } else {
179 match &source {
181 TextureSource::Asset(handle) => {
182 if let Some(binding) = self.texture_bindings.get(*handle) {
183 if let Some(img) = self.gpu_images.get(binding.image_handle) {
184 let view = img.texture.create_view(&wgpu::TextureViewDescriptor {
185 dimension: Some(TextureViewDimension::Cube),
186 ..Default::default()
187 });
188 let id = generate_gpu_resource_id();
189 self.internal_resources.insert(id, view);
190 id
191 } else {
192 self.system_textures.black_cube.id()
193 }
194 } else {
195 self.system_textures.black_cube.id()
196 }
197 }
198 TextureSource::Attachment(id, _) => *id,
199 }
200 };
201
202 let pmrem_mip_levels = (PMREM_SIZE as f32).log2().floor() as u32 + 1;
204 let pmrem_texture = self.device.create_texture(&wgpu::TextureDescriptor {
205 label: Some("PMREM Cubemap"),
206 size: wgpu::Extent3d {
207 width: PMREM_SIZE,
208 height: PMREM_SIZE,
209 depth_or_array_layers: 6,
210 },
211 mip_level_count: pmrem_mip_levels,
212 sample_count: 1,
213 dimension: wgpu::TextureDimension::D2,
214 format: wgpu::TextureFormat::Rgba16Float,
215 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
216 view_formats: &[],
217 });
218
219 let pmrem_view = pmrem_texture.create_view(&wgpu::TextureViewDescriptor {
221 label: Some("PMREM Cube View"),
222 dimension: Some(TextureViewDimension::Cube),
223 ..Default::default()
224 });
225 let pmrem_view_id = generate_gpu_resource_id();
226 self.internal_resources.insert(pmrem_view_id, pmrem_view);
227
228 let env_map_max_mip_level = (pmrem_mip_levels - 1) as f32;
229
230 let gpu_env = GpuEnvironment {
231 source_version: current_version,
232 needs_compute: true,
233 source_type,
234 cube_texture,
235 pmrem_texture,
236 cube_view_id,
237 pmrem_view_id,
238 env_map_max_mip_level,
239 };
240
241 self.pending_ibl_source = Some(source);
242 self.environment_map_cache.insert(source, gpu_env);
243
244 env_map_max_mip_level
245 }
246
247 pub fn ensure_brdf_lut(&mut self) -> u64 {
252 if let Some(id) = self.brdf_lut_view_id {
253 return id;
254 }
255
256 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
257 label: Some("BRDF LUT"),
258 size: wgpu::Extent3d {
259 width: BRDF_LUT_SIZE,
260 height: BRDF_LUT_SIZE,
261 depth_or_array_layers: 1,
262 },
263 mip_level_count: 1,
264 sample_count: 1,
265 dimension: wgpu::TextureDimension::D2,
266 format: wgpu::TextureFormat::Rgba16Float,
267 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
268 view_formats: &[],
269 });
270
271 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
272 let id = self.register_internal_texture_by_name("BRDF_LUT", view);
273
274 self.brdf_lut_texture = Some(texture);
275 self.brdf_lut_view_id = Some(id);
276 self.needs_brdf_compute = true;
277
278 id
279 }
280
281 pub fn get_env_map_max_mip_level(&self, source: Option<TextureSource>) -> f32 {
283 if let Some(src) = source
284 && let Some(gpu_env) = self.environment_map_cache.get(&src)
285 {
286 return gpu_env.env_map_max_mip_level;
287 }
288 0.0
289 }
290}