use crate::components::SceneCommand;
use crate::ecs::asset_id::AssetId;
use crate::ecs::{PipelineContext, StepResult, System};
use crate::gfx::ops::RenderOps;
use crate::gfx::scene_flow;
mod apply;
pub(crate) mod rows;
#[cfg(test)]
mod tests;
pub(crate) mod writer;
pub(crate) struct SettingsState {
pub(crate) keymap: crate::gfx::keymap::KeyMap,
pub(crate) rebind_rows: Vec<crate::gfx::graphics_system::RebindViz>,
pub(crate) gamepad_map: crate::components::GamepadMap,
pub(crate) pad_rebind_rows: Vec<crate::gfx::graphics_system::PadRebindViz>,
pub(crate) sliders: Vec<crate::gfx::graphics_system::SliderViz>,
pub(crate) cycle_value_labels: std::collections::HashMap<String, AssetId>,
pub(crate) post_process: crate::gfx::render_types::PostProcessTunables,
pub(crate) post_config: crate::components::PostProcessConfig,
pub(crate) authored_post_config: crate::components::PostProcessConfig,
pub(crate) ambient_intensity: f32,
pub(crate) quality_preset: crate::gfx::quality_preset::QualityPreset,
pub(crate) gpu_profile: crate::gfx::backend::GpuProfile,
pub(crate) render_scale: crate::components::UpscaleQuality,
pub(crate) upscale_backend: crate::components::UpscalerBackend,
pub(crate) temporal_upscaling: bool,
pub(crate) hdr_display: bool,
pub(crate) hdr_pq: bool,
pub(crate) shadow_map_size: u32,
pub(crate) shadow_update: crate::components::ShadowUpdate,
pub(crate) shadow_distance: u32,
pub(crate) shadow_cascades: u32,
pub(crate) anisotropy: u32,
pub(crate) authored_shadow_map_size: u32,
pub(crate) authored_shadow_update: crate::components::ShadowUpdate,
pub(crate) authored_shadow_distance: u32,
pub(crate) authored_shadow_cascades: u32,
pub(crate) authored_anisotropy: u32,
pub(crate) vsync: bool,
pub(crate) fps_cap: u32,
pub(crate) perf_stats: bool,
pub(crate) show_fps: bool,
pub(crate) show_vram: bool,
pub(crate) perf_sub_row_labels: Vec<(AssetId, [f32; 3])>,
pub(crate) window_args: crate::components::Window,
pub(crate) display_modes: Vec<crate::gfx::display_mode::DisplayMode>,
pub(crate) resolution: Option<crate::gfx::display_mode::DisplayMode>,
pub(crate) current_mode: Option<crate::gfx::display_mode::DisplayMode>,
pub(crate) resolution_row_labels: Vec<(AssetId, [f32; 3])>,
pub(crate) frames_in_flight: usize,
pub(crate) occlusion_two_pass: bool,
pub(crate) texture_cap: u32,
pub(crate) texture_budget: u32,
pub(crate) persisted_graphics: crate::config::GraphicsSettings,
pub(crate) fog_built: bool,
pub(crate) settings_cache: Option<crate::config::Settings>,
pub(crate) settings_writer: Option<writer::SettingsWriter>,
pub(crate) scene_cmd_cursor: crate::ecs::EventCursor,
pub(crate) setting_cmd_cursor: crate::ecs::EventCursor,
pub(crate) published_hud_prefs: Option<crate::ecs::HudPrefs>,
pub(crate) published_disabled_inputs: Option<(bool, bool)>,
}
#[derive(Debug, Default)]
pub(crate) struct SettingsSystem;
impl SettingsSystem {
pub(crate) fn new() -> Self {
Self
}
fn park(ctx: &mut PipelineContext, state: SettingsState) {
match ctx.resources.get_mut::<SettingsSlot>() {
Some(slot) => slot.0 = Some(state),
None => {
ctx.resources.insert(SettingsSlot(Some(state)));
}
}
}
}
pub(crate) struct SettingsSlot(pub(crate) Option<SettingsState>);
impl std::fmt::Debug for SettingsState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SettingsState")
.field("quality_preset", &self.quality_preset)
.finish()
}
}
impl System for SettingsSystem {
fn step(&mut self, ctx: &mut PipelineContext) -> StepResult {
let Some(mut state) = ctx
.resources
.get_mut::<SettingsSlot>()
.and_then(|slot| slot.0.take())
else {
return StepResult::Continue;
};
let Some(mut queues) = crate::ecs::ActiveRenderQueues::take(ctx.resources) else {
Self::park(ctx, state);
return StepResult::Continue;
};
state.apply_scene_commands(ctx, &mut queues.ops);
state.apply_setting_commands(ctx, &mut queues.ops);
crate::ecs::ActiveRenderQueues::put(ctx.resources, queues);
state.publish_hud_state(ctx);
Self::park(ctx, state);
StepResult::Continue
}
}
impl SettingsState {
#[cfg(test)]
pub(crate) fn for_tests() -> Self {
use crate::components::PostProcessConfig;
Self {
keymap: crate::gfx::keymap::KeyMap::default(),
rebind_rows: Vec::new(),
gamepad_map: crate::components::GamepadMap::default(),
pad_rebind_rows: Vec::new(),
sliders: Vec::new(),
cycle_value_labels: std::collections::HashMap::new(),
post_process: crate::gfx::render_types::PostProcessTunables::DEFAULT,
post_config: PostProcessConfig::default(),
authored_post_config: PostProcessConfig::default(),
ambient_intensity: 1.0,
quality_preset: crate::gfx::quality_preset::QualityPreset::Custom,
gpu_profile: crate::gfx::backend::GpuProfile::UNKNOWN,
render_scale: Default::default(),
upscale_backend: Default::default(),
temporal_upscaling: false,
hdr_display: false,
hdr_pq: false,
shadow_map_size: 2048,
shadow_update: Default::default(),
shadow_distance: 200,
shadow_cascades: 4,
anisotropy: 8,
authored_shadow_map_size: 2048,
authored_shadow_update: Default::default(),
authored_shadow_distance: 200,
authored_shadow_cascades: 4,
authored_anisotropy: 8,
vsync: true,
fps_cap: 0,
perf_stats: true,
show_fps: true,
show_vram: true,
perf_sub_row_labels: Vec::new(),
window_args: crate::components::Window::default(),
display_modes: Vec::new(),
resolution: None,
current_mode: None,
resolution_row_labels: Vec::new(),
frames_in_flight: 2,
occlusion_two_pass: false,
texture_cap: 0,
texture_budget: 0,
persisted_graphics: Default::default(),
fog_built: true,
settings_cache: None,
settings_writer: None,
scene_cmd_cursor: Default::default(),
setting_cmd_cursor: Default::default(),
published_hud_prefs: None,
published_disabled_inputs: None,
}
}
pub(crate) fn persisted_graphics(&self) -> &crate::config::GraphicsSettings {
match &self.settings_cache {
Some(cache) => &cache.graphics,
None => &self.persisted_graphics,
}
}
pub(crate) fn ceiling(&self) -> crate::gfx::quality_preset::QualityCeiling {
crate::gfx::quality_preset::resolve_ceiling(self.quality_preset, &self.gpu_profile)
}
fn apply_scene_commands(&mut self, ctx: &mut PipelineContext, ops: &mut RenderOps) {
let scene_cmds: Vec<SceneCommand> = match ctx.events::<SceneCommand>() {
Some(events) => events.read(&mut self.scene_cmd_cursor).cloned().collect(),
None => Vec::new(),
};
if scene_cmds.is_empty() {
return;
}
let mut scratch = crate::gfx::graphics_system::scene::SceneVisibilityScratch::default();
crate::gfx::graphics_system::scene::refresh_visibility_snapshot(ctx, &mut scratch);
let Some(slot) = ctx.resources.get_mut::<crate::ecs::ActiveSceneFlow>() else {
return;
};
let elapsed = slot.epoch.elapsed().as_secs_f32();
let mut scene_ops: Vec<crate::gfx::snapshot::SceneOp> = Vec::new();
for cmd in scene_cmds {
let mut recorder = crate::gfx::snapshot::SceneOpRecorder(&mut scene_ops);
scene_flow::jump_to_scene(
&mut slot.flow,
&scratch.visibility,
elapsed,
cmd.scene,
&cmd.transition,
&mut recorder,
);
}
if !scene_ops.is_empty() {
ops.record(move |backend| {
for op in scene_ops {
match op {
crate::gfx::snapshot::SceneOp::SetFade(fade) => backend.set_fade(fade),
crate::gfx::snapshot::SceneOp::Visibility { draw_idx, visible } => {
backend.update_visibility(draw_idx, visible)
}
}
}
});
}
}
fn publish_hud_state(&mut self, ctx: &mut PipelineContext) {
let prefs = crate::ecs::HudPrefs {
show_fps: self.perf_stats && self.show_fps,
show_vram: self.perf_stats && self.show_vram,
};
if self.published_hud_prefs != Some(prefs) {
ctx.insert_resource(prefs);
self.published_hud_prefs = Some(prefs);
}
let is_fullscreen = self.window_args.mode == crate::components::WindowMode::Fullscreen;
let inputs = (self.perf_stats, is_fullscreen);
if self.published_disabled_inputs != Some(inputs) {
let mut disabled_rows: std::collections::HashSet<String> = if self.perf_stats {
std::collections::HashSet::new()
} else {
["show_fps", "show_vram"]
.iter()
.map(|s| s.to_string())
.collect()
};
if !is_fullscreen {
disabled_rows.insert("resolution".to_string());
}
ctx.insert_resource(crate::ecs::DisabledSettingRows(disabled_rows));
self.published_disabled_inputs = Some(inputs);
}
}
}