From 0fdb5ff82433bc2a7965cb17d12ab5841510321e Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Mon, 6 Apr 2026 00:27:27 +0900 Subject: [PATCH] [GPU/ZPD] Inline XenosReportController into CommandProcessor --- src/xenia/gpu/command_processor.cc | 104 +++++---- src/xenia/gpu/command_processor.h | 41 ++-- .../gpu/d3d12/d3d12_command_processor.cc | 10 +- .../gpu/pm4_command_processor_implement.h | 7 +- .../gpu/vulkan/vulkan_command_processor.cc | 10 +- src/xenia/gpu/xenos_report_controller.cc | 215 ------------------ src/xenia/gpu/xenos_report_controller.h | 132 ----------- 7 files changed, 85 insertions(+), 434 deletions(-) delete mode 100644 src/xenia/gpu/xenos_report_controller.cc delete mode 100644 src/xenia/gpu/xenos_report_controller.h diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index f1e465dcb..dfbe5b82f 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -582,15 +582,10 @@ bool CommandProcessor::Restore(ByteStream* stream) { bool CommandProcessor::SetupContext() { ResetZPDState(); - zpd_report_controller_ = std::make_unique( - &CommandProcessor::ZPDReportCallback, this); return true; } -void CommandProcessor::ShutdownContext() { - ResetZPDState(); - zpd_report_controller_.reset(); -} +void CommandProcessor::ShutdownContext() { ResetZPDState(); } void CommandProcessor::InitializeRingBuffer(uint32_t ptr, uint32_t size_log2) { read_ptr_index_ = 0; @@ -1072,18 +1067,24 @@ bool CommandProcessor::BeginZPDReport(uint32_t report_address) { uint32_t slot_base = XenosZPDReport::GetSlotBase(report_address); uint32_t begin_record = XenosZPDReport::GetBeginRecordBase(slot_base); uint32_t end_record = XenosZPDReport::GetEndRecordBase(slot_base); - XenosReportController::BeginReportResult begin_report_result = - zpd_report_controller_->BeginReport(report_address, begin_record); - XenosReportController::ReportHandle report_handle = - begin_report_result.report_handle; - if (report_handle == XenosReportController::kInvalidReportHandle) { + if (!slot_base) { return false; } + // Bump slot sequence — invalidates pending writes from prior lifetime. + uint64_t slot_sequence_id = ++zpd_slot_sequences_[slot_base]; + + ReportHandle report_handle = zpd_next_report_handle_++; + if (report_handle == kInvalidReportHandle) { + report_handle = zpd_next_report_handle_++; + } + ZPDReport& logical = logical_zpd_reports_[report_handle]; + logical.slot_base = slot_base; + logical.slot_sequence_id = slot_sequence_id; logical.begin_record = begin_record; logical.end_record = end_record; - logical.begin_value = begin_report_result.begin_value; + logical.begin_value = zpd_slot_values_[slot_base]; logical.accumulated_samples = 0; logical.last_segment_end_submission = 0; logical.pending_segments = 0; @@ -1118,7 +1119,7 @@ bool CommandProcessor::EndZPDReport(uint32_t report_address, return false; } - XenosReportController::ReportHandle report_handle = + CommandProcessor::ReportHandle report_handle = zpd_active_segment_.report_handle; uint32_t stored_end_record = zpd_active_segment_.end_record; uint32_t report_record_base = XenosZPDReport::GetRecordBase(report_address); @@ -1186,10 +1187,9 @@ bool CommandProcessor::EndZPDReport(uint32_t report_address, } } - zpd_report_controller_->QueueReportWrite(report_record_base, report_handle); if (resolved_immediately) { - zpd_report_controller_->SetReportResolved(report_handle, final_value); - zpd_report_controller_->RetireReports(); + CommitZPDReport(logical, final_value); + logical_zpd_reports_.erase(it); } bool has_cross_slot_end = @@ -1208,8 +1208,7 @@ bool CommandProcessor::EndZPDReport(uint32_t report_address, } else if (!resolved_immediately) { PumpQueryResolves(); - if (zpd_report_controller_->HasQueuedWriteForAddress(report_record_base) && - zpd_pending_retire_handle_ != report_handle) { + if (zpd_pending_retire_handle_ != report_handle) { zpd_pending_retire_handle_ = report_handle; zpd_pending_retire_stalls_ = 0; } @@ -1375,18 +1374,15 @@ void CommandProcessor::DrainQueryResolves(uint64_t completed_submission) { } fast_zpd_report_cached_values_[logical.end_record] = final_value; } - zpd_report_controller_->SetReportResolved(resolve.report_handle, - final_value); - any_resolved = true; + if (IsZPDReportCurrent(logical)) { + CommitZPDReport(logical, final_value); + } + logical_zpd_reports_.erase(it); } } } } } - - if (any_resolved) { - zpd_report_controller_->RetireReports(); - } } void CommandProcessor::PumpQueryResolves() { @@ -1408,7 +1404,7 @@ void CommandProcessor::PumpQueryResolves() { } bool CommandProcessor::AwaitQueryResolve( - XenosReportController::ReportHandle report_handle) { + CommandProcessor::ReportHandle report_handle) { if (GetZPDMode() == ZPDMode::kFake) { return false; } @@ -1430,7 +1426,12 @@ bool CommandProcessor::AwaitQueryResolve( uint64_t wait_for_submission = it->second.last_segment_end_submission; if (wait_for_submission == 0) { if (it->second.pending_segments == 0 && it->second.ended) { - zpd_report_controller_->RetireReports(); + // Already fully resolved — commit if the slot is still current. + if (IsZPDReportCurrent(it->second)) { + CommitZPDReport(it->second, + NormalizeSampleCount(it->second.accumulated_samples)); + } + logical_zpd_reports_.erase(it); return true; } return false; @@ -1466,16 +1467,15 @@ bool CommandProcessor::AwaitQueryResolve( void CommandProcessor::PumpPendingRetire() { if (zpd_batch_fake_) { - zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; + zpd_pending_retire_handle_ = kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; return; } - XenosReportController::ReportHandle handle_to_await = - zpd_pending_retire_handle_; + CommandProcessor::ReportHandle handle_to_await = zpd_pending_retire_handle_; if (AwaitQueryResolve(handle_to_await)) { - zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; + zpd_pending_retire_handle_ = kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; return; } @@ -1484,7 +1484,7 @@ void CommandProcessor::PumpPendingRetire() { if (logical_report == logical_zpd_reports_.end()) { // If the report is already gone it retired through another path. // Clear so we don't spin on a handle that no longer exists. - zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; + zpd_pending_retire_handle_ = kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; return; } @@ -1498,14 +1498,13 @@ 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); + // Write the cached delta to guest memory to avoid a sudden occlusion flash. + if (IsZPDReportCurrent(logical_report->second)) { + CommitZPDReport(logical_report->second, + logical_report->second.cached_delta); } logical_zpd_reports_.erase(logical_report); - zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; + zpd_pending_retire_handle_ = kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; } } @@ -1530,20 +1529,25 @@ void CommandProcessor::WriteZPDReport(uint32_t begin_record, write_begin_record); } -void CommandProcessor::ZPDReportCallback( - XenosReportController::ReportHandle report_handle, uint32_t slot_base, - uint32_t begin_record, uint32_t begin_value, uint32_t delta_value, - void* callback_context) { - CommandProcessor* processor = - reinterpret_cast(callback_context); +void CommandProcessor::CommitZPDReport(ZPDReport& report, + uint32_t delta_value) { + uint32_t end_record = XenosZPDReport::GetEndRecordBase(report.slot_base); + WriteZPDReport(report.begin_record, end_record, report.begin_value, + delta_value, report.begin_record != 0); - uint32_t end_record = XenosZPDReport::GetEndRecordBase(slot_base); + // Advance running total so the next BeginReport on this slot picks up + // the correct begin_value. + uint64_t end_value = static_cast(report.begin_value) + + static_cast(delta_value); + zpd_slot_values_[report.slot_base] = + end_value > UINT32_MAX ? UINT32_MAX : static_cast(end_value); +} - // begin_record comes from the controller. Erase the CP-side entry. - processor->logical_zpd_reports_.erase(report_handle); - - processor->WriteZPDReport(begin_record, end_record, begin_value, delta_value, - begin_record != 0); +bool CommandProcessor::IsZPDReportCurrent(const ZPDReport& report) const { + auto seq_it = zpd_slot_sequences_.find(report.slot_base); + uint64_t current_seq = + seq_it != zpd_slot_sequences_.end() ? seq_it->second : 0; + return current_seq == report.slot_sequence_id; } uint32_t CommandProcessor::NormalizeSampleCount(uint64_t samples) const { diff --git a/src/xenia/gpu/command_processor.h b/src/xenia/gpu/command_processor.h index b2c63822a..debf53a1f 100644 --- a/src/xenia/gpu/command_processor.h +++ b/src/xenia/gpu/command_processor.h @@ -26,7 +26,6 @@ #include "xenia/gpu/register_file.h" #include "xenia/gpu/trace_writer.h" #include "xenia/gpu/xenos.h" -#include "xenia/gpu/xenos_report_controller.h" #include "xenia/gpu/xenos_zpd_report.h" #include "xenia/kernel/xthread.h" #include "xenia/memory.h" @@ -117,6 +116,10 @@ enum class GammaRampType { }; class CommandProcessor { + public: + using ReportHandle = uint32_t; + static constexpr ReportHandle kInvalidReportHandle = 0; + protected: RingBuffer reader_; // chrispy: instead of having ringbuffer on stack, have it near @@ -332,9 +335,11 @@ class CommandProcessor { // Raw host count across all segments, normalized at retirement. uint64_t accumulated_samples = 0; uint64_t last_segment_end_submission = 0; + uint64_t slot_sequence_id = 0; + uint32_t slot_base = 0; uint32_t begin_record = 0; uint32_t end_record = 0; - // Snapshotted from controller at BEGIN. + // Snapshotted at BEGIN from zpd_slot_values_. uint32_t begin_value = 0; uint32_t pending_segments = 0; // Last known delta. Carried forward on forced close so slot doesn't @@ -345,8 +350,7 @@ class CommandProcessor { // TODO(boma): Replace with a map keyed by slot_base for concurrent slots. struct ActiveZPDSegment { - XenosReportController::ReportHandle report_handle = - XenosReportController::kInvalidReportHandle; + ReportHandle report_handle = kInvalidReportHandle; uint32_t slot_base = 0; uint32_t begin_record = 0; uint32_t end_record = 0; @@ -361,8 +365,7 @@ class CommandProcessor { uint64_t submission = 0; uint32_t query_index = UINT32_MAX; uint32_t query_generation = 0; - XenosReportController::ReportHandle report_handle = - XenosReportController::kInvalidReportHandle; + ReportHandle report_handle = kInvalidReportHandle; }; // Logged by the backend every 100 frames if ZPD logging cvar is true. @@ -445,7 +448,7 @@ class CommandProcessor { // Blocks on the fence for report_handle's last segment, then pumps. // Can't be called from EVENT_WRITE_ZPD or the retirement callback. - bool AwaitQueryResolve(XenosReportController::ReportHandle report_handle); + bool AwaitQueryResolve(ReportHandle report_handle); // Writes guest report with begin_value read from guest memory. // Orphan END path only when no controller snapshot is available. @@ -460,13 +463,17 @@ class CommandProcessor { // Divides host count by draw resolution scale. uint32_t NormalizeSampleCount(uint64_t samples) const; - static void ZPDReportCallback( - XenosReportController::ReportHandle report_handle, uint32_t slot_base, - uint32_t begin_record, uint32_t begin_value, uint32_t delta_value, - void* callback_context); + // Writes the final report to guest memory and advances the slot running + // total. Called when a report fully resolves or is abandoned. + void CommitZPDReport(ZPDReport& report, uint32_t delta_value); + // Checks that the report's slot sequence is still current (not reused). + bool IsZPDReportCurrent(const ZPDReport& report) const; void ResetZPDState() { zpd_active_segment_ = {}; + zpd_next_report_handle_ = 1; + zpd_slot_sequences_.clear(); + zpd_slot_values_.clear(); logical_zpd_reports_.clear(); fast_zpd_report_cached_values_.clear(); zpd_batch_fake_ = false; @@ -475,7 +482,7 @@ class CommandProcessor { zpd_batch_last_record_ = 0; zpd_batch_run_ = 0; fake_zpd_sample_count_ = 0; - zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; + zpd_pending_retire_handle_ = kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; zpd_resolves_in_flight_.clear(); } @@ -515,9 +522,10 @@ class CommandProcessor { GraphicsSystem* graphics_system_ = nullptr; RegisterFile* XE_RESTRICT register_file_ = nullptr; - std::unique_ptr zpd_report_controller_; - std::unordered_map - logical_zpd_reports_; + ReportHandle zpd_next_report_handle_ = 1; + std::unordered_map zpd_slot_sequences_; + std::unordered_map zpd_slot_values_; + std::unordered_map logical_zpd_reports_; ActiveZPDSegment zpd_active_segment_{}; std::deque zpd_resolves_in_flight_; @@ -534,8 +542,7 @@ class CommandProcessor { uint32_t zpd_batch_run_ = 0; // Strict mode defers guest completion until the queued END has retired. - XenosReportController::ReportHandle zpd_pending_retire_handle_ = - XenosReportController::kInvalidReportHandle; + ReportHandle zpd_pending_retire_handle_ = kInvalidReportHandle; uint32_t zpd_pending_retire_stalls_ = 0; // Set by the backend when resolution scale changes. diff --git a/src/xenia/gpu/d3d12/d3d12_command_processor.cc b/src/xenia/gpu/d3d12/d3d12_command_processor.cc index 1320847e2..04bd7c9d0 100644 --- a/src/xenia/gpu/d3d12/d3d12_command_processor.cc +++ b/src/xenia/gpu/d3d12/d3d12_command_processor.cc @@ -28,7 +28,6 @@ #include "xenia/gpu/packet_disassembler.h" #include "xenia/gpu/registers.h" #include "xenia/gpu/xenos.h" -#include "xenia/gpu/xenos_report_controller.h" #include "xenia/kernel/kernel_state.h" #include "xenia/ui/d3d12/d3d12_presenter.h" #include "xenia/ui/d3d12/d3d12_util.h" @@ -3908,20 +3907,15 @@ bool D3D12CommandProcessor::BeginSubmission(bool is_guest_command) { // Log guest ZPD report stats every 100 frames. if (GetZPDMode() != ZPDMode::kFake && cvars::occlusion_query_log && zpd_host_query_pool_ && zpd_host_query_pool_->capacity() && - zpd_report_controller_ && frame_current_ - zpd_stats_.last_log_frame >= 100) { - XenosReportController::Stats report_stats = - zpd_report_controller_->stats(); XELOGI( "Occlusion Query Stats (last 100 frames): " "LogicalBegun={}, LogicalEnded={}, SegBegun={}, SegEnded={}, " - "WritesRetired={}, PoolExhausted={}, Failed={}", + "PoolExhausted={}, Failed={}", zpd_stats_.logical_begun, zpd_stats_.logical_ended, zpd_stats_.segments_begun, zpd_stats_.segments_ended, - report_stats.writes_retired, zpd_stats_.pool_exhausted, - zpd_stats_.failed); + zpd_stats_.pool_exhausted, zpd_stats_.failed); - zpd_report_controller_->ResetStats(); zpd_stats_.Reset(frame_current_); } diff --git a/src/xenia/gpu/pm4_command_processor_implement.h b/src/xenia/gpu/pm4_command_processor_implement.h index 5a424ad10..d1b9779f2 100644 --- a/src/xenia/gpu/pm4_command_processor_implement.h +++ b/src/xenia/gpu/pm4_command_processor_implement.h @@ -1257,7 +1257,7 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( // Don't try to mix real and fake results. zpd_batch_fake_ = true; zpd_batch_fake_count_ = 0; - zpd_pending_retire_handle_ = XenosReportController::kInvalidReportHandle; + zpd_pending_retire_handle_ = CommandProcessor::kInvalidReportHandle; zpd_pending_retire_stalls_ = 0; XELOGI( "ZPD: Batched occlusion query pattern detected, falling back to " @@ -1271,8 +1271,7 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( zpd_batch_run_ = 0; } - if (COMMAND_PROCESSOR::GetZPDMode() != ZPDMode::kFake && - zpd_report_controller_) { + if (COMMAND_PROCESSOR::GetZPDMode() != ZPDMode::kFake) { if (logical_active && is_end_record) { if (slot_base == zpd_active_segment_.slot_base) { COMMAND_PROCESSOR::EndZPDReport(report_address, false); @@ -1302,7 +1301,7 @@ bool COMMAND_PROCESSOR::ExecutePacketType3_EVENT_WRITE_ZPD( cached_delta, false); } else { // In strict mode, just pump in case a previous report has resolved. - zpd_report_controller_->RetireReports(); + COMMAND_PROCESSOR::PumpQueryResolves(); } return true; } diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 148ca8aef..fd7a81a8a 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -32,7 +32,6 @@ #include "xenia/gpu/vulkan/vulkan_shared_memory.h" #include "xenia/gpu/vulkan/vulkan_zpd_query_pool.h" #include "xenia/gpu/xenos.h" -#include "xenia/gpu/xenos_report_controller.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/user_module.h" #include "xenia/ui/vulkan/vulkan_presenter.h" @@ -5097,20 +5096,15 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) { // Log guest ZPD report stats every 100 frames. if (GetZPDMode() != ZPDMode::kFake && cvars::occlusion_query_log && zpd_host_query_pool_ && zpd_host_query_pool_->capacity() && - zpd_report_controller_ && frame_current_ - zpd_stats_.last_log_frame >= 100) { - XenosReportController::Stats report_stats = - zpd_report_controller_->stats(); XELOGI( "Occlusion Query Stats (last 100 frames): " "LogicalBegun={}, LogicalEnded={}, SegBegun={}, SegEnded={}, " - "WritesRetired={}, PoolExhausted={}, Failed={}", + "PoolExhausted={}, Failed={}", zpd_stats_.logical_begun, zpd_stats_.logical_ended, zpd_stats_.segments_begun, zpd_stats_.segments_ended, - report_stats.writes_retired, zpd_stats_.pool_exhausted, - zpd_stats_.failed); + zpd_stats_.pool_exhausted, zpd_stats_.failed); - zpd_report_controller_->ResetStats(); zpd_stats_.Reset(frame_current_); } diff --git a/src/xenia/gpu/xenos_report_controller.cc b/src/xenia/gpu/xenos_report_controller.cc deleted file mode 100644 index 2f5ba139e..000000000 --- a/src/xenia/gpu/xenos_report_controller.cc +++ /dev/null @@ -1,215 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2026 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/gpu/xenos_report_controller.h" - -#include - -#include "xenia/base/logging.h" -#include "xenia/gpu/gpu_flags.h" -#include "xenia/gpu/xenos_zpd_report.h" - -namespace xe { -namespace gpu { - -XenosReportController::BeginReportResult XenosReportController::BeginReport( - uint32_t report_address, uint32_t begin_record) { - std::lock_guard lock(mutex_); - - uint32_t slot_base = XenosZPDReport::GetSlotBase(report_address); - if (!slot_base) { - return {}; - } - - // Bumping before state creation invalidates pending writes from old lifetime. - uint64_t slot_sequence_id = ++slot_sequences_[slot_base]; - - ReportHandle report_handle = next_report_handle_++; - if (report_handle == kInvalidReportHandle) { - // 0 is reserved as the invalid handle. Skip over it if the counter wraps. - report_handle = next_report_handle_++; - } - - LogicalReportState& report_state = logical_reports_[report_handle]; - - report_state.slot_base = slot_base; - report_state.begin_record = begin_record; - report_state.slot_sequence_id = slot_sequence_id; - report_state.begin_value = slot_values_[slot_base]; - report_state.resolved = false; - report_state.delta_value = 0; - - return {report_handle, report_state.begin_value}; -} - -void XenosReportController::QueueReportWrite(uint32_t report_address, - ReportHandle report_handle) { - std::lock_guard lock(mutex_); - - uint32_t slot_base = XenosZPDReport::GetSlotBase(report_address); - if (!slot_base) { - return; - } - - auto existing_report = logical_reports_.find(report_handle); - if (existing_report == logical_reports_.end()) { - return; - } - - QueuedReportWrite queued_write; - queued_write.report_handle = report_handle; - queued_write.slot_base = slot_base; - - // Appended to back, preserves FIFO so a later write can't jump an earlier one - queued_report_writes_.push_back(queued_write); - ++queued_report_write_slot_counts_[queued_write.slot_base]; -} - -void XenosReportController::SetReportResolved(ReportHandle report_handle, - uint32_t delta_value) { - std::lock_guard lock(mutex_); - - auto existing_report = logical_reports_.find(report_handle); - if (existing_report == logical_reports_.end()) { - return; - } - - existing_report->second.resolved = true; - - existing_report->second.delta_value = delta_value; -} - -uint32_t XenosReportController::RetireReports() { - std::vector pending_guest_commits; - { - std::lock_guard lock(mutex_); - ProcessReportWritesLocked(pending_guest_commits); - } - - // Fire callbacks outside the lock. The callback writes directly to guest - // memory and may call back into the backend. - if (commit_guest_report_callback_) { - for (const PendingGuestCommit& commit : pending_guest_commits) { - commit_guest_report_callback_(commit.report_handle, commit.slot_base, - commit.begin_record, commit.begin_value, - commit.delta_value, callback_context_); - } - } - - return static_cast(pending_guest_commits.size()); -} - -bool XenosReportController::HasQueuedWriteForAddress( - uint32_t report_address) const { - std::lock_guard lock(mutex_); - return HasQueuedWriteForSlotLocked( - XenosZPDReport::GetSlotBase(report_address)); -} - -void XenosReportController::ResetStats() { - std::lock_guard lock(mutex_); - stats_ = {}; -} - -void XenosReportController::ProcessReportWritesLocked( - std::vector& pending_guest_commits) { - if (queued_report_writes_.empty()) { - return; - } - - std::unordered_set blocked_slot_bases; - - auto remove_queued_write = [this](uint32_t slot_base) { - auto queued_write_count = queued_report_write_slot_counts_.find(slot_base); - if (queued_write_count == queued_report_write_slot_counts_.end()) { - return; - } - if (--queued_write_count->second == 0) { - queued_report_write_slot_counts_.erase(queued_write_count); - } - }; - - auto write_it = queued_report_writes_.begin(); - while (write_it != queued_report_writes_.end()) { - QueuedReportWrite& queued_write = *write_it; - - auto existing_report = logical_reports_.find(queued_write.report_handle); - if (existing_report == logical_reports_.end()) { - if (cvars::occlusion_query_log) { - XELOGI( - "ZPD: Controller ProcessReportWritesLocked drop missing handle={}", - queued_write.report_handle); - } - write_it = queued_report_writes_.erase(write_it); - remove_queued_write(queued_write.slot_base); - } else { - LogicalReportState& report_state = existing_report->second; - - // Block unresolved later writes in the same slot, preserving FIFO order. - if (!report_state.resolved) { - blocked_slot_bases.insert(queued_write.slot_base); - ++write_it; - } else if (blocked_slot_bases.count(queued_write.slot_base)) { - if (cvars::occlusion_query_log) { - XELOGI( - "ZPD: Controller ProcessReportWritesLocked blocked handle={} " - "slot=0x{:08X}", - queued_write.report_handle, queued_write.slot_base); - } - ++write_it; - } else { - auto seq_it = slot_sequences_.find(report_state.slot_base); - uint64_t current_seq = - seq_it != slot_sequences_.end() ? seq_it->second : 0; - - // The slot has been reused since this write was queued. Discard. - if (current_seq != report_state.slot_sequence_id) { - if (cvars::occlusion_query_log) { - XELOGI( - "ZPD: Controller ProcessReportWritesLocked stale handle={} " - "slot=0x{:08X} report_seq={} current_seq={}", - queued_write.report_handle, queued_write.slot_base, - report_state.slot_sequence_id, current_seq); - } - write_it = queued_report_writes_.erase(write_it); - remove_queued_write(queued_write.slot_base); - logical_reports_.erase(existing_report); - } else { - pending_guest_commits.push_back( - {queued_write.report_handle, queued_write.slot_base, - report_state.begin_record, report_state.begin_value, - report_state.delta_value}); - - // Advance running total so next BeginReport picks up the right - // begin_value. - uint64_t end_value = static_cast(report_state.begin_value) + - static_cast(report_state.delta_value); - - slot_values_[report_state.slot_base] = - end_value > UINT32_MAX ? UINT32_MAX - : static_cast(end_value); - - write_it = queued_report_writes_.erase(write_it); - remove_queued_write(queued_write.slot_base); - logical_reports_.erase(existing_report); - ++stats_.writes_retired; - } - } - } - } -} - -bool XenosReportController::HasQueuedWriteForSlotLocked( - uint32_t slot_base) const { - return queued_report_write_slot_counts_.find(slot_base) != - queued_report_write_slot_counts_.end(); -} - -} // namespace gpu -} // namespace xe diff --git a/src/xenia/gpu/xenos_report_controller.h b/src/xenia/gpu/xenos_report_controller.h deleted file mode 100644 index daffdf77a..000000000 --- a/src/xenia/gpu/xenos_report_controller.h +++ /dev/null @@ -1,132 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2026 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_GPU_XENOS_REPORT_CONTROLLER_H_ -#define XENIA_GPU_XENOS_REPORT_CONTROLLER_H_ - -#include -#include -#include -#include -#include - -namespace xe { -namespace gpu { - -// Sequences ZPD (occlusion query) report writebacks. -// -// Guest report memory is updated directly when the report event fires. On the -// host, results take at least a command list boundary to land, often several -// frames. The guest has usually reused the slot by then. Results can also span -// multiple host query segments across submissions. -// -// Two rules at retirement: FIFO within a slot (a later write can't jump an -// older pending one), and stale generation discard (each BeginReport bumps -// the slot's sequence. Writes with a stale sequence are dropped so old host -// results don't reach a slot that's already been recycled). -// -// TODO(boma): QueryBatch Lock/Unlock is a separate model - not supported yet. -class XenosReportController { - public: - using ReportHandle = uint32_t; - static constexpr ReportHandle kInvalidReportHandle = 0; - - struct BeginReportResult { - ReportHandle report_handle = kInvalidReportHandle; - uint32_t begin_value = 0; - }; - - using CommitGuestReportCallback = void (*)( - ReportHandle report_handle, uint32_t slot_base, uint32_t begin_record, - uint32_t begin_value, uint32_t delta_value, void* callback_context); - - explicit XenosReportController( - CommitGuestReportCallback commit_guest_report_callback, - void* callback_context) - : commit_guest_report_callback_(commit_guest_report_callback), - callback_context_(callback_context) {} - - // Bumps the slot sequence (invalidates pending writes from prior lifetime) - // and snapshots begin_value from slot_values_. - BeginReportResult BeginReport(uint32_t report_address, uint32_t begin_record); - - // Queues a pending write. Deque order preserves FIFO within the slot. - void QueueReportWrite(uint32_t report_address, ReportHandle report_handle); - - void SetReportResolved(ReportHandle report_handle, uint32_t delta_value); - - // Retires resolved writes in FIFO order and fires the writeback callback. - // Callback runs outside the lock. - uint32_t RetireReports(); - - // Used by strict mode to check if a result is still pending. - bool HasQueuedWriteForAddress(uint32_t report_address) const; - - struct Stats { - uint64_t writes_retired = 0; - }; - - const Stats& stats() const { return stats_; } - void ResetStats(); - - private: - // Built under lock, flushed outside so writes to guest don't hold it. - struct PendingGuestCommit { - ReportHandle report_handle = kInvalidReportHandle; - uint32_t slot_base = 0; - uint32_t begin_record = 0; - uint32_t begin_value = 0; - uint32_t delta_value = 0; - }; - - struct LogicalReportState { - uint32_t slot_base = 0; - uint32_t begin_record = 0; - uint64_t slot_sequence_id = 0; - // Snapshotted at BEGIN, not at retirement. Guest will have reused by then. - uint32_t begin_value = 0; - uint32_t delta_value = 0; - bool resolved = false; - }; - - // Maintains FIFO ordering within the slot. - struct QueuedReportWrite { - ReportHandle report_handle = kInvalidReportHandle; - uint32_t slot_base = 0; - }; - - void ProcessReportWritesLocked( - std::vector& pending_guest_commits); - - bool HasQueuedWriteForSlotLocked(uint32_t slot_base) const; - - CommitGuestReportCallback commit_guest_report_callback_ = nullptr; - void* callback_context_ = nullptr; - - mutable std::mutex mutex_; - - std::deque queued_report_writes_; - std::unordered_map queued_report_write_slot_counts_; - std::unordered_map logical_reports_; - - // Bumped at each BeginReport. Stale generation writes get discarded. - std::unordered_map slot_sequences_; - - // Running total per slot. Seeded into begin_value at BEGIN, advanced at - // each retirement so counts accumulate, same as real hardware. - std::unordered_map slot_values_; - - Stats stats_; - ReportHandle next_report_handle_ = 1; -}; - -} // namespace gpu -} // namespace xe - -#endif // XENIA_GPU_XENOS_REPORT_CONTROLLER_H_