diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index 1629571f8..4e11c1c90 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -196,10 +197,10 @@ inline VertexCopyPlan PlanVertexCopies(std::span fetches) { return plan; } -inline std::uint32_t HighestDrawIndex(std::span indices, std::uint32_t indexSize, bool skipRestart) { +inline std::optional HighestDrawIndex(std::span 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 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 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; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 4d484dddd..566e5a091 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -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(indexBytes), draw.indexSize, use); - std::uint32_t highest = copy.derived; + std::optional highest; if (!copy.reused) { highest = HighestDrawIndex(copy.buffer->Bytes().first(static_cast(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"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index c63ceef3f..de6c1abef 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -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 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"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp index 158fb8d50..7b1b8a95b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "Recompiler.hpp" #include "VulkanTestDevice.hpp" +#include #include #include #include @@ -35,9 +36,11 @@ constexpr std::array, 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 VertexBufferDescriptor(std::uint32_t select) { +alignas(256) constexpr std::array RestartIndices{0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}; + +std::array VertexBufferDescriptor(std::uint32_t select, std::uint32_t records) { const auto address = reinterpret_cast(Triangle.data()); - return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), static_cast(Triangle.size()), 0x0104d000u | select}; + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), records, 0x0104d000u | select}; } std::array 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(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 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 vertexMemory{{{reinterpret_cast(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(Triangle.size())), vertexMemory)); const auto vertexPush = static_cast(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(Height), static_cast(Width), -static_cast(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(Triangle.size()), 0, 1, 0, false}; + const auto draw = onlyRestart ? AgcDriver::Pm4::DrawParameters{reinterpret_cast(RestartIndices.data()), static_cast(RestartIndices.size()), 2, 1, 0, true} : AgcDriver::Pm4::DrawParameters{0, static_cast(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) {