fix(agc): skip an indexed draw whose indices are all restart markers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sasha Papo 2026-10-09 08:30:24 -04:00
parent c0a88ce392
commit 98b4bdf843
4 changed files with 33 additions and 17 deletions

View File

@ -5,6 +5,7 @@
#include <algorithm>
#include <cstring>
#include <limits>
#include <optional>
#include <set>
#include <span>
#include <utility>
@ -196,10 +197,10 @@ inline VertexCopyPlan PlanVertexCopies(std::span<const VertexFetch> fetches) {
return plan;
}
inline std::uint32_t HighestDrawIndex(std::span<const std::byte> indices, std::uint32_t indexSize, bool skipRestart) {
inline std::optional<std::uint32_t> HighestDrawIndex(std::span<const std::byte> indices, std::uint32_t indexSize, bool skipRestart) {
Require(indexSize == 2 || indexSize == 4, "unsupported index size");
const auto restartIndex = indexSize == 2 ? 0xffffu : 0xffffffffu;
std::uint32_t highest = 0;
std::optional<std::uint32_t> highest;
for (std::size_t offset = 0; offset + indexSize <= indices.size(); offset += indexSize) {
std::uint32_t index = 0;
if (indexSize == 2) {
@ -210,7 +211,7 @@ inline std::uint32_t HighestDrawIndex(std::span<const std::byte> indices, std::u
std::memcpy(&index, indices.data() + offset, sizeof(index));
}
if (skipRestart && index == restartIndex) continue;
highest = std::max(highest, index);
highest = std::max(highest.value_or(0u), index);
}
return highest;
}

View File

@ -851,14 +851,22 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
const bool skipRestart = state.primitiveRestart && (!listTopology || context.primitiveListRestart) && !fanGeometry;
const auto use = draw.indexSize == 2 ? (skipRestart ? Recorder::SnapshotUse::Index16Restart : Recorder::SnapshotUse::Index16) : (skipRestart ? Recorder::SnapshotUse::Index32Restart : Recorder::SnapshotUse::Index32);
auto copy = CopyDrawInput(context, context.recorder, draw.indexAddress, static_cast<std::size_t>(indexBytes), draw.indexSize, use);
std::uint32_t highest = copy.derived;
std::optional<std::uint32_t> highest;
if (!copy.reused) {
highest = HighestDrawIndex(copy.buffer->Bytes().first(static_cast<std::size_t>(indexBytes)), draw.indexSize, skipRestart);
KeepDrawInput(context.recorder, draw.indexAddress, copy, use, highest);
KeepDrawInput(context.recorder, draw.indexAddress, copy, use, skipRestart ? (highest ? *highest + 1u : 0u) : highest.value_or(0u));
} else if (!skipRestart) {
highest = copy.derived;
} else if (copy.derived != 0) {
highest = copy.derived - 1u;
}
Require(highest <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit");
Require(!fanGeometry || !state.primitiveRestart || highest != (draw.indexSize == 2 ? 0xffffu : 0xffffffffu), "primitive restart in a triangle fan geometry draw is unsupported");
inputs.maxIndex = highest;
if (!highest) {
inputs.nothing = true;
return inputs;
}
Require(*highest <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit");
Require(!fanGeometry || !state.primitiveRestart || *highest != (draw.indexSize == 2 ? 0xffffu : 0xffffffffu), "primitive restart in a triangle fan geometry draw is unsupported");
inputs.maxIndex = *highest;
inputs.indices = std::move(copy.buffer);
}
APS5_LOG_CHARS_OUT_DEBUG("Index validation OK");

View File

@ -2857,7 +2857,8 @@ void highestDrawIndexTests() {
Require(HighestDrawIndex(bytesOf(wide), 4, true) == 0xffff, "a 32-bit restart index counted as a vertex, or 0xffff was taken for it");
Require(HighestDrawIndex(bytesOf(wide), 4, false) == 0xffffffffu, "a 32-bit all-ones index without restart was skipped");
const std::array<std::uint16_t, 4> restartOnly{0xffff, 0xffff, 0xffff, 0xffff};
Require(HighestDrawIndex(bytesOf(restartOnly), 2, true) == 0, "a draw of only restart indices reached a vertex");
Require(!HighestDrawIndex(bytesOf(restartOnly), 2, true).has_value(), "a draw of only restart indices reached a vertex");
Require(HighestDrawIndex(bytesOf(restartOnly), 2, false) == 0xffff, "a 16-bit all-ones index without restart was skipped");
Require(HighestDrawIndex(bytesOf(narrow).first(4), 2, true) == 7 && HighestDrawIndex(bytesOf(narrow).first(2), 2, true) == 0, "the scan read past its index range");
expectFailure([&] { HighestDrawIndex(bytesOf(narrow), 1, false); }, "unsupported index size");
}

View File

@ -2,6 +2,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cstdint>
@ -35,9 +36,11 @@ constexpr std::array<std::array<float, 4>, 3> Triangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.25f, 1.0f}, {-1.0f, 3.0f, 0.75f, 1.0f}
}};
std::array<std::uint32_t, 4> VertexBufferDescriptor(std::uint32_t select) {
alignas(256) constexpr std::array<std::uint16_t, 6> RestartIndices{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff};
std::array<std::uint32_t, 4> VertexBufferDescriptor(std::uint32_t select, std::uint32_t records) {
const auto address = reinterpret_cast<std::uintptr_t>(Triangle.data());
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (16u << 16u), static_cast<std::uint32_t>(Triangle.size()), 0x0104d000u | select};
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (16u << 16u), records, 0x0104d000u | select};
}
std::array<std::uint32_t, 4> OutputDescriptor() {
@ -56,10 +59,10 @@ ShaderRecompiler::RecompileRequest VertexRequest(const AgcDriver::VulkanDevice&
return request;
}
ShaderRecompiler::ShaderVertexStageInfo VertexInfo(std::uint32_t select, std::int32_t registers) {
ShaderRecompiler::ShaderVertexStageInfo VertexInfo(std::uint32_t select, std::int32_t registers, std::uint32_t records = static_cast<std::uint32_t>(Triangle.size())) {
ShaderRecompiler::ShaderVertexStageInfo info{};
info.resourcesNum = 1;
info.resources[0].fields = VertexBufferDescriptor(select);
info.resources[0].fields = VertexBufferDescriptor(select, records);
info.resourcesDst[0] = {8, registers, 0, 0};
info.fetchAttribReg = 8;
info.fetchBufferReg = 10;
@ -76,12 +79,12 @@ std::vector<std::uint32_t> UserData() {
return userData;
}
void Draw(AgcDriver::VulkanDevice& device, std::uint32_t select) {
void Draw(AgcDriver::VulkanDevice& device, std::uint32_t select, bool onlyRestart = false) {
Pixels.fill(std::byte{0x40});
Output.fill(0xdeadbeefu);
const auto userData = UserData();
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
const auto vertexResult = ShaderRecompiler::Recompile(VertexRequest(device, userData, VertexInfo(select, 4), vertexMemory));
const auto vertexResult = ShaderRecompiler::Recompile(VertexRequest(device, userData, VertexInfo(select, 4, onlyRestart ? 0u : static_cast<std::uint32_t>(Triangle.size())), vertexMemory));
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
ShaderRecompiler::ShaderPixelStageInfo pixel{};
@ -111,7 +114,8 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t select) {
state.colors = {state.color};
state.hasColorTarget = true;
state.renderExtent = {Width, Height};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.topology = onlyRestart ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.primitiveRestart = onlyRestart;
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.negativeOneToOne = false;
state.scissor = {{0, 0}, {Width, Height}};
@ -120,7 +124,7 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t select) {
state.blend.colorWriteMask = 15;
state.blends = {state.blend};
state.blendConstants = {};
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
const auto draw = onlyRestart ? AgcDriver::Pm4::DrawParameters{reinterpret_cast<std::uintptr_t>(RestartIndices.data()), static_cast<std::uint32_t>(RestartIndices.size()), 2, 1, 0, true} : AgcDriver::Pm4::DrawParameters{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
device.WaitIdle();
}
@ -159,6 +163,8 @@ int main() {
Draw(*device, ReverseSelect);
Check(ReverseSelect, "fetch-shader call, reversed dst_sel");
CheckRegisterLimit(*device);
Draw(*device, IdentitySelect, true);
Require(std::all_of(Output.begin(), Output.end(), [](std::uint32_t word) { return word == 0xdeadbeefu; }) && std::all_of(Pixels.begin(), Pixels.end(), [](std::byte value) { return value == std::byte{0x40}; }), "an indexed draw of only restart indices ran its vertices or wrote pixels");
std::puts("scalar swappc fetch tests passed");
return 0;
} catch (const std::exception& error) {