Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <memory>
#include <vector>

namespace AgcDriver::Graphics { class VertexStagePlan; }

namespace AgcDriver::DriverDetail {

struct DrawProgram {
Expand All @@ -23,6 +25,7 @@ struct DrawProgram {

std::shared_ptr<const ShaderSnapshot> snapshot;
std::size_t codeOffset = 0;
std::shared_ptr<const Graphics::VertexStagePlan> vertexInputs;
};

struct DrawDecode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<const Graphics::VertexStagePlan>(programs.back().binary.header, programs.back().binary.headerAddress);
roles.push_back(role);
};
const auto initializeMerged = [&](DrawProgram& program, std::uint32_t pointerBase, bool pointerRequired) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@ struct DecodeRead {
std::uint64_t address;
std::vector<std::byte> bytes;
};
class VertexStagePlan {
public:
VertexStagePlan(std::span<const std::byte> header, std::uint64_t headerAddress);
ShaderRecompiler::ShaderVertexStageInfo Read(std::span<const std::uint32_t> userData, std::vector<DecodeRead>* 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<Attribute> attributes;
std::vector<ReadRun> attributeReads;
};
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads = nullptr, bool staticAbi = false, std::uint32_t hiddenUserWords = 0);

}
Expand Down
110 changes: 69 additions & 41 deletions core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <limits>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
Expand Down Expand Up @@ -174,78 +176,100 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
};
}

ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads, bool staticAbi, std::uint32_t hiddenUserWords) {
if (header.empty()) return {};
VertexStagePlan::VertexStagePlan(std::span<const std::byte> 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<ShaderUserData>(header, headerAddress, shader.user_data, userDataBytes);
if (userDataHeader.direct_resource_count > ShaderRegs::AGC_DIRECT_RESOURCE_TYPE_COUNT) throw std::runtime_error("AGC graphics: AGC direct-resource count exceeds the known resource domain");
std::array<std::uint16_t, ShaderRegs::AGC_DIRECT_RESOURCE_TYPE_COUNT> 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<std::uint32_t>(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable)) vertexBufferReg = reg;
if (type == static_cast<std::uint32_t>(ShaderRegs::AgcDirectResourceType::PtrVertexAttribDescTable)) vertexAttribReg = reg;
if (type == static_cast<std::uint32_t>(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable)) bufferRegister = reg;
if (type == static_cast<std::uint32_t>(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<std::uint32_t>(vertexBufferReg) + hiddenUserWords;
const auto attribWord = static_cast<std::uint32_t>(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<ShaderSemantic, ShaderRecompiler::ShaderVertexStageInfo::MaxResources> semantics{};
_readHeaderArray(header, headerAddress, shader.input_semantics, shader.num_input_semantics, semantics.data());
attributes.reserve(shader.num_input_semantics);
std::array<std::uint8_t, ShaderRecompiler::ShaderVertexStageInfo::MaxResources> 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<std::uint8_t>(semantic.semantic), static_cast<std::uint8_t>(semantic.hardware_mapping), static_cast<std::uint8_t>(semantic.size_in_elements)});
indices[i] = static_cast<std::uint8_t>(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<std::uint16_t>(run.count, index - run.first + 1);
} else attributeReads.push_back({index, 1});
}
}

ShaderRecompiler::ShaderVertexStageInfo VertexStagePlan::Read(std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads, bool staticAbi, std::uint32_t hiddenUserWords) const {
ShaderRecompiler::ShaderVertexStageInfo info{};
if (attributeRegister < 0) return info;
const auto bufferWord = static_cast<std::uint32_t>(bufferRegister) + hiddenUserWords;
const auto attribWord = static_cast<std::uint32_t>(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<std::uint64_t>(userData[attribWord]) | (static_cast<std::uint64_t>(userData[attribWord + 1u]) << 32u);
const auto bufferTableAddr = static_cast<std::uint64_t>(userData[bufferWord]) | (static_cast<std::uint64_t>(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<std::uint32_t>(vertexAttribReg);
info.fetchBufferReg = static_cast<std::uint32_t>(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<std::uint32_t>(attributeRegister);
info.fetchBufferReg = static_cast<std::uint32_t>(bufferRegister);
if (staticAbi) {
for (const auto& attribute : attributes) {
auto& destination = info.resourcesDst.at(info.resourcesNum++);
destination.registerStart = static_cast<std::int32_t>(semantic.hardware_mapping);
destination.registersNum = static_cast<std::int32_t>(semantic.size_in_elements);
destination.attrId = static_cast<std::int32_t>(semantic.semantic);
continue;
destination.registerStart = attribute.destination;
destination.registersNum = attribute.components;
destination.attrId = attribute.semantic;
}
std::array<std::byte, 4> attribWordBytes{};
const auto attribWordAddress = attribTableAddr + static_cast<std::uint64_t>(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<std::byte> destination) {
if (offset > std::numeric_limits<std::uint64_t>::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<std::uint32_t, 256> 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<std::array<std::uint32_t, 4>, 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::uint32_t>(~(((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<std::byte, 16> sharpBytes{};
const auto sharpAddress = bufferTableAddr + static_cast<std::uint64_t>(index) * 16u;
AgcDriver::GuestMemory::Read(sharpAddress, sharpBytes, 4);
if (reads != nullptr) reads->push_back({sharpAddress, {sharpBytes.begin(), sharpBytes.end()}});
std::array<std::uint32_t, 4> 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<std::int32_t>(semantic.hardware_mapping);
destination.registersNum = static_cast<std::int32_t>(semantic.size_in_elements);
destination.attrId = static_cast<std::int32_t>(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;
Expand All @@ -266,4 +290,8 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const st
return info;
}

ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* reads, bool staticAbi, std::uint32_t hiddenUserWords) {
return VertexStagePlan(header, headerAddress).Read(userData, reads, staticAbi, hiddenUserWords);
}

}
Loading
Loading