From 73945c06d7bcf4b049cd1cc9f1cc8b1540a9901f Mon Sep 17 00:00:00 2001 From: bomabomabomaboma <69987043+goldislead@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:56:48 -0700 Subject: [PATCH] [GPU] Simplify ZPD cvars --- src/xenia/gpu/command_processor.cc | 18 +++++++++++------- src/xenia/gpu/command_processor.h | 10 +++++----- src/xenia/gpu/d3d12/d3d12_command_processor.cc | 2 +- src/xenia/gpu/gpu_flags.cc | 9 +-------- src/xenia/gpu/gpu_flags.h | 4 +--- .../gpu/pm4_command_processor_implement.h | 2 +- .../gpu/vulkan/vulkan_command_processor.cc | 3 ++- src/xenia/gpu/xenos_zpd_report.h | 3 +-- 8 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index bf1ccfdc8..3c0f65510 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -58,7 +58,10 @@ DEFINE_string( " though some effects may look slightly wrong.\n" " fast: Ask the GPU but don't wait for the answer. Writes a cached\n" " result immediately and updates it when the GPU catches up.\n" - " (default)\n" + " Cached results bias toward visible when guessing. (default)\n" + " fast-alt: Variant of fast mode that keeps cached zero results for\n" + " unresolved reports. May improve effects relying on precise\n" + " visibility, but may be less stable for occlusion culling.\n" " strict: Ask the GPU and wait for the real result before continuing.\n" " Most accurate, but may be somewhat less performant.", "GPU"); @@ -130,6 +133,8 @@ ZPDMode GetZPDMode() { return ZPDMode::kFake; } else if (mode == "strict") { return ZPDMode::kStrict; + } else if (mode == "fast-alt") { + return ZPDMode::kFastAlt; } return ZPDMode::kFast; } @@ -968,7 +973,7 @@ bool CommandProcessor::BeginZPDReport(uint32_t report_address) { PendingZPDSlot pending_slot = GetPendingZPDSlot(slot_base, end_record); if (pending_slot.report_handle != kInvalidReportHandle) { - if (GetZPDMode() == ZPDMode::kFast) { + if (GetZPDMode() == ZPDMode::kFast || GetZPDMode() == ZPDMode::kFastAlt) { if (pending_slot.has_cached_delta) { carried_cached_delta = pending_slot.cached_delta; has_carried_cached_delta = true; @@ -1008,7 +1013,7 @@ bool CommandProcessor::BeginZPDReport(uint32_t report_address) { // By default, BEGIN drops the cached value so an orphaned END doesn't replay // something from a prior lifetime. The alternate fast path keeps it around // long enough for an async zero to help the next unresolved write. - if (!cvars::occlusion_query_fast_preserve_cached_zero) { + if (GetZPDMode() != ZPDMode::kFastAlt) { fast_zpd_report_cached_values_.erase(end_record); } @@ -1136,7 +1141,7 @@ bool CommandProcessor::EndZPDReport(uint32_t report_address, WriteZPDReport(0, stored_end_record, 0, begin_value, false); } - if (GetZPDMode() == ZPDMode::kFast) { + if (GetZPDMode() == ZPDMode::kFast || GetZPDMode() == ZPDMode::kFastAlt) { bool write_begin = begin_record && report_record_base && begin_record != report_record_base; // Unknown still means visible in fast mode. Reusing cached zeroes can help @@ -1147,8 +1152,7 @@ bool CommandProcessor::EndZPDReport(uint32_t report_address, if (!resolved_immediately) { speculative = 1; if (has_cached_delta && - (cached_delta != 0 || - cvars::occlusion_query_fast_preserve_cached_zero)) { + (cached_delta != 0 || GetZPDMode() == ZPDMode::kFastAlt)) { speculative = cached_delta; } } @@ -1201,7 +1205,7 @@ void CommandProcessor::OpenQuerySegment(bool can_close_submission) { case QueryOpenResult::kDeferred: return; case QueryOpenResult::kPoolExhausted: { - if (GetZPDMode() == ZPDMode::kFast) { + if (GetZPDMode() == ZPDMode::kFast || GetZPDMode() == ZPDMode::kFastAlt) { // Fast mode favors forward progress over accuracy. Keep a minimal // accumulated value instead of waiting for a slot to become available. auto it = logical_zpd_reports_.find(zpd_active_segment_.report_handle); diff --git a/src/xenia/gpu/command_processor.h b/src/xenia/gpu/command_processor.h index 113e221b5..9fce69114 100644 --- a/src/xenia/gpu/command_processor.h +++ b/src/xenia/gpu/command_processor.h @@ -45,9 +45,10 @@ enum class ReadbackResolveMode { // Occlusion queries - ZPD report mode. enum class ZPDMode { - kFake, // Fake sample counts, no real GPU queries (fake) - kFast, // Real queries with speculative cached writes (fast) - kStrict, // Real queries, waits before writeback (strict) + kFake, // Fake sample counts, no real GPU queries (fake) + kFast, // Real queries with speculative cached writes (fast) + kFastAlt, // Fast queries, but preserves cached zeroes (fast-alt) + kStrict, // Real queries, waits before writeback (strict) }; void SaveGPUSetting(GPUSetting setting, uint64_t value); @@ -324,8 +325,7 @@ class CommandProcessor { uint32_t begin_value = 0; uint32_t pending_segments = 0; // Last known delta. Carried forward on forced close so slot doesn't - // briefly look fully occluded. 0 is a valid delta if the alternate fast - // cvar is enabled. + // briefly look fully occluded. 0 is a valid delta for alternate fast path. uint32_t cached_delta = 0; bool has_cached_delta = false; bool ended = false; diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 81550a70d..68cbf42a6 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -5312,7 +5312,7 @@ CommandProcessor::QueryOpenResult D3D12CommandProcessor::OpenZPDQuery( bool waited_for_submission = false; if (is_pool_exhausted) { - if (GetZPDMode() == ZPDMode::kFast) { + if (GetZPDMode() == ZPDMode::kFast || GetZPDMode() == ZPDMode::kFastAlt) { return QueryOpenResult::kPoolExhausted; } diff --git a/src/xenia/gpu/gpu_flags.cc b/src/xenia/gpu/gpu_flags.cc index 839eb8b8e..88b55444d 100644 --- a/src/xenia/gpu/gpu_flags.cc +++ b/src/xenia/gpu/gpu_flags.cc @@ -71,13 +71,6 @@ DEFINE_int32(occlusion_query_fake_upper_threshold, 100, "Keep this higher than occlusion_query_fake_lower_threshold.\n" "Ignored if occlusion_query_fake_lower_threshold is -1.", "GPU"); -DEFINE_bool(occlusion_query_fast_preserve_cached_zero, false, - "Alternate fast ZPD behavior that allows saved zero results to be " - "used for unresolved fast writes instead of forcing them visible.\n" - "This can materially improve flare accuracy in titles that might " - "otherwise need strict mode, but it also tends to break occlusion " - "culling in some titles.", - "GPU"); DEFINE_int32(occlusion_query_querybatch_range, 0, "Range of fake sample count values to walk for titles using the " "D3D QueryBatch standard before wrapping back to " @@ -86,7 +79,7 @@ DEFINE_int32(occlusion_query_querybatch_range, 0, "unless necessary for a specific title.", "GPU"); DEFINE_double( - occlusion_query_sample_count_saturation, 1.0, + occlusion_query_saturation, 1.0, "Compress higher occlusion query sample counts before guest writeback.\n" "This can be useful if effects such as lens flares appear too strong.\n" "1.0 = default behavior\n" diff --git a/src/xenia/gpu/gpu_flags.h b/src/xenia/gpu/gpu_flags.h index 701903ff6..3d19728cd 100644 --- a/src/xenia/gpu/gpu_flags.h +++ b/src/xenia/gpu/gpu_flags.h @@ -32,11 +32,9 @@ DECLARE_int32(occlusion_query_fake_lower_threshold); DECLARE_int32(occlusion_query_fake_upper_threshold); -DECLARE_bool(occlusion_query_fast_preserve_cached_zero); - DECLARE_int32(occlusion_query_querybatch_range); -DECLARE_double(occlusion_query_sample_count_saturation); +DECLARE_double(occlusion_query_saturation); DECLARE_int32(anisotropic_override); diff --git a/src/xenia/gpu/pm4_command_processor_implement.h b/src/xenia/gpu/pm4_command_processor_implement.h index c66d9fbf3..8f8519feb 100644 --- a/src/xenia/gpu/pm4_command_processor_implement.h +++ b/src/xenia/gpu/pm4_command_processor_implement.h @@ -1011,7 +1011,7 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( // No logical report is active for this slot, so this is likely an // orphaned END. In fast mode, replay the last cached delta so polling // code does not sit on the sentinel forever. - if (GetZPDMode() == ZPDMode::kFast) { + if (GetZPDMode() == ZPDMode::kFast || GetZPDMode() == ZPDMode::kFastAlt) { uint32_t cached_delta = 1; auto cache_it = fast_zpd_report_cached_values_.find(report_record_base); if (cache_it != fast_zpd_report_cached_values_.end()) { diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 81bcb0b5d..ce46a01ab 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -3318,7 +3318,8 @@ CommandProcessor::QueryOpenResult VulkanCommandProcessor::OpenZPDQuery( is_pool_exhausted = !zpd_host_query_pool_->has_free_indices(); } - if (is_pool_exhausted && GetZPDMode() == ZPDMode::kFast) { + if (is_pool_exhausted && + (GetZPDMode() == ZPDMode::kFast || GetZPDMode() == ZPDMode::kFastAlt)) { return QueryOpenResult::kPoolExhausted; } diff --git a/src/xenia/gpu/xenos_zpd_report.h b/src/xenia/gpu/xenos_zpd_report.h index 437e10947..2fbdf558b 100644 --- a/src/xenia/gpu/xenos_zpd_report.h +++ b/src/xenia/gpu/xenos_zpd_report.h @@ -102,8 +102,7 @@ struct XenosZPDReport { static uint32_t SaturateSampleCount(uint32_t sample_count) { double saturation = std::clamp( - static_cast(cvars::occlusion_query_sample_count_saturation), - 0.0, 1.0); + static_cast(cvars::occlusion_query_saturation), 0.0, 1.0); if (sample_count == 0 || saturation >= 1.0) { return sample_count;