diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index 8f6edf14666..68d72950e9f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace AgcDriver::Graphics { @@ -25,6 +26,15 @@ void RetireUnmappedDepth_nid_no_patch(std::uintptr_t address, std::size_t bytes) void RequireNoNativeDepthFeedback_nid_no_patch(const Context& context, const DepthTarget& target, std::span> sampled); void ClearDepthSurfaces(VkDevice device); void RetireDepthSurfaces(VkDevice device, std::uint64_t address, std::uint64_t bytes); +struct WrittenStorageExtent { + std::uint64_t address; + std::uint32_t width; + std::uint32_t height; + std::uint32_t bytesPerElement; + std::array block; + std::uint32_t blocksPerRow; +}; +void RetireCoveredDepthSurfaces(VkDevice device, std::span> written, std::span extents); bool DepthSurfaceAt(std::uint64_t address); bool DepthSurfaceOverlaps(VkDevice device, std::uint64_t address, std::uint64_t bytes); bool DepthSurfaceAt(const Context& context, std::span words, const GuestTextureResource& resource); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 0ac8d9b1045..729109584c2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -32,6 +32,10 @@ namespace { std::uint64_t depthWrites = 0; +std::pair DepthBlockExtent(std::uint32_t bytesPerTexel) { + return {bytesPerTexel == 4 ? 128u : 256u, bytesPerTexel == 1 ? 256u : 128u}; +} + Recorder* RecorderFor(const Context& context) { auto* recorder = Recorder::Active(); return recorder != nullptr && recorder->Device() == context.device ? recorder : nullptr; @@ -478,6 +482,29 @@ class DepthSurface { return target.stencilAddress != 0 && address < target.stencilAddress + stencilBytes() && target.stencilAddress < end; } + bool CoveredBy(std::span> written, std::span extents) const { + const auto reaches = [&](std::uint64_t address, std::uint32_t bytesPerTexel) { + const auto [blockWidth, blockHeight] = DepthBlockExtent(bytesPerTexel); + const std::array block{65536u, blockWidth, blockHeight}; + const auto blocksPerRow = (target.extent.width + blockWidth - 1u) / blockWidth; + return std::any_of(extents.begin(), extents.end(), [&](const auto& extent) { + return extent.address == address && extent.bytesPerElement == bytesPerTexel && extent.block == block && extent.blocksPerRow == blocksPerRow && extent.width >= target.extent.width && extent.height >= target.extent.height; + }); + }; + const auto covers = [&](std::uint64_t begin, std::uint64_t end) { + for (const auto& [writtenBegin, writtenEnd] : written) { + if (writtenEnd <= begin) continue; + if (writtenBegin > begin) return false; + begin = writtenEnd; + if (begin >= end) return true; + } + return false; + }; + const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; + if (target.samples == 1u && (reaches(target.address, d16 ? 2u : 4u) || (target.stencilAddress != 0 && reaches(target.stencilAddress, 1u)))) return true; + return covers(target.address, target.address + depthBytes()) || (target.stencilAddress != 0 && covers(target.stencilAddress, target.stencilAddress + stencilBytes())); + } + void NoteMemory() { depthMemory = GuestMemory::CollectWrites(target.address, static_cast(depthBytes())); stencilMemory = target.stencilAddress != 0 ? GuestMemory::CollectWrites(target.stencilAddress, static_cast(stencilBytes())) : 0; @@ -1259,8 +1286,7 @@ std::uint64_t DepthSurfacesSerial() { } std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel) { - const std::uint32_t blockWidth = bytesPerTexel == 4 ? 128u : 256u; - const std::uint32_t blockHeight = bytesPerTexel == 1 ? 256u : 128u; + const auto [blockWidth, blockHeight] = DepthBlockExtent(bytesPerTexel); const auto width = static_cast((extent.width + blockWidth - 1) / blockWidth * blockWidth); const auto height = static_cast((extent.height + blockHeight - 1) / blockHeight * blockHeight); return width * height * bytesPerTexel; @@ -1804,4 +1830,11 @@ void RetireDepthSurfaces(VkDevice device, std::uint64_t address, std::uint64_t b } } +void RetireCoveredDepthSurfaces(VkDevice device, std::span> written, std::span extents) { + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (surface->context.device == device && !surface->retired && surface->CoveredBy(written, extents)) retire(*surface); + } +} + } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 36d2c9aa87b..2f711a5eea4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -973,12 +973,41 @@ GuestTextureResource StorageSurface(const Context& context, const GuestTextureRe return surface; } +struct WrittenStorage { + std::vector> bytes; + std::vector extents; +}; + +static WrittenStorage WrittenStorageBytes(const GuestTextureResource& viewed, std::uint32_t mip) { + WrittenStorage written; + const auto geometry = DescribeSurface(viewed); + if (geometry.thick || mip >= geometry.mips.size()) return written; + const auto& level = geometry.mips[mip]; + const auto bytesPerElement = BytesPerElement(viewed.format); + const auto covered = CoveredMipBytes(viewed.tileMode, bytesPerElement, level); + const bool blocks = viewed.tileMode != TextureTileMode::kLinear && !level.tail; + const auto first = geometry.layers > 1u && geometry.imageDepth <= 1u ? std::min(viewed.baseArray, geometry.layers) : 0u; + for (auto layer = first; layer < geometry.layers; ++layer) { + if (!geometry.HasLayer(mip, layer)) continue; + const auto begin = viewed.baseAddress + geometry.GuestLayerOffset(layer) + level.tiledOffset; + for (const auto& [rangeBegin, rangeEnd] : covered) { + if (!written.bytes.empty() && written.bytes.back().second == begin + rangeBegin) written.bytes.back().second = begin + rangeEnd; + else written.bytes.emplace_back(begin + rangeBegin, begin + rangeEnd); + } + if (blocks) written.extents.push_back({begin, level.width, level.height, bytesPerElement, ThinBlockLayout(viewed.tileMode, bytesPerElement), level.blocksPerRow}); + } + return written; +} + // `guestBytes` is the surface size when the caller described the surface already (0: described here). std::shared_ptr cachedStorageTexture(const Context& context, std::span words, const GuestTextureResource& viewed, std::uint32_t mip, std::uint64_t guestBytes, bool written) { Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented"); RequireNoMultisampleTarget(viewed.baseAddress, guestBytes != 0 ? static_cast(guestBytes) : 1u, "a storage image access"); if (auto depth = DepthSurfaceStorage(context, words, viewed, mip)) return depth; - if (written) RetireDepthSurfaces(context.device, viewed.baseAddress, guestBytes != 0 ? guestBytes : DescribeSurface(viewed).guestBytes); + if (written && DepthSurfaceOverlaps(context.device, viewed.baseAddress, guestBytes != 0 ? guestBytes : DescribeSurface(viewed).guestBytes)) { + const auto storage = WrittenStorageBytes(viewed, mip); + RetireCoveredDepthSurfaces(context.device, storage.bytes, storage.extents); + } static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; if (disabled) return StorageTexture::Create(context, *context.detiler, viewed, mip); static_cast(words); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageReuse.cpp index f6b6127eb17..dbd774f671a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthStorageReuse.cpp @@ -17,6 +17,10 @@ using DepthFastClearHarness::Width; alignas(4096) std::array Depth{}; alignas(256) std::array Output{}; + +std::uintptr_t DepthAddress() { + return (reinterpret_cast(Depth.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}; +} alignas(256) constexpr std::array LoadCode{ 0x7e3c0280u, 0x7e3e0280u, 0x7e140280u, 0xf0001108u, 0x00010a1eu, 0xbf8c3f70u, 0xf0201108u, 0x00010a1eu, 0x34060082u, 0xe0701000u, 0x80000a03u, 0xbf810000u, 0xbf810000u, @@ -26,14 +30,14 @@ alignas(256) constexpr std::array SampleCode{ 0x00610a1eu, 0x34060082u, 0xe0701000u, 0x80000a03u, 0xbf810000u, }; -void Read(AgcDriver::VulkanDevice& device, std::uint32_t format, std::uint32_t width, bool sampled, float expected) { +void Read(AgcDriver::VulkanDevice& device, std::uint32_t format, std::uint32_t width, bool sampled, float expected, std::uint32_t height = Height, std::uint32_t swizzle = 0u) { Output.fill(0xdeadbeefu); const auto output = reinterpret_cast(Output.data()); - const auto depth = reinterpret_cast(Depth.data()); + const auto depth = DepthAddress(); const std::array data{ static_cast(output), static_cast(output >> 32u), sizeof(Output), 0x31016facu, static_cast(depth >> 8u), static_cast(depth >> 40u) | (format << 20u) | (((width - 1u) & 3u) << 30u), - ((width - 1u) >> 2u) | ((Height - 1u) << 14u), 0x90000facu, 0u, 0u, 0u, 0u, + ((width - 1u) >> 2u) | ((height - 1u) << 14u), 0x90000facu | (swizzle << 20u), 0u, 0u, 0u, 0u, 0x92u, 0u, 0u, 0u, }; const auto code = sampled ? std::span(SampleCode) : std::span(LoadCode); @@ -60,7 +64,7 @@ int main() { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const auto shaders = DepthFastClearHarness::Compile(*device); - const auto address = reinterpret_cast(Depth.data()); + const auto address = DepthAddress(); for (const auto format : {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM}) { AgcDriver::Graphics::ClearDepthSurfaces(device->Device()); Depth.fill(0.25f); @@ -68,7 +72,14 @@ int main() { Require(DepthFastClearHarness::Draw(*device, shaders, surface, {.depthWrite = true}) == DepthFastClearHarness::Covered, "depth producer did not cover the surface"); Read(*device, format == VK_FORMAT_D16_UNORM ? 7u : 22u, Width, true, 0.5f); Read(*device, 22u, format == VK_FORMAT_D16_UNORM ? Width : Width / 2u, false, 0.25f); - if (format == VK_FORMAT_D32_SFLOAT) Read(*device, 22u, Width, true, 0.25f); + if (format != VK_FORMAT_D32_SFLOAT) continue; + Read(*device, 22u, Width, true, 0.5f); + Read(*device, 20u, Width, false, 0.25f, Height, 25u); + Read(*device, 22u, Width, true, 0.25f); + Require(DepthFastClearHarness::Draw(*device, shaders, surface, {.depthTest = false, .depthWrite = true}) == DepthFastClearHarness::Covered, "depth producer did not cover the surface"); + Read(*device, 22u, Width, true, 0.5f); + Read(*device, 22u, 128u, false, 0.25f, 128u); + Read(*device, 22u, Width, true, 0.25f); } std::puts("depth storage reuse GPU tests passed"); return 0;