diff --git a/include/rex/ui/overlay/simple_settings_overlay.h b/include/rex/ui/overlay/simple_settings_overlay.h index 016beee..d0ef863 100644 --- a/include/rex/ui/overlay/simple_settings_overlay.h +++ b/include/rex/ui/overlay/simple_settings_overlay.h @@ -102,6 +102,7 @@ class SimpleSettingsDialog final : public ImGuiDialog { int aspect_ratio_index_ = 0; int msaa_index_ = 2; int shadow_quality_index_ = 2; + int static_shadow_res_index_ = 2; int monitor_index_ = 0; int audio_buffer_index_ = 0; int language_index_ = 0; @@ -116,6 +117,9 @@ class SimpleSettingsDialog final : public ImGuiDialog { // Live setting values (hot cvars, applied and saved on change). bool renderer_native_ = true; bool ssao_ = true; + bool static_shadows_ = true; + float static_shadow_strength_ = 1.0f; + bool shadow_pcss_ = true; int draw_distance_index_ = 1; int stream_probe_index_ = 0; bool mode_indicator_ = true; diff --git a/src/ui/overlay/simple_settings_overlay.cpp b/src/ui/overlay/simple_settings_overlay.cpp index 6345ce3..9e97033 100644 --- a/src/ui/overlay/simple_settings_overlay.cpp +++ b/src/ui/overlay/simple_settings_overlay.cpp @@ -53,11 +53,15 @@ constexpr std::array kCoreSimpleSettingsCvars = { // Optional cvars persisted when the host defines them (HasCvar-gated: app // cvars like the native-renderer knobs don't exist in every embedder, and // backend/platform cvars don't exist in every build). -constexpr std::array kOptionalSimpleSettingsCvars = { +constexpr std::array kOptionalSimpleSettingsCvars = { "skate3_native_render_scene", "skate3_native_render_scene_msaa", "skate3_native_render_scene_shadows", "skate3_native_render_scene_shadow_tile", + "skate3_native_render_scene_shadow_static_casters", + "skate3_native_render_scene_shadow_static_strength", + "skate3_native_render_scene_shadow_static_size", + "skate3_native_render_scene_shadow_pcss", "skate3_native_render_scene_ssao", "skate3_native_render_mode_indicator", "show_fps_counter", @@ -80,6 +84,12 @@ constexpr std::array kShadowQualityLabels = { "Off", "Auto", "512 (console)", "1024", "1536", "2048"}; constexpr std::array kShadowQualityTiles = {0, 0, 512, 1024, 1536, 2048}; +// Enhanced (static sun) shadow map resolution per cascade tile. +constexpr std::array kStaticShadowResLabels = {"1024", "2048", + "4096", "8192"}; +constexpr std::array kStaticShadowResSizes = {1024, 2048, 4096, + 8192}; + // Draw distance: one multiplier applied to both the world detail cull and // the character LOD switch ranges (paired hot cvars in the app layer). constexpr std::array kDrawDistanceLabels = {"Original", "2x", @@ -400,6 +410,29 @@ int ShadowQualityIndexFromCvar() { rex::cvar::Query("skate3_native_render_scene_shadow_tile")); } +bool HasStaticShadowCvars() { + return HasCvar("skate3_native_render_scene_shadow_static_casters") && + HasCvar("skate3_native_render_scene_shadow_static_size"); +} + +int StaticShadowResIndexFrom(int32_t size) { + int best = 0; + for (int i = 0; i < static_cast(kStaticShadowResSizes.size()); ++i) { + if (size >= kStaticShadowResSizes[i]) { + best = i; + } + } + return best; +} + +int StaticShadowResIndexFromCvar() { + if (!HasStaticShadowCvars()) { + return 2; + } + return StaticShadowResIndexFrom( + rex::cvar::Query("skate3_native_render_scene_shadow_static_size")); +} + bool HasDrawDistanceCvars() { return HasCvar("skate3_draw_distance_scale") && HasCvar("skate3_lod_distance_scale"); @@ -934,6 +967,7 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() { HasCvar("skate3_ultrawide") && rex::cvar::Query("skate3_ultrawide") ? 1 : 0; msaa_index_ = MsaaIndexFromCvar(); shadow_quality_index_ = ShadowQualityIndexFromCvar(); + static_shadow_res_index_ = StaticShadowResIndexFromCvar(); monitor_index_ = MonitorIndexFromCvar(); audio_buffer_index_ = AudioBufferIndexFromCvar(); language_index_ = LanguageIndexFromCvar(); @@ -947,6 +981,17 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() { HasCvar("skate3_native_render_scene") && rex::cvar::Query("skate3_native_render_scene"); ssao_ = HasCvar("skate3_native_render_scene_ssao") && rex::cvar::Query("skate3_native_render_scene_ssao"); + static_shadows_ = + HasCvar("skate3_native_render_scene_shadow_static_casters") && + rex::cvar::Query("skate3_native_render_scene_shadow_static_casters"); + static_shadow_strength_ = + HasCvar("skate3_native_render_scene_shadow_static_strength") + ? float(std::clamp(rex::cvar::Query( + "skate3_native_render_scene_shadow_static_strength"), + 0.0, 1.0)) + : 1.0f; + shadow_pcss_ = HasCvar("skate3_native_render_scene_shadow_pcss") && + rex::cvar::Query("skate3_native_render_scene_shadow_pcss"); draw_distance_index_ = DrawDistanceIndexFromCvar(); stream_probe_index_ = StreamProbeIndexFromCvar(); mode_indicator_ = HasCvar("skate3_native_render_mode_indicator") && @@ -969,6 +1014,8 @@ bool SimpleSettingsDialog::HasSettingsChanges() const { (aspect_ratio_index_ != 0) != rex::cvar::Query("skate3_ultrawide")) || (HasMsaaCvar() && msaa_index_ != MsaaIndexFromCvar()) || (HasShadowQualityCvars() && shadow_quality_index_ != ShadowQualityIndexFromCvar()) || + (HasStaticShadowCvars() && + static_shadow_res_index_ != StaticShadowResIndexFromCvar()) || (HasCvar("monitor") && monitor_index_ != MonitorIndexFromCvar()) || (HasCvar("audio_device_sample_frames") && audio_buffer_index_ != AudioBufferIndexFromCvar()) || @@ -1097,6 +1144,14 @@ void SimpleSettingsDialog::SaveVideo() { std::to_string(kShadowQualityTiles[shadow_quality_index_])); } } + if (HasStaticShadowCvars()) { + static_shadow_res_index_ = std::clamp( + static_shadow_res_index_, 0, + static_cast(kStaticShadowResSizes.size()) - 1); + rex::cvar::SetFlagByName( + "skate3_native_render_scene_shadow_static_size", + std::to_string(kStaticShadowResSizes[static_shadow_res_index_])); + } if (HasCvar("monitor")) { monitor_index_ = std::clamp(monitor_index_, 0, static_cast(kMonitorLabels.size()) - 1); rex::cvar::SetFlagByName("monitor", std::to_string(monitor_index_)); @@ -1384,6 +1439,103 @@ void SimpleSettingsDialog::BuildRows(std::vector& rows, int category) { }; rows.push_back(std::move(row)); } + if (HasCvar("skate3_native_render_scene_shadow_static_casters")) { + RowSpec row; + row.kind = RowSpec::kEnum; + row.label = "Enhanced Shadows"; + row.desc = + "Real-time sun shadows cast by buildings, trees and props from a " + "dedicated sun-aligned shadow map; the original game only bakes " + "these into the world. Also shades the volumetric sun shafts. " + "Applies immediately."; + row.options = {"Off", "On"}; + row.flag = &static_shadows_; + row.on_enum_change = [this](int value) { + SetBoolCvar("skate3_native_render_scene_shadow_static_casters", + value != 0); + SaveSimpleSettingsConfig(config_path_); + }; + row.reset = [this] { + static_shadows_ = CvarDefaultBool( + "skate3_native_render_scene_shadow_static_casters", true); + SetBoolCvar("skate3_native_render_scene_shadow_static_casters", + static_shadows_); + SaveSimpleSettingsConfig(config_path_); + }; + rows.push_back(std::move(row)); + } + if (HasStaticShadowCvars()) { + RowSpec row; + row.kind = RowSpec::kEnum; + row.label = "Enhanced Shadow Resolution"; + row.desc = + "Resolution per cascade of the enhanced shadow map. Higher is " + "sharper at distance but uses more video memory (roughly 50 MB " + "at 2048, 200 MB at 4096, 800 MB at 8192)."; + for (const char* label : kStaticShadowResLabels) { + row.options.push_back(label); + } + row.index = &static_shadow_res_index_; + row.reset = [this] { + static_shadow_res_index_ = StaticShadowResIndexFrom(int32_t( + CvarDefaultDouble("skate3_native_render_scene_shadow_static_size", + 4096.0))); + }; + rows.push_back(std::move(row)); + } + if (HasCvar("skate3_native_render_scene_shadow_static_strength")) { + RowSpec row; + row.kind = RowSpec::kSlider; + row.label = "Enhanced Shadow Strength"; + row.desc = + "How dark the enhanced shadows get (1 matches the game's dynamic " + "shadows). Lower values blend them with the baked lighting where " + "the two disagree. Applies immediately."; + row.value = &static_shadow_strength_; + row.min = 0.0f; + row.max = 1.0f; + row.step = 0.05f; + row.fmt = "%.2f"; + row.on_value_change = [this] { + static_shadow_strength_ = + std::clamp(static_shadow_strength_, 0.0f, 1.0f); + rex::cvar::SetFlagByName( + "skate3_native_render_scene_shadow_static_strength", + std::to_string(static_shadow_strength_)); + }; + row.reset = [this] { + static_shadow_strength_ = float(CvarDefaultDouble( + "skate3_native_render_scene_shadow_static_strength", 1.0)); + rex::cvar::SetFlagByName( + "skate3_native_render_scene_shadow_static_strength", + std::to_string(static_shadow_strength_)); + SaveSimpleSettingsConfig(config_path_); + }; + rows.push_back(std::move(row)); + } + if (HasCvar("skate3_native_render_scene_shadow_pcss")) { + RowSpec row; + row.kind = RowSpec::kEnum; + row.label = "Soft Shadows"; + row.desc = + "Contact-hardening soft shadows: crisp where a shadow meets its " + "caster, progressively softer with distance, following the sun's " + "angular size. Off uses fixed-width filtering. Applies " + "immediately."; + row.options = {"Off", "On"}; + row.flag = &shadow_pcss_; + row.on_enum_change = [this](int value) { + SetBoolCvar("skate3_native_render_scene_shadow_pcss", value != 0); + SaveSimpleSettingsConfig(config_path_); + }; + row.reset = [this] { + shadow_pcss_ = + CvarDefaultBool("skate3_native_render_scene_shadow_pcss", true); + SetBoolCvar("skate3_native_render_scene_shadow_pcss", shadow_pcss_); + SaveSimpleSettingsConfig(config_path_); + }; + rows.push_back(std::move(row)); + } if (HasCvar("skate3_native_render_scene_ssao")) { RowSpec row; row.kind = RowSpec::kEnum;