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 31e692996af9..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; @@ -2373,7 +2451,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"); @@ -2408,44 +2488,23 @@ 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; 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]); - } - 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); + layout.Tile(linear->Bytes().first(static_cast(layout.LinearBytes())), result[index]); + } + 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{}; @@ -2950,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; }; @@ -3088,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)); @@ -3106,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; @@ -3125,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 77ea4b0324e4..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 @@ -22,9 +22,15 @@ 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 agc_driver_native_depth_aliased_depth_depth_only agc_driver_native_depth_aliased_depth_stencil agc_driver_native_depth_aliased_depth_no_stencil 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_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) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp index 36fd21b364bd..42c0421ce021 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp @@ -3348,6 +3348,223 @@ 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"); + } 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)); +} + +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__ @@ -3426,6 +3643,30 @@ 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]) == "--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 513d45551d7e..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. 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