mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 05:46:56 +01:00
gpu: isolate tessellation analysis and SPIR-V emission
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||||
|
#include "graphics/shader/recompiler/Tessellation.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
@@ -465,30 +466,10 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Decoder::Program DecodeFrontProgram(std::span<const uint32_t> front) {
|
|
||||||
Decoder::Program result;
|
|
||||||
uint32_t front_words = 0;
|
|
||||||
while (front_words < front.size()) {
|
|
||||||
auto& inst = result.instructions.emplace_back();
|
|
||||||
Decoder::DecodeInstruction(front, front_words, inst);
|
|
||||||
front_words += inst.word_count;
|
|
||||||
if (inst.opcode == Decoder::Opcode::S_SETPC_B64) {
|
|
||||||
EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr ||
|
|
||||||
inst.src0.reg != 6u);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_ENDPGM);
|
|
||||||
}
|
|
||||||
EXIT_IF(result.instructions.empty() ||
|
|
||||||
result.instructions.back().opcode != Decoder::Opcode::S_SETPC_B64);
|
|
||||||
result.code = front.first(front_words);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<const uint32_t> back,
|
Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<const uint32_t> back,
|
||||||
std::vector<uint32_t>& joined_code) {
|
std::vector<uint32_t>& joined_code) {
|
||||||
EXIT_IF(back.empty());
|
EXIT_IF(back.empty());
|
||||||
auto result = DecodeFrontProgram(front);
|
auto result = Decoder::DecodeFrontProgram(front);
|
||||||
const auto front_words = static_cast<uint32_t>(result.code.size());
|
const auto front_words = static_cast<uint32_t>(result.code.size());
|
||||||
joined_code.assign(result.code.begin(), result.code.end());
|
joined_code.assign(result.code.begin(), result.code.end());
|
||||||
joined_code.insert(joined_code.end(), back.begin(), back.end());
|
joined_code.insert(joined_code.end(), back.begin(), back.end());
|
||||||
@@ -511,316 +492,6 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TessellationAddress {
|
|
||||||
enum class Kind { Unknown, Affine, PackedControlPoint };
|
|
||||||
Kind kind = Kind::Unknown;
|
|
||||||
uint32_t coefficient = 0;
|
|
||||||
uint32_t constant = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
|
|
||||||
uint32_t control_points, uint32_t input_stride) {
|
|
||||||
using namespace Decoder;
|
|
||||||
using Address = TessellationAddress;
|
|
||||||
using Kind = Address::Kind;
|
|
||||||
std::array<Address, IR::NumVectorRegs> registers {};
|
|
||||||
registers[local ? 3u : 1u] =
|
|
||||||
local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint};
|
|
||||||
const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; };
|
|
||||||
const auto read = [&](const Operand& operand) -> Address {
|
|
||||||
if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel ||
|
|
||||||
operand.op_sel_hi || operand.negate_hi || operand.dpp)
|
|
||||||
return {};
|
|
||||||
Address value;
|
|
||||||
if (operand.kind == OperandKind::IntegerInlineConstant ||
|
|
||||||
operand.kind == OperandKind::LiteralConstant) {
|
|
||||||
value = constant(operand.value);
|
|
||||||
} else if (operand.kind == OperandKind::Vgpr) {
|
|
||||||
value = registers.at(operand.reg);
|
|
||||||
}
|
|
||||||
// The native packed HS ID's low byte is the patch ordinal. The logical
|
|
||||||
// interface addresses one patch, as in the frontend's v1 initialization.
|
|
||||||
if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u);
|
|
||||||
return operand.sdwa_sel == 6u ? value : Address {};
|
|
||||||
};
|
|
||||||
const auto affine = [](uint64_t coefficient, uint64_t offset) {
|
|
||||||
return coefficient <= UINT32_MAX && offset <= UINT32_MAX
|
|
||||||
? Address {Kind::Affine, static_cast<uint32_t>(coefficient),
|
|
||||||
static_cast<uint32_t>(offset)}
|
|
||||||
: Address {};
|
|
||||||
};
|
|
||||||
const auto add = [&](Address lhs, Address rhs) {
|
|
||||||
return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine
|
|
||||||
? affine(uint64_t {lhs.coefficient} + rhs.coefficient,
|
|
||||||
uint64_t {lhs.constant} + rhs.constant)
|
|
||||||
: Address {};
|
|
||||||
};
|
|
||||||
const auto multiply = [&](Address lhs, Address rhs) {
|
|
||||||
if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) ||
|
|
||||||
(rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u))
|
|
||||||
return constant(0u);
|
|
||||||
if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {};
|
|
||||||
if (rhs.coefficient != 0u) std::swap(lhs, rhs);
|
|
||||||
return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant,
|
|
||||||
uint64_t {lhs.constant} * rhs.constant)
|
|
||||||
: Address {};
|
|
||||||
};
|
|
||||||
const auto low24 = [&](Address value) {
|
|
||||||
if (value.kind == Kind::Affine && value.coefficient == 0u)
|
|
||||||
return constant(value.constant & 0xffffffu);
|
|
||||||
return value.kind == Kind::Affine &&
|
|
||||||
uint64_t {value.coefficient} * (control_points - 1u) + value.constant <=
|
|
||||||
0xffffffu
|
|
||||||
? value
|
|
||||||
: Address {};
|
|
||||||
};
|
|
||||||
uint32_t stride = 0;
|
|
||||||
for (const auto& inst: program.instructions) {
|
|
||||||
// Stage exits preserve the active path's definitions. An internal join
|
|
||||||
// would require merging register definitions.
|
|
||||||
EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) &&
|
|
||||||
inst.branch_target != program.instructions.back().pc);
|
|
||||||
const bool local_store =
|
|
||||||
inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32;
|
|
||||||
const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD ||
|
|
||||||
inst.opcode == Opcode::BUFFER_STORE_DWORDX2 ||
|
|
||||||
inst.opcode == Opcode::BUFFER_STORE_DWORDX3 ||
|
|
||||||
inst.opcode == Opcode::BUFFER_STORE_DWORDX4;
|
|
||||||
const bool control_store =
|
|
||||||
!local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u;
|
|
||||||
const bool control_read =
|
|
||||||
!local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32);
|
|
||||||
if ((local && local_store) || control_store || control_read) {
|
|
||||||
const auto address = read(inst.src0);
|
|
||||||
if (address.kind != Kind::Affine) {
|
|
||||||
EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS",
|
|
||||||
inst.pc);
|
|
||||||
}
|
|
||||||
if (address.coefficient != 0u) {
|
|
||||||
const auto expected = control_read ? input_stride : stride;
|
|
||||||
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
|
|
||||||
(expected != 0u && expected != address.coefficient));
|
|
||||||
if (!control_read) stride = address.coefficient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Store vdata is a source, despite occupying the decoder's dst field.
|
|
||||||
if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue;
|
|
||||||
const auto lhs = read(inst.src0);
|
|
||||||
const auto rhs = read(inst.src1);
|
|
||||||
const auto third = read(inst.src2);
|
|
||||||
Address value;
|
|
||||||
switch (inst.opcode) {
|
|
||||||
case Opcode::V_BFE_U32:
|
|
||||||
if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine &&
|
|
||||||
rhs.coefficient == 0u && third.kind == Kind::Affine &&
|
|
||||||
third.coefficient == 0u) {
|
|
||||||
if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u};
|
|
||||||
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
|
|
||||||
case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break;
|
|
||||||
case Opcode::V_LSHL_ADD_U32:
|
|
||||||
if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u)
|
|
||||||
value = add(multiply(lhs, constant(1u << rhs.constant)), third);
|
|
||||||
break;
|
|
||||||
case Opcode::V_LSHLREV_B32:
|
|
||||||
if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u)
|
|
||||||
value = multiply(rhs, constant(1u << lhs.constant));
|
|
||||||
break;
|
|
||||||
case Opcode::V_SUB_NC_U32:
|
|
||||||
if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine &&
|
|
||||||
lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant)
|
|
||||||
value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant);
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp ||
|
|
||||||
inst.dst.dpp)
|
|
||||||
value = {};
|
|
||||||
for (uint32_t index = 0;
|
|
||||||
index < DecodedDstSize(inst) && inst.dst.reg + index < registers.size(); index++) {
|
|
||||||
registers[inst.dst.reg + index] = {};
|
|
||||||
}
|
|
||||||
registers.at(inst.dst.reg) = value;
|
|
||||||
}
|
|
||||||
EXIT_NOT_IMPLEMENTED(stride == 0u);
|
|
||||||
return stride;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
|
|
||||||
ShaderTessellationInputInfo& info) {
|
|
||||||
const auto local_program = DecodeFrontProgram(local);
|
|
||||||
Decoder::Program control_program;
|
|
||||||
Decoder::DecodeProgram(control, control_program);
|
|
||||||
info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u);
|
|
||||||
info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points,
|
|
||||||
info.ls_stride);
|
|
||||||
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n",
|
|
||||||
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
|
|
||||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u)
|
|
||||||
return nullptr;
|
|
||||||
const auto* base = inst.Arg(3).Resolve().TryInstruction();
|
|
||||||
return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base
|
|
||||||
: nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
|
|
||||||
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
|
|
||||||
using namespace IR;
|
|
||||||
value = value.Resolve();
|
|
||||||
auto* source = value.TryInstruction();
|
|
||||||
if (source == nullptr) return value;
|
|
||||||
if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
|
|
||||||
if (source->GetOpcode() == ValueOpcode::SelectU32) {
|
|
||||||
return source->Arg(0).Resolve() == predicate.Resolve()
|
|
||||||
? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32)
|
|
||||||
return value;
|
|
||||||
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved);
|
|
||||||
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved);
|
|
||||||
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) {
|
|
||||||
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs},
|
|
||||||
source->Flags<uint64_t>());
|
|
||||||
value = Value(&*copy);
|
|
||||||
}
|
|
||||||
resolved.emplace(source, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) {
|
|
||||||
using namespace IR;
|
|
||||||
if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
|
|
||||||
options.stage != ShaderType::TessellationEvaluation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto& tess = options.input_info.vertex->tess;
|
|
||||||
// Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to
|
|
||||||
// a store guarded by the same EXEC predicate, but must remain intact elsewhere.
|
|
||||||
for (auto* block: program.blocks) {
|
|
||||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
|
||||||
if (TessellationBufferBase(*it) == nullptr) continue;
|
|
||||||
std::unordered_map<Inst*, Value> resolved;
|
|
||||||
it->SetArg(
|
|
||||||
2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConstantPropagationPass(program.blocks);
|
|
||||||
uint32_t reads = 0, writes = 0, factors = 0;
|
|
||||||
for (auto* block: program.blocks) {
|
|
||||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
|
||||||
auto& inst = *it;
|
|
||||||
const auto shared = SharedAccessOf(inst.GetOpcode());
|
|
||||||
const auto buffer = BufferAccessOf(inst.GetOpcode());
|
|
||||||
if (shared == SharedAccess::None && buffer == BufferAccess::None) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto& memory = program.memory_info.at(inst.Flags<MemoryFlags>().index);
|
|
||||||
bool write = false;
|
|
||||||
uint32_t components = 0;
|
|
||||||
TessellationAttribute kind;
|
|
||||||
Value address, predicate;
|
|
||||||
if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) {
|
|
||||||
write = shared == SharedAccess::Write;
|
|
||||||
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u ||
|
|
||||||
(options.stage == ShaderType::Local
|
|
||||||
? !write
|
|
||||||
: options.stage != ShaderType::TessellationControl ||
|
|
||||||
shared != SharedAccess::Read));
|
|
||||||
kind = write ? TessellationAttribute::LocalOutput
|
|
||||||
: TessellationAttribute::ControlInput;
|
|
||||||
components = SharedComponentCount(inst.GetOpcode());
|
|
||||||
address = inst.Arg(0);
|
|
||||||
predicate = inst.Arg(inst.NumArgs() - 1u);
|
|
||||||
} else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) {
|
|
||||||
const auto* base = TessellationBufferBase(inst);
|
|
||||||
if (base == nullptr) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen ||
|
|
||||||
!memory.offen || buffer == BufferAccess::Atomic);
|
|
||||||
write = buffer == BufferAccess::Write;
|
|
||||||
components = BufferComponentCount(inst.GetOpcode());
|
|
||||||
address = inst.Arg(2).Resolve();
|
|
||||||
predicate = inst.Arg(inst.NumArgs() - 1u);
|
|
||||||
if (base->Arg(0).U32() == 1u) {
|
|
||||||
EXIT_NOT_IMPLEMENTED(!write ||
|
|
||||||
options.stage != ShaderType::TessellationControl);
|
|
||||||
kind = TessellationAttribute::Factor;
|
|
||||||
factors += components;
|
|
||||||
} else {
|
|
||||||
kind = write ? TessellationAttribute::ControlOutput
|
|
||||||
: TessellationAttribute::EvaluationInput;
|
|
||||||
EXIT_NOT_IMPLEMENTED(write
|
|
||||||
? options.stage != ShaderType::TessellationControl
|
|
||||||
: options.stage != ShaderType::TessellationEvaluation);
|
|
||||||
if (address.IsImmediate() &&
|
|
||||||
address.U32() >= tess.hs_stride * tess.output_control_points) {
|
|
||||||
EXIT_NOT_IMPLEMENTED(!write);
|
|
||||||
kind = TessellationAttribute::PatchOutput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto emit = [&](ValueOpcode opcode, std::initializer_list<Value> args) {
|
|
||||||
return Value(&*block->PrependNewInst(it, opcode, args));
|
|
||||||
};
|
|
||||||
std::array<Value, 4> values;
|
|
||||||
for (uint32_t component = 0; component < components; component++) {
|
|
||||||
const auto offset = memory.offset + 4u * component;
|
|
||||||
const auto byte_address =
|
|
||||||
offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)});
|
|
||||||
if (write) {
|
|
||||||
Value data = shared != SharedAccess::None ? inst.Arg(component + 1u)
|
|
||||||
: components == 1u
|
|
||||||
? inst.Arg(4)
|
|
||||||
: emit(components == 2u ? ValueOpcode::CompositeExtractU32x2
|
|
||||||
: components == 3u ? ValueOpcode::CompositeExtractU32x3
|
|
||||||
: ValueOpcode::CompositeExtractU32x4,
|
|
||||||
{inst.Arg(4), Value(component)});
|
|
||||||
emit(ValueOpcode::SetTessellationAttribute,
|
|
||||||
{Value(static_cast<uint32_t>(kind)), byte_address, data, predicate});
|
|
||||||
writes++;
|
|
||||||
} else {
|
|
||||||
values[component] =
|
|
||||||
emit(ValueOpcode::GetTessellationAttribute,
|
|
||||||
{Value(static_cast<uint32_t>(kind)), byte_address, predicate});
|
|
||||||
reads++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!write) {
|
|
||||||
Value replacement = values[0];
|
|
||||||
if (components == 2u)
|
|
||||||
replacement =
|
|
||||||
emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]});
|
|
||||||
if (components == 3u)
|
|
||||||
replacement = emit(ValueOpcode::CompositeConstructU32x3,
|
|
||||||
{values[0], values[1], values[2]});
|
|
||||||
if (components == 4u)
|
|
||||||
replacement = emit(ValueOpcode::CompositeConstructU32x4,
|
|
||||||
{values[0], values[1], values[2], values[3]});
|
|
||||||
inst.ReplaceUsesWith(replacement);
|
|
||||||
}
|
|
||||||
inst.Invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConstantPropagationPass(program.blocks);
|
|
||||||
RemoveIdentities(program.blocks);
|
|
||||||
EliminateDeadCode(program.blocks);
|
|
||||||
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n", StageName(options.stage),
|
|
||||||
reads, writes, factors);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOptions& options) {
|
TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||||
@@ -851,7 +522,7 @@ TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOp
|
|||||||
if (!options.back_code.empty()) {
|
if (!options.back_code.empty()) {
|
||||||
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
|
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
|
||||||
} else if (options.stage == ShaderType::Local) {
|
} else if (options.stage == ShaderType::Local) {
|
||||||
decoded = DecodeFrontProgram(code);
|
decoded = Decoder::DecodeFrontProgram(code);
|
||||||
// The separately compiled hull half runs in the next Vulkan stage.
|
// The separately compiled hull half runs in the next Vulkan stage.
|
||||||
auto& handoff = decoded.instructions.back();
|
auto& handoff = decoded.instructions.back();
|
||||||
handoff.opcode = Decoder::Opcode::S_ENDPGM;
|
handoff.opcode = Decoder::Opcode::S_ENDPGM;
|
||||||
|
|||||||
@@ -37,9 +37,6 @@ struct CompileResult {
|
|||||||
IR::Program program;
|
IR::Program program;
|
||||||
};
|
};
|
||||||
|
|
||||||
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
|
|
||||||
ShaderTessellationInputInfo& info);
|
|
||||||
|
|
||||||
[[nodiscard]] TranslateResult TranslateProgram(std::span<const uint32_t> code,
|
[[nodiscard]] TranslateResult TranslateProgram(std::span<const uint32_t> code,
|
||||||
const CompileOptions& options);
|
const CompileOptions& options);
|
||||||
[[nodiscard]] CompileResult CompileProgram(TranslateResult translated,
|
[[nodiscard]] CompileResult CompileProgram(TranslateResult translated,
|
||||||
|
|||||||
@@ -0,0 +1,330 @@
|
|||||||
|
#include "graphics/shader/recompiler/Tessellation.h"
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||||
|
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||||
|
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||||
|
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
|
||||||
|
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Libs::Graphics::ShaderRecompiler {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct TessellationAddress {
|
||||||
|
enum class Kind { Unknown, Affine, PackedControlPoint };
|
||||||
|
Kind kind = Kind::Unknown;
|
||||||
|
uint32_t coefficient = 0;
|
||||||
|
uint32_t constant = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t ReflectTessellationStride(const Decoder::Program& program, bool local,
|
||||||
|
uint32_t control_points, uint32_t input_stride) {
|
||||||
|
using namespace Decoder;
|
||||||
|
using Address = TessellationAddress;
|
||||||
|
using Kind = Address::Kind;
|
||||||
|
std::array<Address, IR::NumVectorRegs> registers {};
|
||||||
|
registers[local ? 3u : 1u] =
|
||||||
|
local ? Address {Kind::Affine, 1u, 0u} : Address {Kind::PackedControlPoint};
|
||||||
|
const auto constant = [](uint32_t value) { return Address {Kind::Affine, 0u, value}; };
|
||||||
|
const auto read = [&](const Operand& operand) -> Address {
|
||||||
|
if (operand.absolute || operand.negate || operand.sdwa_sext || operand.op_sel ||
|
||||||
|
operand.op_sel_hi || operand.negate_hi || operand.dpp)
|
||||||
|
return {};
|
||||||
|
Address value;
|
||||||
|
if (operand.kind == OperandKind::IntegerInlineConstant ||
|
||||||
|
operand.kind == OperandKind::LiteralConstant) {
|
||||||
|
value = constant(operand.value);
|
||||||
|
} else if (operand.kind == OperandKind::Vgpr) {
|
||||||
|
value = registers.at(operand.reg);
|
||||||
|
}
|
||||||
|
// The native packed HS ID's low byte is the patch ordinal. The logical
|
||||||
|
// interface addresses one patch, as in the frontend's v1 initialization.
|
||||||
|
if (value.kind == Kind::PackedControlPoint && operand.sdwa_sel == 0u) return constant(0u);
|
||||||
|
return operand.sdwa_sel == 6u ? value : Address {};
|
||||||
|
};
|
||||||
|
const auto affine = [](uint64_t coefficient, uint64_t offset) {
|
||||||
|
return coefficient <= UINT32_MAX && offset <= UINT32_MAX
|
||||||
|
? Address {Kind::Affine, static_cast<uint32_t>(coefficient),
|
||||||
|
static_cast<uint32_t>(offset)}
|
||||||
|
: Address {};
|
||||||
|
};
|
||||||
|
const auto add = [&](Address lhs, Address rhs) {
|
||||||
|
return lhs.kind == Kind::Affine && rhs.kind == Kind::Affine
|
||||||
|
? affine(uint64_t {lhs.coefficient} + rhs.coefficient,
|
||||||
|
uint64_t {lhs.constant} + rhs.constant)
|
||||||
|
: Address {};
|
||||||
|
};
|
||||||
|
const auto multiply = [&](Address lhs, Address rhs) {
|
||||||
|
if ((lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant == 0u) ||
|
||||||
|
(rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant == 0u))
|
||||||
|
return constant(0u);
|
||||||
|
if (lhs.kind != Kind::Affine || rhs.kind != Kind::Affine) return Address {};
|
||||||
|
if (rhs.coefficient != 0u) std::swap(lhs, rhs);
|
||||||
|
return rhs.coefficient == 0u ? affine(uint64_t {lhs.coefficient} * rhs.constant,
|
||||||
|
uint64_t {lhs.constant} * rhs.constant)
|
||||||
|
: Address {};
|
||||||
|
};
|
||||||
|
const auto low24 = [&](Address value) {
|
||||||
|
if (value.kind == Kind::Affine && value.coefficient == 0u)
|
||||||
|
return constant(value.constant & 0xffffffu);
|
||||||
|
return value.kind == Kind::Affine &&
|
||||||
|
uint64_t {value.coefficient} * (control_points - 1u) + value.constant <=
|
||||||
|
0xffffffu
|
||||||
|
? value
|
||||||
|
: Address {};
|
||||||
|
};
|
||||||
|
uint32_t stride = 0;
|
||||||
|
for (const auto& inst: program.instructions) {
|
||||||
|
// Stage exits preserve the active path's definitions. An internal join
|
||||||
|
// would require merging register definitions.
|
||||||
|
EXIT_NOT_IMPLEMENTED(IsDirectBranch(inst.opcode) &&
|
||||||
|
inst.branch_target != program.instructions.back().pc);
|
||||||
|
const bool local_store =
|
||||||
|
inst.opcode == Opcode::DS_WRITE_B32 || inst.opcode == Opcode::DS_WRITE2_B32;
|
||||||
|
const bool buffer_store = inst.opcode == Opcode::BUFFER_STORE_DWORD ||
|
||||||
|
inst.opcode == Opcode::BUFFER_STORE_DWORDX2 ||
|
||||||
|
inst.opcode == Opcode::BUFFER_STORE_DWORDX3 ||
|
||||||
|
inst.opcode == Opcode::BUFFER_STORE_DWORDX4;
|
||||||
|
const bool control_store =
|
||||||
|
!local && buffer_store && inst.src2.kind == OperandKind::Sgpr && inst.src2.reg == 2u;
|
||||||
|
const bool control_read =
|
||||||
|
!local && (inst.opcode == Opcode::DS_READ_B32 || inst.opcode == Opcode::DS_READ2_B32);
|
||||||
|
if ((local && local_store) || control_store || control_read) {
|
||||||
|
const auto address = read(inst.src0);
|
||||||
|
if (address.kind != Kind::Affine) {
|
||||||
|
EXIT("%s tessellation address is not affine at pc 0x%08x\n", local ? "LS" : "HS",
|
||||||
|
inst.pc);
|
||||||
|
}
|
||||||
|
if (address.coefficient != 0u) {
|
||||||
|
const auto expected = control_read ? input_stride : stride;
|
||||||
|
EXIT_NOT_IMPLEMENTED((address.coefficient & 3u) != 0u ||
|
||||||
|
(expected != 0u && expected != address.coefficient));
|
||||||
|
if (!control_read) stride = address.coefficient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Store vdata is a source, despite occupying the decoder's dst field.
|
||||||
|
if (local_store || buffer_store || inst.dst.kind != OperandKind::Vgpr) continue;
|
||||||
|
const auto lhs = read(inst.src0);
|
||||||
|
const auto rhs = read(inst.src1);
|
||||||
|
const auto third = read(inst.src2);
|
||||||
|
Address value;
|
||||||
|
switch (inst.opcode) {
|
||||||
|
case Opcode::V_BFE_U32:
|
||||||
|
if (lhs.kind == Kind::PackedControlPoint && rhs.kind == Kind::Affine &&
|
||||||
|
rhs.coefficient == 0u && third.kind == Kind::Affine &&
|
||||||
|
third.coefficient == 0u) {
|
||||||
|
if (rhs.constant == 8u && third.constant == 5u) value = {Kind::Affine, 1u, 0u};
|
||||||
|
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
|
||||||
|
case Opcode::V_MAD_U32_U24: value = add(multiply(low24(lhs), low24(rhs)), third); break;
|
||||||
|
case Opcode::V_LSHL_ADD_U32:
|
||||||
|
if (rhs.kind == Kind::Affine && rhs.coefficient == 0u && rhs.constant < 32u)
|
||||||
|
value = add(multiply(lhs, constant(1u << rhs.constant)), third);
|
||||||
|
break;
|
||||||
|
case Opcode::V_LSHLREV_B32:
|
||||||
|
if (lhs.kind == Kind::Affine && lhs.coefficient == 0u && lhs.constant < 32u)
|
||||||
|
value = multiply(rhs, constant(1u << lhs.constant));
|
||||||
|
break;
|
||||||
|
case Opcode::V_SUB_NC_U32:
|
||||||
|
if (lhs.kind == Kind::Affine && rhs.kind == Kind::Affine &&
|
||||||
|
lhs.coefficient >= rhs.coefficient && lhs.constant >= rhs.constant)
|
||||||
|
value = affine(lhs.coefficient - rhs.coefficient, lhs.constant - rhs.constant);
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (inst.dst.sdwa_sel != 6u || inst.dst.op_sel || inst.dst.omod || inst.dst.clamp ||
|
||||||
|
inst.dst.dpp)
|
||||||
|
value = {};
|
||||||
|
for (uint32_t index = 0;
|
||||||
|
index < std::max(inst.data_dwords, 1u) && inst.dst.reg + index < registers.size(); index++) {
|
||||||
|
registers[inst.dst.reg + index] = {};
|
||||||
|
}
|
||||||
|
registers.at(inst.dst.reg) = value;
|
||||||
|
}
|
||||||
|
EXIT_NOT_IMPLEMENTED(stride == 0u);
|
||||||
|
return stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IR::Inst* TessellationBufferBase(const IR::Inst& inst) {
|
||||||
|
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::None || inst.NumArgs() <= 3u)
|
||||||
|
return nullptr;
|
||||||
|
const auto* base = inst.Arg(3).Resolve().TryInstruction();
|
||||||
|
return base != nullptr && base->GetOpcode() == IR::ValueOpcode::TessellationBase ? base
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Value ActiveAddress(IR::Block& block, IR::Block::iterator before, IR::Value value,
|
||||||
|
IR::Value predicate, std::unordered_map<IR::Inst*, IR::Value>& resolved) {
|
||||||
|
using namespace IR;
|
||||||
|
value = value.Resolve();
|
||||||
|
auto* source = value.TryInstruction();
|
||||||
|
if (source == nullptr) return value;
|
||||||
|
if (const auto found = resolved.find(source); found != resolved.end()) return found->second;
|
||||||
|
if (source->GetOpcode() == ValueOpcode::SelectU32) {
|
||||||
|
return source->Arg(0).Resolve() == predicate.Resolve()
|
||||||
|
? ActiveAddress(block, before, source->Arg(1), predicate, resolved)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
if (source->GetOpcode() != ValueOpcode::IAdd32 && source->GetOpcode() != ValueOpcode::ISub32)
|
||||||
|
return value;
|
||||||
|
const auto lhs = ActiveAddress(block, before, source->Arg(0), predicate, resolved);
|
||||||
|
const auto rhs = ActiveAddress(block, before, source->Arg(1), predicate, resolved);
|
||||||
|
if (lhs != source->Arg(0).Resolve() || rhs != source->Arg(1).Resolve()) {
|
||||||
|
auto copy = block.PrependNewInst(before, source->GetOpcode(), {lhs, rhs},
|
||||||
|
source->Flags<uint64_t>());
|
||||||
|
value = Value(&*copy);
|
||||||
|
}
|
||||||
|
resolved.emplace(source, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
|
||||||
|
ShaderTessellationInputInfo& info) {
|
||||||
|
const auto local_program = Decoder::DecodeFrontProgram(local);
|
||||||
|
Decoder::Program control_program;
|
||||||
|
Decoder::DecodeProgram(control, control_program);
|
||||||
|
info.ls_stride = ReflectTessellationStride(local_program, true, info.input_control_points, 0u);
|
||||||
|
info.hs_stride = ReflectTessellationStride(control_program, false, info.output_control_points,
|
||||||
|
info.ls_stride);
|
||||||
|
LOGF("Tessellation interface: input_cp=%u output_cp=%u ls_stride=%u hs_stride=%u\n",
|
||||||
|
info.input_control_points, info.output_control_points, info.ls_stride, info.hs_stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options) {
|
||||||
|
using namespace IR;
|
||||||
|
if (options.stage != ShaderType::Local && options.stage != ShaderType::TessellationControl &&
|
||||||
|
options.stage != ShaderType::TessellationEvaluation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& tess = options.input_info.vertex->tess;
|
||||||
|
// Ring addresses can reuse data VGPRs. Their inactive values are irrelevant to
|
||||||
|
// a store guarded by the same EXEC predicate, but must remain intact elsewhere.
|
||||||
|
for (auto* block: program.blocks) {
|
||||||
|
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||||
|
if (TessellationBufferBase(*it) == nullptr) continue;
|
||||||
|
std::unordered_map<Inst*, Value> resolved;
|
||||||
|
it->SetArg(
|
||||||
|
2, ActiveAddress(*block, it, it->Arg(2), it->Arg(it->NumArgs() - 1u), resolved));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConstantPropagationPass(program.blocks);
|
||||||
|
uint32_t reads = 0, writes = 0, factors = 0;
|
||||||
|
for (auto* block: program.blocks) {
|
||||||
|
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||||
|
auto& inst = *it;
|
||||||
|
const auto shared = SharedAccessOf(inst.GetOpcode());
|
||||||
|
const auto buffer = BufferAccessOf(inst.GetOpcode());
|
||||||
|
if (shared == SharedAccess::None && buffer == BufferAccess::None) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& memory = program.memory_info.at(inst.Flags<MemoryFlags>().index);
|
||||||
|
bool write = false;
|
||||||
|
uint32_t components = 0;
|
||||||
|
TessellationAttribute kind;
|
||||||
|
Value address, predicate;
|
||||||
|
if (shared != SharedAccess::None && memory.kind == ResourceKind::Lds) {
|
||||||
|
write = shared == SharedAccess::Write;
|
||||||
|
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u ||
|
||||||
|
(options.stage == ShaderType::Local
|
||||||
|
? !write
|
||||||
|
: options.stage != ShaderType::TessellationControl ||
|
||||||
|
shared != SharedAccess::Read));
|
||||||
|
kind = write ? TessellationAttribute::LocalOutput
|
||||||
|
: TessellationAttribute::ControlInput;
|
||||||
|
components = SharedComponentCount(inst.GetOpcode());
|
||||||
|
address = inst.Arg(0);
|
||||||
|
predicate = inst.Arg(inst.NumArgs() - 1u);
|
||||||
|
} else if (buffer != BufferAccess::None && memory.kind == ResourceKind::Buffer) {
|
||||||
|
const auto* base = TessellationBufferBase(inst);
|
||||||
|
if (base == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EXIT_NOT_IMPLEMENTED(memory.data_bits != 32u || memory.formatted || memory.idxen ||
|
||||||
|
!memory.offen || buffer == BufferAccess::Atomic);
|
||||||
|
write = buffer == BufferAccess::Write;
|
||||||
|
components = BufferComponentCount(inst.GetOpcode());
|
||||||
|
address = inst.Arg(2).Resolve();
|
||||||
|
predicate = inst.Arg(inst.NumArgs() - 1u);
|
||||||
|
if (base->Arg(0).U32() == 1u) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(!write ||
|
||||||
|
options.stage != ShaderType::TessellationControl);
|
||||||
|
kind = TessellationAttribute::Factor;
|
||||||
|
factors += components;
|
||||||
|
} else {
|
||||||
|
kind = write ? TessellationAttribute::ControlOutput
|
||||||
|
: TessellationAttribute::EvaluationInput;
|
||||||
|
EXIT_NOT_IMPLEMENTED(write
|
||||||
|
? options.stage != ShaderType::TessellationControl
|
||||||
|
: options.stage != ShaderType::TessellationEvaluation);
|
||||||
|
if (address.IsImmediate() &&
|
||||||
|
address.U32() >= tess.hs_stride * tess.output_control_points) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(!write);
|
||||||
|
kind = TessellationAttribute::PatchOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto emit = [&](ValueOpcode opcode, std::initializer_list<Value> args) {
|
||||||
|
return Value(&*block->PrependNewInst(it, opcode, args));
|
||||||
|
};
|
||||||
|
std::array<Value, 4> values;
|
||||||
|
for (uint32_t component = 0; component < components; component++) {
|
||||||
|
const auto offset = memory.offset + 4u * component;
|
||||||
|
const auto byte_address =
|
||||||
|
offset == 0u ? address : emit(ValueOpcode::IAdd32, {address, Value(offset)});
|
||||||
|
if (write) {
|
||||||
|
Value data = shared != SharedAccess::None ? inst.Arg(component + 1u)
|
||||||
|
: components == 1u
|
||||||
|
? inst.Arg(4)
|
||||||
|
: emit(components == 2u ? ValueOpcode::CompositeExtractU32x2
|
||||||
|
: components == 3u ? ValueOpcode::CompositeExtractU32x3
|
||||||
|
: ValueOpcode::CompositeExtractU32x4,
|
||||||
|
{inst.Arg(4), Value(component)});
|
||||||
|
emit(ValueOpcode::SetTessellationAttribute,
|
||||||
|
{Value(static_cast<uint32_t>(kind)), byte_address, data, predicate});
|
||||||
|
writes++;
|
||||||
|
} else {
|
||||||
|
values[component] =
|
||||||
|
emit(ValueOpcode::GetTessellationAttribute,
|
||||||
|
{Value(static_cast<uint32_t>(kind)), byte_address, predicate});
|
||||||
|
reads++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!write) {
|
||||||
|
Value replacement = values[0];
|
||||||
|
if (components == 2u)
|
||||||
|
replacement =
|
||||||
|
emit(ValueOpcode::CompositeConstructU32x2, {values[0], values[1]});
|
||||||
|
if (components == 3u)
|
||||||
|
replacement = emit(ValueOpcode::CompositeConstructU32x3,
|
||||||
|
{values[0], values[1], values[2]});
|
||||||
|
if (components == 4u)
|
||||||
|
replacement = emit(ValueOpcode::CompositeConstructU32x4,
|
||||||
|
{values[0], values[1], values[2], values[3]});
|
||||||
|
inst.ReplaceUsesWith(replacement);
|
||||||
|
}
|
||||||
|
inst.Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConstantPropagationPass(program.blocks);
|
||||||
|
RemoveIdentities(program.blocks);
|
||||||
|
EliminateDeadCode(program.blocks);
|
||||||
|
LOGF("%s tessellation lowering: reads=%u writes=%u factors=%u\n",
|
||||||
|
options.stage == ShaderType::Local ? "LS"
|
||||||
|
: options.stage == ShaderType::TessellationControl ? "HS"
|
||||||
|
: "TES",
|
||||||
|
reads, writes, factors);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libs::Graphics::ShaderRecompiler
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace Libs::Graphics {
|
||||||
|
struct ShaderTessellationInputInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Libs::Graphics::ShaderRecompiler {
|
||||||
|
|
||||||
|
struct CompileOptions;
|
||||||
|
namespace IR {
|
||||||
|
struct Program;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnalyzeTessellationPrograms(std::span<const uint32_t> local, std::span<const uint32_t> control,
|
||||||
|
ShaderTessellationInputInfo& info);
|
||||||
|
void LowerTessellationMemory(IR::Program& program, const CompileOptions& options);
|
||||||
|
|
||||||
|
} // namespace Libs::Graphics::ShaderRecompiler
|
||||||
@@ -533,82 +533,6 @@ uint32_t EmitLaneId(EmitterState& state) {
|
|||||||
: EmitSubgroupLocalInvocationId(state);
|
: EmitSubgroupLocalInvocationId(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|
||||||
auto& state = ctx.state;
|
|
||||||
using Attribute = IR::TessellationAttribute;
|
|
||||||
const auto kind = static_cast<Attribute>(inst.Arg(0).U32());
|
|
||||||
const auto& tess = state.input_info.vertex->tess;
|
|
||||||
const auto variable = state.tess_variables.at(static_cast<uint32_t>(kind));
|
|
||||||
EXIT_IF(variable == 0);
|
|
||||||
const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
|
|
||||||
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
|
|
||||||
const auto pointer = state.builder.AllocateId();
|
|
||||||
if (kind == Attribute::Factor) {
|
|
||||||
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
|
||||||
const auto index = inst.Arg(1).U32() / 4u;
|
|
||||||
const auto outer = index < 3u;
|
|
||||||
EXIT_NOT_IMPLEMENTED(index >= 4u);
|
|
||||||
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
|
|
||||||
pointer, outer ? variable : state.tess_inner_variable,
|
|
||||||
ConstantU32(state, outer ? index : index - 3u));
|
|
||||||
return pointer;
|
|
||||||
}
|
|
||||||
auto address = ctx.Arg(inst, 1);
|
|
||||||
if (kind == Attribute::PatchOutput) {
|
|
||||||
address =
|
|
||||||
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base));
|
|
||||||
}
|
|
||||||
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
|
|
||||||
const auto stride = local ? tess.ls_stride : tess.hs_stride;
|
|
||||||
const auto offset = kind == Attribute::PatchOutput ? address
|
|
||||||
: EmitBinaryU32(state, spv::OpUMod, address,
|
|
||||||
ConstantU32(state, stride));
|
|
||||||
const auto attribute =
|
|
||||||
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
|
|
||||||
const auto component =
|
|
||||||
EmitBinaryU32(state, spv::OpBitwiseAnd,
|
|
||||||
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)),
|
|
||||||
ConstantU32(state, 3));
|
|
||||||
const auto type = TypePointer(state, storage, TypeU32(state));
|
|
||||||
if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) {
|
|
||||||
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
|
|
||||||
component);
|
|
||||||
} else {
|
|
||||||
const auto vertex =
|
|
||||||
kind == Attribute::ControlOutput
|
|
||||||
? EmitBuiltinU32(state, IR::StageInputKind::InvocationId, 0)
|
|
||||||
: EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride));
|
|
||||||
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute,
|
|
||||||
component);
|
|
||||||
}
|
|
||||||
return pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|
||||||
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] {
|
|
||||||
const auto value = ctx.state.builder.AllocateId();
|
|
||||||
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value,
|
|
||||||
TessellationPointer(ctx, inst));
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|
||||||
EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] {
|
|
||||||
auto value = ctx.Arg(inst, 2);
|
|
||||||
if (inst.Arg(0).U32() == static_cast<uint32_t>(IR::TessellationAttribute::Factor)) {
|
|
||||||
const auto floating = ctx.state.builder.AllocateId();
|
|
||||||
ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value);
|
|
||||||
value = floating;
|
|
||||||
}
|
|
||||||
ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
|
uint32_t EmitMeshDrawParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
const auto result = state.builder.AllocateId();
|
const auto result = state.builder.AllocateId();
|
||||||
|
|||||||
@@ -317,7 +317,11 @@ uint32_t ConstantU64(EmitterState& state, uint64_t value);
|
|||||||
|
|
||||||
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components);
|
uint32_t ConstantU32CompositeZero(EmitterState& state, uint32_t components);
|
||||||
|
|
||||||
|
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
|
||||||
|
const char* name);
|
||||||
void DefineModule(EmitterState& state);
|
void DefineModule(EmitterState& state);
|
||||||
|
void DefineTessellationInterfaces(EmitterState& state);
|
||||||
|
void DefineTessellationExecutionModes(EmitterState& state);
|
||||||
void DefineMeshOutputs(EmitterState& state);
|
void DefineMeshOutputs(EmitterState& state);
|
||||||
void EmitMeshEntryPoint(EmitterState& state);
|
void EmitMeshEntryPoint(EmitterState& state);
|
||||||
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
|
|||||||
@@ -334,8 +334,6 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
|
uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::StorageClass storage,
|
||||||
const char* name) {
|
const char* name) {
|
||||||
const auto variable =
|
const auto variable =
|
||||||
@@ -345,6 +343,8 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag
|
|||||||
return variable;
|
return variable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex;
|
case IR::StageInputKind::VertexIndex: return spv::BuiltInVertexIndex;
|
||||||
@@ -544,70 +544,6 @@ void DefineOutputs(EmitterState& state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefineTessellationInterfaces(EmitterState& state) {
|
|
||||||
using Attribute = IR::TessellationAttribute;
|
|
||||||
std::array<bool, 6> used {};
|
|
||||||
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
|
|
||||||
for (const auto* block: state.program.blocks) {
|
|
||||||
for (const auto& inst: *block) {
|
|
||||||
if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute &&
|
|
||||||
inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute)
|
|
||||||
continue;
|
|
||||||
const auto kind = inst.Arg(0).U32();
|
|
||||||
used.at(kind) = true;
|
|
||||||
if (kind == static_cast<uint32_t>(Attribute::PatchOutput)) {
|
|
||||||
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
|
||||||
patch_begin = std::min(patch_begin, inst.Arg(1).U32());
|
|
||||||
patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (std::ranges::none_of(used, [](bool value) { return value; })) return;
|
|
||||||
const auto& tess = state.input_info.vertex->tess;
|
|
||||||
const auto array = [&](uint32_t type, uint32_t count) {
|
|
||||||
return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
|
|
||||||
};
|
|
||||||
for (uint32_t index = 0; index < used.size(); index++) {
|
|
||||||
if (!used[index]) continue;
|
|
||||||
const auto kind = static_cast<Attribute>(index);
|
|
||||||
const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
|
|
||||||
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
|
|
||||||
uint32_t type;
|
|
||||||
if (kind == Attribute::Factor) {
|
|
||||||
type = array(TypeF32(state), 4u);
|
|
||||||
} else if (kind == Attribute::PatchOutput) {
|
|
||||||
state.tess_patch_base = patch_begin;
|
|
||||||
type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u);
|
|
||||||
} else {
|
|
||||||
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
|
|
||||||
const auto stride = local ? tess.ls_stride : tess.hs_stride;
|
|
||||||
type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u);
|
|
||||||
if (kind != Attribute::LocalOutput) {
|
|
||||||
type = array(type, local ? tess.input_control_points : tess.output_control_points);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto& variable = state.tess_variables[index];
|
|
||||||
variable = DefineInterfaceVariable(state, type, storage, "tess_attributes");
|
|
||||||
if (kind == Attribute::Factor) {
|
|
||||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
|
|
||||||
spv::BuiltInTessLevelOuter);
|
|
||||||
state.tess_inner_variable =
|
|
||||||
DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner");
|
|
||||||
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
|
|
||||||
spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
|
|
||||||
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
|
|
||||||
spv::DecorationPatch);
|
|
||||||
} else {
|
|
||||||
state.builder.AddAnnotation(
|
|
||||||
spv::OpDecorate, variable, spv::DecorationLocation,
|
|
||||||
kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u);
|
|
||||||
}
|
|
||||||
if (kind == Attribute::Factor || kind == Attribute::PatchOutput) {
|
|
||||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void DefineModule(EmitterState& state) {
|
void DefineModule(EmitterState& state) {
|
||||||
@@ -639,18 +575,7 @@ void DefineModule(EmitterState& state) {
|
|||||||
}
|
}
|
||||||
if (state.program.stage == ShaderType::TessellationControl ||
|
if (state.program.stage == ShaderType::TessellationControl ||
|
||||||
state.program.stage == ShaderType::TessellationEvaluation) {
|
state.program.stage == ShaderType::TessellationEvaluation) {
|
||||||
const auto& tess = state.input_info.vertex->tess;
|
DefineTessellationExecutionModes(state);
|
||||||
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u ||
|
|
||||||
tess.output_topology != 2u);
|
|
||||||
state.builder.RequireCapability(spv::CapabilityTessellation);
|
|
||||||
if (state.program.stage == ShaderType::TessellationControl) {
|
|
||||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
|
|
||||||
tess.output_control_points);
|
|
||||||
} else {
|
|
||||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
|
|
||||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd);
|
|
||||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
state.entry_label = state.builder.AllocateId();
|
state.entry_label = state.builder.AllocateId();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
|
auto& state = ctx.state;
|
||||||
|
using Attribute = IR::TessellationAttribute;
|
||||||
|
const auto kind = static_cast<Attribute>(inst.Arg(0).U32());
|
||||||
|
const auto& tess = state.input_info.vertex->tess;
|
||||||
|
const auto variable = state.tess_variables.at(static_cast<uint32_t>(kind));
|
||||||
|
EXIT_IF(variable == 0);
|
||||||
|
const auto input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
|
||||||
|
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
|
||||||
|
const auto pointer = state.builder.AllocateId();
|
||||||
|
if (kind == Attribute::Factor) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
||||||
|
const auto index = inst.Arg(1).U32() / 4u;
|
||||||
|
const auto outer = index < 3u;
|
||||||
|
EXIT_NOT_IMPLEMENTED(index >= 4u);
|
||||||
|
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
|
||||||
|
pointer, outer ? variable : state.tess_inner_variable,
|
||||||
|
ConstantU32(state, outer ? index : index - 3u));
|
||||||
|
return pointer;
|
||||||
|
}
|
||||||
|
auto address = ctx.Arg(inst, 1);
|
||||||
|
if (kind == Attribute::PatchOutput) {
|
||||||
|
address =
|
||||||
|
EmitBinaryU32(state, spv::OpISub, address, ConstantU32(state, state.tess_patch_base));
|
||||||
|
}
|
||||||
|
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
|
||||||
|
const auto stride = local ? tess.ls_stride : tess.hs_stride;
|
||||||
|
const auto offset = kind == Attribute::PatchOutput ? address
|
||||||
|
: EmitBinaryU32(state, spv::OpUMod, address,
|
||||||
|
ConstantU32(state, stride));
|
||||||
|
const auto attribute =
|
||||||
|
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 4));
|
||||||
|
const auto component =
|
||||||
|
EmitBinaryU32(state, spv::OpBitwiseAnd,
|
||||||
|
EmitBinaryU32(state, spv::OpShiftRightLogical, offset, ConstantU32(state, 2)),
|
||||||
|
ConstantU32(state, 3));
|
||||||
|
const auto type = TypePointer(state, storage, TypeU32(state));
|
||||||
|
if (kind == Attribute::LocalOutput || kind == Attribute::PatchOutput) {
|
||||||
|
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, attribute,
|
||||||
|
component);
|
||||||
|
} else {
|
||||||
|
const auto vertex =
|
||||||
|
kind == Attribute::ControlOutput
|
||||||
|
? EmitLaneId(state)
|
||||||
|
: EmitBinaryU32(state, spv::OpUDiv, address, ConstantU32(state, stride));
|
||||||
|
state.builder.AddFunction(spv::OpAccessChain, type, pointer, variable, vertex, attribute,
|
||||||
|
component);
|
||||||
|
}
|
||||||
|
return pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DefineTessellationInterfaces(EmitterState& state) {
|
||||||
|
using Attribute = IR::TessellationAttribute;
|
||||||
|
std::array<bool, 6> used {};
|
||||||
|
uint32_t patch_begin = UINT32_MAX, patch_end = 0;
|
||||||
|
for (const auto* block: state.program.blocks) {
|
||||||
|
for (const auto& inst: *block) {
|
||||||
|
if (inst.GetOpcode() != IR::ValueOpcode::GetTessellationAttribute &&
|
||||||
|
inst.GetOpcode() != IR::ValueOpcode::SetTessellationAttribute)
|
||||||
|
continue;
|
||||||
|
const auto kind = inst.Arg(0).U32();
|
||||||
|
used.at(kind) = true;
|
||||||
|
if (kind == static_cast<uint32_t>(Attribute::PatchOutput)) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
|
||||||
|
patch_begin = std::min(patch_begin, inst.Arg(1).U32());
|
||||||
|
patch_end = std::max(patch_end, inst.Arg(1).U32() + 4u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (std::ranges::none_of(used, [](bool value) { return value; })) return;
|
||||||
|
const auto& tess = state.input_info.vertex->tess;
|
||||||
|
const auto array = [&](uint32_t type, uint32_t count) {
|
||||||
|
return state.builder.Type(spv::OpTypeArray, type, ConstantU32(state, count));
|
||||||
|
};
|
||||||
|
for (uint32_t index = 0; index < used.size(); index++) {
|
||||||
|
if (!used[index]) continue;
|
||||||
|
const auto kind = static_cast<Attribute>(index);
|
||||||
|
const bool input = kind == Attribute::ControlInput || kind == Attribute::EvaluationInput;
|
||||||
|
const auto storage = input ? spv::StorageClassInput : spv::StorageClassOutput;
|
||||||
|
uint32_t type;
|
||||||
|
if (kind == Attribute::Factor) {
|
||||||
|
type = array(TypeF32(state), 4u);
|
||||||
|
} else if (kind == Attribute::PatchOutput) {
|
||||||
|
state.tess_patch_base = patch_begin;
|
||||||
|
type = array(TypeU32Vector(state, 4), (patch_end - patch_begin + 15u) / 16u);
|
||||||
|
} else {
|
||||||
|
const bool local = kind == Attribute::LocalOutput || kind == Attribute::ControlInput;
|
||||||
|
const auto stride = local ? tess.ls_stride : tess.hs_stride;
|
||||||
|
type = array(TypeU32Vector(state, 4), (stride + 15u) / 16u);
|
||||||
|
if (kind != Attribute::LocalOutput) {
|
||||||
|
type = array(type, local ? tess.input_control_points : tess.output_control_points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto& variable = state.tess_variables[index];
|
||||||
|
variable = DefineInterfaceVariable(state, type, storage, "tess_attributes");
|
||||||
|
if (kind == Attribute::Factor) {
|
||||||
|
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
|
||||||
|
spv::BuiltInTessLevelOuter);
|
||||||
|
state.tess_inner_variable =
|
||||||
|
DefineInterfaceVariable(state, array(TypeF32(state), 2u), storage, "tess_inner");
|
||||||
|
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
|
||||||
|
spv::DecorationBuiltIn, spv::BuiltInTessLevelInner);
|
||||||
|
state.builder.AddAnnotation(spv::OpDecorate, state.tess_inner_variable,
|
||||||
|
spv::DecorationPatch);
|
||||||
|
} else {
|
||||||
|
state.builder.AddAnnotation(
|
||||||
|
spv::OpDecorate, variable, spv::DecorationLocation,
|
||||||
|
kind == Attribute::PatchOutput ? (tess.hs_stride + 15u) / 16u : 0u);
|
||||||
|
}
|
||||||
|
if (kind == Attribute::Factor || kind == Attribute::PatchOutput) {
|
||||||
|
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DefineTessellationExecutionModes(EmitterState& state) {
|
||||||
|
const auto& tess = state.input_info.vertex->tess;
|
||||||
|
EXIT_NOT_IMPLEMENTED(tess.domain != 1u || tess.partitioning != 2u ||
|
||||||
|
tess.output_topology != 2u);
|
||||||
|
state.builder.RequireCapability(spv::CapabilityTessellation);
|
||||||
|
if (state.program.stage == ShaderType::TessellationControl) {
|
||||||
|
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
|
||||||
|
tess.output_control_points);
|
||||||
|
} else {
|
||||||
|
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
|
||||||
|
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSpacingFractionalOdd);
|
||||||
|
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EmitGetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
|
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, 2), [&] {
|
||||||
|
const auto value = ctx.state.builder.AllocateId();
|
||||||
|
ctx.state.builder.AddFunction(spv::OpLoad, TypeU32(ctx.state), value,
|
||||||
|
TessellationPointer(ctx, inst));
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitSetTessellationAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
|
EmitIfCondition(ctx.state, ctx.Arg(inst, 3), [&] {
|
||||||
|
auto value = ctx.Arg(inst, 2);
|
||||||
|
if (inst.Arg(0).U32() == static_cast<uint32_t>(IR::TessellationAttribute::Factor)) {
|
||||||
|
const auto floating = ctx.state.builder.AllocateId();
|
||||||
|
ctx.state.builder.AddFunction(spv::OpBitcast, TypeF32(ctx.state), floating, value);
|
||||||
|
value = floating;
|
||||||
|
}
|
||||||
|
ctx.state.builder.AddFunction(spv::OpStore, TessellationPointer(ctx, inst), value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||||
@@ -381,6 +381,25 @@ void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Inst
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Program DecodeFrontProgram(std::span<const uint32_t> front) {
|
||||||
|
Program result;
|
||||||
|
uint32_t front_words = 0;
|
||||||
|
while (front_words < front.size()) {
|
||||||
|
auto& inst = result.instructions.emplace_back();
|
||||||
|
DecodeInstruction(front, front_words, inst);
|
||||||
|
front_words += inst.word_count;
|
||||||
|
if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(inst.src0.kind != OperandKind::Sgpr || inst.src0.reg != 6u);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EXIT_NOT_IMPLEMENTED(inst.opcode == Opcode::S_ENDPGM);
|
||||||
|
}
|
||||||
|
EXIT_IF(result.instructions.empty() ||
|
||||||
|
result.instructions.back().opcode != Opcode::S_SETPC_B64);
|
||||||
|
result.code = front.first(front_words);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void DecodeProgram(std::span<const uint32_t> code, Program& program) {
|
void DecodeProgram(std::span<const uint32_t> code, Program& program) {
|
||||||
program.instructions.clear();
|
program.instructions.clear();
|
||||||
program.instructions.reserve(code.size());
|
program.instructions.reserve(code.size());
|
||||||
|
|||||||
@@ -722,6 +722,7 @@ struct Program {
|
|||||||
Family GetInstructionFamily(uint32_t word);
|
Family GetInstructionFamily(uint32_t word);
|
||||||
// The output object must be freshly initialized.
|
// The output object must be freshly initialized.
|
||||||
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
|
void DecodeInstruction(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst);
|
||||||
|
Program DecodeFrontProgram(std::span<const uint32_t> front);
|
||||||
void DecodeProgram(std::span<const uint32_t> code, Program& program);
|
void DecodeProgram(std::span<const uint32_t> code, Program& program);
|
||||||
bool IsConditionalBranch(Opcode opcode);
|
bool IsConditionalBranch(Opcode opcode);
|
||||||
bool IsDirectBranch(Opcode opcode);
|
bool IsDirectBranch(Opcode opcode);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "graphics/guest_gpu/hardwareContext.h"
|
#include "graphics/guest_gpu/hardwareContext.h"
|
||||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||||
|
#include "graphics/shader/recompiler/Tessellation.h"
|
||||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||||
#include "graphics/shader/shaderCompiler.h"
|
#include "graphics/shader/shaderCompiler.h"
|
||||||
#include "graphics/shader/shaderVertexMetadata.h"
|
#include "graphics/shader/shaderVertexMetadata.h"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "graphics/host_gpu/vulkanCommon.h"
|
#include "graphics/host_gpu/vulkanCommon.h"
|
||||||
#include "graphics/presentation/window/windowInternal.h"
|
#include "graphics/presentation/window/windowInternal.h"
|
||||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||||
|
#include "graphics/shader/recompiler/Tessellation.h"
|
||||||
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
||||||
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
||||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user