mirror of
https://github.com/sal063/AC6_recomp
synced 2026-07-08 14:36:57 -04:00
Fix invisible missile/jet trails (stale vertex-buffer residency)
Missile/jet trail position history is a fixed guest-address ring, so its vertex fetch constants never change and the command processor's vertex_buffer_states_ address cache skipped RequestRange forever, the GPU copy of the trail history froze at zero (trails invisible) while CPU memory was correct. Register a physical-memory invalidation callback that flags when the CPU writes watched GPU-visible memory, and drop the cached vertex-buffer states at the next draw so the ring's pages get re-uploaded. Also notify those callbacks from the access-violation page-recovery path (xmemory), since a recovered page becomes read-write and would otherwise never fault again to report the write. Gated by ac6_fix_trails (default ON).
This commit is contained in:
@@ -437,6 +437,12 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||
void InvalidateAllVertexBufferResidency();
|
||||
void InvalidateVertexBufferResidency(uint32_t vfetch_index);
|
||||
void InvalidateVertexBufferResidencyRange(uint32_t first_vfetch, uint32_t last_vfetch);
|
||||
// Registered with the memory system: flags that a CPU write invalidated
|
||||
// watched GPU-visible memory, so the cached vertex buffer states must be
|
||||
// dropped before the next draw (otherwise fixed-address buffers never
|
||||
// re-request their ranges and their GPU copies go permanently stale).
|
||||
static std::pair<uint32_t, uint32_t> VertexBufferMemoryInvalidationCallbackThunk(
|
||||
void* context_ptr, uint32_t physical_address_start, uint32_t length, bool exact_range);
|
||||
|
||||
void WriteGammaRampSRV(bool is_pwl, D3D12_CPU_DESCRIPTOR_HANDLE handle) const;
|
||||
|
||||
@@ -687,6 +693,8 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||
};
|
||||
std::array<VertexBufferState, 96> vertex_buffer_states_{};
|
||||
uint64_t vertex_buffers_in_sync_[2] = {};
|
||||
std::atomic<bool> vertex_buffer_memory_invalidated_{false};
|
||||
void* vertex_buffer_memory_invalidation_callback_handle_ = nullptr;
|
||||
|
||||
std::atomic<bool> pix_capture_requested_ = false;
|
||||
bool pix_capturing_;
|
||||
|
||||
@@ -114,4 +114,7 @@ REXCVAR_DECLARE(std::string, render_target_path_d3d12);
|
||||
REXCVAR_DECLARE(bool, d3d12_readback_memexport);
|
||||
REXCVAR_DECLARE(bool, d3d12_readback_resolve);
|
||||
|
||||
// AC6 game-specific fixes
|
||||
REXCVAR_DECLARE(bool, ac6_fix_trails);
|
||||
|
||||
#define XE_GPU_FINE_GRAINED_DRAW_SCOPES 1
|
||||
|
||||
@@ -225,6 +225,15 @@ void D3D12CommandProcessor::InvalidateVertexBufferResidency(uint32_t vfetch_inde
|
||||
vertex_buffers_in_sync_[vfetch_index >> 6] &= ~(uint64_t(1) << (vfetch_index & 63));
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> D3D12CommandProcessor::VertexBufferMemoryInvalidationCallbackThunk(
|
||||
void* context_ptr, uint32_t physical_address_start, uint32_t length, bool exact_range) {
|
||||
// Runs on the writing (guest) thread - just flag; the GPU thread drops its
|
||||
// cached vertex buffer states at the next draw.
|
||||
auto command_processor = static_cast<D3D12CommandProcessor*>(context_ptr);
|
||||
command_processor->vertex_buffer_memory_invalidated_.store(true, std::memory_order_release);
|
||||
return std::make_pair(uint32_t(0), UINT32_MAX);
|
||||
}
|
||||
|
||||
void D3D12CommandProcessor::InvalidateVertexBufferResidencyRange(uint32_t first_vfetch,
|
||||
uint32_t last_vfetch) {
|
||||
if (first_vfetch > last_vfetch) {
|
||||
@@ -1098,6 +1107,16 @@ bool D3D12CommandProcessor::SetupContext() {
|
||||
REXGPU_ERROR("Failed to initialize shared memory");
|
||||
return false;
|
||||
}
|
||||
// CPU writes to watched GPU-visible memory must drop the cached vertex
|
||||
// buffer states: the vertex_buffer_states_ address cache otherwise skips
|
||||
// RequestRanges forever for buffers whose fetch constants never change
|
||||
// (e.g. AC6's fixed-address trail history ring), so pages invalidated by
|
||||
// the CPU would never be re-uploaded to the GPU copy.
|
||||
if (REXCVAR_GET(ac6_fix_trails)) {
|
||||
vertex_buffer_memory_invalidation_callback_handle_ =
|
||||
memory_->RegisterPhysicalMemoryInvalidationCallback(
|
||||
VertexBufferMemoryInvalidationCallbackThunk, this);
|
||||
}
|
||||
|
||||
// Initialize the render target cache before configuring binding - need to
|
||||
// know if using rasterizer-ordered views for the bindless root signature.
|
||||
@@ -1905,6 +1924,11 @@ void D3D12CommandProcessor::ShutdownContext() {
|
||||
|
||||
render_target_cache_.reset();
|
||||
|
||||
if (vertex_buffer_memory_invalidation_callback_handle_) {
|
||||
memory_->UnregisterPhysicalMemoryInvalidationCallback(
|
||||
vertex_buffer_memory_invalidation_callback_handle_);
|
||||
vertex_buffer_memory_invalidation_callback_handle_ = nullptr;
|
||||
}
|
||||
shared_memory_.reset();
|
||||
|
||||
deferred_command_list_.Reset();
|
||||
@@ -2932,6 +2956,14 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint3
|
||||
// Must not call anything that can change the descriptor heap from now on!
|
||||
|
||||
// Ensure vertex buffers are resident.
|
||||
if (vertex_buffer_memory_invalidated_.exchange(false, std::memory_order_acquire)) {
|
||||
// The CPU wrote to watched GPU-visible memory since the last draw: cached
|
||||
// vertex buffer states may cover invalidated pages, and the address-match
|
||||
// shortcut below would skip the RequestRanges re-upload forever. Drop the
|
||||
// cache; the shared-memory validity fast path keeps still-valid buffers
|
||||
// cheap to re-request.
|
||||
InvalidateAllVertexBufferResidency();
|
||||
}
|
||||
const Shader::ConstantRegisterMap& constant_map_vertex = vertex_shader->constant_register_map();
|
||||
for (uint32_t i = 0; i < rex::countof(constant_map_vertex.vertex_fetch_bitmap); ++i) {
|
||||
uint32_t vfetch_bits_remaining = constant_map_vertex.vertex_fetch_bitmap[i];
|
||||
|
||||
@@ -37,6 +37,10 @@ REXCVAR_DEFINE_BOOL(gpu_debug_markers, false, "GPU",
|
||||
"Insert debug markers into GPU command streams for tools "
|
||||
"like PIX and RenderDoc. Automatically enabled when "
|
||||
"RenderDoc is detected.");
|
||||
REXCVAR_DEFINE_BOOL(ac6_fix_trails, true, "AC6",
|
||||
"Fix invisible missile/jet trails: drop stale cached vertex-buffer "
|
||||
"residency so the GPU copy of AC6's fixed-address trail history ring "
|
||||
"refreshes when the CPU writes it. On by default.");
|
||||
|
||||
bool IsGpuDebugMarkersEnabled() {
|
||||
static bool cached = false;
|
||||
|
||||
+19
@@ -29,6 +29,7 @@
|
||||
// TODO(benvanik): move xbox.h out
|
||||
#include <rex/system/xtypes.h>
|
||||
|
||||
REXCVAR_DECLARE(bool, ac6_fix_trails); // defined in graphics/flags.cpp
|
||||
REXCVAR_DEFINE_BOOL(protect_zero, true, "Memory", "Protect the zero page from reads and writes")
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
|
||||
@@ -527,6 +528,24 @@ bool Memory::AccessViolationCallback(std::unique_lock<std::recursive_mutex> glob
|
||||
reinterpret_cast<uintptr_t>(host_address) & ~(uintptr_t(host_page_size - 1));
|
||||
if (rex::memory::Protect(reinterpret_cast<void*>(page_base), host_page_size,
|
||||
rex::memory::PageAccess::kReadWrite, nullptr)) {
|
||||
// The write is about to proceed on a page that cached consumers (e.g.
|
||||
// the GPU shared-memory copy of guest RAM) may still consider valid.
|
||||
// Since the page is read-write from here on, no further faults will
|
||||
// report writes to it, so without an explicit invalidation the cached
|
||||
// copies go permanently stale (this froze AC6's missile/jet trail
|
||||
// position history at zero on the GPU while CPU memory was fine).
|
||||
// Notify the physical-memory invalidation callbacks about the whole
|
||||
// page so caches refetch it and re-arm their own watches.
|
||||
uint32_t recovered_physical_address = GetPhysicalAddress(virtual_address);
|
||||
if (REXCVAR_GET(ac6_fix_trails) && recovered_physical_address != UINT32_MAX) {
|
||||
uint32_t recovered_page_start =
|
||||
recovered_physical_address & ~uint32_t(host_page_size - 1);
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
for (auto invalidation_callback : physical_memory_invalidation_callbacks_) {
|
||||
invalidation_callback->first(invalidation_callback->second, recovered_page_start,
|
||||
uint32_t(host_page_size), true);
|
||||
}
|
||||
}
|
||||
REXSYS_WARN(
|
||||
"Recovered stale physical page protection for guest {:08X} (host {:016X}, "
|
||||
"guest_protect {:08X})",
|
||||
|
||||
Reference in New Issue
Block a user