1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
10pub enum AlphaMode {
11 #[default]
13 Opaque,
14 Mask { cutoff: f32 },
16 Blend,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub enum MaterialClass {
23 PbrLit,
24 PbrLitUntextured,
25 PbrEmissive,
26 PbrMasked,
27 PbrTransparent,
28 Unlit,
29 SpriteLit2D,
30 SpriteUnlit2D,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum RtQuality {
36 Full,
38 ShadowsOnly,
40 GiOnly,
42}
43
44impl Default for RtQuality {
45 fn default() -> Self {
46 Self::Full
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52pub enum SceneDreamMode {
53 PbrDefault,
55 PbrLightweight,
57 Unlit,
59 SpriteLit2D,
61 SpriteUnlit2D,
63 PbrRayTraced(RtQuality),
66 Custom,
68}
69
70impl Default for SceneDreamMode {
71 fn default() -> Self {
72 Self::PbrDefault
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78pub enum TonemapOperator {
79 AcesFilmic,
81 Uncharted2,
83 Reinhard,
85 None,
87}
88
89impl Default for TonemapOperator {
90 fn default() -> Self {
91 Self::AcesFilmic
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98pub struct PbrMaterial {
99 pub base_color: [f32; 4],
101 pub emissive: [f32; 3],
103 pub roughness: f32,
105 pub metallic: f32,
107 pub base_color_tex: Option<u32>,
109 pub normal_tex: Option<u32>,
111 pub emissive_tex: Option<u32>,
113 pub roughness_metalness_tex: Option<u32>,
115 pub occlusion_tex: Option<u32>,
117 pub occlusion_strength: f32,
119 pub normal_scale: f32,
121 pub emissive_strength: f32,
123 pub alpha_mode: AlphaMode,
125 pub double_sided: bool,
127}
128
129pub type LitMaterialDesc = PbrMaterial;
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
140pub enum DreamLightingQuality {
141 Low,
143 Medium,
145 High,
147 Ultra,
149 Cinematic,
151}
152
153impl Default for DreamLightingQuality {
154 fn default() -> Self {
155 Self::Medium
156 }
157}
158
159impl DreamLightingQuality {
160 pub fn auto_detect(ray_tracing: bool, max_storage_bytes: u64, max_tlas_instances: u32) -> Self {
162 if !ray_tracing {
163 if max_storage_bytes >= 64 * 1024 * 1024 {
164 return Self::Medium;
165 }
166 return Self::Low;
167 }
168 if max_tlas_instances >= 16_000_000 {
170 Self::Ultra
171 } else {
172 Self::High
173 }
174 }
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct DreamLightingConfig {
181 pub rt_gi_enabled: bool,
183 pub rt_shadows_enabled: bool,
184 pub ssgi_enabled: bool,
186 pub ssao_enabled: bool,
187 pub ssr_enabled: bool,
188 pub taa_enabled: bool,
189 pub screen_traces_enabled: bool,
190 pub sharc_hash_capacity: u32,
192 pub sharc_sparse_rate: f32,
193 pub sharc_max_bounces: u32,
194 pub sharc_grid_log_base: f32,
195 pub denoiser_enabled: bool,
197 pub denoiser_temporal_alpha: f32,
198 pub hiz_enabled: bool,
200 pub motion_vectors_enabled: bool,
201 pub csm_cascades: u32,
203 pub reflection_max_roughness: f32,
205 pub ibl_resolution: u32,
207 pub volumetric_fog_enabled: bool,
209 pub dof_enabled: bool,
211 pub dream_tsr_enabled: bool,
213}
214
215impl Default for DreamLightingConfig {
216 fn default() -> Self {
217 Self::for_quality(DreamLightingQuality::Medium)
218 }
219}
220
221impl DreamLightingConfig {
222 pub fn for_quality(quality: DreamLightingQuality) -> Self {
224 match quality {
225 DreamLightingQuality::Low => Self {
227 rt_gi_enabled: false,
228 rt_shadows_enabled: false,
229 ssgi_enabled: false,
230 ssao_enabled: false,
231 ssr_enabled: false,
232 taa_enabled: false,
233 screen_traces_enabled: false,
234 sharc_hash_capacity: 0,
235 sharc_sparse_rate: 0.0,
236 sharc_max_bounces: 0,
237 sharc_grid_log_base: 2.0,
238 denoiser_enabled: false,
239 denoiser_temporal_alpha: 0.0,
240 hiz_enabled: false,
241 motion_vectors_enabled: false,
242 csm_cascades: 2,
243 reflection_max_roughness: 0.0,
244 ibl_resolution: 32,
245 volumetric_fog_enabled: false,
246 dof_enabled: false,
247 dream_tsr_enabled: false,
248 },
249 DreamLightingQuality::Medium => Self {
251 rt_gi_enabled: false,
252 rt_shadows_enabled: false,
253 ssgi_enabled: true,
254 ssao_enabled: true,
255 ssr_enabled: true,
256 taa_enabled: true,
257 screen_traces_enabled: false,
258 sharc_hash_capacity: 0,
259 sharc_sparse_rate: 0.0,
260 sharc_max_bounces: 0,
261 sharc_grid_log_base: 2.0,
262 denoiser_enabled: false,
263 denoiser_temporal_alpha: 0.0,
264 hiz_enabled: false,
265 motion_vectors_enabled: true,
266 csm_cascades: 4,
267 reflection_max_roughness: 0.3,
268 ibl_resolution: 64,
269 volumetric_fog_enabled: false,
270 dof_enabled: false,
271 dream_tsr_enabled: false,
272 },
273 DreamLightingQuality::High => Self {
275 rt_gi_enabled: true,
276 rt_shadows_enabled: true,
277 ssgi_enabled: false,
278 ssao_enabled: true,
279 ssr_enabled: true,
280 taa_enabled: true,
281 screen_traces_enabled: true,
282 sharc_hash_capacity: 1 << 18, sharc_sparse_rate: 0.02,
284 sharc_max_bounces: 2,
285 sharc_grid_log_base: 2.5,
286 denoiser_enabled: true,
287 denoiser_temporal_alpha: 0.80,
288 hiz_enabled: true,
289 motion_vectors_enabled: true,
290 csm_cascades: 0, reflection_max_roughness: 0.4,
292 ibl_resolution: 128,
293 volumetric_fog_enabled: false,
294 dof_enabled: false,
295 dream_tsr_enabled: false,
296 },
297 DreamLightingQuality::Ultra => Self {
299 rt_gi_enabled: true,
300 rt_shadows_enabled: true,
301 ssgi_enabled: false,
302 ssao_enabled: true,
303 ssr_enabled: true,
304 taa_enabled: true,
305 screen_traces_enabled: true,
306 sharc_hash_capacity: 1 << 20, sharc_sparse_rate: 0.04,
308 sharc_max_bounces: 3,
309 sharc_grid_log_base: 2.0,
310 denoiser_enabled: true,
311 denoiser_temporal_alpha: 0.85,
312 hiz_enabled: true,
313 motion_vectors_enabled: true,
314 csm_cascades: 0, reflection_max_roughness: 0.5,
316 ibl_resolution: 128,
317 volumetric_fog_enabled: true,
318 dof_enabled: false,
319 dream_tsr_enabled: false,
320 },
321 DreamLightingQuality::Cinematic => Self {
323 rt_gi_enabled: true,
324 rt_shadows_enabled: true,
325 ssgi_enabled: false,
326 ssao_enabled: true,
327 ssr_enabled: true,
328 taa_enabled: false, screen_traces_enabled: true,
330 sharc_hash_capacity: 1 << 22, sharc_sparse_rate: 0.08,
332 sharc_max_bounces: 5,
333 sharc_grid_log_base: 1.5,
334 denoiser_enabled: true,
335 denoiser_temporal_alpha: 0.90,
336 hiz_enabled: true,
337 motion_vectors_enabled: true,
338 csm_cascades: 0, reflection_max_roughness: 0.6,
340 ibl_resolution: 256,
341 volumetric_fog_enabled: true,
342 dof_enabled: true,
343 dream_tsr_enabled: true,
344 },
345 }
346 }
347
348 pub fn infer_quality(&self) -> DreamLightingQuality {
350 if self.dream_tsr_enabled {
351 DreamLightingQuality::Cinematic
352 } else if self.rt_gi_enabled && self.sharc_hash_capacity >= (1 << 20) {
353 DreamLightingQuality::Ultra
354 } else if self.rt_gi_enabled {
355 DreamLightingQuality::High
356 } else if self.ssgi_enabled {
357 DreamLightingQuality::Medium
358 } else {
359 DreamLightingQuality::Low
360 }
361 }
362}
363
364impl Default for PbrMaterial {
365 fn default() -> Self {
366 Self {
367 base_color: [1.0, 1.0, 1.0, 1.0],
368 emissive: [0.0, 0.0, 0.0],
369 roughness: 0.5,
370 metallic: 0.0,
371 base_color_tex: None,
372 normal_tex: None,
373 emissive_tex: None,
374 roughness_metalness_tex: None,
375 occlusion_tex: None,
376 occlusion_strength: 1.0,
377 normal_scale: 1.0,
378 emissive_strength: 1.0,
379 alpha_mode: AlphaMode::Opaque,
380 double_sided: false,
381 }
382 }
383}
384
385impl PbrMaterial {
386 pub fn painted_metal() -> Self {
387 Self {
388 roughness: 0.4,
389 metallic: 1.0,
390 base_color: [0.8, 0.1, 0.1, 1.0],
391 ..Default::default()
392 }
393 }
394
395 pub fn brushed_steel() -> Self {
396 Self {
397 roughness: 0.3,
398 metallic: 1.0,
399 base_color: [0.7, 0.7, 0.7, 1.0],
400 ..Default::default()
401 }
402 }
403
404 pub fn matte_plastic() -> Self {
405 Self {
406 roughness: 0.9,
407 metallic: 0.0,
408 base_color: [0.8, 0.8, 0.8, 1.0],
409 ..Default::default()
410 }
411 }
412
413 pub fn glossy_ceramic() -> Self {
414 Self {
415 roughness: 0.15,
416 metallic: 0.0,
417 base_color: [0.95, 0.95, 0.9, 1.0],
418 ..Default::default()
419 }
420 }
421
422 pub fn rough_wood() -> Self {
423 Self {
424 roughness: 0.85,
425 metallic: 0.0,
426 base_color: [0.55, 0.35, 0.2, 1.0],
427 ..Default::default()
428 }
429 }
430
431 pub fn polished_marble() -> Self {
432 Self {
433 roughness: 0.2,
434 metallic: 0.0,
435 base_color: [0.95, 0.93, 0.88, 1.0],
436 ..Default::default()
437 }
438 }
439
440 pub fn wet_stone() -> Self {
441 Self {
442 roughness: 0.3,
443 metallic: 0.0,
444 base_color: [0.4, 0.4, 0.4, 1.0],
445 ..Default::default()
446 }
447 }
448
449 pub fn gold() -> Self {
450 Self {
451 roughness: 0.25,
452 metallic: 1.0,
453 base_color: [1.0, 0.76, 0.33, 1.0],
454 ..Default::default()
455 }
456 }
457
458 pub fn copper() -> Self {
459 Self {
460 roughness: 0.3,
461 metallic: 1.0,
462 base_color: [0.95, 0.64, 0.54, 1.0],
463 ..Default::default()
464 }
465 }
466
467 pub fn rubber() -> Self {
468 Self {
469 roughness: 0.95,
470 metallic: 0.0,
471 base_color: [0.15, 0.15, 0.15, 1.0],
472 ..Default::default()
473 }
474 }
475
476 pub fn glass() -> Self {
477 Self {
478 roughness: 0.05,
479 metallic: 0.0,
480 alpha_mode: AlphaMode::Blend,
481 base_color: [1.0, 1.0, 1.0, 0.3],
482 ..Default::default()
483 }
484 }
485
486 pub fn fabric() -> Self {
487 Self {
488 roughness: 0.8,
489 metallic: 0.0,
490 base_color: [0.6, 0.5, 0.4, 1.0],
491 ..Default::default()
492 }
493 }
494
495 pub fn skin() -> Self {
496 Self {
497 roughness: 0.5,
498 metallic: 0.0,
499 base_color: [0.9, 0.7, 0.6, 1.0],
500 ..Default::default()
501 }
502 }
503
504 pub fn emissive_panel() -> Self {
505 Self {
506 emissive: [5.0, 5.0, 5.0],
507 emissive_strength: 2.0,
508 base_color: [0.1, 0.1, 0.1, 1.0],
509 ..Default::default()
510 }
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517
518 #[test]
519 fn alpha_mode_default_opaque() {
520 assert_eq!(AlphaMode::default(), AlphaMode::Opaque);
521 }
522
523 #[test]
524 fn pbr_material_default() {
525 let m = PbrMaterial::default();
526 assert_eq!(m.base_color, [1.0, 1.0, 1.0, 1.0]);
527 assert_eq!(m.roughness, 0.5);
528 assert_eq!(m.metallic, 0.0);
529 assert!(!m.double_sided);
530 }
531
532 #[test]
533 fn alpha_mask_cutoff() {
534 let mode = AlphaMode::Mask { cutoff: 0.5 };
535 if let AlphaMode::Mask { cutoff } = mode {
536 assert_eq!(cutoff, 0.5);
537 } else {
538 panic!("expected Mask");
539 }
540 }
541
542 #[test]
543 fn serde_roundtrip() {
544 let m = PbrMaterial {
545 roughness: 0.8,
546 metallic: 1.0,
547 alpha_mode: AlphaMode::Blend,
548 ..Default::default()
549 };
550 let json = serde_json::to_string(&m).unwrap();
551 let back: PbrMaterial = serde_json::from_str(&json).unwrap();
552 assert_eq!(m, back);
553 }
554
555 #[test]
556 fn pbr_material_new_fields_default() {
557 let m = PbrMaterial::default();
558 assert_eq!(m.roughness_metalness_tex, None);
559 assert_eq!(m.occlusion_tex, None);
560 assert_eq!(m.occlusion_strength, 1.0);
561 assert_eq!(m.normal_scale, 1.0);
562 assert_eq!(m.emissive_strength, 1.0);
563 }
564
565 #[test]
566 fn scene_dream_mode_default() {
567 assert_eq!(SceneDreamMode::default(), SceneDreamMode::PbrDefault);
568 }
569
570 #[test]
571 fn rt_quality_default() {
572 assert_eq!(RtQuality::default(), RtQuality::Full);
573 }
574
575 #[test]
576 fn scene_dream_mode_ray_traced() {
577 let mode = SceneDreamMode::PbrRayTraced(RtQuality::Full);
578 assert_ne!(mode, SceneDreamMode::PbrDefault);
579 let mode2 = SceneDreamMode::PbrRayTraced(RtQuality::ShadowsOnly);
580 assert_ne!(mode, mode2);
581 }
582
583 #[test]
584 fn serde_roundtrip_rt_quality() {
585 let q = RtQuality::GiOnly;
586 let json = serde_json::to_string(&q).unwrap();
587 let back: RtQuality = serde_json::from_str(&json).unwrap();
588 assert_eq!(q, back);
589 }
590
591 #[test]
592 fn serde_roundtrip_pbr_ray_traced() {
593 let mode = SceneDreamMode::PbrRayTraced(RtQuality::Full);
594 let json = serde_json::to_string(&mode).unwrap();
595 let back: SceneDreamMode = serde_json::from_str(&json).unwrap();
596 assert_eq!(mode, back);
597 }
598
599 #[test]
600 fn tonemap_operator_default() {
601 assert_eq!(TonemapOperator::default(), TonemapOperator::AcesFilmic);
602 }
603
604 #[test]
605 fn material_class_variants() {
606 let variants = [
607 MaterialClass::PbrLit,
608 MaterialClass::PbrLitUntextured,
609 MaterialClass::PbrEmissive,
610 MaterialClass::PbrMasked,
611 MaterialClass::PbrTransparent,
612 MaterialClass::Unlit,
613 MaterialClass::SpriteLit2D,
614 MaterialClass::SpriteUnlit2D,
615 ];
616 assert_eq!(variants.len(), 8);
617 }
618
619 #[test]
620 fn preset_painted_metal() {
621 let m = PbrMaterial::painted_metal();
622 assert_eq!(m.roughness, 0.4);
623 assert_eq!(m.metallic, 1.0);
624 assert_eq!(m.base_color, [0.8, 0.1, 0.1, 1.0]);
625 }
626
627 #[test]
628 fn preset_glass_is_blend() {
629 let m = PbrMaterial::glass();
630 assert_eq!(m.alpha_mode, AlphaMode::Blend);
631 assert_eq!(m.base_color[3], 0.3);
632 }
633
634 #[test]
635 fn serde_roundtrip_scene_dream_mode() {
636 let mode = SceneDreamMode::SpriteLit2D;
637 let json = serde_json::to_string(&mode).unwrap();
638 let back: SceneDreamMode = serde_json::from_str(&json).unwrap();
639 assert_eq!(mode, back);
640 }
641
642 #[test]
643 fn serde_roundtrip_tonemap() {
644 let op = TonemapOperator::Uncharted2;
645 let json = serde_json::to_string(&op).unwrap();
646 let back: TonemapOperator = serde_json::from_str(&json).unwrap();
647 assert_eq!(op, back);
648 }
649
650 #[test]
651 fn serde_roundtrip_material_class() {
652 let cls = MaterialClass::PbrEmissive;
653 let json = serde_json::to_string(&cls).unwrap();
654 let back: MaterialClass = serde_json::from_str(&json).unwrap();
655 assert_eq!(cls, back);
656 }
657
658 #[test]
661 fn dream_lighting_quality_default() {
662 assert_eq!(DreamLightingQuality::default(), DreamLightingQuality::Medium);
663 }
664
665 #[test]
666 fn dream_lighting_config_default_matches_medium() {
667 let config = DreamLightingConfig::default();
668 let medium = DreamLightingConfig::for_quality(DreamLightingQuality::Medium);
669 assert_eq!(config.rt_gi_enabled, medium.rt_gi_enabled);
670 assert_eq!(config.ssao_enabled, medium.ssao_enabled);
671 assert_eq!(config.csm_cascades, medium.csm_cascades);
672 assert!(!config.hiz_enabled); assert_eq!(config.ibl_resolution, 64); assert!((config.reflection_max_roughness - 0.3).abs() < 0.01);
675 }
676
677 #[test]
678 fn quality_preset_low_no_rt() {
679 let config = DreamLightingConfig::for_quality(DreamLightingQuality::Low);
680 assert!(!config.rt_gi_enabled);
681 assert!(!config.rt_shadows_enabled);
682 assert!(!config.ssgi_enabled);
683 assert!(!config.ssao_enabled); assert!(!config.taa_enabled); assert_eq!(config.ibl_resolution, 32);
686 assert_eq!(config.csm_cascades, 2);
687 }
688
689 #[test]
690 fn quality_preset_high_has_rt() {
691 let config = DreamLightingConfig::for_quality(DreamLightingQuality::High);
692 assert!(config.rt_gi_enabled);
693 assert!(config.rt_shadows_enabled);
694 assert!(config.screen_traces_enabled);
695 assert_eq!(config.sharc_hash_capacity, 1 << 18); assert_eq!(config.sharc_max_bounces, 2); assert_eq!(config.csm_cascades, 0); assert!((config.sharc_sparse_rate - 0.02).abs() < 0.001);
699 assert!((config.sharc_grid_log_base - 2.5).abs() < 0.01);
700 assert!((config.denoiser_temporal_alpha - 0.80).abs() < 0.01);
701 assert_eq!(config.ibl_resolution, 128);
702 }
703
704 #[test]
705 fn quality_preset_cinematic_has_tsr() {
706 let config = DreamLightingConfig::for_quality(DreamLightingQuality::Cinematic);
707 assert!(config.dream_tsr_enabled);
708 assert!(!config.taa_enabled); assert_eq!(config.sharc_max_bounces, 5); assert_eq!(config.sharc_hash_capacity, 1 << 22);
711 assert_eq!(config.csm_cascades, 0); assert_eq!(config.ibl_resolution, 256);
713 assert!((config.sharc_grid_log_base - 1.5).abs() < 0.01);
714 assert!((config.denoiser_temporal_alpha - 0.90).abs() < 0.01);
715 assert!((config.reflection_max_roughness - 0.6).abs() < 0.01);
716 }
717
718 #[test]
719 fn auto_detect_no_rt_64mb_returns_medium() {
720 let q = DreamLightingQuality::auto_detect(false, 64 * 1024 * 1024, 0);
721 assert_eq!(q, DreamLightingQuality::Medium);
722 }
723
724 #[test]
725 fn auto_detect_no_rt_low_storage_returns_low() {
726 let q = DreamLightingQuality::auto_detect(false, 32 * 1024 * 1024, 0);
727 assert_eq!(q, DreamLightingQuality::Low);
728 }
729
730 #[test]
731 fn auto_detect_rt_16m_tlas_returns_ultra() {
732 let q = DreamLightingQuality::auto_detect(true, 512 * 1024 * 1024, 16_000_000);
733 assert_eq!(q, DreamLightingQuality::Ultra);
734 }
735
736 #[test]
737 fn auto_detect_rt_below_16m_returns_high() {
738 let q = DreamLightingQuality::auto_detect(true, 256 * 1024 * 1024, 100_000);
739 assert_eq!(q, DreamLightingQuality::High);
740 }
741
742 #[test]
743 fn infer_quality_roundtrip() {
744 for quality in [
745 DreamLightingQuality::Low,
746 DreamLightingQuality::Medium,
747 DreamLightingQuality::High,
748 DreamLightingQuality::Ultra,
749 DreamLightingQuality::Cinematic,
750 ] {
751 let config = DreamLightingConfig::for_quality(quality);
752 assert_eq!(
753 config.infer_quality(),
754 quality,
755 "infer_quality failed for {:?}",
756 quality
757 );
758 }
759 }
760
761 #[test]
762 fn serde_roundtrip_dream_lighting_quality() {
763 let q = DreamLightingQuality::Cinematic;
764 let json = serde_json::to_string(&q).unwrap();
765 let back: DreamLightingQuality = serde_json::from_str(&json).unwrap();
766 assert_eq!(q, back);
767 }
768
769 #[test]
770 fn serde_roundtrip_dream_lighting_config() {
771 let config = DreamLightingConfig::for_quality(DreamLightingQuality::Ultra);
772 let json = serde_json::to_string(&config).unwrap();
773 let back: DreamLightingConfig = serde_json::from_str(&json).unwrap();
774 assert_eq!(config.rt_gi_enabled, back.rt_gi_enabled);
775 assert_eq!(config.sharc_hash_capacity, back.sharc_hash_capacity);
776 assert_eq!(config.dream_tsr_enabled, back.dream_tsr_enabled);
777 }
778}