From 288fc4e5848a28c76387071692e1ae7d6e29cfe6 Mon Sep 17 00:00:00 2001 From: hugodearaujo Date: Tue, 3 Nov 2026 23:40:08 -0300 Subject: [PATCH 1/3] fix(agc): publish a written native multisample D32S8 owner without HTILE to its depth and stencil planes --- .../Graphics/src/DepthSurface.cpp | 20 ++-- ...river_native_multisample_depth_tests.cmake | 5 +- .../execution/NativeMultisampleDepth.cpp | 94 +++++++++++++++++++ docs/dev/debt/libSceAgcDriver.md | 2 +- 4 files changed, 110 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index b48d2bb68727..baad04f6131b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -2325,7 +2325,9 @@ void DepthSurface::PublishToGuest() { } scope{publicationInProgress}; publicationInProgress = true; if (publicationFailure) std::rethrow_exception(publicationFailure); - Require(target.format == VK_FORMAT_D32_SFLOAT_S8_UINT && hostFormat == target.format && publication.size() == 3u, "sample-preserving guest publication of native multisample depth is not implemented for this owner"); + const bool htile = !uniformPlaneTarget(target); + Require(target.format == VK_FORMAT_D32_SFLOAT_S8_UINT && hostFormat == target.format && publication.size() == (htile ? 3u : 2u), "sample-preserving guest publication of native multisample depth is not implemented for this owner"); + const std::size_t stencilPlane = publication.size() - 1u; Require(sampling.refusal.empty(), sampling.refusal); RequireStencilProven(); Require(!clearPending && clearGeneration == 0u && stencilClearGeneration == 0u && pendingClear == 0u, "native multisample depth publication with an unapplied clear is not implemented"); @@ -2360,7 +2362,7 @@ void DepthSurface::PublishToGuest() { if (auto* recorder = RecorderFor(context)) recorder->Sync(); } std::array, 3> result; - for (std::size_t index : {0u, 2u}) { + for (const std::size_t index : {std::size_t{0u}, stencilPlane}) { const auto elementBytes = index == 0u ? 4u : 1u; const auto aspect = index == 0u ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT; const auto format = index == 0u ? VK_FORMAT_D32_SFLOAT : VK_FORMAT_R8_UINT; @@ -2392,12 +2394,14 @@ void DepthSurface::PublishToGuest() { result[index] = publication[index].Expand(); layout.Tile(linear.Bytes().first(static_cast(layout.LinearBytes())), result[index]); } - result[1] = publication[1].Expand(); - for (std::size_t offset = 0u; offset < result[1].size(); offset += 4u) { - std::uint32_t word; - std::memcpy(&word, result[1].data() + offset, 4u); - word = expandedHtileWord(word, !target.htileDepthOnly, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); - std::memcpy(result[1].data() + offset, &word, 4u); + if (htile) { + result[1] = publication[1].Expand(); + for (std::size_t offset = 0u; offset < result[1].size(); offset += 4u) { + std::uint32_t word; + std::memcpy(&word, result[1].data() + offset, 4u); + word = expandedHtileWord(word, !target.htileDepthOnly, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + std::memcpy(result[1].data() + offset, &word, 4u); + } } std::array, 3> current; std::array tookGuest{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake index 3a52997aaef2..4321311f703a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake @@ -19,9 +19,10 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_native_depth_clears agc_driver_native_depth_clear_refusals agc_driver_native_depth_stencil agc_driver_native_depth_separate_stencil agc_driver_native_depth_aliased_stencil agc_driver_native_depth_expand_gate agc_driver_native_depth_stencil_view agc_driver_native_depth_buffer_clear agc_driver_native_depth_uniform_planes PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_native_depth_publication COMMAND agc_driver_native_multisample_depth_tests --publication) - set_tests_properties(agc_driver_native_depth_publication PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_native_depth_publication_without_htile COMMAND agc_driver_native_multisample_depth_tests --publication-without-htile) + set_tests_properties(agc_driver_native_depth_publication agc_driver_native_depth_publication_without_htile PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) - set_tests_properties(agc_driver_native_depth_publication PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_driver_native_depth_publication agc_driver_native_depth_publication_without_htile PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_native_depth_driver_path COMMAND agc_driver_native_multisample_depth_tests --driver-path) add_test(NAME agc_driver_native_depth_driver_queued_fill COMMAND agc_driver_native_multisample_depth_tests --driver-queued-fill) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp index c0907ae3a69c..ba4e2f325a67 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp @@ -3191,6 +3191,88 @@ void CheckUniformPlanesWithoutHtile(Context context, std::uint32_t samples) { std::printf("Native D32S8 without HTILE, samples=%u: uniform planes imported and sampled, other planes refused\n", samples); } +enum class UniformPublication { Read, Pages, StencilRace, PlaneStore }; + +void CheckUniformPlanePublication(Context context, std::uint32_t samples, UniformPublication mode) { + constexpr VkExtent2D extent{129u, 3u}; + const auto depthBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, samples)); + TargetMemory memory(depthBytes, true), stencil(stencilBytes, true); + const auto one = std::bit_cast(1.0f); + for (std::size_t offset = 0u; offset < depthBytes; offset += 4u) std::memcpy(memory.Data() + offset, &one, 4u); + std::memset(stencil.Data(), 0x5a, stencilBytes); + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, 1.0f, 0u, 0u, false, samples}; + target.depthSwizzle = 24u; + target.pipeBankXor = samples == 2u ? 0u : 0x8c00u; + const MultisampleTextureLayout depthLayout(TextureTileMode::kZ64KBX, 4u, samples, extent.width, extent.height, 1u, target.pipeBankXor); + const MultisampleTextureLayout stencilLayout(TextureTileMode::kZ64KBX, 1u, samples, extent.width, extent.height, 1u, target.pipeBankXor); + Recorder recorder(context); + recorder.Activate(); + std::unique_ptr raster; + struct Cleanup { + Recorder& recorder; + Context context; + ~Cleanup() { recorder.Sync(); ClearCachedTextures(context.device); ClearDepthSurfaces(context.device); ClearMultisampleTargets(context.device); } + } cleanup{recorder, context}; + const auto view = DepthSurfaceView(context, target); + Require(view != VK_NULL_HANDLE && DepthSurfaceAt(target.address), "a uniform-plane D32S8 target without HTILE was not imported"); + raster = std::make_unique(context, target, view, StencilUse{VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x20u}, false, true); + std::array, 2> expected{std::vector(memory.Data(), memory.Data() + depthBytes), std::vector(stencil.Data(), stencil.Data() + stencilBytes)}; + const std::array addresses{target.address, target.stencilAddress}; + const auto draw = [&](float base) { + NoteDepthSurfaceWrite(context, target); + raster->Run(recorder.Commands(), base); + for (std::uint32_t y = 0u; y < extent.height; ++y) { + for (std::uint32_t x = 0u; x < extent.width; ++x) { + for (std::uint32_t sample = 0u; sample < samples; ++sample) { + const float value = base + static_cast(sample + 1u) / 16.0f; + std::memcpy(expected[0].data() + depthLayout.Offset(x, y, 0u, sample), &value, 4u); + expected[1][stencilLayout.Offset(x, y, 0u, sample)] = static_cast(0x20u + sample); + } + } + } + }; + const auto published = [&] { + Require(std::equal(expected[0].begin(), expected[0].end(), memory.Data()), "a written D32S8 owner without HTILE did not publish its depth samples and padding"); + Require(std::equal(expected[1].begin(), expected[1].end(), stencil.Data()), "a written D32S8 owner without HTILE did not publish its stencil samples and padding"); + }; + draw(0.25f); + std::array probe; + const auto stencilSample = static_cast(stencilLayout.Offset(0u, 0u, 0u, 0u)); + if (mode == UniformPublication::Read) { + AgcDriver::GuestMemory::Read(target.stencilAddress, probe); + published(); + Require(DepthSurfaceAt(target.address) && DepthSurfaceView(context, target) == view, "a published D32S8 owner without HTILE was not reused"); + draw(0.5f); + AgcDriver::GuestMemory::Read(target.address, probe); + published(); + } else if (mode == UniformPublication::Pages) { + for (std::size_t index = 0u; index < addresses.size(); ++index) { + auto* guest = reinterpret_cast(addresses[index] + 4096u); + for (std::size_t offset = 0u; offset < 4096u; ++offset) { + guest[offset] ^= std::byte{0xff}; + expected[index][4096u + offset] = guest[offset]; + } + } + AgcDriver::GuestMemory::Read(target.address, probe); + published(); + Require(DepthSurfaceAt(target.address), "a D32S8 owner without HTILE that took guest bytes was retired"); + TargetRefuses([&] { DepthSurfaceView(context, target); }, "guest writes over native multisample depth are not implemented"); + } else { + const bool betweenPlanes = mode == UniformPublication::PlaneStore; + bool stored = false; + const PublicationHookScope hook([&](DepthPublicationStage stage) { + if (stored || stage != (betweenPlanes ? DepthPublicationStage::PlaneCommitted : DepthPublicationStage::GuestCopied)) return; + stored = true; + *reinterpret_cast(target.stencilAddress + stencilSample) = std::byte{0x7e}; + }); + TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address, probe); }, "guest writes raced native multisample depth publication"); + Require(stored && stencil.Data()[stencilSample] == std::byte{0x7e} && DepthSurfaceAt(target.address), "a CPU store to the stencil plane of a D32S8 owner without HTILE was overwritten or its owner retired"); + if (betweenPlanes) Require(std::equal(expected[0].begin(), expected[0].end(), memory.Data()), "the depth plane of a D32S8 owner without HTILE was not committed before its stencil plane"); + } + std::printf("Native D32S8 without HTILE publication samples=%u mode=%u checked\n", samples, static_cast(mode)); +} + int main(int argc, char** argv) { try { #ifdef __linux__ @@ -3269,6 +3351,18 @@ int main(int argc, char** argv) { } return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--publication-without-htile") { + TextureDetiler detiler(context); + auto publishing = context; + publishing.detiler = &detiler; + const auto supported = [&](std::uint32_t samples) { return DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) && (context.limits.framebufferStencilSampleCounts & samples) != 0u; }; + Require(supported(4u), "native depth publication without HTILE requires sampled D32S8 with four samples"); + for (const auto samples : {2u, 4u, 8u}) { + if (supported(samples)) CheckUniformPlanePublication(publishing, samples, UniformPublication::Read); + } + for (const auto mode : {UniformPublication::Pages, UniformPublication::StencilRace, UniformPublication::PlaneStore}) CheckUniformPlanePublication(publishing, 4u, mode); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--uniform-planes") { std::uint32_t checked = 0u, unavailable = 0u; for (const auto samples : {2u, 4u, 8u}) { diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 2c3c1b13c60f..4bba3acd5157 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -67,7 +67,7 @@ - [Native MSAA depth attachment feedback](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) is not implemented. A draw sampling a native multisampled depth view while attaching its source image throws before pipeline creation or render-pass recording, including read-only attachment feedback. Sampling that image in a separate draw or dispatch remains supported. The refusal compares the held native image views and their device and attachment identity; single-sample depth behavior is retained. - [Native D32S8 guest publication](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) overwrites a guest byte only when a CPU store of the snapshot's own value lands in a byte the host changes while that part commits; any other late store is kept and the read throws, leaving committed planes published and later reads throwing. It throws when a written page holds a byte equal to its snapshot while the host result differs and no driver store covers it: a same-value CPU store cannot be told from none. HTILE is published expanded, not as the console's compressed stream; the stencil plane is retiled with the depth XOR. The owner stays resident: a plane whose bytes are the host's is rebased, one that took a guest byte or has an unmapped hole throws at the next use. D16/D32-only owners, unproven clear transitions, resident colour aliases, a colour target acquired during a read, pending storage-image writes and publication inside GPU completions throw. - [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, and D32 with an 8-bit stencil plane whose state is in HTILE or, with a depth-only HTILE, in the plane, two/four/eight samples, one Z_X slice and mip, and pipe-aligned HTILE, or a depth-only HTILE that is not pipe-aligned whose words are all one clear word, without VRS, with ITERATE_256 on or off: the initial import reads no compressed Z planes, and Mesa's HTILE clear words (`radv_get_htile_fast_clear_value`) and Addrlib's HTILE footprint do not depend on ITERATE_256, which `ac_descriptors.c` ties to TC reads of compressed MSAA HTILE. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one and, with the stencil in HTILE, SMem 0 (the stencil holds `DB_STENCIL_CLEAR`), or with a depth-only HTILE every stencil plane byte must equal it; and the complete sample-aware depth, stencil and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes and unproven HTILE clear/fill transitions. A fill of the whole HTILE, or a one-byte fill of the whole stencil plane (a stencil clear), is proven at the next sample like an HTILE mask clear: complete tracked stores since the fill, or a fill still queued that is the one write recorded over the range since it was noted (a fill noted again once that write is recorded counts once), and every word proving the depth and, with the stencil in HTILE, the stencil clear, or every plane byte the fill byte; partial fills, an HTILE mask clear of a target with stencil, and an HTILE fill noted while an HTILE mask clear dispatch is pending (from its notification until a sample takes it) are refused. A queued fill of more than 64 KiB is stored through a host import, so where host imports are compared rather than write-watched (always on Windows, and on Linux where the import probe finds that imports resolve write protection) its HTILE is no longer write-watched after the store and the fill is refused. A stencil plane whose exact footprint a resident multisampled R8 colour target of the same sample count covers takes its value from that target's newest uniform content (a fill it absorbed, a CMASK fast clear, or a recorded clear with no draw since), and a plane fill is proven the same way, without reading guest memory; any colour target over the depth or HTILE, or a colour draw over a plane taken this way, refuses sampling, and other colour content over the plane leaves it unproven as listed below. A descriptor without metadata whose XOR and ITERATE_256 match is sampled from the decompressed host image only after an observed in-place expand, a draw over the target with `DB_RENDER_CONTROL.DEPTH_COMPRESS_DISABLE` and, with a stencil plane, `STENCIL_COMPRESS_DISABLE`, and until an HTILE clear or fill, a plane fill or a clear draw; a later depth write that recompresses tiles is not observed. Other metadata is refused. A guest read over the depth or HTILE range of a surface the host has not written since import (draws with depth or stencil writes, depth/stencil copies, storage write-back; not clears after a proven HTILE clear) retires it instead of throwing; that a draw without those writes leaves the console's Z planes and HTILE unchanged is inferred, not measured. Arbitrary guest depth import, raw-bit sampling, arrays and storage access remain unimplemented; stencil plane sampling is listed below. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in [#419](https://github.com/boykopovar/AnyPS5/pull/419#issuecomment-6105597729). -- [Native multisampled D32S8 depth without HTILE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) is imported only when its depth plane repeats one float in [0, 1], its separate stencil plane repeats one byte and no multisampled colour target covers either plane; the host image starts at those values and the sampled descriptor must name no metadata, whose pipe-aligned and ITERATE_256 bits are then not compared. The depth plane is sampled as a 32-bit float and the stencil plane through the R8_UINT descriptor of the stencil plane sampling entry below. That every sample then holds them is inferred from the uncompressed layout, not measured on the console. Planes holding more than one value, and D16 or D32 without HTILE, still throw. +- [Native multisampled D32S8 depth without HTILE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) is imported only when its depth plane repeats one float in [0, 1], its separate stencil plane repeats one byte and no multisampled colour target covers either plane; the host image starts at those values and the sampled descriptor must name no metadata, whose pipe-aligned and ITERATE_256 bits are then not compared. The depth plane is sampled as a 32-bit float and the stencil plane through the R8_UINT descriptor of the stencil plane sampling entry below. That every sample then holds them is inferred from the uncompressed layout, not measured on the console. A written owner is published on a guest read as the D32S8 guest publication entry above describes, to its depth and stencil planes, with no HTILE to expand. Planes holding more than one value, and D16 or D32 without HTILE, still throw. - [Native multisample stencil plane drawn as colour](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a native D32S8 target whose separate stencil plane a multisampled colour target covers with anything but a known R8 value of the same sample count is imported with the plane unproven: its host stencil is not taken from it, and a stencil-tested draw or a sample of the plane refuses (`native multisample stencil plane aliases a multisampled colour target whose samples are only on the host`) until a whole-plane fill gives its byte, which a colour target still over the plane must have absorbed as the same R8 byte at the same sample count. Taking a drawn plane without a fill needs a GPU copy of the colour target's R8 samples, in the same sample order, into the depth image's stencil aspect, which is not implemented. Whether the console's stencil reads of a plane written through the colour path match that copy is not measured. - [Native multisample stencil plane sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - the separate stencil plane of a native multisampled D32S8 target with a depth-only HTILE is sampled through an R8_UINT 2D MSAA descriptor at the plane with the target's extent, samples, tile mode and XOR and no metadata, as a stencil-aspect view of the host depth image or of its sample shadow, which then copies the stencil with the depth. A stencil kept in HTILE, another format, metadata or layout is refused, and a plane left unproven by a colour target refuses as listed above. Whether the console's reads of the plane through such a descriptor match the host stencil is not measured. - [sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - the five validation calls return `0x8A6C1000` without reading their arguments or writing caller memory, and `sceAgcDriverGetShaderDebuggingStatus` returns 1, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L250-L291) reads them from Sony's libSceAgcDriver.sprx: each validation export is `mov eax, 0x8a6c1000; ret` in the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them From fa590e435ae15b7a606b7090ddd0f80215cd9baa Mon Sep 17 00:00:00 2001 From: hugodearaujo Date: Wed, 4 Nov 2026 07:10:34 -0300 Subject: [PATCH 2/3] test(agc): drop the assertion that guest writes over a no-HTILE owner stay unimplemented --- .../libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp index 9238c37dddfe..6567e64bde29 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp @@ -3414,7 +3414,6 @@ void CheckUniformPlanePublication(Context context, std::uint32_t samples, Unifor AgcDriver::GuestMemory::Read(target.address, probe); published(); Require(DepthSurfaceAt(target.address), "a D32S8 owner without HTILE that took guest bytes was retired"); - TargetRefuses([&] { DepthSurfaceView(context, target); }, "guest writes over native multisample depth are not implemented"); } else { const bool betweenPlanes = mode == UniformPublication::PlaneStore; bool stored = false; From b61aa1e53bbf5dfa128b06c82f8da7b2505bf194 Mon Sep 17 00:00:00 2001 From: hugodearaujo Date: Fri, 6 Nov 2026 08:00:47 -0300 Subject: [PATCH 3/3] fix(agc): import a native multisample D32S8 target over uneven planes once its first draw wrote every sample --- .../Graphics/include/DepthSurface.hpp | 2 +- .../Graphics/src/DepthSurface.cpp | 159 +++++++++++++----- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 11 +- ...river_native_multisample_depth_tests.cmake | 5 + .../execution/NativeMultisampleDepth.cpp | 148 ++++++++++++++++ docs/dev/debt/libSceAgcDriver.md | 2 +- 6 files changed, 278 insertions(+), 49 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index 03f158fc0c71..839bed1eaa3b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -18,7 +18,7 @@ class Texture; class StorageTexture; VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten = true, bool stencilTested = false); -VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested, bool stencilWritten); +VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested, bool stencilWritten, std::optional overwrittenStencil = std::nullopt); SampleOrderTarget DepthSurfaceOrderTarget(const Context& context, const DepthTarget& target); DepthSampleLocations DepthSurfaceSampleLocations(const Context& context, const DepthTarget& target); enum class DepthPlaneScope { Overlapped, Whole }; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 1134e43e57c2..c329587d3ea9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -220,6 +220,11 @@ std::atomic& anyDepthGuard() { return any; } +std::atomic& pendingOverwriteProofs() { + static std::atomic count{0u}; + return count; +} + struct DepthSamplingState { std::vector initial; std::optional initialDepth; @@ -228,6 +233,8 @@ struct DepthSamplingState { std::optional aliasStencil; std::optional unprovenStencil; std::optional depthAlias; + bool unevenPlanes = false; + std::optional overwriteStencil; void Refuse(const char* reason) { if (refusal.empty()) refusal = reason; @@ -344,9 +351,11 @@ DepthSamplingState prepareUniformPlaneSampling(const Context& context, const Dep } const auto depthWord = uniformPlaneValue(depth.address, depth.bytes); const auto depthValue = depthWord ? std::bit_cast(*depthWord) : 0.0f; + state.unevenPlanes = true; if (!depthWord || !(depthValue >= 0.0f && depthValue <= 1.0f)) return refuse("native multisample depth plane without HTILE does not hold one depth value in [0, 1]"); const auto stencilByte = uniformPlaneValue(stencil.address, stencil.bytes); if (!stencilByte) return refuse("native multisample stencil plane without HTILE does not hold one stencil value"); + state.unevenPlanes = false; for (const auto& range : state.initial) GuestMemory::CollectWritesUncached(range.address, range.bytes); if (!GuestMemory::UnchangedSinceAll(state.initial)) return refuse("guest writes raced native multisample depth import"); state.initialDepth = depthValue; @@ -634,14 +643,19 @@ class DepthSurface { release(); throw; } + if (this->sampling.overwriteStencil) pendingOverwriteProofs().fetch_add(1u, std::memory_order_acq_rel); } - ~DepthSurface() { release(); } + ~DepthSurface() { + if (sampling.overwriteStencil) pendingOverwriteProofs().fetch_sub(1u, std::memory_order_acq_rel); + release(); + } DepthSurface(const DepthSurface&) = delete; DepthSurface& operator=(const DepthSurface&) = delete; void CheckCurrent() { if (target.samples == 1u) return; + ProveOverwrite(); const auto clearLease = clearGeneration == 0u && stencilClearGeneration == 0u ? GuestAllocations::Lease{} : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); Require(active->load(std::memory_order_acquire), "the native multisample depth owner was released"); Require(sampling.refusal.empty(), sampling.refusal); @@ -935,28 +949,44 @@ class DepthSurface { } void NoteHostWrite() { - if (samplingActive && target.format == VK_FORMAT_D32_SFLOAT_S8_UINT && context.detiler != nullptr && !hostWritten->load(std::memory_order_acquire)) { - try { - const auto lease = GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); - CheckCurrent(); - std::vector captured; - for (const auto& range : sampling.initial) captured.push_back(CapturePublication(range)); - for (const auto& plane : captured) { - Require(GuestMemory::UnchangedSinceCollected(plane.range.address, plane.range.bytes, plane.range.generation), "guest writes raced native multisample depth publication capture"); - Require(GuestMemory::UnchangedSince(plane.range.address, plane.range.bytes, plane.range.generation), "guest writes raced native multisample depth publication capture"); - } - for (const auto& range : sampling.initial) Require(GuestMemory::UnchangedSinceCollected(range.address, range.bytes, range.generation), "guest writes raced native multisample depth publication capture"); - Require(GuestMemory::UnchangedSinceAll(sampling.initial), "guest writes raced native multisample depth publication capture"); - publication = std::move(captured); - } catch (...) { - publicationFailure = std::current_exception(); - publication.clear(); - } - } + if (samplingActive && !hostWritten->load(std::memory_order_acquire)) capturePublication(); hostEverWritten->store(true, std::memory_order_release); hostWritten->store(true, std::memory_order_release); } + void ProveOverwrite() { + if (!sampling.overwriteStencil || !hostEverWritten->load(std::memory_order_acquire) || Recorder::InCompletion()) return; + const auto value = *sampling.overwriteStencil; + sampling.overwriteStencil.reset(); + pendingOverwriteProofs().fetch_sub(1u, std::memory_order_acq_rel); + if (!sampleable || context.detiler == nullptr || !active->load(std::memory_order_acquire)) return; + if (auto* recorder = RecorderFor(context)) recorder->Sync(); + for (const auto& range : sampling.initial) { + StorageTexture::FlushPending(range.address, range.bytes); + GuestMemory::FlushGpuWrites(range.address, range.bytes); + } + if (useSampleShadow) { + if (sampleShadow == nullptr) createSampleShadow(); + refreshSampleShadow(true, true); + if (auto* recorder = RecorderFor(context)) recorder->Sync(); + } + const auto samples = static_cast(target.extent.width) * target.extent.height * target.samples; + const auto stencil = readBackSamples(VK_IMAGE_ASPECT_STENCIL_BIT, samples); + const auto bytes = stencil->Bytes().first(static_cast(samples)); + if (std::ranges::any_of(bytes, [&](std::byte stored) { return stored != static_cast(value); })) return; + for (const auto& range : sampling.initial) GuestMemory::CollectWritesUncached(range.address, range.bytes); + if (!GuestMemory::UnchangedSinceAll(sampling.initial)) return; + sampling.refusal.clear(); + { + std::unique_lock guardLock(depthGuardMutex()); + for (const auto& range : sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, active, {{range.address, range.bytes, 0u}}, hostWritten, hostEverWritten}); + anyDepthGuard().store(true, std::memory_order_release); + } + InstallResidentImageReadGuard_nid_no_patch(); + samplingActive = true; + capturePublication(); + } + void PublishToGuest(); std::uint64_t written = ++depthWrites; @@ -1534,6 +1564,54 @@ class DepthSurface { bool publicationFailed = false; std::exception_ptr publicationFailure; + void capturePublication() { + if (target.format != VK_FORMAT_D32_SFLOAT_S8_UINT || context.detiler == nullptr) return; + try { + const auto lease = GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); + CheckCurrent(); + std::vector captured; + for (const auto& range : sampling.initial) captured.push_back(CapturePublication(range)); + for (const auto& plane : captured) { + Require(GuestMemory::UnchangedSinceCollected(plane.range.address, plane.range.bytes, plane.range.generation), "guest writes raced native multisample depth publication capture"); + Require(GuestMemory::UnchangedSince(plane.range.address, plane.range.bytes, plane.range.generation), "guest writes raced native multisample depth publication capture"); + } + for (const auto& range : sampling.initial) Require(GuestMemory::UnchangedSinceCollected(range.address, range.bytes, range.generation), "guest writes raced native multisample depth publication capture"); + Require(GuestMemory::UnchangedSinceAll(sampling.initial), "guest writes raced native multisample depth publication capture"); + publication = std::move(captured); + } catch (...) { + publicationFailure = std::current_exception(); + publication.clear(); + } + } + + std::unique_ptr readBackSamples(VkImageAspectFlagBits aspect, std::uint64_t elements) { + const bool depth = aspect == VK_IMAGE_ASPECT_DEPTH_BIT; + const auto linearBytes = (elements * (depth ? 4u : 1u) + 3u) & ~std::uint64_t{3u}; + Require(linearBytes <= context.limits.maxStorageBufferRange, "native multisample depth publication exceeds the device's readback range"); + auto linear = std::make_unique(context, static_cast(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + VkImageView arrayView = VK_NULL_HANDLE; + try { + VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + info.image = useSampleShadow ? sampleShadow->image : image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + info.format = hostFormat; + info.subresourceRange = {static_cast(aspect), 0u, 1u, 0u, 1u}; + Check(context.Function("vkCreateImageView")(context.device, &info, nullptr, &arrayView), "vkCreateImageView depth publication"); + CommandBatch batch(context); + constexpr VkAccessFlags writes = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT; + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, writes, VK_ACCESS_SHADER_READ_BIT); + context.detiler->DispatchMultisampleReadback(batch.Handle(), depth ? VK_FORMAT_D32_SFLOAT : VK_FORMAT_R8_UINT, linear->Handle(), 0u, linearBytes, arrayView, target.extent.width, target.extent.height, 1u, target.samples, 0u, static_cast(elements)); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + batch.SubmitAndWait(); + } catch (...) { + if (arrayView != VK_NULL_HANDLE) context.Function("vkDestroyImageView")(context.device, arrayView, nullptr); + throw; + } + context.Function("vkDestroyImageView")(context.device, arrayView, nullptr); + linear->Invalidate(); + return linear; + } + PublicationPlane CapturePublication(const GuestMemory::UnchangedQuery& range) { PublicationPlane plane; plane.range = range; @@ -2413,34 +2491,11 @@ void DepthSurface::PublishToGuest() { for (const std::size_t index : {std::size_t{0u}, stencilPlane}) { const auto elementBytes = index == 0u ? 4u : 1u; const auto aspect = index == 0u ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT; - const auto format = index == 0u ? VK_FORMAT_D32_SFLOAT : VK_FORMAT_R8_UINT; const MultisampleTextureLayout layout(TextureTileMode::kZ64KBX, elementBytes, target.samples, target.extent.width, target.extent.height, 1u, target.pipeBankXor); Require(layout.Bytes() == publication[index].range.bytes, "native multisample depth publication layout differs from its guarded range"); - const auto linearBytes = (layout.LinearBytes() + 3u) & ~std::uint64_t{3u}; - Require(linearBytes <= context.limits.maxStorageBufferRange, "native multisample depth publication exceeds the device's readback range"); - Buffer linear(context, static_cast(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - VkImageView arrayView = VK_NULL_HANDLE; - try { - VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - info.image = useSampleShadow ? sampleShadow->image : image; - info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; - info.format = hostFormat; - info.subresourceRange = {static_cast(aspect), 0u, 1u, 0u, 1u}; - Check(context.Function("vkCreateImageView")(context.device, &info, nullptr, &arrayView), "vkCreateImageView depth publication"); - CommandBatch batch(context); - constexpr VkAccessFlags writes = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT; - RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, writes, VK_ACCESS_SHADER_READ_BIT); - context.detiler->DispatchMultisampleReadback(batch.Handle(), format, linear.Handle(), 0u, linearBytes, arrayView, target.extent.width, target.extent.height, 1u, target.samples, 0u, static_cast(layout.LinearBytes() / elementBytes)); - RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); - batch.SubmitAndWait(); - } catch (...) { - if (arrayView != VK_NULL_HANDLE) context.Function("vkDestroyImageView")(context.device, arrayView, nullptr); - throw; - } - context.Function("vkDestroyImageView")(context.device, arrayView, nullptr); - linear.Invalidate(); + const auto linear = readBackSamples(aspect, layout.LinearBytes() / elementBytes); result[index] = publication[index].Expand(); - layout.Tile(linear.Bytes().first(static_cast(layout.LinearBytes())), result[index]); + layout.Tile(linear->Bytes().first(static_cast(layout.LinearBytes())), result[index]); } if (htile) { result[1] = publication[1].Expand(); @@ -2954,7 +3009,16 @@ std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32 return width * height * bytesPerTexel * samples; } +void proveOverwrittenPlanes(std::uint64_t address, std::size_t bytes) { + std::lock_guard gpu(GuestMemory::GpuMutex()); + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (surface->sampling.overwriteStencil && surface->active->load(std::memory_order_acquire) && surface->Overlaps(address, bytes)) surface->ProveOverwrite(); + } +} + void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes, std::optional> htileClear, bool allowPublication) { + if (bytes != 0u && pendingOverwriteProofs().load(std::memory_order_acquire) != 0u && allowPublication && !HoldsMultisampleTargetsLock()) proveOverwrittenPlanes(address, bytes); if (bytes == 0u || !anyDepthGuard().load(std::memory_order_acquire)) return; Require(address <= std::numeric_limits::max() - bytes, "native depth read range overflows"); const auto overlaps = [&](const DepthGuard& range) { return address < range.address + range.bytes && range.address < address + bytes; }; @@ -3092,7 +3156,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, return DepthSurfaceViewWithStencilWriteIntent(context, target, depthWritten, stencilTested, true); } -VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested, bool stencilWritten) { +VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested, bool stencilWritten, std::optional overwrittenStencil) { const auto planeBytes = [&](std::uint32_t bytesPerTexel) { return target.samples == 1u ? DepthSliceBytes(target.extent, bytesPerTexel) : MultisampledDepthBytes_nid_no_patch(target.extent, bytesPerTexel, target.samples); }; ClaimMultisampleTargetMemory(target.address, planeBytes(target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT ? 2u : 4u)); if (target.stencilAddress != 0u) ClaimMultisampleTargetMemory(target.stencilAddress, planeBytes(1u)); @@ -3110,6 +3174,7 @@ VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const } } const bool created = surface == nullptr; + if (!created) surface->ProveOverwrite(); GuestAllocations::Lease creationLease; if (created) { const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; @@ -3129,6 +3194,10 @@ VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const } } auto sampling = target.samples == 1u ? DepthSamplingState{} : prepareDepthSampling(context, target); + if (sampling.unevenPlanes && overwrittenStencil) { + sampling.overwriteStencil = overwrittenStencil; + sampling.initialStencil = static_cast(~*overwrittenStencil); + } creationLease = target.samples == 1u || !sampling.refusal.empty() ? GuestAllocations::Lease{} : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); lock.lock(); releaseRetired(context); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index c7559238f7af..b8bd28097b50 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -400,6 +400,13 @@ bool writesDepthTarget(const State& state) { return state.depthWrite || writesStencilTarget(state); } +std::optional overwrittenStencil(const State& state) { + const auto replaces = [](const VkStencilOpState& face) { return face.compareOp == VK_COMPARE_OP_ALWAYS && face.passOp == VK_STENCIL_OP_REPLACE && face.writeMask == 0xffu; }; + if (!state.depthTest || !state.depthWrite || state.depthCompare != VK_COMPARE_OP_ALWAYS || state.depthBoundsTest || !state.stencilTest) return std::nullopt; + if (!replaces(state.stencilFront) || !replaces(state.stencilBack) || state.stencilFront.reference != state.stencilBack.reference) return std::nullopt; + return static_cast(state.stencilFront.reference); +} + std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) { if (color.mipCount != 1 || color.depth != 1 || !color.dccPipeAligned) return DccKeyBytes(bytes); return DccKeyCount(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, bytes); @@ -3102,7 +3109,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara targetViews.push_back(binding.target->View()); } if (state.depth) { - targetViews.push_back(DepthSurfaceViewWithStencilWriteIntent(context, *state.depth, state.depthWrite, state.stencilTest, writesStencilTarget(state))); + targetViews.push_back(DepthSurfaceViewWithStencilWriteIntent(context, *state.depth, state.depthWrite, state.stencilTest, writesStencilTarget(state), overwrittenStencil(state))); } timer.phase(PhasePrepare); const bool recordDraws = RecordDraws(); @@ -3888,7 +3895,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con } if (recipe.hasDepth) { timer.phase(PhaseSetup); - const auto depthView = DepthSurfaceViewWithStencilWriteIntent(context, *state.depth, state.depthWrite, state.stencilTest, writesStencilTarget(state)); + const auto depthView = DepthSurfaceViewWithStencilWriteIntent(context, *state.depth, state.depthWrite, state.stencilTest, writesStencilTarget(state), overwrittenStencil(state)); timer.phase(PhaseReadTarget); if (recipe.targetViews.back() != depthView) return miss(DrawRecipeMiss::TargetGone); } diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake index e976a4fa2646..884345bbf120 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake @@ -27,6 +27,11 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_driver_native_depth_publication agc_driver_native_depth_publication_without_htile PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_driver_native_depth_overwritten_planes COMMAND agc_driver_native_multisample_depth_tests --overwritten-planes) + set_tests_properties(agc_driver_native_depth_overwritten_planes PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_native_depth_overwritten_planes PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_native_depth_driver_path COMMAND agc_driver_native_multisample_depth_tests --driver-path) add_test(NAME agc_driver_native_depth_driver_queued_fill COMMAND agc_driver_native_multisample_depth_tests --driver-queued-fill) set_tests_properties(agc_driver_native_depth_driver_path agc_driver_native_depth_driver_queued_fill PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp index 6567e64bde29..42c0421ce021 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp @@ -3429,6 +3429,142 @@ void CheckUniformPlanePublication(Context context, std::uint32_t samples, Unifor std::printf("Native D32S8 without HTILE publication samples=%u mode=%u checked\n", samples, static_cast(mode)); } +enum class OverwrittenPlanes { NextDraw, SampledFirst, GuestAccess, Partial, OtherValue, GuestWrite, PendingStorage }; + +void CheckOverwrittenPlanes(Context context, std::uint32_t samples, OverwrittenPlanes mode) { + constexpr VkExtent2D extent{200u, 130u}, inner{193u, 129u}; + constexpr std::uint8_t reference = 0x21u; + const auto depthBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, samples)); + TargetMemory memory(depthBytes, true), stencil(stencilBytes, true); + for (std::size_t offset = 0u; offset < depthBytes; offset += 4u) { + const std::uint32_t word = offset % 12u == 0u ? 0x4c531a62u : offset % 12u == 4u ? 0xffffff00u : static_cast(offset * 2654435761u); + std::memcpy(memory.Data() + offset, &word, 4u); + } + for (std::size_t offset = 0u; offset < stencilBytes; ++offset) stencil.Data()[offset] = static_cast(offset * 37u); + std::array, 2> expected{std::vector(memory.Data(), memory.Data() + depthBytes), std::vector(stencil.Data(), stencil.Data() + stencilBytes)}; + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, 1.0f, 0u, 0u, false, samples}; + target.depthSwizzle = 24u; + target.pipeBankXor = samples == 2u ? 0u : 0x8c00u; + auto partial = target; + partial.extent = inner; + const MultisampleTextureLayout depthLayout(TextureTileMode::kZ64KBX, 4u, samples, extent.width, extent.height, 1u, target.pipeBankXor); + const MultisampleTextureLayout stencilLayout(TextureTileMode::kZ64KBX, 1u, samples, extent.width, extent.height, 1u, target.pipeBankXor); + Recorder recorder(context); + recorder.Activate(); + std::unique_ptr first, second; + std::shared_ptr texture; + std::shared_ptr image; + struct Cleanup { + Recorder& recorder; + Context context; + ~Cleanup() { recorder.Sync(); ClearCachedTextures(context.device); ClearDepthSurfaces(context.device); ClearMultisampleTargets(context.device); } + } cleanup{recorder, context}; + auto words = DepthSampleWords(target); + words[6] &= ~((1u << 19u) | (1u << 21u)); + const auto lookup = [&] { return DepthSurfaceTexture(context, words, DecodeTextureResource(words), VkComponentMapping{}); }; + const auto view = DepthSurfaceViewWithStencilWriteIntent(context, target, true, true, true, reference); + Require(view != VK_NULL_HANDLE && DepthSurfaceAt(target.address), "a target over uneven planes whose first draw overwrites them has no attachment"); + const std::array, 1> written{{{memory.Address(), memory.Address() + depthBytes}}}; + if (mode == OverwrittenPlanes::PendingStorage) { + GuestTextureResource storage{}; + storage.baseAddress = memory.Address(); + storage.width = 256u; + storage.height = static_cast(depthBytes / 1024u); + storage.mipCount = storage.allocatedMipCount = 1u; + storage.dimension = TextureDimension::k2D; + storage.tileMode = TextureTileMode::kLinear; + storage.format = 22u; + storage.dstSelX = 4u; + storage.dstSelY = storage.dstSelZ = 0u; + storage.dstSelW = 1u; + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + image = StorageTexture::Create(context, *context.detiler, storage, 0u); + const auto commands = recorder.Commands(); + recorder.Keep(image); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}; + VkClearColorValue value{}; + value.uint32[0] = 0x3c3c3c3cu; + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + context.Function("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1u, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + image->MarkWholeWritten(); + } + const auto& drawn = mode == OverwrittenPlanes::Partial ? partial : target; + first = std::make_unique(context, drawn, view, StencilUse{VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, mode == OverwrittenPlanes::OtherValue ? static_cast(reference + 1u) : reference}, false, false); + NoteDepthSurfaceWrite(context, target); + first->Run(recorder.Commands(), 0.25f); + const auto record = [&](VkExtent2D area, float base, std::uint8_t value) { + for (std::uint32_t y = 0u; y < area.height; ++y) { + for (std::uint32_t x = 0u; x < area.width; ++x) { + for (std::uint32_t sample = 0u; sample < samples; ++sample) { + const float depth = base + static_cast(sample + 1u) / 16.0f; + std::memcpy(expected[0].data() + depthLayout.Offset(x, y, 0u, sample), &depth, 4u); + expected[1][stencilLayout.Offset(x, y, 0u, sample)] = static_cast(value); + } + } + } + }; + record(extent, 0.25f, reference); + if (mode == OverwrittenPlanes::GuestWrite) memory.Data()[depthLayout.Offset(3u, 2u, 0u, 0u)] ^= std::byte{0x10}; + if (mode == OverwrittenPlanes::Partial || mode == OverwrittenPlanes::OtherValue || mode == OverwrittenPlanes::GuestWrite || mode == OverwrittenPlanes::PendingStorage) { + Require(DepthSurfaceView(context, target) == view, "a refused owner over uneven planes was not reused by its next draw"); + TargetRefuses([&] { lookup(); }, "native multisample depth plane without HTILE does not hold one depth value in [0, 1]"); + if (mode == OverwrittenPlanes::PendingStorage) Require(!StorageTexture::AnyPendingOverlaps(written) && std::all_of(memory.Data(), memory.Data() + depthBytes, [](std::byte value) { return value == std::byte{0x3c}; }), "storage results recorded over the planes before the proof were not stored to them"); + std::printf("Native depth overwritten planes samples=%u mode=%u: the import stays refused\n", samples, static_cast(mode)); + return; + } + if (mode == OverwrittenPlanes::NextDraw) { + Require(DepthSurfaceView(context, target) == view, "the owner over overwritten planes was not reused by its next draw"); + second = std::make_unique(context, partial, view, StencilUse{VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x40u}, false, false); + NoteDepthSurfaceWrite(context, target); + second->Run(recorder.Commands(), 0.5f); + record(inner, 0.5f, 0x40u); + } + recorder.Sync(); + if (mode == OverwrittenPlanes::GuestAccess) { + RequireNoMultisampleTarget(memory.Address(), depthBytes, "a storage image access"); + for (std::size_t plane = 0u; plane < 2u; ++plane) { + const auto* guest = plane == 0u ? memory.Data() : stencil.Data(); + Require(std::equal(expected[plane].begin(), expected[plane].end(), guest), std::string("a guest access over an overwritten owner did not publish its samples over the ") + (plane == 0u ? "depth plane" : "stencil plane")); + } + std::printf("Native depth overwritten planes samples=%u mode=%u: imported and published by a guest access after its first draw\n", samples, static_cast(mode)); + return; + } + texture = lookup(); + Require(texture != nullptr && texture->View() != VK_NULL_HANDLE, "an owner whose first draw overwrote its uneven planes was not sampled natively"); + const auto count = extent.width * extent.height * samples; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + { + const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, samples, false, true)); + CommandBatch batch(context); + fetch.Run(batch.Handle(), count); + RecordMemoryBarrier(context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + batch.SubmitAndWait(); + } + output.Invalidate(); + for (std::uint32_t index = 0u; index < count; ++index) { + const auto sample = index % samples; + const auto x = index / samples % extent.width; + const auto y = index / samples / extent.width; + const float base = mode == OverwrittenPlanes::NextDraw && x < inner.width && y < inner.height ? 0.5f : 0.25f; + float sampled = 0.0f; + std::memcpy(&sampled, output.Bytes().data() + (index + 1u) * 16u, 4u); + Require(sampled == base + static_cast(sample + 1u) / 16.0f, "an overwritten owner does not hold its drawn sample at " + std::to_string(x) + "," + std::to_string(y) + " sample " + std::to_string(sample)); + } + texture.reset(); + std::array probe; + AgcDriver::GuestMemory::Read(target.stencilAddress, probe); + for (std::size_t plane = 0u; plane < 2u; ++plane) { + const auto* guest = plane == 0u ? memory.Data() : stencil.Data(); + for (std::size_t offset = 0u; offset < expected[plane].size(); ++offset) { + if (guest[offset] != expected[plane][offset]) Require(false, std::string("an overwritten owner did not publish its samples over its uneven guest bytes, keeping the padding, at byte ") + std::to_string(offset) + (plane == 0u ? " of the depth plane" : " of the stencil plane")); + } + } + std::printf("Native depth overwritten planes samples=%u mode=%u: imported after its first draw wrote every sample, sampled and published\n", samples, static_cast(mode)); +} + int main(int argc, char** argv) { try { #ifdef __linux__ @@ -3519,6 +3655,18 @@ int main(int argc, char** argv) { for (const auto mode : {UniformPublication::Pages, UniformPublication::StencilRace, UniformPublication::PlaneStore}) CheckUniformPlanePublication(publishing, 4u, mode); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--overwritten-planes") { + TextureDetiler detiler(context); + auto publishing = context; + publishing.detiler = &detiler; + std::uint32_t checked = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) || (context.limits.framebufferStencilSampleCounts & samples) == 0u) continue; + for (const auto mode : {OverwrittenPlanes::NextDraw, OverwrittenPlanes::SampledFirst, OverwrittenPlanes::GuestAccess, OverwrittenPlanes::Partial, OverwrittenPlanes::OtherValue, OverwrittenPlanes::GuestWrite, OverwrittenPlanes::PendingStorage}) CheckOverwrittenPlanes(publishing, samples, mode); + ++checked; + } + return checked != 0u ? 0 : VulkanTestSkipped; + } if (argc == 2 && std::string_view(argv[1]) == "--uniform-planes") { std::uint32_t checked = 0u, unavailable = 0u; for (const auto samples : {2u, 4u, 8u}) { diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 0469dc2c8e42..fd02b9a8b9c2 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -69,7 +69,7 @@ - [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, and D32 with an 8-bit stencil plane whose state is in HTILE or, with a depth-only HTILE, in the plane, two/four/eight samples, one Z_X slice and mip, and pipe-aligned HTILE, or a depth-only HTILE that is not pipe-aligned whose words are all one clear word, without VRS, with ITERATE_256 on or off: the initial import reads no compressed Z planes, and Mesa's HTILE clear words (`radv_get_htile_fast_clear_value`) and Addrlib's HTILE footprint do not depend on ITERATE_256, which `ac_descriptors.c` ties to TC reads of compressed MSAA HTILE. Sampling eligibility is recorded before the attachment's first host clear: every HTILE word must prove the same initial clear of zero or one and, with the stencil in HTILE, SMem 0 (the stencil holds `DB_STENCIL_CLEAR`), or with a depth-only HTILE every stencil plane byte must equal it; and the complete sample-aware depth, stencil and HTILE ranges must be accessible and write-watched. Unsupported initial state or device sampling support retains attachment rendering but sampling throws; eligibility is never retried. Sampling rejects later guest writes, queued external GPU writes and unproven HTILE clear/fill transitions. A fill of the whole HTILE, or a one-byte fill of the whole stencil plane (a stencil clear), is proven at the next sample like an HTILE mask clear: complete tracked stores since the fill, or a fill still queued that is the one write recorded over the range since it was noted (a fill noted again once that write is recorded counts once), and every word proving the depth and, with the stencil in HTILE, the stencil clear, or every plane byte the fill byte; partial fills, an HTILE mask clear of a target with stencil, and an HTILE fill noted while an HTILE mask clear dispatch is pending (from its notification until a sample takes it) are refused. A queued fill of more than 64 KiB is stored through a host import, so where host imports are compared rather than write-watched (always on Windows, and on Linux where the import probe finds that imports resolve write protection) its HTILE is no longer write-watched after the store and the fill is refused. A stencil plane whose exact footprint a resident multisampled R8 colour target of the same sample count covers takes its value from that target's newest uniform content (a fill it absorbed, a CMASK fast clear, or a recorded clear with no draw since), and a plane fill is proven the same way, without reading guest memory; any colour target over the depth or HTILE, or a colour draw over a plane taken this way, refuses sampling, and other colour content over the plane leaves it unproven as listed below. A descriptor without metadata whose XOR and ITERATE_256 match is sampled from the decompressed host image only after an observed in-place expand, a draw over the target with `DB_RENDER_CONTROL.DEPTH_COMPRESS_DISABLE` and, with a stencil plane, `STENCIL_COMPRESS_DISABLE`, and until an HTILE clear or fill, a plane fill or a clear draw; a later depth write that recompresses tiles is not observed. Other metadata is refused. A guest read over the depth or HTILE range of a surface the host has not written since import (draws with depth or stencil writes, depth/stencil copies, storage write-back; not clears after a proven HTILE clear) retires it instead of throwing; that a draw without those writes leaves the console's Z planes and HTILE unchanged is inferred, not measured. Arbitrary guest depth import, raw-bit sampling, arrays and storage access remain unimplemented; stencil plane sampling is listed below. The block footprint follows main's AMD Addrlib thin-block rule; the accepted HTILE clear words and PS5 equivalence are not measured on console hardware. PPSA99902's historical resident eight-sample D32 reader is documented in [#419](https://github.com/boykopovar/AnyPS5/pull/419#issuecomment-6105597729). - [Native multisampled depth sampling under a colour target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - that a depth sample of tiles HTILE proves cleared reads `DB_DEPTH_CLEAR` whatever a colour target stored over the depth plane follows the HTILE fast-clear rule and is not measured on PS5 hardware. As an exception to the colour-target refusal above, one multisampled colour target of the depth target's sample count over the depth plane is accepted once every HTILE word proves the clear, in either HTILE layout. It refuses at the next sample once that target clears or draws again, takes any fill but one or more identical ones over its whole range of exactly its recorded clear value's bits, or another colour target covers the plane. - [Native multisampled depth sampling under a colour target over unproven tiles or the stencil plane](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - not implemented: a colour target over depth tiles HTILE does not prove cleared still refuses sampling, and with the stencil in HTILE, the stencil is still proven only by SMem 0 and a colour target over the stencil plane refuses. -- [Native multisampled D32S8 depth without HTILE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) is imported only when its depth plane repeats one float in [0, 1], its separate stencil plane repeats one byte and no multisampled colour target covers either plane; the host image starts at those values and the sampled descriptor must name no metadata, whose pipe-aligned and ITERATE_256 bits are then not compared. The depth plane is sampled as a 32-bit float and the stencil plane through the R8_UINT descriptor of the stencil plane sampling entry below. That every sample then holds them is inferred from the uncompressed layout, not measured on the console. A written owner is published on a guest read as the D32S8 guest publication entry above describes, to its depth and stencil planes, with no HTILE to expand. Planes holding more than one value, and D16 or D32 without HTILE, still throw. +- [Native multisampled D32S8 depth without HTILE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) is imported when its depth plane repeats one float in [0, 1], its separate stencil plane repeats one byte and no multisampled colour target covers either plane; the host image starts at those values and the sampled descriptor must name no metadata, whose pipe-aligned and ITERATE_256 bits are then not compared. The depth plane is sampled as a 32-bit float, the stencil plane through the R8_UINT descriptor of the stencil plane sampling entry below. That every sample then holds them is inferred from the uncompressed layout, not measured on the console. A written owner is published on a guest read as the D32S8 guest publication entry above describes, with no HTILE to expand. Other planes import once a stencil readback shows the first draw (depth ALWAYS with writes, stencil ALWAYS/REPLACE, mask 0xff) reached every sample; else they throw, as D16 or D32 without HTILE do. - [Native multisample stencil plane drawn as colour](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a native D32S8 target whose separate stencil plane a multisampled colour target covers with anything but a known R8 value of the same sample count is imported with the plane unproven: its host stencil is not taken from it, and a stencil-tested draw or a sample of the plane refuses (`native multisample stencil plane aliases a multisampled colour target whose samples are only on the host`) until a whole-plane fill gives its byte, which a colour target still over the plane must have absorbed as the same R8 byte at the same sample count. Taking a drawn plane without a fill needs a GPU copy of the colour target's R8 samples, in the same sample order, into the depth image's stencil aspect, which is not implemented. Whether the console's stencil reads of a plane written through the colour path match that copy is not measured. - [Native multisample stencil plane sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - the separate stencil plane of a native multisampled D32S8 target with a depth-only HTILE is sampled through an R8_UINT 2D MSAA descriptor at the plane with the target's extent, samples, tile mode and XOR and no metadata, as a stencil-aspect view of the host depth image or of its sample shadow, which then copies the stencil with the depth. A stencil kept in HTILE, another format, metadata or layout is refused, and a plane left unproven by a colour target refuses as listed above. Whether the console's reads of the plane through such a descriptor match the host stencil is not measured. - [sceAgcDriverSetSubmitValidationMode, sceAgcDriverGetSubmitValidationMode, sceAgcDriverSetSubmitValidationConfig, sceAgcDriverGetSubmitValidationConfig, sceAgcDriverSetValidationErrorOutputFrequency, sceAgcDriverGetShaderDebuggingStatus](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - the five validation calls return `0x8A6C1000` without reading their arguments or writing caller memory, and `sceAgcDriverGetShaderDebuggingStatus` returns 1, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L250-L291) reads them from Sony's libSceAgcDriver.sprx: each validation export is `mov eax, 0x8a6c1000; ret` in the copy Dragon Quest VII Reimagined bundles (0x7bb0-0x7bf0), and the debugging status is 1 in the firmware 5.50 library (0x6740). The parameters (a 32-bit mode or frequency, a mode output, a configuration pointer) follow OpenAGC and are never read. Whether later retail firmware enables submit validation or shader debugging is unverified; no title in this repository is known to call them