@@ -60,9 +60,11 @@ constexpr uint32_t kLightingSceneCbRegister = 0u;
6060constexpr uint32_t kLightingXeGtaoRegister = 22u ;
6161constexpr uint32_t kXeGtaoPushConstantsLayoutParam = 4u ;
6262constexpr uint64_t kSceneCbMinimumBytes = 95u * 16u ;
63+ constexpr uint64_t kSceneCbMaximumBytes = 64u * 1024u ;
6364constexpr uint64_t kXeGTAODeferredStartupGuardFrames = 3u ;
6465constexpr uint64_t kXeGTAOStartupDispatchGuardFrames = 16u ;
6566constexpr uint64_t kXeGTAOStartupRequireCurrentSceneCbvFrames = 64u ;
67+ constexpr uint64_t kXeGTAOFallbackStartupQuarantineFrames = 240u ;
6668constexpr uint64_t kXeGTAOClearStartupGuardFrames = 8u ;
6769constexpr uint64_t kXeGTAOResizeDispatchGuardFrames = 4u ;
6870constexpr uint64_t kXeGTAOFallbackSceneCbvMaxAgeFrames = 2u ;
@@ -367,6 +369,7 @@ float xegtao_depth_mip_sampling_offset = 3.3f;
367369float xegtao_denoise_blur_beta = 8 .f;
368370float xegtao_debug_mode = 0 .f;
369371float xegtao_runtime_debug_logging = 0 .f;
372+ float xegtao_enable_fallbacks = 1 .f;
370373float xegtao_foliage_ao_blend = 1 .0f ;
371374float xegtao_foliage_mask_method = 0 .f; // 0=SSS parity(t1 strict), 1=legacy broad(t1), 2=t10 strict
372375
@@ -2095,6 +2098,10 @@ uint32_t ClampBooleanToggle(float value) {
20952098 return static_cast <uint32_t >(std::clamp (std::round (value), 0 .f , 1 .f ));
20962099}
20972100
2101+ bool AreXeGTAOFallbacksEnabled () {
2102+ return ClampBooleanToggle (xegtao_enable_fallbacks) != 0u ;
2103+ }
2104+
20982105bool IsXeGTAOFixLevelAtLeast (XeGTAOFixMode level) {
20992106 return ClampXeGTAOFixMode () >= static_cast <uint32_t >(level);
21002107}
@@ -2135,47 +2142,72 @@ float ClampXeGTAOIsFastJitterAmount() {
21352142 return std::clamp (xegtao_isfast_jitter_amount, 0 .f , 1 .f );
21362143}
21372144
2138- bool IsSceneCbvCandidateValid (reshade::api::device* device, const reshade::api::buffer_range& range) {
2139- if (device == nullptr ) return false ;
2140- if (range.buffer .handle == 0u ) return false ;
2145+ bool TryNormalizeSceneCbvRange (
2146+ reshade::api::device* device,
2147+ const reshade::api::buffer_range& input,
2148+ reshade::api::buffer_range* output) {
2149+ if (device == nullptr || output == nullptr ) return false ;
2150+ if (input.buffer .handle == 0u ) return false ;
21412151
2142- const auto desc = device->get_resource_desc (range .buffer );
2152+ const auto desc = device->get_resource_desc (input .buffer );
21432153 if (desc.type != reshade::api::resource_type::buffer) return false ;
21442154 if (desc.buffer .size < kSceneCbMinimumBytes ) return false ;
2145- if (range .offset >= desc.buffer .size ) return false ;
2155+ if (input .offset >= desc.buffer .size ) return false ;
21462156
2147- // In D3D11-style paths size may be UINT64_MAX (whole buffer) or 0 (unknown),
2148- // so only validate explicit ranges.
2149- if (range. size != 0u && range .size != UINT64_MAX) {
2150- if (range. size < kSceneCbMinimumBytes ) return false ;
2151- if (range. size > desc. buffer . size ) return false ;
2152- if (range. offset > desc. buffer . size - range. size ) return false ;
2153- }
2157+ const uint64_t available_bytes = desc. buffer . size - input. offset ;
2158+ const bool has_unknown_size = input. size == 0u || input. size == UINT64_MAX;
2159+ if (!has_unknown_size && input .size < kSceneCbMinimumBytes ) return false ;
2160+
2161+ uint64_t normalized_size = has_unknown_size ? available_bytes : input. size ;
2162+ if (normalized_size > available_bytes) normalized_size = available_bytes ;
2163+ if (normalized_size > kSceneCbMaximumBytes ) normalized_size = kSceneCbMaximumBytes ;
21542164
2165+ // Constant buffer payload is 16-byte aligned; align down to keep updates valid.
2166+ normalized_size &= ~0xFu ;
2167+ if (normalized_size < kSceneCbMinimumBytes ) return false ;
2168+
2169+ reshade::api::buffer_range normalized = input;
2170+ normalized.size = normalized_size;
2171+ *output = normalized;
21552172 return true ;
21562173}
21572174
2175+ bool IsSceneCbvCandidateValid (reshade::api::device* device, const reshade::api::buffer_range& range) {
2176+ reshade::api::buffer_range normalized = {};
2177+ return TryNormalizeSceneCbvRange (device, range, &normalized);
2178+ }
2179+
21582180void CacheFallbackSceneCbv (reshade::api::command_list* cmd_list, const reshade::api::buffer_range& range) {
2181+ if (!AreXeGTAOFallbacksEnabled ()) return ;
21592182 if (cmd_list == nullptr ) return ;
21602183 auto * device = cmd_list->get_device ();
21612184 if (device == nullptr ) return ;
21622185
21632186 auto * data = device->get_private_data <DeviceData>();
21642187 if (data == nullptr ) return ;
21652188
2166- if (range.buffer .handle == 0u ) return ;
2167- if (!IsSceneCbvCandidateValid (device, range)) return ;
2168- data->fallback_scene_cbv = range;
2189+ // Startup quarantine: keep fallback paths dormant during unstable bootstrap.
2190+ if (data->present_frame_index < kXeGTAOFallbackStartupQuarantineFrames ) return ;
2191+
2192+ reshade::api::buffer_range normalized = {};
2193+ if (!TryNormalizeSceneCbvRange (device, range, &normalized)) return ;
2194+ data->fallback_scene_cbv = normalized;
21692195 data->fallback_scene_cbv_seen = true ;
21702196 data->fallback_scene_cbv_frame = data->present_frame_index ;
21712197}
21722198
21732199bool TryAdoptFallbackSceneCbv (reshade::api::device* device, DeviceData* data) {
21742200 if (device == nullptr || data == nullptr ) return false ;
2201+ if (!AreXeGTAOFallbacksEnabled ()) return false ;
2202+
2203+ // Startup quarantine: avoid binding fallback CBV until runtime settles.
2204+ if (data->present_frame_index < kXeGTAOFallbackStartupQuarantineFrames ) return false ;
21752205
2206+ reshade::api::buffer_range normalized_current = {};
21762207 if (data->captured_scene_cbv_valid
21772208 && data->captured_scene_cbv_source == XeGTAOSceneCbvSource::kCurrentLighting
2178- && IsSceneCbvCandidateValid (device, data->captured_scene_cbv )) {
2209+ && TryNormalizeSceneCbvRange (device, data->captured_scene_cbv , &normalized_current)) {
2210+ data->captured_scene_cbv = normalized_current;
21792211 return true ;
21802212 }
21812213
@@ -2193,9 +2225,12 @@ bool TryAdoptFallbackSceneCbv(reshade::api::device* device, DeviceData* data) {
21932225 && data->present_frame_index - data->fallback_scene_cbv_frame > kXeGTAOFallbackSceneCbvMaxAgeFrames ) {
21942226 return false ;
21952227 }
2196- if (!IsSceneCbvCandidateValid (device, data->fallback_scene_cbv )) return false ;
2228+ reshade::api::buffer_range normalized_fallback = {};
2229+ if (!TryNormalizeSceneCbvRange (device, data->fallback_scene_cbv , &normalized_fallback)) return false ;
21972230
2198- data->captured_scene_cbv = data->fallback_scene_cbv ;
2231+ data->fallback_scene_cbv = normalized_fallback;
2232+
2233+ data->captured_scene_cbv = normalized_fallback;
21992234 data->captured_scene_cbv_valid = true ;
22002235 data->captured_scene_cbv_frame = data->present_frame_index ;
22012236 data->captured_scene_cbv_source = XeGTAOSceneCbvSource::kFallback ;
@@ -3395,7 +3430,12 @@ bool RunXeGTAOForFrame(
33953430 || data->captured_scene_cbv .buffer .handle == 0u
33963431 || !IsSceneCbvCandidateValid (device, data->captured_scene_cbv )) {
33973432 std::string reason = " lighting scene CB b0 is not captured" ;
3398- if (!data->fallback_scene_cbv_seen ) {
3433+ if (data->present_frame_index < kXeGTAOFallbackStartupQuarantineFrames ) {
3434+ reason += std::format (
3435+ " (fallback startup quarantine active: frame {} < {})" ,
3436+ data->present_frame_index ,
3437+ kXeGTAOFallbackStartupQuarantineFrames );
3438+ } else if (!data->fallback_scene_cbv_seen ) {
33993439 reason += " (tracked + fallback cache missing)" ;
34003440 } else if (!IsSceneCbvCandidateValid (device, data->fallback_scene_cbv )) {
34013441 reason += " (fallback cache rejected by validation)" ;
@@ -3454,6 +3494,12 @@ bool RunXeGTAOForFrame(
34543494 note_predispatch_reject (" lighting scene CB b0 became invalid before dispatch" );
34553495 return fail (" lighting scene CB b0 became invalid before dispatch" );
34563496 }
3497+ reshade::api::buffer_range normalized_scene_cbv = {};
3498+ if (!TryNormalizeSceneCbvRange (device, data->captured_scene_cbv , &normalized_scene_cbv)) {
3499+ note_predispatch_reject (" lighting scene CB b0 range is unsafe for dispatch" );
3500+ return fail (" lighting scene CB b0 range is unsafe for dispatch" );
3501+ }
3502+ data->captured_scene_cbv = normalized_scene_cbv;
34573503 const uint32_t fix_mode = ClampXeGTAOFixMode ();
34583504 const bool l5_pass_isolation =
34593505 fix_mode >= static_cast <uint32_t >(XeGTAOFixMode::kPassIsolationDiagnostics );
@@ -3899,8 +3945,9 @@ void CaptureTrackedConstantBuffer(
38993945 if (device == nullptr ) return ;
39003946 auto * data = device->get_private_data <DeviceData>();
39013947 if (data == nullptr ) return ;
3902- if (!IsSceneCbvCandidateValid (device, range)) return ;
3903- data->captured_scene_cbv = range;
3948+ reshade::api::buffer_range normalized = {};
3949+ if (!TryNormalizeSceneCbvRange (device, range, &normalized)) return ;
3950+ data->captured_scene_cbv = normalized;
39043951 data->captured_scene_cbv_valid = true ;
39053952 data->captured_scene_cbv_frame = data->present_frame_index ;
39063953 data->captured_scene_cbv_source = XeGTAOSceneCbvSource::kCurrentLighting ;
@@ -4291,10 +4338,11 @@ void ResolveXeGTAOInputsFromCurrentBindings(reshade::api::command_list* cmd_list
42914338 if (kLightingSceneCbRegister >= range.dx_register_index
42924339 && kLightingSceneCbRegister < (range.dx_register_index + range.count )) {
42934340 reshade::api::buffer_range cbv = {};
4341+ reshade::api::buffer_range normalized_cbv = {};
42944342 const uint32_t descriptor_index = kLightingSceneCbRegister - range.dx_register_index ;
42954343 if (TryGetBufferRangeFromBoundDescriptorTable (device, table, range, descriptor_index, &cbv)
4296- && IsSceneCbvCandidateValid (device, cbv)) {
4297- data->captured_scene_cbv = cbv ;
4344+ && TryNormalizeSceneCbvRange (device, cbv, &normalized_cbv )) {
4345+ data->captured_scene_cbv = normalized_cbv ;
42984346 data->captured_scene_cbv_valid = true ;
42994347 data->captured_scene_cbv_frame = data->present_frame_index ;
43004348 data->captured_scene_cbv_source = XeGTAOSceneCbvSource::kCurrentLighting ;
@@ -5297,14 +5345,15 @@ void OnPresentAdvanceFrame(
52975345 AddonLog (
52985346 reshade::log::level::info,
52995347 std::format (
5300- " XeGTAO startup mode: fix={}, l5(prefilter={}, main={}, denoise={}, composite={}), probeA={}, probeB={}" ,
5348+ " XeGTAO startup mode: fix={}, l5(prefilter={}, main={}, denoise={}, composite={}), probeA={}, probeB={}, fallbacks ={}" ,
53015349 GetXeGTAOFixModeName (ClampXeGTAOFixMode ()),
53025350 ClampBooleanToggle (xegtao_fix_l5_prefilter),
53035351 ClampBooleanToggle (xegtao_fix_l5_main),
53045352 ClampBooleanToggle (xegtao_fix_l5_denoise),
53055353 ClampBooleanToggle (xegtao_fix_l5_composite),
53065354 ClampBooleanToggle (xegtao_probe_a_dispatch_no_t22),
5307- ClampBooleanToggle (xegtao_probe_b_t22_no_dispatch)));
5355+ ClampBooleanToggle (xegtao_probe_b_t22_no_dispatch),
5356+ ClampBooleanToggle (xegtao_enable_fallbacks)));
53085357 }
53095358 const uint64_t frame = data->present_frame_index ;
53105359 if (data->xegtao_deferred_dispatch_pending
@@ -5355,7 +5404,9 @@ void OnPresentAdvanceFrame(
53555404 reshade::api::resource_view deferred_mrt_normal_srv =
53565405 data->xegtao_deferred_mrt_normal_srv ;
53575406 bool deferred_mrt_normal_valid = deferred_mrt_normal_srv.handle != 0u ;
5358- if (!deferred_mrt_normal_valid && fallback_lighting_mrt0.handle != 0u ) {
5407+ if (!deferred_mrt_normal_valid
5408+ && AreXeGTAOFallbacksEnabled ()
5409+ && fallback_lighting_mrt0.handle != 0u ) {
53595410 deferred_mrt_normal_srv = fallback_lighting_mrt0;
53605411 deferred_mrt_normal_valid = true ;
53615412 }
@@ -5366,6 +5417,14 @@ void OnPresentAdvanceFrame(
53665417 data->xegtao_deferred_scene_cbv_valid
53675418 && data->xegtao_deferred_scene_cbv .buffer .handle != 0u
53685419 && IsSceneCbvCandidateValid (device, data->xegtao_deferred_scene_cbv );
5420+ if (deferred_scene_cbv_valid) {
5421+ reshade::api::buffer_range normalized_deferred_scene_cbv = {};
5422+ if (TryNormalizeSceneCbvRange (device, data->xegtao_deferred_scene_cbv , &normalized_deferred_scene_cbv)) {
5423+ data->xegtao_deferred_scene_cbv = normalized_deferred_scene_cbv;
5424+ } else {
5425+ deferred_scene_cbv_valid = false ;
5426+ }
5427+ }
53695428
53705429 if (!deferred_scene_cbv_valid && data->fallback_scene_cbv_seen ) {
53715430 data->captured_scene_cbv = data->xegtao_deferred_scene_cbv ;
@@ -5536,6 +5595,7 @@ void OnPresentAdvanceFrame(
55365595 xegtao_probe_a_dispatch_no_t22 = static_cast <float >(ClampBooleanToggle (xegtao_probe_a_dispatch_no_t22));
55375596 xegtao_probe_b_t22_no_dispatch = static_cast <float >(ClampBooleanToggle (xegtao_probe_b_t22_no_dispatch));
55385597 xegtao_runtime_debug_logging = static_cast <float >(ClampBooleanToggle (xegtao_runtime_debug_logging));
5598+ xegtao_enable_fallbacks = static_cast <float >(ClampBooleanToggle (xegtao_enable_fallbacks));
55395599 g_enable_runtime_addon_logs.store (xegtao_runtime_debug_logging >= 0 .5f , std::memory_order_relaxed);
55405600 xegtao_denoiser_mode = 0 .f ;
55415601 xegtao_isfast_jitter_amount = std::clamp (xegtao_isfast_jitter_amount, 0 .f , 1 .f );
@@ -6108,6 +6168,19 @@ renodx::utils::settings::Settings settings = {
61086168 .is_visible = []() { return IsAdvancedSettingsMode (); },
61096169 },
61106170 new renodx::utils::settings::Setting{
6171+ .key = " XeGTAOFallbacks" ,
6172+ .binding = &xegtao_enable_fallbacks,
6173+ .value_type = renodx::utils::settings::SettingValueType::BOOLEAN,
6174+ .default_value = 1 .f ,
6175+ .label = " Fallbacks" ,
6176+ .section = " XeGTAO" ,
6177+ .tooltip =
6178+ " Off disables XeGTAO fallback sources (fallback scene CBV and deferred MRT-normal fallback). On enables those fallback paths." ,
6179+ .labels = {" Off" , " On" },
6180+ .is_enabled = []() { return xegtao_mode >= 0 .5f ; },
6181+ .is_visible = []() { return true ; },
6182+ },
6183+ new renodx::utils::settings::Setting{
61116184 .key = " XeGTAORuntimeDebugLogging" ,
61126185 .binding = &xegtao_runtime_debug_logging,
61136186 .value_type = renodx::utils::settings::SettingValueType::BOOLEAN,
0 commit comments