diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index aae04cbfc5a..6dafa8345a5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -61,12 +61,12 @@ 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); struct DepthCopyTag {}; Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag); static VkFormat DepthCopyFormat(VkFormat colorFormat); static bool CanDepthCopyFrom(const StorageTexture& source, const GuestTextureResource& descriptor); - bool IsDepthCopy() const { return depthCopy; } + bool IsDepthCopy() const { return perUseCopy == PerUseCopy::RefreshedOnReuse; } bool RefreshDepthCopy(); struct DepthCopyCounts { std::uint64_t textures; @@ -99,6 +99,10 @@ class Texture { // the source image's own (snapshots, views over other metadata); one per cache entry, so every // object binding the texture shares it. Under GuestMemory::GpuMutex only. DccKeyProof& KeyProof() const { return keyProof; } + enum class PerUseCopy : std::uint8_t { None, RefreshedOnReuse, RemadePerUse }; + PerUseCopy CopyKind() const { return perUseCopy; } + bool RefreshedPerUse() const { return perUseCopy == PerUseCopy::RemadePerUse; } + void MarkRefreshedPerUse() { perUseCopy = PerUseCopy::RemadePerUse; } private: void release() noexcept; @@ -125,7 +129,7 @@ class Texture { std::shared_ptr source; std::shared_ptr storageSource; std::unique_ptr upload; - bool depthCopy = false; + PerUseCopy perUseCopy = PerUseCopy::None; bool copiedOnce = false; std::uint64_t copiedVersion = 0; VkFormat depthFormat = VK_FORMAT_UNDEFINED; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 9f1166b8739..b41a46a9867 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -3,6 +3,9 @@ #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/Graphics/include/TextureTiling.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include #include @@ -12,6 +15,7 @@ #include #include #include +#include #include namespace AgcDriver::Graphics { @@ -42,7 +46,7 @@ class DepthSurface { info.arrayLayers = 1; info.samples = static_cast(target.samples); info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (target.samples == 1 ? VK_IMAGE_USAGE_SAMPLED_BIT : 0u); + info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (target.samples == 1 ? VK_IMAGE_USAGE_SAMPLED_BIT : 0u); info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth"); @@ -120,14 +124,16 @@ class DepthSurface { 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) { + const bool oneSlice = resource.dimension == TextureDimension::k2D || (resource.dimension == TextureDimension::k2DArray && resource.depthOrLastArray == 0); + if ((format != expected && !depthBits) || !oneSlice || resource.width != target.extent.width || resource.height != target.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || 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, hostFormat, stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, components); + const auto viewType = resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; + auto texture = std::make_shared(context, image, hostFormat, stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, components, viewType); textures.emplace(key, texture); return texture; } @@ -139,6 +145,7 @@ class DepthSurface { VkImageAspectFlags pendingClear = 0; float clearDepth = 0.0f; std::uint8_t clearStencil = 0; + const Context context; const DepthTarget target; const VkFormat hostFormat; @@ -148,7 +155,6 @@ class DepthSurface { private: std::map, std::shared_ptr> textures; - void release() noexcept { textures.clear(); if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); @@ -160,6 +166,146 @@ class DepthSurface { } }; +class DepthPlaneCopy { +public: + DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers) { + this->context.bufferPool.reset(); + VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + info.flags = layers % 6u == 0 && extent.width == extent.height ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = format; + info.extent = {extent.width, extent.height, 1}; + info.mipLevels = 1; + info.arrayLayers = layers; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + try { + Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth plane copy"); + VkMemoryRequirements requirements{}; + context.Function("vkGetImageMemoryRequirements")(context.device, image, &requirements); + VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocation.allocationSize = requirements.size; + allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + Check(context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory depth plane copy"); + Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory depth plane copy"); + staging = std::make_unique(context, static_cast(sliceBytes()) * layers, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + } catch (...) { + release(); + throw; + } + } + ~DepthPlaneCopy() { release(); } + DepthPlaneCopy(const DepthPlaneCopy&) = delete; + DepthPlaneCopy& operator=(const DepthPlaneCopy&) = delete; + + std::shared_ptr Refresh(std::span slices, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType) { + Require(slices.size() == layers, "depth plane copy slices do not match its layers"); + const auto geometry = DescribeSurface(resource); + Require(geometry.layers == layers && geometry.sliceLinearBytes == sliceBytes() && !geometry.mips.empty(), "depth plane copy geometry does not match its layers"); + std::vector> uploads; + for (std::uint32_t layer = 0; layer < layers; ++layer) { + if (slices[layer] != VK_NULL_HANDLE) continue; + auto upload = std::make_shared(context, static_cast(geometry.layerBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + GuestMemory::ReadCommitted(resource.baseAddress + geometry.GuestLayerOffset(layer), upload->Bytes().first(static_cast(geometry.layerBytes))); + uploads.push_back(std::move(upload)); + } + if (!uploads.empty()) { + Require(context.detiler != nullptr, "depth plane copy requires a texture detiler"); + context.detiler->BeginBatch(); + } + auto* recorder = RecorderFor(context); + std::unique_ptr batch; + if (recorder == nullptr) batch = std::make_unique(context); + const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle(); + const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier"); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + std::vector regions(layers); + for (std::uint32_t layer = 0; layer < layers; ++layer) { + auto& region = regions[layer]; + region.bufferOffset = sliceBytes() * layer; + region.imageSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}; + region.imageExtent = {extent.width, extent.height, 1}; + if (slices[layer] != VK_NULL_HANDLE) context.Function("vkCmdCopyImageToBuffer")(commands, slices[layer], VK_IMAGE_LAYOUT_GENERAL, staging->Handle(), 1, ®ion); + region.imageSubresource = {aspect(), 0, layer, 1}; + } + if (!uploads.empty()) { + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + std::size_t next = 0; + for (std::uint32_t layer = 0; layer < layers; ++layer) { + if (slices[layer] != VK_NULL_HANDLE) continue; + context.detiler->Dispatch(commands, resource.tileMode, format == VK_FORMAT_D32_SFLOAT ? 4u : 2u, uploads[next++]->Handle(), 0, staging->Handle(), geometry.LinearLayerOffset(layer), geometry.mips.front(), false, layer, geometry.thick); + } + if (recorder != nullptr) { + for (auto& upload : uploads) recorder->Keep(upload); + } + } + VkImageMemoryBarrier toTransfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + toTransfer.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toTransfer.image = image; + toTransfer.subresourceRange = {aspect(), 0, 1, 0, layers}; + const VkBufferMemoryBarrier staged{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, staging->Handle(), 0, VK_WHOLE_SIZE}; + const auto copyCommands = commands; + barrier(copyCommands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &staged, 1, &toTransfer); + context.Function("vkCmdCopyBufferToImage")(copyCommands, staging->Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, layers, regions.data()); + VkImageMemoryBarrier toGeneral = toTransfer; + toGeneral.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toGeneral.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + toGeneral.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier(copyCommands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); + if (batch) batch->SubmitAndWait(); + else Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); + const std::array key{static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a), static_cast(viewType)}; + auto& texture = textures[key]; + if (texture == nullptr) { + texture = std::make_shared(context, image, format, aspect(), components, viewType); + texture->MarkRefreshedPerUse(); + } + return texture; + } + + const Context context; + const VkExtent2D extent; + const VkFormat format; + const std::uint32_t layers; + +private: + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + std::unique_ptr staging; + std::map, std::shared_ptr> textures; + + VkDeviceSize sliceBytes() const { + return static_cast(extent.width) * extent.height * (format == VK_FORMAT_D32_SFLOAT ? 4u : 2u); + } + + VkImageAspectFlags aspect() const { + return format == VK_FORMAT_R16_UINT ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; + } + + void release() noexcept { + textures.clear(); + staging.reset(); + if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); + if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); + image = VK_NULL_HANDLE; + memory = VK_NULL_HANDLE; + } +}; + +std::map, std::unique_ptr>& planeCopies() { + static auto* copies = new std::map, std::unique_ptr>(); + return *copies; +} + bool sameSurface(const DepthTarget& a, const DepthTarget& b) { return a.address == b.address && a.stencilAddress == b.stencilAddress && a.extent.width == b.extent.width && a.extent.height == b.extent.height && a.format == b.format && a.samples == b.samples; } @@ -237,6 +383,8 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target) return surface->view; } surfaces().push_back(std::make_unique(context, target)); + surfaces().back()->clearDepth = target.clearDepth; + surfaces().back()->clearStencil = target.clearStencil; return surfaces().back()->view; } @@ -262,27 +410,58 @@ void NoteHtileDepthClear(std::uint64_t htileAddress) { void ClearDepthSurfaces(VkDevice device) { std::lock_guard lock(surfacesMutex()); std::erase_if(surfaces(), [&](const auto& surface) { return surface->context.device == device; }); + std::erase_if(planeCopies(), [&](const auto& entry) { return std::get<0>(entry.first) == device; }); } std::shared_ptr DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components) { + const auto held = [&](const auto& surface) { + return surface->context.device == context.device && (surface->target.address == resource.baseAddress || (surface->target.stencilAddress != 0 && surface->target.stencilAddress == resource.baseAddress)); + }; + { + std::lock_guard lock(surfacesMutex()); + if (std::none_of(surfaces().begin(), surfaces().end(), held)) return nullptr; + } + std::lock_guard gpu(GuestMemory::GpuMutex()); 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)); }); if (found == list.rend()) return nullptr; - const auto& target = (*found)->target; - if (resource.width != target.extent.width || resource.height != target.extent.height) return nullptr; - const bool stencil = target.stencilAddress != 0 && resource.baseAddress == target.stencilAddress; - if (!stencil) { - const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; - const bool depthBits = words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u); - if (ResolveTextureFormat(resource.format) != (d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT) && !depthBits) return nullptr; - } else if (BytesPerElement(resource.format) != 1u) { - return nullptr; - } + const auto& base = (*found)->target; + if (resource.width != base.extent.width || resource.height != base.extent.height) return nullptr; + const bool stencil = base.stencilAddress != 0 && resource.baseAddress == base.stencilAddress; + const bool d16 = base.format == VK_FORMAT_D16_UNORM || base.format == VK_FORMAT_D16_UNORM_S8_UINT; + const auto format = ResolveTextureFormat(resource.format); + const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u); + const bool integer = d16 && format == VK_FORMAT_R16_UINT && !depthBits; + if (!stencil && format != (d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT) && !depthBits && !integer) return nullptr; + if (stencil && BytesPerElement(resource.format) != 1u) return nullptr; (*found)->ApplyFastClear(); - return (*found)->Sampled(words, resource, components); + const bool cube = resource.dimension == TextureDimension::kCube; + const bool layered = cube || (resource.dimension == TextureDimension::k2DArray && resource.depthOrLastArray != 0); + if (stencil || (!layered && !integer)) return (*found)->Sampled(words, resource, components); + Require((*found)->hostFormat == base.format, "sampling several slices or an integer view of a depth plane that the device stores in another depth format is not implemented"); + const auto copyFormat = integer ? VK_FORMAT_R16_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT; + const auto layers = cube ? 6u : layered ? resource.depthOrLastArray + 1u : 1u; + if ((format != copyFormat && !depthBits) || resource.width != base.extent.width || resource.height != base.extent.height || resource.baseLevel != 0 || resource.lastLevel != 0 || resource.baseArray != 0 || (cube && resource.depthOrLastArray != 0 && resource.depthOrLastArray != 5) || (resource.dimension != TextureDimension::k2D && resource.dimension != TextureDimension::k2DArray && !cube)) { + char text[320]; + std::snprintf(text, sizeof(text), "AGC graphics: sampling the depth planes of depth surface 0x%llx (%ux%u, vk format %d) as a %ux%u texture of guest format %u (vk %d), dimension %d, %u slices, levels %u-%u, base slice %u is not implemented", static_cast(base.address), base.extent.width, base.extent.height, static_cast(base.format), resource.width, resource.height, resource.format, static_cast(format), static_cast(resource.dimension), layers, resource.baseLevel, resource.lastLevel, resource.baseArray); + throw std::runtime_error(text); + } + std::vector slices; + for (std::uint32_t layer = 0; layer < layers; ++layer) { + const auto address = base.address + static_cast(layer) * DepthSliceBytes(base.extent, d16 ? 2u : 4u); + const auto slice = std::find_if(list.rbegin(), list.rend(), [&](const auto& surface) { return surface->context.device == context.device && surface->target.address == address && surface->target.extent.width == base.extent.width && surface->target.extent.height == base.extent.height && surface->target.format == base.format; }); + if (slice != list.rend()) (*slice)->ApplyFastClear(); + slices.push_back(slice == list.rend() ? VK_NULL_HANDLE : (*slice)->image); + } + const auto imageFormat = integer ? VK_FORMAT_R16_UINT : d16 ? VK_FORMAT_D16_UNORM : VK_FORMAT_D32_SFLOAT; + auto& copy = planeCopies()[{context.device, base.address, base.extent.width, base.extent.height, imageFormat, layers}]; + if (copy == nullptr) { + copy = std::make_unique(context, base.extent, imageFormat, layers); + } + return copy->Refresh(slices, resource, components, cube || resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D); } bool DepthSurfaceHolds(const Context& context, const GuestTextureResource& resource, const Texture* texture) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 63bd7b18ec5..8dc5166c5d3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1717,6 +1717,7 @@ void ShaderResources::noteReusable() { reusable = false; directRegions.clear(); if (guestMemory.HasCopiedWrites() || HoldsLease() || usesBda || (bda != nullptr && LoopGuardEnabled())) return; + if (std::any_of(textures.begin(), textures.end(), [](const std::shared_ptr& texture) { return texture != nullptr && texture->RefreshedPerUse(); })) return; if (TemplateDataRefresh() && std::any_of(allocations.begin(), allocations.end(), [](const Allocation& allocation) { return allocation.buffer != nullptr && !allocation.guest && allocation.size > MaxRefreshBytes; })) return; const auto regions = guestMemory.DirectRegions(); if (!regions.has_value()) return; @@ -3194,7 +3195,7 @@ bool ShaderResources::precollectImages() { } std::shared_ptr ShaderResources::fastTexture(const ImageRecord& record) { - if (record.texture == nullptr) return nullptr; + if (record.texture == nullptr || record.texture->RefreshedPerUse()) return nullptr; struct Outcome { bool profile; std::chrono::steady_clock::time_point start; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index aa3e8d1bac6..b4800d00c50 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -475,7 +475,7 @@ bool Texture::CanDepthCopyFrom(const StorageTexture& source, const GuestTextureR return geometry.imageDepth == 1 && source.ImageDepth() == 1 && source.ImageLayers() >= geometry.imageLayers && source.Descriptor().mipCount >= descriptor.mipCount; } -Texture::Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag) : context(context), storageSource(source), depthCopy(true) { +Texture::Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag) : context(context), storageSource(source), perUseCopy(PerUseCopy::RefreshedOnReuse) { try { Require(source != nullptr && CanDepthCopyFrom(*source, descriptor), "storage image cannot serve a depth copy of the sampled texture"); const auto colorFormat = ResolveTextureFormat(descriptor.format); @@ -535,7 +535,7 @@ Texture::Texture(const Context& context, const std::shared_ptr& } bool Texture::RefreshDepthCopy() { - if (!depthCopy || storageSource == nullptr) return false; + if (perUseCopy != PerUseCopy::RefreshedOnReuse || storageSource == nullptr) return false; if (copiedOnce && storageSource->Version() == copiedVersion) return false; std::uint64_t linearBytes = 0; for (std::uint32_t level = 0; level < copyLevels; ++level) linearBytes += static_cast(std::max(copyWidth >> level, 1u)) * std::max(copyHeight >> level, 1u) * copyElementBytes * copyLayers; @@ -660,17 +660,22 @@ 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) : 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}; + viewInfo.subresourceRange = {aspect, 0, 1, 0, viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : VK_REMAINING_ARRAY_LAYERS}; Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane"); viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount}; + if (viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) return; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.subresourceRange.layerCount = 1; + Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &firstLayerView), "vkCreateImageView depth plane first layer"); + firstLayerRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount}; } Texture::~Texture() { diff --git a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp index 81b3bc25ae3..4cc2428add3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp @@ -20,6 +20,7 @@ constexpr std::uint32_t FormatR32Uint = 20; constexpr std::uint32_t FormatR32Float = 22; constexpr std::uint32_t FormatR11G11B10Float = 36; constexpr std::uint32_t FormatR8G8B8A8Unorm = 56; +constexpr std::uint32_t TileStandard64KB = 0x9; constexpr std::uint32_t TileDepth64KB = 0x18; constexpr std::uint32_t TileRenderTarget64KB = 0x1b; constexpr std::uint32_t Type2D = 9; @@ -181,6 +182,16 @@ void TestD16StencilFallback(const Context& context) { Require(lookup(without, {d16s8Stencil, FormatR8Uint, 256, 256}) != nullptr, "an 8-bit view of the stencil plane of a D16 surface stored as D24 must sample it"); Require(HostDepthBiasConstant(without, VK_FORMAT_D16_UNORM_S8_UINT, 2.0f) == 512.0f, "a 16-bit depth bias must be scaled by 256 for D24 storage"); Require(HostDepthBiasConstant(without, VK_FORMAT_D32_SFLOAT_S8_UINT, 2.0f) == 2.0f, "a D32 depth bias must not be scaled"); + const auto refused = [&](const View& view) { + try { + lookup(without, view); + } catch (const std::runtime_error& error) { + return std::string_view(error.what()).find("a depth plane that the device stores in another depth format is not implemented") != std::string_view::npos; + } + return false; + }; + Require(refused({d16s8, FormatR16Unorm, 256, 256, TileDepth64KB, Type2DArray, 1}), "two slices of a D16 surface stored as D24 must be refused, as the plane copy would read the wider image as 16-bit texels"); + Require(refused({d16s8, FormatR16Uint, 256, 256, TileStandard64KB}), "an R16_UINT view of a D16 surface stored as D24 must be refused"); ClearDepthSurfaces(without.device); static int onlyTag = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp new file mode 100644 index 00000000000..2cdf03f8897 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneSampling.cpp @@ -0,0 +1,353 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "DepthFastClearHarness.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using DepthFastClearHarness::BufferDescriptor; +using DepthFastClearHarness::PixelCode; +using DepthFastClearHarness::VertexCode; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Format16Uint = 11; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t TypeCube = 11; +constexpr std::uint32_t Type2DArray = 13; +constexpr std::uint32_t Swizzle64KbS = 9; +constexpr std::uint32_t Swizzle64KbZX = 24; +constexpr std::uint64_t SliceBytes = 65536; +constexpr std::size_t BlockBytes = 2u << 20u; +constexpr std::size_t OutputOffset = 0; +constexpr std::size_t ArrayOffset = 0x10000; +constexpr std::size_t CubeOffset = 0x40000; +constexpr std::size_t IntegerOffset = 0xa0000; +constexpr std::size_t DepthBitsOffset = 0xb0000; +constexpr std::size_t OneSliceOffset = 0xd0000; +constexpr std::size_t FirstSliceOffset = 0xe0000; +constexpr float DrawnDepth = 0.25f; +constexpr float DrawnD16Depth = 0.2f; +constexpr std::uint32_t DrawnD16Bits = 13107; + +// image_load of row 0, one texel per thread (x = thread id), slice / face from user data, stored as a dword per +// thread into the buffer at s[0:3]. The T# is s[4:11], the slice s12. +constexpr std::array LoadDims{1u, 3u, 5u}; + +std::array LoadCode(std::uint32_t dim) { + return { + 0x7e020280, // v_mov_b32 v1, 0 (y) + 0x7e04020c, // v_mov_b32 v2, s12 (slice or face) + 0xf0001100u | (dim << 3u), // image_load v3, v[0:2], s[4:11] dmask:0x1 unorm, dim 2D / CUBE / 2D_ARRAY + 0x00010300, + 0xbf8c3f70, // s_waitcnt vmcnt(0) + 0x34080082, // v_lshlrev_b32 v4, 2, v0 + 0xe0701000, // buffer_store_dword v3, v4, s[0:3], 0 offen + 0x80000304, + 0xbf810000, // s_endpgm + 0xbf810000, + 0xbf810000, + }; +} + +alignas(256) std::array Load2D = LoadCode(LoadDims[0]); +alignas(256) std::array LoadCube = LoadCode(LoadDims[1]); +alignas(256) std::array LoadArray = LoadCode(LoadDims[2]); + +alignas(256) std::array Pixels{}; + +class GuestBlock { +public: + GuestBlock() { +#ifdef _WIN32 + block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(BlockBytes, 65536)); + if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, BlockBytes, PAGE_READWRITE, BlockBytes); +#else + constexpr std::uintptr_t alignment = 65536; + void* mapped = mmap(nullptr, BlockBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped != MAP_FAILED) { + const auto begin = reinterpret_cast(mapped); + const auto aligned = (begin + alignment - 1) & ~(alignment - 1); + if (aligned != begin) munmap(mapped, aligned - begin); + if (aligned + BlockBytes != begin + BlockBytes + alignment) munmap(reinterpret_cast(aligned + BlockBytes), begin + alignment - aligned); + block = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, BlockBytes); + } +#endif + Require(block != nullptr, "depth plane sampling: cannot allocate the guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + AgcDriver::GuestMemory::CollectWritesUncached(reinterpret_cast(block), BlockBytes); + } + + ~GuestBlock() { + GuestAllocations::Mutation().Remove(block); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(block, BlockBytes); + GuestArena::GuestArenaRelease_nid_postfix(block, BlockBytes); +#else + munmap(block, BlockBytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(block, BlockBytes); +#endif + } + + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + + std::uint8_t* At(std::size_t offset) { return block + offset; } + +private: + std::uint8_t* block = nullptr; +}; + +struct Shape { + std::uint32_t format; + std::uint32_t width; + std::uint32_t height; + std::uint32_t type; + std::uint32_t lastArray; + std::uint32_t swizzle; + std::uint32_t baseArray = 0; +}; + +std::array TextureDescriptor(const void* data, const Shape& shape) { + const auto address = static_cast(reinterpret_cast(data)); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (shape.format << 20u) | (((shape.width - 1u) & 3u) << 30u), + ((shape.width - 1u) >> 2u) | ((shape.height - 1u) << 14u), + 0xfacu | (shape.swizzle << 20u) | (shape.type << 28u), + shape.lastArray | (shape.baseArray << 16u), + 0u, 0u, 0u, + }; +} + +std::uint32_t Bits(float value) { + std::uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +// Draws a full-target triangle at depth `z` into the depth surface at `address`, which makes the driver create the +// surface (the base slice a later plane copy reads from the surface instead of from guest memory). +void DrawDepth(AgcDriver::VulkanDevice& device, std::uint64_t address, VkExtent2D extent, VkFormat format, float z) { + static std::deque>> vertexData; + vertexData.push_back({{-1.0f, -1.0f, z, 1.0f}, {3.0f, -1.0f, z, 1.0f}, {-1.0f, 3.0f, z, 1.0f}}); + const auto& vertices = vertexData.back(); + const auto target = device.Target(); + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(vertices.data(), 16u, static_cast(vertices.size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertexRequest{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {32, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertexRequest.useCache = false; + const auto vertex = ShaderRecompiler::Recompile(vertexRequest); + const auto vertexPush = static_cast(vertex.pushConstants.size()); + ShaderRecompiler::ShaderPixelStageInfo pixelInfo{}; + pixelInfo.wave32 = true; + pixelInfo.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixelInfo.posX = true; + pixelInfo.posY = true; + pixelInfo.targetOutputMode[0] = 9; + pixelInfo.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(4, 0u); + const std::array pixelMemory{{{reinterpret_cast(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}}; + ShaderRecompiler::RecompileRequest fragmentRequest{ + {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, + {32, 0, pixelUserData, std::nullopt, pixelInfo, std::nullopt, pixelMemory}, + target, + PixelPushLayout(vertexPush, target) + }; + fragmentRequest.useCache = false; + const auto pixel = ShaderRecompiler::Recompile(fragmentRequest); + const std::array shaders{{ + {ShaderStage::Vertex, &vertex, 0}, + {ShaderStage::Fragment, &pixel, PixelPushOffset(vertexPush, target)} + }}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Pixels.data()), extent, VK_FORMAT_R8G8B8A8_UNORM, static_cast(extent.width) * extent.height * 4u, 0xe4u}; + state.colors = {state.color}; + state.hasColorTarget = true; + state.depth = AgcDriver::Graphics::DepthTarget{address, 0, extent, format, 1.0f, 0}; + state.depthTest = true; + state.depthWrite = true; + state.depthCompare = VK_COMPARE_OP_ALWAYS; + state.renderExtent = extent; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(extent.height), static_cast(extent.width), -static_cast(extent.height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, extent}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(vertices.size()), 0, 1, 0, false}; + device.Draw(state, draw, shaders); + device.WaitIdle(); +} + +// Loads row 0 of `slice` of the texture `shape` describes at `texels` into `output` (one dword per thread). With +// `shaderImage2D` the shader declares a 2D image whatever the T#'s type is. +void Load(AgcDriver::VulkanDevice& device, std::uint8_t* texels, const Shape& shape, std::uint32_t slice, std::span output, bool shaderImage2D = false) { + const auto& code = shaderImage2D ? Load2D : shape.type == TypeCube ? LoadCube : shape.type == Type2DArray ? LoadArray : Load2D; + std::vector userData(16, 0u); + const auto out = std::array{static_cast(reinterpret_cast(output.data())), static_cast((reinterpret_cast(output.data()) >> 32u) & 0xffffu), static_cast(output.size_bytes()), 0x31016facu}; + const auto texture = TextureDescriptor(texels, shape); + std::copy(out.begin(), out.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + userData[12] = slice; + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + std::fill(output.begin(), output.end(), 0xdeadbeefu); + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Expect(std::span output, std::uint32_t expected, const std::string& what) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + char text[160]; + std::snprintf(text, sizeof(text), "%s: texel %u read 0x%08x, expected 0x%08x", what.c_str(), tid, output[tid], expected); + Require(output[tid] == expected, text); + } +} + +void FillSlice(std::uint8_t* slice, std::uint64_t bytes, std::uint32_t value, std::uint32_t elementBytes) { + for (std::uint64_t offset = 0; offset < bytes; offset += elementBytes) std::memcpy(slice + offset, &value, elementBytes); +} + +// A 3-slice D32 array: slice 0 from the live depth surface, slices 1 and 2 from guest memory (detiled). +void ArrayCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + auto* base = block.At(ArrayOffset); + DrawDepth(device, reinterpret_cast(base), {128, 128}, VK_FORMAT_D32_SFLOAT, DrawnDepth); + FillSlice(base + SliceBytes, SliceBytes, Bits(0.5f), 4u); + FillSlice(base + 2u * SliceBytes, SliceBytes, Bits(0.75f), 4u); + const Shape shape{Format32Float, 128, 128, Type2DArray, 2, Swizzle64KbZX}; + const std::array expected{DrawnDepth, 0.5f, 0.75f}; + for (std::uint32_t slice = 0; slice < 3u; ++slice) { + Load(device, base, shape, slice, output); + Expect(output, Bits(expected[slice]), "D32 2D array slice " + std::to_string(slice)); + } +} + +// A cube over six square D32 slices: face 0 from the live surface, faces 1-5 from guest memory. +void CubeCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + auto* base = block.At(CubeOffset); + DrawDepth(device, reinterpret_cast(base), {128, 128}, VK_FORMAT_D32_SFLOAT, DrawnDepth); + for (std::uint32_t face = 1; face < 6u; ++face) FillSlice(base + face * SliceBytes, SliceBytes, Bits(static_cast(face) / 8.0f), 4u); + const Shape shape{Format32Float, 128, 128, TypeCube, 0, Swizzle64KbZX}; + for (std::uint32_t face = 0; face < 6u; ++face) { + Load(device, base, shape, face, output); + Expect(output, Bits(face == 0 ? DrawnDepth : static_cast(face) / 8.0f), "D32 cube face " + std::to_string(face)); + } +} + +// A D16 plane read through an R16_UINT view (a non-depth swizzle, so not flagged as depth bits): the raw depth value. +void IntegerCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + auto* base = block.At(IntegerOffset); + DrawDepth(device, reinterpret_cast(base), {256, 128}, VK_FORMAT_D16_UNORM, DrawnD16Depth); + Load(device, base, {Format16Uint, 256, 128, Type2D, 0, Swizzle64KbS}, 0, output); + Expect(output, DrawnD16Bits, "D16 plane as R16_UINT"); +} + +// A D16 2D array whose R16_UINT T# has a depth swizzle (flagged as depth bits): both slices' raw values. +void DepthBitsArrayCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + auto* base = block.At(DepthBitsOffset); + DrawDepth(device, reinterpret_cast(base), {256, 128}, VK_FORMAT_D16_UNORM, DrawnD16Depth); + FillSlice(base + SliceBytes, SliceBytes, 0x1234u, 2u); + const Shape shape{Format16Uint, 256, 128, Type2DArray, 1, Swizzle64KbZX}; + Load(device, base, shape, 0, output); + Expect(output, DrawnD16Bits, "D16 depth-bits array slice 0"); + Load(device, base, shape, 1, output); + Expect(output, 0x1234u, "D16 depth-bits array slice 1"); +} + +// A 2D shader image over a 2D-array T# reads the array's first slice: a one-slice array, which samples the depth +// image itself, and a 3-slice array, which samples the plane copy. +void FirstSliceCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + auto* one = block.At(OneSliceOffset); + DrawDepth(device, reinterpret_cast(one), {128, 128}, VK_FORMAT_D32_SFLOAT, DrawnDepth); + Load(device, one, {Format32Float, 128, 128, Type2DArray, 0, Swizzle64KbZX}, 0, output, true); + Expect(output, Bits(DrawnDepth), "one-slice D32 2D array through a 2D shader image"); + auto* base = block.At(FirstSliceOffset); + DrawDepth(device, reinterpret_cast(base), {128, 128}, VK_FORMAT_D32_SFLOAT, DrawnDepth); + FillSlice(base + SliceBytes, SliceBytes, Bits(0.5f), 4u); + FillSlice(base + 2u * SliceBytes, SliceBytes, Bits(0.75f), 4u); + Load(device, base, {Format32Float, 128, 128, Type2DArray, 2, Swizzle64KbZX}, 0, output, true); + Expect(output, Bits(DrawnDepth), "3-slice D32 2D array through a 2D shader image"); +} + +// A plane array from a later base slice is still refused (its slice offsets are not modeled here). +void RefusalCase(AgcDriver::VulkanDevice& device, GuestBlock& block, std::span output) { + bool refused = false; + try { + Shape shape{Format32Float, 128, 128, Type2DArray, 2, Swizzle64KbZX}; + shape.baseArray = 1; + Load(device, block.At(ArrayOffset), shape, 0, output); + } catch (const std::runtime_error& error) { + refused = std::string(error.what()).find("sampling the depth planes of depth surface") != std::string::npos; + if (!refused) throw; + } + Require(refused, "a depth plane array from base slice 1 was not refused"); +} + +} + +int main() { + try { + GuestBlock block; + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const std::span output(reinterpret_cast(block.At(OutputOffset)), Threads); + ArrayCase(*device, block, output); + CubeCase(*device, block, output); + IntegerCase(*device, block, output); + DepthBitsArrayCase(*device, block, output); + FirstSliceCase(*device, block, output); + RefusalCase(*device, block, output); + std::puts("depth plane sampling tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 7ae815525be..5227da96e6b 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -104,6 +104,7 @@ flowchart LR - A compute shader whose `s_swappc_b64` calls a user-data pointer cannot be linked at registration, because the callee's address is only known at dispatch. `InvocationFor` in [`ShaderRegistry.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) reads each callee from guest memory, appends one copy per call after the caller (`LinkUserDataCalls`) and prepares that linked code, so a different callee gets its own prepared entry. - A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking, `VK_EXT_extended_dynamic_state` and `VK_KHR_dynamic_rendering`, draws render with `vkCmdBeginRenderingKHR` instead of render passes and the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend, vertex layout or target format builds only that part (the shader libraries name no attachment format), and cull mode, front face, depth and stencil state are dynamic, so changing them only links. The fast link is what the draw waits for; a background thread then links the same libraries with link-time optimization and the pipeline binds that one from the next draw on. Push constants then use a 256-byte block with a 128-byte slot per side: the pre-rasterization stages stack from byte 0, the pixel shader always starts at byte 128 (`StagePushOffset`), so its library does not depend on how much push data its vertex shader uses. Pipelines with per-face depth bias, a separate alpha blend into an 8_8 target with the alternate component order (both draw twice), rect lists, geometry shaders or a stage without a variant id are created whole, still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines. - A draw whose `PA_SC_AA_CONFIG` selects 2, 4 or 8 samples renders into host-only multisampled images. [`MultisampleTarget.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) keeps one image per device, surface address, format, extent and sample count (`MultisampleTargetView`); the pipeline, render pass and library keys carry `State::samples`, and a multisampled depth target is a multisampled depth surface. The guest sample planes and FMASK are never read or written, so the registry also guards the target's guest range: a CPU read (`GuestMemory::SetReadGuard`), a DMA copy, and a sampled or storage image read of it throw. A `CB_COLOR_CONTROL` resolve mode over a multisampled CB0 runs as a render pass whose resolve attachment is CB1's resident image (`ResolveMultisampleTarget`). A zero fill of the target's CMASK (`NoteColorMetadataFill`, from fill kernels and CP DMA fills) clears the image at its next use. +- A texture over a depth surface samples the depth image itself when it views one 2D slice of the plane's own format (`DepthSurfaceTexture` in [`DepthSurface.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp)). A view of several slices (a 2D array or a cube) or an integer view of a D16 plane is served by a `DepthPlaneCopy`, an image the slices are copied into at every use: from the depth surface bound at each slice's address, else from guest memory. Such a texture is never kept in the resource templates (`RefreshedPerUse`). ### Untracked storage image comparisons diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 5f637a65e5a..4b17db2855d 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -27,6 +27,7 @@ ### Functional - [Reused depth storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp): a storage view with another extent or a format that cannot hold the cached depth plane uses guest memory. Depth rendering is not written back to those guest bytes; this admission rule does not prove allocation reuse or preserve a depth writer through an incompatible view. Matching depth/stencil storage access still throws. +- [Depth plane copies](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver): a depth plane sampled as a 2D array or a cube, or a D16 plane read as `R16_UINT`, is copied into an image of its own (`DepthPlaneCopy`) at every use, without a cache: each draw or dispatch that binds it copies every slice again, from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A slice's address is the base plus a whole 64 KiB-block depth slice per layer (`DepthSliceBytes`), and the cube face order is the array order; both are inferred from the titles' use, not measured on hardware. Planes with mips, a base slice other than 0, a cube with other than 1 or 6 slices, and other views of the planes still throw, and so do these copies of a 16-bit plane with stencil that the device stores as D24 or D32 (16-bit depth with stencil, below). - [SW_64KB_D_X color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ColorTargetLayout.cpp) (libSceAgcDriver) use the existing texture block/mip geometry and equations from [AMD addrlib GFX10 non-RB+](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp) for 16 pipes and 256-byte interleave. The address-reference tests use legal pipe/bank XOR bits for that configuration. Raw column bits outside the source configuration's pipe/bank mask 0xcf00 throw; their console meaning is unresolved. Console address equations and render results have not been measured. - [Per-face depth bias](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): Vulkan has one depth bias per draw, so a draw that rasterizes both faces with different front and back bias (PA_SU_SC_MODE_CNTL, PA_SU_POLY_OFFSET_FRONT/BACK_*) is recorded twice, front faces with the back faces culled and then back faces with the front faces culled. The vertex stages run twice, so a vertex shader that writes memory writes twice, and all front faces land before all back faces instead of in primitive order, which differs only for blending or stencil updates that depend on the order. - [Partial rect-list primitives](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver): a rect-list draw whose vertex or index count is not a multiple of 3 draws its complete rectangles and leaves out the 1 or 2 vertices after them, because rect lists are drawn as Vulkan patch lists of 3 control points, which generate [⌊vertexCount/3⌋ patches](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#drawing-patch-lists). What the console draws for the incomplete rectangle is not measured; Palworld (PPSA23126) draws 4 vertices × 9 instances as a rect list when its character creation opens.