[GPU] ZPD occlusion query fixes and performance improvements

This commit is contained in:
Herman S.
2026-04-06 01:14:33 +09:00
parent f4b124b379
commit 2d07d228ee
13 changed files with 195 additions and 77 deletions
+34 -9
View File
@@ -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);
+19 -1
View File
@@ -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;
+13 -5
View File
@@ -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 {
+24 -25
View File
@@ -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_;
+23 -23
View File
@@ -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;
};
+1 -1
View File
@@ -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) {
+2 -1
View File
@@ -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) {