use concinnity_core::components::GamepadMap;
use concinnity_core::components::GraphicsConfig;
use concinnity_core::components::PostProcessConfig;
use concinnity_core::components::SceneCommand;
use concinnity_core::components::ShadowUpdate;
use concinnity_core::components::UpscaleQuality;
use concinnity_core::components::UpscalerBackend;
use concinnity_core::components::Window;
use concinnity_core::components::WindowMode;
use concinnity_core::ecs::asset_id::AssetId;
use concinnity_core::ecs::{EventCursor, HudPrefs, PipelineContext, StepResult, System};
use concinnity_core::gfx::render_types;
use concinnity_core::input::keymap;
use concinnity_core::render::backend;
use concinnity_core::render::ops::RenderOps;
use concinnity_core::render::scene_flow;
use concinnity_core::render::snapshot;
use concinnity_core::settings::SettingKey;
use concinnity_core::window::display_mode;
mod apply;
mod quality;
mod rebind;
pub(crate) mod rows;
#[cfg(test)]
mod tests;
pub(crate) mod writer;
pub(crate) struct SettingsState {
pub(crate) keymap: keymap::KeyMap,
pub(crate) rebind_rows: Vec<crate::gfx::system::RebindViz>,
pub(crate) gamepad_map: GamepadMap,
pub(crate) pad_rebind_rows: Vec<crate::gfx::system::PadRebindViz>,
pub(crate) sliders: Vec<crate::gfx::system::SliderViz>,
pub(crate) cycle_value_labels: std::collections::HashMap<SettingKey, AssetId>,
pub(crate) post_process: render_types::PostProcessTunables,
pub(crate) post_config: PostProcessConfig,
pub(crate) authored_post_config: PostProcessConfig,
pub(crate) ambient_intensity: f32,
pub(crate) quality_preset: crate::gfx::quality_preset::QualityPreset,
pub(crate) gpu_profile: backend::GpuProfile,
pub(crate) render_scale: UpscaleQuality,
pub(crate) upscale_backend: 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: 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: 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: Window,
pub(crate) display_modes: Vec<display_mode::DisplayMode>,
pub(crate) resolution: Option<display_mode::DisplayMode>,
pub(crate) current_mode: Option<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: EventCursor,
pub(crate) setting_cmd_cursor: EventCursor,
pub(crate) published_hud_prefs: Option<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 {
pub(crate) fn new() -> Self {
let gfx = GraphicsConfig::default();
Self {
keymap: keymap::KeyMap::default(),
rebind_rows: Vec::new(),
gamepad_map: GamepadMap::default(),
pad_rebind_rows: Vec::new(),
sliders: Vec::new(),
cycle_value_labels: std::collections::HashMap::new(),
post_process: render_types::PostProcessTunables::DEFAULT,
post_config: PostProcessConfig::default(),
authored_post_config: PostProcessConfig::default(),
ambient_intensity: 1.0,
quality_preset: crate::gfx::quality_preset::QualityPreset::Auto,
gpu_profile: backend::GpuProfile::UNKNOWN,
render_scale: UpscaleQuality::default(),
upscale_backend: UpscalerBackend::default(),
temporal_upscaling: false,
hdr_display: false,
hdr_pq: false,
shadow_map_size: gfx.shadow_map_size,
shadow_update: gfx.shadow_update,
shadow_distance: gfx.shadow_distance,
shadow_cascades: gfx.shadow_cascades,
anisotropy: gfx.anisotropy,
authored_shadow_map_size: gfx.shadow_map_size,
authored_shadow_update: gfx.shadow_update,
authored_shadow_distance: gfx.shadow_distance,
authored_shadow_cascades: gfx.shadow_cascades,
authored_anisotropy: gfx.anisotropy,
vsync: gfx.vsync,
fps_cap: gfx.fps_cap,
perf_stats: true,
show_fps: true,
show_vram: true,
perf_sub_row_labels: Vec::new(),
window_args: Window::default(),
display_modes: Vec::new(),
resolution: None,
current_mode: None,
resolution_row_labels: Vec::new(),
frames_in_flight: gfx.frames_in_flight as usize,
occlusion_two_pass: PostProcessConfig::default().occlusion_two_pass,
texture_cap: 96,
texture_budget: 4,
persisted_graphics: crate::config::GraphicsSettings::default(),
fog_built: false,
settings_cache: None,
settings_writer: None,
scene_cmd_cursor: EventCursor::default(),
setting_cmd_cursor: EventCursor::default(),
published_hud_prefs: None,
published_disabled_inputs: None,
}
}
#[cfg(test)]
pub(crate) fn for_tests() -> Self {
Self {
quality_preset: crate::gfx::quality_preset::QualityPreset::Custom,
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,
frames_in_flight: 2,
occlusion_two_pass: false,
texture_cap: 0,
texture_budget: 0,
fog_built: true,
..Self::new()
}
}
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::system::scene::SceneVisibilityScratch::default();
crate::gfx::system::scene::refresh_visibility_snapshot(ctx, &mut scratch);
let now = ctx
.resource::<concinnity_core::ecs::FrameTime>()
.copied()
.unwrap_or_default()
.elapsed;
let Some(slot) = ctx.resources.get_mut::<crate::ecs::ActiveSceneFlow>() else {
return;
};
let elapsed = slot.elapsed(now);
let mut scene_ops: Vec<snapshot::SceneOp> = Vec::new();
for cmd in scene_cmds {
let mut recorder = snapshot::SceneOpRecorder(&mut scene_ops);
if let Err(rejection) = scene_flow::jump_to_scene(
&mut slot.flow,
&scratch.visibility,
elapsed,
cmd.scene,
cmd.transition,
&mut recorder,
) {
tracing::warn!("scene jump to {} rejected: {rejection:?}", cmd.scene.0);
}
}
if !scene_ops.is_empty() {
ops.record(move |backend| {
for op in scene_ops {
match op {
snapshot::SceneOp::SetFade(fade) => backend.set_fade(fade),
snapshot::SceneOp::Visibility { draw_idx, visible } => {
backend.update_visibility(draw_idx, visible)
}
}
}
});
}
}
fn publish_hud_state(&mut self, ctx: &mut PipelineContext) {
let prefs = 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 == WindowMode::Fullscreen;
let inputs = (self.perf_stats, is_fullscreen);
if self.published_disabled_inputs != Some(inputs) {
let mut disabled_rows = std::collections::HashSet::new();
if !self.perf_stats {
disabled_rows.extend([SettingKey::ShowFps, SettingKey::ShowVram]);
}
if !is_fullscreen {
disabled_rows.insert(SettingKey::Resolution);
}
ctx.insert_resource(crate::ecs::DisabledSettingRows(disabled_rows));
self.published_disabled_inputs = Some(inputs);
}
}
}