From 17ca4531992aa4c2e003a4c0e2f62afb6d913aca Mon Sep 17 00:00:00 2001 From: alfalfa-star Date: Wed, 14 Oct 2026 19:44:03 +0200 Subject: [PATCH 1/2] perf(agc): draw an indirect rect list from its records on the GPU ClassifyIndirectDraw sent every indirect rect-list draw to the CPU path (non-vertex path), which reads the records on the CPU. When the GPU writes those records earlier in the frame, the read waits for every batch up to the producer: in Kena (PPSA01802) each such DRAW_INDIRECT waited about 15 ms, about once per frame. A rect list on the vertex path is drawn by its generated tessellation stages from three-vertex patches, with no draw-dependent constants, so the records can be consumed on the GPU like any vertex-path draw. The classifier now keeps a rect list without mesh or tessellation stages on the GPU path, and Graphics::Draw accepts it. APS5_NO_RECT_INDIRECT=1 restores the CPU path. agc_driver_rect_list_indirect submits a direct rect-list draw and the same rectangle from a DRAW_INDIRECT record and requires the same bytes, and a record without vertices to draw nothing; it passes on either path. agc_driver_draw_offset_indices requires an auto indirect rect-list record whose packet names no offset registers to be classified to the GPU. Co-Authored-By: Claude Opus 5.5 --- .../src/Driver/Draw/IndirectDraw.cpp | 4 +- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 2 +- .../tests/execution/DrawOffsetIndices.cpp | 18 +- .../tests/execution/RectListIndirect.cpp | 204 ++++++++++++++++++ 4 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp index 21d38fdea8e..e52db4829bc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp @@ -31,7 +31,9 @@ std::optional Driver::ClassifyIndirectDraw(const Sha constexpr std::uint32_t NoLocation = 0x280u; const bool meshPath = graphics.stages.mesh.has_value() && !graphics.stages.tessellation && !graphics.rectList; const bool meshGpuSide = meshPath && drawParameters.indexed && indirect.count == 1 && !indirect.countIndirect && indirect.baseVertexLocation == NoLocation && indirect.startInstanceLocation == NoLocation && (!indirect.drawIndexEnabled || indirect.drawIndexLocation == NoLocation); - if ((graphics.stages.path != Graphics::ShaderPath::Vertex || graphics.rectList) && !meshGpuSide) fail(Path::NonVertexPath); + static const bool rectListOnGpu = std::getenv("APS5_NO_RECT_INDIRECT") == nullptr; + const bool rectListGpuSide = rectListOnGpu && graphics.rectList && !graphics.stages.mesh && !graphics.stages.tessellation; + if ((graphics.stages.path != Graphics::ShaderPath::Vertex || (graphics.rectList && !rectListGpuSide)) && !meshGpuSide) fail(Path::NonVertexPath); if (indirect.drawIndexEnabled && indirect.drawIndexSgpr >= 0) fail(Path::DrawIndex); if (indirect.countIndirect && !support.count) fail(Path::FeatureGap); if (indirect.countIndirect && indirect.count > 1 && !support.multi) fail(Path::FeatureGap); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index f38a09b9340..d7d40317901 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1022,7 +1022,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P inputs.nothing = true; return inputs; } - } else Require((!state.stages.mesh || draw.indexed) && !state.stages.tessellation && !state.rectList, "indirect draw on a non-vertex path must be resolved by the driver"); + } else Require((!state.stages.mesh || draw.indexed) && !state.stages.tessellation, "indirect draw on a non-vertex path must be resolved by the driver"); inputs.indexBytes = static_cast(draw.indexCount) * draw.indexSize; const auto indexBytes = inputs.indexBytes; APS5_LOG_OUT_DEBUG("Index buffer bytes=%llu", static_cast(indexBytes)); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp index 6754f0c84df..0fa8c4ebcd5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp @@ -262,14 +262,15 @@ class Fixture { return result; } - AgcDriver::Graphics::State State() const { + AgcDriver::Graphics::State State(bool rectList = false) const { AgcDriver::Graphics::State state{}; state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; state.colors = {state.color}; state.hasColorTarget = true; state.renderExtent = {Width, Height}; - state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.topology = rectList ? VK_PRIMITIVE_TOPOLOGY_PATCH_LIST : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.rectList = rectList; state.viewport = {0.0f, static_cast(Height), static_cast(Width), -static_cast(Height), 0.0f, 1.0f}; state.scissor = {{0, 0}, {Width, Height}}; state.cullMode = VK_CULL_MODE_NONE; @@ -346,8 +347,8 @@ std::string PathName(const std::optional& path) { return path ? "the CPU path (" + std::string(AgcDriver::Graphics::IndirectDrawPathName(*path)) + ")" : std::string("the GPU"); } -std::string Classified(Fixture& fixture, const std::string& what, const ShaderRecompiler::RecompileResult& vertex, const std::vector& userData, AgcDriver::Pm4::DrawParameters& draw, std::optional expectedPath, Rule expectedRule) { - const auto path = AgcDriver::DriverDetail::Driver::ClassifyIndirectDraw(vertex, fixture.State(), Program(userData), fixture.device, draw, false); +std::string Classified(Fixture& fixture, const std::string& what, const ShaderRecompiler::RecompileResult& vertex, const std::vector& userData, AgcDriver::Pm4::DrawParameters& draw, std::optional expectedPath, Rule expectedRule, bool rectList = false) { + const auto path = AgcDriver::DriverDetail::Driver::ClassifyIndirectDraw(vertex, fixture.State(rectList), Program(userData), fixture.device, draw, false); const auto& indirect = *draw.indirect; const bool rules = expectedPath || (indirect.vertexRule == expectedRule && indirect.instanceRule == expectedRule); std::cout << what << ": classified to " << PathName(path) << '\n'; @@ -475,6 +476,15 @@ std::string Run(Fixture& fixture) { fixture.Draw(vertex, draw); failures += fixture.Check(what + ", drawn on the GPU", bufferIndices, 0x51u, 0x62u, true); } + { + const std::string what = "auto indirect rect-list record whose packet names no register for its offsets, user data s16 0x51, s17 0x62"; + const std::array record{Vertices, Instances, 300, 7}; + std::copy(record.begin(), record.end(), fixture.Records() + 32); + const auto vertex = fixture.Vertex(VertexCode, staleUserData); + AgcDriver::Pm4::DrawParameters draw{0, 0, 0, 0, 0, false, 0, 0}; + draw.indirect = AgcDriver::Pm4::DrawParameters::IndirectDraw{reinterpret_cast(fixture.Records() + 32), 0x24u, 16u, 16u, 1u, false, 0u, NoLocation, NoLocation, NoLocation, false, 0u}; + failures += Classified(fixture, what, vertex, staleUserData, draw, std::nullopt, Rule::Constant, true); + } } return failures; } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp new file mode 100644 index 00000000000..aa000b47644 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp @@ -0,0 +1,204 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::byte Kept{0x40}; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060208u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array SlotVertexCode{ + 0x7e000d05u, 0x100c00ffu, 0x3eaaaaabu, 0x7e0c4906u, 0x100e0cffu, 0x40400000u, 0x08000f00u, + 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e120d08u, 0x101212f4u, 0x060c0d09u, 0x100c0cf0u, 0x100202ffu, 0x3e000000u, 0x06020306u, 0x060202ffu, 0xbf600000u, + 0x7e060208u, 0x7e0802f2u, 0x7e0a02ffu, 0x44800000u, 0xf80000d1u, 0x00000005u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u, +}; +alignas(256) constexpr std::array PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u}; +alignas(256) std::array DirectMemory{}; +alignas(256) std::array IndirectMemory{}; +alignas(256) std::array EmptyMemory{}; +alignas(256) std::array DirectInstancedMemory{}; +alignas(256) std::array IndirectInstancedMemory{}; +alignas(256) std::array DirectIndexedMemory{}; +alignas(256) std::array IndirectIndexedMemory{}; +alignas(256) std::array Records{3, 1, 0, 0, 0, 1, 0, 0, 6, 2, 0, 0, 6, 1, 0, 0, 0, 0, 0, 0}; +alignas(256) std::array Indices{6, 7, 8, 3, 4, 5, 0, 0}; + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; + +alignas(8) Header VertexHeader{}; +alignas(8) Header SlotVertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) { + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18u; + header.shader.header_size = sizeof(Header); + header.shader.shader_size = static_cast(bytes); + header.shader.code = code; + header.shader.type = type; + header.shader.user_data = &header.userData; + AgcDriverRegisterShader_nid_postfix(&header.shader); +} + +void Append(std::vector& commands, std::uint32_t opcode, std::initializer_list payload) { + commands.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + commands.insert(commands.end(), payload); +} + +std::vector DrawRectangles() { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto slotVertex = reinterpret_cast(SlotVertexCode.data()); + const auto indices = reinterpret_cast(Indices.data()); + const auto pixel = reinterpret_cast(PixelCode.data()); + const auto records = reinterpret_cast(Records.data()); + const std::vector> context{ + {0x2d5, 0x2000}, + {0x1b6, 0}, {0x207, 0x01200000}, {0x200, 0}, {0x203, 0x800}, + {0x2dc, 0xaa00}, {0x2f8, 0}, {0x292, 2}, {0x293, 0}, + {0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa}, + {0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff}, + {0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240}, + {0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010}, + {0x1c4, 0}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028}, + {0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u}, + {0x3b8, 0x9000000}, {0x1e0, 0}, + {0xc, 0}, {0xd, 0x40040}, + {0x81, 0x80000000}, {0x82, 0x40040}, + {0x90, 0x80000000}, {0x91, 0x40040}, + {0x94, 0x80000000}, {0x95, 0x40040}, + {0x10f, std::bit_cast(32.0f)}, {0x110, std::bit_cast(32.0f)}, + {0x111, std::bit_cast(-2.0f)}, {0x112, std::bit_cast(2.0f)}, + {0x113, std::bit_cast(1.0f)}, {0x114, 0}, + {0xb4, 0}, {0xb5, std::bit_cast(1.0f)}, + {0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0}, + }; + const std::vector> shader{ + {0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 1u << 1u}, + {0x008, static_cast(pixel >> 8u)}, {0x009, static_cast(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast(1.0f)}, + }; + std::vector commands; + Append(commands, 0x79, {0x242, 17}); + Append(commands, 0x79, {0x24a, 0}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value}); + Append(commands, 0x11, {1, static_cast(records), static_cast(records >> 32u)}); + const auto target = [&](std::array& memory) { + const auto color = reinterpret_cast(memory.data()); + Append(commands, 0x69, {0x318, static_cast(color >> 8u)}); + Append(commands, 0x69, {0x390, static_cast(color >> 40u)}); + }; + target(DirectMemory); + Append(commands, 0x2d, {3, 2}); + target(IndirectMemory); + Append(commands, 0x24, {0, 0, 0, 2}); + target(EmptyMemory); + Append(commands, 0x24, {16, 0, 0, 2}); + Append(commands, 0x76, {0xc8, static_cast(slotVertex >> 8u)}); + Append(commands, 0x76, {0xc9, static_cast(slotVertex >> 40u)}); + target(DirectInstancedMemory); + Append(commands, 0x2f, {2}); + Append(commands, 0x2d, {6, 2}); + Append(commands, 0x2f, {1}); + target(IndirectInstancedMemory); + Append(commands, 0x24, {32, 0, 0, 2}); + Append(commands, 0x2a, {0}); + target(DirectIndexedMemory); + Append(commands, 0x27, {6, static_cast(indices), static_cast(indices >> 32u), 6, 0}); + Append(commands, 0x26, {static_cast(indices), static_cast(indices >> 32u)}); + Append(commands, 0x13, {6}); + target(IndirectIndexedMemory); + Append(commands, 0x25, {48, 0, 0, 0}); + return commands; +} + +std::size_t Covered(const std::array& memory) { + return static_cast(std::count_if(memory.begin(), memory.end(), [](std::byte value) { return value != Kept; })); +} + +std::string Slots(const std::array& memory) { + std::string slots; + for (std::size_t slot = 0; slot < 4; ++slot) { + std::size_t covered = 0; + for (std::size_t row = 0; row < 4; ++row) { + for (std::size_t x = slot * 16; x < slot * 16 + 16; ++x) { + const auto pixel = (row * 64 + x) * 4; + covered += std::any_of(memory.begin() + static_cast(pixel), memory.begin() + static_cast(pixel + 4), [](std::byte value) { return value != Kept; }) ? 1u : 0u; + } + } + if (!slots.empty()) slots += ' '; + slots += std::to_string(covered); + } + return slots; +} + +void Same(const std::array& indirect, const std::array& direct, const std::string& what) { + for (std::size_t index = 0; index < direct.size(); ++index) { + Require(indirect[index] == direct[index], "byte " + std::to_string(index) + " of the indirect " + what + " is " + std::to_string(static_cast(indirect[index])) + ", the direct draw wrote " + std::to_string(static_cast(direct[index]))); + } +} + +void IndirectRectListsDrawAsDirectOnes() { + DirectMemory.fill(Kept); + IndirectMemory.fill(Kept); + EmptyMemory.fill(Kept); + DirectInstancedMemory.fill(Kept); + IndirectInstancedMemory.fill(Kept); + DirectIndexedMemory.fill(Kept); + IndirectIndexedMemory.fill(Kept); + Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Registered(SlotVertexHeader, SlotVertexCode.data(), sizeof(SlotVertexCode), 2); + Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + auto commands = DrawRectangles(); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); + const auto direct = Covered(DirectMemory); + Require(direct != 0, "the direct rect-list draw covered nothing"); + Same(IndirectMemory, DirectMemory, "rect-list draw"); + const auto empty = Covered(EmptyMemory); + Require(empty == 0, "the indirect rect-list draw whose record has no vertices covered " + std::to_string(empty) + " bytes, expected none"); + Require(Slots(DirectInstancedMemory) == "32 32 32 32", "the direct rect-list draw of 6 vertices and 2 instances covered pixels " + Slots(DirectInstancedMemory) + " of its four rectangles, expected 32 each"); + Same(IndirectInstancedMemory, DirectInstancedMemory, "rect-list draw of 6 vertices and 2 instances"); + Require(Slots(DirectIndexedMemory) == "0 32 32 0", "the direct indexed rect-list draw covered pixels " + Slots(DirectIndexedMemory) + " of the four rectangles, expected only the second and third"); + Same(IndirectIndexedMemory, DirectIndexedMemory, "indexed rect-list draw"); +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + IndirectRectListsDrawAsDirectOnes(); + AgcDriverShutdown_nid_postfix(); + std::puts("indirect rect-list draws drew what the direct draw did"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + AgcDriverShutdown_nid_postfix(); + return 1; + } +} From ac64ea45242268e9dffd166f49c6162d7a85b57e Mon Sep 17 00:00:00 2001 From: alfalfa-star Date: Fri, 16 Oct 2026 23:20:51 +0200 Subject: [PATCH 2/2] test(agc): require the GPU route for the indirect rect-list draws agc_driver_rect_list_indirect compared the indirect draws' output with the direct draws only, so it also passed when the records were read on the CPU. Its records lived in a static array that is not guest memory, so every indirect draw fell back to the CPU path as not imported. The records now sit in a registered 64 KiB guest allocation, the test turns on APS5_PROFILE_DRAW and reads the indirect-draw counters through the new Graphics::IndirectDrawCounts, and it requires all four indirect draws on the GPU path. It fails with APS5_NO_RECT_INDIRECT=1 and with the IndirectDraw.cpp and Draw.cpp changes reverted (4 non-vertex path). Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Graphics/include/Draw.hpp | 1 + .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 6 ++++ .../tests/execution/RectListIndirect.cpp | 36 +++++++++++++++++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index d852d674327..88b940d5f81 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -93,6 +93,7 @@ bool DrawRecipes(); // `rewritten` counts a GPU-side draw whose records were copied and patched with a constant. enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, Count }; void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false); +std::array(IndirectDrawPath::Count)> IndirectDrawCounts(); const char* IndirectDrawPathName(IndirectDrawPath path); // Draw packets that drew nothing, for the [draws] line: Nothing (an empty count, or no color writes diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index d7d40317901..0f6af925780 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -831,6 +831,12 @@ void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten) { stats.indirectReadUs += readMs * 1000.0; } +std::array IndirectDrawCounts() { + auto& stats = Profile(); + std::lock_guard lock(stats.mutex); + return stats.indirect; +} + void CountDrawSkip(DrawSkip kind, double us) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile) return; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp index aa000b47644..560be5f9d54 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/RectListIndirect.cpp @@ -1,5 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" #include "VulkanTestDevice.hpp" #include #include @@ -7,9 +9,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -38,7 +42,9 @@ alignas(256) std::array DirectInstancedMemory{}; alignas(256) std::array IndirectInstancedMemory{}; alignas(256) std::array DirectIndexedMemory{}; alignas(256) std::array IndirectIndexedMemory{}; -alignas(256) std::array Records{3, 1, 0, 0, 0, 1, 0, 0, 6, 2, 0, 0, 6, 1, 0, 0, 0, 0, 0, 0}; +constexpr std::array RecordWords{3, 1, 0, 0, 0, 1, 0, 0, 6, 2, 0, 0, 6, 1, 0, 0, 0, 0, 0, 0}; +constexpr std::size_t RecordBytes = 65536; +std::uint32_t* Records = nullptr; alignas(256) std::array Indices{6, 7, 8, 3, 4, 5, 0, 0}; struct Header { @@ -71,7 +77,7 @@ std::vector DrawRectangles() { const auto slotVertex = reinterpret_cast(SlotVertexCode.data()); const auto indices = reinterpret_cast(Indices.data()); const auto pixel = reinterpret_cast(PixelCode.data()); - const auto records = reinterpret_cast(Records.data()); + const auto records = reinterpret_cast(Records); const std::vector> context{ {0x2d5, 0x2000}, {0x1b6, 0}, {0x207, 0x01200000}, {0x200, 0}, {0x203, 0x800}, @@ -158,6 +164,18 @@ void Same(const std::array& indirect, const std::array(path)); + } + Require(cpu.empty(), "indirect rect-list draws read their records on the CPU: " + cpu); + Require(counts[0] == 4, "the GPU drew " + std::to_string(counts[0]) + " indirect rect-list draws from their records, expected 4"); +} + void IndirectRectListsDrawAsDirectOnes() { DirectMemory.fill(Kept); IndirectMemory.fill(Kept); @@ -169,6 +187,12 @@ void IndirectRectListsDrawAsDirectOnes() { Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); Registered(SlotVertexHeader, SlotVertexCode.data(), sizeof(SlotVertexCode), 2); Registered(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + Records = static_cast(::operator new(RecordBytes, std::align_val_t{RecordBytes})); + std::copy(RecordWords.begin(), RecordWords.end(), Records); + { + GuestAllocations::Mutation mutation; + mutation.Add(Records, RecordBytes, true, true, true); + } auto commands = DrawRectangles(); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; Require(sceAgcDriverSubmitDcb(&packet) == 0, "the draw submission failed"); @@ -182,19 +206,25 @@ void IndirectRectListsDrawAsDirectOnes() { Same(IndirectInstancedMemory, DirectInstancedMemory, "rect-list draw of 6 vertices and 2 instances"); Require(Slots(DirectIndexedMemory) == "0 32 32 0", "the direct indexed rect-list draw covered pixels " + Slots(DirectIndexedMemory) + " of the four rectangles, expected only the second and third"); Same(IndirectIndexedMemory, DirectIndexedMemory, "indexed rect-list draw"); + DrawnOnTheGpu(); } } int main() { try { +#ifdef _WIN32 + _putenv_s("APS5_PROFILE_DRAW", "1"); +#else + setenv("APS5_PROFILE_DRAW", "1", 1); +#endif { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; } IndirectRectListsDrawAsDirectOnes(); AgcDriverShutdown_nid_postfix(); - std::puts("indirect rect-list draws drew what the direct draw did"); + std::puts("indirect rect-list draws were drawn on the GPU and drew what the direct draw did"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n';