fix(agc): read a merged program's vertex tables past its hidden user words
A merged (NGG geometry path) program's user data starts with eight hidden words, so the vertex buffer and attribute table offsets of its AGC header, which count from the first user SGPR, were read eight words too early and found zeros. Killzone: Liberation (PPSA11142) stopped on `AGC graphics: null vertex buffer table address` at the first frame of its PSP game. The decoder now adds the hidden word count to the table reads; the registers reported to the recompiler are unchanged, as it already maps them from the first user SGPR. `agc_driver_graphics` gains a merged and a plain layout case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a11c8f7f70
commit
b63996f1ff
|
|
@ -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]);
|
||||
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr);
|
||||
};
|
||||
if (!registerKey) {
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i);
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
|
|||
if (firstUser == 0) userCount += 8;
|
||||
}
|
||||
plan->userData.resize(userCount);
|
||||
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, plan->userData, nullptr, true);
|
||||
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, plan->userData, nullptr, true, 8u - firstUser);
|
||||
plan->swappc = {vertex.has_value(), firstUser, userCount};
|
||||
if (deferred != nullptr) {
|
||||
if (const auto counter = ShaderRecompiler::UnresolvableSwappcTarget(plan->decoded, &plan->swappc)) {
|
||||
|
|
@ -805,7 +805,7 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
|
|||
const bool fragment = program.binary.stage == ShaderRecompiler::ShaderStage::Fragment;
|
||||
const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize;
|
||||
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
|
||||
if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true);
|
||||
if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr);
|
||||
pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots);
|
||||
const auto offset = pushOffset;
|
||||
const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ struct DecodeRead {
|
|||
std::uint64_t address;
|
||||
std::vector<std::byte> bytes;
|
||||
};
|
||||
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);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ 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) {
|
||||
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 {};
|
||||
if (header.size() < sizeof(Shader)) throw std::runtime_error("AGC graphics: shader header is smaller than the fixed AGC header");
|
||||
Shader shader;
|
||||
|
|
@ -185,14 +185,16 @@ ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const st
|
|||
}
|
||||
if (vertexAttribReg < 0) return info;
|
||||
if (vertexBufferReg < 0) throw std::runtime_error("AGC graphics: vertex attribute table requires a vertex buffer table");
|
||||
if (static_cast<std::uint32_t>(vertexBufferReg) + 1u >= userData.size()) throw std::runtime_error("AGC graphics: vertex buffer table pointer exceeds the user-SGPR domain");
|
||||
if (static_cast<std::uint32_t>(vertexAttribReg) + 1u >= userData.size()) throw std::runtime_error("AGC graphics: vertex attribute table pointer exceeds the user-SGPR domain");
|
||||
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 (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());
|
||||
const auto attribTableAddr = static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexAttribReg)]) | (static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexAttribReg) + 1u]) << 32u);
|
||||
const auto bufferTableAddr = static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexBufferReg)]) | (static_cast<std::uint64_t>(userData[static_cast<std::uint32_t>(vertexBufferReg) + 1u]) << 32u);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "SceShaders.hpp"
|
||||
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
|
||||
#include "ControlFlow/RequestSerializer.hpp"
|
||||
#include "CacheKey.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
|
|
@ -723,6 +724,43 @@ void ColorPipeBankXorTests() {
|
|||
}
|
||||
}
|
||||
|
||||
void HiddenUserWordVertexTablesTests() {
|
||||
constexpr std::size_t userDataOffset = 288;
|
||||
constexpr std::size_t directOffset = 352;
|
||||
constexpr std::size_t semanticOffset = 384;
|
||||
static std::vector<std::byte> header(semanticOffset + sizeof(ShaderSemantic));
|
||||
static std::array<std::uint32_t, 4> bufferTable{0x1000u, 0x20000u, 64u, 0x3000u};
|
||||
static std::array<std::uint32_t, 1> attribTable{(0x2cu << 5u)};
|
||||
std::array<std::uint16_t, ShaderRegs::AGC_DIRECT_RESOURCE_TYPE_COUNT> direct{};
|
||||
direct.fill(ShaderRegs::AGC_ILLEGAL_DIRECT_OFFSET);
|
||||
direct[static_cast<std::size_t>(ShaderRegs::AgcDirectResourceType::PtrVertexBufferTable)] = 4;
|
||||
direct[static_cast<std::size_t>(ShaderRegs::AgcDirectResourceType::PtrVertexAttribDescTable)] = 6;
|
||||
std::memcpy(header.data() + directOffset, direct.data(), sizeof(direct));
|
||||
ShaderSemantic semantic{};
|
||||
semantic.size_in_elements = 1;
|
||||
std::memcpy(header.data() + semanticOffset, &semantic, sizeof(semantic));
|
||||
ShaderUserData userData{};
|
||||
userData.direct_resource_offset = reinterpret_cast<std::uint16_t*>(header.data() + directOffset);
|
||||
userData.direct_resource_count = ShaderRegs::AGC_DIRECT_RESOURCE_TYPE_COUNT;
|
||||
std::memcpy(header.data() + userDataOffset, &userData, sizeof(userData));
|
||||
Shader shader{};
|
||||
shader.user_data = reinterpret_cast<ShaderUserData*>(header.data() + userDataOffset);
|
||||
shader.input_semantics = reinterpret_cast<ShaderSemantic*>(header.data() + semanticOffset);
|
||||
shader.num_input_semantics = 1;
|
||||
std::memcpy(header.data(), &shader, sizeof(shader));
|
||||
const auto headerAddress = reinterpret_cast<std::uintptr_t>(header.data());
|
||||
const auto low = [](const void* pointer) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer)); };
|
||||
const auto high = [](const void* pointer) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer) >> 32u); };
|
||||
const std::array<std::uint32_t, 8> registers{0x31016facu, 0x7efff390u, 0x100002u, 0x49u, low(bufferTable.data()), high(bufferTable.data()), low(attribTable.data()), high(attribTable.data())};
|
||||
std::vector<std::uint32_t> merged(8, 0u);
|
||||
merged.insert(merged.end(), registers.begin(), registers.end());
|
||||
const auto info = AgcDriver::Graphics::DecodeVertexStageInfo(header, headerAddress, merged, nullptr, false, 8);
|
||||
Require(info.fetchEmbedded && info.fetchBufferReg == 4u && info.fetchAttribReg == 6u && info.resourcesNum == 1 && info.resources[0].fields[0] == 0x1000u && info.resources[0].fields[2] == 64u, "a merged program's vertex tables were not read past its eight hidden user words");
|
||||
expectFailure([&] { static_cast<void>(AgcDriver::Graphics::DecodeVertexStageInfo(header, headerAddress, merged, nullptr, false, 0)); }, "null vertex attribute table");
|
||||
const auto plain = AgcDriver::Graphics::DecodeVertexStageInfo(header, headerAddress, registers, nullptr, false, 0);
|
||||
Require(plain.fetchEmbedded && plain.fetchBufferReg == 4u && plain.resourcesNum == 1 && plain.resources[0].fields[0] == 0x1000u, "a vertex program's tables moved");
|
||||
}
|
||||
|
||||
void HeaderlessVertexStageTests() {
|
||||
const std::array<std::uint32_t, 4> userData{1, 2, 3, 4};
|
||||
const auto info = AgcDriver::Graphics::DecodeVertexStageInfo({}, 0, userData);
|
||||
|
|
@ -4316,6 +4354,7 @@ int main(int argc, char** argv) {
|
|||
PixelInputLayoutTests();
|
||||
ColorPipeBankXorTests();
|
||||
HeaderlessVertexStageTests();
|
||||
HiddenUserWordVertexTablesTests();
|
||||
ComputeScratchTests();
|
||||
shaderUserDataTailPaddingTests();
|
||||
InitialContextTests();
|
||||
|
|
|
|||
Loading…
Reference in New Issue