diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 438dd4d981..454bd52c38 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -165,10 +165,13 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi const Common::ProcessAddress addr = (reinterpret_cast(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); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 837016de1d..74ca244191 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -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) { diff --git a/src/core/memory.h b/src/core/memory.h index 7167efbb84..f206b7a728 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -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 managers); - bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size); + NCEAccessFaultAction InvalidateNCE(Common::ProcessAddress vaddr, size_t size); bool InvalidateSeparateHeap(void* fault_address); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index f30c85911f..76cc6feb9d 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -125,7 +125,8 @@ void BufferCache

::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 @@ -153,7 +154,7 @@ bool BufferCache

::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

::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; diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index b8547f3149..029f8b7a4a 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -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(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(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); }); } diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 76d608bbe0..0970c38c97 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -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 state_words = Span(type); [[maybe_unused]] std::span untracked_words = Span(Type::Untracked); [[maybe_unused]] std::span cached_words = Span(Type::CachedCPU); std::vector> 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); } } diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 9901cf4262..69977e98a5 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -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); } diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 75956ea199..92f96d2247 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -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);