mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 17:13:13 -05:00
167 lines
8.6 KiB
C++
167 lines
8.6 KiB
C++
#include "Recompiler.hpp"
|
|
#include "PipelineSpecialization.hpp"
|
|
#include "Optimization/ResourceProgram.hpp"
|
|
#include <spirv/unified1/spirv.hpp>
|
|
#include <array>
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
struct ModuleFacts {
|
|
std::map<std::uint32_t, std::uint32_t> specializationDefaults;
|
|
std::uint32_t clipDistanceLength = 0;
|
|
bool clipDistanceBuiltIn = false;
|
|
std::size_t multiplies = 0;
|
|
bool pushConstantVariable = false;
|
|
std::set<std::uint32_t> pushDwordLoads;
|
|
};
|
|
|
|
ModuleFacts inspect(std::span<const std::uint32_t> words) {
|
|
ModuleFacts facts;
|
|
std::map<std::uint32_t, std::uint32_t> specIds;
|
|
std::map<std::uint32_t, std::uint32_t> specValues;
|
|
std::map<std::uint32_t, std::uint32_t> constants;
|
|
std::map<std::uint32_t, std::uint32_t> arrayLengths;
|
|
std::map<std::uint32_t, std::uint32_t> pointerTargets;
|
|
std::map<std::uint32_t, std::uint32_t> variableTypes;
|
|
std::set<std::uint32_t> clipVariables;
|
|
std::set<std::uint32_t> pushVariables;
|
|
std::vector<std::uint32_t> pushIndexIds;
|
|
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::OpDecorate && count == 4 && words[at + 2] == spv::DecorationSpecId) specIds[words[at + 1]] = words[at + 3];
|
|
if (op == spv::OpDecorate && count == 4 && words[at + 2] == spv::DecorationBuiltIn && words[at + 3] == spv::BuiltInClipDistance) clipVariables.insert(words[at + 1]);
|
|
if (op == spv::OpSpecConstant && count == 4) specValues[words[at + 2]] = words[at + 3];
|
|
if (op == spv::OpConstant && count == 4) constants[words[at + 2]] = words[at + 3];
|
|
if (op == spv::OpTypeArray && count == 4) arrayLengths[words[at + 1]] = words[at + 3];
|
|
if (op == spv::OpTypePointer && count == 4) pointerTargets[words[at + 1]] = words[at + 3];
|
|
if (op == spv::OpVariable && count >= 4) variableTypes[words[at + 2]] = words[at + 1];
|
|
if (op == spv::OpVariable && count >= 4 && words[at + 3] == spv::StorageClassPushConstant) pushVariables.insert(words[at + 2]);
|
|
if (op == spv::OpAccessChain && count == 6 && pushVariables.contains(words[at + 3])) pushIndexIds.push_back(words[at + 5]);
|
|
if (op == spv::OpFMul) ++facts.multiplies;
|
|
}
|
|
for (const auto& [id, specId] : specIds) {
|
|
if (const auto value = specValues.find(id); value != specValues.end()) facts.specializationDefaults[specId] = value->second;
|
|
}
|
|
facts.pushConstantVariable = !pushVariables.empty();
|
|
for (const auto id : pushIndexIds) {
|
|
if (const auto value = constants.find(id); value != constants.end()) facts.pushDwordLoads.insert(value->second);
|
|
}
|
|
for (const auto variable : clipVariables) {
|
|
facts.clipDistanceBuiltIn = true;
|
|
const auto pointer = variableTypes.find(variable);
|
|
if (pointer == variableTypes.end()) continue;
|
|
const auto array = pointerTargets.find(pointer->second);
|
|
if (array == pointerTargets.end()) continue;
|
|
const auto length = arrayLengths.find(array->second);
|
|
if (length == arrayLengths.end()) continue;
|
|
const auto constant = constants.find(length->second);
|
|
if (constant != constants.end()) facts.clipDistanceLength = constant->second;
|
|
}
|
|
return facts;
|
|
}
|
|
|
|
ModuleFacts inspect(const ShaderRecompiler::RecompileResult& result) {
|
|
return inspect(result.spirv.Words());
|
|
}
|
|
|
|
constexpr std::array<std::uint32_t, 3> PositionExport{0xf80008cfu, 0u, 0xbf810000u};
|
|
|
|
ShaderRecompiler::RecompileRequest baseRequest(bool remap) {
|
|
using namespace ShaderRecompiler;
|
|
RecompileRequest request{};
|
|
request.shader = {ShaderStage::Vertex, 0x10000u, PositionExport, 0, {}};
|
|
request.context.waveSize = 64;
|
|
ShaderVertexStageInfo vertex{};
|
|
vertex.depthRemapMode = 2u;
|
|
request.context.vertex = vertex;
|
|
request.target.vulkanVersion = 0x00401000u;
|
|
request.target.spirvVersion = 0x00010300u;
|
|
request.target.subgroupSize = 64;
|
|
static constexpr std::array<std::uint32_t, 2> capabilities{spv::CapabilityShader, spv::CapabilityClipDistance};
|
|
request.target.supportedCapabilities = capabilities;
|
|
request.target.depthRangeRemap = remap;
|
|
request.layout.pushConstantSizeBytes = ShaderRecompiler::DepthRemapPushOffsetBytes;
|
|
request.useCache = false;
|
|
return request;
|
|
}
|
|
|
|
void require(bool condition, const char* what) {
|
|
if (!condition) throw std::runtime_error(what);
|
|
}
|
|
|
|
void preparedTransformTests() {
|
|
using namespace ShaderRecompiler;
|
|
auto request = baseRequest(true);
|
|
request.useCache = true;
|
|
const auto handle = PrepareShader(request);
|
|
const auto materialize = [&](std::uint32_t mode) {
|
|
auto moved = request;
|
|
moved.context.vertex->depthRemapMode = mode;
|
|
const auto invocation = PreparedShaderInvocation::TryCreate(moved, handle);
|
|
require(invocation.has_value(), "the prepared vertex shader did not match a request of its own mode");
|
|
return invocation->Materialize(*invocation->Capture(SrtRuntime{}));
|
|
};
|
|
const auto prepared = inspect(GetPreparedArtifact(*handle).spirv.Words());
|
|
require(prepared.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !prepared.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 1u) && !prepared.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 2u), "only the depth remap mode may be a specialization constant of the prepared module");
|
|
const auto first = materialize(2u);
|
|
require(materialize(2u)->PipelineVariantId() == first->PipelineVariantId(), "the same depth remap mode did not reuse its materialized vertex shader");
|
|
auto other = request;
|
|
other.context.vertex->depthRemapMode = 1u;
|
|
require(!PreparedShaderInvocation::TryCreate(other, handle).has_value(), "a prepared vertex shader matched a request of another depth remap mode");
|
|
}
|
|
|
|
void pushedTransformTests() {
|
|
using namespace ShaderRecompiler;
|
|
const auto remapped = Recompile(baseRequest(true));
|
|
const auto plain = Recompile(baseRequest(false));
|
|
const auto facts = inspect(remapped);
|
|
require(!facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 1u) && !facts.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 2u), "the depth remap scale or offset is a specialization constant");
|
|
require(facts.pushConstantVariable && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u) && facts.pushDwordLoads.contains(DepthRemapPushOffsetBytes / 4u + 1u), "the remapped vertex shader does not read the scale and offset from the push block");
|
|
require(!inspect(plain).pushConstantVariable, "a vertex shader without a depth remap gained a push constant block");
|
|
auto crowded = baseRequest(true);
|
|
crowded.layout.pushConstantSizeBytes = DepthRemapPushOffsetBytes + 4u;
|
|
bool refused = false;
|
|
try {
|
|
static_cast<void>(Recompile(crowded));
|
|
} catch (const std::runtime_error&) {
|
|
refused = true;
|
|
}
|
|
require(refused, "a remapped vertex shader with push data over the depth remap words was accepted");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
using namespace ShaderRecompiler;
|
|
try {
|
|
const auto remapped = inspect(Recompile(baseRequest(true)));
|
|
const auto plain = inspect(Recompile(baseRequest(false)));
|
|
require(remapped.clipDistanceBuiltIn && remapped.clipDistanceLength == plain.clipDistanceLength + 2u, "the remap did not add its two clip distances");
|
|
require(remapped.multiplies >= plain.multiplies + 2u, "the remap does not scale the clip z and offset it by w");
|
|
require(!plain.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase) && !remapped.specializationDefaults.contains(PipelineSpecialization::DepthRemapBase + 3u), "unexpected specialization constants");
|
|
preparedTransformTests();
|
|
pushedTransformTests();
|
|
std::cout << "Depth range remap tests passed\n";
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::cerr << "Depth range remap test failed: " << error.what() << '\n';
|
|
for (const bool remap : {true, false}) {
|
|
const auto facts = inspect(Recompile(baseRequest(remap)));
|
|
std::cerr << " remap=" << remap << ": spec ids";
|
|
for (const auto& [id, value] : facts.specializationDefaults) std::cerr << ' ' << id << '=' << value;
|
|
std::cerr << "; clip builtin " << facts.clipDistanceBuiltIn << " length " << facts.clipDistanceLength << "; fmul " << facts.multiplies << '\n';
|
|
}
|
|
return 1;
|
|
}
|
|
}
|