From 8c84dfcd35f4f224ed16df6f1f5b112fc3d86a74 Mon Sep 17 00:00:00 2001 From: Gjon Gojcevic Date: Sat, 17 Oct 2026 14:12:37 -0400 Subject: [PATCH] feat(agc): render layered draws into 2D array color views A draw whose vertex shader exports the render target array index into a color view of several slices of a 2D array target was refused. The view now renders layers lastSlice - slice + 1 through a 2D array attachment view of the array's resident image starting at its first slice, and the draw marks every rendered slice dirty. A view of several slices of a surface that is not a 2D array is still refused, as is a layered draw without the array's resident image. AI-assisted: yes --- .../libSceAgcDriver/Graphics/include/Texture.hpp | 2 +- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 15 ++++++++------- .../prx/libSceAgcDriver/Graphics/src/State.cpp | 5 ++++- .../prx/libSceAgcDriver/Graphics/src/Texture.cpp | 11 ++++++----- core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 5 +++-- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index b271f40672..38444c091e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -197,7 +197,7 @@ public: void WriteBack(); // Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead). void MarkDirty(); - void MarkSliceDirty(std::uint32_t slice); + void MarkSliceDirty(std::uint32_t slice, std::uint32_t count = 1); void Flush(); bool Dirty() const; // Stores every pending image overlapping the range, except `except`; returns whether any was. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 1fb537a91b..44af2caae2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1648,7 +1648,7 @@ struct Kept { // The completion side of a recorded draw: the kept objects, the record check, the lease outcome, // the GPU write notes, the copied-buffer write-back (listed in DrawCopiedWriters or run as a // completion action) and the targets marked dirty. -void keepRecordedDraw(Recorder& recorder, const std::shared_ptr& resources, std::shared_ptr pipeline, std::shared_ptr framebuffer, DrawInputs& inputs, std::vector> targets, std::vector slices, std::unique_ptr scratch, std::function checkRecords, bool listed, bool completion, const DrawOutcome& outcome) { +void keepRecordedDraw(Recorder& recorder, const std::shared_ptr& resources, std::shared_ptr pipeline, std::shared_ptr framebuffer, DrawInputs& inputs, std::vector> targets, std::vector> slices, std::unique_ptr scratch, std::function checkRecords, bool listed, bool completion, const DrawOutcome& outcome) { auto kept = std::make_shared(); kept->resources = resources; kept->pipeline = std::move(pipeline); @@ -1689,7 +1689,7 @@ void keepRecordedDraw(Recorder& recorder, const std::shared_ptr } for (std::size_t index = 0; index < residents.size(); ++index) { if (residents[index] == nullptr) continue; - if (index < slices.size()) residents[index]->MarkSliceDirty(slices[index]); + if (index < slices.size()) residents[index]->MarkSliceDirty(slices[index].first, slices[index].second); else residents[index]->MarkDirty(); } for (const auto& resident : residents) { @@ -1709,7 +1709,7 @@ struct RecordedDraw { std::shared_ptr framebuffer; std::span targetViews; std::vector> targets; - std::vector targetSlices; + std::vector> targetSlices; std::vector> multisampleTargets; std::vector> proxies; std::vector multisampleImages; @@ -2217,6 +2217,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& materializeCmaskClear(context, color, nullptr); Require(!color.mipTail || binding.gpuTiling, "rendering into a packed mip tail requires the GPU tiled fallback"); Require(color.depth == 1, "rendering into a 3D color target needs the resident image of its surface"); + Require(color.layers == 1, "layered rendering into a 2D array color target needs the resident image of its array"); Require(color.arraySlice == 0 || !ColorTileModeIsXor(color.tileMode), "rendering into a slice of a 2D array color target with an XOR tile mode needs the resident image of its array"); if (binding.gpuTiling) { binding.mip = color.mipTail ? ComputeColorTargetMipLayout(color.tileMode, color.elementBytes, color.surfaceExtent.width, color.surfaceExtent.height, color.mipCount, color.mip) : ColorTargetMip(color, colorLayout); @@ -2451,7 +2452,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& record.framebuffer = framebuffer; record.targetViews = targetViews; record.targets = owners; - for (const auto& binding : targets) record.targetSlices.push_back(binding.color.arraySlice); + for (const auto& binding : targets) record.targetSlices.emplace_back(binding.color.arraySlice, binding.color.arrayLastSlice != 0 ? binding.color.layers : 1u); for (const auto& binding : targets) { if (binding.proxied) record.proxies.push_back(binding.resident); if (binding.multisampled) { @@ -2665,7 +2666,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& APS5_LOG_CHARS_OUT_DEBUG("SubmitAndWait"); timer.phase(PhaseRecord); if (recorded) { - keepRecordedDraw(*recorder, resources, pipeline, framebuffer, inputs, owners, [&] { std::vector slices; for (const auto& binding : targets) slices.push_back(binding.color.arraySlice); return slices; }(), std::move(scratch), std::move(checkRecords), listed, completion, outcome); + keepRecordedDraw(*recorder, resources, pipeline, framebuffer, inputs, owners, [&] { std::vector> slices; for (const auto& binding : targets) slices.emplace_back(binding.color.arraySlice, binding.color.arrayLastSlice != 0 ? binding.color.layers : 1u); return slices; }(), std::move(scratch), std::move(checkRecords), listed, completion, outcome); timer.phase(PhaseKeep); if (outcome.waited) { // The wait the dispatch path makes for such work (source 3, "address-based"): the @@ -2711,7 +2712,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& if (skipTargetWrite) break; if (binding.resident != nullptr) { // The results stay on the GPU until something reads the target's memory. - binding.resident->MarkSliceDirty(binding.color.arraySlice); + binding.resident->MarkSliceDirty(binding.color.arraySlice, binding.color.arrayLastSlice != 0 ? binding.color.layers : 1u); continue; } if (binding.gpuTiling) GuestMemory::WriteChanged(binding.color.address, binding.tiled->Bytes(), binding.original); @@ -2854,7 +2855,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con record.framebuffer = std::move(framebuffer); record.targetViews = recipe.targetViews; record.targets = std::move(targets); - for (const auto& color : state.colors) record.targetSlices.push_back(color.arraySlice); + for (const auto& color : state.colors) record.targetSlices.emplace_back(color.arraySlice, color.arrayLastSlice != 0 ? color.layers : 1u); record.pushBytes = &recipe.pushBytes; record.pushStages = recipe.pushStages; recordDraw(context, state, draw, shaders, inputs, record, outcome, timer, ownWaitedMs); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index eba588931f..0165062814 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -1659,7 +1659,10 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo } if (!volume && lastSlice != slice) { const auto exports = find(cx, 0x207); - Require(exports == cx.end() || (exports->second & (1u << 18u)) == 0, "layered draws into a color view of several array slices are unsupported"); + if (exports != cx.end() && (exports->second & (1u << 18u)) != 0) { + Require(color.arrayLastSlice != 0 && lastSlice > slice, "layered draws into a color view of several slices of a surface that is not a 2D array are unsupported"); + color.layers = lastSlice + 1u - slice; + } } color.extent = {((attrib2 >> 14u) & 0x3fffu) + 1u, (attrib2 & 0x3fffu) + 1u}; color.elementBytes = decoded.elementBytes; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 209907de3c..a80765d73e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1820,8 +1820,9 @@ VkImageView StorageTexture::AttachmentView(VkFormat format, std::uint32_t mip, s Require(attachable, "storage image cannot be a color attachment"); Require(mip < descriptor.mipCount, "attachment mip exceeds the storage image"); const bool volume = descriptor.dimension == TextureDimension::k3D; - Require((depthSlice == 0 && layers == 1) || (volume && mip == 0 && layers != 0 && depthSlice + layers - 1u <= descriptor.depthOrLastArray), "attachment slice is outside the storage image"); - Require(layer == 0 || (!volume && descriptor.dimension == TextureDimension::k2DArray && descriptor.baseArray + layer <= descriptor.depthOrLastArray), "attachment layer is outside the storage image"); + const bool array = !volume && descriptor.dimension == TextureDimension::k2DArray; + Require((depthSlice == 0 && layers == 1) || (volume && mip == 0 && layers != 0 && depthSlice + layers - 1u <= descriptor.depthOrLastArray) || (array && depthSlice == 0 && layers != 0), "attachment slice is outside the storage image"); + Require((layer == 0 && layers == 1) || volume || (array && descriptor.baseArray + layer + layers - 1u <= descriptor.depthOrLastArray), "attachment layer is outside the storage image"); const auto found = attachmentViews.find({format, mip, depthSlice, layers, layer}); if (found != attachmentViews.end()) return found->second; VkImageViewUsageCreateInfo usage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; @@ -3513,14 +3514,14 @@ void StorageTexture::MarkDirty() { markLayersPending(0, trackedLayers); } -void StorageTexture::MarkSliceDirty(std::uint32_t slice) { - if (geometry.thick || geometry.imageLayers <= 1 || slice >= geometry.imageLayers || geometry.layerBytes == 0 || trackedLayerBytes == 0 || descriptor.dccAddress != 0) { +void StorageTexture::MarkSliceDirty(std::uint32_t slice, std::uint32_t count) { + if (geometry.thick || geometry.imageLayers <= 1 || count == 0 || slice >= geometry.imageLayers || count > geometry.imageLayers - slice || geometry.layerBytes == 0 || trackedLayerBytes == 0 || descriptor.dccAddress != 0) { MarkDirty(); return; } const auto begin = geometry.GuestLayerOffset(slice); const auto first = static_cast(begin / trackedLayerBytes); - const auto last = static_cast(std::min((begin + geometry.layerBytes + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers)); + const auto last = static_cast(std::min((begin + geometry.layerBytes * count + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers)); if (first >= last) { MarkDirty(); return; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index aa15f4523f..9d0634db34 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -2341,7 +2341,7 @@ void DepthStencilTests() { const auto firstSlice = AgcDriver::Graphics::DecodeState(queue); Require(firstSlice.color.address == reinterpret_cast(colorMemory.data()), "a color view of several slices did not retain its first slice"); queue.context[0x207] = 1u << 18u; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "layered draws into a color view of several array slices"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "layered draws into a color view of several slices of a surface that is not a 2D array"); queue.context[0x207] = 0; const auto sliced = reinterpret_cast(sliceMemory.data()); queue.context[0x318] = static_cast(sliced >> 8u); @@ -2356,7 +2356,8 @@ void DepthStencilTests() { const auto range = AgcDriver::Graphics::DecodeState(queue); Require(range.color.address == sliced && range.color.bytes == 1024u, "a color view of slices 0 to 1 of a 2D array target did not render into its first slice"); queue.context[0x207] = 1u << 18u; - expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "layered draws into a color view of several array slices"); + const auto layeredArray = AgcDriver::Graphics::DecodeState(queue); + Require(layeredArray.color.address == sliced && layeredArray.color.arraySlice == 0u && layeredArray.color.layers == 2u && layeredArray.renderLayers == 2u && layeredArray.layerExports != 0u, "a layeredArray draw into slices 0 to 1 of a 2D array target did not render two layers"); queue.context[0x207] = 0; queue.context[0x31b] = 2u | (2u << 13u); expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "beyond the array surface");