fix(shader): allow fetch-shader calls at registration and test the fetched values

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-08 20:05:24 +02:00
parent c48e01977d
commit c3e16afddd
2 changed files with 171 additions and 2 deletions

View File

@ -314,8 +314,6 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(const ShaderSnapshot& snap
const auto codeOffset = static_cast<std::size_t>((address - snapshot.codeAddress) / 4u);
const auto code = std::span(snapshot.code).subspan(codeOffset);
const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code);
auto graph = ShaderRecompiler::GraphBuilder{}.Build(decoded);
ShaderRecompiler::Structurizer{}.Structurize(graph);
std::optional<ShaderRecompiler::ShaderComputeStageInfo> compute;
std::optional<ShaderRecompiler::ShaderPixelStageInfo> pixel;
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
@ -350,6 +348,9 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(const ShaderSnapshot& snap
}
std::vector<std::uint32_t> userData(userCount);
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, userData, nullptr, true);
const ShaderRecompiler::SwappcInfo swappc{vertex.has_value(), firstUser, userCount};
auto graph = ShaderRecompiler::GraphBuilder{}.Build(decoded, &swappc);
ShaderRecompiler::Structurizer{}.Structurize(graph);
const std::array<ShaderRecompiler::MemoryRegion, 2> memory{{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}};
ShaderRecompiler::RecompileRequest request{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, userData, compute, pixel, vertex, memory}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics};
if (graphics && graphics->mesh) request.layout.pushConstantSizeBytes = ShaderRecompiler::MeshDrawPushOffsetBytes;

View File

@ -0,0 +1,168 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Width = 64;
constexpr std::uint32_t Height = 32;
constexpr std::uint32_t IdentitySelect = 0xfacu;
constexpr std::uint32_t ReverseSelect = 0x977u;
alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
alignas(256) std::array<std::uint32_t, 12> Output{};
alignas(256) constexpr std::array<std::uint32_t, 7> VertexCode{
0xbe822100, 0x34020a84, 0xe0781000, 0x80010801, 0xf80008cf, 0x0b0a0908, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 4> PixelCode{
0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000,
};
constexpr std::array<std::array<float, 4>, 3> Triangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.25f, 1.0f}, {-1.0f, 3.0f, 0.75f, 1.0f}
}};
std::array<std::uint32_t, 4> VertexBufferDescriptor(std::uint32_t select) {
const auto address = reinterpret_cast<std::uintptr_t>(Triangle.data());
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (16u << 16u), static_cast<std::uint32_t>(Triangle.size()), 0x0104d000u | select};
}
std::array<std::uint32_t, 4> OutputDescriptor() {
const auto address = reinterpret_cast<std::uintptr_t>(Output.data());
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), static_cast<std::uint32_t>(sizeof(Output)), 0x31016facu};
}
ShaderRecompiler::RecompileRequest VertexRequest(const AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderVertexStageInfo& info, std::span<const ShaderRecompiler::MemoryRegion> memory) {
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Vertex, reinterpret_cast<std::uintptr_t>(VertexCode.data()), VertexCode, 0, {}},
{64u, 0, userData, std::nullopt, std::nullopt, info, memory},
device.Target(),
{0, 0, 0, 64}
};
request.useCache = false;
return request;
}
ShaderRecompiler::ShaderVertexStageInfo VertexInfo(std::uint32_t select, std::int32_t registers) {
ShaderRecompiler::ShaderVertexStageInfo info{};
info.resourcesNum = 1;
info.resources[0].fields = VertexBufferDescriptor(select);
info.resourcesDst[0] = {8, registers, 0, 0};
info.fetchAttribReg = 8;
info.fetchBufferReg = 10;
info.fetchEmbedded = true;
return info;
}
std::vector<std::uint32_t> UserData() {
std::vector<std::uint32_t> userData(12, 0u);
userData[0] = 0x10000000u;
userData[1] = 0x00000001u;
const auto output = OutputDescriptor();
std::copy(output.begin(), output.end(), userData.begin() + 4);
return userData;
}
void Draw(AgcDriver::VulkanDevice& device, std::uint32_t select) {
Pixels.fill(std::byte{0x40});
Output.fill(0xdeadbeefu);
const auto userData = UserData();
const std::array<ShaderRecompiler::MemoryRegion, 1> vertexMemory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
const auto vertexResult = ShaderRecompiler::Recompile(VertexRequest(device, userData, VertexInfo(select, 4), vertexMemory));
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
ShaderRecompiler::ShaderPixelStageInfo pixel{};
pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
pixel.posX = true;
pixel.posY = true;
pixel.targetOutputMode[0] = 9;
pixel.targetExportMapping.fill(0xe4u);
const std::vector<std::uint32_t> pixelUserData(8, 0u);
const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(PixelCode.data()), std::as_bytes(std::span(PixelCode))}}};
ShaderRecompiler::RecompileRequest fragment{
{ShaderStage::Fragment, reinterpret_cast<std::uintptr_t>(PixelCode.data()), PixelCode, 0, {}},
{64u, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory},
device.Target(),
{0, 0, vertexPush, 128 - vertexPush}
};
fragment.useCache = false;
const auto pixelResult = ShaderRecompiler::Recompile(fragment);
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{
{ShaderStage::Vertex, &vertexResult, 0},
{ShaderStage::Fragment, &pixelResult, vertexPush}
}};
AgcDriver::Graphics::State state{};
state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt};
state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
state.colors = {state.color};
state.hasColorTarget = true;
state.renderExtent = {Width, Height};
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.negativeOneToOne = false;
state.scissor = {{0, 0}, {Width, Height}};
state.cullMode = VK_CULL_MODE_NONE;
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
state.blend.colorWriteMask = 15;
state.blends = {state.blend};
state.blendConstants = {};
const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
device.Draw(state, draw, shaders);
device.WaitIdle();
}
void Check(std::uint32_t select, const std::string& what) {
for (std::uint32_t vertex = 0; vertex < Triangle.size(); ++vertex) {
for (std::uint32_t component = 0; component < 4u; ++component) {
const auto source = select == ReverseSelect ? 3u - component : component;
const auto expected = std::bit_cast<std::uint32_t>(Triangle[vertex][source]);
const auto actual = Output[vertex * 4u + component];
Require(actual == expected, what + ": vertex " + std::to_string(vertex) + " v" + std::to_string(8u + component) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected));
}
}
}
void CheckRegisterLimit(AgcDriver::VulkanDevice& device) {
const auto userData = UserData();
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}};
try {
static_cast<void>(ShaderRecompiler::Recompile(VertexRequest(device, userData, VertexInfo(IdentitySelect, 5), memory)));
} catch (const std::exception& error) {
Require(std::string(error.what()).find("the inline model supports one to four") != std::string::npos, std::string("fetch-shader register limit: unexpected error: ") + error.what());
return;
}
throw std::runtime_error("fetch-shader register limit: five destination registers compiled");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Draw(*device, IdentitySelect);
Check(IdentitySelect, "fetch-shader call, identity dst_sel");
Draw(*device, ReverseSelect);
Check(ReverseSelect, "fetch-shader call, reversed dst_sel");
CheckRegisterLimit(*device);
std::puts("scalar swappc fetch tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}