Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1525,6 +1525,13 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
add_test(NAME agc_driver_sparse_indirect_indices_unreadable COMMAND agc_driver_sparse_indirect_indices_tests --unreadable)
set_tests_properties(agc_driver_sparse_indirect_indices agc_driver_sparse_indirect_indices_unreadable 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 PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <cstdlib>

Expand Down Expand Up @@ -28,6 +29,7 @@ std::optional<Graphics::IndirectDrawPath> 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 && !GuestMemory::Accessible(reinterpret_cast<const void*>(drawParameters.indexAddress), static_cast<std::size_t>(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);
Expand Down
3 changes: 0 additions & 3 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,13 +839,10 @@ DrawParameters resolveIndirectDraw(std::span<const std::uint32_t> 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<std::uint64_t>(queue.indexBufferSize) * indexSize;
require(bytes <= std::numeric_limits<std::size_t>::max() && bytes <= std::numeric_limits<std::uint64_t>::max() - queue.indexBase, "index range size overflow");
GuestMemory::CheckRange(reinterpret_cast<const void*>(queue.indexBase), static_cast<std::size_t>(bytes), indexSize);
draw.indexAddress = queue.indexBase;
draw.indexCount = queue.indexBufferSize;
draw.indexSize = indexSize;
Expand Down
2 changes: 1 addition & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, Count };
void CountIndirectDraw(IndirectDrawPath path, double readMs, bool rewritten = false);
const char* IndirectDrawPathName(IndirectDrawPath path);

Expand Down
2 changes: 1 addition & 1 deletion core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ constexpr std::array<const char*, PhaseCount> DrawPhaseNames{"validate", "vertex
enum SyncReason : std::size_t { SyncNone, SyncNotResident, SyncNoRecorder, SyncDisabled, SyncCopiedWrites, SyncLease, SyncCount };
constexpr std::array<const char*, SyncCount> SyncReasonNames{"none", "non-resident target", "no recorder", "disabled", "copied writes", "lease"};
constexpr std::size_t IndirectPathCount = static_cast<std::size_t>(IndirectDrawPath::Count);
constexpr std::array<const char*, IndirectPathCount> 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<const char*, IndirectPathCount> 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"};

// What became of one draw, for the totals.
struct DrawOutcome {
Expand Down
8 changes: 7 additions & 1 deletion core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,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<std::uint64_t>::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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#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 <algorithm>
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#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<std::uint32_t, 20> 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<std::uint32_t, 8> PixelCode{0x7e000200u, 0x7e020280u, 0xf800180fu, 0x00010100u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 9> WriterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0x7e020203u, 0x7e040204u, 0xe07c0000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::byte, 1024> Reference{};
alignas(256) std::array<std::byte, 1024> Actual{};
alignas(256) std::array<std::uint32_t, 24> Records{};
alignas(256) std::array<std::uint32_t, 4> RecordDescriptor{};
alignas(256) std::array<std::uint32_t, 4> Count{};
alignas(256) std::array<std::uint32_t, 4> CountDescriptor{};

class SparseIndices {
public:
SparseIndices() {
#ifdef _WIN32
SYSTEM_INFO info{};
GetSystemInfo(&info);
page = info.dwPageSize;
memory = static_cast<std::byte*>(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<std::size_t>(hostPage);
auto* allocation = mmap(nullptr, ReservationBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(allocation != MAP_FAILED, "index reservation failed");
memory = static_cast<std::byte*>(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<std::uint32_t>((page + 16) / size); }

void Fill(std::uint32_t size) {
const std::array<std::uint16_t, 3> small{0, 1, 2};
const std::array<std::uint32_t, 3> large{0, 1, 2};
const void* source = size == 2 ? static_cast<const void*>(small.data()) : static_cast<const void*>(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<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer)); }
std::uint32_t High(const void* pointer) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer) >> 32u); }

void Append(std::vector<std::uint32_t>& words, std::uint32_t opcode, std::initializer_list<std::uint32_t> payload) {
words.push_back(0xc0000000u | (static_cast<std::uint32_t>(payload.size() - 1) << 16u) | (opcode << 8u));
words.insert(words.end(), payload);
}

void WriteArguments(std::vector<std::uint32_t>& words, const std::uint32_t* descriptor, std::array<std::uint32_t, 3> values) {
const auto address = reinterpret_cast<std::uintptr_t>(WriterCode.data());
Append(words, 0x76, {0x20c, static_cast<std::uint32_t>(address >> 8u)});
Append(words, 0x76, {0x20d, static_cast<std::uint32_t>(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<std::uint32_t>(bytes);
header.shader.code = code;
header.shader.type = type;
header.shader.user_data = &header.userData;
AgcDriverRegisterShader_nid_postfix(&header.shader);
}

std::vector<std::uint32_t> State() {
const auto vertex = reinterpret_cast<std::uintptr_t>(VertexCode.data());
const auto pixel = reinterpret_cast<std::uintptr_t>(PixelCode.data());
const std::vector<std::pair<std::uint32_t, std::uint32_t>> 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<std::uint32_t>(32.0f)}, {0x110, std::bit_cast<std::uint32_t>(32.0f)},
{0x111, std::bit_cast<std::uint32_t>(-2.0f)}, {0x112, std::bit_cast<std::uint32_t>(2.0f)},
{0x113, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0}, {0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)},
{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0},
};
std::vector<std::uint32_t> 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<std::uint32_t>(vertex >> 8u)});
Append(words, 0x76, {0xc9, static_cast<std::uint32_t>(vertex >> 40u)});
Append(words, 0x76, {0x8b, 2});
Append(words, 0x76, {0x8c, 0});
Append(words, 0x76, {0x008, static_cast<std::uint32_t>(pixel >> 8u)});
Append(words, 0x76, {0x009, static_cast<std::uint32_t>(pixel >> 40u)});
Append(words, 0x76, {0x00b, 2});
Append(words, 0x76, {0x00c, std::bit_cast<std::uint32_t>(1.0f)});
return words;
}

void Target(std::vector<std::uint32_t>& words, void* pointer) {
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
Append(words, 0x69, {0x318, static_cast<std::uint32_t>(address >> 8u)});
Append(words, 0x69, {0x390, static_cast<std::uint32_t>(address >> 40u)});
}

void Run(SparseIndices& indices, std::uint32_t size, bool multi, bool afterGap, bool unreadable) {
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] = DeclaredIndices;
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())});
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<std::uint32_t>(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") << " 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;
if (argc == 2 && std::string(argv[1]) == "--unreadable") {
bool refused = false;
try {
Run(indices, 2, false, false, true);
} 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;
}
for (const auto size : {2u, 4u}) {
for (const auto multi : {false, true}) {
Run(indices, size, multi, false, false);
Run(indices, size, multi, true, false);
}
}
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;
}
}
Loading