use crate::ecs::camera::components::EffectiveShading;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ViewportFocusPolicy {
#[default]
FocusedAlways,
Manual,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum PbrDebugMode {
#[default]
None,
BaseColor,
Normal,
Metallic,
Roughness,
Occlusion,
Emissive,
F,
G,
D,
Diffuse,
Specular,
MipRainbow,
}
impl PbrDebugMode {
pub const ALL: &'static [PbrDebugMode] = &[
PbrDebugMode::None,
PbrDebugMode::BaseColor,
PbrDebugMode::Normal,
PbrDebugMode::Metallic,
PbrDebugMode::Roughness,
PbrDebugMode::Occlusion,
PbrDebugMode::Emissive,
PbrDebugMode::F,
PbrDebugMode::G,
PbrDebugMode::D,
PbrDebugMode::Diffuse,
PbrDebugMode::Specular,
PbrDebugMode::MipRainbow,
];
pub fn name(&self) -> &'static str {
match self {
PbrDebugMode::None => "None",
PbrDebugMode::BaseColor => "Base Color",
PbrDebugMode::Normal => "Normal",
PbrDebugMode::Metallic => "Metallic",
PbrDebugMode::Roughness => "Roughness",
PbrDebugMode::Occlusion => "Occlusion",
PbrDebugMode::Emissive => "Emissive",
PbrDebugMode::F => "Fresnel (F)",
PbrDebugMode::G => "Geometry (G)",
PbrDebugMode::D => "Distribution (D)",
PbrDebugMode::Diffuse => "Diffuse",
PbrDebugMode::Specular => "Specular",
PbrDebugMode::MipRainbow => "Mip Rainbow",
}
}
pub fn as_u32(&self) -> u32 {
match self {
PbrDebugMode::None => 0,
PbrDebugMode::BaseColor => 1,
PbrDebugMode::Normal => 2,
PbrDebugMode::Metallic => 3,
PbrDebugMode::Roughness => 4,
PbrDebugMode::Occlusion => 5,
PbrDebugMode::Emissive => 6,
PbrDebugMode::F => 7,
PbrDebugMode::G => 8,
PbrDebugMode::D => 9,
PbrDebugMode::Diffuse => 10,
PbrDebugMode::Specular => 11,
PbrDebugMode::MipRainbow => 12,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum DepthOfFieldQuality {
Low,
#[default]
Medium,
High,
}
impl DepthOfFieldQuality {
pub const ALL: &'static [DepthOfFieldQuality] = &[
DepthOfFieldQuality::Low,
DepthOfFieldQuality::Medium,
DepthOfFieldQuality::High,
];
pub fn name(&self) -> &'static str {
match self {
DepthOfFieldQuality::Low => "Low",
DepthOfFieldQuality::Medium => "Medium",
DepthOfFieldQuality::High => "High",
}
}
pub fn sample_count(&self) -> u32 {
match self {
DepthOfFieldQuality::Low => 8,
DepthOfFieldQuality::Medium => 16,
DepthOfFieldQuality::High => 32,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct DepthOfField {
pub enabled: bool,
pub focus_distance: f32,
pub focus_range: f32,
pub max_blur_radius: f32,
pub bokeh_threshold: f32,
pub bokeh_intensity: f32,
pub quality: DepthOfFieldQuality,
pub visualize_coc: bool,
pub tilt_shift_enabled: bool,
pub tilt_shift_angle: f32,
pub tilt_shift_center: f32,
pub tilt_shift_blur_amount: f32,
pub visualize_tilt_shift: bool,
}
impl Default for DepthOfField {
fn default() -> Self {
Self {
enabled: false,
focus_distance: 10.0,
focus_range: 5.0,
max_blur_radius: 8.0,
bokeh_threshold: 0.8,
bokeh_intensity: 1.0,
quality: DepthOfFieldQuality::Medium,
visualize_coc: false,
tilt_shift_enabled: false,
tilt_shift_angle: 0.0,
tilt_shift_center: 0.0,
tilt_shift_blur_amount: 1.0,
visualize_tilt_shift: false,
}
}
}
impl DepthOfField {
pub fn portrait() -> Self {
Self {
enabled: true,
focus_distance: 3.0,
focus_range: 1.5,
max_blur_radius: 12.0,
bokeh_threshold: 0.6,
bokeh_intensity: 1.2,
quality: DepthOfFieldQuality::High,
visualize_coc: false,
tilt_shift_enabled: false,
tilt_shift_angle: 0.0,
tilt_shift_center: 0.0,
tilt_shift_blur_amount: 1.0,
visualize_tilt_shift: false,
}
}
pub fn cinematic() -> Self {
Self {
enabled: true,
focus_distance: 8.0,
focus_range: 4.0,
max_blur_radius: 10.0,
bokeh_threshold: 0.7,
bokeh_intensity: 1.0,
quality: DepthOfFieldQuality::Medium,
visualize_coc: false,
tilt_shift_enabled: false,
tilt_shift_angle: 0.0,
tilt_shift_center: 0.0,
tilt_shift_blur_amount: 1.0,
visualize_tilt_shift: false,
}
}
pub fn macro_shot() -> Self {
Self {
enabled: true,
focus_distance: 0.5,
focus_range: 0.2,
max_blur_radius: 16.0,
bokeh_threshold: 0.5,
bokeh_intensity: 1.5,
quality: DepthOfFieldQuality::High,
visualize_coc: false,
tilt_shift_enabled: false,
tilt_shift_angle: 0.0,
tilt_shift_center: 0.0,
tilt_shift_blur_amount: 1.0,
visualize_tilt_shift: false,
}
}
pub fn landscape() -> Self {
Self {
enabled: true,
focus_distance: 50.0,
focus_range: 100.0,
max_blur_radius: 4.0,
bokeh_threshold: 0.9,
bokeh_intensity: 0.5,
quality: DepthOfFieldQuality::Low,
visualize_coc: false,
tilt_shift_enabled: false,
tilt_shift_angle: 0.0,
tilt_shift_center: 0.0,
tilt_shift_blur_amount: 1.0,
visualize_tilt_shift: false,
}
}
pub fn tilt_shift() -> Self {
Self {
enabled: true,
focus_distance: 10.0,
focus_range: 5.0,
max_blur_radius: 12.0,
bokeh_threshold: 0.8,
bokeh_intensity: 0.8,
quality: DepthOfFieldQuality::Medium,
visualize_coc: false,
tilt_shift_enabled: true,
tilt_shift_angle: 0.0,
tilt_shift_center: 0.0,
tilt_shift_blur_amount: 1.0,
visualize_tilt_shift: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum TonemapAlgorithm {
#[default]
Aces,
Reinhard,
ReinhardExtended,
Uncharted2,
AgX,
Neutral,
None,
}
impl TonemapAlgorithm {
pub const ALL: &'static [TonemapAlgorithm] = &[
TonemapAlgorithm::Aces,
TonemapAlgorithm::Reinhard,
TonemapAlgorithm::ReinhardExtended,
TonemapAlgorithm::Uncharted2,
TonemapAlgorithm::AgX,
TonemapAlgorithm::Neutral,
TonemapAlgorithm::None,
];
pub fn as_u32(&self) -> u32 {
match self {
TonemapAlgorithm::Aces => 0,
TonemapAlgorithm::Reinhard => 1,
TonemapAlgorithm::ReinhardExtended => 2,
TonemapAlgorithm::Uncharted2 => 3,
TonemapAlgorithm::AgX => 4,
TonemapAlgorithm::Neutral => 5,
TonemapAlgorithm::None => 6,
}
}
pub fn name(&self) -> &'static str {
match self {
TonemapAlgorithm::Aces => "ACES",
TonemapAlgorithm::Reinhard => "Reinhard",
TonemapAlgorithm::ReinhardExtended => "Reinhard Extended",
TonemapAlgorithm::Uncharted2 => "Uncharted 2",
TonemapAlgorithm::AgX => "AgX",
TonemapAlgorithm::Neutral => "Neutral",
TonemapAlgorithm::None => "None",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ColorGradingPreset {
#[default]
Default,
Vibrant,
Cinematic,
Muted,
HighContrast,
Warm,
Cool,
Retro,
Desaturated,
Custom,
}
impl ColorGradingPreset {
pub const ALL: &'static [ColorGradingPreset] = &[
ColorGradingPreset::Default,
ColorGradingPreset::Vibrant,
ColorGradingPreset::Cinematic,
ColorGradingPreset::Muted,
ColorGradingPreset::HighContrast,
ColorGradingPreset::Warm,
ColorGradingPreset::Cool,
ColorGradingPreset::Retro,
ColorGradingPreset::Desaturated,
ColorGradingPreset::Custom,
];
pub fn name(&self) -> &'static str {
match self {
ColorGradingPreset::Default => "Default",
ColorGradingPreset::Vibrant => "Vibrant",
ColorGradingPreset::Cinematic => "Cinematic",
ColorGradingPreset::Muted => "Muted",
ColorGradingPreset::HighContrast => "High Contrast",
ColorGradingPreset::Warm => "Warm",
ColorGradingPreset::Cool => "Cool",
ColorGradingPreset::Retro => "Retro",
ColorGradingPreset::Desaturated => "Desaturated",
ColorGradingPreset::Custom => "Custom",
}
}
pub fn to_color_grading(&self) -> ColorGrading {
let preset = *self;
match self {
ColorGradingPreset::Default => ColorGrading {
preset,
..ColorGrading::default()
},
ColorGradingPreset::Vibrant => ColorGrading {
saturation: 1.3,
brightness: 0.02,
contrast: 1.1,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Cinematic => ColorGrading {
gamma: 2.4,
saturation: 0.9,
brightness: -0.02,
contrast: 1.15,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Muted => ColorGrading {
saturation: 0.7,
contrast: 0.9,
tonemap_algorithm: TonemapAlgorithm::Reinhard,
preset,
..ColorGrading::default()
},
ColorGradingPreset::HighContrast => ColorGrading {
saturation: 1.1,
contrast: 1.4,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Warm => ColorGrading {
gamma: 2.1,
saturation: 1.1,
brightness: 0.03,
contrast: 1.05,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Cool => ColorGrading {
gamma: 2.3,
saturation: 0.95,
brightness: -0.01,
contrast: 1.05,
tonemap_algorithm: TonemapAlgorithm::Neutral,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Retro => ColorGrading {
gamma: 2.0,
saturation: 0.8,
brightness: 0.05,
contrast: 1.2,
tonemap_algorithm: TonemapAlgorithm::Reinhard,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Desaturated => ColorGrading {
saturation: 0.3,
preset,
..ColorGrading::default()
},
ColorGradingPreset::Custom => ColorGrading::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ColorGrading {
pub exposure: f32,
pub exposure_compensation_ev: f32,
pub auto_exposure: bool,
pub auto_exposure_target: f32,
pub auto_exposure_rate: f32,
pub auto_exposure_min_ev: f32,
pub auto_exposure_max_ev: f32,
pub gamma: f32,
pub saturation: f32,
pub brightness: f32,
pub contrast: f32,
pub tonemap_algorithm: TonemapAlgorithm,
pub preset: ColorGradingPreset,
}
impl Default for ColorGrading {
fn default() -> Self {
Self {
exposure: 1.0,
exposure_compensation_ev: 0.0,
auto_exposure: false,
auto_exposure_target: 0.18,
auto_exposure_rate: 1.5,
auto_exposure_min_ev: -3.0,
auto_exposure_max_ev: 5.0,
gamma: 2.2,
saturation: 1.0,
brightness: 0.0,
contrast: 1.0,
tonemap_algorithm: TonemapAlgorithm::Aces,
preset: ColorGradingPreset::Default,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct VertexSnap {
pub resolution: [f32; 2],
}
impl Default for VertexSnap {
fn default() -> Self {
Self {
resolution: [160.0, 120.0],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Fog {
pub color: [f32; 3],
pub start: f32,
pub end: f32,
}
impl Default for Fog {
fn default() -> Self {
Self {
color: [0.5, 0.5, 0.55],
start: 2.0,
end: 15.0,
}
}
}
#[derive(Clone)]
pub struct DayNightState {
pub hour: f32,
pub speed: f32,
pub auto_cycle: bool,
pub sun_entity: Option<crate::ecs::world::Entity>,
}
impl Default for DayNightState {
fn default() -> Self {
Self {
hour: 12.0,
speed: 0.0,
auto_cycle: false,
sun_entity: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum Atmosphere {
#[default]
None,
Sky,
CloudySky,
Space,
Nebula,
Sunset,
DayNight,
Hdr,
HdrMip0,
HdrMip1,
HdrMip2,
HdrMip3,
HdrMip4,
Irradiance,
PrefilterMip0,
PrefilterMip1,
PrefilterMip2,
PrefilterMip3,
PrefilterMip4,
}
impl Atmosphere {
pub const ALL: &'static [Atmosphere] = &[
Atmosphere::None,
Atmosphere::Sky,
Atmosphere::CloudySky,
Atmosphere::Space,
Atmosphere::Nebula,
Atmosphere::Sunset,
Atmosphere::DayNight,
Atmosphere::Hdr,
Atmosphere::HdrMip0,
Atmosphere::HdrMip1,
Atmosphere::HdrMip2,
Atmosphere::HdrMip3,
Atmosphere::HdrMip4,
Atmosphere::Irradiance,
Atmosphere::PrefilterMip0,
Atmosphere::PrefilterMip1,
Atmosphere::PrefilterMip2,
Atmosphere::PrefilterMip3,
Atmosphere::PrefilterMip4,
];
pub fn mip_level(&self) -> f32 {
match self {
Atmosphere::HdrMip0 | Atmosphere::PrefilterMip0 => 0.0,
Atmosphere::HdrMip1 | Atmosphere::PrefilterMip1 => 1.0,
Atmosphere::HdrMip2 | Atmosphere::PrefilterMip2 => 2.0,
Atmosphere::HdrMip3 | Atmosphere::PrefilterMip3 => 3.0,
Atmosphere::HdrMip4 | Atmosphere::PrefilterMip4 => 4.0,
_ => 0.0,
}
}
pub fn next(self) -> Self {
let all = Self::ALL;
let current_index = all.iter().position(|&a| a == self).unwrap_or(0);
let next_index = (current_index + 1) % all.len();
all[next_index]
}
pub fn previous(self) -> Self {
let all = Self::ALL;
let current_index = all.iter().position(|&a| a == self).unwrap_or(0);
let prev_index = if current_index == 0 {
all.len() - 1
} else {
current_index - 1
};
all[prev_index]
}
pub fn is_procedural(&self) -> bool {
matches!(
self,
Atmosphere::Sky
| Atmosphere::CloudySky
| Atmosphere::Space
| Atmosphere::Nebula
| Atmosphere::Sunset
| Atmosphere::DayNight
)
}
pub fn as_procedural_cubemap_type(&self) -> Option<u32> {
match self {
Atmosphere::Sky => Some(0),
Atmosphere::CloudySky => Some(1),
Atmosphere::Space => Some(2),
Atmosphere::Nebula => Some(3),
Atmosphere::Sunset => Some(4),
Atmosphere::DayNight => Some(5),
_ => None,
}
}
}
#[derive(Default)]
pub struct IblViews {
pub brdf_lut_view: Option<wgpu::TextureView>,
pub irradiance_view: Option<wgpu::TextureView>,
pub prefiltered_view: Option<wgpu::TextureView>,
}
impl Clone for IblViews {
fn clone(&self) -> Self {
Self {
brdf_lut_view: self.brdf_lut_view.clone(),
irradiance_view: self.irradiance_view.clone(),
prefiltered_view: self.prefiltered_view.clone(),
}
}
}
#[derive(Clone)]
pub struct MeshLodLevel {
pub mesh_name: String,
pub min_screen_pixels: f32,
}
#[derive(Clone)]
pub struct MeshLodChain {
pub base_mesh: String,
pub levels: Vec<MeshLodLevel>,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct EffectsUniforms {
pub time: f32,
pub chromatic_aberration: f32,
pub wave_distortion: f32,
pub color_shift: f32,
pub kaleidoscope_segments: f32,
pub crt_scanlines: f32,
pub vignette: f32,
pub plasma_intensity: f32,
pub glitch_intensity: f32,
pub mirror_mode: f32,
pub invert: f32,
pub hue_rotation: f32,
pub raymarch_mode: f32,
pub raymarch_blend: f32,
pub film_grain: f32,
pub sharpen: f32,
pub pixelate: f32,
pub color_posterize: f32,
pub radial_blur: f32,
pub tunnel_speed: f32,
pub fractal_iterations: f32,
pub glow_intensity: f32,
pub screen_shake: f32,
pub zoom_pulse: f32,
pub speed_lines: f32,
pub color_grade_mode: f32,
pub vhs_distortion: f32,
pub lens_flare: f32,
pub edge_glow: f32,
pub saturation: f32,
pub warp_speed: f32,
pub pulse_rings: f32,
pub heat_distortion: f32,
pub digital_rain: f32,
pub strobe: f32,
pub color_cycle_speed: f32,
pub feedback_amount: f32,
pub ascii_mode: f32,
}
impl Default for EffectsUniforms {
fn default() -> Self {
Self {
time: 0.0,
chromatic_aberration: 0.0,
wave_distortion: 0.0,
color_shift: 0.0,
kaleidoscope_segments: 0.0,
crt_scanlines: 0.0,
vignette: 0.0,
plasma_intensity: 0.0,
glitch_intensity: 0.0,
mirror_mode: 0.0,
invert: 0.0,
hue_rotation: 0.0,
raymarch_mode: 0.0,
raymarch_blend: 0.0,
film_grain: 0.0,
sharpen: 0.0,
pixelate: 0.0,
color_posterize: 0.0,
radial_blur: 0.0,
tunnel_speed: 1.0,
fractal_iterations: 4.0,
glow_intensity: 0.0,
screen_shake: 0.0,
zoom_pulse: 0.0,
speed_lines: 0.0,
color_grade_mode: 0.0,
vhs_distortion: 0.0,
lens_flare: 0.0,
edge_glow: 0.0,
saturation: 1.0,
warp_speed: 0.0,
pulse_rings: 0.0,
heat_distortion: 0.0,
digital_rain: 0.0,
strobe: 0.0,
color_cycle_speed: 1.0,
feedback_amount: 0.0,
ascii_mode: 0.0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RaymarchMode {
Off = 0,
Tunnel = 1,
Fractal = 2,
Mandelbulb = 3,
PlasmaVortex = 4,
Geometric = 5,
}
impl From<u32> for RaymarchMode {
fn from(value: u32) -> Self {
match value {
1 => RaymarchMode::Tunnel,
2 => RaymarchMode::Fractal,
3 => RaymarchMode::Mandelbulb,
4 => RaymarchMode::PlasmaVortex,
5 => RaymarchMode::Geometric,
_ => RaymarchMode::Off,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorGradeMode {
None = 0,
Cyberpunk = 1,
Sunset = 2,
Grayscale = 3,
Sepia = 4,
Matrix = 5,
HotMetal = 6,
}
impl From<u32> for ColorGradeMode {
fn from(value: u32) -> Self {
match value {
1 => ColorGradeMode::Cyberpunk,
2 => ColorGradeMode::Sunset,
3 => ColorGradeMode::Grayscale,
4 => ColorGradeMode::Sepia,
5 => ColorGradeMode::Matrix,
6 => ColorGradeMode::HotMetal,
_ => ColorGradeMode::None,
}
}
}
#[derive(Clone, Debug)]
pub struct EffectsState {
pub uniforms: EffectsUniforms,
pub enabled: bool,
pub animate_hue: bool,
}
impl Default for EffectsState {
fn default() -> Self {
Self {
uniforms: EffectsUniforms::default(),
enabled: true,
animate_hue: false,
}
}
}
#[derive(Clone)]
pub struct Graphics {
pub frame_rate_limit: Option<f32>,
pub auto_frame_rate_limit_baseline: Option<f32>,
pub gpu_culling_enabled: bool,
pub occlusion_culling_enabled: bool,
pub min_screen_pixel_size: f32,
pub culling_camera_override: Option<freecs::Entity>,
pub min_window_size: Option<(u32, u32)>,
pub show_grid: bool,
pub show_bounding_volumes: bool,
pub show_selected_bounding_volume: bool,
pub bounding_volume_selected_entity: Option<freecs::Entity>,
pub selected_entities: Vec<freecs::Entity>,
pub show_normals: bool,
pub normal_line_length: f32,
pub normal_line_color: [f32; 4],
pub atmosphere: Atmosphere,
pub show_sky: bool,
pub render_layer_world_enabled: bool,
pub render_world_to_swapchain: bool,
pub render_layer_overlay_enabled: bool,
pub use_fullscreen: bool,
pub show_cursor: bool,
pub clear_color: [f32; 4],
pub ui_scale: Option<f32>,
pub letterbox_amount: f32,
pub letterbox_target: f32,
pub unlit_mode: bool,
pub selection_outline_enabled: bool,
pub selection_outline_color: [f32; 4],
pub vertex_snap: Option<VertexSnap>,
pub affine_texture_mapping: bool,
pub material_anisotropy_filtering: u16,
pub fog: Option<Fog>,
pub color_grading: ColorGrading,
pub bloom_enabled: bool,
pub bloom_intensity: f32,
pub bloom_threshold: f32,
pub bloom_knee: f32,
pub bloom_filter_radius: f32,
pub depth_of_field: DepthOfField,
pub ssao_enabled: bool,
pub ssao_radius: f32,
pub ssao_bias: f32,
pub ssao_intensity: f32,
pub ssao_sample_count: u32,
pub ssao_visualization: bool,
pub ssao_blur_depth_threshold: f32,
pub ssao_blur_normal_power: f32,
pub ssgi_enabled: bool,
pub ssgi_radius: f32,
pub ssgi_intensity: f32,
pub ssgi_max_steps: u32,
pub ssr_enabled: bool,
pub ssr_max_steps: u32,
pub ssr_thickness: f32,
pub ssr_max_distance: f32,
pub ssr_stride: f32,
pub ssr_fade_start: f32,
pub ssr_fade_end: f32,
pub ssr_intensity: f32,
pub ambient_light: [f32; 4],
pub pbr_debug_mode: PbrDebugMode,
pub texture_debug_stripes: bool,
pub texture_debug_stripes_speed: f32,
pub fxaa_enabled: bool,
pub render_scale: f32,
pub day_night: DayNightState,
pub ibl_blend_factor: f32,
pub mesh_lod_chains: Vec<MeshLodChain>,
pub active_view: EffectiveShading,
pub settings_version: u64,
pub focus_policy: ViewportFocusPolicy,
pub adaptive_sampling: AdaptiveSamplingState,
pub effects: EffectsState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PerformanceTarget {
#[default]
Unbounded,
Interactive,
Balanced,
Quality,
}
impl PerformanceTarget {
pub fn target_frame_time_ms(self) -> Option<f32> {
match self {
PerformanceTarget::Unbounded => None,
PerformanceTarget::Interactive => Some(1000.0 / 60.0),
PerformanceTarget::Balanced => Some(1000.0 / 30.0),
PerformanceTarget::Quality => Some(1000.0 / 15.0),
}
}
}
#[derive(Debug, Clone)]
pub struct AdaptiveSamplingState {
pub target: PerformanceTarget,
pub frame_time_rolling_ms: f32,
pub frame_time_sample_count: u32,
pub ssao_sample_scale: f32,
pub ssgi_sample_scale: f32,
pub ssr_step_scale: f32,
}
impl Default for AdaptiveSamplingState {
fn default() -> Self {
Self {
target: PerformanceTarget::Unbounded,
frame_time_rolling_ms: 16.67,
frame_time_sample_count: 0,
ssao_sample_scale: 1.0,
ssgi_sample_scale: 1.0,
ssr_step_scale: 1.0,
}
}
}
impl AdaptiveSamplingState {
pub fn record_frame_time(&mut self, frame_time_ms: f32) {
const WINDOW: u32 = 100;
if self.frame_time_sample_count == 0 {
self.frame_time_rolling_ms = frame_time_ms;
self.frame_time_sample_count = 1;
} else {
let alpha = 1.0 / (self.frame_time_sample_count.min(WINDOW) as f32 + 1.0);
self.frame_time_rolling_ms =
self.frame_time_rolling_ms * (1.0 - alpha) + frame_time_ms * alpha;
self.frame_time_sample_count = (self.frame_time_sample_count + 1).min(WINDOW);
}
let Some(target_ms) = self.target.target_frame_time_ms() else {
self.ssao_sample_scale = 1.0;
self.ssgi_sample_scale = 1.0;
self.ssr_step_scale = 1.0;
return;
};
let error = self.frame_time_rolling_ms - target_ms;
let sensitivity = 0.002;
let max_delta = 0.05;
let adjustment = (error * sensitivity).clamp(-max_delta, max_delta);
self.ssao_sample_scale = (self.ssao_sample_scale - adjustment).clamp(0.25, 1.0);
self.ssgi_sample_scale = (self.ssgi_sample_scale - adjustment).clamp(0.25, 1.0);
self.ssr_step_scale = (self.ssr_step_scale - adjustment).clamp(0.25, 1.0);
}
}
impl Graphics {
pub fn settings_signature(&self) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.bounding_volume_selected_entity
.map(|entity| (entity.id, entity.generation))
.hash(&mut hasher);
for entity in &self.selected_entities {
(entity.id, entity.generation).hash(&mut hasher);
}
self.show_grid.hash(&mut hasher);
self.show_bounding_volumes.hash(&mut hasher);
self.show_normals.hash(&mut hasher);
self.normal_line_length.to_bits().hash(&mut hasher);
for component in self.normal_line_color {
component.to_bits().hash(&mut hasher);
}
(self.atmosphere as u32).hash(&mut hasher);
self.show_sky.hash(&mut hasher);
self.render_layer_world_enabled.hash(&mut hasher);
self.render_layer_overlay_enabled.hash(&mut hasher);
self.render_world_to_swapchain.hash(&mut hasher);
for component in self.clear_color {
component.to_bits().hash(&mut hasher);
}
self.unlit_mode.hash(&mut hasher);
self.selection_outline_enabled.hash(&mut hasher);
for component in self.selection_outline_color {
component.to_bits().hash(&mut hasher);
}
self.vertex_snap.is_some().hash(&mut hasher);
if let Some(ref snap) = self.vertex_snap {
for component in snap.resolution {
component.to_bits().hash(&mut hasher);
}
}
self.affine_texture_mapping.hash(&mut hasher);
self.fog.is_some().hash(&mut hasher);
if let Some(ref fog) = self.fog {
for component in fog.color {
component.to_bits().hash(&mut hasher);
}
fog.start.to_bits().hash(&mut hasher);
fog.end.to_bits().hash(&mut hasher);
}
let cg = &self.color_grading;
cg.exposure.to_bits().hash(&mut hasher);
cg.exposure_compensation_ev.to_bits().hash(&mut hasher);
cg.auto_exposure.hash(&mut hasher);
cg.auto_exposure_target.to_bits().hash(&mut hasher);
cg.auto_exposure_rate.to_bits().hash(&mut hasher);
cg.auto_exposure_min_ev.to_bits().hash(&mut hasher);
cg.auto_exposure_max_ev.to_bits().hash(&mut hasher);
cg.gamma.to_bits().hash(&mut hasher);
cg.saturation.to_bits().hash(&mut hasher);
cg.brightness.to_bits().hash(&mut hasher);
cg.contrast.to_bits().hash(&mut hasher);
(cg.tonemap_algorithm as u32).hash(&mut hasher);
self.bloom_enabled.hash(&mut hasher);
self.bloom_intensity.to_bits().hash(&mut hasher);
self.bloom_threshold.to_bits().hash(&mut hasher);
self.bloom_knee.to_bits().hash(&mut hasher);
self.bloom_filter_radius.to_bits().hash(&mut hasher);
let dof = &self.depth_of_field;
dof.enabled.hash(&mut hasher);
dof.focus_distance.to_bits().hash(&mut hasher);
dof.focus_range.to_bits().hash(&mut hasher);
dof.max_blur_radius.to_bits().hash(&mut hasher);
dof.bokeh_threshold.to_bits().hash(&mut hasher);
dof.bokeh_intensity.to_bits().hash(&mut hasher);
(dof.quality as u32).hash(&mut hasher);
dof.visualize_coc.hash(&mut hasher);
dof.tilt_shift_enabled.hash(&mut hasher);
dof.tilt_shift_angle.to_bits().hash(&mut hasher);
dof.tilt_shift_center.to_bits().hash(&mut hasher);
dof.tilt_shift_blur_amount.to_bits().hash(&mut hasher);
dof.visualize_tilt_shift.hash(&mut hasher);
self.ssao_enabled.hash(&mut hasher);
self.ssao_radius.to_bits().hash(&mut hasher);
self.ssao_bias.to_bits().hash(&mut hasher);
self.ssao_intensity.to_bits().hash(&mut hasher);
self.ssao_sample_count.hash(&mut hasher);
self.ssao_visualization.hash(&mut hasher);
self.ssao_blur_depth_threshold.to_bits().hash(&mut hasher);
self.ssao_blur_normal_power.to_bits().hash(&mut hasher);
self.ssgi_enabled.hash(&mut hasher);
self.ssgi_radius.to_bits().hash(&mut hasher);
self.ssgi_intensity.to_bits().hash(&mut hasher);
self.ssgi_max_steps.hash(&mut hasher);
self.ssr_enabled.hash(&mut hasher);
self.ssr_max_steps.hash(&mut hasher);
self.ssr_thickness.to_bits().hash(&mut hasher);
self.ssr_max_distance.to_bits().hash(&mut hasher);
self.ssr_stride.to_bits().hash(&mut hasher);
self.ssr_fade_start.to_bits().hash(&mut hasher);
self.ssr_fade_end.to_bits().hash(&mut hasher);
self.ssr_intensity.to_bits().hash(&mut hasher);
for component in self.ambient_light {
component.to_bits().hash(&mut hasher);
}
self.pbr_debug_mode.as_u32().hash(&mut hasher);
self.texture_debug_stripes.hash(&mut hasher);
self.texture_debug_stripes_speed.to_bits().hash(&mut hasher);
self.fxaa_enabled.hash(&mut hasher);
self.render_scale.to_bits().hash(&mut hasher);
self.ibl_blend_factor.to_bits().hash(&mut hasher);
hasher.finish()
}
}
impl Default for Graphics {
fn default() -> Self {
Self {
frame_rate_limit: None,
auto_frame_rate_limit_baseline: None,
gpu_culling_enabled: true,
occlusion_culling_enabled: true,
min_screen_pixel_size: 0.0,
culling_camera_override: None,
min_window_size: None,
show_grid: false,
show_bounding_volumes: false,
show_selected_bounding_volume: false,
bounding_volume_selected_entity: None,
selected_entities: Vec::new(),
show_normals: false,
normal_line_length: 0.1,
normal_line_color: [0.0, 1.0, 0.0, 1.0],
atmosphere: Atmosphere::None,
show_sky: true,
render_layer_world_enabled: true,
render_layer_overlay_enabled: true,
render_world_to_swapchain: true,
use_fullscreen: false,
show_cursor: true,
clear_color: [0.0, 0.0, 0.0, 1.0],
ui_scale: None,
letterbox_amount: 0.0,
letterbox_target: 0.0,
unlit_mode: false,
selection_outline_enabled: false,
selection_outline_color: [1.0, 0.45, 0.0, 1.0],
vertex_snap: None,
affine_texture_mapping: false,
material_anisotropy_filtering: 16,
fog: None,
color_grading: ColorGrading::default(),
bloom_enabled: true,
bloom_intensity: 0.08,
bloom_threshold: 1.0,
bloom_knee: 0.5,
bloom_filter_radius: 0.005,
depth_of_field: DepthOfField::default(),
ssao_enabled: false,
ssao_radius: 0.5,
ssao_bias: 0.025,
ssao_intensity: 1.0,
ssao_sample_count: 64,
ssao_visualization: false,
ssao_blur_depth_threshold: 0.005,
ssao_blur_normal_power: 8.0,
ssgi_enabled: false,
ssgi_radius: 2.0,
ssgi_intensity: 1.0,
ssgi_max_steps: 16,
ssr_enabled: false,
ssr_max_steps: 64,
ssr_thickness: 0.3,
ssr_max_distance: 50.0,
ssr_stride: 1.0,
ssr_fade_start: 0.8,
ssr_fade_end: 1.0,
ssr_intensity: 1.0,
ambient_light: [0.1, 0.1, 0.1, 1.0],
pbr_debug_mode: PbrDebugMode::None,
texture_debug_stripes: false,
texture_debug_stripes_speed: 100.0,
fxaa_enabled: true,
render_scale: 1.0,
day_night: DayNightState::default(),
ibl_blend_factor: 0.0,
mesh_lod_chains: Vec::new(),
active_view: EffectiveShading::default(),
settings_version: 0,
focus_policy: ViewportFocusPolicy::default(),
adaptive_sampling: AdaptiveSamplingState::default(),
effects: EffectsState::default(),
}
}
}