mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[GPU] ZPD occlusion query fixes and performance improvements
This commit is contained in:
@@ -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<PendingQueryResolve> 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<xenos::xe_gpu_depth_sample_counts*>(
|
||||
begin_record)
|
||||
: nullptr;
|
||||
if (!end_record) {
|
||||
return;
|
||||
}
|
||||
xenos::xe_gpu_depth_sample_counts* end =
|
||||
memory_->TranslatePhysical<xenos::xe_gpu_depth_sample_counts*>(
|
||||
end_record);
|
||||
|
||||
@@ -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<uint32_t, uint32_t> 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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<ui::d3d12::D3D12Provider*>(
|
||||
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 {
|
||||
|
||||
@@ -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<ResolveRange> 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));
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/ui/d3d12/d3d12_api.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -88,6 +89,11 @@ class D3D12ZPDQueryPool {
|
||||
std::vector<uint32_t> index_generations_;
|
||||
|
||||
std::vector<uint8_t> 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<uint32_t> resolve_batch_indices_;
|
||||
// Reusable scratch for coalesced contiguous ranges during flush.
|
||||
std::vector<ResolveRange> resolve_batch_ranges_;
|
||||
uint32_t resolve_batch_index_count_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<const ui::vulkan::VulkanProvider*>(
|
||||
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<VulkanZPDQueryPool> 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<DeferredQueryRelease> zpd_deferred_releases_;
|
||||
|
||||
std::unique_ptr<VulkanPipelineCache> pipeline_cache_;
|
||||
|
||||
std::unique_ptr<VulkanTextureCache> texture_cache_;
|
||||
|
||||
@@ -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<ResolveRange> 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;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/ui/vulkan/vulkan_api.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -98,6 +99,11 @@ class VulkanZPDQueryPool {
|
||||
std::vector<uint32_t> index_generations_;
|
||||
|
||||
std::vector<uint8_t> 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<uint32_t> resolve_batch_indices_;
|
||||
// Reusable scratch for coalesced contiguous ranges during flush.
|
||||
std::vector<ResolveRange> resolve_batch_ranges_;
|
||||
uint32_t resolve_batch_index_count_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<int>(mode)]);
|
||||
}
|
||||
|
||||
void ImGuiPerformanceDialog::OnClearMemoryPageStateChanged(bool enabled) {
|
||||
|
||||
Reference in New Issue
Block a user