use concinnity_host::store::paths::StateTree;
use crate::components::{PostProcessResolve, Window};
use crate::ecs::asset_id::AssetId;
use crate::ecs::{PipelineContext, StepResult, System};
use crate::gfx::backend::RenderBackend;
use crate::gfx::{scene_flow, text};
use std::time::Instant;
const IDENTITY4: [[f32; 4]; 4] = crate::gfx::draw_list::IDENTITY4;
struct PickCandidate {
asset_id: AssetId,
entity: crate::ecs::Entity,
local_min: [f32; 3],
local_max: [f32; 3],
}
pub struct GraphicsSystem {
state: Option<StateTree>,
window_args: Window,
clear_color: [f32; 4],
frames_in_flight: usize,
vsync: bool,
fps_cap: u32,
display_modes: Vec<crate::gfx::display_mode::DisplayMode>,
resolution: Option<crate::gfx::display_mode::DisplayMode>,
current_mode: Option<crate::gfx::display_mode::DisplayMode>,
resolution_row_labels: Vec<(AssetId, [f32; 3])>,
perf_stats: bool,
show_fps: bool,
show_vram: bool,
perf_sub_row_labels: Vec<(AssetId, [f32; 3])>,
max_frames: Option<u64>,
shadow_map_size: u32,
shadow_update: crate::components::ShadowUpdate,
shadow_distance: u32,
shadow_cascades: u32,
anisotropy: u32,
failed: bool,
start_time: Option<Instant>,
frame_count: u64,
frame_policy: frame_policy::FramePolicy,
menu_mode: bool,
render_scale: crate::components::UpscaleQuality,
upscale_backend: crate::components::UpscalerBackend,
backend: Option<Box<dyn RenderBackend>>,
scene_flow: Option<scene_flow::SceneFlow>,
scene_visibility: scene::SceneVisibilityScratch,
loaded_fonts: text::FontSet,
sprite_texture_slots: crate::gfx::overlay_maps::TextureSlots,
debug_hud_chips: Vec<AssetId>,
stat_hud_chips: Vec<AssetId>,
pick_candidates: Vec<PickCandidate>,
texture_streamer: Option<crate::gfx::streaming::texture::TextureStreamer>,
mesh_streamer: Option<crate::gfx::streaming::mesh::MeshStreamer>,
mesh_stream_draw_indices: Vec<usize>,
chunk_stream: Option<crate::gfx::streaming_system::ChunkStreamState>,
shader_warmup: Option<crate::gfx::streaming::shader::ShaderWarmup>,
deferred_shader_scenes: Vec<(u32, AssetId)>,
pending_hot_reload_sources: Option<hot_reload_sources::HotReloadSources>,
world_reload: Option<WorldReloadState>,
persisted_graphics: crate::config::GraphicsSettings,
fog_built: bool,
last_fog_settings: Option<crate::gfx::volumetric_fog::FogSettings>,
post_process: crate::gfx::render_types::PostProcessTunables,
ambient_intensity: f32,
post_config: crate::components::PostProcessConfig,
sliders: Vec<SliderViz>,
cycle_value_labels: std::collections::HashMap<String, AssetId>,
clip_rects: crate::gfx::overlay_maps::ClipRects,
keymap: crate::gfx::keymap::KeyMap,
rebind_rows: Vec<RebindViz>,
gamepad_map: crate::components::GamepadMap,
pad_rebind_rows: Vec<PadRebindViz>,
caps: crate::gfx::backend::DeviceCapabilities,
gpu_profile: crate::gfx::backend::GpuProfile,
quality_preset: crate::gfx::quality_preset::QualityPreset,
authored_post_config: crate::components::PostProcessConfig,
temporal_upscaling: bool,
hdr_display: bool,
hdr_pq: bool,
authored_shadow_map_size: u32,
authored_shadow_update: crate::components::ShadowUpdate,
authored_shadow_distance: u32,
authored_shadow_cascades: u32,
authored_anisotropy: u32,
occlusion_two_pass: bool,
texture_cap: u32,
texture_budget: u32,
transform_cache: crate::gfx::transform_propagation::TransformCache,
model_push: model_push::ModelPushCache,
skinned_model_push: model_push::ModelPushCache,
snapshot: crate::gfx::snapshot::RenderSnapshot,
viewport: (f32, f32),
#[cfg(test)]
pub(crate) test_hooks: Option<crate::gfx::mock_backend::TestHooks>,
}
pub(crate) struct RebindViz {
pub(crate) action: crate::gfx::keymap::Bindable,
pub(crate) value_id: AssetId,
}
pub(crate) struct PadRebindViz {
pub(crate) action: crate::components::GamepadAction,
pub(crate) value_id: AssetId,
}
pub(crate) struct SliderViz {
pub(crate) key: String,
pub(crate) track_x: f32,
pub(crate) track_w: f32,
pub(crate) handle_w: f32,
pub(crate) handle_id: AssetId,
pub(crate) value_id: AssetId,
}
pub struct WorldReloadState {
pub texture_name_to_slot: std::collections::HashMap<AssetId, usize>,
}
pub struct HotReloadApplyParts<'a> {
pub backend: &'a mut dyn RenderBackend,
pub world_reload: &'a Option<WorldReloadState>,
pub last_fog_settings: &'a mut Option<crate::gfx::volumetric_fog::FogSettings>,
}
impl std::fmt::Debug for GraphicsSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GraphicsSystem")
.field("frame_count", &self.frame_count)
.field("failed", &self.failed)
.finish()
}
}
impl GraphicsSystem {
pub fn new(tree: Option<&StateTree>) -> Self {
let gfx = crate::components::GraphicsConfig::default();
Self {
state: tree.cloned(),
window_args: Default::default(),
clear_color: gfx.clear_color,
frames_in_flight: gfx.frames_in_flight as usize,
vsync: gfx.vsync,
fps_cap: gfx.fps_cap,
display_modes: Vec::new(),
resolution: None,
current_mode: None,
resolution_row_labels: Vec::new(),
perf_stats: true,
show_fps: true,
show_vram: true,
perf_sub_row_labels: Vec::new(),
max_frames: gfx.max_frames,
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,
failed: false,
start_time: None,
frame_count: 0,
frame_policy: frame_policy::FramePolicy::default(),
menu_mode: false,
render_scale: crate::components::UpscaleQuality::default(),
upscale_backend: crate::components::UpscalerBackend::default(),
backend: None,
scene_flow: None,
scene_visibility: Default::default(),
loaded_fonts: text::FontSet::default(),
sprite_texture_slots: crate::gfx::overlay_maps::TextureSlots::new(),
debug_hud_chips: Vec::new(),
stat_hud_chips: Vec::new(),
pick_candidates: Vec::new(),
texture_streamer: None,
mesh_streamer: None,
mesh_stream_draw_indices: Vec::new(),
chunk_stream: None,
shader_warmup: None,
deferred_shader_scenes: Vec::new(),
pending_hot_reload_sources: None,
world_reload: None,
persisted_graphics: crate::config::GraphicsSettings::default(),
fog_built: false,
last_fog_settings: None,
post_process: crate::gfx::render_types::PostProcessTunables::DEFAULT,
ambient_intensity: 1.0,
post_config: crate::components::PostProcessConfig::default(),
sliders: Vec::new(),
cycle_value_labels: std::collections::HashMap::new(),
clip_rects: crate::gfx::overlay_maps::ClipRects::new(),
keymap: crate::gfx::keymap::KeyMap::default(),
rebind_rows: Vec::new(),
gamepad_map: crate::components::GamepadMap::default(),
pad_rebind_rows: Vec::new(),
caps: crate::gfx::backend::DeviceCapabilities::ALL,
gpu_profile: crate::gfx::backend::GpuProfile::UNKNOWN,
quality_preset: crate::gfx::quality_preset::QualityPreset::Auto,
authored_post_config: crate::components::PostProcessConfig::default(),
temporal_upscaling: false,
hdr_display: false,
hdr_pq: false,
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,
occlusion_two_pass: crate::components::PostProcessConfig::default().occlusion_two_pass,
texture_cap: 96,
texture_budget: 4,
transform_cache: crate::gfx::transform_propagation::TransformCache::default(),
model_push: model_push::ModelPushCache::default(),
skinned_model_push: model_push::ModelPushCache::default(),
snapshot: crate::gfx::snapshot::RenderSnapshot::default(),
viewport: (0.0, 0.0),
#[cfg(test)]
test_hooks: None,
}
}
fn persisted_settings(&self) -> crate::config::Settings {
#[cfg(test)]
if let Some(hooks) = &self.test_hooks {
return hooks.settings.clone();
}
crate::config::Settings::load(self.state.as_ref())
}
pub(crate) fn assets_dir(&self) -> Option<std::path::PathBuf> {
self.state.as_ref().map(StateTree::assets_dir)
}
fn detect_gpu_profile(&self) -> crate::gfx::backend::GpuProfile {
#[cfg(test)]
if let Some(hooks) = &self.test_hooks {
return hooks.gpu_profile;
}
crate::device::probe_gpu_profile()
}
fn seed_first_launch_preset(&self) {
#[cfg(test)]
if self.test_hooks.is_some() {
return;
}
let mut s = crate::config::Settings::load(self.state.as_ref());
s.graphics.quality_preset = Some(crate::gfx::quality_preset::QualityPreset::Auto);
if let Err(e) = s.save(self.state.as_ref()) {
tracing::warn!("first-launch quality preset save failed: {e}");
}
}
fn effective_resolution(&self) -> crate::gfx::display_mode::DisplayMode {
self.resolution
.or(self.current_mode)
.unwrap_or(crate::gfx::display_mode::DisplayMode {
width: self.window_args.width,
height: self.window_args.height,
refresh_hz: 0,
})
}
}
impl System for GraphicsSystem {
fn init(&mut self, ctx: &mut PipelineContext) {
self.run_init(ctx);
}
fn step(&mut self, ctx: &mut PipelineContext) -> StepResult {
self.run_step(ctx)
}
}
impl GraphicsSystem {
pub fn hot_reload_apply_parts<'a>(
&'a mut self,
backend: &'a mut dyn RenderBackend,
) -> HotReloadApplyParts<'a> {
HotReloadApplyParts {
backend,
world_reload: &self.world_reload,
last_fog_settings: &mut self.last_fog_settings,
}
}
pub fn take_hot_reload_sources(&mut self) -> Option<hot_reload_sources::HotReloadSources> {
self.pending_hot_reload_sources.take()
}
}
pub(crate) fn quality_toggle_on(
cfg: &crate::components::PostProcessConfig,
key: &str,
) -> Option<bool> {
match key {
"ssao" => Some(cfg.ssao),
"ssr" => Some(cfg.ssr),
"ray_traced_reflections" => Some(cfg.ray_traced_reflections),
"ssgi" => Some(cfg.indirect_lighting == crate::components::IndirectLighting::Ssgi),
"auto_exposure" => Some(cfg.auto_exposure),
_ => None,
}
}
pub(crate) fn set_quality_toggle(
cfg: &mut crate::components::PostProcessConfig,
key: &str,
on: bool,
) {
match key {
"ssao" => cfg.ssao = on,
"ssr" => cfg.ssr = on,
"ray_traced_reflections" => cfg.ray_traced_reflections = on,
"ssgi" => {
cfg.indirect_lighting = if on {
crate::components::IndirectLighting::Ssgi
} else {
crate::components::IndirectLighting::Ibl
}
}
"auto_exposure" => cfg.auto_exposure = on,
_ => {}
}
}
pub(crate) fn is_quality_cycle(key: &str) -> bool {
crate::gfx::settings::QUALITY_CYCLE_KEYS.contains(&key)
}
pub(crate) fn quality_cycle_index(
cfg: &crate::components::PostProcessConfig,
key: &str,
) -> Option<usize> {
use crate::gfx::settings;
match key {
"aa_mode" => Some(settings::aa_mode_index(cfg.aa_mode)),
"ssgi_resolution" => Some(settings::ssgi_resolution_index(cfg.ssgi_resolution)),
"ssgi_rays" => Some(settings::ssgi_rays_index(cfg.ssgi_rays)),
"ssgi_steps" => Some(settings::ssgi_steps_index(cfg.ssgi_steps)),
"reflection_blur_resolution" => Some(settings::reflection_blur_index(
cfg.reflection_blur_resolution,
)),
_ => None,
}
}
pub(crate) fn set_quality_cycle(
cfg: &mut crate::components::PostProcessConfig,
key: &str,
index: usize,
) {
use crate::gfx::settings;
match key {
"aa_mode" => cfg.aa_mode = settings::aa_mode_at(index),
"ssgi_resolution" => cfg.ssgi_resolution = settings::ssgi_resolution_at(index),
"ssgi_rays" => cfg.ssgi_rays = settings::ssgi_rays_at(index),
"ssgi_steps" => cfg.ssgi_steps = settings::ssgi_steps_at(index),
"reflection_blur_resolution" => {
cfg.reflection_blur_resolution = settings::reflection_blur_at(index)
}
_ => {}
}
}
pub(crate) fn clamp_quality_cycle(
cfg: &mut crate::components::PostProcessConfig,
key: &str,
ceiling: &crate::gfx::quality_preset::QualityCeiling,
overridden: bool,
) {
if overridden {
return;
}
use crate::gfx::quality_preset::{
clamp_aa_mode, coarser_reflection_blur, coarser_ssgi_resolution,
};
match key {
"aa_mode" => cfg.aa_mode = clamp_aa_mode(cfg.aa_mode, ceiling.aa_mode),
"ssgi_resolution" => {
cfg.ssgi_resolution =
coarser_ssgi_resolution(cfg.ssgi_resolution, ceiling.ssgi_resolution)
}
"ssgi_rays" => cfg.ssgi_rays = cfg.ssgi_rays.min(ceiling.ssgi_rays),
"ssgi_steps" => cfg.ssgi_steps = cfg.ssgi_steps.min(ceiling.ssgi_steps),
"reflection_blur_resolution" => {
cfg.reflection_blur_resolution = coarser_reflection_blur(
cfg.reflection_blur_resolution,
ceiling.reflection_blur_resolution,
)
}
_ => {}
}
}
pub(crate) fn derive_quality_settings(
cfg: &crate::components::PostProcessConfig,
) -> crate::gfx::backend::QualitySettings {
crate::gfx::backend::QualitySettings {
taa: cfg.aa_mode.taa_enabled(),
ssao: cfg.ssao_settings(),
ssr: cfg.ssr_settings(),
rt_reflections: cfg.rt_reflection_settings(),
ssgi: cfg.ssgi_settings(),
reflection_blur_scale: cfg.reflection_blur_divisor(),
auto_exposure: cfg.auto_exposure_settings(),
auto_exposure_bias_ev: cfg.exposure_ev,
}
}
pub(crate) mod character_shape;
mod frame;
pub(crate) mod frame_policy;
mod helpers;
pub mod hot_reload_sources;
mod init;
mod lines;
mod model_push;
pub(crate) mod scene;
mod streaming;
pub(crate) mod submit;
#[cfg(test)]
mod tests;