diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 40c7b7629653..13f8c58a9bbb 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -370,10 +370,31 @@ add_custom_command( VERBATIM ) +foreach(agcSampleAspect IN ITEMS Depth Stencil) + set(agcSampleArrayDefines) + if(agcSampleAspect STREQUAL "Stencil") + set(agcSampleArrayDefines -DSTENCIL=1) + endif() + set(agcSampleArraySource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/SampleArray.comp) + set(agcSampleArraySpv ${agcTestShaderGeneratedRoot}/Sample${agcSampleAspect}Array.spv) + set(agcSampleArrayHeader ${agcTestShaderGeneratedRoot}/Sample${agcSampleAspect}Array_spv.h) + add_custom_command( + OUTPUT "${agcSampleArrayHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTestShaderGeneratedRoot}" + COMMAND "$" -V --target-env vulkan1.1 ${agcSampleArrayDefines} -o "${agcSampleArraySpv}" "${agcSampleArraySource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcSampleArraySpv} -DOUTPUT=${agcSampleArrayHeader} + -DSYMBOL=SAMPLE_${agcSampleAspect}_ARRAY_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcSampleArraySource}" glslang-standalone + VERBATIM + ) + list(APPEND agcSampleArrayHeaders "${agcSampleArrayHeader}") +endforeach() + add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL tests/Recorder.cpp tests/ResidentPresent.cpp ${agcSampleLodShaderHeader} + ${agcSampleArrayHeaders} ) target_include_directories(agc_driver_recorder_tests PRIVATE diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index d66bdb19c037..b782a2ac76e1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -156,7 +156,10 @@ inline Registers InitialContextRegisters() { {0x318, 0}, {0x31b, 0}, {0x31c, 0}, {0x31d, 0}, {0x390, 0}, {0x3b0, 0}, {0x3b8, 0} }; - for (std::uint32_t i = 0; i < 8; ++i) result.emplace(0x1e0 + i, 0x20010001); + for (std::uint32_t i = 0; i < 8; ++i) { + result.emplace(0x1e0 + i, 0x20010001); + result.emplace(0x31c + 0xfu * i, 0); + } for (std::uint32_t i = 0; i < 16; ++i) { result.emplace(0x94 + 2 * i, 0x80000000); result.emplace(0x95 + 2 * i, 0x40004000); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index 8828e54a3a29..d7766c8357a0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -280,6 +280,8 @@ class VulkanDevice { // built for its draw-cache entry (design_cpu_final M8); null otherwise. void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}, std::shared_ptr* recipe = nullptr); std::optional KnownDrawRejection(const Graphics::State& graphics, std::span shaders) const; + void DepthClearPass(const Graphics::DepthClearPass& pass); + void DepthCopyPass(const Graphics::DepthCopyPass& pass); void ColorMetadataPass(const Graphics::ColorMetadataPass& pass); // A draw-cache hit recorded from its recipe (Graphics::DrawWithRecipe), under the mutex after // the packet's labels; Rebuild when the recipe's device is not this one or DrawWithRecipe diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp index f2842ad619a8..2461b427bc79 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp @@ -23,13 +23,35 @@ std::optional Driver::precheckDraw(const QueueState& queue, const S localDevice->ColorMetadataPass(*pass); return DrawVerdict::Drawn; } + rejected = Graphics::DepthMaintenanceRejection(queue); + if (!rejected.empty()) return DrawVerdict::Rejected; + if (const auto pass = Graphics::DecodeDepthCopyPass(queue)) { + require(!drawParameters.indirect, "indirect depth copies are unsupported"); + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + if (device == nullptr) device = std::make_shared(); + const std::shared_ptr localDevice = device; + recordLabelsForPacket(localDevice.get(), submission.queue); + localDevice->DepthCopyPass(*pass); + return DrawVerdict::Drawn; + } + if (const auto pass = Graphics::DecodeDepthClearPass(queue)) { + require(!drawParameters.indirect, "indirect depth clears are unsupported"); + GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw); + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + if (device == nullptr) device = std::make_shared(); + const std::shared_ptr localDevice = device; + recordLabelsForPacket(localDevice.get(), submission.queue); + localDevice->DepthClearPass(*pass); + return DrawVerdict::Drawn; + } static const bool traceIndirectEnabled = std::getenv("APS5_TRACE_INDIRECT_DRAWS") != nullptr; traceIndirect = traceIndirectEnabled; if (traceIndirect && drawParameters.indirect) std::fprintf(stderr, "[draw] indirect packet %s args 0x%llx count %u reached\n", Pm4::Name(packet[0]).c_str(), static_cast(drawParameters.indirect->arguments), drawParameters.indirect->count); { const auto targetMask = queue.context.find(0x8e); const auto shaderMask = queue.context.find(0x8f); - const bool colorWrites = targetMask != queue.context.end() && shaderMask != queue.context.end() && (targetMask->second & shaderMask->second) != 0; + const bool colorWrites = targetMask != queue.context.end() && shaderMask != queue.context.end() && Graphics::ColorWriteMask(queue.context) != 0; if (!colorWrites && !queue.shader.contains(0x8)) { const auto word = [&](std::uint32_t offset) { const auto it = queue.context.find(offset); return it == queue.context.end() ? 0u : it->second; }; if ((word(0x200) & 3u) == 0 || ((word(0x010) & 3u) == 0 && (word(0x011) & 1u) == 0)) return DrawVerdict::Nothing; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 755ba86979a4..771dd6aef9a5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2488,6 +2488,14 @@ std::optional VulkanDevice::KnownDrawRejection(const Graphics::Stat return Graphics::KnownValidationFailure(graphicsContext(), shaders, graphics); } +void VulkanDevice::DepthClearPass(const Graphics::DepthClearPass& pass) { + Graphics::RunDepthClearPass(graphicsContext(), pass); +} + +void VulkanDevice::DepthCopyPass(const Graphics::DepthCopyPass& pass) { + Graphics::RunDepthCopyPass(graphicsContext(), pass); +} + void VulkanDevice::ColorMetadataPass(const Graphics::ColorMetadataPass& pass) { Graphics::RunColorMetadataPass(graphicsContext(), pass); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index 2e8ed728c806..656c7939d06d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -12,6 +12,10 @@ namespace AgcDriver::Graphics { class Texture; VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target); +void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target, VkImageAspectFlags aspects); +void NoteDepthSurfaceWrite(const Context& context, const State& state); +void RunDepthClearPass(const Context& context, const DepthClearPass& pass); +void RunDepthCopyPass(const Context& context, const DepthCopyPass& pass); std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel); void ClearDepthSurfaces(VkDevice device); bool DepthSurfaceAt(std::uint64_t address); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 753fa01ce5b6..8742682be520 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -103,6 +103,7 @@ State DecodeState(const QueueState& queue); std::array ExportMappings(const State& state); ColorTarget DecodeColorBuffer(const Registers& context, std::uint32_t slot); std::size_t CmaskBytes(std::uint32_t width, std::uint32_t height); +std::uint32_t ColorWriteMask(const Registers& context); struct ColorMetadataPass { enum class Mode { EliminateFastClear, DccDecompress }; @@ -110,6 +111,18 @@ struct ColorMetadataPass { std::vector targets; }; std::optional DecodeColorMetadataPass(const QueueState& queue); +struct DepthClearPass { + DepthTarget target; + VkImageAspectFlags aspects; +}; +std::optional DecodeDepthClearPass(const QueueState& queue); +struct DepthCopyPass { + DepthTarget source; + DepthTarget destination; + VkImageAspectFlags aspects; +}; +std::optional DecodeDepthCopyPass(const QueueState& queue); +std::string DepthMaintenanceRejection(const QueueState& queue); // The message DecodeState (or the pixel stage decode after it) would throw for the register rules // this precheck covers, evaluated without exceptions before the draw is decoded; empty when they // pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 05af6a3c5a11..2e16d6bd81a2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -58,7 +58,7 @@ class Texture { // compute pass writing it and the next pass sampling it share one image and copy nothing. // CanCopyFrom says whether the two descriptors address the same surface compatibly. Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components); - Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components); + Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t layers = 1); static bool CanCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor); ~Texture(); Texture(const Texture&) = delete; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 33194fdb2fb8..65bcde0c2628 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -3,10 +3,13 @@ #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include #include #include +#include #include #include #include @@ -16,9 +19,14 @@ namespace AgcDriver::Graphics { namespace { +struct GuestDepthSlice { + std::vector bytes; + std::uint64_t generation = 0; +}; + class DepthSurface { public: - DepthSurface(const Context& context, const DepthTarget& target) : context(context), target(target) { + DepthSurface(const Context& context, const DepthTarget& target, std::uint32_t layers = 1) : context(context), target(target) { this->context.bufferPool.reset(); VkFormatProperties properties{}; context.formatProperties(context.physical, target.format, &properties); @@ -32,10 +40,11 @@ class DepthSurface { info.format = target.format; info.extent = {target.extent.width, target.extent.height, 1}; info.mipLevels = 1; - info.arrayLayers = 1; + Require(layers != 0 && layers <= context.limits.maxImageArrayLayers, "depth array exceeds device limits"); + info.arrayLayers = layers; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth"); @@ -63,7 +72,7 @@ class DepthSurface { toGeneral.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toGeneral.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toGeneral.image = image; - toGeneral.subresourceRange = viewInfo.subresourceRange; + toGeneral.subresourceRange = {aspects, 0, 1, 0, layers}; const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier"); barrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); const VkClearDepthStencilValue clear{target.clearDepth, target.clearStencil}; @@ -80,29 +89,191 @@ class DepthSurface { DepthSurface(const DepthSurface&) = delete; DepthSurface& operator=(const DepthSurface&) = delete; - std::shared_ptr Sampled(std::span words, const GuestTextureResource& resource, VkComponentMapping components) { - std::array key{}; - std::copy_n(words.begin(), std::min(words.size(), 8), key.begin()); - key[8] = components.r; - key[9] = components.g; - key[10] = components.b; - key[11] = components.a; - if (const auto found = textures.find(key); found != textures.end()) return found->second; + void Clear(const DepthClearPass& pass) { + auto* recorder = Recorder::Active(); + std::unique_ptr batch; + if (recorder == nullptr) batch = std::make_unique(context); + const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle(); + constexpr VkAccessFlags access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, access, VK_ACCESS_TRANSFER_WRITE_BIT); + const VkClearDepthStencilValue clear{pass.target.clearDepth, pass.target.clearStencil}; + const VkImageSubresourceRange range{pass.aspects, 0, 1, 0, 1}; + context.Function("vkCmdClearDepthStencilImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &clear, 1, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, access); + NoteWrite(pass.aspects); + if (batch) batch->SubmitAndWait(); + else Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); + } + + void NoteWrite(VkImageAspectFlags aspects) { + if (aspects & VK_IMAGE_ASPECT_DEPTH_BIT) ++depthVersion; + if (aspects & VK_IMAGE_ASPECT_STENCIL_BIT) ++stencilVersion; + } + + void CopyFrom(const DepthSurface& source, VkImageAspectFlags aspects) { + auto* recorder = Recorder::Active(); + std::unique_ptr batch; + if (recorder == nullptr) batch = std::make_unique(context); + const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle(); + constexpr VkAccessFlags access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, access, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + std::array copies{}; + std::uint32_t count = 0; + for (const auto aspect : {VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_ASPECT_STENCIL_BIT}) { + if ((aspects & aspect) == 0) continue; + auto& copy = copies[count++]; + copy.srcSubresource = {static_cast(aspect), 0, 0, 1}; + copy.dstSubresource = copy.srcSubresource; + copy.extent = {target.extent.width, target.extent.height, 1}; + } + context.Function("vkCmdCopyImage")(commands, source.image, VK_IMAGE_LAYOUT_GENERAL, image, VK_IMAGE_LAYOUT_GENERAL, count, copies.data()); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, access); + NoteWrite(aspects); + if (batch) batch->SubmitAndWait(); + else Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); + } + + std::uint64_t Version(VkImageAspectFlags aspect) const { + return aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? stencilVersion : depthVersion; + } + + void ValidateSampled(std::span words, const GuestTextureResource& resource) const { + std::array key{}; + std::copy_n(words.begin(), std::min(words.size(), key.size()), key.begin()); const bool stencil = target.stencilAddress != 0 && resource.baseAddress == target.stencilAddress; const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; const auto expected = stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT; const auto format = ResolveTextureFormat(resource.format); const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u); - if ((format != expected && !depthBits) || resource.dimension != TextureDimension::k2D || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || resource.baseArray != 0) { + if ((format != expected && !depthBits) || (resource.dimension != TextureDimension::k2D && resource.dimension != TextureDimension::k2DArray) || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || (resource.dimension == TextureDimension::k2D && resource.baseArray != 0)) { char text[448]; std::snprintf(text, sizeof(text), "AGC graphics: sampling the %s plane of depth surface 0x%llx (%ux%u, vk format %d) as a %ux%u texture of guest format %u (vk %d), tile mode %u, dimension %d, levels %u-%u, slice %u is not implemented (T# %08x %08x %08x %08x %08x %08x %08x %08x)", stencil ? "stencil" : "depth", static_cast(target.address), target.extent.width, target.extent.height, static_cast(target.format), resource.width, resource.height, resource.format, static_cast(format), static_cast(resource.tileMode), static_cast(resource.dimension), resource.baseLevel, resource.lastLevel, resource.baseArray, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]); throw std::runtime_error(text); } - auto texture = std::make_shared(context, image, target.format, stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, components); - textures.emplace(key, texture); - return texture; + } + + std::shared_ptr Sampled(const GuestTextureResource& resource, VkComponentMapping components, std::span slices, bool stencil) { + const VkImageAspectFlags aspect = stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; + const BackingKey backingKey{resource.baseAddress, resource.baseArray, slices.size(), static_cast(resource.tileMode), aspect, resource.dccAddress, resource.dccAlphaOnMsb, resource.format}; + auto backing = arrays.find(backingKey); + if (backing == arrays.end() && (slices.size() > 1 || slices.front() == nullptr)) { + SampledDepth sampled; + sampled.array = std::make_shared(context, target, static_cast(slices.size())); + sampled.versions.resize(slices.size()); + sampled.guest.resize(slices.size()); + backing = arrays.emplace(backingKey, std::move(sampled)).first; + } + const auto key = std::pair{backingKey, std::array{components.r, components.g, components.b, components.a, static_cast(resource.dimension)}}; + auto found = textures.find(key); + if (found == textures.end()) { + const auto type = resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; + std::shared_ptr texture; + if (backing == arrays.end()) { + texture = std::make_shared(context, slices.front()->image, target.format, aspect, components, type, 1); + } else { + auto owned = std::make_shared(backing->second.array, aspect, components, type, static_cast(slices.size())); + texture = std::shared_ptr(owned, &owned->texture); + } + found = textures.emplace(key, std::move(texture)).first; + } + if (backing != arrays.end()) { + auto& sampled = backing->second; + sampled.array->CopySlices(resource, slices, aspect, sampled.versions, sampled.guest); + } + return found->second; + } + + void CopySlices(const GuestTextureResource& resource, std::span slices, VkImageAspectFlags aspect, std::vector>& versions, std::vector& guest) { + const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; + const auto bytes = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? 1u : d16 ? 2u : 4u; + const auto stride = DepthSliceBytes(target.extent, bytes); + std::vector changed(slices.size()); + std::vector pending(slices.size()); + for (std::size_t i = 0; i < slices.size(); ++i) { + if (slices[i] != nullptr) { + changed[i] = versions[i] != std::pair{slices[i]->image, slices[i]->Version(aspect)}; + continue; + } + Require(resource.dccAddress == 0, "guest depth array layers with compression metadata are not implemented"); + const auto address = resource.baseAddress + (resource.baseArray + i) * stride; + StorageTexture::FlushPending(address, stride, nullptr, "depth array layer"); + GuestMemory::FlushGpuWrites(address, stride); + const auto generation = GuestMemory::CollectWrites(address, stride); + if (!guest[i].bytes.empty() && (GuestMemory::UnchangedSince(address, stride, guest[i].generation) || GuestMemory::CompareMapped(address, guest[i].bytes) == GuestMemory::Compare::Equal)) { + guest[i].generation = generation; + continue; + } + pending[i].bytes.resize(stride); + GuestMemory::Read(address, pending[i].bytes); + pending[i].generation = generation; + changed[i] = true; + } + if (std::none_of(changed.begin(), changed.end(), [](bool value) { return value; })) return; + if (std::any_of(pending.begin(), pending.end(), [](const auto& slice) { return !slice.bytes.empty(); })) { + Require(context.detiler != nullptr, "depth array upload requires a texture detiler"); + context.detiler->BeginBatch(); + } + auto* recorder = Recorder::Active(); + std::unique_ptr batch; + if (recorder == nullptr) batch = std::make_unique(context); + const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle(); + constexpr VkAccessFlags access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, access, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + std::vector> staging; + std::vector> scratch; + for (std::uint32_t i = 0; i < slices.size(); ++i) { + if (!changed[i]) continue; + if (slices[i] == nullptr) { + Require(context.detiler != nullptr, "depth array upload requires a texture detiler"); + const auto mips = ComputeElementMipLayout(resource.tileMode, bytes, resource.width, resource.height, 1); + Require(mips.size() == 1 && mips[0].tiledSize == stride && mips[0].tiledOffset == 0 && !mips[0].tail, "depth array upload layout disagrees with the attachment slice stride"); + const auto& mip = mips[0]; + auto upload = std::make_shared(context, stride, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + auto tiled = std::make_shared(context, stride, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + auto linear = std::make_shared(context, mip.linearSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + std::memcpy(upload->Bytes().data(), pending[i].bytes.data(), pending[i].bytes.size()); + if (recorder != nullptr) { + recorder->Keep(upload); + recorder->Keep(tiled); + recorder->Keep(linear); + } + staging.push_back(upload); + scratch.push_back(tiled); + scratch.push_back(linear); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + CopyBuffer(context, commands, upload->Handle(), 0, tiled->Handle(), 0, stride); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + context.detiler->Dispatch(commands, resource.tileMode, bytes, tiled->Handle(), 0, linear->Handle(), 0, mip, false, resource.baseArray + i); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + VkBufferImageCopy copy{}; + copy.bufferRowLength = mip.pitchBytes / bytes; + copy.imageSubresource = {aspect, 0, i, 1}; + copy.imageExtent = {resource.width, resource.height, 1}; + context.Function("vkCmdCopyBufferToImage")(commands, linear->Handle(), image, VK_IMAGE_LAYOUT_GENERAL, 1, ©); + if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); + } else { + VkImageCopy copy{}; + copy.srcSubresource = {aspect, 0, 0, 1}; + copy.dstSubresource = {aspect, 0, i, 1}; + copy.extent = {target.extent.width, target.extent.height, 1}; + context.Function("vkCmdCopyImage")(commands, slices[i]->image, VK_IMAGE_LAYOUT_GENERAL, image, VK_IMAGE_LAYOUT_GENERAL, 1, ©); + } + } + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, access); + if (batch) batch->SubmitAndWait(); + else Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); + for (std::size_t i = 0; i < slices.size(); ++i) { + if (!changed[i]) continue; + if (slices[i] != nullptr) { + versions[i] = {slices[i]->image, slices[i]->Version(aspect)}; + guest[i] = {}; + } else { + versions[i] = {}; + guest[i] = std::move(pending[i]); + } + } } const Context context; @@ -112,10 +283,26 @@ class DepthSurface { VkDeviceMemory memory = VK_NULL_HANDLE; private: - std::map, std::shared_ptr> textures; + std::uint64_t depthVersion = 1; + std::uint64_t stencilVersion = 1; + using BackingKey = std::array; + struct SampledDepth { + std::shared_ptr array; + std::vector> versions; + std::vector guest; + }; + struct SampledView { + std::shared_ptr array; + Texture texture; + SampledView(std::shared_ptr source, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType type, std::uint32_t layers) + : array(std::move(source)), texture(array->context, array->image, array->target.format, aspect, components, type, layers) {} + }; + std::map arrays; + std::map>, std::shared_ptr> textures; void release() noexcept { textures.clear(); + arrays.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); @@ -152,24 +339,131 @@ std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel) { VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target) { std::lock_guard lock(surfacesMutex()); for (const auto& surface : surfaces()) { - if (surface->context.device == context.device && sameSurface(surface->target, target)) return surface->view; + if (surface->context.device == context.device && sameSurface(surface->target, target)) { + return surface->view; + } } surfaces().push_back(std::make_unique(context, target)); return surfaces().back()->view; } +void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target, VkImageAspectFlags aspects) { + if (aspects == 0) return; + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (surface->context.device != context.device || !sameSurface(surface->target, target)) continue; + surface->NoteWrite(aspects); + return; + } + throw std::runtime_error("AGC graphics: recording a write to an unknown depth surface"); +} + +void NoteDepthSurfaceWrite(const Context& context, const State& state) { + if (!state.depth) return; + VkImageAspectFlags aspects = state.depthTest && state.depthWrite ? VK_IMAGE_ASPECT_DEPTH_BIT : 0u; + const auto writesStencil = [](const VkStencilOpState& stencil) { + return stencil.writeMask != 0 && (stencil.failOp != VK_STENCIL_OP_KEEP || stencil.passOp != VK_STENCIL_OP_KEEP || stencil.depthFailOp != VK_STENCIL_OP_KEEP); + }; + if (state.stencilTest && (writesStencil(state.stencilFront) || writesStencil(state.stencilBack))) aspects |= VK_IMAGE_ASPECT_STENCIL_BIT; + NoteDepthSurfaceWrite(context, *state.depth, aspects); +} + void ClearDepthSurfaces(VkDevice device) { std::lock_guard lock(surfacesMutex()); std::erase_if(surfaces(), [&](const auto& surface) { return surface->context.device == device; }); } +void RunDepthClearPass(const Context& context, const DepthClearPass& pass) { + Require(pass.aspects != 0 && (pass.aspects & ~(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) == 0, "invalid depth clear aspects"); + Require((pass.aspects & VK_IMAGE_ASPECT_DEPTH_BIT) == 0 || pass.target.address != 0, "depth clear has no depth plane"); + Require((pass.aspects & VK_IMAGE_ASPECT_STENCIL_BIT) == 0 || pass.target.stencilAddress != 0, "stencil clear has no stencil plane"); + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (surface->context.device != context.device || !sameSurface(surface->target, pass.target)) continue; + surface->Clear(pass); + return; + } + surfaces().push_back(std::make_unique(context, pass.target)); + surfaces().back()->Clear(pass); +} + +void RunDepthCopyPass(const Context& context, const DepthCopyPass& pass) { + Require(pass.aspects != 0 && (pass.aspects & ~(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) == 0, "invalid depth copy aspects"); + Require(pass.source.format == pass.destination.format && pass.source.extent.width == pass.destination.extent.width && pass.source.extent.height == pass.destination.extent.height, "depth copy surfaces have incompatible formats or extents"); + Require((pass.aspects & VK_IMAGE_ASPECT_DEPTH_BIT) == 0 || (pass.source.address != 0 && pass.destination.address != 0), "depth copy has an absent depth plane"); + Require((pass.aspects & VK_IMAGE_ASPECT_STENCIL_BIT) == 0 || (pass.source.stencilAddress != 0 && pass.destination.stencilAddress != 0), "depth copy has an absent stencil plane"); + std::lock_guard lock(surfacesMutex()); + DepthSurface* source = nullptr; + DepthSurface* destination = nullptr; + for (const auto& surface : surfaces()) { + if (surface->context.device != context.device) continue; + if (sameSurface(surface->target, pass.source)) source = surface.get(); + if (sameSurface(surface->target, pass.destination)) destination = surface.get(); + } + Require(source != nullptr, "depth copy source has no resident surface"); + if (source == destination) return; + Require(pass.source.address == 0 || pass.source.address != pass.destination.address, "depth copies with a shared depth plane are unsupported"); + Require(pass.source.stencilAddress == 0 || pass.source.stencilAddress != pass.destination.stencilAddress, "depth copies with a shared stencil plane are unsupported"); + if (destination == nullptr) { + Require(((pass.aspects & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || pass.destination.address == 0) && ((pass.aspects & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 || pass.destination.stencilAddress == 0), "partial depth copy destination has no resident surface"); + surfaces().push_back(std::make_unique(context, pass.destination)); + destination = surfaces().back().get(); + } + destination->CopyFrom(*source, pass.aspects); +} + std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components) { std::lock_guard lock(surfacesMutex()); const auto& list = surfaces(); - const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { - return surface->context.device == context.device && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress)); - }); - return found == list.rend() ? nullptr : (*found)->Sampled(words, resource, components); + const auto find = [&](std::uint64_t address) -> DepthSurface* { + const auto found = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { + return surface->context.device == context.device && (surface->target.address == address || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == address)); + }); + return found == list.rend() ? nullptr : found->get(); + }; + auto* base = find(resource.baseAddress); + auto matchedAddress = resource.baseAddress; + const bool array = resource.dimension == TextureDimension::k2DArray; + if (base == nullptr && array) { + for (auto it = list.rbegin(); it != list.rend(); ++it) { + const auto& surface = **it; + if (surface.context.device != context.device || surface.target.extent.width != resource.width || surface.target.extent.height != resource.height) continue; + const auto matches = [&](std::uint64_t address, std::uint32_t bytes) { + if (address == 0 || address < resource.baseAddress) return false; + const auto stride = DepthSliceBytes(surface.target.extent, bytes); + const auto offset = address - resource.baseAddress; + return offset % stride == 0 && offset / stride >= resource.baseArray && offset / stride <= resource.depthOrLastArray; + }; + const bool d16 = surface.target.format == VK_FORMAT_D16_UNORM || surface.target.format == VK_FORMAT_D16_UNORM_S8_UINT; + if (matches(surface.target.address, d16 ? 2u : 4u)) matchedAddress = surface.target.address; + else if (matches(surface.target.stencilAddress, 1u)) matchedAddress = surface.target.stencilAddress; + else continue; + base = it->get(); + break; + } + } + if (base == nullptr) return nullptr; + auto viewed = resource; + viewed.baseAddress = matchedAddress; + base->ValidateSampled(words, viewed); + const bool stencil = base->target.stencilAddress != 0 && matchedAddress == base->target.stencilAddress; + const auto last = array ? resource.depthOrLastArray : 0u; + Require(resource.baseArray <= last && last < 8192u && last - resource.baseArray < context.limits.maxImageArrayLayers, "depth array layer range is invalid"); + if (array) Require(resource.tileMode == TextureTileMode::kZ64KBX, "depth arrays outside the 64 KiB Z layout are not implemented"); + const bool d16 = base->target.format == VK_FORMAT_D16_UNORM || base->target.format == VK_FORMAT_D16_UNORM_S8_UINT; + const auto stride = DepthSliceBytes(base->target.extent, stencil ? 1u : d16 ? 2u : 4u); + std::vector slices; + for (auto layer = resource.baseArray; layer <= last; ++layer) { + const auto address = resource.baseAddress + layer * stride; + auto* slice = find(address); + if (slice != nullptr && (slice->target.format != base->target.format || slice->target.extent.width != resource.width || slice->target.extent.height != resource.height || (stencil ? slice->target.stencilAddress : slice->target.address) != address)) { + char text[192]; + std::snprintf(text, sizeof(text), "AGC graphics: depth array 0x%llx layer %u at 0x%llx has no matching resident %s surface", static_cast(resource.baseAddress), layer, static_cast(address), stencil ? "stencil" : "depth"); + throw std::runtime_error(text); + } + slices.push_back(slice); + } + return base->Sampled(resource, components, slices, stencil); } bool DepthSurfaceAt(std::uint64_t address) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index f9e21abd9e9a..a4eb69b38dde 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1412,6 +1412,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset); + NoteDepthSurfaceWrite(context, state); if (meshArguments != nullptr) recorder->Keep(meshArguments); if (args != nullptr) CountIndirectDraw(record.indirect->path, record.indirect->readMs, rewritten); auto checkRecords = indirectRecordCheck(record.indirect); @@ -1883,6 +1884,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset); + NoteDepthSurfaceWrite(context, state); if (meshArguments != nullptr && recorded) recorder->Keep(meshArguments); if (args != nullptr) CountIndirectDraw(indirect.path, indirect.readMs, rewritten); auto checkRecords = indirectRecordCheck(args != nullptr ? &indirect : nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 959031d35b8b..1d1fbc3ba515 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -155,8 +155,8 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint return face; } -void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result) { - zero(cx, 0x000, 0x00001f9fu, "depth/stencil clear, copy, resummarize or decompress draws (DB_RENDER_CONTROL)"); +void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result, bool clearing = false, bool copying = false) { + zero(cx, 0x000, clearing ? 0x00001f9cu : 0x00001f9fu, "depth/stencil clear, copy, resummarize or decompress draws (DB_RENDER_CONTROL)"); const auto view = read(cx, 0x002); zero(cx, 0x002, 0x3c000000u, "depth mips (DB_DEPTH_VIEW MIP_LEVEL)"); zero(cx, 0x010, 0x000f100cu, "multisampled, partially resident or mipmapped depth (DB_Z_INFO)"); @@ -175,8 +175,8 @@ void decodeDepth(const Registers& cx, std::uint32_t depthControl, State& result) DepthTarget depth{}; depth.address = zFormat != 0 ? base(0x012, 0x01a) : 0; depth.stencilAddress = stencil ? base(0x013, 0x01b) : 0; - Require(zFormat == 0 || depthReadOnly || base(0x014, 0x01c) == depth.address, "depth read and written at different addresses is unsupported"); - Require(!stencil || stencilReadOnly || base(0x015, 0x01d) == depth.stencilAddress, "stencil read and written at different addresses is unsupported"); + Require(copying || zFormat == 0 || depthReadOnly || base(0x014, 0x01c) == depth.address, "depth read and written at different addresses is unsupported"); + Require(copying || !stencil || stencilReadOnly || base(0x015, 0x01d) == depth.stencilAddress, "stencil read and written at different addresses is unsupported"); const auto size = read(cx, 0x007); depth.extent = {(size & 0x3fffu) + 1u, ((size >> 16u) & 0x3fffu) + 1u}; if (const auto slice = view & 0x1fffu; slice != 0) { @@ -431,7 +431,14 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { return result; } +std::string DepthMaintenanceRejection(const QueueState& queue) { + const auto control = find(queue.context, 0x000); + if (control == queue.context.end() || (control->second & ~0x2063u) == 0) return {}; + return zeroMessage(0x000, control->second, "DB_RENDER_CONTROL depth copy, resummarize or decompress"); +} + State DecodeState(const QueueState& queue) { + if (auto reason = DepthMaintenanceRejection(queue); !reason.empty()) throw std::runtime_error(reason); const auto& cx = queue.context; State result{}; result.stages = DecodeShaderStages(queue); @@ -514,7 +521,7 @@ State DecodeState(const QueueState& queue) { const auto shaderMask = read(cx, 0x8f); // Channels of targets the pixel shader does not export are never written, so the target mask only // matters where the shader exports. - const auto targetMask = read(cx, 0x8e) & shaderMask; + const auto targetMask = ColorWriteMask(cx); APS5_LOG_OUT_DEBUG("CB_TARGET_MASK=0x%x CB_SHADER_MASK=0x%x", targetMask, shaderMask); std::vector exportSlots; for (std::uint32_t slot = 0; slot < 8; ++slot) { @@ -639,6 +646,17 @@ std::size_t CmaskBytes(std::uint32_t width, std::uint32_t height) { return ((width + metablockWidth - 1) / metablockWidth) * ((height + metablockHeight - 1) / metablockHeight) * metablockBytes; } +std::uint32_t ColorWriteMask(const Registers& context) { + auto mask = read(context, 0x8e) & read(context, 0x8f); + for (std::uint32_t slot = 0; slot < 8; ++slot) { + const auto channels = 0xfu << (slot * 4u); + if ((mask & channels) == 0) continue; + const auto info = find(context, 0x31c + slot * 0xfu); + if (info != context.end() && ((info->second >> 2u) & 0x1fu) == 0) mask &= ~channels; + } + return mask; +} + ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot) { const auto stride = slot * 0xfu; ColorTarget color{}; @@ -758,7 +776,108 @@ std::optional DecodeColorMetadataPass(const QueueState& queue return pass; } +std::optional DecodeDepthClearPass(const QueueState& queue) { + const auto& cx = queue.context; + const auto control = find(cx, 0x000); + if (control == cx.end()) return std::nullopt; + Require((control->second & ~0x2063u) == 0, "depth copy, resummarize or decompress is unsupported"); + if ((control->second & 3u) == 0) return std::nullopt; + Require(ColorWriteMask(cx) == 0, "depth clear with color writes is unsupported"); + zero(cx, 0x1c4, ~0u, "depth clear with shader depth or sample-mask export"); + zero(cx, 0x2f8, ~0u, "depth clear with multisampling or coverage conversion"); + zero(cx, 0x080, ~0u, "depth clear with a window offset"); + const auto view = read(cx, 0x002); + const auto lastSlice = ((view >> 13u) & 0x7ffu) | ((view >> 19u) & 0x1800u); + Require((view & 0x1fffu) == lastSlice, "depth clear over multiple slices is unsupported"); + const auto depthControl = read(cx, 0x200); + Require((depthControl & 8u) == 0, "depth clear with depth bounds is unsupported"); + const bool depthUnchanged = (depthControl & 2u) == 0 || ((depthControl & 4u) == 0 && ((depthControl >> 4u) & 7u) == 7u); + Require((control->second & 1u) != 0 || depthUnchanged, "stencil clear with ordinary depth work is unsupported"); + Require((control->second & 2u) != 0 || (depthControl & 1u) == 0, "depth clear with ordinary stencil work is unsupported"); + VkImageAspectFlags aspects = 0; + if ((control->second & 1u) != 0) { + Require((depthControl & 6u) == 6u && (view & 0x01000000u) == 0 && (read(cx, 0x010) & 3u) != 0, "depth clear needs a writable enabled depth plane"); + aspects |= VK_IMAGE_ASPECT_DEPTH_BIT; + } + if ((control->second & 2u) != 0) { + Require((depthControl & 1u) != 0 && (view & 0x02000000u) == 0 && (read(cx, 0x011) & 1u) != 0, "stencil clear needs a writable enabled stencil plane"); + aspects |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + State decoded{}; + decodeDepth(cx, depthControl, decoded, true); + const auto& target = *decoded.depth; + if ((aspects & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) Require(std::isfinite(target.clearDepth) && target.clearDepth >= 0.0f && target.clearDepth <= 1.0f, "depth clear value is outside [0, 1]"); + Require(read(cx, 0x206) == 0x43fu, "unsupported viewport transform for depth clear"); + const auto xs = std::fabs(readFloat(cx, 0x10f)); + const auto xo = readFloat(cx, 0x110); + const auto ys = std::fabs(readFloat(cx, 0x111)); + const auto yo = readFloat(cx, 0x112); + Require(std::isfinite(xs) && std::isfinite(xo) && std::isfinite(ys) && std::isfinite(yo), "non-finite depth clear viewport"); + VkRect2D covered{{0, 0}, {0x7fffu, 0x7fffu}}; + intersect(covered, cx, 0x00c, true); + intersect(covered, cx, 0x081, false); + intersect(covered, cx, 0x090, false); + if ((read(cx, 0x292) & 2u) != 0) intersect(covered, cx, 0x094, false); + const bool viewportCovers = xo - xs <= 0.0f && xo + xs >= target.extent.width && yo - ys <= 0.0f && yo + ys >= target.extent.height; + const bool scissorCovers = covered.offset.x == 0 && covered.offset.y == 0 && covered.extent.width >= target.extent.width && covered.extent.height >= target.extent.height; + Require(viewportCovers && scissorCovers, "depth clear over part of a surface is unsupported"); + return DepthClearPass{target, aspects}; +} + +std::optional DecodeDepthCopyPass(const QueueState& queue) { + const auto& cx = queue.context; + const auto overrideRegister = find(cx, 0x003); + if (overrideRegister == cx.end()) return std::nullopt; + const auto overrides = overrideRegister->second; + const bool depth = (overrides & 0x28000000u) == 0x28000000u && (read(cx, 0x010) & 3u) != 0; + const bool stencil = (overrides & 0x50000000u) == 0x50000000u && (read(cx, 0x011) & 1u) != 0; + if ((!depth && !stencil) || ((read(cx, 0x202) >> 4u) & 7u) != 0) return std::nullopt; + const auto base = [&](std::uint32_t low, std::uint32_t high) { + const auto upper = find(cx, high); + return (upper == cx.end() ? 0ull : static_cast(upper->second & 0xffu) << 40u) | (static_cast(read(cx, low)) << 8u); + }; + const auto depthWrite = base(0x014, 0x01c); + const auto stencilWrite = base(0x015, 0x01d); + const bool copyDepth = depth && depthWrite != base(0x012, 0x01a); + const bool copyStencil = stencil && stencilWrite != base(0x013, 0x01b); + if (!copyDepth && !copyStencil) return std::nullopt; + Require((read(cx, 0x000) & ~0x2060u) == 0, "depth override copy with clear, color copy or resummarization is unsupported"); + Require((read(cx, 0x200) & DepthControlMask) == 0, "depth override copy with depth or stencil tests is unsupported"); + zero(cx, 0x1c4, ~0u, "depth override copy with shader depth or sample-mask export"); + zero(cx, 0x2f8, ~0u, "depth override copy with multisampling or coverage conversion"); + zero(cx, 0x080, ~0u, "depth override copy with a window offset"); + const auto view = read(cx, 0x002); + const auto slice = view & 0x1fffu; + const auto lastSlice = ((view >> 13u) & 0x7ffu) | ((view >> 19u) & 0x1800u); + Require(slice == lastSlice, "depth override copy over multiple slices is unsupported"); + Require(!copyDepth || (view & 0x01000000u) == 0, "depth override copy has a read-only depth destination"); + Require(!copyStencil || (view & 0x02000000u) == 0, "depth override copy has a read-only stencil destination"); + State decoded{}; + decodeDepth(cx, 0, decoded, false, true); + const auto& source = *decoded.depth; + auto destination = source; + if (source.address != 0) destination.address = depthWrite + slice * DepthSliceBytes(source.extent, (read(cx, 0x010) & 3u) == 1 ? 2u : 4u); + if (source.stencilAddress != 0) destination.stencilAddress = stencilWrite + slice * DepthSliceBytes(source.extent, 1u); + Require(!copyDepth || (depthWrite != 0 && source.address != 0), "depth override copy has an absent depth plane"); + Require(!copyStencil || (stencilWrite != 0 && source.stencilAddress != 0), "depth override copy has an absent stencil plane"); + Require(read(cx, 0x206) == 0x43fu, "unsupported viewport transform for depth override copy"); + const auto xs = std::fabs(readFloat(cx, 0x10f)); + const auto xo = readFloat(cx, 0x110); + const auto ys = std::fabs(readFloat(cx, 0x111)); + const auto yo = readFloat(cx, 0x112); + VkRect2D covered{{0, 0}, {0x7fffu, 0x7fffu}}; + intersect(covered, cx, 0x00c, true); + intersect(covered, cx, 0x081, false); + intersect(covered, cx, 0x090, false); + if ((read(cx, 0x292) & 2u) != 0) intersect(covered, cx, 0x094, false); + const bool viewportCovers = xo - xs <= 0.0f && xo + xs >= source.extent.width && yo - ys <= 0.0f && yo + ys >= source.extent.height; + const bool scissorCovers = covered.offset.x == 0 && covered.offset.y == 0 && covered.extent.width >= source.extent.width && covered.extent.height >= source.extent.height; + Require(viewportCovers && scissorCovers, "depth override copy over part of a surface is unsupported"); + return DepthCopyPass{source, destination, (copyDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0u) | (copyStencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u)}; +} + std::string DrawRejection(const QueueState& queue, bool indexed) { + if (auto reason = DepthMaintenanceRejection(queue); !reason.empty()) return reason; const auto& cx = queue.context; // A register a rule needs that is absent gives no verdict here: DecodeState reports it. const auto value = [&](const Registers& registers, std::uint32_t offset, std::uint32_t& out) { @@ -805,7 +924,7 @@ std::string DrawRejection(const QueueState& queue, bool indexed) { if (value(cx, 0x206, word) && word != 0x43fu) return vteMessage(word); if (auto reason = nonzero(cx, 0x204, ClipControlMask, "unsupported PA_CL_CLIP_CNTL flags"); !reason.empty()) return reason; std::uint32_t targetMask = 0, shaderMask = 0; - if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, (targetMask & shaderMask) != 0)) return colorControlMessage(word); + if (value(cx, 0x8e, targetMask) && value(cx, 0x8f, shaderMask) && value(cx, 0x202, word) && !colorControlSupported(word, ColorWriteMask(cx) != 0)) return colorControlMessage(word); if (auto reason = nonzero(cx, 0x1c4, zFormatSupported(zFormat) ? 0u : ~0u, "depth or sample-mask export"); !reason.empty()) return reason; if (PixelProgramSkipped(queue)) return NullPixelProgramRejection(queue); for (const auto offset : {0x1b3u, 0x1b4u, 0x1c5u}) { @@ -834,7 +953,7 @@ std::string NullPixelProgramRejection(const QueueState& queue) { const auto targetMask = find(queue.context, 0x8e); const auto shaderMask = find(queue.context, 0x8f); if (targetMask == queue.context.end() || shaderMask == queue.context.end()) return "AGC graphics: a draw without a pixel program needs CB_TARGET_MASK and CB_SHADER_MASK"; - if ((targetMask->second & shaderMask->second) != 0) return "AGC graphics: a draw without a pixel program writes color"; + if (ColorWriteMask(queue.context) != 0) return "AGC graphics: a draw without a pixel program writes color"; return {}; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index a5468427941a..897c45d900f2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -491,17 +491,27 @@ Texture::Texture(const Context& context, const std::shared_ptr& } } -Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components) : context(context) { +Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t layers) : context(context) { layout = VK_IMAGE_LAYOUT_GENERAL; VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; viewInfo.image = depthImage; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.viewType = viewType; viewInfo.format = depthFormat; viewFormat = depthFormat; viewInfo.components = components; - viewInfo.subresourceRange = {aspect, 0, 1, 0, 1}; - Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane"); - viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount}; + viewInfo.subresourceRange = {aspect, 0, 1, 0, layers}; + viewRange = {viewType, 1, layers}; + try { + Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane"); + if (viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.subresourceRange.layerCount = 1; + Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &firstLayerView), "vkCreateImageView depth first layer"); + } + } catch (...) { + release(); + throw; + } } Texture::~Texture() { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 29e3ea5ccd9e..d0a04042fe64 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" @@ -84,6 +85,14 @@ void stateTests() { initial.context[0xdead] = 1; initial.ClearContext(); Require(initial.context.at(0x200) == 0 && !initial.context.contains(0xdead), "context reset did not restore defaults"); + for (std::uint32_t slot = 0; slot < 8; ++slot) { + const auto info = 0x31cu + 0xfu * slot; + Require(initial.context.at(info) == 0, "reset color target was not disabled"); + initial.context[info] = 10u << 2u; + } + initial.ClearContext(); + initial.context[0x8e] = initial.context[0x8f] = 0xffffffffu; + Require(AgcDriver::Graphics::ColorWriteMask(initial.context) == 0, "context clear kept old color targets enabled"); Require(initial.userConfig.at(0x24b) == 0, "primitive restart must be disabled in initial queue state"); initial.userConfig[0x24b] = 1; initial.ClearContext(); @@ -541,6 +550,27 @@ void DisabledColorTests() { Require(partial.hasColorTarget && partial.blend.colorWriteMask == 3, "partial color write mask changed"); queue.context.erase(0x31c); expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); + queue = makeState(); + queue.context[0x31c] = 0; + for (const auto offset : {0x31bu, 0x31du, 0x3b0u, 0x3b8u, 0x390u, 0x318u, 0x1e0u}) queue.context.erase(offset); + const auto disabled = AgcDriver::Graphics::DecodeState(queue); + Require(!disabled.hasColorTarget && disabled.colors.empty() && disabled.blends.empty(), "COLOR_INVALID retained an attachment despite the disabled buffer"); + Require(disabled.renderExtent.width == 64 && disabled.renderExtent.height == 4, "COLOR_INVALID lost the attachment-free render extent"); + queue.shader[0x008] = 0; + queue.shader[0x009] = 0; + Require(AgcDriver::Graphics::NullPixelProgramRejection(queue).empty(), "COLOR_INVALID rejected a draw without a pixel shader"); + queue.context[0x200] = 0x36; + queue.context[0x000] = 0; + queue.context[0x002] = 0; + queue.context[0x010] = 0x80000181; + queue.context[0x011] = 0x20000180; + queue.context[0x012] = 0x100; + queue.context[0x014] = 0x100; + queue.context[0x007] = 0x003f003f; + queue.context[0x00a] = 0; + queue.context[0x00b] = std::bit_cast(1.0f); + const auto depthOnly = AgcDriver::Graphics::DecodeState(queue); + Require(!depthOnly.hasColorTarget && depthOnly.depth && depthOnly.depthTest && depthOnly.depthWrite && depthOnly.renderExtent.height == 64, "COLOR_INVALID discarded a depth-only draw"); } void CompactedExportTests() { @@ -562,6 +592,10 @@ void CompactedExportTests() { Require(state.colors[1].slot == 4 && state.colors[1].exportIndex == 1 && state.colors[1].address == slotFour, "export 1 did not reach MRT slot 4, the second slot CB_SHADER_MASK enables"); Require(!state.blends[0].blendEnable && state.blends[1].blendEnable && state.blends[1].colorWriteMask == (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT), "export 1 did not take MRT slot 4's blend control and target mask"); Require(AgcDriver::Graphics::ExportMappings(state)[1] == state.colors[1].componentMapping, "export 1 did not take MRT slot 4's component mapping"); + auto disabledFirst = queue; + disabledFirst.context[0x31c] = 0; + const auto withHole = AgcDriver::Graphics::DecodeState(disabledFirst); + Require(withHole.colors.size() == 1 && withHole.colors[0].slot == 4 && withHole.colors[0].exportIndex == 1 && withHole.blends.size() == 2 && withHole.blends[0].colorWriteMask == 0, "COLOR_INVALID shifted a later MRT export"); queue.context[0x1c5] = 0x90009u; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "color export format 0"); queue.context[0x8e] = 0xf000fu; @@ -648,6 +682,31 @@ void DepthStencilTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "DCC 3D color targets"); } +void depthMaintenanceTests() { + for (const auto mode : {0x4u, 0x8u, 0x10u, 0x80u, 0x100u, 0x1000u, 0x4000u}) { + for (const auto clear : {0u, 1u, 2u, 3u}) { + auto queue = makeState(); + queue.context[0x000] = mode | clear; + queue.context[0x200] = 0; + queue.context[0x8e] = 0; + queue.context[0x8f] = 0; + queue.shader.erase(0x8); + const auto reason = AgcDriver::Graphics::DepthMaintenanceRejection(queue); + Require(reason.find("DB_RENDER_CONTROL") != std::string::npos, "depth maintenance passed without depth tests or color writes"); + Require(AgcDriver::Graphics::DrawRejection(queue, false) == reason, "draw precheck did not reject depth maintenance first"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, reason); + } + } + for (const auto control : {0u, 1u, 2u, 3u, 0x20u, 0x40u, 0x2000u, 0x2063u}) { + auto queue = makeState(); + queue.context[0x000] = control; + Require(AgcDriver::Graphics::DepthMaintenanceRejection(queue).empty(), "ordinary depth controls were mistaken for maintenance"); + } + auto absent = makeState(); + absent.context.erase(0x000); + Require(AgcDriver::Graphics::DepthMaintenanceRejection(absent).empty(), "an absent depth control produced a maintenance verdict"); +} + // SPI_SHADER_Z_FORMAT (0x1c4) and the export enables of DB_SHADER_CONTROL (0x203): Z export needs a // format with a depth channel (1, 2, 3 or 32_ABGR 9), the sample mask needs 32_ABGR, and the // formats the export path does not lay out are refused. A missing 0x1c4 gives no verdict here. @@ -726,6 +785,120 @@ void DepthBoundsBiasTests() { alignas(256) std::array dccKeys{}; +void depthClearPassTests() { + using namespace AgcDriver::Graphics; + auto queue = makeState(); + queue.context[0x000] = 0; + Require(!DecodeDepthClearPass(queue), "ordinary draw was decoded as a depth clear"); + for (const auto mode : {0x4u, 0x8u, 0x10u, 0x1000u}) { + auto maintenance = queue; + maintenance.context[0x000] = mode; + maintenance.context[0x200] = 0; + maintenance.context[0x8e] = maintenance.context[0x8f] = 0; + expectFailure([&] { DecodeDepthClearPass(maintenance); }, "copy, resummarize or decompress"); + } + queue.context[0x000] = 0x2060; + Require(!DecodeDepthClearPass(queue), "ordinary compression and invocation controls were decoded as a clear"); + queue.context[0x000] = 3; + queue.context[0x002] = 0; + queue.context[0x200] = 0x777; + queue.context[0x8e] = 0; + queue.context[0x010] = 0x80000183; + queue.context[0x011] = 0x20000181; + queue.context[0x012] = queue.context[0x014] = 0x100; + queue.context[0x013] = queue.context[0x015] = 0x200; + queue.context[0x007] = (3u << 16u) | 63u; + queue.context[0x00a] = 37; + queue.context[0x00b] = std::bit_cast(0.75f); + queue.context[0x10b] = 0x333; + queue.context[0x10c] = queue.context[0x10d] = 0xffffffff; + const auto pass = DecodeDepthClearPass(queue); + Require(pass && pass->aspects == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) && pass->target.clearDepth == 0.75f && pass->target.clearStencil == 37, "explicit depth/stencil clear lost its aspects or values"); + Require(!queue.context.contains(0x1b3), "clear test accidentally supplies pixel inputs"); + auto invalid = queue; + invalid.context[0x8e] = 0xf; + expectFailure([&] { DecodeDepthClearPass(invalid); }, "color writes"); + invalid = queue; + invalid.context[0x91] = 0x40020; + expectFailure([&] { DecodeDepthClearPass(invalid); }, "part of a surface"); + invalid = queue; + invalid.context[0x2] = 1u << 24u; + expectFailure([&] { DecodeDepthClearPass(invalid); }, "writable enabled depth plane"); + invalid = queue; + invalid.context[0x2] = 1u << 13u; + expectFailure([&] { DecodeDepthClearPass(invalid); }, "multiple slices"); + queue.context[0x000] = 1; + queue.context[0x200] = 0x76; + Require(DecodeDepthClearPass(queue)->aspects == VK_IMAGE_ASPECT_DEPTH_BIT, "depth-only clear included stencil"); + queue.context[0x000] = 2; + queue.context[0x200] = 0x701; + Require(DecodeDepthClearPass(queue)->aspects == VK_IMAGE_ASPECT_STENCIL_BIT, "stencil-only clear included depth"); + queue.context[0x200] = 0x773; + Require(DecodeDepthClearPass(queue)->aspects == VK_IMAGE_ASPECT_STENCIL_BIT, "an always-pass read-only depth test rejected a stencil clear"); + queue.context[0x200] = 0x777; + expectFailure([&] { DecodeDepthClearPass(queue); }, "ordinary depth work"); + queue.context[0x200] = 0x733; + expectFailure([&] { DecodeDepthClearPass(queue); }, "ordinary depth work"); + queue.context[0x200] = 0x77b; + expectFailure([&] { DecodeDepthClearPass(queue); }, "depth bounds"); +} + +void depthCopyPassTests() { + using namespace AgcDriver::Graphics; + auto queue = makeState(); + Require(!DecodeDepthCopyPass(queue), "unconfigured depth override became a copy"); + queue.context[0x000] = 0x60; + queue.context[0x00a] = 0; + queue.context[0x00b] = std::bit_cast(1.0f); + queue.context[0x003] = 0xf8000000; + queue.context[0x002] = 0; + queue.context[0x200] = 0; + queue.context[0x202] = 0xcc0000; + queue.context[0x8e] = queue.context[0x8f] = 0; + queue.context[0x010] = 0xa0000183; + queue.context[0x011] = 0x20000181; + queue.context[0x012] = 0x100; + queue.context[0x013] = 0x200; + queue.context[0x014] = 0x300; + queue.context[0x015] = 0x400; + queue.context[0x007] = (3u << 16u) | 63u; + const auto pass = DecodeDepthCopyPass(queue); + Require(pass && pass->source.address == 0x10000 && pass->destination.address == 0x30000 && pass->source.stencilAddress == 0x20000 && pass->destination.stencilAddress == 0x40000 && pass->aspects == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT), "depth override copy lost its read/write bases or aspects"); + auto changed = queue; + changed.context[0x003] = 0x28000000; + Require(DecodeDepthCopyPass(changed)->aspects == VK_IMAGE_ASPECT_DEPTH_BIT, "depth-only override copied stencil"); + changed.context[0x003] = 0x50000000; + Require(DecodeDepthCopyPass(changed)->aspects == VK_IMAGE_ASPECT_STENCIL_BIT, "stencil-only override copied depth"); + changed.context[0x003] = 0x18000000; + Require(!DecodeDepthCopyPass(changed), "dirty without valid requested a depth copy"); + changed.context[0x003] = 0x60000000; + Require(!DecodeDepthCopyPass(changed), "valid without dirty requested a depth copy"); + changed = queue; + changed.context[0x202] = 0xcc0010; + Require(!DecodeDepthCopyPass(changed), "ordinary color rendering became a depth copy"); + changed = queue; + changed.context[0x014] = changed.context[0x012]; + changed.context[0x015] = changed.context[0x013]; + Require(!DecodeDepthCopyPass(changed), "in-place expansion became a copy"); + changed = queue; + changed.context[0x002] = 2u | (2u << 13u); + changed.context[0x01a] = changed.context[0x01c] = 1; + const auto sliced = DecodeDepthCopyPass(changed); + Require(sliced->source.address == (1ull << 40u) + 0x10000 + 2 * DepthSliceBytes({64, 4}, 4) && sliced->destination.address == (1ull << 40u) + 0x30000 + 2 * DepthSliceBytes({64, 4}, 4), "depth copy lost slice or high address bits"); + changed = queue; + changed.context[0x200] = 2; + expectFailure([&] { DecodeDepthCopyPass(changed); }, "tests"); + changed = queue; + changed.context[0x000] = 0x61; + expectFailure([&] { DecodeDepthCopyPass(changed); }, "clear"); + changed = queue; + changed.context[0x002] = 1u << 13u; + expectFailure([&] { DecodeDepthCopyPass(changed); }, "multiple slices"); + changed = queue; + changed.context[0x091] = 0x40020; + expectFailure([&] { DecodeDepthCopyPass(changed); }, "part of a surface"); +} + void metadataPassTests() { using AgcDriver::Graphics::ColorMetadataPass; using AgcDriver::Graphics::DecodeColorMetadataPass; @@ -2289,6 +2462,7 @@ int main() { AgcDriver::Graphics::ValidateDepthBounds(unrestricted, bounded); } stateTests(); + depthMaintenanceTests(); hardwareScreenOffsetTests(); srgb8TargetTests(); DepthClipTests(); @@ -2300,6 +2474,8 @@ int main() { CompactedExportTests(); metadataPassTests(); cmaskTests(); + depthClearPassTests(); + depthCopyPassTests(); ShaderStageTests(); PixelInputLayoutTests(); InitialContextTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 61d55d291841..a2ae9661741b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" @@ -6,7 +7,9 @@ #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -16,6 +19,8 @@ #include "prx/libc/include/GuestWriteWatch.hpp" #include "ResidentPresent.hpp" #include "SampleLod_spv.h" +#include "SampleDepthArray_spv.h" +#include "SampleStencilArray_spv.h" #include #ifdef _WIN32 #ifndef NOMINMAX @@ -28,9 +33,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -183,11 +190,13 @@ class Device { function("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory); VkPhysicalDeviceProperties properties{}; function("vkGetPhysicalDeviceProperties")(context.physical, &properties); + std::cout << "Vulkan device: " << properties.deviceName << '\n'; context.limits = properties.limits; context.bufferDeviceAddress = true; context.formatProperties = function("vkGetPhysicalDeviceFormatProperties"); context.Function("vkGetDeviceQueue")(context.device, family, 0, &context.queue); VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + pool.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; pool.queueFamilyIndex = family; Check(context.Function("vkCreateCommandPool")(context.device, &pool, nullptr, &context.pool), "vkCreateCommandPool"); } catch (...) { @@ -2460,7 +2469,7 @@ void dataRefreshTests(const Device& device, Recorder& recorder) { class SampleProgram { public: - SampleProgram(const Context& context, Recorder& recorder) : context(context), recorder(recorder), result(context, sizeof(float) * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) { + SampleProgram(const Context& context, Recorder& recorder, std::span code = SAMPLE_LOD_SPV, bool integer = false) : context(context), recorder(recorder), result(context, sizeof(float) * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) { VkDescriptorSetLayoutBinding bindings[2]{}; bindings[0] = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; bindings[1] = {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; @@ -2468,7 +2477,7 @@ class SampleProgram { layoutInfo.bindingCount = 2; layoutInfo.pBindings = bindings; Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &layoutInfo, nullptr, &setLayout), "vkCreateDescriptorSetLayout"); - const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(float)}; + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(float) * 2}; VkPipelineLayoutCreateInfo pipelineLayoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &setLayout; @@ -2476,8 +2485,8 @@ class SampleProgram { pipelineLayoutInfo.pPushConstantRanges = &push; Check(context.Function("vkCreatePipelineLayout")(context.device, &pipelineLayoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout"); VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - moduleInfo.codeSize = sizeof(SAMPLE_LOD_SPV); - moduleInfo.pCode = SAMPLE_LOD_SPV; + moduleInfo.codeSize = code.size_bytes(); + moduleInfo.pCode = code.data(); Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule"); VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, module, "main", nullptr}; @@ -2486,7 +2495,7 @@ class SampleProgram { VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; samplerInfo.magFilter = VK_FILTER_NEAREST; samplerInfo.minFilter = VK_FILTER_NEAREST; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.mipmapMode = integer ? VK_SAMPLER_MIPMAP_MODE_NEAREST : VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.addressModeU = samplerInfo.addressModeV = samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.maxLod = VK_LOD_CLAMP_NONE; Check(context.Function("vkCreateSampler")(context.device, &samplerInfo, nullptr, &sampler), "vkCreateSampler"); @@ -2509,7 +2518,7 @@ class SampleProgram { SampleProgram(const SampleProgram&) = delete; SampleProgram& operator=(const SampleProgram&) = delete; - float Red(VkImageView view, VkImageLayout layout, float lod) { + float Red(VkImageView view, VkImageLayout layout, float lod, float layer = 0) { VkDescriptorSetAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; allocateInfo.descriptorPool = pool; allocateInfo.descriptorSetCount = 1; @@ -2534,7 +2543,8 @@ class SampleProgram { RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr); - context.Function("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(lod), &lod); + const float parameters[]{lod, layer}; + context.Function("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(parameters), parameters); context.Function("vkCmdDispatch")(commands, 1, 1, 1); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); recorder.Submit(); @@ -2561,6 +2571,516 @@ void expectRed(float got, float want, const char* what) { if (std::abs(got - want) > 1.5f / 255.0f) throw std::runtime_error(std::string(what) + ": read " + std::to_string(got) + ", expected " + std::to_string(want)); } +void depthClearPassTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + DepthTarget target{0x10000, 0x20000, {64, 64}, VK_FORMAT_D32_SFLOAT_S8_UINT, 0.25f, 19}; + SampleProgram program(context, recorder); + struct Release { + const Context& context; + Recorder& recorder; + ~Release() { recorder.Sync(); ClearDepthSurfaces(context.device); } + } release{context, recorder}; + RunDepthClearPass(context, {target, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT}); + GuestTextureResource resource{}; + resource.baseAddress = target.address; + resource.width = 64; + resource.height = 64; + resource.mipCount = 1; + resource.dimension = TextureDimension::k2D; + resource.format = 22; + const std::array words{}; + const VkComponentMapping mapping{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; + const auto texture = DepthSurfaceTexture(context, words, resource, mapping); + Require(texture != nullptr, "depth clear did not create a sampleable surface"); + expectRed(program.Red(texture->View(), texture->Layout(), 0.0f), 0.25f, "initial depth clear"); + target.clearDepth = 0.75f; + RunDepthClearPass(context, {target, VK_IMAGE_ASPECT_DEPTH_BIT}); + expectRed(program.Red(texture->View(), texture->Layout(), 0.0f), 0.75f, "repeated depth clear kept old depth"); + target.clearDepth = 0.0f; + target.clearStencil = 47; + RunDepthClearPass(context, {target, VK_IMAGE_ASPECT_STENCIL_BIT}); + expectRed(program.Red(texture->View(), texture->Layout(), 0.0f), 0.75f, "stencil-only clear changed depth"); + recorder.Sync(); +} + +void depthCopyPassTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + SampleProgram depthProgram(context, recorder); + SampleProgram stencilProgram(context, recorder, SAMPLE_Stencil_ARRAY_SPV, true); + struct Release { + const Context& context; + Recorder& recorder; + ~Release() { recorder.Sync(); ClearDepthSurfaces(context.device); } + } release{context, recorder}; + for (const auto format : {VK_FORMAT_D16_UNORM, VK_FORMAT_D32_SFLOAT_S8_UINT}) { + const bool stencil = format == VK_FORMAT_D32_SFLOAT_S8_UINT; + const VkImageAspectFlags both = VK_IMAGE_ASPECT_DEPTH_BIT | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); + DepthTarget source{stencil ? 0x50000u : 0x10000u, stencil ? 0x60000u : 0u, {64, 64}, format, 0.25f, 19}; + auto destination = source; + destination.address += 0x20000; + if (stencil) destination.stencilAddress += 0x20000; + RunDepthClearPass(context, {source, both}); + const auto submissions = recorder.Submissions(); + RunDepthCopyPass(context, {source, destination, both}); + Require(recorder.Submissions() == submissions, "depth copy submitted or waited instead of recording"); + GuestTextureResource resource{}; + resource.baseAddress = destination.address; + resource.width = 64; + resource.height = 64; + resource.mipCount = 1; + resource.dimension = TextureDimension::k2D; + resource.tileMode = TextureTileMode::kZ64KBX; + resource.format = stencil ? 22 : 7; + const std::array words{}; + const VkComponentMapping mapping{}; + const auto texture = DepthSurfaceTexture(context, words, resource, mapping); + Require(texture != nullptr, "depth copy did not create a sampleable destination"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0), 0.25f, "copied depth contents"); + source.clearDepth = 0.75f; + RunDepthClearPass(context, {source, VK_IMAGE_ASPECT_DEPTH_BIT}); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0), 0.25f, "copy destination aliases its source"); + RunDepthCopyPass(context, {source, destination, VK_IMAGE_ASPECT_DEPTH_BIT}); + Require(DepthSurfaceTexture(context, words, resource, mapping) == texture, "repeated depth copy replaced the destination view"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0), 0.75f, "repeated depth copy kept stale contents"); + if (stencil) { + auto stencilResource = resource; + stencilResource.baseAddress = destination.stencilAddress; + stencilResource.format = 5; + stencilResource.dimension = TextureDimension::k2DArray; + const auto stencilTexture = DepthSurfaceTexture(context, words, stencilResource, mapping); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 0), 19, "depth-only copy changed destination stencil"); + source.clearStencil = 47; + RunDepthClearPass(context, {source, VK_IMAGE_ASPECT_STENCIL_BIT}); + RunDepthCopyPass(context, {source, destination, VK_IMAGE_ASPECT_STENCIL_BIT}); + Require(DepthSurfaceTexture(context, words, stencilResource, mapping) == stencilTexture, "stencil copy replaced the destination view"); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 0), 47, "stencil copy did not refresh its sampled view"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0), 0.75f, "stencil-only copy changed destination depth"); + } + } +} + +class DepthOperations { +public: + explicit DepthOperations(Context& context) : context(context), resolver(context.deviceProc), + createImage(context.Function("vkCreateImage")), + copyImage(context.Function("vkCmdCopyImage")), + uploadImage(context.Function("vkCmdCopyBufferToImage")) { + Require(active == nullptr, "nested depth operation counters"); + active = this; + context.deviceProc = Resolve; + } + ~DepthOperations() { + context.deviceProc = resolver; + active = nullptr; + } + std::size_t images = 0; + std::size_t copies = 0; + std::size_t uploads = 0; + +private: + static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL Resolve(VkDevice device, const char* name) { + if (std::strcmp(name, "vkCreateImage") == 0) return reinterpret_cast(CreateImage); + if (std::strcmp(name, "vkCmdCopyImage") == 0) return reinterpret_cast(CopyImage); + if (std::strcmp(name, "vkCmdCopyBufferToImage") == 0) return reinterpret_cast(UploadImage); + return active->resolver(device, name); + } + static VKAPI_ATTR VkResult VKAPI_CALL CreateImage(VkDevice device, const VkImageCreateInfo* info, const VkAllocationCallbacks* allocator, VkImage* image) { + const auto result = active->createImage(device, info, allocator, image); + if (result == VK_SUCCESS) ++active->images; + return result; + } + static VKAPI_ATTR void VKAPI_CALL CopyImage(VkCommandBuffer commands, VkImage source, VkImageLayout sourceLayout, VkImage destination, VkImageLayout destinationLayout, std::uint32_t count, const VkImageCopy* regions) { + active->copies += count; + active->copyImage(commands, source, sourceLayout, destination, destinationLayout, count, regions); + } + static VKAPI_ATTR void VKAPI_CALL UploadImage(VkCommandBuffer commands, VkBuffer source, VkImage destination, VkImageLayout layout, std::uint32_t count, const VkBufferImageCopy* regions) { + active->uploads += count; + active->uploadImage(commands, source, destination, layout, count, regions); + } + Context& context; + PFN_vkGetDeviceProcAddr resolver; + PFN_vkCreateImage createImage; + PFN_vkCmdCopyImage copyImage; + PFN_vkCmdCopyBufferToImage uploadImage; + static inline DepthOperations* active = nullptr; +}; + +void clearDepthAttachment(const Context& context, Recorder& recorder, const DepthTarget& target, float depth, const std::function& beforeWrite = {}) { + const auto view = DepthSurfaceView(context, target); + if (beforeWrite) beforeWrite(); + VkAttachmentDescription attachment{}; + attachment.format = target.format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + const VkAttachmentReference reference{0, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.pDepthStencilAttachment = &reference; + const auto stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + const auto access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + const VkSubpassDependency dependencies[]{ + {VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, stages, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, access, 0}, + {0, VK_SUBPASS_EXTERNAL, stages, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, access, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, 0}, + }; + VkRenderPassCreateInfo info{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + info.attachmentCount = 1; + info.pAttachments = &attachment; + info.subpassCount = 1; + info.pSubpasses = &subpass; + info.dependencyCount = 2; + info.pDependencies = dependencies; + VkRenderPass pass = VK_NULL_HANDLE; + VkFramebuffer framebuffer = VK_NULL_HANDLE; + struct Release { + const Context& context; + VkRenderPass& pass; + VkFramebuffer& framebuffer; + Recorder& recorder; + ~Release() { + recorder.Sync(); + if (framebuffer) context.Function("vkDestroyFramebuffer")(context.device, framebuffer, nullptr); + if (pass) context.Function("vkDestroyRenderPass")(context.device, pass, nullptr); + } + } release{context, pass, framebuffer, recorder}; + Check(context.Function("vkCreateRenderPass")(context.device, &info, nullptr, &pass), "depth test render pass"); + VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + framebufferInfo.renderPass = pass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = &view; + framebufferInfo.width = target.extent.width; + framebufferInfo.height = target.extent.height; + framebufferInfo.layers = 1; + Check(context.Function("vkCreateFramebuffer")(context.device, &framebufferInfo, nullptr, &framebuffer), "depth test framebuffer"); + VkClearValue clear{}; + clear.depthStencil.depth = depth; + VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + begin.renderPass = pass; + begin.framebuffer = framebuffer; + begin.renderArea.extent = target.extent; + begin.clearValueCount = 1; + begin.pClearValues = &clear; + const auto commands = recorder.Commands(); + context.Function("vkCmdBeginRenderPass")(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); + context.Function("vkCmdEndRenderPass")(commands); + NoteDepthSurfaceWrite(context, target, VK_IMAGE_ASPECT_DEPTH_BIT); + recorder.Sync(); +} + +void depthArrayTests(const Device& device, Recorder& recorder, bool failBeforeSync = false) { + constexpr std::size_t reservedBytes = 12 * 1024 * 1024; +#ifdef _WIN32 + void* reserved = VirtualAlloc(nullptr, reservedBytes, MEM_RESERVE, PAGE_NOACCESS); + Require(reserved != nullptr, "cannot reserve depth array test addresses"); +#else + void* reserved = mmap(nullptr, reservedBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(reserved != MAP_FAILED, "cannot reserve depth array test addresses"); +#endif + struct Reservation { + void* address; + ~Reservation() { +#ifdef _WIN32 + VirtualFree(address, 0, MEM_RELEASE); +#else + munmap(address, reservedBytes); +#endif + } + } reservation{reserved}; + const auto reservedAddress = reinterpret_cast(reserved); + auto context = device.GetContext(); + DepthOperations operations(context); + TextureDetiler detiler(context); + context.detiler = &detiler; + TextureCache cache(context); + context.textureCache = &cache; + SampleProgram arrayProgram(context, recorder, SAMPLE_Depth_ARRAY_SPV); + SampleProgram stencilProgram(context, recorder, SAMPLE_Stencil_ARRAY_SPV, true); + SampleProgram flatProgram(context, recorder); + struct Release { + const Context& context; + Recorder& recorder; + ~Release() { recorder.Sync(); ClearDepthSurfaces(context.device); } + } release{context, recorder}; + for (const auto format : {VK_FORMAT_D16_UNORM, VK_FORMAT_D32_SFLOAT_S8_UINT}) { + const bool stencil = format == VK_FORMAT_D32_SFLOAT_S8_UINT; + DepthTarget target{reservedAddress + (stencil ? 0x500000u : 0x100000u), stencil ? reservedAddress + 0x900000u : 0u, {129, 137}, format, 0.0f, 0}; + const auto depthStride = DepthSliceBytes(target.extent, stencil ? 4u : 2u); + const auto stencilStride = DepthSliceBytes(target.extent, 1); + std::array targets; + for (std::uint32_t layer = 0; layer < targets.size(); ++layer) { + auto& slice = targets[layer]; + slice = target; + slice.address += layer * depthStride; + if (stencil) slice.stencilAddress += layer * stencilStride; + slice.clearDepth = (layer + 1) / 8.0f; + slice.clearStencil = static_cast(11 + layer); + RunDepthClearPass(context, {slice, VK_IMAGE_ASPECT_DEPTH_BIT | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u)}); + } + GuestTextureResource resource{}; + resource.baseAddress = target.address; + resource.width = target.extent.width; + resource.height = target.extent.height; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kZ64KBX; + resource.dimension = TextureDimension::k2DArray; + resource.depthOrLastArray = 3; + resource.format = stencil ? 22 : 7; + const auto descriptorWords = [](const GuestTextureResource& descriptor) { + std::array words{}; + words[0] = static_cast(descriptor.baseAddress >> 8); + words[1] = static_cast(descriptor.baseAddress >> 40) | (descriptor.format << 20) | (((descriptor.width - 1) & 3u) << 30); + words[2] = ((descriptor.width - 1) >> 2) | ((descriptor.height - 1) << 14); + words[3] = 0xd1800fac; + words[4] = descriptor.depthOrLastArray | (descriptor.baseArray << 16); + return words; + }; + const VkComponentMapping swizzle{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; + const auto sample = [&](const GuestTextureResource& descriptor) { + return DepthSurfaceTexture(context, descriptorWords(descriptor), descriptor, swizzle); + }; + const auto texture = sample(resource); + Require(texture != nullptr && texture->FirstLayerView() != VK_NULL_HANDLE, "depth array views are missing"); + for (std::uint32_t layer = 0; layer < targets.size(); ++layer) expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, static_cast(layer)), targets[layer].clearDepth, "depth array slice contents"); + expectRed(flatProgram.Red(texture->FirstLayerView(), texture->Layout(), 0), targets[0].clearDepth, "depth array first-layer view"); + Require(sample(resource) == texture, "an unchanged depth array was not reused"); + const auto variantImages = operations.images; + const auto variantCopies = operations.copies; + auto oneMapping = swizzle; + oneMapping.r = VK_COMPONENT_SWIZZLE_ONE; + const auto oneTexture = DepthSurfaceTexture(context, descriptorWords(resource), resource, oneMapping); + expectRed(arrayProgram.Red(oneTexture->View(), oneTexture->Layout(), 0, 2), 1.0f, "depth array view ignored its component mapping"); + auto alphaMapping = swizzle; + alphaMapping.a = VK_COMPONENT_SWIZZLE_R; + const auto alphaTexture = DepthSurfaceTexture(context, descriptorWords(resource), resource, alphaMapping); + if (failBeforeSync) { + auto extra = resource; + extra.baseArray = 1; + static_cast(sample(extra)); + throw std::runtime_error("intentional depth test failure with pending copies"); + } + recorder.Sync(); + Require(operations.images == variantImages, "a component mapping allocated another depth array"); + Require(operations.copies == variantCopies, "a component mapping copied unchanged depth slices"); + const auto readOnlyCopies = operations.copies; + static_cast(DepthSurfaceView(context, targets[2])); + State readOnly{}; + readOnly.depth = targets[2]; + readOnly.depthTest = true; + readOnly.stencilTest = stencil; + readOnly.stencilFront.writeMask = 0xff; + readOnly.stencilBack.passOp = VK_STENCIL_OP_REPLACE; + NoteDepthSurfaceWrite(context, readOnly); + static_cast(sample(resource)); + Require(operations.copies == readOnlyCopies, "read-only attachment use copied a depth array"); + auto disabled = readOnly; + disabled.depthTest = false; + disabled.depthWrite = true; + disabled.stencilTest = false; + disabled.stencilFront.passOp = VK_STENCIL_OP_REPLACE; + NoteDepthSurfaceWrite(context, disabled); + static_cast(sample(resource)); + Require(operations.copies == readOnlyCopies, "disabled depth testing copied a depth array"); + ShaderRecompiler::RecompileResult compiled; + ShaderRecompiler::DescriptorBinding binding; + binding.kind = ShaderRecompiler::DescriptorKind::SampledImage; + binding.role = ShaderRecompiler::DescriptorRole::GuestImages; + binding.descriptorSet = 0; + binding.binding = 0; + binding.count = 1; + binding.imageShape = ShaderRecompiler::DescriptorImageShape::Image2DArray; + binding.imageSamplers = {0}; + const auto words = descriptorWords(resource); + binding.guestDescriptor.assign(words.begin(), words.end()); + compiled.bindings.push_back(binding); + const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &compiled, 0}; + ShaderResources bindings(context, shader); + Require(bindings.Reusable(), "depth array bindings cannot be revalidated"); + targets[2].clearDepth = 0.875f; + RunDepthClearPass(context, {targets[2], VK_IMAGE_ASPECT_DEPTH_BIT}); + Require(bindings.ProveCurrent(shader), "depth array bindings failed revalidation after a clear"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.875f, "cached shader resources did not refresh a depth array"); + expectRed(arrayProgram.Red(alphaTexture->View(), alphaTexture->Layout(), 0, 2), 0.875f, "component mappings do not share depth refreshes"); + Require(sample(resource) == texture, "updating a depth array replaced its sampled view"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.875f, "depth array retained a cleared layer"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 1), targets[1].clearDepth, "depth array clear changed another layer"); + auto subset = resource; + subset.baseArray = 2; + auto subsetTexture = sample(subset); + expectRed(arrayProgram.Red(subsetTexture->View(), subsetTexture->Layout(), 0, 0), 0.875f, "depth array BASE_ARRAY is ignored"); + expectRed(arrayProgram.Red(subsetTexture->View(), subsetTexture->Layout(), 0, 1), targets[3].clearDepth, "depth array subset last layer"); + expectRed(flatProgram.Red(subsetTexture->FirstLayerView(), subsetTexture->Layout(), 0), 0.875f, "depth array first-layer view ignores BASE_ARRAY"); + subset.baseArray = 3; + subsetTexture = sample(subset); + expectRed(arrayProgram.Red(subsetTexture->View(), subsetTexture->Layout(), 0, 0), targets[3].clearDepth, "single-layer depth array"); + clearDepthAttachment(context, recorder, targets[2], 0.625f, [&] { + Require(sample(resource) == texture, "attachment setup replaced a depth array view"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.875f, "depth array changed before the attachment write"); + }); + Require(bindings.ProveCurrent(shader), "depth array bindings failed revalidation after attachment use"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.625f, "depth array retained old attachment contents"); + auto shifted = resource; + shifted.baseAddress -= depthStride; + shifted.baseArray = 1; + shifted.depthOrLastArray = 4; + const auto shiftedTexture = sample(shifted); + Require(shiftedTexture != nullptr, "a depth array subview requires its unused base slice to be resident"); + expectRed(arrayProgram.Red(shiftedTexture->View(), shiftedTexture->Layout(), 0, 0), targets[0].clearDepth, "depth array shifted first layer"); + expectRed(arrayProgram.Red(shiftedTexture->View(), shiftedTexture->Layout(), 0, 2), 0.625f, "depth array shifted attachment layer"); + if (stencil) { + auto stencilResource = resource; + stencilResource.baseAddress = target.stencilAddress; + stencilResource.format = 5; + const auto stencilTexture = sample(stencilResource); + for (std::uint32_t layer = 0; layer < targets.size(); ++layer) expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, static_cast(layer)), targets[layer].clearStencil, "stencil array slice contents"); + const auto stencilCopies = operations.copies; + NoteDepthSurfaceWrite(context, readOnly); + static_cast(sample(stencilResource)); + Require(operations.copies == stencilCopies, "read-only attachment use copied a stencil array"); + targets[2].clearStencil = 37; + RunDepthClearPass(context, {targets[2], VK_IMAGE_ASPECT_STENCIL_BIT}); + Require(sample(stencilResource) == stencilTexture, "stencil array view changed after a clear"); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 2), 37, "stencil array retained a cleared layer"); + Require(operations.copies == stencilCopies + 1, "a stencil clear copied unchanged slices"); + static_cast(sample(resource)); + Require(operations.copies == stencilCopies + 1, "a stencil-only clear copied depth"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.625f, "stencil array clear changed depth"); + RunDepthClearPass(context, {targets[2], VK_IMAGE_ASPECT_DEPTH_BIT}); + static_cast(sample(stencilResource)); + Require(operations.copies == stencilCopies + 1, "a depth-only clear copied stencil"); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 2), 37, "depth array clear changed stencil"); + static_cast(sample(resource)); + Require(operations.copies == stencilCopies + 2, "a depth clear copied unchanged slices"); + expectRed(arrayProgram.Red(texture->View(), texture->Layout(), 0, 2), targets[2].clearDepth, "depth-only clear did not refresh depth"); + auto writableStencil = readOnly; + writableStencil.stencilBack.writeMask = 0xff; + NoteDepthSurfaceWrite(context, writableStencil); + static_cast(sample(resource)); + Require(operations.copies == stencilCopies + 2, "a stencil-writing state copied depth"); + static_cast(sample(stencilResource)); + Require(operations.copies == stencilCopies + 3, "a stencil-writing state did not refresh its slice"); + } + auto missing = resource; + missing.depthOrLastArray = 4; + bool refused = false; + try { static_cast(sample(missing)); } catch (const std::runtime_error&) { refused = true; } + Require(refused, "depth array with an unbacked slice was silently sampled"); + shifted.baseArray = 0; + refused = false; + try { static_cast(sample(shifted)); } catch (const std::runtime_error&) { refused = true; } + Require(refused, "depth array with a missing first slice ignored its resident later layers"); + } + recorder.Sync(); +} + +void depthArrayGuestTests(const Device& device, Recorder& recorder) { + auto context = device.GetContext(); + DepthOperations operations(context); + TextureDetiler detiler(context); + context.detiler = &detiler; + SampleProgram depthProgram(context, recorder, SAMPLE_Depth_ARRAY_SPV); + SampleProgram stencilProgram(context, recorder, SAMPLE_Stencil_ARRAY_SPV, true); + for (const auto format : {VK_FORMAT_D16_UNORM, VK_FORMAT_D32_SFLOAT_S8_UINT}) { + const bool stencil = format == VK_FORMAT_D32_SFLOAT_S8_UINT; + const VkExtent2D extent{129, 137}; + const auto depthBytes = stencil ? 4u : 2u; + const auto depthStride = DepthSliceBytes(extent, depthBytes); + const auto stencilStride = DepthSliceBytes(extent, 1); + std::vector memory(3 * (depthStride + stencilStride) + 256); + const auto address = (reinterpret_cast(memory.data()) + 255) & ~std::uint64_t{255}; + DepthTarget target{address, stencil ? address + 3 * depthStride : 0, extent, format, 0.25f, 19}; + struct Release { + const Context& context; + Recorder& recorder; + ~Release() { recorder.Sync(); ClearDepthSurfaces(context.device); } + } release{context, recorder}; + RunDepthClearPass(context, {target, VK_IMAGE_ASPECT_DEPTH_BIT | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u)}); + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = extent.width; + resource.height = extent.height; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kZ64KBX; + resource.dimension = TextureDimension::k2DArray; + resource.depthOrLastArray = 2; + resource.format = stencil ? 22 : 7; + const std::array words{}; + const VkComponentMapping mapping{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; + const auto sample = [&](const GuestTextureResource& descriptor) { + auto texture = DepthSurfaceTexture(context, words, descriptor, mapping); + Require(texture != nullptr, "mixed depth array was not recognized"); + return texture; + }; + const auto writeCenter = [&](std::uint64_t base, std::uint32_t bytes, std::uint32_t layer, std::uint32_t value) { + const auto block = ThinBlockLayout(TextureTileMode::kZ64KBX, bytes); + const auto* equation = FindTextureSwizzleEquation(24, bytes); + Require(equation != nullptr, "missing depth swizzle equation"); + const auto x = extent.width / 2; + const auto y = extent.height / 2; + const auto coordinates = x | (y << 12) | (layer << 24); + std::uint64_t offset = 0; + for (std::uint32_t bit = 0; bit < equation->bits.size(); ++bit) offset |= std::uint64_t{std::popcount(coordinates & equation->bits[bit]) & 1u} << bit; + offset += ((y / block[2]) * ((extent.width + block[1] - 1) / block[1]) + x / block[1]) * block[0]; + const auto destination = base + layer * DepthSliceBytes(extent, bytes) + offset; + AgcDriver::GuestMemory::Write(destination, std::span(reinterpret_cast(&value), bytes)); + }; + const auto depthValue = [&](float value) { return stencil ? std::bit_cast(value) : static_cast(value * 65535.0f); }; + writeCenter(address, depthBytes, 1, depthValue(0.5f)); + writeCenter(address, depthBytes, 2, depthValue(0.75f)); + auto texture = sample(resource); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 0), 0.25f, "mixed array lost its resident slice"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 1), 0.5f, "mixed array did not detile guest layer one"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.75f, "mixed array did not detile guest layer two"); + Require(sample(resource) == texture, "unchanged mixed array was replaced"); + const auto variantImages = operations.images; + const auto variantCopies = operations.copies; + const auto variantUploads = operations.uploads; + auto alphaMapping = mapping; + alphaMapping.a = VK_COMPONENT_SWIZZLE_R; + const auto alphaTexture = DepthSurfaceTexture(context, words, resource, alphaMapping); + recorder.Sync(); + Require(operations.images == variantImages, "a mixed array mapping allocated another image"); + Require(operations.copies == variantCopies && operations.uploads == variantUploads, "a mixed array mapping copied or uploaded unchanged slices"); + writeCenter(address, depthBytes, 1, depthValue(0.875f)); + Require(sample(resource) == texture, "guest write replaced the mixed array view"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 1), 0.875f, "mixed array retained old guest bytes"); + expectRed(depthProgram.Red(alphaTexture->View(), alphaTexture->Layout(), 0, 1), 0.875f, "mixed array mappings do not share guest uploads"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.75f, "guest write changed an unrelated layer"); + auto subset = resource; + subset.baseArray = 2; + auto subview = sample(subset); + expectRed(depthProgram.Red(subview->View(), subview->Layout(), 0), 0.75f, "guest-only subview lost its absolute slice swizzle"); + if (stencil) { + auto plane = resource; + plane.baseAddress = target.stencilAddress; + plane.format = 5; + writeCenter(plane.baseAddress, 1, 1, 37); + writeCenter(plane.baseAddress, 1, 2, 59); + auto stencilTexture = sample(plane); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 0), 19, "mixed stencil array lost its resident slice"); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 1), 37, "mixed stencil array lost guest layer one"); + expectRed(stencilProgram.Red(stencilTexture->View(), stencilTexture->Layout(), 0, 2), 59, "mixed stencil array lost guest layer two"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 1), 0.875f, "guest stencil upload changed depth"); + } + auto rendered = target; + rendered.address += 2 * depthStride; + if (stencil) rendered.stencilAddress += 2 * stencilStride; + rendered.clearDepth = 0.125f; + RunDepthClearPass(context, {rendered, VK_IMAGE_ASPECT_DEPTH_BIT}); + Require(sample(resource) == texture, "rendering a guest layer replaced the mixed array view"); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.125f, "mixed array ignored a newly rendered layer"); + Require(sample(subset) == subview, "rendering replaced a guest-only subview"); + expectRed(depthProgram.Red(subview->View(), subview->Layout(), 0), 0.125f, "guest-only subview ignored a newly rendered layer"); + recorder.Sync(); + ClearDepthSurfaces(context.device); + expectRed(depthProgram.Red(texture->View(), texture->Layout(), 0, 2), 0.125f, "a surviving depth array view lost its backing"); + expectRed(depthProgram.Red(alphaTexture->View(), alphaTexture->Layout(), 0, 1), 0.875f, "a surviving component view lost its backing"); + expectRed(depthProgram.Red(subview->View(), subview->Layout(), 0), 0.125f, "a surviving single-layer array view lost its backing"); + } +} + void minLodTests(const Device& device, Recorder& recorder) { const auto& context = device.GetContext(); if (!context.imageViewMinLod) { @@ -2812,13 +3332,42 @@ void keysFillTests(const Device& device, Recorder& recorder) { recorder.Sync(); } -int main() { +int main(int argc, char** argv) { try { Device device; std::lock_guard gpu(GpuMutex()); std::cout << "host imports " << (PrepareImportWatch(device.GetContext()) == ImportWatch::Unwatch ? "are compared" : "stay watched") << '\n'; Recorder recorder(device.GetContext()); recorder.Activate(); + if (argc == 2 && std::string_view(argv[1]) == "--depth-array-failure-only") { + bool failed = false; + try { + depthArrayTests(device, recorder, true); + } catch (const std::runtime_error& error) { + if (std::string_view(error.what()) != "intentional depth test failure with pending copies") throw; + failed = true; + } + Require(failed, "the depth test did not exercise failure cleanup"); + depthArrayTests(device, recorder); + std::cout << "Depth array failure cleanup and subsequent sampling tests passed\n"; + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--depth-array-only") { + depthArrayTests(device, recorder); + depthArrayGuestTests(device, recorder); + std::cout << "Depth/stencil array sampling, subviews and refresh tests passed\n"; + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--depth-clear-only") { + depthClearPassTests(device, recorder); + std::cout << "Resident depth clear sampling and aspect preservation tests passed\n"; + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--depth-copy-only") { + depthCopyPassTests(device, recorder); + std::cout << "Resident depth/stencil copy, refresh and aspect preservation tests passed\n"; + return 0; + } readTrackingTests(device, recorder); writeSettledTests(device, recorder); completionCountTests(device, recorder); @@ -2855,6 +3404,10 @@ int main() { firstLayerViewTests(device, recorder); atomicViewTests(device, recorder); metadataPassTests(device, recorder); + depthClearPassTests(device, recorder); + depthCopyPassTests(device, recorder); + depthArrayTests(device, recorder); + depthArrayGuestTests(device, recorder); pendingKeyStoreTests(device, recorder); sampleDumpTests(device, recorder); std::cout << "Recorder read tracking and label tests passed\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/shaders/SampleArray.comp b/core/libs/prx/libSceAgcDriver/tests/shaders/SampleArray.comp new file mode 100644 index 000000000000..0e2543f99ba5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/shaders/SampleArray.comp @@ -0,0 +1,14 @@ +#version 450 + +layout(local_size_x = 1) in; +#ifdef STENCIL +layout(set = 0, binding = 0) uniform usampler2DArray source; +#else +layout(set = 0, binding = 0) uniform sampler2DArray source; +#endif +layout(set = 0, binding = 1, std430) buffer Result { vec4 texel; }; +layout(push_constant) uniform Parameters { float lod; float layer; } parameters; + +void main() { + texel = vec4(textureLod(source, vec3(0.5, 0.5, parameters.layer), parameters.lod)); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 1787c8127e96..25b371df4c35 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -281,6 +281,9 @@ Throughout the project, every function at every stage either **does exactly what - [CMASK fast clears](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - only a CMASK whose nibbles are all fast-cleared (0) or all expanded (0xF) is modeled, checked over the target's whole CMASK, 4 KiB per 1024x512-pixel metablock (the size PS5 titles clear, for every color format seen); the addressing within a metablock is not decoded. The first draw or fast-clear eliminate over a fast-cleared CMASK fills the whole target with `CB_COLOR_CLEAR_WORD` and stores those bytes as expanded, so a draw that covers only part of the target reads the clear value in the rest as the eliminate would leave it. On a DCC target the clear is in the DCC keys, so its CMASK must be all expanded. A mixed CMASK throws, as does a DCC target's CMASK that is not all expanded and a fast clear of a mipmapped, 3D or array color target - [Host imports](../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) on Linux skip ranges that are not mapped read-write, which are copied instead. The NVIDIA driver (595.91.07) refuses `VK_EXT_external_memory_host` imports of read-only or executable mappings (and shared mappings of regular files) with `VK_ERROR_OUT_OF_DEVICE_MEMORY`, after which every `vkAllocateMemory` on the device fails the same way. Read-only ranges are never imported, even on drivers that accept them. Shared mappings of regular files are not checked for. - [DCC display buffers](../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) are presented only from uniform uncompressed or fast-clear keys; other keys, and register-clear keys over a pending image, throw. Keys are read as one byte per 256 bytes (the driver's model), not in the console's layout. +- [Explicit depth/stencil clears](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) recognize `DB_RENDER_CONTROL.DEPTH_CLEAR_ENABLE` and `STENCIL_CLEAR_ENABLE` before pixel-shader input validation, following the clear selection in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/video_core/renderer_vulkan/vk_rasterizer.cpp). Direct clear draws with a full viewport and scissor clear the selected resident image aspects to `DB_DEPTH_CLEAR` and `DB_STENCIL_CLEAR`. Stencil-only clears allow an enabled read-only depth test with `ALWAYS` comparison, which cannot reject fragments or modify depth; depth bounds and depth writes still throw. They are assumed to cover the viewport; arbitrary clear-draw geometry has not been verified. Partial viewport/scissor clears, multisampling, multiple slices, simultaneous color or ordinary depth/stencil work, and indirect clears throw. HTILE resummarization and metadata-driven clears remain unimplemented. Viewport and scissor bounds do not prove rasterized coverage: vertex/index geometry, clipping and culling are not evaluated. A matching partial draw can overwrite untouched pixels. General use requires a coverage proof or the original rasterized operation. +- [Depth/stencil override copies](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) recognize paired `FORCE_Z_DIRTY`/`FORCE_Z_VALID` and `FORCE_STENCIL_DIRTY`/`FORCE_STENCIL_VALID` flags with distinct read/write bases and disabled color/depth/stencil tests, following [AMD CIK 3D Registers](https://www.x.org/docs/AMD/old/CIK_3D_registers_v2.pdf), pp. 215 and 225. Direct draws with a full viewport and scissor copy selected resident image aspects on the GPU; derived sampled arrays refresh after copies. Like explicit clears, these draws are assumed to cover the viewport. Arbitrary geometry and console equivalence have not been measured. Partial viewport/scissor coverage, multiple slices, multisampling, indirect draws, shared source/destination planes, nonresident sources and partial copies into nonresident destinations throw. Guest-memory coherence and HTILE resummarization remain unimplemented. Viewport and scissor bounds do not prove rasterized coverage: vertex/index geometry, clipping and culling are not evaluated. A matching partial draw can overwrite untouched pixels. General use requires a coverage proof or the original rasterized operation. +- [Depth/stencil texture arrays](../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) assemble resident single-slice targets with GPU copies and refresh changed slices before sampling. Only single-mip, single-sample 2D arrays in the 64 KiB Z layout are implemented. The slice stride uses the same padded block dimensions as depth attachment selection; console layout equivalence has not been measured. Viewed layers without resident targets are detiled from readable guest memory with their absolute slice index, and refreshed after guest writes; compressed guest layers and unbacked memory throw. Component mappings share array storage, but distinct layer ranges remain cached until device teardown; eviction is unimplemented. CPU-write coherence for resident targets, storage-image aliases, other tile modes, cubes and depth mip chains remain unimplemented. - [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) on a device that cannot attach and blend R8_SRGB render into an RGBA8_SRGB copy of the target, so the sRGB encode, decode and blend rounding are the device's; the console's are not measured. - [8_SRGB color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none. - [COPY_DATA from the GPU clock](../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp, not the console's GPU clock. The `COPY_DATA` cache policy fields are accepted and have no effect.