From 2d07d228ee6328799cdee20b972849c7443116f2 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:08:18 +0900 Subject: [PATCH] [GPU] ZPD occlusion query fixes and performance improvements --- src/xenia/gpu/command_processor.cc | 43 ++++++++++++---- src/xenia/gpu/command_processor.h | 20 +++++++- .../gpu/d3d12/d3d12_command_processor.cc | 18 +++++-- src/xenia/gpu/d3d12/d3d12_command_processor.h | 8 ++- src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc | 49 +++++++++---------- src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h | 6 +++ .../gpu/pm4_command_processor_implement.h | 12 ++++- .../gpu/vulkan/vulkan_command_processor.cc | 40 ++++++++++++--- .../gpu/vulkan/vulkan_command_processor.h | 19 ++++++- src/xenia/gpu/vulkan/vulkan_zpd_query_pool.cc | 46 ++++++++--------- src/xenia/gpu/vulkan/vulkan_zpd_query_pool.h | 6 +++ src/xenia/gpu/xenos_zpd_report.h | 2 +- src/xenia/ui/imgui_performance_dialog.cc | 3 +- 13 files changed, 195 insertions(+), 77 deletions(-) diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index 27d2abdda..9de7edad2 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -368,6 +368,13 @@ void CommandProcessor::SetZPDMode(ZPDMode mode) { if (cached_zpd_mode_ == mode) { return; } + // Close any active query segment before the mode changes so that a + // BeginQuery recorded under the old mode gets a matching EndQuery. + // Without this, switching to kFake mid-frame would cause EndRenderPass + // to skip CloseQuerySegment, leaving the query dangling. + if (zpd_active_segment_.segment_active) { + CloseQuerySegment(); + } cached_zpd_mode_ = mode; const char* mode_str = "fake"; switch (mode) { @@ -1045,8 +1052,13 @@ bool CommandProcessor::BeginZPDReport(uint32_t report_address) { } if (zpd_active_segment_.segment_active) { - if (DiscardZPDQuery(zpd_active_segment_.query_index, - zpd_active_segment_.query_generation)) { + uint32_t discard_index = zpd_active_segment_.query_index; + uint32_t discard_generation = zpd_active_segment_.query_generation; + // Deactivate the segment before DiscardZPDQuery so that + // EndSubmission -> CloseQuerySegment does not re-enter and + // issue a second EndQuery on the same slot. + zpd_active_segment_.segment_active = false; + if (DiscardZPDQuery(discard_index, discard_generation)) { zpd_stats_.segments_ended++; } else { zpd_stats_.failed++; @@ -1158,6 +1170,10 @@ bool CommandProcessor::EndZPDReport(uint32_t report_address, ? logical.cached_delta : final_value; logical.cached_delta = cached_delta; + if (fast_zpd_report_cached_values_.size() >= kFastZPDCacheMaxEntries && + !fast_zpd_report_cached_values_.count(report_record_base)) { + fast_zpd_report_cached_values_.clear(); + } fast_zpd_report_cached_values_[report_record_base] = cached_delta; final_value = cached_delta; } else { @@ -1313,7 +1329,6 @@ void CommandProcessor::DrainQueryResolves(uint64_t completed_submission) { } ZPDSubmissionBridge* submission_bridge = GetZPDSubmissionBridge(); - std::vector ready_resolves; bool any_resolved = false; if (submission_bridge != nullptr) { @@ -1321,15 +1336,11 @@ void CommandProcessor::DrainQueryResolves(uint64_t completed_submission) { } while (!zpd_resolves_in_flight_.empty()) { - PendingQueryResolve resolve = zpd_resolves_in_flight_.front(); - if (resolve.submission > completed_submission) { + if (zpd_resolves_in_flight_.front().submission > completed_submission) { break; } + PendingQueryResolve resolve = zpd_resolves_in_flight_.front(); zpd_resolves_in_flight_.pop_front(); - ready_resolves.push_back(resolve); - } - - for (const PendingQueryResolve& resolve : ready_resolves) { uint64_t raw_samples = GetZPDQueryResult(resolve.query_index); bool is_valid = IsZPDQueryResultValid(resolve.query_index, resolve.query_generation); @@ -1357,6 +1368,11 @@ void CommandProcessor::DrainQueryResolves(uint64_t completed_submission) { logical.cached_delta = final_value; if (logical.end_record) { + if (fast_zpd_report_cached_values_.size() >= + kFastZPDCacheMaxEntries && + !fast_zpd_report_cached_values_.count(logical.end_record)) { + fast_zpd_report_cached_values_.clear(); + } fast_zpd_report_cached_values_[logical.end_record] = final_value; } zpd_report_controller_->SetReportResolved(resolve.report_handle, @@ -1482,6 +1498,12 @@ void CommandProcessor::PumpPendingRetire() { "handle={}, abandoning", handle_to_await); } + // Notify the controller so it can retire the report and clean up its + // internal maps. Use the cached delta to avoid a sudden occlusion flash. + if (zpd_report_controller_) { + zpd_report_controller_->SetReportResolved( + handle_to_await, logical_report->second.cached_delta); + } logical_zpd_reports_.erase(logical_report); zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; @@ -1497,6 +1519,9 @@ void CommandProcessor::WriteZPDReport(uint32_t begin_record, ? memory_->TranslatePhysical( begin_record) : nullptr; + if (!end_record) { + return; + } xenos::xe_gpu_depth_sample_counts* end = memory_->TranslatePhysical( end_record); diff --git a/src/xenia/gpu/command_processor.h b/src/xenia/gpu/command_processor.h index 4afff7259..a79097149 100644 --- a/src/xenia/gpu/command_processor.h +++ b/src/xenia/gpu/command_processor.h @@ -64,10 +64,28 @@ bool GetGPUSetting(GPUSetting setting); // Shared pool capacity for D3D12 and Vulkan. constexpr uint32_t kZPDQueryPoolCapacity = 8192; +// Contiguous range of query indices for batched resolve/copy operations. +struct ResolveRange { + uint32_t start; + uint32_t count; +}; + // Backstop for strict mode. Abandon any pending retires after this many polls // so EVENT_WRITE_ZPD doesn't keep spinning on an unresolved report. constexpr uint32_t kStrictZPDRetireMaxStalls = 16; +// Cap for the fast-mode cached delta map. Games reuse a small set of report +// addresses so this should never be hit, but prevents unbounded growth if a +// title cycles through unique addresses. Clearing the cache has no +// correctness impact — it only removes speculative writeback hints. +constexpr size_t kFastZPDCacheMaxEntries = 1024; + +// Consecutive stepping records on the same batch page before switching to +// cumulative fake mode. Raised to 16 for repeated orphan ENDs (no matching +// BEGIN) since those are more common in normal use. +constexpr uint32_t kZPDBatchRunThreshold = 4; +constexpr uint32_t kZPDBatchRunThresholdOrphanEnd = 16; + class GraphicsSystem; class Shader; @@ -507,7 +525,7 @@ class CommandProcessor { std::unordered_map fast_zpd_report_cached_values_; // PM4 batched query ripcord. If the guest walks the page of pending 0x20 - // checkpoints then permanenetly switch to cumulative fake mode. + // checkpoints then permanently switch to cumulative fake mode. bool zpd_batch_fake_ = false; uint32_t zpd_batch_fake_count_ = 0; uint32_t zpd_batch_page_ = 0; diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 60d511846..1320847e2 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -147,6 +147,18 @@ void D3D12CommandProcessor::RestoreEdramSnapshot(const void* snapshot) { render_target_cache_->RestoreEdramSnapshot(snapshot); } +void D3D12CommandProcessor::PrepareForWait() { + // Refresh completion data so PumpPendingRetire in the base class sees the + // latest GPU progress. + CheckSubmissionCompletion(0); + CommandProcessor::PrepareForWait(); +} + +void D3D12CommandProcessor::ReturnFromWait() { + CheckSubmissionCompletion(0); + CommandProcessor::ReturnFromWait(); +} + bool D3D12CommandProcessor::PushTransitionBarrier( ID3D12Resource* resource, D3D12_RESOURCE_STATES old_state, D3D12_RESOURCE_STATES new_state, UINT subresource) { @@ -5735,7 +5747,7 @@ ID3D12Resource* D3D12CommandProcessor::RequestReadbackBuffer(uint32_t size) { } void D3D12CommandProcessor::EnsureZPDQueryResources() { - if (GetZPDMode() == ZPDMode::kFake) { + if (GetZPDMode() == ZPDMode::kFake || !zpd_host_query_pool_) { return; } @@ -5846,10 +5858,6 @@ bool D3D12CommandProcessor::ZPDSubmissionBridge::EnsureProgress() { return false; } - if (!command_processor_.CanEndSubmissionImmediately() && - !command_processor_.pipeline_cache_->IsCreatingPipelines()) { - return false; - } if (!command_processor_.CanEndSubmissionImmediately()) { if (cvars::occlusion_query_log) { XELOGI( diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.h b/src/xenia/gpu/d3d12/d3d12_command_processor.h index c7b18d6c8..46c75e959 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.h +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.h @@ -82,6 +82,9 @@ class D3D12CommandProcessor final : public CommandProcessor { void RestoreEdramSnapshot(const void* snapshot) override; + void PrepareForWait() override; + void ReturnFromWait() override; + ui::d3d12::D3D12Provider& GetD3D12Provider() const { return *static_cast( graphics_system_->provider()); @@ -512,7 +515,10 @@ class D3D12CommandProcessor final : public CommandProcessor { // always produce 0 if the pool was exhausted when it was issued. void EnsureZPDQueryResources() override; void ShutdownZPDQueryResources() override { - zpd_host_query_pool_->Shutdown(); + zpd_resolves_in_flight_.clear(); + if (zpd_host_query_pool_) { + zpd_host_query_pool_->Shutdown(); + } } bool IsZPDQueryPoolReady() const override { diff --git a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc index 5c8fe8eaf..6fb4ef719 100644 --- a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc +++ b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.cc @@ -19,13 +19,6 @@ namespace xe { namespace gpu { namespace d3d12 { -namespace { - -struct ResolveRange { - uint32_t start; - uint32_t count; -}; -} // namespace bool D3D12ZPDQueryPool::EnsureInitialized( const ui::d3d12::D3D12Provider& provider, uint32_t requested_capacity, @@ -87,6 +80,7 @@ bool D3D12ZPDQueryPool::EnsureInitialized( capacity_ = requested_capacity; resolve_batch_pending_.assign(requested_capacity, 0); + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; free_indices_.clear(); @@ -101,6 +95,7 @@ bool D3D12ZPDQueryPool::EnsureInitialized( void D3D12ZPDQueryPool::Shutdown() { resolve_batch_pending_.clear(); + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; free_indices_.clear(); index_generations_.clear(); @@ -108,7 +103,9 @@ void D3D12ZPDQueryPool::Shutdown() { capacity_ = 0; if (readback_mapping_ && readback_buffer_) { - readback_buffer_->Unmap(0, nullptr); + // CPU never writes to this READBACK buffer — empty written range. + D3D12_RANGE written_range = {0, 0}; + readback_buffer_->Unmap(0, &written_range); } readback_mapping_ = nullptr; @@ -146,6 +143,8 @@ void D3D12ZPDQueryPool::ReleaseQueryIndex(uint32_t query_index, return; } + // Bump generation so a second release with the same generation is rejected. + ++index_generations_[query_index]; free_indices_.push_back(query_index); } @@ -184,6 +183,7 @@ void D3D12ZPDQueryPool::QueueQueryResolve(uint32_t query_index) { // the batch drains at EndSubmission. if (!resolve_batch_pending_[query_index]) { resolve_batch_pending_[query_index] = 1; + resolve_batch_indices_.push_back(query_index); ++resolve_batch_index_count_; } } @@ -199,22 +199,22 @@ void D3D12ZPDQueryPool::FlushResolveBatch( } if (!is_initialized()) { - std::fill(resolve_batch_pending_.begin(), resolve_batch_pending_.end(), 0); + for (uint32_t index : resolve_batch_indices_) { + resolve_batch_pending_[index] = 0; + } + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; return; } - std::vector ranges; + // Sort so we can coalesce contiguous indices into ranges, cutting down on + // ResolveQueryData calls which have considerable overhead. + std::sort(resolve_batch_indices_.begin(), resolve_batch_indices_.end()); - // Coalesce into contiguous ranges to cut down on ResolveQueryData calls, - // which have considerable overhead. + resolve_batch_ranges_.clear(); uint32_t range_start = 0; uint32_t range_count = 0; - for (uint32_t index = 0; index < capacity_; ++index) { - if (!resolve_batch_pending_[index]) { - continue; - } - + for (uint32_t index : resolve_batch_indices_) { if (range_count == 0) { range_start = index; range_count = 1; @@ -226,24 +226,23 @@ void D3D12ZPDQueryPool::FlushResolveBatch( continue; } - ranges.push_back({range_start, range_count}); + resolve_batch_ranges_.push_back({range_start, range_count}); range_start = index; range_count = 1; } if (range_count != 0) { - ranges.push_back({range_start, range_count}); + resolve_batch_ranges_.push_back({range_start, range_count}); } // Reset the batch. ENDs from later in this submission belong to the next. - std::fill(resolve_batch_pending_.begin(), resolve_batch_pending_.end(), 0); + for (uint32_t index : resolve_batch_indices_) { + resolve_batch_pending_[index] = 0; + } + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; - if (ranges.empty()) { - return; - } - - for (const ResolveRange& range : ranges) { + for (const ResolveRange& range : resolve_batch_ranges_) { deferred_command_list.D3DResolveQueryData( query_heap_.Get(), D3D12_QUERY_TYPE_OCCLUSION, range.start, range.count, readback_buffer_.Get(), range.start * sizeof(uint64_t)); diff --git a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h index 63e60dab8..3f741f936 100644 --- a/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h +++ b/src/xenia/gpu/d3d12/d3d12_zpd_query_pool.h @@ -13,6 +13,7 @@ #include #include +#include "xenia/gpu/command_processor.h" #include "xenia/ui/d3d12/d3d12_api.h" namespace xe { @@ -88,6 +89,11 @@ class D3D12ZPDQueryPool { std::vector index_generations_; std::vector resolve_batch_pending_; + // Active indices with resolve_batch_pending_[i] == 1, so flush iterates + // only the active entries instead of scanning the full capacity. + std::vector resolve_batch_indices_; + // Reusable scratch for coalesced contiguous ranges during flush. + std::vector resolve_batch_ranges_; uint32_t resolve_batch_index_count_ = 0; }; diff --git a/src/xenia/gpu/pm4_command_processor_implement.h b/src/xenia/gpu/pm4_command_processor_implement.h index af70c4121..5a424ad10 100644 --- a/src/xenia/gpu/pm4_command_processor_implement.h +++ b/src/xenia/gpu/pm4_command_processor_implement.h @@ -1252,7 +1252,8 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( } zpd_batch_last_record_ = report_record_base; - if (zpd_batch_run_ >= (repeated_orphan_end ? 16u : 4u)) { + if (zpd_batch_run_ >= (repeated_orphan_end ? kZPDBatchRunThresholdOrphanEnd + : kZPDBatchRunThreshold)) { // Don't try to mix real and fake results. zpd_batch_fake_ = true; zpd_batch_fake_count_ = 0; @@ -1279,6 +1280,11 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( return true; } if (is_begin_record) { + // Clear the record so the game knows the BEGIN was processed and + // stale sentinel data from a prior query lifetime doesn't persist. + if (report) { + std::memset(report, 0, sizeof(xe_gpu_depth_sample_counts)); + } COMMAND_PROCESSOR::BeginZPDReport(report_address); return true; } @@ -1300,7 +1306,9 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( } return true; } - return true; + // Address is neither BEGIN nor END (non-standard layout). Fall through + // to the fake path so the guest at least gets a result written rather + // than leaving the sentinel in place forever. } // Conventional fake fallback, which only touches records marked as pending. diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index f5689d913..148ca8aef 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -179,6 +179,18 @@ void VulkanCommandProcessor::InitializeShaderStorage( void VulkanCommandProcessor::RestoreEdramSnapshot(const void* snapshot) {} +void VulkanCommandProcessor::PrepareForWait() { + // Refresh completion data so PumpPendingRetire in the base class sees the + // latest GPU progress. + CheckSubmissionCompletionAndDeviceLoss(GetCompletedSubmission()); + CommandProcessor::PrepareForWait(); +} + +void VulkanCommandProcessor::ReturnFromWait() { + CheckSubmissionCompletionAndDeviceLoss(GetCompletedSubmission()); + CommandProcessor::ReturnFromWait(); +} + std::string VulkanCommandProcessor::GetWindowTitleText() const { std::ostringstream title; title << "Vulkan"; @@ -4675,7 +4687,7 @@ VkBuffer VulkanCommandProcessor::RequestReadbackBuffer(uint32_t size) { } void VulkanCommandProcessor::EnsureZPDQueryResources() { - if (GetZPDMode() == ZPDMode::kFake) { + if (GetZPDMode() == ZPDMode::kFake || !zpd_host_query_pool_) { return; } @@ -4800,11 +4812,16 @@ bool VulkanCommandProcessor::CloseZPDQuery(uint32_t host_index, bool VulkanCommandProcessor::DiscardZPDQuery(uint32_t host_index, uint32_t host_generation) { - // vkCmdEndQuery is invalid outside a render pass. - // CPU reset via vkResetQueryPool is enough. if (!in_render_pass_) { + // vkCmdEndQuery is invalid outside a render pass for occlusion queries. + // The deferred buffer still has a BeginQuery for this slot that will + // execute on the GPU. An immediate host vkResetQueryPool would race + // that pending BeginQuery and the slot could be reused before the GPU + // finishes, causing a double-active-query. Defer the release until the + // current submission (which contains the stale BeginQuery) completes. XELOGW("ZPD: Discard segment requested outside render pass"); - zpd_host_query_pool_->ReleaseQueryIndex(host_index, host_generation); + zpd_deferred_releases_.push_back( + {GetCurrentSubmission(), host_index, host_generation}); return true; } @@ -4827,6 +4844,17 @@ VulkanCommandProcessor::ZPDSubmissionBridge::GetState() const { void VulkanCommandProcessor::ZPDSubmissionBridge::PrepareReadback( uint64_t completed_submission) { + // Release any query slots whose discarding submission has completed. + while (!command_processor_.zpd_deferred_releases_.empty()) { + auto& entry = command_processor_.zpd_deferred_releases_.front(); + if (entry.submission > completed_submission) { + break; + } + command_processor_.zpd_host_query_pool_->ReleaseQueryIndex( + entry.query_index, entry.query_generation); + command_processor_.zpd_deferred_releases_.pop_front(); + } + // Invalidate CPU cache before reading results on non-coherent memory. if (!command_processor_.zpd_resolves_in_flight_.empty() && command_processor_.zpd_resolves_in_flight_.front().submission <= @@ -4840,10 +4868,6 @@ bool VulkanCommandProcessor::ZPDSubmissionBridge::EnsureProgress() { return false; } - if (!command_processor_.CanEndSubmissionImmediately() && - !command_processor_.pipeline_cache_->IsCreatingPipelines()) { - return false; - } if (!command_processor_.CanEndSubmissionImmediately()) { if (cvars::occlusion_query_log) { XELOGI( diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.h b/src/xenia/gpu/vulkan/vulkan_command_processor.h index d7b086707..e9f98ffa6 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.h +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.h @@ -156,6 +156,9 @@ class VulkanCommandProcessor final : public CommandProcessor { void RestoreEdramSnapshot(const void* snapshot) override; + void PrepareForWait() override; + void ReturnFromWait() override; + ui::vulkan::VulkanDevice* GetVulkanDevice() const { return static_cast( graphics_system_->provider()) @@ -469,7 +472,11 @@ class VulkanCommandProcessor final : public CommandProcessor { // Strict ZPD retirement may need to end the pass before blocking. void EnsureZPDQueryResources() override; void ShutdownZPDQueryResources() override { - zpd_host_query_pool_->Shutdown(); + zpd_resolves_in_flight_.clear(); + zpd_deferred_releases_.clear(); + if (zpd_host_query_pool_) { + zpd_host_query_pool_->Shutdown(); + } } bool IsZPDQueryPoolReady() const override { @@ -652,6 +659,16 @@ class VulkanCommandProcessor final : public CommandProcessor { std::unique_ptr zpd_host_query_pool_; + // Deferred query slot releases for discards that happen outside a render + // pass, where vkCmdEndQuery cannot be issued. The slot is held until the + // submission containing the stale BeginQuery completes on the GPU. + struct DeferredQueryRelease { + uint64_t submission; + uint32_t query_index; + uint32_t query_generation; + }; + std::deque zpd_deferred_releases_; + std::unique_ptr pipeline_cache_; std::unique_ptr texture_cache_; diff --git a/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.cc b/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.cc index d48841de4..3a5523f5b 100644 --- a/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.cc +++ b/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.cc @@ -19,13 +19,6 @@ namespace xe { namespace gpu { namespace vulkan { -namespace { - -struct ResolveRange { - uint32_t start; - uint32_t count; -}; -} // namespace bool VulkanZPDQueryPool::EnsureInitialized( const ui::vulkan::VulkanDevice* vulkan_device, uint32_t requested_capacity, @@ -141,6 +134,7 @@ bool VulkanZPDQueryPool::EnsureInitialized( index_generations_.assign(requested_capacity, 0); resolve_batch_pending_.assign(requested_capacity, 0); + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; return true; @@ -157,6 +151,7 @@ void VulkanZPDQueryPool::Shutdown() { free_indices_.clear(); index_generations_.clear(); resolve_batch_pending_.clear(); + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; return; } @@ -167,6 +162,7 @@ void VulkanZPDQueryPool::Shutdown() { free_indices_.clear(); index_generations_.clear(); resolve_batch_pending_.clear(); + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; capacity_ = 0; @@ -222,6 +218,9 @@ void VulkanZPDQueryPool::ReleaseQueryIndex(uint32_t query_index, return; } + // Bump generation so a second release with the same generation is rejected. + ++index_generations_[query_index]; + const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device_->functions(); const VkDevice device = vulkan_device_->device(); @@ -268,6 +267,7 @@ void VulkanZPDQueryPool::QueueQueryResolve(uint32_t query_index) { // Guard against duplicates within the same submission. if (!resolve_batch_pending_[query_index]) { resolve_batch_pending_[query_index] = 1; + resolve_batch_indices_.push_back(query_index); ++resolve_batch_index_count_; } } @@ -278,21 +278,22 @@ void VulkanZPDQueryPool::RecordResolveBatch(VkCommandBuffer command_buffer) { } if (!is_initialized()) { - std::fill(resolve_batch_pending_.begin(), resolve_batch_pending_.end(), 0); + for (uint32_t index : resolve_batch_indices_) { + resolve_batch_pending_[index] = 0; + } + resolve_batch_indices_.clear(); resolve_batch_index_count_ = 0; return; } - std::vector ranges; + // Sort so we can coalesce contiguous indices into ranges, cutting down on + // vkCmdCopyQueryPoolResults calls. + std::sort(resolve_batch_indices_.begin(), resolve_batch_indices_.end()); - // Coalesce into contiguous ranges - minimize vkCmdCopyQueryPoolResults calls. + resolve_batch_ranges_.clear(); uint32_t range_start = 0; uint32_t range_count = 0; - for (uint32_t index = 0; index < capacity_; ++index) { - if (!resolve_batch_pending_[index]) { - continue; - } - + for (uint32_t index : resolve_batch_indices_) { if (range_count == 0) { range_start = index; range_count = 1; @@ -304,28 +305,27 @@ void VulkanZPDQueryPool::RecordResolveBatch(VkCommandBuffer command_buffer) { continue; } - ranges.push_back({range_start, range_count}); + resolve_batch_ranges_.push_back({range_start, range_count}); range_start = index; range_count = 1; } if (range_count != 0) { - ranges.push_back({range_start, range_count}); + resolve_batch_ranges_.push_back({range_start, range_count}); } // Reset the batch. ENDs from later in this submission belong to the next. - std::fill(resolve_batch_pending_.begin(), resolve_batch_pending_.end(), 0); - resolve_batch_index_count_ = 0; - - if (ranges.empty()) { - return; + for (uint32_t index : resolve_batch_indices_) { + resolve_batch_pending_[index] = 0; } + resolve_batch_indices_.clear(); + resolve_batch_index_count_ = 0; const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device_->functions(); VkDeviceSize barrier_offset = VK_WHOLE_SIZE; VkDeviceSize barrier_end = 0; - for (const ResolveRange& range : ranges) { + for (const ResolveRange& range : resolve_batch_ranges_) { if (range.start >= capacity_) { continue; } diff --git a/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.h b/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.h index dd2cfeec4..f02bc1973 100644 --- a/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.h +++ b/src/xenia/gpu/vulkan/vulkan_zpd_query_pool.h @@ -13,6 +13,7 @@ #include #include +#include "xenia/gpu/command_processor.h" #include "xenia/ui/vulkan/vulkan_api.h" namespace xe { @@ -98,6 +99,11 @@ class VulkanZPDQueryPool { std::vector index_generations_; std::vector resolve_batch_pending_; + // Active indices with resolve_batch_pending_[i] == 1, so flush iterates + // only the active entries instead of scanning the full capacity. + std::vector resolve_batch_indices_; + // Reusable scratch for coalesced contiguous ranges during flush. + std::vector resolve_batch_ranges_; uint32_t resolve_batch_index_count_ = 0; }; diff --git a/src/xenia/gpu/xenos_zpd_report.h b/src/xenia/gpu/xenos_zpd_report.h index cded5da5d..e9eb91ea0 100644 --- a/src/xenia/gpu/xenos_zpd_report.h +++ b/src/xenia/gpu/xenos_zpd_report.h @@ -70,7 +70,7 @@ struct XenosZPDReport { // Boundary detection only looks at ZPass_A first, then ZFail_A. // Some titles (4D5307E8) have unique, non-zero values in the B fields, which - // aren't understand well enough to count them yet. + // aren't understood well enough to count them yet. // Proper support might need separate queries for both A & B. static bool HasPendingSentinel( const xenos::xe_gpu_depth_sample_counts* report) { diff --git a/src/xenia/ui/imgui_performance_dialog.cc b/src/xenia/ui/imgui_performance_dialog.cc index 26ac92fd1..72c7796c1 100644 --- a/src/xenia/ui/imgui_performance_dialog.cc +++ b/src/xenia/ui/imgui_performance_dialog.cc @@ -39,6 +39,7 @@ ImGuiPerformanceDialog::ImGuiPerformanceDialog( // Initialize highlight positions to match current selections resolve_highlight_ = readback_resolve_mode_; memexport_highlight_ = readback_memexport_mode_; + occlusion_query_highlight_ = occlusion_query_mode_; } void ImGuiPerformanceDialog::OnClose() { @@ -184,7 +185,7 @@ void ImGuiPerformanceDialog::OnOcclusionQueryChanged(int value) { command_processor->SetZPDMode(mode); const char* mode_names[] = {"Fake", "Fast", "Strict"}; - ShowNotification("Occlusion Query Mode", mode_names[value]); + ShowNotification("Occlusion Query Mode", mode_names[static_cast(mode)]); } void ImGuiPerformanceDialog::OnClearMemoryPageStateChanged(bool enabled) {