use crate::components::{
AaMode, ReflectionBlurResolution, SettingOp, ShadowUpdate, SsgiResolution, UpscaleQuality,
UpscalerBackend, WindowMode,
};
use crate::gfx::backend::GpuVendor;
pub(crate) use concinnity_core::gfx::settings::{QUALITY_TOGGLE_KEYS, is_quality_toggle, options};
pub(crate) fn setting_available(key: &str, caps: &crate::gfx::backend::DeviceCapabilities) -> bool {
match key {
"ray_traced_reflections" => caps.ray_tracing,
"upscale_backend" => caps.selectable_upscaler,
_ => true,
}
}
const FPS_CAP_VALUES: [u32; 6] = [0, 30, 60, 120, 144, 240];
const SSGI_RAYS_COUNTS: [u32; 4] = [4, 8, 16, 32];
const SSGI_STEPS_COUNTS: [u32; 4] = [8, 12, 24, 48];
const SHADOW_RESOLUTION_SIZES: [u32; 4] = [0, 1024, 2048, 4096];
const SHADOW_DISTANCE_VALUES: [u32; 4] = [40, 80, 160, 320];
const SHADOW_CASCADES_VALUES: [u32; 3] = [2, 3, 4];
const ANISOTROPY_LEVELS: [u32; 5] = [1, 2, 4, 8, 16];
const FRAME_BUFFERING_COUNTS: [u32; 3] = [1, 2, 3];
const TEXTURE_QUALITY_CAPS: [u32; 4] = [48, 96, 192, 384];
const TEXTURE_QUALITY_BUDGETS: [u32; 4] = [2, 4, 8, 12];
pub(crate) const QUALITY_CYCLE_KEYS: [&str; 5] = [
"aa_mode",
"ssgi_resolution",
"ssgi_rays",
"ssgi_steps",
"reflection_blur_resolution",
];
const VOLUME_GAINS: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
pub(crate) const DEFAULT_VOLUME: f32 = 1.0;
pub(crate) const DEFAULT_MOUSE_SENSITIVITY: f32 = 0.0015;
pub(crate) const DEFAULT_GAMEPAD_LOOK_SENSITIVITY: f32 = 2.5;
pub(crate) const DEFAULT_GAMEPAD_DEADZONE: f32 = 0.15;
pub(crate) fn window_mode_at(index: usize) -> WindowMode {
match index {
1 => WindowMode::Borderless,
2 => WindowMode::Fullscreen,
_ => WindowMode::Windowed,
}
}
pub(crate) fn window_mode_index(mode: WindowMode) -> usize {
match mode {
WindowMode::Windowed => 0,
WindowMode::Borderless => 1,
WindowMode::Fullscreen => 2,
}
}
pub(crate) fn render_scale_at(index: usize) -> UpscaleQuality {
match index {
1 => UpscaleQuality::Balanced,
2 => UpscaleQuality::Performance,
3 => UpscaleQuality::UltraPerformance,
_ => UpscaleQuality::Quality,
}
}
pub(crate) fn render_scale_index(quality: UpscaleQuality) -> usize {
match quality {
UpscaleQuality::Quality => 0,
UpscaleQuality::Balanced => 1,
UpscaleQuality::Performance => 2,
UpscaleQuality::UltraPerformance => 3,
}
}
pub(crate) fn upscale_backend_at(index: usize) -> UpscalerBackend {
match index {
1 => UpscalerBackend::Fsr3,
2 => UpscalerBackend::Dlss,
3 => UpscalerBackend::Xess,
_ => UpscalerBackend::Auto,
}
}
pub(crate) fn upscale_backend_index(backend: UpscalerBackend) -> usize {
match backend {
UpscalerBackend::Auto => 0,
UpscalerBackend::Fsr3 => 1,
UpscalerBackend::Dlss => 2,
UpscalerBackend::Xess => 3,
}
}
pub(crate) fn upscale_backend_available(backend: UpscalerBackend, vendor: GpuVendor) -> bool {
match backend {
UpscalerBackend::Auto | UpscalerBackend::Fsr3 => true,
UpscalerBackend::Dlss => vendor == GpuVendor::Nvidia,
UpscalerBackend::Xess => vendor == GpuVendor::Intel,
}
}
pub(crate) fn aa_mode_at(index: usize) -> AaMode {
match index {
0 => AaMode::Off,
2 => AaMode::Taa,
_ => AaMode::Fxaa,
}
}
pub(crate) fn aa_mode_index(mode: AaMode) -> usize {
match mode {
AaMode::Off => 0,
AaMode::Fxaa => 1,
AaMode::Taa => 2,
}
}
pub(crate) fn ssgi_resolution_at(index: usize) -> SsgiResolution {
match index {
0 => SsgiResolution::Full,
2 => SsgiResolution::Quarter,
_ => SsgiResolution::Half,
}
}
pub(crate) fn ssgi_resolution_index(res: SsgiResolution) -> usize {
match res {
SsgiResolution::Full => 0,
SsgiResolution::Half => 1,
SsgiResolution::Quarter => 2,
}
}
pub(crate) fn ssgi_rays_at(index: usize) -> u32 {
*SSGI_RAYS_COUNTS.get(index).unwrap_or(&SSGI_RAYS_COUNTS[1])
}
pub(crate) fn ssgi_rays_index(count: u32) -> usize {
nearest_count_index(&SSGI_RAYS_COUNTS, count)
}
pub(crate) fn ssgi_steps_at(index: usize) -> u32 {
*SSGI_STEPS_COUNTS
.get(index)
.unwrap_or(&SSGI_STEPS_COUNTS[1])
}
pub(crate) fn ssgi_steps_index(count: u32) -> usize {
nearest_count_index(&SSGI_STEPS_COUNTS, count)
}
fn nearest_count_index(levels: &[u32], count: u32) -> usize {
levels
.iter()
.enumerate()
.min_by_key(|&(_, &v)| v.abs_diff(count))
.map(|(i, _)| i)
.unwrap_or(0)
}
pub(crate) fn reflection_blur_at(index: usize) -> ReflectionBlurResolution {
match index {
0 => ReflectionBlurResolution::Full,
2 => ReflectionBlurResolution::Quarter,
_ => ReflectionBlurResolution::Half,
}
}
pub(crate) fn reflection_blur_index(res: ReflectionBlurResolution) -> usize {
match res {
ReflectionBlurResolution::Full => 0,
ReflectionBlurResolution::Half => 1,
ReflectionBlurResolution::Quarter => 2,
}
}
pub(crate) fn shadow_resolution_at(index: usize) -> u32 {
*SHADOW_RESOLUTION_SIZES
.get(index)
.unwrap_or(&SHADOW_RESOLUTION_SIZES[2])
}
pub(crate) fn shadow_resolution_index(size: u32) -> usize {
nearest_count_index(&SHADOW_RESOLUTION_SIZES, size)
}
pub(crate) fn shadow_update_at(index: usize) -> ShadowUpdate {
match index {
0 => ShadowUpdate::EveryFrame,
_ => ShadowUpdate::Hybrid,
}
}
pub(crate) fn shadow_update_index(update: ShadowUpdate) -> usize {
match update {
ShadowUpdate::EveryFrame => 0,
ShadowUpdate::Hybrid => 1,
}
}
pub(crate) fn shadow_distance_at(index: usize) -> u32 {
*SHADOW_DISTANCE_VALUES
.get(index)
.unwrap_or(&SHADOW_DISTANCE_VALUES[1])
}
pub(crate) fn shadow_distance_index(distance: u32) -> usize {
nearest_count_index(&SHADOW_DISTANCE_VALUES, distance)
}
pub(crate) fn shadow_cascades_at(index: usize) -> u32 {
*SHADOW_CASCADES_VALUES
.get(index)
.unwrap_or(&SHADOW_CASCADES_VALUES[2])
}
pub(crate) fn shadow_cascades_index(count: u32) -> usize {
nearest_count_index(&SHADOW_CASCADES_VALUES, count)
}
pub(crate) fn anisotropy_at(index: usize) -> u32 {
*ANISOTROPY_LEVELS
.get(index)
.unwrap_or(&ANISOTROPY_LEVELS[3])
}
pub(crate) fn anisotropy_index(level: u32) -> usize {
nearest_count_index(&ANISOTROPY_LEVELS, level)
}
pub(crate) fn fps_cap_at(index: usize) -> u32 {
*FPS_CAP_VALUES.get(index).unwrap_or(&FPS_CAP_VALUES[0])
}
pub(crate) fn fps_cap_index(cap: u32) -> usize {
nearest_count_index(&FPS_CAP_VALUES, cap)
}
pub(crate) fn frames_in_flight_at(index: usize) -> u32 {
*FRAME_BUFFERING_COUNTS
.get(index)
.unwrap_or(&FRAME_BUFFERING_COUNTS[1])
}
pub(crate) fn frames_in_flight_index(count: u32) -> usize {
nearest_count_index(&FRAME_BUFFERING_COUNTS, count)
}
pub(crate) fn texture_quality_at(index: usize) -> (u32, u32) {
let i = index.min(TEXTURE_QUALITY_CAPS.len() - 1);
(TEXTURE_QUALITY_CAPS[i], TEXTURE_QUALITY_BUDGETS[i])
}
pub(crate) fn texture_quality_index(cap: u32) -> usize {
nearest_count_index(&TEXTURE_QUALITY_CAPS, cap)
}
pub(crate) fn volume_at(index: usize) -> f32 {
*VOLUME_GAINS.get(index).unwrap_or(&DEFAULT_VOLUME)
}
pub(crate) fn volume_index(gain: f32) -> usize {
VOLUME_GAINS
.iter()
.position(|g| (g - gain).abs() < 1.0e-4)
.unwrap_or(VOLUME_GAINS.len() - 1)
}
pub(crate) fn cycle(index: usize, len: usize, op: SettingOp) -> usize {
debug_assert!(len > 0);
match op {
SettingOp::Prev => (index + len - 1) % len,
SettingOp::SetIndex(i) => i.min(len.saturating_sub(1)),
SettingOp::Next
| SettingOp::SetFraction(_)
| SettingOp::Rebind(_)
| SettingOp::RebindButton(_) => (index + 1) % len,
}
}
const EXPOSURE_EV_RANGE: (f32, f32) = (-3.0, 3.0);
const BLOOM_INTENSITY_RANGE: (f32, f32) = (0.0, 2.0);
const BLOOM_THRESHOLD_RANGE: (f32, f32) = (0.0, 4.0);
const VIGNETTE_RANGE: (f32, f32) = (0.0, 1.0);
const LUT_STRENGTH_RANGE: (f32, f32) = (0.0, 1.0);
const AMBIENT_RANGE: (f32, f32) = (0.0, 4.0);
const BLOOM_KNEE_RANGE: (f32, f32) = (0.0, 1.0);
const SSAO_RADIUS_RANGE: (f32, f32) = (0.05, 2.0);
const SSAO_INTENSITY_RANGE: (f32, f32) = (0.0, 4.0);
const SSR_INTENSITY_RANGE: (f32, f32) = (0.0, 1.0);
const SSR_MAX_DISTANCE_RANGE: (f32, f32) = (1.0, 200.0);
const SSGI_INTENSITY_RANGE: (f32, f32) = (0.0, 4.0);
const SSGI_MAX_DISTANCE_RANGE: (f32, f32) = (0.5, 40.0);
const AE_MIN_EV_RANGE: (f32, f32) = (-16.0, 16.0);
const AE_MAX_EV_RANGE: (f32, f32) = (-16.0, 16.0);
const AE_SPEED_RANGE: (f32, f32) = (0.1, 6.0);
pub(crate) const QUALITY_PARAM_SLIDER_KEYS: [&str; 9] = [
"ssao_radius",
"ssao_intensity",
"ssr_intensity",
"ssr_max_distance",
"ssgi_intensity",
"ssgi_max_distance",
"auto_exposure_min_ev",
"auto_exposure_max_ev",
"auto_exposure_speed",
];
pub(crate) fn is_quality_param_slider(key: &str) -> bool {
QUALITY_PARAM_SLIDER_KEYS.contains(&key)
}
const MOUSE_SENSITIVITY_RANGE: (f32, f32) = (1.0, 100.0);
const MOUSE_SENS_MIN: f32 = 0.0003;
const MOUSE_SENS_MAX: f32 = 0.005;
const GAMEPAD_LOOK_RANGE: (f32, f32) = (1.0, 100.0);
const GAMEPAD_LOOK_MIN: f32 = 0.5;
const GAMEPAD_LOOK_MAX: f32 = 6.0;
const GAMEPAD_DEADZONE_RANGE: (f32, f32) = (0.0, 40.0);
const FOV_RANGE: (f32, f32) = (50.0, 100.0);
pub(crate) const DEFAULT_FOV: f32 = 75.0;
pub(crate) const SLIDER_STEP_FRACTION: f32 = 0.05;
pub(crate) fn slider_range(key: &str) -> Option<(f32, f32)> {
match key {
"exposure" => Some(EXPOSURE_EV_RANGE),
"bloom_intensity" => Some(BLOOM_INTENSITY_RANGE),
"bloom_threshold" => Some(BLOOM_THRESHOLD_RANGE),
"vignette" => Some(VIGNETTE_RANGE),
"lut_strength" => Some(LUT_STRENGTH_RANGE),
"ambient_intensity" => Some(AMBIENT_RANGE),
"bloom_knee" => Some(BLOOM_KNEE_RANGE),
"ssao_radius" => Some(SSAO_RADIUS_RANGE),
"ssao_intensity" => Some(SSAO_INTENSITY_RANGE),
"ssr_intensity" => Some(SSR_INTENSITY_RANGE),
"ssr_max_distance" => Some(SSR_MAX_DISTANCE_RANGE),
"ssgi_intensity" => Some(SSGI_INTENSITY_RANGE),
"ssgi_max_distance" => Some(SSGI_MAX_DISTANCE_RANGE),
"auto_exposure_min_ev" => Some(AE_MIN_EV_RANGE),
"auto_exposure_max_ev" => Some(AE_MAX_EV_RANGE),
"auto_exposure_speed" => Some(AE_SPEED_RANGE),
"mouse_sensitivity" => Some(MOUSE_SENSITIVITY_RANGE),
"gamepad_look_sensitivity" => Some(GAMEPAD_LOOK_RANGE),
"gamepad_deadzone" => Some(GAMEPAD_DEADZONE_RANGE),
"fov" => Some(FOV_RANGE),
_ => None,
}
}
pub(crate) fn is_controls_slider(key: &str) -> bool {
matches!(
key,
"mouse_sensitivity" | "fov" | "gamepad_look_sensitivity" | "gamepad_deadzone"
)
}
pub(crate) fn slider_value_at(key: &str, fraction: f32) -> Option<f32> {
let (lo, hi) = slider_range(key)?;
Some(lo + (hi - lo) * fraction.clamp(0.0, 1.0))
}
pub(crate) fn slider_fraction(key: &str, value: f32) -> Option<f32> {
let (lo, hi) = slider_range(key)?;
let span = hi - lo;
if span.abs() < f32::EPSILON {
return Some(0.0);
}
Some(((value - lo) / span).clamp(0.0, 1.0))
}
pub(crate) fn format_slider_value(key: &str, value: f32) -> String {
match key {
"exposure" | "auto_exposure_min_ev" | "auto_exposure_max_ev" => {
format!("{value:+.1} EV")
}
"ssr_max_distance" | "ssgi_max_distance" | "ssao_radius" => format!("{value:.1} m"),
"vignette" | "lut_strength" => format!("{}%", (value * 100.0).round() as i32),
"mouse_sensitivity" | "gamepad_look_sensitivity" => format!("{}", value.round() as i32),
"gamepad_deadzone" => format!("{}%", value.round() as i32),
"fov" => format!("{}\u{00b0}", value.round() as i32),
_ => format!("{value:.2}"),
}
}
pub(crate) fn slider_apply_value(key: &str, value: f32) -> f32 {
match key {
"exposure" => value.clamp(-16.0, 16.0).exp2(),
"bloom_intensity" | "bloom_threshold" => value.max(0.0),
"bloom_knee" => value.max(0.0),
"vignette" | "lut_strength" => value.clamp(0.0, 1.0),
"ambient_intensity" => value.clamp(0.0, 16.0),
"ssao_radius" => value.max(1.0e-3),
"ssao_intensity" => value.clamp(0.0, 4.0),
"ssr_intensity" => value.clamp(0.0, 1.0),
"ssr_max_distance" => value.clamp(1.0, 200.0),
"ssgi_intensity" => value.clamp(0.0, 4.0),
"ssgi_max_distance" => value.clamp(0.5, 100.0),
"auto_exposure_min_ev" | "auto_exposure_max_ev" => value.clamp(-16.0, 16.0),
"auto_exposure_speed" => value.clamp(1.0e-3, 20.0),
"mouse_sensitivity" => {
let v = value.clamp(MOUSE_SENSITIVITY_RANGE.0, MOUSE_SENSITIVITY_RANGE.1);
MOUSE_SENS_MIN + (MOUSE_SENS_MAX - MOUSE_SENS_MIN) * (v - 1.0) / 99.0
}
"gamepad_look_sensitivity" => {
let v = value.clamp(GAMEPAD_LOOK_RANGE.0, GAMEPAD_LOOK_RANGE.1);
GAMEPAD_LOOK_MIN + (GAMEPAD_LOOK_MAX - GAMEPAD_LOOK_MIN) * (v - 1.0) / 99.0
}
"gamepad_deadzone" => {
value.clamp(GAMEPAD_DEADZONE_RANGE.0, GAMEPAD_DEADZONE_RANGE.1) / 100.0
}
"fov" => value.clamp(FOV_RANGE.0, FOV_RANGE.1),
_ => value,
}
}
pub(crate) fn slider_recover_value(key: &str, stored: f32) -> f32 {
match key {
"exposure" => stored.max(1.0e-6).log2(),
"mouse_sensitivity" => {
1.0 + (stored - MOUSE_SENS_MIN) / (MOUSE_SENS_MAX - MOUSE_SENS_MIN) * 99.0
}
"gamepad_look_sensitivity" => {
1.0 + (stored - GAMEPAD_LOOK_MIN) / (GAMEPAD_LOOK_MAX - GAMEPAD_LOOK_MIN) * 99.0
}
"gamepad_deadzone" => stored * 100.0,
_ => stored,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vsync_options_are_off_then_on() {
assert_eq!(options("vsync"), Some(&["Off", "On"][..]));
}
#[test]
fn stats_hud_toggles_are_off_then_on() {
for key in ["perf_stats", "show_fps", "show_vram"] {
assert_eq!(options(key), Some(&["Off", "On"][..]), "{key}");
let caps = crate::gfx::backend::DeviceCapabilities {
ray_tracing: false,
..crate::gfx::backend::DeviceCapabilities::ALL
};
assert!(setting_available(key, &caps), "{key}");
}
}
#[test]
fn unknown_key_has_no_options() {
assert!(options("does_not_exist").is_none());
}
#[test]
fn graphics_quality_options_match_preset_order() {
use crate::gfx::quality_preset::QualityPreset;
let labels = options("graphics_quality").expect("graphics_quality options");
assert_eq!(labels.len(), QualityPreset::ALL.len());
for (i, p) in QualityPreset::ALL.iter().enumerate() {
assert_eq!(labels[i], p.name(), "label {i}");
}
}
#[test]
fn quality_toggles_are_off_then_on_and_classified() {
for key in QUALITY_TOGGLE_KEYS {
assert!(is_quality_toggle(key), "{key} should classify as a toggle");
assert_eq!(options(key), Some(&["Off", "On"][..]), "{key} options");
assert!(slider_range(key).is_none(), "{key} should not be a slider");
}
assert!(!is_quality_toggle("vsync"));
assert!(!is_quality_toggle("exposure"));
assert!(!is_quality_toggle("nope"));
}
#[test]
fn rebind_keys_are_a_distinct_category() {
use crate::gfx::keymap::Bindable;
for b in Bindable::ALL {
let key = b.setting_key();
assert!(options(key).is_none(), "{key} should not be a cycle row");
assert!(slider_range(key).is_none(), "{key} should not be a slider");
}
}
#[test]
fn rt_toggle_gated_on_ray_tracing_capability() {
use crate::gfx::backend::DeviceCapabilities;
let capable = DeviceCapabilities {
ray_tracing: true,
..DeviceCapabilities::ALL
};
let incapable = DeviceCapabilities {
ray_tracing: false,
..DeviceCapabilities::ALL
};
assert!(setting_available("ray_traced_reflections", &capable));
assert!(!setting_available("ray_traced_reflections", &incapable));
for key in ["vsync", "aa_mode", "ssao", "ssr", "ssgi", "auto_exposure"] {
assert!(
setting_available(key, &incapable),
"{key} should be available"
);
}
assert!(setting_available(
"ray_traced_reflections",
&DeviceCapabilities::default()
));
}
#[test]
fn aa_mode_round_trips_and_orders_by_cost() {
for (i, mode) in [AaMode::Off, AaMode::Fxaa, AaMode::Taa]
.into_iter()
.enumerate()
{
assert_eq!(aa_mode_index(mode), i);
assert_eq!(aa_mode_at(i), mode);
}
assert_eq!(options("aa_mode").unwrap().len(), 3);
assert_eq!(aa_mode_at(9), AaMode::Fxaa);
}
#[test]
fn fps_cap_round_trips_and_snaps() {
assert_eq!(options("fps_cap").unwrap().len(), FPS_CAP_VALUES.len());
for (i, &cap) in FPS_CAP_VALUES.iter().enumerate() {
assert_eq!(fps_cap_index(cap), i);
assert_eq!(fps_cap_at(i), cap);
}
assert_eq!(fps_cap_at(0), 0);
assert_eq!(fps_cap_at(99), 0);
assert_eq!(fps_cap_index(58), fps_cap_index(60));
assert_eq!(fps_cap_index(1000), FPS_CAP_VALUES.len() - 1);
}
#[test]
fn cycle_next_wraps() {
assert_eq!(cycle(0, 2, SettingOp::Next), 1);
assert_eq!(cycle(1, 2, SettingOp::Next), 0);
}
#[test]
fn cycle_prev_wraps() {
assert_eq!(cycle(0, 2, SettingOp::Prev), 1);
assert_eq!(cycle(1, 2, SettingOp::Prev), 0);
}
#[test]
fn cycle_three_options() {
assert_eq!(cycle(2, 3, SettingOp::Next), 0);
assert_eq!(cycle(0, 3, SettingOp::Prev), 2);
}
#[test]
fn cycle_set_index_jumps_and_clamps() {
assert_eq!(cycle(0, 4, SettingOp::SetIndex(2)), 2);
assert_eq!(cycle(3, 4, SettingOp::SetIndex(0)), 0);
assert_eq!(cycle(1, 4, SettingOp::SetIndex(9)), 3);
}
#[test]
fn known_settings_have_options() {
assert_eq!(options("window_mode").unwrap().len(), 3);
assert_eq!(options("render_scale").unwrap().len(), 4);
assert!(options("resolution").is_none());
assert!(concinnity_core::gfx::settings::is_dynamic_dropdown(
"resolution"
));
assert_eq!(options("master_volume").unwrap().len(), 5);
assert!(options("mouse_sensitivity").is_none());
assert!(slider_range("mouse_sensitivity").is_some());
}
#[test]
fn volume_index_and_at_round_trip() {
for i in 0..VOLUME_GAINS.len() {
assert_eq!(volume_index(volume_at(i)), i);
}
assert_eq!(volume_index(0.33), VOLUME_GAINS.len() - 1);
assert_eq!(volume_at(volume_index(DEFAULT_VOLUME)), 1.0);
}
#[test]
fn mouse_sensitivity_is_a_slider_1_to_100() {
assert_eq!(slider_range("mouse_sensitivity"), Some((1.0, 100.0)));
assert!(options("mouse_sensitivity").is_none());
for &ui in &[1.0_f32, 25.0, 50.0, 100.0] {
let stored = slider_apply_value("mouse_sensitivity", ui);
let back = slider_recover_value("mouse_sensitivity", stored);
assert!((back - ui).abs() < 1.0e-2, "ui={ui} -> {stored} -> {back}");
}
assert!((slider_apply_value("mouse_sensitivity", 1.0) - MOUSE_SENS_MIN).abs() < 1.0e-9);
assert!((slider_apply_value("mouse_sensitivity", 100.0) - MOUSE_SENS_MAX).abs() < 1.0e-9);
assert!(
slider_apply_value("mouse_sensitivity", 10.0)
< slider_apply_value("mouse_sensitivity", 90.0)
);
assert_eq!(format_slider_value("mouse_sensitivity", 26.3), "26");
let def = slider_recover_value("mouse_sensitivity", DEFAULT_MOUSE_SENSITIVITY);
assert!(
(1.0..=100.0).contains(&def),
"default UI value {def} in range"
);
}
#[test]
fn fov_is_a_degrees_slider() {
assert_eq!(slider_range("fov"), Some((50.0, 100.0)));
assert!(options("fov").is_none());
for ° in &[50.0_f32, 75.0, 100.0] {
let stored = slider_apply_value("fov", deg);
assert!((stored - deg).abs() < 1.0e-6);
assert!((slider_recover_value("fov", stored) - deg).abs() < 1.0e-6);
}
assert_eq!(slider_apply_value("fov", 10.0), 50.0);
assert_eq!(slider_apply_value("fov", 200.0), 100.0);
assert_eq!(format_slider_value("fov", 74.6), "75\u{00b0}");
assert!((50.0..=100.0).contains(&DEFAULT_FOV));
}
#[test]
fn window_mode_index_and_at_round_trip() {
for m in [
WindowMode::Windowed,
WindowMode::Borderless,
WindowMode::Fullscreen,
] {
assert_eq!(window_mode_at(window_mode_index(m)), m);
}
}
#[test]
fn ssgi_sub_quality_round_trips_and_snaps() {
for r in [
SsgiResolution::Full,
SsgiResolution::Half,
SsgiResolution::Quarter,
] {
assert_eq!(ssgi_resolution_at(ssgi_resolution_index(r)), r);
}
for i in 0..SSGI_RAYS_COUNTS.len() {
assert_eq!(ssgi_rays_index(ssgi_rays_at(i)), i);
}
for i in 0..SSGI_STEPS_COUNTS.len() {
assert_eq!(ssgi_steps_index(ssgi_steps_at(i)), i);
}
assert_eq!(ssgi_rays_index(7), 1); assert_eq!(ssgi_rays_index(20), 2); assert_eq!(ssgi_steps_index(40), 3); for key in ["ssgi_resolution", "ssgi_rays", "ssgi_steps"] {
assert!(options(key).is_some(), "{key} should be a cycle row");
assert!(slider_range(key).is_none(), "{key} should not be a slider");
}
}
#[test]
fn reflection_blur_round_trips() {
for r in [
ReflectionBlurResolution::Full,
ReflectionBlurResolution::Half,
ReflectionBlurResolution::Quarter,
] {
assert_eq!(reflection_blur_at(reflection_blur_index(r)), r);
}
assert_eq!(
options("reflection_blur_resolution").map(|o| o.len()),
Some(3)
);
assert!(QUALITY_CYCLE_KEYS.contains(&"reflection_blur_resolution"));
}
#[test]
fn display_toggles_are_off_on_cycle_rows() {
for key in ["temporal_upscaling", "hdr_display", "hdr_pq"] {
assert_eq!(options(key), Some(&["Off", "On"][..]), "{key} options");
assert!(slider_range(key).is_none(), "{key} should not be a slider");
assert!(!is_quality_toggle(key), "{key} is not a quality toggle");
assert!(
!QUALITY_CYCLE_KEYS.contains(&key),
"{key} is not a quality cycle knob"
);
}
}
#[test]
fn shadow_resolution_round_trips_and_snaps() {
for i in 0..SHADOW_RESOLUTION_SIZES.len() {
assert_eq!(shadow_resolution_index(shadow_resolution_at(i)), i);
}
assert_eq!(shadow_resolution_at(0), 0);
assert_eq!(shadow_resolution_index(2048), 2);
assert_eq!(shadow_resolution_index(1500), 1); assert_eq!(shadow_resolution_index(8192), 3); assert!(options("shadow_map_size").is_some());
assert!(slider_range("shadow_map_size").is_none());
}
#[test]
fn anisotropy_round_trips_and_snaps() {
for i in 0..ANISOTROPY_LEVELS.len() {
assert_eq!(anisotropy_index(anisotropy_at(i)), i);
}
assert_eq!(anisotropy_at(0), 1);
assert_eq!(anisotropy_index(8), 3);
assert_eq!(anisotropy_index(3), 1); assert_eq!(anisotropy_index(32), 4); assert!(options("anisotropy").is_some());
assert!(slider_range("anisotropy").is_none());
}
#[test]
fn shadow_distance_round_trips_and_snaps() {
for i in 0..SHADOW_DISTANCE_VALUES.len() {
assert_eq!(shadow_distance_index(shadow_distance_at(i)), i);
}
assert_eq!(shadow_distance_at(1), 80);
assert_eq!(shadow_distance_index(80), 1);
assert_eq!(shadow_distance_index(50), 0); assert_eq!(shadow_distance_index(1000), 3); assert!(options("shadow_distance").is_some());
assert!(slider_range("shadow_distance").is_none());
}
#[test]
fn shadow_cascades_round_trips_and_snaps() {
for i in 0..SHADOW_CASCADES_VALUES.len() {
assert_eq!(shadow_cascades_index(shadow_cascades_at(i)), i);
}
assert_eq!(shadow_cascades_at(2), 4);
assert_eq!(shadow_cascades_index(4), 2);
assert_eq!(shadow_cascades_at(9), 4);
assert_eq!(shadow_cascades_index(1), 0); assert!(options("shadow_cascades").is_some());
assert!(slider_range("shadow_cascades").is_none());
}
#[test]
fn shadow_update_round_trips() {
for u in [ShadowUpdate::EveryFrame, ShadowUpdate::Hybrid] {
assert_eq!(shadow_update_at(shadow_update_index(u)), u);
}
assert_eq!(shadow_update_at(0), ShadowUpdate::EveryFrame);
assert_eq!(options("shadow_update").map(|o| o.len()), Some(2));
}
#[test]
fn frame_buffering_round_trips_and_snaps() {
for i in 0..FRAME_BUFFERING_COUNTS.len() {
assert_eq!(frames_in_flight_index(frames_in_flight_at(i)), i);
}
assert_eq!(frames_in_flight_at(0), 1);
assert_eq!(frames_in_flight_index(4), 2); assert!(options("frames_in_flight").is_some());
}
#[test]
fn texture_quality_pairs_cap_and_budget() {
for i in 0..TEXTURE_QUALITY_CAPS.len() {
let (cap, budget) = texture_quality_at(i);
assert_eq!(texture_quality_index(cap), i);
assert_eq!(cap, TEXTURE_QUALITY_CAPS[i]);
assert_eq!(budget, TEXTURE_QUALITY_BUDGETS[i]);
}
assert_eq!(texture_quality_index(96), 1);
assert_eq!(texture_quality_index(300), 3); assert_eq!(options("occlusion_two_pass"), Some(&["Off", "On"][..]));
assert!(slider_range("occlusion_two_pass").is_none());
assert!(!is_quality_toggle("occlusion_two_pass"));
}
#[test]
fn render_scale_index_and_at_round_trip() {
for q in [
UpscaleQuality::Quality,
UpscaleQuality::Balanced,
UpscaleQuality::Performance,
UpscaleQuality::UltraPerformance,
] {
assert_eq!(render_scale_at(render_scale_index(q)), q);
}
}
#[test]
fn upscale_backend_round_trips_and_vendor_gates() {
assert_eq!(options("upscale_backend").unwrap().len(), 4);
for b in [
UpscalerBackend::Auto,
UpscalerBackend::Fsr3,
UpscalerBackend::Dlss,
UpscalerBackend::Xess,
] {
assert_eq!(upscale_backend_at(upscale_backend_index(b)), b);
}
assert!(options("upscale_backend").is_some());
assert!(slider_range("upscale_backend").is_none());
for vendor in [
GpuVendor::Apple,
GpuVendor::Nvidia,
GpuVendor::Amd,
GpuVendor::Intel,
GpuVendor::Other,
] {
assert!(upscale_backend_available(UpscalerBackend::Auto, vendor));
assert!(upscale_backend_available(UpscalerBackend::Fsr3, vendor));
}
assert!(upscale_backend_available(
UpscalerBackend::Dlss,
GpuVendor::Nvidia
));
assert!(!upscale_backend_available(
UpscalerBackend::Dlss,
GpuVendor::Amd
));
assert!(upscale_backend_available(
UpscalerBackend::Xess,
GpuVendor::Intel
));
assert!(!upscale_backend_available(
UpscalerBackend::Xess,
GpuVendor::Nvidia
));
assert!(setting_available(
"upscale_backend",
&crate::gfx::backend::DeviceCapabilities::ALL
));
assert!(!setting_available(
"upscale_backend",
&crate::gfx::backend::DeviceCapabilities {
selectable_upscaler: false,
..crate::gfx::backend::DeviceCapabilities::ALL
}
));
}
#[test]
fn exposure_is_a_slider_not_a_cycle() {
assert!(slider_range("exposure").is_some());
assert!(options("exposure").is_none());
assert!(slider_range("vsync").is_none());
}
#[test]
fn slider_value_and_fraction_round_trip() {
assert_eq!(slider_value_at("exposure", 0.0), Some(-3.0));
assert_eq!(slider_value_at("exposure", 1.0), Some(3.0));
assert_eq!(slider_value_at("exposure", 0.5), Some(0.0));
for &f in &[0.0_f32, 0.25, 0.5, 0.75, 1.0] {
let v = slider_value_at("exposure", f).unwrap();
let back = slider_fraction("exposure", v).unwrap();
assert!((back - f).abs() < 1.0e-5, "f={f} -> v={v} -> {back}");
}
}
#[test]
fn slider_fraction_clamps_out_of_range() {
assert_eq!(slider_fraction("exposure", -100.0), Some(0.0));
assert_eq!(slider_fraction("exposure", 100.0), Some(1.0));
assert_eq!(slider_fraction("exposure", 0.0), Some(0.5));
}
#[test]
fn unknown_slider_key_has_no_range() {
assert!(slider_range("nope").is_none());
assert!(slider_value_at("nope", 0.5).is_none());
assert!(slider_fraction("nope", 0.0).is_none());
}
#[test]
fn exposure_value_is_formatted_in_stops() {
assert_eq!(format_slider_value("exposure", 0.0), "+0.0 EV");
assert_eq!(format_slider_value("exposure", 1.5), "+1.5 EV");
assert_eq!(format_slider_value("exposure", -2.0), "-2.0 EV");
}
#[test]
fn post_process_sliders_have_ranges_and_round_trip() {
for key in [
"bloom_intensity",
"bloom_threshold",
"vignette",
"lut_strength",
"ambient_intensity",
] {
assert!(slider_range(key).is_some(), "{key} should be a slider");
assert!(options(key).is_none(), "{key} should not be a cycle row");
let (lo, hi) = slider_range(key).unwrap();
assert!(lo < hi, "{key} range must be non-empty");
assert_eq!(slider_value_at(key, 0.0), Some(lo));
assert_eq!(slider_value_at(key, 1.0), Some(hi));
for &f in &[0.0_f32, 0.25, 0.5, 0.75, 1.0] {
let v = slider_value_at(key, f).unwrap();
let back = slider_fraction(key, v).unwrap();
assert!((back - f).abs() < 1.0e-5, "{key}: f={f} -> {v} -> {back}");
}
}
}
#[test]
fn slider_apply_and_recover_round_trip() {
for key in [
"exposure",
"bloom_intensity",
"bloom_threshold",
"vignette",
"lut_strength",
"ambient_intensity",
] {
for &f in &[0.0_f32, 0.25, 0.5, 0.75, 1.0] {
let v = slider_value_at(key, f).unwrap();
let stored = slider_apply_value(key, v);
let recovered = slider_recover_value(key, stored);
assert!(
(recovered - v).abs() < 1.0e-4,
"{key}: v={v} stored={stored} recovered={recovered}"
);
}
}
}
#[test]
fn slider_apply_value_clamps_match_resolve() {
assert_eq!(slider_apply_value("bloom_intensity", -5.0), 0.0);
assert_eq!(slider_apply_value("vignette", 2.0), 1.0);
assert_eq!(slider_apply_value("lut_strength", -1.0), 0.0);
assert_eq!(slider_apply_value("ambient_intensity", 100.0), 16.0);
assert_eq!(slider_apply_value("exposure", 2.0), 4.0);
assert!((slider_recover_value("exposure", 4.0) - 2.0).abs() < 1.0e-5);
assert_eq!(slider_apply_value("bloom_knee", -1.0), 0.0);
assert_eq!(slider_apply_value("ssao_intensity", 100.0), 4.0);
assert_eq!(slider_apply_value("ssr_intensity", 9.0), 1.0);
assert_eq!(slider_apply_value("ssr_max_distance", 1.0e6), 200.0);
assert_eq!(slider_apply_value("ssgi_intensity", 99.0), 4.0);
assert_eq!(slider_apply_value("ssgi_max_distance", 1.0e6), 100.0);
assert_eq!(slider_apply_value("auto_exposure_min_ev", -100.0), -16.0);
assert_eq!(slider_apply_value("auto_exposure_max_ev", 100.0), 16.0);
assert_eq!(slider_apply_value("auto_exposure_speed", 100.0), 20.0);
}
#[test]
fn quality_param_sliders_are_independent_sliders() {
for key in QUALITY_PARAM_SLIDER_KEYS {
assert!(
is_quality_param_slider(key),
"{key} should be a qparam slider"
);
assert!(
slider_range(key).is_some(),
"{key} should have a slider range"
);
assert!(options(key).is_none(), "{key} should not be a cycle row");
assert!(
!QUALITY_CYCLE_KEYS.contains(&key),
"{key} should not be preset-governed"
);
}
assert!(slider_range("bloom_knee").is_some());
assert!(!is_quality_param_slider("bloom_knee"));
}
#[test]
fn strength_sliders_format_as_percent() {
assert_eq!(format_slider_value("vignette", 0.0), "0%");
assert_eq!(format_slider_value("vignette", 0.5), "50%");
assert_eq!(format_slider_value("lut_strength", 1.0), "100%");
assert_eq!(format_slider_value("bloom_intensity", 0.6), "0.60");
assert_eq!(format_slider_value("ambient_intensity", 1.25), "1.25");
}
}