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
This commit is contained in:
Gjon Gojcevic authored and Adrià Franch committed 2026-10-19 15:52:35 +02:00
1 parent 95d901b937
commit 8c84dfcd35
5 files changed
+22 -16

No files matched your search

@@ -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.
@@ -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<ShaderResources>& resources, std::shared_ptr<Pipeline> pipeline, std::shared_ptr<Framebuffer> framebuffer, DrawInputs& inputs, std::vector<std::shared_ptr<StorageTexture>> targets, std::vector<std::uint32_t> slices, std::unique_ptr<DeviceBuffer> scratch, std::function<void()> checkRecords, bool listed, bool completion, const DrawOutcome& outcome) {
void keepRecordedDraw(Recorder& recorder, const std::shared_ptr<ShaderResources>& resources, std::shared_ptr<Pipeline> pipeline, std::shared_ptr<Framebuffer> framebuffer, DrawInputs& inputs, std::vector<std::shared_ptr<StorageTexture>> targets, std::vector<std::pair<std::uint32_t, std::uint32_t>> slices, std::unique_ptr<DeviceBuffer> scratch, std::function<void()> checkRecords, bool listed, bool completion, const DrawOutcome& outcome) {
auto kept = std::make_shared<Kept>();
kept->resources = resources;
kept->pipeline = std::move(pipeline);
@@ -1689,7 +1689,7 @@ void keepRecordedDraw(Recorder& recorder, const std::shared_ptr<ShaderResources>
}
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> framebuffer;
std::span<const VkImageView> targetViews;
std::vector<std::shared_ptr<StorageTexture>> targets;
std::vector<std::uint32_t> targetSlices;
std::vector<std::pair<std::uint32_t, std::uint32_t>> targetSlices;
std::vector<std::shared_ptr<void>> multisampleTargets;
std::vector<std::shared_ptr<StorageTexture>> proxies;
std::vector<VkImage> 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<std::uint32_t> 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<std::pair<std::uint32_t, std::uint32_t>> 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);
@@ -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;
@@ -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<std::uint32_t>(begin / trackedLayerBytes);
const auto last = static_cast<std::uint32_t>(std::min<std::uint64_t>((begin + geometry.layerBytes + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers));
const auto last = static_cast<std::uint32_t>(std::min<std::uint64_t>((begin + geometry.layerBytes * count + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers));
if (first >= last) {
MarkDirty();
return;
@@ -2341,7 +2341,7 @@ void DepthStencilTests() {
const auto firstSlice = AgcDriver::Graphics::DecodeState(queue);
Require(firstSlice.color.address == reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(sliceMemory.data());
queue.context[0x318] = static_cast<std::uint32_t>(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");