From 50e5f088c97a05b1e6947584b3d257a9aa2ae6cc Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Fri, 9 Oct 2026 22:45:00 +1300 Subject: [PATCH] perf(agc): skip the depth plane copy when no slice changed Sampling a depth array, cube or R16_UINT view of depth surfaces went through DepthPlaneCopy::Refresh on every use. Each use copied every slice a depth surface holds into the staging buffer and the whole staging buffer into the copy image, with three ALL_COMMANDS barriers, although nothing had written the slices since the previous use. Each depth surface now carries a write serial, bumped when a draw binds it as its depth target and when a pending fast clear is applied. The copy keeps the image and serial of every slice it copied. A use copies only the slices whose surface or serial changed and the guest layers that changed (as before), and records nothing when none did. A slice whose surface is the depth target of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. Test: agc_driver_depth_plane_guest_layers counts the recorded slice copies and copy fills; a second use with nothing written records none (it records one of each without this change), a draw to a slice's surface or a CPU write to a guest layer copies only that layer. --- .../Graphics/src/DepthSurface.cpp | 144 +++++++++++------- .../tests/execution/DepthPlaneGuestLayers.cpp | 55 ++++++- docs/dev/debt/libSceAgcDriver.md | 2 +- 3 files changed, 144 insertions(+), 57 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 3517e33f2..1c25c2ea1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -23,6 +23,8 @@ namespace AgcDriver::Graphics { namespace { +std::uint64_t depthWrites = 0; + Recorder* RecorderFor(const Context& context) { auto* recorder = Recorder::Active(); return recorder != nullptr && recorder->Device() == context.device ? recorder : nullptr; @@ -98,6 +100,7 @@ public: const auto aspects = pendingClear & (VK_IMAGE_ASPECT_DEPTH_BIT | (target.stencilAddress != 0 ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u)); pendingClear = 0; if (aspects == 0) return; + written = ++depthWrites; auto* recorder = RecorderFor(context); std::unique_ptr batch; if (recorder == nullptr) batch = std::make_unique(context); @@ -144,6 +147,8 @@ public: return std::any_of(textures.begin(), textures.end(), [&](const auto& entry) { return entry.second.get() == texture; }); } + std::uint64_t written = ++depthWrites; + VkImageAspectFlags pendingClear = 0; float clearDepth = 0.0f; std::uint8_t clearStencil = 0; @@ -168,9 +173,14 @@ private: } }; +struct DepthPlaneSlice { + VkImage image = VK_NULL_HANDLE; + std::uint64_t written = 0; +}; + class DepthPlaneCopy { public: - DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers), guestLayers(layers) { + DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers), guestLayers(layers), surfaceLayers(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; @@ -203,18 +213,24 @@ public: DepthPlaneCopy(const DepthPlaneCopy&) = delete; DepthPlaneCopy& operator=(const DepthPlaneCopy&) = delete; - std::shared_ptr Refresh(std::span slices, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType) { + 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; std::vector uploadLayers; + std::vector copiedLayers; for (std::uint32_t layer = 0; layer < layers; ++layer) { auto& held = guestLayers[layer]; - if (slices[layer] != VK_NULL_HANDLE) { + auto& surface = surfaceLayers[layer]; + if (slices[layer].image != VK_NULL_HANDLE) { held = {}; + if (slices[layer].written != 0 && surface.image == slices[layer].image && surface.written == slices[layer].written) continue; + surface = slices[layer]; + copiedLayers.push_back(layer); continue; } + surface = {}; const GuestLayer current{resource.baseAddress + geometry.GuestLayerOffset(layer), geometry.layerBytes, resource.tileMode, geometry.thick, GuestMemory::CollectWrites(resource.baseAddress + geometry.GuestLayerOffset(layer), static_cast(geometry.layerBytes))}; if (held.generation != 0 && held.address == current.address && held.bytes == current.bytes && held.tileMode == current.tileMode && held.thick == current.thick && !Recorder::SnapshotWriteOverlaps(current.address, static_cast(current.bytes)) && GuestMemory::UnchangedSince(current.address, static_cast(current.bytes), held.generation)) { held.generation = current.generation; @@ -226,56 +242,7 @@ public: uploadLayers.push_back(layer); held = current; } - 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); - for (std::size_t upload = 0; upload < uploads.size(); ++upload) { - const auto layer = uploadLayers[upload]; - context.detiler->Dispatch(commands, resource.tileMode, format == VK_FORMAT_D32_SFLOAT ? 4u : 2u, uploads[upload]->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); + if (!copiedLayers.empty() || !uploads.empty()) record(slices, resource, geometry, copiedLayers, uploads, uploadLayers); 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) { @@ -304,6 +271,66 @@ private: std::unique_ptr staging; std::map, std::shared_ptr> textures; std::vector guestLayers; + std::vector surfaceLayers; + bool filled = false; + + void record(std::span slices, const GuestTextureResource& resource, const SurfaceGeometry& geometry, std::span copiedLayers, std::span> uploads, std::span uploadLayers) { + 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; + const auto region = [&](std::uint32_t layer, VkImageAspectFlags aspectMask, std::uint32_t imageLayer) { + VkBufferImageCopy copy{}; + copy.bufferOffset = sliceBytes() * layer; + copy.imageSubresource = {aspectMask, 0, imageLayer, 1}; + copy.imageExtent = {extent.width, extent.height, 1}; + return copy; + }; + for (const auto layer : copiedLayers) { + const auto copy = region(layer, VK_IMAGE_ASPECT_DEPTH_BIT, 0); + context.Function("vkCmdCopyImageToBuffer")(commands, slices[layer].image, VK_IMAGE_LAYOUT_GENERAL, staging->Handle(), 1, ©); + regions.push_back(region(layer, aspect(), layer)); + } + 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); + for (std::size_t upload = 0; upload < uploads.size(); ++upload) { + const auto layer = uploadLayers[upload]; + context.detiler->Dispatch(commands, resource.tileMode, format == VK_FORMAT_D32_SFLOAT ? 4u : 2u, uploads[upload]->Handle(), 0, staging->Handle(), geometry.LinearLayerOffset(layer), geometry.mips.front(), false, layer, geometry.thick); + regions.push_back(region(layer, aspect(), layer)); + } + if (recorder != nullptr) { + for (const 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 = filled ? VK_IMAGE_LAYOUT_GENERAL : 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}; + barrier(commands, 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")(commands, staging->Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(regions.size()), 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(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); + filled = true; + if (batch) batch->SubmitAndWait(); + else Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); + } VkDeviceSize sliceBytes() const { return static_cast(extent.width) * extent.height * (format == VK_FORMAT_D32_SFLOAT ? 4u : 2u); @@ -347,6 +374,8 @@ std::set& clearedHtiles() { return *cleared; } +const DepthSurface* drawnDepth = nullptr; + } std::uint64_t DepthSliceBytes(VkExtent2D extent, std::uint32_t bytesPerTexel) { @@ -402,11 +431,14 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target) surface->clearStencil = target.clearStencil; if (cleared) surface->pendingClear |= VK_IMAGE_ASPECT_DEPTH_BIT; surface->ApplyFastClear(); + surface->written = ++depthWrites; + drawnDepth = surface.get(); return surface->view; } surfaces().push_back(std::make_unique(context, target)); surfaces().back()->clearDepth = target.clearDepth; surfaces().back()->clearStencil = target.clearStencil; + drawnDepth = surfaces().back().get(); return surfaces().back()->view; } @@ -456,6 +488,7 @@ void NoteHtileDepthClear(std::uint64_t htileAddress) { void ClearDepthSurfaces(VkDevice device) { std::lock_guard lock(surfacesMutex()); + drawnDepth = nullptr; 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; }); } @@ -496,12 +529,13 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::span(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; + 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); + if (slice == list.rend()) slices.push_back({}); + else slices.push_back({(*slice)->image, slice->get() == drawnDepth ? 0u : (*slice)->written}); } 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}]; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp index a03da4ce0..a04e690b2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthPlaneGuestLayers.cpp @@ -39,6 +39,36 @@ constexpr std::size_t LayerBytes = std::size_t{Side} * Side * 4u; constexpr std::size_t BlockBytes = 2u * LayerBytes; constexpr int Skipped = 77; +PFN_vkGetDeviceProcAddr realDeviceProc = nullptr; +PFN_vkCmdCopyImageToBuffer realCopyImageToBuffer = nullptr; +PFN_vkCmdCopyBufferToImage realCopyBufferToImage = nullptr; +std::atomic imageToBufferCopies{0}; +std::atomic bufferToImageCopies{0}; + +VKAPI_ATTR void VKAPI_CALL CountImageToBuffer(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkBuffer buffer, std::uint32_t count, const VkBufferImageCopy* regions) { + imageToBufferCopies.fetch_add(1); + realCopyImageToBuffer(commands, image, layout, buffer, count, regions); +} + +VKAPI_ATTR void VKAPI_CALL CountBufferToImage(VkCommandBuffer commands, VkBuffer buffer, VkImage image, VkImageLayout layout, std::uint32_t count, const VkBufferImageCopy* regions) { + bufferToImageCopies.fetch_add(1); + realCopyBufferToImage(commands, buffer, image, layout, count, regions); +} + +VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL CountingDeviceProc(VkDevice device, const char* name) { + const auto function = realDeviceProc(device, name); + if (function == nullptr) return nullptr; + if (std::strcmp(name, "vkCmdCopyImageToBuffer") == 0) { + realCopyImageToBuffer = reinterpret_cast(function); + return reinterpret_cast(&CountImageToBuffer); + } + if (std::strcmp(name, "vkCmdCopyBufferToImage") == 0) { + realCopyBufferToImage = reinterpret_cast(function); + return reinterpret_cast(&CountBufferToImage); + } + return function; +} + class Device { public: Device() { @@ -93,7 +123,8 @@ public: device.ppEnabledExtensionNames = extensionsEnabled.data(); device.pEnabledFeatures = &enabled; Check(function("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice"); - context.deviceProc = function("vkGetDeviceProcAddr"); + realDeviceProc = function("vkGetDeviceProcAddr"); + context.deviceProc = &CountingDeviceProc; function("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory); VkPhysicalDeviceProperties properties{}; function("vkGetPhysicalDeviceProperties")(context.physical, &properties); @@ -235,6 +266,28 @@ void Run(const Context& base, const WatchedBlock& block) { Require(sample("after the re-read") == 0, "sampling the depth planes after the re-read of the written layer read it once more"); AgcDriver::GuestMemory::MarkWritten(address + LayerBytes, 4096); Require(sample("after a driver store") != 0, "sampling the depth planes after a driver store over the guest layer did not read it again"); + const DepthTarget slice{address, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}; + const DepthTarget other{address + 2 * BlockBytes, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0}; + const auto copies = [&](const char* when, std::uint32_t fromSlices, std::uint32_t intoCopy) { + imageToBufferCopies = 0; + bufferToImageCopies = 0; + sample(when); + if (imageToBufferCopies.load() != fromSlices || bufferToImageCopies.load() != intoCopy) { + throw std::runtime_error(std::string("sampling the depth planes ") + when + " recorded " + std::to_string(imageToBufferCopies.load()) + " depth surface copies and " + std::to_string(bufferToImageCopies.load()) + " plane copy fills, expected " + std::to_string(fromSlices) + " and " + std::to_string(intoCopy)); + } + }; + DepthSurfaceView(context, other); + copies("after a draw to another depth surface", 1, 1); + copies("with nothing written since", 0, 0); + DepthSurfaceView(context, slice); + copies("while a draw to its depth surface is being recorded", 1, 1); + copies("again while that draw is the last depth draw", 1, 1); + DepthSurfaceView(context, other); + copies("after that draw", 1, 1); + copies("with nothing written since that draw", 0, 0); + FillLayer(block.Data() + LayerBytes, 0.5f); + copies("after a CPU write to the guest layer only", 0, 1); + copies("with nothing written since the CPU write", 0, 0); } catch (...) { AgcDriver::GuestMemory::SetFlushHook(nullptr); throw; diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 8c8b96a70..fd4323226 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -27,7 +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). +- [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`), kept per base address, extent, format and slice count; a slice comes from the live depth surface when one is bound at the slice's address and from guest memory (detiled) otherwise. A draw or dispatch that binds the plane copies again only the slices that changed and records nothing when none did: a surface slice when its image or write serial differs from the one copied (the serial is bumped when the surface is created, when a draw binds it as its depth target and when a pending fast clear is applied), a guest-memory slice when its bytes were written since they were last read (`GuestMemory::UnchangedSince`), a snapshot write overlaps them, or its address, size or tile mode changed. The surface of the latest depth draw is copied at every use until another depth target is bound, since that draw may still be recorded after the copy. 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.