Compare commits

...

2 Commits

Author SHA1 Message Date
xbzk 15d18e4db6 retrigger ci
retrigger ci
2026-10-31 13:18:27 +01:00
xbzk 8a142b490f [core, video_core] Preserve GPU writes during NCE fault handling 2026-10-30 23:52:28 -03:00
8 changed files with 65 additions and 28 deletions
+6 -3
View File
@@ -165,10 +165,13 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
const Common::ProcessAddress addr = const Common::ProcessAddress addr =
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK); (reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory(); auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { const auto result = memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE);
// We handled the access successfully and are returning to guest code. if (result == Memory::NCEAccessFaultAction::Retry) {
return true; return true;
} }
if (result == Memory::NCEAccessFaultAction::Instruction) {
return HandleGuestAlignmentFault(guest_ctx, raw_info, raw_context);
}
// We couldn't handle the access. // We couldn't handle the access.
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
@@ -231,7 +234,7 @@ HaltReason ArmNce::RunThread(Kernel::KThread* thread) {
hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); // Android: Use "process handle SIGUSR2 -n true -p true -s false" (and SIGURG) in LLDB when debugging hr = ReturnToRunCodeByExceptionLevelChange(m_thread_id, thread_params); // Android: Use "process handle SIGUSR2 -n true -p true -s false" (and SIGURG) in LLDB when debugging
} }
// Critical section for thread cleanup // Critical section for thread cleanup.
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
// Cache values before releasing thread // Cache values before releasing thread
+13 -8
View File
@@ -739,7 +739,7 @@ struct Memory::Impl {
PAddr last_address; 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; constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1;
const size_t core = (std::min)(system.GetCurrentHostThreadID(), const size_t core = (std::min)(system.GetCurrentHostThreadID(),
sys_core); // any other calls threads go to syscore. sys_core); // any other calls threads go to syscore.
@@ -755,9 +755,13 @@ struct Memory::Impl {
sys_core_guard.unlock(); sys_core_guard.unlock();
} }
}; };
bool has_pending_writes = false;
auto& gpu = system.GPU(); auto& gpu = system.GPU();
gpu_device_memory->ApplyOpOnPointer( 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; Core::System& system;
@@ -961,7 +965,7 @@ void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug)
impl->MarkRegionDebug(GetInteger(vaddr), size, 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 mapped = true;
[[maybe_unused]] bool rasterizer = false; [[maybe_unused]] bool rasterizer = false;
@@ -973,17 +977,18 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
mapped = false; mapped = false;
}, },
[&] { rasterizer = true; }); [&] { rasterizer = true; });
if (rasterizer) {
impl->InvalidateGPUMemory(ptr, size);
}
#ifdef __ANDROID__ #ifdef __ANDROID__
if (!rasterizer && mapped) { if (!rasterizer && mapped) {
impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
} }
#endif #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) { 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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project // 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_PAGESIZE = 1ULL << YUZU_PAGEBITS;
constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1; constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
enum class NCEAccessFaultAction {
Unmapped,
Retry,
Instruction,
};
/// Virtual user-space memory regions /// Virtual user-space memory regions
enum : u64 { enum : u64 {
/// TLS (Thread-Local Storage) related. /// TLS (Thread-Local Storage) related.
@@ -493,7 +499,7 @@ public:
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers); 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); 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); ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(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> template <class P>
@@ -153,7 +154,7 @@ bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
if (!is_dirty) { if (!is_dirty) {
return false; return false;
} }
if (memory_tracker.IsRegionGpuModified(device_addr, size)) { if (IsRegionGpuModified(device_addr, size)) {
return true; return true;
} }
WriteMemory(device_addr, size); WriteMemory(device_addr, size);
@@ -1781,13 +1782,23 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
u64 largest_copy = 0; u64 largest_copy = 0;
DAddr buffer_start = buffer.CpuAddr(); DAddr buffer_start = buffer.CpuAddr();
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
copies.push_back(BufferCopy{ const auto add_upload = [&](DAddr start, DAddr end) {
.src_offset = total_size_bytes, if (start == end) return;
.dst_offset = device_addr_out - buffer_start, const u64 upload_size = end - start;
.size = range_size, 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; add_upload(upload_start, device_addr_out + range_size);
largest_copy = (std::max)(largest_copy, range_size);
}); });
if (total_size_bytes == 0) { if (total_size_bytes == 0) {
return true; return true;
@@ -71,9 +71,9 @@ public:
} }
/// Mark region as CPU modified, notifying the device_tracker about this change /// Mark region as CPU modified, notifying the device_tracker about this change
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size, bool keep_tracking = false) {
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) { 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); 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 enable If enabling or disabling
/// @param dirty_addr Base address to mark or unmark as modified /// @param dirty_addr Base address to mark or unmark as modified
/// @param size Size in bytes 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); std::span<u64> state_words = Span(type);
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked); [[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU); [[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
std::vector<std::pair<VAddr, u64>> ranges; std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
if (type == Type::CPU || type == Type::CachedCPU) { 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) { if (enable) {
state_words[index] |= mask; state_words[index] |= mask;
if (type == Type::CPU || type == Type::CachedCPU) 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) if (type == Type::CPU)
cached_words[index] &= ~mask; cached_words[index] &= ~mask;
} else { } else {
@@ -139,7 +140,7 @@ struct WordManager {
} }
}); });
if (!ranges.empty()) { 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); 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 /// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size) { void FlushAndInvalidateRegion(DAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size, is_async); gpu_thread.FlushAndInvalidateRegion(addr, size, is_async);
@@ -526,6 +530,10 @@ bool GPU::OnCPUWrite(DAddr addr, u64 size) {
return impl->OnCPUWrite(addr, 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) { void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) {
impl->FlushAndInvalidateRegion(addr, size); impl->FlushAndInvalidateRegion(addr, size);
} }
+3
View File
@@ -254,6 +254,9 @@ public:
/// sensible, false otherwise, addr and size must be a valid combination /// sensible, false otherwise, addr and size must be a valid combination
bool OnCPUWrite(DAddr addr, u64 size); 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 /// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size); void FlushAndInvalidateRegion(DAddr addr, u64 size);