diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hle/service/nvdrv/nvdrv.cpp | 1 | ||||
| -rw-r--r-- | src/video_core/buffer_cache/buffer_cache.h | 7 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.cpp | 27 | ||||
| -rw-r--r-- | src/video_core/dma_pusher.h | 2 | ||||
| -rw-r--r-- | src/video_core/nx2.h | 38 |
5 files changed, 75 insertions, 0 deletions
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 12923496fb..7853d17235 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -133,6 +133,7 @@ Module::Module(Core::System& system) Devices::Nx2GpuMemoryRegistry::Get().Reset(); Tegra::Nx2ClearSemaphoreRegions(); Tegra::Nx2ClearSparseRegions(); + Tegra::Nx2ClearGpuWrittenPages(); Devices::g_nx2_driver = false; Tegra::g_nx2_ampere = false; Core::g_nx2_present_count = 0; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index e90af3214d..fb9f1f0553 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -17,6 +17,7 @@ #include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/guest_memory.h" #include "video_core/host1x/gpu_device_memory_manager.h" +#include "video_core/nx2.h" #include "video_core/nx2_profile.h" #include "video_core/texture_cache/util.h" @@ -291,6 +292,9 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am const bool has_new_downloads = tmp_intervals.size() != 0; for (const auto& pair : tmp_intervals) { gpu_modified_ranges.Add(pair.first, pair.second); + if (Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) { + Tegra::Nx2MarkGpuWritten(pair.first, pair.second); + } } const auto& copy = copies[0]; src_buffer.MarkUsage(copy.src_offset, copy.size); @@ -1610,6 +1614,9 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3 } else { ArmReadTraps(device_addr, size); } + if (Tegra::g_nx2_ampere.load(std::memory_order_relaxed)) { + Tegra::Nx2MarkGpuWritten(device_addr, size); + } } template <class P> diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 88d571ae5f..8c142b36db 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -162,6 +162,7 @@ bool DmaPusher::Step() { command_resume_offset += ProcessCommands(commands.subspan(command_resume_offset), command_resume_offset); } else { + FlushGpuWrittenCommands(dma_state.dma_get, size_t{header.size} * sizeof(u32)); Tegra::Memory::GpuGuestMemory<Tegra::CommandHeader, Tegra::Memory::GuestMemoryFlags::UnsafeRead>headers(memory_manager, dma_state.dma_get, header.size, &command_headers); const std::span<const CommandHeader> commands{headers.data(), headers.size()}; RecordParsedEntry(header, commands); @@ -445,6 +446,32 @@ void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) { } } +void DmaPusher::FlushGpuWrittenCommands(GPUVAddr address, size_t size) { + // Title 1 runs small command lists its culling dispatches fill in each frame. Accurate GPU + // mode reads every command list through the caches (safe reads); here only the ones on pages + // the GPU has written are, or their draws run with an older frame's words (trees vanish). + if (!Tegra::g_nx2_ampere.load(std::memory_order_relaxed) || size == 0) { + return; + } + bool written = false; + for (GPUVAddr page = address & ~GPUVAddr{0xfff}; page < address + size; page += 0x1000) { + if (const auto device = memory_manager.GpuToCpuAddress(page); + device && Tegra::Nx2IsGpuWritten(*device)) { + written = true; + break; + } + } + if (!written) { + return; + } + // The caches belong to the split's back: it first runs what the front queued (the dispatch + // that writes these words among it). + VideoCommon::GpuSplit::Get().Drain(); + if (memory_manager.IsMemoryDirty(address, size, VideoCommon::CacheType::BufferCache)) { + memory_manager.FlushRegion(address, size, VideoCommon::CacheType::BufferCache); + } +} + void DmaPusher::ReportUnboundSubchannel() { // Methods sent to a subchannel with no engine (no SetObject yet) are dropped. static std::atomic<u32> reported{}; diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 82d29edb4b..e8f2707096 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -182,6 +182,8 @@ public: void DumpCurrentEntry(const char* reason); void CheckSubmitHash(const CommandListHeader& header, u64 submit_hash); void RecordParsedEntry(const CommandListHeader& header, std::span<const CommandHeader> commands); + /// Brings command list words the GPU wrote (still only in host buffers) to guest memory. + void FlushGpuWrittenCommands(GPUVAddr address, size_t size); private: diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h index 318607384b..1fb49e15a6 100644 --- a/src/video_core/nx2.h +++ b/src/video_core/nx2.h @@ -20,6 +20,44 @@ namespace Tegra { // Ampere classes and the Turing+ macro ISA, which Eden's Maxwell engines don't implement. inline std::atomic<bool> g_nx2_ampere{false}; +// Device pages the GPU has written through the buffer cache (shader stores, copies), ever. Title 1 +// runs command lists the GPU writes each frame (20-byte entries its culling fills in); read +// without a flush they are stale and the draws behind them (trees) go missing. The bits never +// clear: the dispatch that rewrites a page may still be queued on the split's back when its front +// reads the command list. One bit per 4 KiB of the 36-bit device address space (2 MiB). +inline constexpr u64 Nx2WrittenPageBits = 12; +inline std::array<std::atomic<u64>, (u64{1} << (36 - Nx2WrittenPageBits)) / 64> g_nx2_gpu_written_pages{}; + +inline void Nx2MarkGpuWritten(u64 device_address, u64 size) { + if (size == 0) { + return; + } + const u64 last = std::min<u64>((device_address + size - 1) >> Nx2WrittenPageBits, + g_nx2_gpu_written_pages.size() * 64 - 1); + for (u64 page = device_address >> Nx2WrittenPageBits; page <= last; ++page) { + auto& word = g_nx2_gpu_written_pages[page / 64]; + const u64 bit = u64{1} << (page % 64); + if ((word.load(std::memory_order_relaxed) & bit) == 0) { + word.fetch_or(bit, std::memory_order_relaxed); + } + } +} + +/// A new emulation session starts: no page has been written by its GPU yet. +inline void Nx2ClearGpuWrittenPages() { + for (auto& word : g_nx2_gpu_written_pages) { + word.store(0, std::memory_order_relaxed); + } +} + +[[nodiscard]] inline bool Nx2IsGpuWritten(u64 device_address) { + const u64 page = device_address >> Nx2WrittenPageBits; + if (page >= g_nx2_gpu_written_pages.size() * 64) { + return false; + } + return (g_nx2_gpu_written_pages[page / 64].load(std::memory_order_relaxed) >> (page % 64)) & 1; +} + // NVN2 queues release their progress values into the second page of a two-page GPU-accessible // region and GPU acquires read the same offsets in the first page (the CPU polls the second). // Something on the console copies one into the other; until that is known, the puller mirrors |
