mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
[GPU/ZPD] Inline XenosReportController into CommandProcessor
This commit is contained in:
@@ -582,15 +582,10 @@ bool CommandProcessor::Restore(ByteStream* stream) {
|
||||
|
||||
bool CommandProcessor::SetupContext() {
|
||||
ResetZPDState();
|
||||
zpd_report_controller_ = std::make_unique<XenosReportController>(
|
||||
&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<CommandProcessor*>(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<uint64_t>(report.begin_value) +
|
||||
static_cast<uint64_t>(delta_value);
|
||||
zpd_slot_values_[report.slot_base] =
|
||||
end_value > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(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 {
|
||||
|
||||
@@ -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<XenosReportController> zpd_report_controller_;
|
||||
std::unordered_map<XenosReportController::ReportHandle, ZPDReport>
|
||||
logical_zpd_reports_;
|
||||
ReportHandle zpd_next_report_handle_ = 1;
|
||||
std::unordered_map<uint32_t, uint64_t> zpd_slot_sequences_;
|
||||
std::unordered_map<uint32_t, uint32_t> zpd_slot_values_;
|
||||
std::unordered_map<ReportHandle, ZPDReport> logical_zpd_reports_;
|
||||
ActiveZPDSegment zpd_active_segment_{};
|
||||
std::deque<PendingQueryResolve> 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.
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <unordered_set>
|
||||
|
||||
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<PendingGuestCommit> pending_guest_commits;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<uint32_t>(pending_guest_commits.size());
|
||||
}
|
||||
|
||||
bool XenosReportController::HasQueuedWriteForAddress(
|
||||
uint32_t report_address) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return HasQueuedWriteForSlotLocked(
|
||||
XenosZPDReport::GetSlotBase(report_address));
|
||||
}
|
||||
|
||||
void XenosReportController::ResetStats() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
stats_ = {};
|
||||
}
|
||||
|
||||
void XenosReportController::ProcessReportWritesLocked(
|
||||
std::vector<PendingGuestCommit>& pending_guest_commits) {
|
||||
if (queued_report_writes_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_set<uint32_t> 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<uint64_t>(report_state.begin_value) +
|
||||
static_cast<uint64_t>(report_state.delta_value);
|
||||
|
||||
slot_values_[report_state.slot_base] =
|
||||
end_value > UINT32_MAX ? UINT32_MAX
|
||||
: static_cast<uint32_t>(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
|
||||
@@ -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 <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
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<PendingGuestCommit>& 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<QueuedReportWrite> queued_report_writes_;
|
||||
std::unordered_map<uint32_t, uint32_t> queued_report_write_slot_counts_;
|
||||
std::unordered_map<ReportHandle, LogicalReportState> logical_reports_;
|
||||
|
||||
// Bumped at each BeginReport. Stale generation writes get discarded.
|
||||
std::unordered_map<uint32_t, uint64_t> 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<uint32_t, uint32_t> slot_values_;
|
||||
|
||||
Stats stats_;
|
||||
ReportHandle next_report_handle_ = 1;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_XENOS_REPORT_CONTROLLER_H_
|
||||
Reference in New Issue
Block a user