diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index d9a8d2734941..4d0af6e7b651 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1830,6 +1830,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test(NAME agc_driver_sparse_indirect_indices_unreadable COMMAND agc_driver_sparse_indirect_indices_tests --unreadable) + add_test(NAME agc_driver_sparse_indirect_indices_no_size COMMAND agc_driver_sparse_indirect_indices_tests --no-size) + add_test(NAME agc_driver_sparse_indirect_indices_no_size_unreadable COMMAND agc_driver_sparse_indirect_indices_tests --no-size-unreadable) + add_test(NAME agc_driver_sparse_indirect_indices_no_size_unreadable_record COMMAND agc_driver_sparse_indirect_indices_tests --no-size-unreadable-record) + set_tests_properties(agc_driver_sparse_indirect_indices agc_driver_sparse_indirect_indices_unreadable agc_driver_sparse_indirect_indices_no_size agc_driver_sparse_indirect_indices_no_size_unreadable agc_driver_sparse_indirect_indices_no_size_unreadable_record PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_sparse_indirect_indices_unreadable agc_driver_sparse_indirect_indices_no_size agc_driver_sparse_indirect_indices_no_size_unreadable agc_driver_sparse_indirect_indices_no_size_unreadable_record PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_driver_partially_resident_pending_stores COMMAND agc_driver_partially_resident_snapshot_tests --pending-stores) set_tests_properties(agc_driver_partially_resident_pending_stores PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) if(WIN32) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index f902632e1a5a..34b475d49a90 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -46,6 +46,7 @@ struct DrawParameters { std::uint32_t drawIndexLocation; bool drawIndexEnabled; std::uint32_t indxOffset; + bool indexLimitKnown = true; enum class Rule : std::uint8_t { InPlace, Constant }; Rule vertexRule = Rule::Constant; Rule instanceRule = Rule::Constant; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 0d6a6da583fb..42c902132a2a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -272,7 +272,7 @@ struct QueueState { std::uint64_t indexBase = 0; std::uint64_t drawIndirectBase = 0; std::uint64_t dispatchIndirectBase = 0; - std::uint32_t indexBufferSize = 0; + std::optional indexBufferSize; std::uint32_t indexType = 0; std::uint32_t instanceCount = 1; std::vector markers; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 71066bbe9aeb..7a64f055c0a3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -532,10 +532,16 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } Pm4::DrawParameters direct{0, arguments.count, 0, arguments.instances, drawParameters.flags, drawParameters.indexed, 0, 0}; if (drawParameters.indexed) { - - if (arguments.firstVertexOrIndex >= drawParameters.indexCount) continue; + if (indirect.indexLimitKnown) { + if (arguments.firstVertexOrIndex >= drawParameters.indexCount) continue; + direct.indexCount = std::min(arguments.count, drawParameters.indexCount - arguments.firstVertexOrIndex); + } else { + const auto end = static_cast(arguments.firstVertexOrIndex) + arguments.count; + require(end <= std::numeric_limits::max(), "indirect draw index range overflow"); + require(end * drawParameters.indexSize <= std::numeric_limits::max() - drawParameters.indexAddress, "indirect draw index range address overflow"); + direct.indexCount = arguments.count; + } direct.indexAddress = drawParameters.indexAddress + static_cast(arguments.firstVertexOrIndex) * drawParameters.indexSize; - direct.indexCount = std::min(arguments.count, drawParameters.indexCount - arguments.firstVertexOrIndex); direct.indexSize = drawParameters.indexSize; } else { direct.firstVertex = indirect.indxOffset; 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 e52db4829bc3..fa1676e68c33 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include @@ -28,6 +29,8 @@ std::optional Driver::ClassifyIndirectDraw(const Sha using Path = Graphics::IndirectDrawPath; const auto support = localDevice->DrawIndirectSupport(); const auto fail = [&](Path reason) { if (!indirectCpu) indirectCpu = reason; }; + if (drawParameters.indexed && !indirect.indexLimitKnown) fail(Path::IndexLimitUnknown); + else if (drawParameters.indexed && !GuestMemory::Accessible(reinterpret_cast(drawParameters.indexAddress), static_cast(drawParameters.indexCount) * drawParameters.indexSize)) fail(Path::IndexRange); 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); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index 4a7d05570616..5998db7ee11a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -851,16 +851,18 @@ DrawParameters resolveIndirectDraw(std::span packet, const draw.firstVertex = offset->second; indirect.indxOffset = offset->second; } else { - // The CP clamps firstIndex + indexCount to INDEX_BUFFER_SIZE: the declared range is the - // hardware's view of the index buffer, so that is what gets bound. const auto indexSize = indexSizeOf(queue); - require(queue.indexBufferSize != 0, "INDEX_BUFFER_SIZE has not been set"); - const auto bytes = static_cast(queue.indexBufferSize) * indexSize; - require(bytes <= std::numeric_limits::max() && bytes <= std::numeric_limits::max() - queue.indexBase, "index range size overflow"); - GuestMemory::CheckRange(reinterpret_cast(queue.indexBase), static_cast(bytes), indexSize); draw.indexAddress = queue.indexBase; - draw.indexCount = queue.indexBufferSize; draw.indexSize = indexSize; + if (queue.indexBufferSize) { + require(*queue.indexBufferSize != 0, "INDEX_BUFFER_SIZE is zero"); + const auto bytes = static_cast(*queue.indexBufferSize) * indexSize; + require(bytes <= std::numeric_limits::max() && bytes <= std::numeric_limits::max() - queue.indexBase, "index range size overflow"); + draw.indexCount = *queue.indexBufferSize; + } else { + draw.indexCount = 0; + indirect.indexLimitKnown = false; + } } draw.indirect = indirect; return draw; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 0b596093b7a2..08abdaa0efec 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -91,7 +91,7 @@ bool DrawRecipes(); // draw index, the vertex ranges, the device features, APS5_NO_GPU_INDIRECT_DRAW) or under the device // lock in Draw (the memory state of the records). `readMs` is the CPU read's time (its sync); // `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 }; +enum class IndirectDrawPath : std::uint8_t { Gpu, NotFolded, FetchUnknown, NonVertexPath, IndxOffset, DrawIndex, VertexRange, FeatureGap, PendingImage, PendingLabelOrCopy, NotImported, Disabled, IndexRange, IndexLimitUnknown, Count }; void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false); std::array(IndirectDrawPath::Count)> IndirectDrawCounts(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 9aaa23964265..c71b778f0d79 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -343,7 +343,7 @@ constexpr std::array DrawPhaseNames{"validate", "vertex enum SyncReason : std::size_t { SyncNone, SyncNotResident, SyncNoRecorder, SyncDisabled, SyncCopiedWrites, SyncLease, SyncCount }; constexpr std::array SyncReasonNames{"none", "non-resident target", "no recorder", "disabled", "copied writes", "lease"}; constexpr std::size_t IndirectPathCount = static_cast(IndirectDrawPath::Count); -constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled"}; +constexpr std::array IndirectDrawPathNames{"gpu-side", "patched SGPR not folded", "fetch offset unknown", "non-vertex path", "GE_INDX_OFFSET", "draw index", "vertex range too large", "feature gap", "pending image results", "pending label or copied write", "not imported", "disabled", "index range not fully readable", "index limit unknown"}; // What became of one draw, for the totals. struct DrawOutcome { diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 9de9997f8572..2206b643a695 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -354,8 +354,17 @@ void testIndirectDraw() { alignas(4) std::array indices{}; state.indexBase = reinterpret_cast(indices.data()); state.indexType = 0; + state.indexBufferSize.reset(); + const auto unlimited = AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state); + check(unlimited.indexed && unlimited.indirect && !unlimited.indirect->indexLimitKnown && unlimited.indexCount == 0 && unlimited.indexAddress == state.indexBase && unlimited.indexSize == 2 && unlimited.indirect->recordBytes == 20, "an indexed indirect draw without INDEX_BUFFER_SIZE did not resolve with an unknown limit"); + const auto unlimitedMulti = AgcDriver::Pm4::ResolveDraw(makePacket(0x38, {0, 0x8c, 0x280, 0x280, 3, 0, 0, 20, 0}), state); + check(unlimitedMulti.indirect && !unlimitedMulti.indirect->indexLimitKnown && unlimitedMulti.indirect->count == 3 && unlimitedMulti.indirect->stride == 20, "a multi indexed indirect draw without INDEX_BUFFER_SIZE did not resolve with an unknown limit"); + execute(state, makePacket(0x13, {0})); + check(state.indexBufferSize.has_value() && *state.indexBufferSize == 0, "INDEX_BUFFER_SIZE of zero was not recorded as written"); expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state); }, "INDEX_BUFFER_SIZE"); + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x38, {0, 0x8c, 0x280, 0x280, 3, 0, 0, 20, 0}), state); }, "INDEX_BUFFER_SIZE"); state.indexBufferSize = 8; + check(AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state).indirect->indexLimitKnown, "a written INDEX_BUFFER_SIZE resolved with an unknown limit"); const auto indexed = AgcDriver::Pm4::ResolveDraw(makePacket(0x25, {0, 0x8c, 0x280, 0}), state); check(indexed.indexed && indexed.indexAddress == state.indexBase && indexed.indexCount == 8 && indexed.indexSize == 2 && indexed.indirect->recordBytes == 20 && indexed.indirect->stride == 20 && indexed.firstVertex == 0 && indexed.indirect->indxOffset == 0, "indexed indirect draw mismatch"); check(indexed.indirect->VertexDwordOffset() == 12 && indexed.indirect->InstanceDwordOffset() == 16, "indexed record geometry mismatch"); @@ -414,7 +423,13 @@ void testIndexAttributesIndirect() { check(sameDraw(indirectFromRecord, AgcDriver::Pm4::ResolveDraw(indirectDraw, direct)) && indirectFromRecord.indexAddress == indexAddress && indirectFromRecord.indexCount == 12 && indirectFromRecord.indexSize == 4, "an indexed indirect draw after INDEX_ATTRIBUTES_INDIRECT differs from the same draw after the direct packets"); records[6] = 0x7fffffffu; execute(loaded, packet); - expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "guest"); + const auto largeIndirect = AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); + check(largeIndirect.indexAddress == indexAddress && largeIndirect.indexCount == records[6] && largeIndirect.indexSize == 4, "an indirect draw did not preserve its declared index limit for record resolution"); + loaded.indexBase = std::numeric_limits::max() - 3u; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "index range size overflow"); + loaded.indexBase = indexAddress + 1u; + expectFailure([&] { AgcDriver::Pm4::ResolveDraw(indirectDraw, loaded); }, "misaligned index base"); + loaded.indexBase = indexAddress; records[6] = 12; static std::size_t flushed = 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp new file mode 100644 index 000000000000..20a4dc48e24d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SparseIndirectIndices.cpp @@ -0,0 +1,252 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::byte Kept{0x40}; +constexpr std::uint32_t DeclaredIndices = 0x10000000u; +constexpr std::size_t ReservationBytes = std::size_t{DeclaredIndices} * 4; +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 PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u}; +alignas(256) constexpr std::array WriterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0x7e020203u, 0x7e040204u, 0xe07c0000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array Reference{}; +alignas(256) std::array Actual{}; +alignas(256) std::array Records{}; +alignas(256) std::array RecordDescriptor{}; +alignas(256) std::array Count{}; +alignas(256) std::array CountDescriptor{}; + +class SparseIndices { +public: + SparseIndices() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + memory = static_cast(VirtualAlloc(nullptr, ReservationBytes, MEM_RESERVE, PAGE_NOACCESS)); + Require(memory != nullptr, "index reservation failed"); + Require(VirtualAlloc(memory, page, MEM_COMMIT, PAGE_READWRITE) == memory, "first index page commit failed"); + Require(VirtualAlloc(memory + 2 * page, page, MEM_COMMIT, PAGE_READWRITE) == memory + 2 * page, "last index page commit failed"); +#else + const auto hostPage = sysconf(_SC_PAGESIZE); + Require(hostPage > 0, "host page size is unavailable"); + page = static_cast(hostPage); + auto* allocation = mmap(nullptr, ReservationBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(allocation != MAP_FAILED, "index reservation failed"); + memory = static_cast(allocation); + Require(mprotect(memory, page, PROT_READ | PROT_WRITE) == 0, "first index page commit failed"); + Require(mprotect(memory + 2 * page, page, PROT_READ | PROT_WRITE) == 0, "last index page commit failed"); +#endif + } + + ~SparseIndices() { +#ifdef _WIN32 + VirtualFree(memory, 0, MEM_RELEASE); +#else + munmap(memory, ReservationBytes); +#endif + } + + SparseIndices(const SparseIndices&) = delete; + SparseIndices& operator=(const SparseIndices&) = delete; + + std::byte* Base() const { return memory + page - 16; } + std::uint32_t AfterGap(std::uint32_t size) const { return static_cast((page + 16) / size); } + + void Fill(std::uint32_t size) { + const std::array small{0, 1, 2}; + const std::array large{0, 1, 2}; + const void* source = size == 2 ? static_cast(small.data()) : static_cast(large.data()); + std::memcpy(Base(), source, 3 * size); + std::memcpy(memory + 2 * page, source, 3 * size); + } + +private: + std::byte* memory = nullptr; + std::size_t page = 0; +}; + +std::uint32_t Low(const void* pointer) { return static_cast(reinterpret_cast(pointer)); } +std::uint32_t High(const void* pointer) { return static_cast(reinterpret_cast(pointer) >> 32u); } + +void Append(std::vector& words, std::uint32_t opcode, std::initializer_list payload) { + words.push_back(0xc0000000u | (static_cast(payload.size() - 1) << 16u) | (opcode << 8u)); + words.insert(words.end(), payload); +} + +void WriteArguments(std::vector& words, const std::uint32_t* descriptor, std::array values) { + const auto address = reinterpret_cast(WriterCode.data()); + Append(words, 0x76, {0x20c, static_cast(address >> 8u)}); + Append(words, 0x76, {0x20d, static_cast(address >> 40u)}); + for (const auto offset : {0x207u, 0x208u, 0x209u}) Append(words, 0x76, {offset, 1}); + Append(words, 0x76, {0x212, 0}); + Append(words, 0x76, {0x213, 10}); + Append(words, 0x76, {0x240, Low(descriptor), High(descriptor), values[0], values[1], values[2]}); + Append(words, 0x15, {1, 1, 1, 0x8041}); +} + +struct Header { + Shader shader{}; + ShaderUserData userData{}; +}; +alignas(8) Header VertexHeader{}; +alignas(8) Header PixelHeader{}; + +void Register(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); +} + +std::vector State() { + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(PixelCode.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}, + }; + std::vector words; + Append(words, 0x79, {0x242, 4}); + Append(words, 0x79, {0x24a, 0}); + for (const auto& [offset, value] : context) Append(words, 0x69, {offset, value}); + Append(words, 0x76, {0xc8, static_cast(vertex >> 8u)}); + Append(words, 0x76, {0xc9, static_cast(vertex >> 40u)}); + Append(words, 0x76, {0x8b, 2}); + Append(words, 0x76, {0x8c, 0}); + Append(words, 0x76, {0x008, static_cast(pixel >> 8u)}); + Append(words, 0x76, {0x009, static_cast(pixel >> 40u)}); + Append(words, 0x76, {0x00b, 2}); + Append(words, 0x76, {0x00c, std::bit_cast(1.0f)}); + return words; +} + +void Target(std::vector& words, void* pointer) { + const auto address = reinterpret_cast(pointer); + Append(words, 0x69, {0x318, static_cast(address >> 8u)}); + Append(words, 0x69, {0x390, static_cast(address >> 40u)}); +} + +void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, bool unreadable, bool sized = true, std::uint32_t second = DeclaredIndices) { + indices.Fill(size); + Reference.fill(Kept); + Actual.fill(Kept); + Records.fill(0); + Count.fill(0); + const auto first = unreadable ? 16u / size : afterGap ? indices.AfterGap(size) : 0u; + Records[8] = 3; + Records[9] = 1; + Records[10] = second; + Records[16] = 3; + Records[17] = 1; + Records[18] = 16u / size; + RecordDescriptor = {Low(Records.data()), High(Records.data()) & 0xffffu, sizeof(Records), 0x31016facu}; + CountDescriptor = {Low(Count.data()), High(Count.data()) & 0xffffu, sizeof(Count), 0x31016facu}; + auto words = State(); + Target(words, Reference.data()); + Append(words, 0x2a, {1}); + Append(words, 0x2d, {3, 2}); + WriteArguments(words, RecordDescriptor.data(), {3, 1, first}); + if (multi) WriteArguments(words, CountDescriptor.data(), {2, 0, 0}); + Target(words, Actual.data()); + Append(words, 0x2a, {size == 2 ? 0u : 1u}); + Append(words, 0x26, {Low(indices.Base()), High(indices.Base())}); + if (sized) Append(words, 0x13, {DeclaredIndices}); + Append(words, 0x11, {1, Low(Records.data()), High(Records.data())}); + if (multi) Append(words, 0x38, {0, 0, 0, 0x40000280u, 3, Low(Count.data()), High(Count.data()), 32, 0}); + else Append(words, 0x25, {0, 0, 0, 0}); + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "sparse indexed draw submit failed"); + AgcDriverWaitIdle_nid_postfix(); + Require(Records[0] == 3 && Records[1] == 1 && Records[2] == first, "the GPU did not write the current indirect record"); + Require(!multi || Count[0] == 2, "the GPU did not write the indirect draw count"); + const auto covered = std::count_if(Reference.begin(), Reference.end(), [](std::byte value) { return value != Kept; }); + Require(covered != 0, "the reference triangle did not draw"); + Require(Actual == Reference, "the sparse indexed indirect draw differs from the reference triangle"); + std::cout << "sparse indices " << size << " bytes, " << (multi ? "counted multi" : "single") << ", " << (afterGap ? "after gap" : "before gap") << (sized ? "" : ", no index size") << " passed\n"; +} + +} + +int main(int argc, char** argv) { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + Register(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Register(PixelHeader, PixelCode.data(), sizeof(PixelCode), 1); + SparseIndices indices; + const std::string mode = argc == 2 ? argv[1] : ""; + if (mode == "--unreadable" || mode == "--no-size-unreadable" || mode == "--no-size-unreadable-record") { + bool refused = false; + try { + if (mode == "--unreadable") Run(indices, 2, false, false, true); + else if (mode == "--no-size-unreadable") Run(indices, 2, false, false, true, false); + else Run(indices, 2, true, false, false, false); + } catch (const std::runtime_error& error) { + const std::string message = error.what(); + refused = message.find("guest memory is not readable") != std::string::npos && message.find("+0x6)") != std::string::npos; + Require(refused, "the unreadable record did not fail on its actual six-byte index range: " + message); + } + Require(refused, "an indirect record reading the unmapped index page was accepted"); + try { AgcDriverShutdown_nid_postfix(); } catch (const std::exception&) {} + std::cout << "the unreadable indirect index range was refused\n"; + return 0; + } + const bool sized = mode != "--no-size"; + for (const auto size : {2u, 4u}) { + for (const auto multi : {false, true}) { + Run(indices, size, multi, false, false, sized, sized ? DeclaredIndices : 0u); + Run(indices, size, multi, true, false, sized, sized ? DeclaredIndices : 0u); + } + } + AgcDriverShutdown_nid_postfix(); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + try { AgcDriverShutdown_nid_postfix(); } catch (const std::exception&) {} + return 1; + } +} diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 6f94c272d1c6..a02f2dc13093 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -148,6 +148,7 @@ - [COPY_DATA from a register](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 0) copies the value the queue holds for a shader, context or user config register (`COUNT_SEL` two consecutive ones) and throws for a register with no value. A dispatch records `COMPUTE_DISPATCH_INITIATOR` and, for `DISPATCH_DIRECT`, `COMPUTE_DIM_X/Y/Z` in the shader registers; `DISPATCH_INDIRECT` clears the three counts. Neither the register values the CP writes nor the copy's ordering against in-flight dispatches have been measured on hardware. - [EVENT_WRITE stream-out statistics events](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcode 0x46) - `SAMPLE_STREAMOUTSTATS1`-`3` and `SAMPLE_STREAMOUTSTATS` (event types 0x01-0x03 and 0x20 in [navi10_enum.h](https://github.com/torvalds/linux/blob/0fcb05b72344f40806cbe810de8872f1698bf053/drivers/gpu/drm/amd/include/navi10_enum.h#L14155-L14188)) are accepted on the graphics queue as a 2-dword packet with event index 0 and do nothing. [nvd.h](https://github.com/torvalds/linux/blob/0fcb05b72344f40806cbe810de8872f1698bf053/drivers/gpu/drm/amd/amdgpu/nvd.h#L326-L347) documents these events only with the sampling index (3, or `EVENT_INDEX__SAMPLE_STREAMOUTSTATS*` 8-11 on gfx10); the index-0 packet has no destination dwords, and what the CP does with it is neither documented nor measured on hardware. AnyPS5 does not emulate the VGT stream-out counters, so there is nothing to sample. The sampling indices throw that sampling to memory is not implemented, and other indices, other sizes and the compute queue throw. [sceAgcDcbEventWrite](../../../core/libs/prx/libSceAgc/DcbFlow/src/Sync.cpp) ignores the address for every event type except 0x38 and 0x39, so a call with one of these event types and an address builds the index-0 packet: nothing is written and nothing throws. Whether the console's builder emits a sampling packet for an address is not known. - [INDEX_ATTRIBUTES_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcode 0x91) loads INDEX_BASE, INDEX_BUFFER_SIZE (in indices) and INDEX_TYPE from the 16-byte record at `attribute_base + attribute_index * 16`, read when the packet runs. The record layout (address low, address high, size, type) is PAL's [`IndexAttribIndirect`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9Chip.h#L60-L67) and the packet is the one [`BuildIndexAttributesIndirect`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9CmdUtil.cpp#L2586-L2613) writes; neither is measured on a PS5 or seen in a title. `indirect_mode` 1 (`addr_offset`, which only PAL's ExecuteIndirect path emits) throws, and so does a record whose index type is above 3, as INDEX_TYPE does. +- [Indexed indirect draws without an index limit](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcodes 0x25 and 0x38) - on a queue that has received neither INDEX_BUFFER_SIZE (0x13) nor INDEX_ATTRIBUTES_INDIRECT (0x91) since its last reset, the draw has no index limit: it takes the CPU record path ([IndirectDraw.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/IndirectDraw.cpp)), and [Draw.cpp](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp) reads each record's firstIndex and indexCount range as written, without clamping, and refuses a range that overflows or is not readable. An INDEX_BUFFER_SIZE of 0 that was written still throws. The console's initial INDEX_BUFFER_SIZE and whether DRAW_INDEX_2 or DRAW_INDEX_OFFSET_2 MAX_SIZE changes it are not measured. The suspend-point queue reset also clears a written size. PPSA08338 issues DRAW_INDEX_INDIRECT_MULTI this way without ever emitting either packet. - [16-bit RGBA color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) support the standard and the alternate (`COMP_SWAP` 1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, following `ac_translate_colorswap` in [Mesa 26.2.2 ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c). Blending into the reverse orders (`COMP_SWAP` 2 and 3) is covered by the reversed-order blending entry, and simultaneous targets requiring conflicting blend constants throw. Console equivalence has not been measured on hardware. - [UINT16_ABGR color exports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - export format 7 is accepted only into an unsigned integer target, and each 16-bit value reaches the target unchanged; a value beyond a narrower target's range (`R8_UINT`) is undefined in Vulkan, and whether the console clamps or truncates it is not confirmed. SINT16_ABGR (8) throws - [Layered color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - only 3D color targets with 64 KiB render-target or 4 KiB standard tiling render several slices: a `CB_COLOR_VIEW` slice range on an array target, a layered draw with a depth target, and color targets of different slice counts throw. The vertex layer export (`PA_CL_VS_OUT_CNTL` USE_VTX_RENDER_TARGET_INDX) reaches the shader only for a layered draw and is otherwise ignored, as before; a layer beyond the view's last slice is undefined in Vulkan, while the console's handling (clamping to `SLICE_MAX`) is not modeled, and a view whose last slice lies past the surface is clamped to its last slice. DCC keys of a 3D color target are taken as one key per 256 bytes over the whole surface, every slice included, and only uniform keys (all uncompressed, or one fast-clear code) are interpreted, as for 2D targets; mixed keys leave the stored texels as they are. The console's DCC key layout for 3D surfaces is not checked (LEGO Horizon Adventures' (PPSA14632) lighting volumes are DCC)