diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 371ccc8c20e..23b723da783 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -380,6 +380,17 @@ target_link_libraries(agc_shared_write_tests PRIVATE libSceAgcDriver libkernel l set_target_properties(agc_shared_write_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_shared_write_tests) +add_test_executable(agc_vertex_stage_tests EXCLUDE_FROM_ALL tests/VertexStage.cpp) +target_include_directories(agc_vertex_stage_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include) +target_link_libraries(agc_vertex_stage_tests PRIVATE libSceAgcDriver libc) +set_target_properties(agc_vertex_stage_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_vertex_stage_tests) +if(BUILD_TESTING) + set_target_properties(agc_vertex_stage_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_vertex_stage COMMAND agc_vertex_stage_tests) + set_tests_properties(agc_vertex_stage PROPERTIES TIMEOUT 30) +endif() + add_test_executable(agc_write_tracking_tests EXCLUDE_FROM_ALL tests/WriteTracking.cpp Execution/src/GuestMemory.cpp diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp index 5fa2834621b..983f8b94dcc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -12,6 +12,8 @@ #include #include +namespace AgcDriver::Graphics { class VertexStagePlan; } + namespace AgcDriver::DriverDetail { struct DrawProgram { @@ -23,6 +25,7 @@ struct DrawProgram { std::shared_ptr snapshot; std::size_t codeOffset = 0; + std::shared_ptr vertexInputs; }; struct DrawDecode { 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 dafff997f9b..f3d0a1fc0f4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -144,7 +144,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe const auto& program = programs[i]; if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return; decodeReads[i].clear(); - vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr); + vertexInfos[i] = program.vertexInputs->Read(program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr); if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207)); }; if (!registerKey) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index 590cf275098..50f3d63ead9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -61,6 +61,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const roles.reserve(5); const auto append = [&](std::uint32_t base, std::uint8_t type, Stage stage, std::uint32_t resources, std::uint32_t users, Role role) { programs.push_back(prepare(programAddress(base), type, stage, resources, users)); + if (role != Role::GeometryBack && role != Role::Fragment) programs.back().vertexInputs = std::make_shared(programs.back().binary.header, programs.back().binary.headerAddress); roles.push_back(role); }; const auto initializeMerged = [&](DrawProgram& program, std::uint32_t pointerBase, bool pointerRequired) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp index 6ae3f8ab60b..8683bfbf749 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp @@ -19,6 +19,26 @@ struct DecodeRead { std::uint64_t address; std::vector bytes; }; +class VertexStagePlan { +public: + VertexStagePlan(std::span header, std::uint64_t headerAddress); + ShaderRecompiler::ShaderVertexStageInfo Read(std::span userData, std::vector* reads = nullptr, bool staticAbi = false, std::uint32_t hiddenUserWords = 0) const; + +private: + struct Attribute { + std::uint8_t semantic; + std::uint8_t destination; + std::uint8_t components; + }; + struct ReadRun { + std::uint16_t first; + std::uint16_t count; + }; + std::int32_t bufferRegister = -1; + std::int32_t attributeRegister = -1; + std::vector attributes; + std::vector attributeReads; +}; ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span header, std::uint64_t headerAddress, std::span userData, std::vector* reads = nullptr, bool staticAbi = false, std::uint32_t hiddenUserWords = 0); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 61cfa6b4204..f2fa1e48045 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -5,6 +5,8 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include #include +#include +#include #include #include #include @@ -174,12 +176,11 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con }; } -ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span header, std::uint64_t headerAddress, std::span userData, std::vector* reads, bool staticAbi, std::uint32_t hiddenUserWords) { - if (header.empty()) return {}; +VertexStagePlan::VertexStagePlan(std::span header, std::uint64_t headerAddress) { + if (header.empty()) return; if (header.size() < sizeof(Shader)) throw std::runtime_error("AGC graphics: shader header is smaller than the fixed AGC header"); Shader shader; std::memcpy(&shader, header.data(), sizeof(Shader)); - ShaderRecompiler::ShaderVertexStageInfo info{}; if (shader.user_data == nullptr) throw std::runtime_error("AGC graphics: missing AGC user-data header"); constexpr auto userDataBytes = offsetof(ShaderUserData, sharp_resource_count) + sizeof(ShaderUserData{}.sharp_resource_count); const auto userDataHeader = _readHeaderPod(header, headerAddress, shader.user_data, userDataBytes); @@ -187,65 +188,88 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span directOffsets{}; directOffsets.fill(ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET); if (userDataHeader.direct_resource_count != 0) _readHeaderArray(header, headerAddress, userDataHeader.direct_resource_offset, userDataHeader.direct_resource_count, directOffsets.data()); - std::int32_t vertexBufferReg = -1; - std::int32_t vertexAttribReg = -1; for (std::uint32_t type = 0; type < userDataHeader.direct_resource_count; ++type) { const auto reg = directOffsets[type]; if (reg == ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET) continue; - if (type == static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable)) vertexBufferReg = reg; - if (type == static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexAttribDescTable)) vertexAttribReg = reg; + if (type == static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable)) bufferRegister = reg; + if (type == static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexAttribDescTable)) attributeRegister = reg; } - if (vertexAttribReg < 0) return info; - if (vertexBufferReg < 0) throw std::runtime_error("AGC graphics: vertex attribute table requires a vertex buffer table"); - const auto bufferWord = static_cast(vertexBufferReg) + hiddenUserWords; - const auto attribWord = static_cast(vertexAttribReg) + hiddenUserWords; - if (bufferWord + 1u >= userData.size()) throw std::runtime_error("AGC graphics: vertex buffer table pointer exceeds the user-SGPR domain"); - if (attribWord + 1u >= userData.size()) throw std::runtime_error("AGC graphics: vertex attribute table pointer exceeds the user-SGPR domain"); + if (attributeRegister < 0) return; + if (bufferRegister < 0) throw std::runtime_error("AGC graphics: vertex attribute table requires a vertex buffer table"); if (shader.num_input_semantics == 0 || shader.num_input_semantics > ShaderRecompiler::ShaderVertexStageInfo::MaxResources) throw std::runtime_error("AGC graphics: vertex semantic count is outside the supported domain"); if (shader.input_semantics == nullptr) throw std::runtime_error("AGC graphics: missing vertex input semantics"); std::array semantics{}; _readHeaderArray(header, headerAddress, shader.input_semantics, shader.num_input_semantics, semantics.data()); + attributes.reserve(shader.num_input_semantics); + std::array indices; + for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) { + const auto& semantic = semantics[i]; + if (semantic.static_vb_index == 1 || semantic.static_attribute == 1) throw std::runtime_error("AGC graphics: statically bound vertex attributes are not implemented"); + attributes.push_back({static_cast(semantic.semantic), static_cast(semantic.hardware_mapping), static_cast(semantic.size_in_elements)}); + indices[i] = static_cast(semantic.semantic); + } + const auto sorted = std::span(indices).first(shader.num_input_semantics); + std::ranges::sort(sorted); + for (const auto index : sorted) { + if (!attributeReads.empty() && index <= attributeReads.back().first + attributeReads.back().count) { + auto& run = attributeReads.back(); + run.count = std::max(run.count, index - run.first + 1); + } else attributeReads.push_back({index, 1}); + } +} + +ShaderRecompiler::ShaderVertexStageInfo VertexStagePlan::Read(std::span userData, std::vector* reads, bool staticAbi, std::uint32_t hiddenUserWords) const { + ShaderRecompiler::ShaderVertexStageInfo info{}; + if (attributeRegister < 0) return info; + const auto bufferWord = static_cast(bufferRegister) + hiddenUserWords; + const auto attribWord = static_cast(attributeRegister) + hiddenUserWords; + if (bufferWord + 1u >= userData.size()) throw std::runtime_error("AGC graphics: vertex buffer table pointer exceeds the user-SGPR domain"); + if (attribWord + 1u >= userData.size()) throw std::runtime_error("AGC graphics: vertex attribute table pointer exceeds the user-SGPR domain"); const auto attribTableAddr = static_cast(userData[attribWord]) | (static_cast(userData[attribWord + 1u]) << 32u); const auto bufferTableAddr = static_cast(userData[bufferWord]) | (static_cast(userData[bufferWord + 1u]) << 32u); if (!staticAbi && attribTableAddr == 0) throw std::runtime_error("AGC graphics: null vertex attribute table address"); if (!staticAbi && bufferTableAddr == 0) throw std::runtime_error("AGC graphics: null vertex buffer table address"); info.fetchEmbedded = true; - info.fetchAttribReg = static_cast(vertexAttribReg); - info.fetchBufferReg = static_cast(vertexBufferReg); - for (std::uint32_t i = 0; i < shader.num_input_semantics; ++i) { - const auto& semantic = semantics[i]; - if (semantic.static_vb_index == 1 || semantic.static_attribute == 1) throw std::runtime_error("AGC graphics: statically bound vertex attributes are not implemented"); - if (staticAbi) { + info.fetchAttribReg = static_cast(attributeRegister); + info.fetchBufferReg = static_cast(bufferRegister); + if (staticAbi) { + for (const auto& attribute : attributes) { auto& destination = info.resourcesDst.at(info.resourcesNum++); - destination.registerStart = static_cast(semantic.hardware_mapping); - destination.registersNum = static_cast(semantic.size_in_elements); - destination.attrId = static_cast(semantic.semantic); - continue; + destination.registerStart = attribute.destination; + destination.registersNum = attribute.components; + destination.attrId = attribute.semantic; } - std::array attribWordBytes{}; - const auto attribWordAddress = attribTableAddr + static_cast(semantic.semantic) * 4u; - AgcDriver::GuestMemory::Read(attribWordAddress, attribWordBytes, 4); - if (reads != nullptr) reads->push_back({attribWordAddress, {attribWordBytes.begin(), attribWordBytes.end()}}); - std::uint32_t attribWord; - std::memcpy(&attribWord, attribWordBytes.data(), 4); + return info; + } + const auto readTable = [&](std::uint64_t base, std::uint64_t offset, std::span destination) { + if (offset > std::numeric_limits::max() - base) throw std::runtime_error("AGC graphics: vertex table address overflow"); + const auto address = base + offset; + AgcDriver::GuestMemory::Read(address, destination, 4); + if (reads != nullptr) reads->push_back({address, {destination.begin(), destination.end()}}); + }; + std::array attributeWords; + for (const auto& run : attributeReads) readTable(attribTableAddr, run.first * 4u, std::as_writable_bytes(std::span(attributeWords).subspan(run.first, run.count))); + std::uint32_t buffers = 0; + for (const auto& attribute : attributes) buffers |= 1u << (attributeWords[attribute.semantic] & 0x1fu); + std::array, ShaderRecompiler::ShaderVertexStageInfo::MaxResources> bufferWords; + while (buffers != 0) { + const auto first = std::countr_zero(buffers); + const auto count = std::countr_one(buffers >> first); + readTable(bufferTableAddr, first * 16u, std::as_writable_bytes(std::span(bufferWords).subspan(first, count))); + buffers &= static_cast(~(((std::uint64_t{1} << count) - 1) << first)); + } + for (const auto& attribute : attributes) { + const auto attribWord = attributeWords[attribute.semantic]; const auto index = attribWord & 0x1fu; const auto format = (attribWord >> 5u) & 0x1ffu; const auto offset = (attribWord >> 14u) & 0xfffu; const auto fetchIndex = (attribWord >> 26u) & 0x1u; - if (index >= ShaderRecompiler::ShaderVertexStageInfo::MaxResources) throw std::runtime_error("AGC graphics: vertex buffer index exceeds the supported domain"); - std::array sharpBytes{}; - const auto sharpAddress = bufferTableAddr + static_cast(index) * 16u; - AgcDriver::GuestMemory::Read(sharpAddress, sharpBytes, 4); - if (reads != nullptr) reads->push_back({sharpAddress, {sharpBytes.begin(), sharpBytes.end()}}); - std::array sharp{}; - std::memcpy(sharp.data(), sharpBytes.data(), 16); - if (info.resourcesNum >= ShaderRecompiler::ShaderVertexStageInfo::MaxResources) throw std::runtime_error("AGC graphics: vertex resource count exceeds the supported domain"); auto& resource = info.resources[info.resourcesNum]; auto& destination = info.resourcesDst[info.resourcesNum]; - resource.fields = sharp; - destination.registerStart = static_cast(semantic.hardware_mapping); - destination.registersNum = static_cast(semantic.size_in_elements); - destination.attrId = static_cast(semantic.semantic); + resource.fields = bufferWords[index]; + destination.registerStart = attribute.destination; + destination.registersNum = attribute.components; + destination.attrId = attribute.semantic; destination.fetchIndex = fetchIndex; if (format != 0u) { const auto bufferFormat = format >> 2u; @@ -266,4 +290,8 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span header, std::uint64_t headerAddress, std::span userData, std::vector* reads, bool staticAbi, std::uint32_t hiddenUserWords) { + return VertexStagePlan(header, headerAddress).Read(userData, reads, staticAbi, hiddenUserWords); +} + } diff --git a/core/libs/prx/libSceAgcDriver/tests/VertexStage.cpp b/core/libs/prx/libSceAgcDriver/tests/VertexStage.cpp new file mode 100644 index 00000000000..3234d74f0b4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/VertexStage.cpp @@ -0,0 +1,261 @@ +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "SceShaders.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__linux__) +#include +#include +#endif + +namespace { + +using namespace AgcDriver::Graphics; +using Info = ShaderRecompiler::ShaderVertexStageInfo; +constexpr auto bufferType = static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable); +constexpr auto attributeType = static_cast(ShaderRegs::AgcDirectResourceType::PtrVertexAttribDescTable); + +void require(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +template void fails(TAction&& action) { + try { action(); } catch (const std::runtime_error&) { return; } + throw std::runtime_error("invalid vertex table was accepted"); +} + +struct Header { + Shader shader{}; + ShaderUserData user{}; + std::array offsets; + std::array semantics{}; + + Header() { + shader.user_data = &user; + shader.input_semantics = semantics.data(); + shader.num_input_semantics = 1; + user.direct_resource_count = offsets.size(); + user.direct_resource_offset = offsets.data(); + offsets.fill(ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET); + offsets[bufferType] = 0; + offsets[attributeType] = 2; + semantics[0].size_in_elements = 4; + } + Header(const Header&) = delete; + Header& operator=(const Header&) = delete; + VertexStagePlan plan() const { + return {std::as_bytes(std::span(this, 1)), reinterpret_cast(this)}; + } +}; + +struct Tables { + std::array attributes{}; + std::array, 32> buffers{}; + Tables() { + GuestAllocations::Mutation mutation; + mutation.Add(this, sizeof(*this), true, true, true); + } + ~Tables() { GuestAllocations::Mutation mutation; mutation.Remove(this); } + Tables(const Tables&) = delete; + Tables& operator=(const Tables&) = delete; +}; + +std::array roots(std::uint64_t buffers, std::uint64_t attributes) { + return {static_cast(buffers), static_cast(buffers >> 32), static_cast(attributes), static_cast(attributes >> 32)}; +} + +auto roots(const Tables& tables) { + return roots(reinterpret_cast(tables.buffers.data()), reinterpret_cast(tables.attributes.data())); +} + +void expectBytes(const Header& header, const Tables& tables, std::span reads) { + std::map expected, actual; + const auto add = [](auto& result, const void* pointer, std::size_t bytes) { + const auto* data = static_cast(pointer); + for (std::size_t i = 0; i < bytes; ++i) result[reinterpret_cast(data + i)] = data[i]; + }; + for (unsigned i = 0; i < header.shader.num_input_semantics; ++i) { + const auto* attribute = &tables.attributes[header.semantics[i].semantic]; + add(expected, attribute, 4); + add(expected, tables.buffers[*attribute & 31].data(), 16); + } + for (const auto& read : reads) { + for (std::size_t i = 0; i < read.bytes.size(); ++i) { + require(actual.emplace(read.address + i, read.bytes[i]).second, "descriptor byte was read twice"); + } + } + require(actual == expected, "decode observed unrelated or missing bytes"); +} + +void liveInputs() { + Header header; + header.shader.num_input_semantics = 5; + const std::array ids{255, 2, 1, 2, 128}; + for (unsigned i = 0; i < ids.size(); ++i) { + header.semantics[i].semantic = ids[i]; + header.semantics[i].hardware_mapping = i * 4; + header.semantics[i].size_in_elements = i % 4 + 1; + } + const auto plan = header.plan(); + std::array staticWords{}; + std::vector staticReads; + const auto staticInfo = plan.Read(staticWords, &staticReads, true, 8); + require(staticInfo.resourcesNum == ids.size() && staticReads.empty(), "static ABI touched live tables"); + require(staticInfo.resourcesDst[0].attrId == 255 && staticInfo.fetchAttribReg == 2, "static ABI changed semantics"); + Tables first, second; + std::mt19937 random(18243); + for (unsigned pass = 0; pass < 128; ++pass) { + auto& tables = pass % 2 ? first : second; + for (auto& word : tables.attributes) word = random() % 32; + for (auto& buffer : tables.buffers) for (auto& word : buffer) word = random(); + std::vector reads; + const auto info = plan.Read(roots(tables), &reads); + require(info.resourcesNum == ids.size() && info.fetchEmbedded && info.fetchBufferReg == 0 && info.fetchAttribReg == 2, "vertex metadata changed"); + for (unsigned i = 0; i < ids.size(); ++i) { + require(info.resources[i].fields == tables.buffers[tables.attributes[ids[i]]], "changed descriptor or root was not read"); + const auto& destination = info.resourcesDst[i]; + require(destination.attrId == static_cast(ids[i]) && destination.registerStart == static_cast(i * 4) && destination.registersNum == static_cast(i % 4 + 1), "semantic order or duplicates changed"); + } + expectBytes(header, tables, reads); + } + std::array hidden{}; + const auto direct = roots(first); + std::copy(direct.begin(), direct.end(), hidden.begin() + 8); + const auto hiddenInfo = plan.Read(hidden, nullptr, false, 8); + require(hiddenInfo.resources[0].fields == plan.Read(direct).resources[0].fields, "merged hidden user words changed table roots"); + header.semantics.fill({}); + require(plan.Read(roots(first)).resourcesDst[0].attrId == 255, "plan retained mutable header pointers"); +} + +void fullTableAndFormats() { + Header header; + Tables tables; + header.shader.num_input_semantics = 32; + for (unsigned i = 0; i < 32; ++i) { + header.semantics[i].semantic = 31 - i; + tables.attributes[i] = i; + tables.buffers[i] = {0xfffffff0u, 0x00200001u, 100u + i, 0x12345678u}; + } + const auto plan = header.plan(); + std::vector reads; + auto info = plan.Read(roots(tables), &reads); + require(info.resourcesNum == 32 && reads.size() == 2 && reads[0].bytes.size() == 128 && reads[1].bytes.size() == 512, "full buffer mask did not form two exact reads"); + expectBytes(header, tables, reads); + for (unsigned i = 0; i < 32; ++i) require(info.resources[i].fields == tables.buffers[31 - i], "full table changed result ordering"); + tables.attributes[31] |= ((77u * 4u + 3u) << 5u) | (32u << 14u) | (1u << 26u); + info = plan.Read(roots(tables)); + require(info.resources[0].fields == std::array{0x10u, 0x00200002u, 131u, 0x1234dfacu}, "live format, channels, or address carry was lost"); + require(info.resourcesDst[0].fetchIndex == 1, "live instance fetch index was lost"); +} + +void invalidInputs() { + Header header; + Tables tables; + auto user = roots(tables); + const auto valid = header.plan(); + fails([&] { valid.Read(std::span(user).first(1)); }); + fails([&] { valid.Read(std::span(user).first(3)); }); + fails([&] { valid.Read(roots(0, 1)); }); + fails([&] { valid.Read(roots(1, 0)); }); + header.shader.user_data = nullptr; + fails([&] { header.plan(); }); + header.shader.user_data = reinterpret_cast(std::numeric_limits::max() - 7); + fails([&] { header.plan(); }); + header.shader.user_data = &header.user; + header.user.direct_resource_count = header.offsets.size() + 1; + fails([&] { header.plan(); }); + header.user.direct_resource_count = header.offsets.size(); + header.user.direct_resource_offset = nullptr; + fails([&] { header.plan(); }); + header.user.direct_resource_offset = header.offsets.data(); + header.offsets[bufferType] = ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET; + fails([&] { header.plan(); }); + header.offsets[bufferType] = 0; + for (const auto count : {0u, 33u}) { + header.shader.num_input_semantics = count; + fails([&] { header.plan(); }); + } + header.shader.num_input_semantics = 1; + header.shader.input_semantics = nullptr; + fails([&] { header.plan(); }); + header.shader.input_semantics = header.semantics.data(); + header.semantics[0].static_vb_index = 1; + fails([&] { header.plan(); }); + header.semantics[0].static_vb_index = 0; + header.semantics[0].static_attribute = 1; + fails([&] { header.plan(); }); + header.semantics[0].static_attribute = 0; + header.semantics[0].semantic = 255; + const auto high = header.plan(); + fails([&] { high.Read(roots(1, std::numeric_limits::max() - 3)); }); + header.offsets[attributeType] = ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET; + require(header.plan().Read({}).resourcesNum == 0, "shader without attribute table requires roots"); + require(VertexStagePlan({}, 0).Read({}).resourcesNum == 0, "raw shader requires an AGC header"); +} + +void parallelReads() { + Header header; + const auto plan = header.plan(); + std::atomic okay{true}; + std::array threads; + for (unsigned i = 0; i < threads.size(); ++i) threads[i] = std::thread([&, i] { + Tables tables; + for (unsigned n = 0; n < 256; ++n) { + tables.attributes[0] = (n + i) % 32; + tables.buffers[tables.attributes[0]][2] = n + 1024 * i; + if (plan.Read(roots(tables)).resources[0].fields[2] != n + 1024 * i) okay = false; + } + }); + for (auto& thread : threads) thread.join(); + require(okay, "shared plan mixed concurrent live inputs"); +} + +void guardBoundary() { +#if defined(__linux__) + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + auto* memory = static_cast(mmap(nullptr, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + require(memory != MAP_FAILED, "guard allocation failed"); + require(mprotect(memory + page, page, PROT_NONE) == 0, "guard protection failed"); + { GuestAllocations::Mutation mutation; mutation.Add(memory, page, true, true, true); } + Header header; + const auto plan = header.plan(); + const std::array descriptor{0x1000, 0x00200000, 127, 0}; + std::memcpy(memory + page - 16, descriptor.data(), 16); + std::uint32_t attribute = 31; + std::memcpy(memory, &attribute, 4); + const auto bufferBase = reinterpret_cast(memory + page - 512); + require(plan.Read(roots(bufferBase, reinterpret_cast(memory))).resources[0].fields == descriptor, "last buffer descriptor failed"); + attribute = 0; + std::memcpy(memory + page - 4, &attribute, 4); + std::memcpy(memory, descriptor.data(), 16); + require(plan.Read(roots(reinterpret_cast(memory), reinterpret_cast(memory + page - 4))).resources[0].fields == descriptor, "last attribute word failed"); + { GuestAllocations::Mutation mutation; mutation.Remove(memory); } + munmap(memory, page * 2); +#endif +} + +} + +int main() { + try { + liveInputs(); + fullTableAndFormats(); + invalidInputs(); + parallelReads(); + guardBoundary(); + std::cout << "vertex stage plans passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +}