mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +02:00
shader_recompiler: add SSA
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
||||
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ValueProgram.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
|
||||
namespace {
|
||||
|
||||
bool Fail(std::string* error, const std::string& message) {
|
||||
if (error != nullptr) {
|
||||
*error = message;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kind) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
using Dim = Decoder::ImageDimension;
|
||||
if (image.kind == IR::ResourceKind::Image || image.kind == IR::ResourceKind::ImageUint) {
|
||||
const bool integer = image.kind == IR::ResourceKind::ImageUint;
|
||||
switch (image.dimension) {
|
||||
case Dim::Dim1D: kind = integer ? Kind::SampledUint1D : Kind::Sampled1D; return true;
|
||||
case Dim::Dim1DArray:
|
||||
kind = integer ? Kind::SampledUint1DArray : Kind::Sampled1DArray;
|
||||
return true;
|
||||
case Dim::Dim2D: kind = integer ? Kind::SampledUint2D : Kind::Sampled2D; return true;
|
||||
case Dim::Dim2DMsaa:
|
||||
kind = integer ? Kind::SampledUint2DMsaa : Kind::Sampled2DMsaa;
|
||||
return true;
|
||||
case Dim::Dim3D: kind = integer ? Kind::SampledUint3D : Kind::Sampled3D; return true;
|
||||
case Dim::Dim2DArray:
|
||||
kind = integer ? Kind::SampledUint2DArray : Kind::Sampled2DArray;
|
||||
return true;
|
||||
case Dim::Dim2DMsaaArray:
|
||||
kind = integer ? Kind::SampledUint2DMsaaArray : Kind::Sampled2DMsaaArray;
|
||||
return true;
|
||||
case Dim::Unknown: return false;
|
||||
}
|
||||
}
|
||||
const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint;
|
||||
if (image.kind != IR::ResourceKind::StorageImage && !uint_image) {
|
||||
return false;
|
||||
}
|
||||
switch (image.dimension) {
|
||||
case Dim::Dim1D: kind = uint_image ? Kind::StorageUint1D : Kind::Storage1D; return true;
|
||||
case Dim::Dim1DArray:
|
||||
kind = uint_image ? Kind::StorageUint1DArray : Kind::Storage1DArray;
|
||||
return true;
|
||||
case Dim::Dim2D: kind = uint_image ? Kind::StorageUint2D : Kind::Storage2D; return true;
|
||||
case Dim::Dim3D: kind = uint_image ? Kind::StorageUint3D : Kind::Storage3D; return true;
|
||||
case Dim::Dim2DArray:
|
||||
kind = uint_image ? Kind::StorageUint2DArray : Kind::Storage2DArray;
|
||||
return true;
|
||||
case Dim::Dim2DMsaa:
|
||||
case Dim::Dim2DMsaaArray: return false;
|
||||
case Dim::Unknown: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
|
||||
std::array<std::vector<uint32_t>, KindCount> expected;
|
||||
std::array<bool, KindCount> present {};
|
||||
const auto Dense = [](size_t size) {
|
||||
std::vector<uint32_t> values(size);
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
values[i] = i;
|
||||
}
|
||||
return values;
|
||||
};
|
||||
auto Expect = [&](Kind kind, std::vector<uint32_t> resources = {}) {
|
||||
const auto index = static_cast<size_t>(kind);
|
||||
present[index] = true;
|
||||
expected[index] = std::move(resources);
|
||||
};
|
||||
if (!program.info.buffers.empty()) {
|
||||
Expect(Kind::Buffers, Dense(program.info.buffers.size()));
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
Kind kind;
|
||||
if (!ImageBinding(program.info.images[i], kind)) {
|
||||
return Fail(error, "native shader plan has an invalid image class");
|
||||
}
|
||||
present[static_cast<size_t>(kind)] = true;
|
||||
expected[static_cast<size_t>(kind)].push_back(i);
|
||||
}
|
||||
if (!program.info.samplers.empty()) {
|
||||
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
|
||||
}
|
||||
const auto uses_gds = program.values != nullptr &&
|
||||
std::ranges::any_of(program.values->blocks, [](const IR::Block* block) {
|
||||
return std::ranges::any_of(*block, [](const IR::Inst& inst) {
|
||||
return inst.GetOpcode() == IR::ValueOpcode::GetGdsResource;
|
||||
});
|
||||
});
|
||||
if (uses_gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
if (!program.info.addresses.empty()) {
|
||||
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
|
||||
}
|
||||
if (program.values != nullptr && !program.values->srt_reads.empty()) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
|
||||
Expect(Kind::UserData);
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
for (uint32_t i = 0; i < program.bindings.descriptors.size(); i++) {
|
||||
const auto& binding = program.bindings.descriptors[i];
|
||||
const auto kind = static_cast<size_t>(binding.kind);
|
||||
if (kind >= KindCount || seen[kind] || !present[kind] ||
|
||||
binding.resources != expected[kind]) {
|
||||
return Fail(error, "native descriptor groups do not match shader topology");
|
||||
}
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
if (program.bindings.descriptors[j].binding == binding.binding) {
|
||||
return Fail(error, "native descriptor binding numbers are not unique");
|
||||
}
|
||||
}
|
||||
seen[kind] = true;
|
||||
}
|
||||
for (size_t i = 0; i < KindCount; i++) {
|
||||
if (present[i] != seen[i]) {
|
||||
return Fail(error, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
|
||||
if (program.bindings.buffer_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.buffer_offset_count != program.info.buffers.size() ||
|
||||
(!has_user_storage &&
|
||||
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
|
||||
(has_user_storage && program.bindings.push_constant_size != 0) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
return Fail(error, "native user-data layout is inconsistent");
|
||||
}
|
||||
|
||||
if (program.values == nullptr) {
|
||||
return Fail(error, "native shader plan has no typed SSA");
|
||||
}
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
return !handle.Uses().empty() &&
|
||||
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
|
||||
const auto op = use.user->GetOpcode();
|
||||
if (op != IR::ValueOpcode::LoadAddressU32 &&
|
||||
op != IR::ValueOpcode::ReadConstBuffer) {
|
||||
return false;
|
||||
}
|
||||
const auto index = use.user->Flags<IR::MemoryFlags>().index;
|
||||
return index < program.values->memory_info.size() &&
|
||||
program.values->memory_info[index].planning_only;
|
||||
});
|
||||
};
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto dense = inst.Flags<uint32_t>();
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::GetBufferResource:
|
||||
if (planning_only_handle(inst)) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.buffers.size()) {
|
||||
return Fail(error, "typed buffer handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetAddressResource:
|
||||
if (planning_only_handle(inst)) {
|
||||
break;
|
||||
}
|
||||
if (dense >= program.info.addresses.size()) {
|
||||
return Fail(error, "typed address handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetImageResource:
|
||||
if (dense >= program.info.images.size()) {
|
||||
return Fail(error, "typed image handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::GetSamplerResource:
|
||||
if (dense >= program.info.samplers.size()) {
|
||||
return Fail(error, "typed sampler handle has an invalid dense resource");
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::ReadConst: {
|
||||
const auto slot = inst.Arg(1).Resolve();
|
||||
if (!slot.IsImmediate() || slot.GetType() != IR::Type::U32 ||
|
||||
slot.U32() >= program.values->srt_reads.size()) {
|
||||
return Fail(error, "flattened SRT read has an invalid dense slot");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static constexpr size_t InitialEmitterVectorReserve = 4096;
|
||||
|
||||
static void ReportReserveExceeded(const IR::Program& program, const char* vector_name,
|
||||
size_t size) {
|
||||
if (size <= InitialEmitterVectorReserve) {
|
||||
return;
|
||||
}
|
||||
std::printf("SPIR-V emitter reserve exceeded: hash=0x%016" PRIx64
|
||||
" stage=%u vector=%s size=%zu reserve=%zu\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage), vector_name, size,
|
||||
InitialEmitterVectorReserve);
|
||||
}
|
||||
|
||||
void CollectValueRegisterMirrors(const IR::ValueProgram& program,
|
||||
std::vector<Emitter::RegisterBinding>& registers) {
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::SetScalarRegister:
|
||||
case IR::ValueOpcode::SetThreadBitScalarRegister:
|
||||
if (block != program.blocks.front()) {
|
||||
Emitter::CollectRegister(
|
||||
registers,
|
||||
{IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())});
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::SetVectorRegister:
|
||||
Emitter::CollectRegister(
|
||||
registers,
|
||||
{IR::RegisterFile::Vector, IR::RegIndex(inst.Arg(0).VectorRegister())});
|
||||
break;
|
||||
case IR::ValueOpcode::SetM0:
|
||||
if (block != program.blocks.front()) {
|
||||
Emitter::CollectRegister(registers, {IR::RegisterFile::M0, 0});
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::SetExecLo:
|
||||
case IR::ValueOpcode::SetExecHi:
|
||||
if (block != program.blocks.front()) {
|
||||
Emitter::CollectRegister(
|
||||
registers, {IR::RegisterFile::Exec,
|
||||
inst.GetOpcode() == IR::ValueOpcode::SetExecLo ? 0u : 1u});
|
||||
}
|
||||
break;
|
||||
case IR::ValueOpcode::SetVccLo:
|
||||
case IR::ValueOpcode::SetVccHi:
|
||||
if (block != program.blocks.front()) {
|
||||
Emitter::CollectRegister(
|
||||
registers, {IR::RegisterFile::Vcc,
|
||||
inst.GetOpcode() == IR::ValueOpcode::SetVccLo ? 0u : 1u});
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ValueProgramHasOpcode(const IR::ValueProgram& program, IR::ValueOpcode opcode) {
|
||||
return std::ranges::any_of(program.blocks, [opcode](const IR::Block* block) {
|
||||
return std::ranges::any_of(
|
||||
*block, [opcode](const IR::Inst& inst) { return inst.GetOpcode() == opcode; });
|
||||
});
|
||||
}
|
||||
|
||||
bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
|
||||
if (program.values == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* block: program.values->blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::ValueOpcode::Ballot:
|
||||
case IR::ValueOpcode::DppMoveU32:
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WqmMask:
|
||||
case IR::ValueOpcode::ReadFirstLane:
|
||||
case IR::ValueOpcode::ReadLane:
|
||||
case IR::ValueOpcode::WriteLane:
|
||||
case IR::ValueOpcode::Permlane16U32:
|
||||
case IR::ValueOpcode::SwizzleU32:
|
||||
case IR::ValueOpcode::DataAppend:
|
||||
case IR::ValueOpcode::DataConsume:
|
||||
case IR::ValueOpcode::GdsDataAppend:
|
||||
case IR::ValueOpcode::GdsDataConsume: return true;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
|
||||
const ShaderVertexInputInfo* vertex_input_info,
|
||||
const ShaderPixelInputInfo* pixel_input_info,
|
||||
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>& spirv,
|
||||
std::string* error, bool preserve_debug_values) {
|
||||
using namespace Emitter;
|
||||
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
program.stage != ShaderType::Pixel) {
|
||||
SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
|
||||
return false;
|
||||
}
|
||||
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
|
||||
!program.shader_info_complete || !program.binding_layout_complete) {
|
||||
SetError(error, "SPIR-V emitter requires a fully planned native shader program");
|
||||
return false;
|
||||
}
|
||||
if (!IR::ValidateResourceSnapshot(program, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!IR::ValidateResourceSpecialization(program, resources, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidateNativeProgram(program, error)) {
|
||||
return false;
|
||||
}
|
||||
if (program.values == nullptr) {
|
||||
SetError(error, "SPIR-V emitter requires planned typed SSA");
|
||||
return false;
|
||||
}
|
||||
ShaderVertexInputInfo default_vertex {};
|
||||
ShaderPixelInputInfo default_pixel {};
|
||||
ShaderComputeInputInfo default_compute {};
|
||||
const auto* vertex = vertex_input_info != nullptr ? vertex_input_info : &default_vertex;
|
||||
const auto* pixel = pixel_input_info != nullptr ? pixel_input_info : &default_pixel;
|
||||
const auto* compute = compute_input_info != nullptr ? compute_input_info : &default_compute;
|
||||
(void)preserve_debug_values;
|
||||
const auto& value_program = *program.values;
|
||||
if (!IR::ValidateValueProgram(value_program, true, error)) {
|
||||
return false;
|
||||
}
|
||||
EmitterState state(program, resources);
|
||||
state.vertex_input_info = vertex;
|
||||
state.pixel_input_info = pixel;
|
||||
state.compute_input_info = compute;
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
|
||||
state.exact_subgroup_operations =
|
||||
!state.per_invocation_masks && ProgramRequiresExactSubgroupSize(program);
|
||||
state.registers.reserve(InitialEmitterVectorReserve);
|
||||
state.inputs.reserve(InitialEmitterVectorReserve);
|
||||
state.outputs.reserve(InitialEmitterVectorReserve);
|
||||
state.interface_variables.reserve(InitialEmitterVectorReserve);
|
||||
state.reachable_blocks.reserve(InitialEmitterVectorReserve);
|
||||
CollectValueRegisterMirrors(value_program, state.registers);
|
||||
CopyProgramInputsAndOutputs(state, program);
|
||||
state.needs_subgroup_ballot = ProgramNeedsSubgroupBallot(program) ||
|
||||
(!state.per_invocation_masks &&
|
||||
ValueProgramHasOpcode(value_program, IR::ValueOpcode::Ballot));
|
||||
state.needs_subgroup_shuffle = ProgramNeedsSubgroupShuffle(program);
|
||||
state.needs_subgroup_local_invocation_id = ProgramNeedsSubgroupLocalInvocationId(program);
|
||||
state.needs_compute_derivatives = ProgramNeedsComputeDerivatives(program);
|
||||
state.needs_image_gather_extended = ProgramNeedsImageGatherExtended(program);
|
||||
state.needs_function_lds = ProgramNeedsFunctionLds(program);
|
||||
state.needs_pixel_valid_mask = ProgramNeedsPixelValidMask(program);
|
||||
AllocateInputVariables(state);
|
||||
AllocateOutputVariables(state);
|
||||
AllocateDescriptorVariables(state);
|
||||
EmitHeaderAndTypes(state);
|
||||
AllocateRegisterVariables(state);
|
||||
if (!EmitValueProgram(state, value_program, error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportReserveExceeded(program, "registers", state.registers.size());
|
||||
ReportReserveExceeded(program, "inputs", state.inputs.size());
|
||||
ReportReserveExceeded(program, "outputs", state.outputs.size());
|
||||
ReportReserveExceeded(program, "interface_variables", state.interface_variables.size());
|
||||
ReportReserveExceeded(program, "reachable_blocks", state.reachable_blocks.size());
|
||||
|
||||
auto binary = state.builder.Build();
|
||||
if (binary.empty()) {
|
||||
SetError(error, "SPIR-V builder returned an empty module");
|
||||
return false;
|
||||
}
|
||||
spirv = std::move(binary);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
Reference in New Issue
Block a user