diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index efb583ed9..707c036cc 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -495,6 +495,13 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach default: return false; } }; + if (dst_sel == 2) { + // kNowhere discards the GL2 prefetch destination without a guest-visible write. + if (src_sel != 3) { + EXIT("unsupported dmaData nowhere source selector 0x%02" PRIx8 "\n", src_sel); + } + return; + } bool dst_gds = false; if (!decode_gds(dst_sel, dst_gds)) { EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d84bb7895..feb1e805c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -2180,22 +2180,27 @@ public: constexpr uint64_t memory_copy_dst = fault_base + 0x6000; constexpr uint64_t memory_dst = fault_base + 0x8000; constexpr uint64_t l2_copy_dst = fault_base + 0xa000; + constexpr uint64_t nowhere_dst = fault_base + 0xc000; constexpr uint64_t clean_cached_fill = fault_base + 0x3000; constexpr uint64_t clean_cached_copy = fault_base + 0x3008; constexpr uint64_t clean_cached_source = fault_base + 0x3010; constexpr uint64_t clean_cached_readback = fault_base + 0xb000; constexpr std::array source_words{0x11223344u, 0x55667788u}; + constexpr std::array nowhere_words{0x89abcdefu, 0x01234567u}; constexpr std::array clean_copy_words{0x13579bdfu, 0x2468ace0u}; std::memcpy(reinterpret_cast(memory_src), source_words.data(), sizeof(source_words)); + std::memcpy(reinterpret_cast(nowhere_dst), nowhere_words.data(), + sizeof(nowhere_words)); std::memcpy(reinterpret_cast(clean_cached_source), clean_copy_words.data(), sizeof(clean_copy_words)); - std::array dma_commands{}; + std::array dma_commands{}; size_t dma_cursor = 0; const auto append_dma = [&](uint8_t dst_sel, uint64_t dst, uint8_t src_sel, - uint64_t src, uint32_t bytes) { + uint64_t src, uint32_t bytes, + uint32_t command_flags = 0) { auto *packet = dma_commands.data() + dma_cursor; dma_cursor += 7; packet[0] = KYTY_PM4(7, Pm4::IT_DMA_DATA, 0); @@ -2205,7 +2210,7 @@ public: packet[3] = static_cast(src >> 32u); packet[4] = static_cast(dst); packet[5] = static_cast(dst >> 32u); - packet[6] = bytes; + packet[6] = bytes | command_flags; }; constexpr uint64_t gds_immediate_offset = 0; constexpr uint64_t gds_memory_offset = 0x10; @@ -2219,9 +2224,10 @@ public: sizeof(source_words)); append_dma(0, memory_copy_dst, 0, memory_src, sizeof(source_words)); append_dma(3, l2_copy_dst, 3, memory_src, sizeof(source_words)); + append_dma(2, nowhere_dst, 3, nowhere_dst, sizeof(nowhere_words), 1u << 31u); Require("GpuCommandLane", "DMA_DATA packet assembly", dma_cursor == dma_commands.size(), - "DMA_DATA GDS packet stream has the wrong size"); + "DMA_DATA packet stream has the wrong size"); gpu.Submit(dma_commands.data(), static_cast(dma_commands.size()), nullptr, 0); gpu.Done(); @@ -2231,9 +2237,12 @@ public: Require("GpuCommandLane", "clean cached setup", buffer_cache.IsRegionRegistered(clean_cached_fill, sizeof(source_words)) && + !buffer_cache.HasGpuDirtyBytes(nowhere_dst, + sizeof(nowhere_words)) && buffer_cache.HasGpuDirtyBytes(immediate_dst, sizeof(source_words)), - "DMA did not establish a cached page with a dirty sibling"); + "DMA prefetch modified ownership or did not establish a dirty " + "cached page"); buffer_cache.FillBuffer(clean_cached_fill, sizeof(source_words), clean_fill_value); @@ -2312,6 +2321,10 @@ public: "memory-to-memory bytes do not match"); Require("GpuCommandLane", "DMA_DATA L2 contents", l2_copy_words == source_words, "MemoryUsingL2 bytes do not match"); + Require("GpuCommandLane", "DMA_DATA nowhere contents", + std::memcmp(reinterpret_cast(nowhere_dst), + nowhere_words.data(), sizeof(nowhere_words)) == 0, + "Nowhere destination modified guest memory"); Require("GpuCommandLane", "clean cached DMA contents", clean_cached_words == std::array{clean_fill_value, clean_fill_value,