use crate::shadows::{
config::ShadowsConfig,
light_shadow::{EvsmCutoff, LightShadowHardness, LightShadowParams},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ShadowQualityTier {
Low,
#[default]
Medium,
High,
Ultra,
Custom,
}
#[derive(Clone, Copy, Debug)]
pub struct ShadowQualityPreset {
pub atlas_size: u32,
pub cascade_count: u8,
pub hardness: LightShadowHardness,
pub max_point_shadows: u32,
pub evsm_cutoff: EvsmCutoff,
pub sscs_enabled: bool,
}
impl ShadowQualityTier {
pub fn preset_unchecked(self) -> ShadowQualityPreset {
match self {
ShadowQualityTier::Low => ShadowQualityPreset {
atlas_size: 1024,
cascade_count: 2,
hardness: LightShadowHardness::Soft,
max_point_shadows: 2,
evsm_cutoff: EvsmCutoff::Off,
sscs_enabled: false,
},
ShadowQualityTier::Medium => ShadowQualityPreset {
atlas_size: 2048,
cascade_count: 3,
hardness: LightShadowHardness::Soft,
max_point_shadows: 4,
evsm_cutoff: EvsmCutoff::LastCascade,
sscs_enabled: false,
},
ShadowQualityTier::High => ShadowQualityPreset {
atlas_size: 4096,
cascade_count: 4,
hardness: LightShadowHardness::Pcss,
max_point_shadows: 8,
evsm_cutoff: EvsmCutoff::LastTwoCascades,
sscs_enabled: true,
},
ShadowQualityTier::Ultra => ShadowQualityPreset {
atlas_size: 8192,
cascade_count: 4,
hardness: LightShadowHardness::Pcss,
max_point_shadows: 16,
evsm_cutoff: EvsmCutoff::LastTwoCascades,
sscs_enabled: true,
},
ShadowQualityTier::Custom => {
unreachable!("Custom tier has no preset; call .preset() and handle None")
}
}
}
pub fn preset(self) -> Option<ShadowQualityPreset> {
match self {
ShadowQualityTier::Custom => None,
other => Some(other.preset_unchecked()),
}
}
pub fn is_named(self) -> bool {
!matches!(self, ShadowQualityTier::Custom)
}
}
impl ShadowQualityPreset {
pub fn apply_to_config(&self, config: &mut ShadowsConfig) {
config.atlas_size = self.atlas_size;
config.max_point_shadows = self.max_point_shadows;
config.sscs_enabled = self.sscs_enabled;
}
pub fn apply_to_light_params(&self, params: &mut LightShadowParams) {
params.cascade_count = self.cascade_count;
params.evsm_cutoff = self.evsm_cutoff;
params.hardness = self.hardness;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn named_tiers_have_strictly_growing_atlas_sizes() {
let low = ShadowQualityTier::Low.preset_unchecked();
let med = ShadowQualityTier::Medium.preset_unchecked();
let high = ShadowQualityTier::High.preset_unchecked();
let ultra = ShadowQualityTier::Ultra.preset_unchecked();
assert!(low.atlas_size < med.atlas_size);
assert!(med.atlas_size < high.atlas_size);
assert!(high.atlas_size < ultra.atlas_size);
}
#[test]
fn custom_has_no_preset() {
assert!(ShadowQualityTier::Custom.preset().is_none());
assert!(!ShadowQualityTier::Custom.is_named());
}
#[test]
fn high_tier_application_preserves_cast() {
let preset = ShadowQualityTier::High.preset_unchecked();
let mut params = LightShadowParams {
cast: true,
..LightShadowParams::default()
};
preset.apply_to_light_params(&mut params);
assert!(params.cast, "tier application must not touch cast flag");
assert_eq!(params.cascade_count, 4);
assert_eq!(params.evsm_cutoff, EvsmCutoff::LastTwoCascades);
assert_eq!(params.hardness, LightShadowHardness::Pcss);
}
#[test]
fn low_and_medium_apply_soft_hardness() {
for tier in [ShadowQualityTier::Low, ShadowQualityTier::Medium] {
let preset = tier.preset_unchecked();
let mut params = LightShadowParams::default();
preset.apply_to_light_params(&mut params);
assert_eq!(
params.hardness,
LightShadowHardness::Soft,
"tier {tier:?} should apply Soft hardness"
);
}
}
}