Files
AnyPS5/core/libs/prx/libSceAgcDriver/tests/PositionInvariance.cpp
T
Gjon Gojcevic 21f94288ac fix(shader): mark the vertex position output invariant
Two different vertex programs that compute the same position give
bit-identical depth on the GPU, which a depth pre-pass followed by an
equal-or-greater redraw relies on. Without Invariant, a Vulkan driver may
compile the same position arithmetic differently in each shader (for
example contracting into an FMA in only one), so the redraw fails the
depth test.

Decorate the gl_PerVertex Position member and the mesh shader's Position
output Invariant, and check both in a new recompiler test.

AI-assisted: yes
2026-10-09 17:53:07 -04:00

90 lines
3.8 KiB
C++

#include "Recompiler.hpp"
#include <spirv/unified1/spirv.hpp>
#include <array>
#include <cstdint>
#include <iostream>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
namespace {
void requireInvariantPositions(const ShaderRecompiler::RecompileResult& result, const char* what) {
constexpr std::uint32_t Variable = ~0u;
const auto& words = result.spirv.Words();
std::set<std::pair<std::uint32_t, std::uint32_t>> positions;
std::set<std::pair<std::uint32_t, std::uint32_t>> invariant;
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) {
const auto op = static_cast<spv::Op>(words[at] & 0xffffu);
const auto count = words[at] >> 16u;
if (op == spv::OpMemberDecorate && count >= 4) {
if (words[at + 3] == spv::DecorationBuiltIn && count == 5 && words[at + 4] == spv::BuiltInPosition) positions.emplace(words[at + 1], words[at + 2]);
if (words[at + 3] == spv::DecorationInvariant) invariant.emplace(words[at + 1], words[at + 2]);
}
if (op == spv::OpDecorate && count >= 3) {
if (words[at + 2] == spv::DecorationBuiltIn && count == 4 && words[at + 3] == spv::BuiltInPosition) positions.emplace(words[at + 1], Variable);
if (words[at + 2] == spv::DecorationInvariant) invariant.emplace(words[at + 1], Variable);
}
}
if (positions.empty()) throw std::runtime_error(std::string(what) + ": the module declares no position output");
for (const auto& position : positions) {
if (!invariant.contains(position)) throw std::runtime_error(std::string(what) + ": the position output is not Invariant");
}
}
constexpr std::array<std::uint32_t, 3> PositionExport{0xf80008cfu, 0u, 0xbf810000u};
ShaderRecompiler::RecompileRequest baseRequest(ShaderRecompiler::ShaderStage stage) {
using namespace ShaderRecompiler;
RecompileRequest request{};
request.shader = {stage, 0x10000u, PositionExport, 0, {}};
request.context.waveSize = 64;
request.context.vertex = ShaderVertexStageInfo{};
request.target.vulkanVersion = 0x00401000u;
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 64;
request.layout.pushConstantSizeBytes = 128;
request.useCache = false;
return request;
}
void verifyVertex() {
using namespace ShaderRecompiler;
requireInvariantPositions(Recompile(baseRequest(ShaderStage::Vertex)), "vertex shader");
}
void verifyMesh() {
using namespace ShaderRecompiler;
static constexpr std::array<std::uint32_t, 1> capabilities{spv::CapabilityMeshShadingEXT};
static constexpr std::array<std::string_view, 1> extensions{"SPV_EXT_mesh_shader"};
static constexpr std::array<std::uint32_t, 8> hiddenUserWords{};
auto request = baseRequest(ShaderStage::Mesh);
request.context.userData = hiddenUserWords;
request.target.spirvVersion = 0x00010400u;
request.target.supportedCapabilities = capabilities;
request.target.supportedExtensions = extensions;
request.target.maxWorkgroupSize = {1024u, 1024u, 64u};
request.target.maxWorkgroupInvocations = 1024u;
request.target.maxWorkgroupSharedMemoryBytes = 32768u;
request.target.mesh = MeshTargetLimits{{128u, 1u, 1u}, 128u, 32768u, 256u, 256u, 128u, 32768u, 1u, 1u};
request.graphics = GraphicsCompileContext{0u, {}, MeshConfiguration{4u, 1u, 3u, 3u, 1u, 64u, 128u, 0u, 4u}, std::nullopt, {}};
requireInvariantPositions(Recompile(request), "mesh shader");
}
} // namespace
int main() {
try {
verifyVertex();
verifyMesh();
std::cout << "Vertex and mesh position outputs are Invariant\n";
return 0;
} catch (const std::exception& error) {
const std::string message(error.what());
std::cerr << message.substr(0, message.find("RecompileRequest:")) << '\n';
return 1;
}
}