[core, video_core] Preserve GPU writes during NCE fault handling

This commit is contained in:
xbzk
2026-10-30 23:31:02 -03:00
parent 943f1fc760
commit 8a142b490f
8 changed files with 64 additions and 27 deletions
+5 -2
View File
@@ -165,10 +165,13 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
const Common::ProcessAddress addr =
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
// We handled the access successfully and are returning to guest code.
const auto result = memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE);
if (result == Memory::NCEAccessFaultAction::Retry) {
return true;
}
if (result == Memory::NCEAccessFaultAction::Instruction) {
return HandleGuestAlignmentFault(guest_ctx, raw_info, raw_context);
}
// We couldn't handle the access.
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
+13 -8
View File
@@ -739,7 +739,7 @@ struct Memory::Impl {
PAddr last_address;
};
void InvalidateGPUMemory(u8* p, size_t size) {
bool TryInvalidateGPUMemory(const u8* p, size_t size) {
constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1;
const size_t core = (std::min)(system.GetCurrentHostThreadID(),
sys_core); // any other calls threads go to syscore.
@@ -755,9 +755,13 @@ struct Memory::Impl {
sys_core_guard.unlock();
}
};
bool has_pending_writes = false;
auto& gpu = system.GPU();
gpu_device_memory->ApplyOpOnPointer(
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
p, scratch_buffers[core], [&](DAddr address) { has_pending_writes |= gpu.MustFlushRegion(address, size); });
if (has_pending_writes) return false;
gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
return true;
}
Core::System& system;
@@ -961,7 +965,7 @@ void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug)
impl->MarkRegionDebug(GetInteger(vaddr), size, debug);
}
bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
NCEAccessFaultAction Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
[[maybe_unused]] bool mapped = true;
[[maybe_unused]] bool rasterizer = false;
@@ -973,17 +977,18 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
mapped = false;
},
[&] { rasterizer = true; });
if (rasterizer) {
impl->InvalidateGPUMemory(ptr, size);
}
#ifdef __ANDROID__
if (!rasterizer && mapped) {
impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
}
#endif
return mapped && ptr != nullptr;
if (rasterizer) {
if (!impl->TryInvalidateGPUMemory(ptr, size)) {
return NCEAccessFaultAction::Instruction;
}
}
return mapped && ptr != nullptr ? NCEAccessFaultAction::Retry : NCEAccessFaultAction::Unmapped;
}
bool Memory::InvalidateSeparateHeap(void* fault_address) {
+8 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -46,6 +46,12 @@ constexpr std::size_t YUZU_PAGEBITS = 12;
constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS;
constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
enum class NCEAccessFaultAction {
Unmapped,
Retry,
Instruction,
};
/// Virtual user-space memory regions
enum : u64 {
/// TLS (Thread-Local Storage) related.
@@ -493,7 +499,7 @@ public:
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
NCEAccessFaultAction InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
bool InvalidateSeparateHeap(void* fault_address);
+19 -8
View File
@@ -125,7 +125,8 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size);
}
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
const DAddr page_start = Common::AlignDown(device_addr, DEVICE_PAGESIZE);
memory_tracker.MarkRegionAsCpuModified(device_addr, size,IsRegionGpuModified(page_start, Common::AlignUp(device_addr + size, DEVICE_PAGESIZE) - page_start));
}
template <class P>
@@ -153,7 +154,7 @@ bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
if (!is_dirty) {
return false;
}
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
if (IsRegionGpuModified(device_addr, size)) {
return true;
}
WriteMemory(device_addr, size);
@@ -1781,13 +1782,23 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
u64 largest_copy = 0;
DAddr buffer_start = buffer.CpuAddr();
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = device_addr_out - buffer_start,
.size = range_size,
const auto add_upload = [&](DAddr start, DAddr end) {
if (start == end) return;
const u64 upload_size = end - start;
copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = start - buffer_start,
.size = upload_size,
});
total_size_bytes += upload_size;
largest_copy = (std::max)(largest_copy, upload_size);
};
DAddr upload_start = device_addr_out;
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
add_upload(upload_start, gpu_start);
upload_start = gpu_end;
});
total_size_bytes += range_size;
largest_copy = (std::max)(largest_copy, range_size);
add_upload(upload_start, device_addr_out + range_size);
});
if (total_size_bytes == 0) {
return true;
@@ -71,9 +71,9 @@ public:
}
/// Mark region as CPU modified, notifying the device_tracker about this change
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size, bool keep_tracking = false) {
IteratePages<true>(dirty_cpu_addr, query_size, [keep_tracking](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size, keep_tracking);
});
}
+5 -4
View File
@@ -115,19 +115,20 @@ struct WordManager {
/// @param enable If enabling or disabling
/// @param dirty_addr Base address to mark or unmark as modified
/// @param size Size in bytes to mark or unmark as modified
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
/// @param keep_tracking Keep CPU-modified pages protected for subsequent native writes.
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size, bool keep_tracking = false) noexcept {
std::span<u64> state_words = Span(type);
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
if (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
CollectChangedRanges(!enable || keep_tracking, index, untracked_words[index], mask, ranges);
}
if (enable) {
state_words[index] |= mask;
if (type == Type::CPU || type == Type::CachedCPU)
untracked_words[index] |= mask;
untracked_words[index] = (untracked_words[index] & ~mask) | (keep_tracking ? u64{0} : mask);
if (type == Type::CPU)
cached_words[index] &= ~mask;
} else {
@@ -139,7 +140,7 @@ struct WordManager {
}
});
if (!ranges.empty()) {
ApplyCollectedRanges(ranges, (!enable) ? 1 : -1);
ApplyCollectedRanges(ranges, (!enable || keep_tracking) ? 1 : -1);
}
}
+8
View File
@@ -234,6 +234,10 @@ struct GPU::Impl {
return renderer->ReadRasterizer()->OnCPUWrite(addr, size);
}
bool MustFlushRegion(DAddr addr, u64 size) {
return renderer->ReadRasterizer()->MustFlushRegion(addr, size);
}
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size, is_async);
@@ -526,6 +530,10 @@ bool GPU::OnCPUWrite(DAddr addr, u64 size) {
return impl->OnCPUWrite(addr, size);
}
bool GPU::MustFlushRegion(DAddr addr, u64 size) {
return impl->MustFlushRegion(addr, size);
}
void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) {
impl->FlushAndInvalidateRegion(addr, size);
}
+3
View File
@@ -254,6 +254,9 @@ public:
/// sensible, false otherwise, addr and size must be a valid combination
bool OnCPUWrite(DAddr addr, u64 size);
/// Check whether this range still has GPU writes that must be flushed to CPU memory.
bool MustFlushRegion(DAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size);