mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-12-28 19:30:58 +01:00
spirv: Initial bindings support
This commit is contained in:
parent
d5d468cf2c
commit
b5d7279d87
23 changed files with 679 additions and 300 deletions
externals
src/shader_recompiler
CMakeLists.txt
backend/spirv
emit_context.cppemit_context.hemit_spirv.cppemit_spirv.hemit_spirv_bitwise_conversion.cppemit_spirv_composite.cppemit_spirv_context_get_set.cppemit_spirv_control_flow.cppemit_spirv_floating_point.cppemit_spirv_integer.cppemit_spirv_memory.cppemit_spirv_undefined.cpp
frontend
ir_opt
collect_shader_info_pass.cppconstant_propagation_pass.cppglobal_memory_to_storage_buffer_pass.cpppasses.h
main.cppshader_info.h
2
externals/sirit
vendored
2
externals/sirit
vendored
|
@ -1 +1 @@
|
|||
Subproject commit f819ade0efe925a782090dea9e1bf300fedffb39
|
||||
Subproject commit 200310e8faa756b9869dd6dfc902c255246ac74a
|
|
@ -1,4 +1,6 @@
|
|||
add_executable(shader_recompiler
|
||||
backend/spirv/emit_context.cpp
|
||||
backend/spirv/emit_context.h
|
||||
backend/spirv/emit_spirv.cpp
|
||||
backend/spirv/emit_spirv.h
|
||||
backend/spirv/emit_spirv_bitwise_conversion.cpp
|
||||
|
@ -75,6 +77,7 @@ add_executable(shader_recompiler
|
|||
frontend/maxwell/translate/impl/move_special_register.cpp
|
||||
frontend/maxwell/translate/translate.cpp
|
||||
frontend/maxwell/translate/translate.h
|
||||
ir_opt/collect_shader_info_pass.cpp
|
||||
ir_opt/constant_propagation_pass.cpp
|
||||
ir_opt/dead_code_elimination_pass.cpp
|
||||
ir_opt/global_memory_to_storage_buffer_pass.cpp
|
||||
|
@ -84,6 +87,7 @@ add_executable(shader_recompiler
|
|||
ir_opt/verification_pass.cpp
|
||||
main.cpp
|
||||
object_pool.h
|
||||
shader_info.h
|
||||
)
|
||||
|
||||
target_include_directories(video_core PRIVATE sirit)
|
||||
|
|
160
src/shader_recompiler/backend/spirv/emit_context.cpp
Normal file
160
src/shader_recompiler/backend/spirv/emit_context.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_context.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
void VectorTypes::Define(Sirit::Module& sirit_ctx, Id base_type, std::string_view name) {
|
||||
defs[0] = sirit_ctx.Name(base_type, name);
|
||||
|
||||
std::array<char, 6> def_name;
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
const std::string_view def_name_view(
|
||||
def_name.data(),
|
||||
fmt::format_to_n(def_name.data(), def_name.size(), "{}x{}", name, i + 1).size);
|
||||
defs[i] = sirit_ctx.Name(sirit_ctx.TypeVector(base_type, i + 1), def_name_view);
|
||||
}
|
||||
}
|
||||
|
||||
EmitContext::EmitContext(IR::Program& program) : Sirit::Module(0x00010000) {
|
||||
AddCapability(spv::Capability::Shader);
|
||||
DefineCommonTypes(program.info);
|
||||
DefineCommonConstants();
|
||||
DefineSpecialVariables(program.info);
|
||||
DefineConstantBuffers(program.info);
|
||||
DefineStorageBuffers(program.info);
|
||||
DefineLabels(program);
|
||||
}
|
||||
|
||||
EmitContext::~EmitContext() = default;
|
||||
|
||||
Id EmitContext::Def(const IR::Value& value) {
|
||||
if (!value.IsImmediate()) {
|
||||
return value.Inst()->Definition<Id>();
|
||||
}
|
||||
switch (value.Type()) {
|
||||
case IR::Type::U1:
|
||||
return value.U1() ? true_value : false_value;
|
||||
case IR::Type::U32:
|
||||
return Constant(U32[1], value.U32());
|
||||
case IR::Type::F32:
|
||||
return Constant(F32[1], value.F32());
|
||||
default:
|
||||
throw NotImplementedException("Immediate type {}", value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
void EmitContext::DefineCommonTypes(const Info& info) {
|
||||
void_id = TypeVoid();
|
||||
|
||||
U1 = Name(TypeBool(), "u1");
|
||||
|
||||
F32.Define(*this, TypeFloat(32), "f32");
|
||||
U32.Define(*this, TypeInt(32, false), "u32");
|
||||
|
||||
if (info.uses_fp16) {
|
||||
AddCapability(spv::Capability::Float16);
|
||||
F16.Define(*this, TypeFloat(16), "f16");
|
||||
}
|
||||
if (info.uses_fp64) {
|
||||
AddCapability(spv::Capability::Float64);
|
||||
F64.Define(*this, TypeFloat(64), "f64");
|
||||
}
|
||||
}
|
||||
|
||||
void EmitContext::DefineCommonConstants() {
|
||||
true_value = ConstantTrue(U1);
|
||||
false_value = ConstantFalse(U1);
|
||||
u32_zero_value = Constant(U32[1], 0U);
|
||||
}
|
||||
|
||||
void EmitContext::DefineSpecialVariables(const Info& info) {
|
||||
const auto define{[this](Id type, spv::BuiltIn builtin, spv::StorageClass storage_class) {
|
||||
const Id pointer_type{TypePointer(storage_class, type)};
|
||||
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::Input)};
|
||||
Decorate(id, spv::Decoration::BuiltIn, builtin);
|
||||
return id;
|
||||
}};
|
||||
using namespace std::placeholders;
|
||||
const auto define_input{std::bind(define, _1, _2, spv::StorageClass::Input)};
|
||||
|
||||
if (info.uses_workgroup_id) {
|
||||
workgroup_id = define_input(U32[3], spv::BuiltIn::WorkgroupId);
|
||||
}
|
||||
if (info.uses_local_invocation_id) {
|
||||
local_invocation_id = define_input(U32[3], spv::BuiltIn::LocalInvocationId);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitContext::DefineConstantBuffers(const Info& info) {
|
||||
if (info.constant_buffer_descriptors.empty()) {
|
||||
return;
|
||||
}
|
||||
const Id array_type{TypeArray(U32[1], Constant(U32[1], 4096))};
|
||||
Decorate(array_type, spv::Decoration::ArrayStride, 16U);
|
||||
|
||||
const Id struct_type{TypeStruct(array_type)};
|
||||
Name(struct_type, "cbuf_block");
|
||||
Decorate(struct_type, spv::Decoration::Block);
|
||||
MemberName(struct_type, 0, "data");
|
||||
MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U);
|
||||
|
||||
const Id uniform_type{TypePointer(spv::StorageClass::Uniform, struct_type)};
|
||||
uniform_u32 = TypePointer(spv::StorageClass::Uniform, U32[1]);
|
||||
|
||||
u32 binding{};
|
||||
for (const Info::ConstantBufferDescriptor& desc : info.constant_buffer_descriptors) {
|
||||
const Id id{AddGlobalVariable(uniform_type, spv::StorageClass::Uniform)};
|
||||
Decorate(id, spv::Decoration::Binding, binding);
|
||||
Name(id, fmt::format("c{}", desc.index));
|
||||
std::fill_n(cbufs.data() + desc.index, desc.count, id);
|
||||
binding += desc.count;
|
||||
}
|
||||
}
|
||||
|
||||
void EmitContext::DefineStorageBuffers(const Info& info) {
|
||||
if (info.storage_buffers_descriptors.empty()) {
|
||||
return;
|
||||
}
|
||||
AddExtension("SPV_KHR_storage_buffer_storage_class");
|
||||
|
||||
const Id array_type{TypeRuntimeArray(U32[1])};
|
||||
Decorate(array_type, spv::Decoration::ArrayStride, 4U);
|
||||
|
||||
const Id struct_type{TypeStruct(array_type)};
|
||||
Name(struct_type, "ssbo_block");
|
||||
Decorate(struct_type, spv::Decoration::Block);
|
||||
MemberName(struct_type, 0, "data");
|
||||
MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U);
|
||||
|
||||
const Id storage_type{TypePointer(spv::StorageClass::StorageBuffer, struct_type)};
|
||||
storage_u32 = TypePointer(spv::StorageClass::StorageBuffer, U32[1]);
|
||||
|
||||
u32 binding{};
|
||||
for (const Info::StorageBufferDescriptor& desc : info.storage_buffers_descriptors) {
|
||||
const Id id{AddGlobalVariable(storage_type, spv::StorageClass::StorageBuffer)};
|
||||
Decorate(id, spv::Decoration::Binding, binding);
|
||||
Name(id, fmt::format("ssbo{}", binding));
|
||||
std::fill_n(ssbos.data() + binding, desc.count, id);
|
||||
binding += desc.count;
|
||||
}
|
||||
}
|
||||
|
||||
void EmitContext::DefineLabels(IR::Program& program) {
|
||||
for (const IR::Function& function : program.functions) {
|
||||
for (IR::Block* const block : function.blocks) {
|
||||
block->SetDefinition(OpLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
67
src/shader_recompiler/backend/spirv/emit_context.h
Normal file
67
src/shader_recompiler/backend/spirv/emit_context.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
#include <sirit/sirit.h>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
using Sirit::Id;
|
||||
|
||||
class VectorTypes {
|
||||
public:
|
||||
void Define(Sirit::Module& sirit_ctx, Id base_type, std::string_view name);
|
||||
|
||||
[[nodiscard]] Id operator[](size_t size) const noexcept {
|
||||
return defs[size - 1];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Id, 4> defs{};
|
||||
};
|
||||
|
||||
class EmitContext final : public Sirit::Module {
|
||||
public:
|
||||
explicit EmitContext(IR::Program& program);
|
||||
~EmitContext();
|
||||
|
||||
[[nodiscard]] Id Def(const IR::Value& value);
|
||||
|
||||
Id void_id{};
|
||||
Id U1{};
|
||||
VectorTypes F32;
|
||||
VectorTypes U32;
|
||||
VectorTypes F16;
|
||||
VectorTypes F64;
|
||||
|
||||
Id true_value{};
|
||||
Id false_value{};
|
||||
Id u32_zero_value{};
|
||||
|
||||
Id uniform_u32{};
|
||||
Id storage_u32{};
|
||||
|
||||
std::array<Id, Info::MAX_CBUFS> cbufs{};
|
||||
std::array<Id, Info::MAX_SSBOS> ssbos{};
|
||||
|
||||
Id workgroup_id{};
|
||||
Id local_invocation_id{};
|
||||
|
||||
private:
|
||||
void DefineCommonTypes(const Info& info);
|
||||
void DefineCommonConstants();
|
||||
void DefineSpecialVariables(const Info& info);
|
||||
void DefineConstantBuffers(const Info& info);
|
||||
void DefineStorageBuffers(const Info& info);
|
||||
void DefineLabels(IR::Program& program);
|
||||
};
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
|
@ -12,60 +12,22 @@
|
|||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
namespace {
|
||||
template <class Func>
|
||||
struct FuncTraits : FuncTraits<decltype(&Func::operator())> {};
|
||||
|
||||
EmitContext::EmitContext(IR::Program& program) {
|
||||
AddCapability(spv::Capability::Shader);
|
||||
AddCapability(spv::Capability::Float16);
|
||||
AddCapability(spv::Capability::Float64);
|
||||
void_id = TypeVoid();
|
||||
template <class ClassType, class ReturnType_, class... Args>
|
||||
struct FuncTraits<ReturnType_ (ClassType::*)(Args...)> {
|
||||
using ReturnType = ReturnType_;
|
||||
|
||||
u1 = Name(TypeBool(), "u1");
|
||||
f32.Define(*this, TypeFloat(32), "f32");
|
||||
u32.Define(*this, TypeInt(32, false), "u32");
|
||||
f16.Define(*this, TypeFloat(16), "f16");
|
||||
f64.Define(*this, TypeFloat(64), "f64");
|
||||
static constexpr size_t NUM_ARGS = sizeof...(Args);
|
||||
|
||||
true_value = ConstantTrue(u1);
|
||||
false_value = ConstantFalse(u1);
|
||||
|
||||
for (const IR::Function& function : program.functions) {
|
||||
for (IR::Block* const block : function.blocks) {
|
||||
block_label_map.emplace_back(block, OpLabel());
|
||||
}
|
||||
}
|
||||
std::ranges::sort(block_label_map, {}, &std::pair<IR::Block*, Id>::first);
|
||||
}
|
||||
|
||||
EmitContext::~EmitContext() = default;
|
||||
|
||||
EmitSPIRV::EmitSPIRV(IR::Program& program) {
|
||||
EmitContext ctx{program};
|
||||
const Id void_function{ctx.TypeFunction(ctx.void_id)};
|
||||
// FIXME: Forward declare functions (needs sirit support)
|
||||
Id func{};
|
||||
for (IR::Function& function : program.functions) {
|
||||
func = ctx.OpFunction(ctx.void_id, spv::FunctionControlMask::MaskNone, void_function);
|
||||
for (IR::Block* const block : function.blocks) {
|
||||
ctx.AddLabel(ctx.BlockLabel(block));
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
EmitInst(ctx, &inst);
|
||||
}
|
||||
}
|
||||
ctx.OpFunctionEnd();
|
||||
}
|
||||
ctx.AddEntryPoint(spv::ExecutionModel::GLCompute, func, "main");
|
||||
|
||||
std::vector<u32> result{ctx.Assemble()};
|
||||
std::FILE* file{std::fopen("shader.spv", "wb")};
|
||||
std::fwrite(result.data(), sizeof(u32), result.size(), file);
|
||||
std::fclose(file);
|
||||
std::system("spirv-dis shader.spv");
|
||||
std::system("spirv-val shader.spv");
|
||||
std::system("spirv-cross shader.spv");
|
||||
}
|
||||
template <size_t I>
|
||||
using ArgType = std::tuple_element_t<I, std::tuple<Args...>>;
|
||||
};
|
||||
|
||||
template <auto method, typename... Args>
|
||||
static void SetDefinition(EmitSPIRV& emit, EmitContext& ctx, IR::Inst* inst, Args... args) {
|
||||
void SetDefinition(EmitSPIRV& emit, EmitContext& ctx, IR::Inst* inst, Args... args) {
|
||||
const Id forward_id{inst->Definition<Id>()};
|
||||
const bool has_forward_id{Sirit::ValidId(forward_id)};
|
||||
Id current_id{};
|
||||
|
@ -80,42 +42,90 @@ static void SetDefinition(EmitSPIRV& emit, EmitContext& ctx, IR::Inst* inst, Arg
|
|||
}
|
||||
}
|
||||
|
||||
template <auto method>
|
||||
static void Invoke(EmitSPIRV& emit, EmitContext& ctx, IR::Inst* inst) {
|
||||
using M = decltype(method);
|
||||
using std::is_invocable_r_v;
|
||||
if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&>) {
|
||||
SetDefinition<method>(emit, ctx, inst);
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, Id>) {
|
||||
SetDefinition<method>(emit, ctx, inst, ctx.Def(inst->Arg(0)));
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, Id, Id>) {
|
||||
SetDefinition<method>(emit, ctx, inst, ctx.Def(inst->Arg(0)), ctx.Def(inst->Arg(1)));
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, Id, Id, Id>) {
|
||||
SetDefinition<method>(emit, ctx, inst, ctx.Def(inst->Arg(0)), ctx.Def(inst->Arg(1)),
|
||||
ctx.Def(inst->Arg(2)));
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, IR::Inst*>) {
|
||||
SetDefinition<method>(emit, ctx, inst, inst);
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, IR::Inst*, Id, Id>) {
|
||||
SetDefinition<method>(emit, ctx, inst, inst, ctx.Def(inst->Arg(0)), ctx.Def(inst->Arg(1)));
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, IR::Inst*, Id, Id, Id>) {
|
||||
SetDefinition<method>(emit, ctx, inst, inst, ctx.Def(inst->Arg(0)), ctx.Def(inst->Arg(1)),
|
||||
ctx.Def(inst->Arg(2)));
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, Id, u32>) {
|
||||
SetDefinition<method>(emit, ctx, inst, ctx.Def(inst->Arg(0)), inst->Arg(1).U32());
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, const IR::Value&>) {
|
||||
SetDefinition<method>(emit, ctx, inst, inst->Arg(0));
|
||||
} else if constexpr (is_invocable_r_v<Id, M, EmitSPIRV&, EmitContext&, const IR::Value&,
|
||||
const IR::Value&>) {
|
||||
SetDefinition<method>(emit, ctx, inst, inst->Arg(0), inst->Arg(1));
|
||||
} else if constexpr (is_invocable_r_v<void, M, EmitSPIRV&, EmitContext&, IR::Inst*>) {
|
||||
(emit.*method)(ctx, inst);
|
||||
} else if constexpr (is_invocable_r_v<void, M, EmitSPIRV&, EmitContext&>) {
|
||||
(emit.*method)(ctx);
|
||||
} else {
|
||||
static_assert(false, "Bad format");
|
||||
template <typename ArgType>
|
||||
ArgType Arg(EmitContext& ctx, const IR::Value& arg) {
|
||||
if constexpr (std::is_same_v<ArgType, Id>) {
|
||||
return ctx.Def(arg);
|
||||
} else if constexpr (std::is_same_v<ArgType, const IR::Value&>) {
|
||||
return arg;
|
||||
} else if constexpr (std::is_same_v<ArgType, u32>) {
|
||||
return arg.U32();
|
||||
} else if constexpr (std::is_same_v<ArgType, IR::Block*>) {
|
||||
return arg.Label();
|
||||
}
|
||||
}
|
||||
|
||||
template <auto method, bool is_first_arg_inst, size_t... I>
|
||||
void Invoke(EmitSPIRV& emit, EmitContext& ctx, IR::Inst* inst, std::index_sequence<I...>) {
|
||||
using Traits = FuncTraits<decltype(method)>;
|
||||
if constexpr (std::is_same_v<Traits::ReturnType, Id>) {
|
||||
if constexpr (is_first_arg_inst) {
|
||||
SetDefinition<method>(emit, ctx, inst, inst,
|
||||
Arg<Traits::ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||
} else {
|
||||
SetDefinition<method>(emit, ctx, inst,
|
||||
Arg<Traits::ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||
}
|
||||
} else {
|
||||
if constexpr (is_first_arg_inst) {
|
||||
(emit.*method)(ctx, inst, Arg<Traits::ArgType<I + 2>>(ctx, inst->Arg(I))...);
|
||||
} else {
|
||||
(emit.*method)(ctx, Arg<Traits::ArgType<I + 1>>(ctx, inst->Arg(I))...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <auto method>
|
||||
void Invoke(EmitSPIRV& emit, EmitContext& ctx, IR::Inst* inst) {
|
||||
using Traits = FuncTraits<decltype(method)>;
|
||||
static_assert(Traits::NUM_ARGS >= 1, "Insufficient arguments");
|
||||
if constexpr (Traits::NUM_ARGS == 1) {
|
||||
Invoke<method, false>(emit, ctx, inst, std::make_index_sequence<0>{});
|
||||
} else {
|
||||
using FirstArgType = typename Traits::template ArgType<1>;
|
||||
static constexpr bool is_first_arg_inst = std::is_same_v<FirstArgType, IR::Inst*>;
|
||||
using Indices = std::make_index_sequence<Traits::NUM_ARGS - (is_first_arg_inst ? 2 : 1)>;
|
||||
Invoke<method, is_first_arg_inst>(emit, ctx, inst, Indices{});
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
EmitSPIRV::EmitSPIRV(IR::Program& program) {
|
||||
EmitContext ctx{program};
|
||||
const Id void_function{ctx.TypeFunction(ctx.void_id)};
|
||||
// FIXME: Forward declare functions (needs sirit support)
|
||||
Id func{};
|
||||
for (IR::Function& function : program.functions) {
|
||||
func = ctx.OpFunction(ctx.void_id, spv::FunctionControlMask::MaskNone, void_function);
|
||||
for (IR::Block* const block : function.blocks) {
|
||||
ctx.AddLabel(block->Definition<Id>());
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
EmitInst(ctx, &inst);
|
||||
}
|
||||
}
|
||||
ctx.OpFunctionEnd();
|
||||
}
|
||||
boost::container::small_vector<Id, 32> interfaces;
|
||||
if (program.info.uses_workgroup_id) {
|
||||
interfaces.push_back(ctx.workgroup_id);
|
||||
}
|
||||
if (program.info.uses_local_invocation_id) {
|
||||
interfaces.push_back(ctx.local_invocation_id);
|
||||
}
|
||||
|
||||
const std::span interfaces_span(interfaces.data(), interfaces.size());
|
||||
ctx.AddEntryPoint(spv::ExecutionModel::Fragment, func, "main", interfaces_span);
|
||||
ctx.AddExecutionMode(func, spv::ExecutionMode::OriginUpperLeft);
|
||||
|
||||
std::vector<u32> result{ctx.Assemble()};
|
||||
std::FILE* file{std::fopen("D:\\shader.spv", "wb")};
|
||||
std::fwrite(result.data(), sizeof(u32), result.size(), file);
|
||||
std::fclose(file);
|
||||
std::system("spirv-dis D:\\shader.spv") == 0 &&
|
||||
std::system("spirv-val --uniform-buffer-standard-layout D:\\shader.spv") == 0 &&
|
||||
std::system("spirv-cross -V D:\\shader.spv") == 0;
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitInst(EmitContext& ctx, IR::Inst* inst) {
|
||||
switch (inst->Opcode()) {
|
||||
#define OPCODE(name, result_type, ...) \
|
||||
|
@ -130,9 +140,9 @@ void EmitSPIRV::EmitInst(EmitContext& ctx, IR::Inst* inst) {
|
|||
static Id TypeId(const EmitContext& ctx, IR::Type type) {
|
||||
switch (type) {
|
||||
case IR::Type::U1:
|
||||
return ctx.u1;
|
||||
return ctx.U1;
|
||||
case IR::Type::U32:
|
||||
return ctx.u32[1];
|
||||
return ctx.U32[1];
|
||||
default:
|
||||
throw NotImplementedException("Phi node type {}", type);
|
||||
}
|
||||
|
@ -162,7 +172,7 @@ Id EmitSPIRV::EmitPhi(EmitContext& ctx, IR::Inst* inst) {
|
|||
}
|
||||
IR::Block* const phi_block{inst->PhiBlock(index)};
|
||||
operands.push_back(def);
|
||||
operands.push_back(ctx.BlockLabel(phi_block));
|
||||
operands.push_back(phi_block->Definition<Id>());
|
||||
}
|
||||
const Id result_type{TypeId(ctx, inst->Arg(0).Type())};
|
||||
return ctx.OpPhi(result_type, std::span(operands.data(), operands.size()));
|
||||
|
@ -174,29 +184,6 @@ void EmitSPIRV::EmitIdentity(EmitContext&) {
|
|||
throw NotImplementedException("SPIR-V Instruction");
|
||||
}
|
||||
|
||||
// FIXME: Move to its own file
|
||||
void EmitSPIRV::EmitBranch(EmitContext& ctx, IR::Inst* inst) {
|
||||
ctx.OpBranch(ctx.BlockLabel(inst->Arg(0).Label()));
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitBranchConditional(EmitContext& ctx, IR::Inst* inst) {
|
||||
ctx.OpBranchConditional(ctx.Def(inst->Arg(0)), ctx.BlockLabel(inst->Arg(1).Label()),
|
||||
ctx.BlockLabel(inst->Arg(2).Label()));
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitLoopMerge(EmitContext& ctx, IR::Inst* inst) {
|
||||
ctx.OpLoopMerge(ctx.BlockLabel(inst->Arg(0).Label()), ctx.BlockLabel(inst->Arg(1).Label()),
|
||||
spv::LoopControlMask::MaskNone);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitSelectionMerge(EmitContext& ctx, IR::Inst* inst) {
|
||||
ctx.OpSelectionMerge(ctx.BlockLabel(inst->Arg(0).Label()), spv::SelectionControlMask::MaskNone);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitReturn(EmitContext& ctx) {
|
||||
ctx.OpReturn();
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitGetZeroFromOp(EmitContext&) {
|
||||
throw LogicError("Unreachable instruction");
|
||||
}
|
||||
|
|
|
@ -7,82 +7,12 @@
|
|||
#include <sirit/sirit.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_context.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
using Sirit::Id;
|
||||
|
||||
class VectorTypes {
|
||||
public:
|
||||
void Define(Sirit::Module& sirit_ctx, Id base_type, std::string_view name) {
|
||||
defs[0] = sirit_ctx.Name(base_type, name);
|
||||
|
||||
std::array<char, 6> def_name;
|
||||
for (int i = 1; i < 4; ++i) {
|
||||
const std::string_view def_name_view(
|
||||
def_name.data(),
|
||||
fmt::format_to_n(def_name.data(), def_name.size(), "{}x{}", name, i + 1).size);
|
||||
defs[i] = sirit_ctx.Name(sirit_ctx.TypeVector(base_type, i + 1), def_name_view);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] Id operator[](size_t size) const noexcept {
|
||||
return defs[size - 1];
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<Id, 4> defs;
|
||||
};
|
||||
|
||||
class EmitContext final : public Sirit::Module {
|
||||
public:
|
||||
explicit EmitContext(IR::Program& program);
|
||||
~EmitContext();
|
||||
|
||||
[[nodiscard]] Id Def(const IR::Value& value) {
|
||||
if (!value.IsImmediate()) {
|
||||
return value.Inst()->Definition<Id>();
|
||||
}
|
||||
switch (value.Type()) {
|
||||
case IR::Type::U1:
|
||||
return value.U1() ? true_value : false_value;
|
||||
case IR::Type::U32:
|
||||
return Constant(u32[1], value.U32());
|
||||
case IR::Type::F32:
|
||||
return Constant(f32[1], value.F32());
|
||||
default:
|
||||
throw NotImplementedException("Immediate type {}", value.Type());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] Id BlockLabel(IR::Block* block) const {
|
||||
const auto it{std::ranges::lower_bound(block_label_map, block, {},
|
||||
&std::pair<IR::Block*, Id>::first)};
|
||||
if (it == block_label_map.end()) {
|
||||
throw LogicError("Undefined block");
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Id void_id{};
|
||||
Id u1{};
|
||||
VectorTypes f32;
|
||||
VectorTypes u32;
|
||||
VectorTypes f16;
|
||||
VectorTypes f64;
|
||||
|
||||
Id true_value{};
|
||||
Id false_value{};
|
||||
|
||||
Id workgroup_id{};
|
||||
Id local_invocation_id{};
|
||||
|
||||
private:
|
||||
std::vector<std::pair<IR::Block*, Id>> block_label_map;
|
||||
};
|
||||
|
||||
class EmitSPIRV {
|
||||
public:
|
||||
explicit EmitSPIRV(IR::Program& program);
|
||||
|
@ -94,10 +24,11 @@ private:
|
|||
Id EmitPhi(EmitContext& ctx, IR::Inst* inst);
|
||||
void EmitVoid(EmitContext& ctx);
|
||||
void EmitIdentity(EmitContext& ctx);
|
||||
void EmitBranch(EmitContext& ctx, IR::Inst* inst);
|
||||
void EmitBranchConditional(EmitContext& ctx, IR::Inst* inst);
|
||||
void EmitLoopMerge(EmitContext& ctx, IR::Inst* inst);
|
||||
void EmitSelectionMerge(EmitContext& ctx, IR::Inst* inst);
|
||||
void EmitBranch(EmitContext& ctx, IR::Block* label);
|
||||
void EmitBranchConditional(EmitContext& ctx, Id condition, IR::Block* true_label,
|
||||
IR::Block* false_label);
|
||||
void EmitLoopMerge(EmitContext& ctx, IR::Block* merge_label, IR::Block* continue_label);
|
||||
void EmitSelectionMerge(EmitContext& ctx, IR::Block* merge_label);
|
||||
void EmitReturn(EmitContext& ctx);
|
||||
void EmitGetRegister(EmitContext& ctx);
|
||||
void EmitSetRegister(EmitContext& ctx);
|
||||
|
@ -150,7 +81,8 @@ private:
|
|||
void EmitWriteStorageS8(EmitContext& ctx);
|
||||
void EmitWriteStorageU16(EmitContext& ctx);
|
||||
void EmitWriteStorageS16(EmitContext& ctx);
|
||||
void EmitWriteStorage32(EmitContext& ctx);
|
||||
void EmitWriteStorage32(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset,
|
||||
Id value);
|
||||
void EmitWriteStorage64(EmitContext& ctx);
|
||||
void EmitWriteStorage128(EmitContext& ctx);
|
||||
void EmitCompositeConstructU32x2(EmitContext& ctx);
|
||||
|
|
|
@ -11,7 +11,7 @@ void EmitSPIRV::EmitBitCastU16F16(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitBitCastU32F32(EmitContext& ctx, Id value) {
|
||||
return ctx.OpBitcast(ctx.u32[1], value);
|
||||
return ctx.OpBitcast(ctx.U32[1], value);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitBitCastU64F64(EmitContext&) {
|
||||
|
@ -23,7 +23,7 @@ void EmitSPIRV::EmitBitCastF16U16(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitBitCastF32U32(EmitContext& ctx, Id value) {
|
||||
return ctx.OpBitcast(ctx.f32[1], value);
|
||||
return ctx.OpBitcast(ctx.F32[1], value);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitBitCastF64U64(EmitContext&) {
|
||||
|
|
|
@ -23,7 +23,7 @@ void EmitSPIRV::EmitCompositeExtractU32x2(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitCompositeExtractU32x3(EmitContext& ctx, Id vector, u32 index) {
|
||||
return ctx.OpCompositeExtract(ctx.u32[1], vector, index);
|
||||
return ctx.OpCompositeExtract(ctx.U32[1], vector, index);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitCompositeExtractU32x4(EmitContext&) {
|
||||
|
|
|
@ -37,7 +37,10 @@ Id EmitSPIRV::EmitGetCbuf(EmitContext& ctx, const IR::Value& binding, const IR::
|
|||
if (!offset.IsImmediate()) {
|
||||
throw NotImplementedException("Variable constant buffer offset");
|
||||
}
|
||||
return ctx.Name(ctx.OpUndef(ctx.u32[1]), "unimplemented_cbuf");
|
||||
const Id imm_offset{ctx.Constant(ctx.U32[1], offset.U32() / 4)};
|
||||
const Id cbuf{ctx.cbufs[binding.U32()]};
|
||||
const Id access_chain{ctx.OpAccessChain(ctx.uniform_u32, cbuf, ctx.u32_zero_value, imm_offset)};
|
||||
return ctx.OpLoad(ctx.U32[1], access_chain);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitGetAttribute(EmitContext&) {
|
||||
|
@ -89,22 +92,11 @@ void EmitSPIRV::EmitSetOFlag(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitWorkgroupId(EmitContext& ctx) {
|
||||
if (ctx.workgroup_id.value == 0) {
|
||||
ctx.workgroup_id = ctx.AddGlobalVariable(
|
||||
ctx.TypePointer(spv::StorageClass::Input, ctx.u32[3]), spv::StorageClass::Input);
|
||||
ctx.Decorate(ctx.workgroup_id, spv::Decoration::BuiltIn, spv::BuiltIn::WorkgroupId);
|
||||
}
|
||||
return ctx.OpLoad(ctx.u32[3], ctx.workgroup_id);
|
||||
return ctx.OpLoad(ctx.U32[3], ctx.workgroup_id);
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitLocalInvocationId(EmitContext& ctx) {
|
||||
if (ctx.local_invocation_id.value == 0) {
|
||||
ctx.local_invocation_id = ctx.AddGlobalVariable(
|
||||
ctx.TypePointer(spv::StorageClass::Input, ctx.u32[3]), spv::StorageClass::Input);
|
||||
ctx.Decorate(ctx.local_invocation_id, spv::Decoration::BuiltIn,
|
||||
spv::BuiltIn::LocalInvocationId);
|
||||
}
|
||||
return ctx.OpLoad(ctx.u32[3], ctx.local_invocation_id);
|
||||
return ctx.OpLoad(ctx.U32[3], ctx.local_invocation_id);
|
||||
}
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
||||
|
|
|
@ -3,3 +3,29 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
void EmitSPIRV::EmitBranch(EmitContext& ctx, IR::Block* label) {
|
||||
ctx.OpBranch(label->Definition<Id>());
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitBranchConditional(EmitContext& ctx, Id condition, IR::Block* true_label,
|
||||
IR::Block* false_label) {
|
||||
ctx.OpBranchConditional(condition, true_label->Definition<Id>(), false_label->Definition<Id>());
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitLoopMerge(EmitContext& ctx, IR::Block* merge_label, IR::Block* continue_label) {
|
||||
ctx.OpLoopMerge(merge_label->Definition<Id>(), continue_label->Definition<Id>(),
|
||||
spv::LoopControlMask::MaskNone);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitSelectionMerge(EmitContext& ctx, IR::Block* merge_label) {
|
||||
ctx.OpSelectionMerge(merge_label->Definition<Id>(), spv::SelectionControlMask::MaskNone);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitReturn(EmitContext& ctx) {
|
||||
ctx.OpReturn();
|
||||
}
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
||||
|
|
|
@ -46,27 +46,27 @@ void EmitSPIRV::EmitFPAbs64(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPAdd16(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||
return Decorate(ctx, inst, ctx.OpFAdd(ctx.f16[1], a, b));
|
||||
return Decorate(ctx, inst, ctx.OpFAdd(ctx.F16[1], a, b));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||
return Decorate(ctx, inst, ctx.OpFAdd(ctx.f32[1], a, b));
|
||||
return Decorate(ctx, inst, ctx.OpFAdd(ctx.F32[1], a, b));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPAdd64(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||
return Decorate(ctx, inst, ctx.OpFAdd(ctx.f64[1], a, b));
|
||||
return Decorate(ctx, inst, ctx.OpFAdd(ctx.F64[1], a, b));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPFma16(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
|
||||
return Decorate(ctx, inst, ctx.OpFma(ctx.f16[1], a, b, c));
|
||||
return Decorate(ctx, inst, ctx.OpFma(ctx.F16[1], a, b, c));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPFma32(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
|
||||
return Decorate(ctx, inst, ctx.OpFma(ctx.f32[1], a, b, c));
|
||||
return Decorate(ctx, inst, ctx.OpFma(ctx.F32[1], a, b, c));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPFma64(EmitContext& ctx, IR::Inst* inst, Id a, Id b, Id c) {
|
||||
return Decorate(ctx, inst, ctx.OpFma(ctx.f64[1], a, b, c));
|
||||
return Decorate(ctx, inst, ctx.OpFma(ctx.F64[1], a, b, c));
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitFPMax32(EmitContext&) {
|
||||
|
@ -86,15 +86,15 @@ void EmitSPIRV::EmitFPMin64(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPMul16(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||
return Decorate(ctx, inst, ctx.OpFMul(ctx.f16[1], a, b));
|
||||
return Decorate(ctx, inst, ctx.OpFMul(ctx.F16[1], a, b));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPMul32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||
return Decorate(ctx, inst, ctx.OpFMul(ctx.f32[1], a, b));
|
||||
return Decorate(ctx, inst, ctx.OpFMul(ctx.F32[1], a, b));
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitFPMul64(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
||||
return Decorate(ctx, inst, ctx.OpFMul(ctx.f64[1], a, b));
|
||||
return Decorate(ctx, inst, ctx.OpFMul(ctx.F64[1], a, b));
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitFPNeg16(EmitContext&) {
|
||||
|
|
|
@ -10,7 +10,7 @@ Id EmitSPIRV::EmitIAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b) {
|
|||
if (inst->HasAssociatedPseudoOperation()) {
|
||||
throw NotImplementedException("Pseudo-operations on IAdd32");
|
||||
}
|
||||
return ctx.OpIAdd(ctx.u32[1], a, b);
|
||||
return ctx.OpIAdd(ctx.U32[1], a, b);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitIAdd64(EmitContext&) {
|
||||
|
@ -18,7 +18,7 @@ void EmitSPIRV::EmitIAdd64(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitISub32(EmitContext& ctx, Id a, Id b) {
|
||||
return ctx.OpISub(ctx.u32[1], a, b);
|
||||
return ctx.OpISub(ctx.U32[1], a, b);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitISub64(EmitContext&) {
|
||||
|
@ -26,7 +26,7 @@ void EmitSPIRV::EmitISub64(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitIMul32(EmitContext& ctx, Id a, Id b) {
|
||||
return ctx.OpIMul(ctx.u32[1], a, b);
|
||||
return ctx.OpIMul(ctx.U32[1], a, b);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitINeg32(EmitContext&) {
|
||||
|
@ -38,7 +38,7 @@ void EmitSPIRV::EmitIAbs32(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitShiftLeftLogical32(EmitContext& ctx, Id base, Id shift) {
|
||||
return ctx.OpShiftLeftLogical(ctx.u32[1], base, shift);
|
||||
return ctx.OpShiftLeftLogical(ctx.U32[1], base, shift);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitShiftRightLogical32(EmitContext&) {
|
||||
|
@ -70,11 +70,11 @@ void EmitSPIRV::EmitBitFieldSExtract(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitBitFieldUExtract(EmitContext& ctx, Id base, Id offset, Id count) {
|
||||
return ctx.OpBitFieldUExtract(ctx.u32[1], base, offset, count);
|
||||
return ctx.OpBitFieldUExtract(ctx.U32[1], base, offset, count);
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitSLessThan(EmitContext& ctx, Id lhs, Id rhs) {
|
||||
return ctx.OpSLessThan(ctx.u1, lhs, rhs);
|
||||
return ctx.OpSLessThan(ctx.U1, lhs, rhs);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitULessThan(EmitContext&) {
|
||||
|
@ -94,7 +94,7 @@ void EmitSPIRV::EmitULessThanEqual(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitSGreaterThan(EmitContext& ctx, Id lhs, Id rhs) {
|
||||
return ctx.OpSGreaterThan(ctx.u1, lhs, rhs);
|
||||
return ctx.OpSGreaterThan(ctx.U1, lhs, rhs);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitUGreaterThan(EmitContext&) {
|
||||
|
@ -110,7 +110,7 @@ void EmitSPIRV::EmitSGreaterThanEqual(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitUGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs) {
|
||||
return ctx.OpUGreaterThanEqual(ctx.u1, lhs, rhs);
|
||||
return ctx.OpUGreaterThanEqual(ctx.U1, lhs, rhs);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitLogicalOr(EmitContext&) {
|
||||
|
|
|
@ -2,10 +2,26 @@
|
|||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <bit>
|
||||
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
static Id StorageIndex(EmitContext& ctx, const IR::Value& offset, size_t element_size) {
|
||||
if (offset.IsImmediate()) {
|
||||
const u32 imm_offset{static_cast<u32>(offset.U32() / element_size)};
|
||||
return ctx.Constant(ctx.U32[1], imm_offset);
|
||||
}
|
||||
const u32 shift{static_cast<u32>(std::countr_zero(element_size))};
|
||||
const Id index{ctx.Def(offset)};
|
||||
if (shift == 0) {
|
||||
return index;
|
||||
}
|
||||
const Id shift_id{ctx.Constant(ctx.U32[1], shift)};
|
||||
return ctx.OpShiftRightLogical(ctx.U32[1], index, shift_id);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitLoadGlobalU8(EmitContext&) {
|
||||
throw NotImplementedException("SPIR-V Instruction");
|
||||
}
|
||||
|
@ -79,11 +95,14 @@ void EmitSPIRV::EmitLoadStorageS16(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitLoadStorage32(EmitContext& ctx, const IR::Value& binding,
|
||||
[[maybe_unused]] const IR::Value& offset) {
|
||||
const IR::Value& offset) {
|
||||
if (!binding.IsImmediate()) {
|
||||
throw NotImplementedException("Storage buffer indexing");
|
||||
throw NotImplementedException("Dynamic storage buffer indexing");
|
||||
}
|
||||
return ctx.Name(ctx.OpUndef(ctx.u32[1]), "unimplemented_sbuf");
|
||||
const Id ssbo{ctx.ssbos[binding.U32()]};
|
||||
const Id index{StorageIndex(ctx, offset, sizeof(u32))};
|
||||
const Id pointer{ctx.OpAccessChain(ctx.storage_u32, ssbo, ctx.u32_zero_value, index)};
|
||||
return ctx.OpLoad(ctx.U32[1], pointer);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitLoadStorage64(EmitContext&) {
|
||||
|
@ -110,8 +129,15 @@ void EmitSPIRV::EmitWriteStorageS16(EmitContext&) {
|
|||
throw NotImplementedException("SPIR-V Instruction");
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitWriteStorage32(EmitContext& ctx) {
|
||||
ctx.Name(ctx.OpUndef(ctx.u32[1]), "unimplemented_sbuf_store");
|
||||
void EmitSPIRV::EmitWriteStorage32(EmitContext& ctx, const IR::Value& binding,
|
||||
const IR::Value& offset, Id value) {
|
||||
if (!binding.IsImmediate()) {
|
||||
throw NotImplementedException("Dynamic storage buffer indexing");
|
||||
}
|
||||
const Id ssbo{ctx.ssbos[binding.U32()]};
|
||||
const Id index{StorageIndex(ctx, offset, sizeof(u32))};
|
||||
const Id pointer{ctx.OpAccessChain(ctx.storage_u32, ssbo, ctx.u32_zero_value, index)};
|
||||
ctx.OpStore(pointer, value);
|
||||
}
|
||||
|
||||
void EmitSPIRV::EmitWriteStorage64(EmitContext&) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
Id EmitSPIRV::EmitUndefU1(EmitContext& ctx) {
|
||||
return ctx.OpUndef(ctx.u1);
|
||||
return ctx.OpUndef(ctx.U1);
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitUndefU8(EmitContext&) {
|
||||
|
@ -19,7 +19,7 @@ Id EmitSPIRV::EmitUndefU16(EmitContext&) {
|
|||
}
|
||||
|
||||
Id EmitSPIRV::EmitUndefU32(EmitContext& ctx) {
|
||||
return ctx.OpUndef(ctx.u32[1]);
|
||||
return ctx.OpUndef(ctx.U32[1]);
|
||||
}
|
||||
|
||||
Id EmitSPIRV::EmitUndefU64(EmitContext&) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "shader_recompiler/frontend/ir/condition.h"
|
||||
#include "shader_recompiler/frontend/ir/microinstruction.h"
|
||||
#include "shader_recompiler/frontend/ir/value.h"
|
||||
|
@ -68,6 +69,18 @@ public:
|
|||
/// Gets an immutable span to the immediate predecessors.
|
||||
[[nodiscard]] std::span<Block* const> ImmediatePredecessors() const noexcept;
|
||||
|
||||
/// Intrusively store the host definition of this instruction.
|
||||
template <typename DefinitionType>
|
||||
void SetDefinition(DefinitionType def) {
|
||||
definition = Common::BitCast<u32>(def);
|
||||
}
|
||||
|
||||
/// Return the intrusively stored host definition of this instruction.
|
||||
template <typename DefinitionType>
|
||||
[[nodiscard]] DefinitionType Definition() const noexcept {
|
||||
return Common::BitCast<DefinitionType>(definition);
|
||||
}
|
||||
|
||||
[[nodiscard]] Condition BranchCondition() const noexcept {
|
||||
return branch_cond;
|
||||
}
|
||||
|
@ -161,6 +174,9 @@ private:
|
|||
Block* branch_false{nullptr};
|
||||
/// Block immediate predecessors
|
||||
std::vector<Block*> imm_predecessors;
|
||||
|
||||
/// Intrusively stored host definition of this block.
|
||||
u32 definition{};
|
||||
};
|
||||
|
||||
using BlockList = std::vector<Block*>;
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "shader_recompiler/frontend/ir/function.h"
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
struct Program {
|
||||
boost::container::small_vector<Function, 1> functions;
|
||||
Info info;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string DumpProgram(const Program& program);
|
||||
|
|
|
@ -53,21 +53,22 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
|
|||
for (Flow::Function& cfg_function : cfg.Functions()) {
|
||||
functions.push_back(IR::Function{
|
||||
.blocks{TranslateCode(inst_pool, block_pool, env, cfg_function)},
|
||||
.post_order_blocks{},
|
||||
});
|
||||
}
|
||||
|
||||
fmt::print(stdout, "No optimizations: {}", IR::DumpProgram(program));
|
||||
for (IR::Function& function : functions) {
|
||||
function.post_order_blocks = PostOrder(function.blocks);
|
||||
Optimization::SsaRewritePass(function.post_order_blocks);
|
||||
}
|
||||
fmt::print(stdout, "{}\n", IR::DumpProgram(program));
|
||||
Optimization::GlobalMemoryToStorageBufferPass(program);
|
||||
for (IR::Function& function : functions) {
|
||||
Optimization::PostOrderInvoke(Optimization::GlobalMemoryToStorageBufferPass, function);
|
||||
Optimization::PostOrderInvoke(Optimization::ConstantPropagationPass, function);
|
||||
Optimization::PostOrderInvoke(Optimization::DeadCodeEliminationPass, function);
|
||||
Optimization::IdentityRemovalPass(function);
|
||||
Optimization::VerificationPass(function);
|
||||
}
|
||||
Optimization::CollectShaderInfoPass(program);
|
||||
//*/
|
||||
return program;
|
||||
}
|
||||
|
|
81
src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
Normal file
81
src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
namespace {
|
||||
void AddConstantBufferDescriptor(Info& info, u32 index) {
|
||||
auto& descriptor{info.constant_buffers.at(index)};
|
||||
if (descriptor) {
|
||||
return;
|
||||
}
|
||||
descriptor = &info.constant_buffer_descriptors.emplace_back(Info::ConstantBufferDescriptor{
|
||||
.index{index},
|
||||
.count{1},
|
||||
});
|
||||
}
|
||||
|
||||
void Visit(Info& info, IR::Inst& inst) {
|
||||
switch (inst.Opcode()) {
|
||||
case IR::Opcode::WorkgroupId:
|
||||
info.uses_workgroup_id = true;
|
||||
break;
|
||||
case IR::Opcode::LocalInvocationId:
|
||||
info.uses_local_invocation_id = true;
|
||||
break;
|
||||
case IR::Opcode::FPAbs16:
|
||||
case IR::Opcode::FPAdd16:
|
||||
case IR::Opcode::FPCeil16:
|
||||
case IR::Opcode::FPFloor16:
|
||||
case IR::Opcode::FPFma16:
|
||||
case IR::Opcode::FPMul16:
|
||||
case IR::Opcode::FPNeg16:
|
||||
case IR::Opcode::FPRoundEven16:
|
||||
case IR::Opcode::FPSaturate16:
|
||||
case IR::Opcode::FPTrunc16:
|
||||
info.uses_fp16;
|
||||
break;
|
||||
case IR::Opcode::FPAbs64:
|
||||
case IR::Opcode::FPAdd64:
|
||||
case IR::Opcode::FPCeil64:
|
||||
case IR::Opcode::FPFloor64:
|
||||
case IR::Opcode::FPFma64:
|
||||
case IR::Opcode::FPMax64:
|
||||
case IR::Opcode::FPMin64:
|
||||
case IR::Opcode::FPMul64:
|
||||
case IR::Opcode::FPNeg64:
|
||||
case IR::Opcode::FPRecip64:
|
||||
case IR::Opcode::FPRecipSqrt64:
|
||||
case IR::Opcode::FPRoundEven64:
|
||||
case IR::Opcode::FPSaturate64:
|
||||
case IR::Opcode::FPTrunc64:
|
||||
info.uses_fp64 = true;
|
||||
break;
|
||||
case IR::Opcode::GetCbuf:
|
||||
if (const IR::Value index{inst.Arg(0)}; index.IsImmediate()) {
|
||||
AddConstantBufferDescriptor(info, index.U32());
|
||||
} else {
|
||||
throw NotImplementedException("Constant buffer with non-immediate index");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void CollectShaderInfoPass(IR::Program& program) {
|
||||
Info& info{program.info};
|
||||
for (IR::Function& function : program.functions) {
|
||||
for (IR::Block* const block : function.post_order_blocks) {
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
Visit(info, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Optimization
|
|
@ -77,6 +77,16 @@ bool FoldCommutative(IR::Inst& inst, ImmFn&& imm_fn) {
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
bool FoldWhenAllImmediates(IR::Inst& inst, Func&& func) {
|
||||
if (!inst.AreAllArgsImmediates() || inst.HasAssociatedPseudoOperation()) {
|
||||
return false;
|
||||
}
|
||||
using Indices = std::make_index_sequence<LambdaTraits<decltype(func)>::NUM_ARGS>;
|
||||
inst.ReplaceUsesWith(EvalImmediates(inst, func, Indices{}));
|
||||
return true;
|
||||
}
|
||||
|
||||
void FoldGetRegister(IR::Inst& inst) {
|
||||
if (inst.Arg(0).Reg() == IR::Reg::RZ) {
|
||||
inst.ReplaceUsesWith(IR::Value{u32{0}});
|
||||
|
@ -103,6 +113,52 @@ void FoldAdd(IR::Inst& inst) {
|
|||
}
|
||||
}
|
||||
|
||||
void FoldISub32(IR::Inst& inst) {
|
||||
if (FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a - b; })) {
|
||||
return;
|
||||
}
|
||||
if (inst.Arg(0).IsImmediate() || inst.Arg(1).IsImmediate()) {
|
||||
return;
|
||||
}
|
||||
// ISub32 is generally used to subtract two constant buffers, compare and replace this with
|
||||
// zero if they equal.
|
||||
const auto equal_cbuf{[](IR::Inst* a, IR::Inst* b) {
|
||||
return a->Opcode() == IR::Opcode::GetCbuf && b->Opcode() == IR::Opcode::GetCbuf &&
|
||||
a->Arg(0) == b->Arg(0) && a->Arg(1) == b->Arg(1);
|
||||
}};
|
||||
IR::Inst* op_a{inst.Arg(0).InstRecursive()};
|
||||
IR::Inst* op_b{inst.Arg(1).InstRecursive()};
|
||||
if (equal_cbuf(op_a, op_b)) {
|
||||
inst.ReplaceUsesWith(IR::Value{u32{0}});
|
||||
return;
|
||||
}
|
||||
// It's also possible a value is being added to a cbuf and then subtracted
|
||||
if (op_b->Opcode() == IR::Opcode::IAdd32) {
|
||||
// Canonicalize local variables to simplify the following logic
|
||||
std::swap(op_a, op_b);
|
||||
}
|
||||
if (op_b->Opcode() != IR::Opcode::GetCbuf) {
|
||||
return;
|
||||
}
|
||||
IR::Inst* const inst_cbuf{op_b};
|
||||
if (op_a->Opcode() != IR::Opcode::IAdd32) {
|
||||
return;
|
||||
}
|
||||
IR::Value add_op_a{op_a->Arg(0)};
|
||||
IR::Value add_op_b{op_a->Arg(1)};
|
||||
if (add_op_b.IsImmediate()) {
|
||||
// Canonicalize
|
||||
std::swap(add_op_a, add_op_b);
|
||||
}
|
||||
if (add_op_b.IsImmediate()) {
|
||||
return;
|
||||
}
|
||||
IR::Inst* const add_cbuf{add_op_b.InstRecursive()};
|
||||
if (equal_cbuf(add_cbuf, inst_cbuf)) {
|
||||
inst.ReplaceUsesWith(add_op_a);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void FoldSelect(IR::Inst& inst) {
|
||||
const IR::Value cond{inst.Arg(0)};
|
||||
|
@ -170,15 +226,6 @@ IR::Value EvalImmediates(const IR::Inst& inst, Func&& func, std::index_sequence<
|
|||
return IR::Value{func(Arg<Traits::ArgType<I>>(inst.Arg(I))...)};
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void FoldWhenAllImmediates(IR::Inst& inst, Func&& func) {
|
||||
if (!inst.AreAllArgsImmediates() || inst.HasAssociatedPseudoOperation()) {
|
||||
return;
|
||||
}
|
||||
using Indices = std::make_index_sequence<LambdaTraits<decltype(func)>::NUM_ARGS>;
|
||||
inst.ReplaceUsesWith(EvalImmediates(inst, func, Indices{}));
|
||||
}
|
||||
|
||||
void FoldBranchConditional(IR::Inst& inst) {
|
||||
const IR::U1 cond{inst.Arg(0)};
|
||||
if (cond.IsImmediate()) {
|
||||
|
@ -205,6 +252,8 @@ void ConstantPropagation(IR::Inst& inst) {
|
|||
return FoldGetPred(inst);
|
||||
case IR::Opcode::IAdd32:
|
||||
return FoldAdd<u32>(inst);
|
||||
case IR::Opcode::ISub32:
|
||||
return FoldISub32(inst);
|
||||
case IR::Opcode::BitCastF32U32:
|
||||
return FoldBitCast<f32, u32>(inst, IR::Opcode::BitCastU32F32);
|
||||
case IR::Opcode::BitCastU32F32:
|
||||
|
@ -220,17 +269,20 @@ void ConstantPropagation(IR::Inst& inst) {
|
|||
case IR::Opcode::LogicalNot:
|
||||
return FoldLogicalNot(inst);
|
||||
case IR::Opcode::SLessThan:
|
||||
return FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a < b; });
|
||||
FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a < b; });
|
||||
return;
|
||||
case IR::Opcode::ULessThan:
|
||||
return FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a < b; });
|
||||
FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a < b; });
|
||||
return;
|
||||
case IR::Opcode::BitFieldUExtract:
|
||||
return FoldWhenAllImmediates(inst, [](u32 base, u32 shift, u32 count) {
|
||||
FoldWhenAllImmediates(inst, [](u32 base, u32 shift, u32 count) {
|
||||
if (static_cast<size_t>(shift) + static_cast<size_t>(count) > Common::BitSize<u32>()) {
|
||||
throw LogicError("Undefined result in {}({}, {}, {})", IR::Opcode::BitFieldUExtract,
|
||||
base, shift, count);
|
||||
}
|
||||
return (base >> shift) & ((1U << count) - 1);
|
||||
});
|
||||
return;
|
||||
case IR::Opcode::BranchConditional:
|
||||
return FoldBranchConditional(inst);
|
||||
default:
|
||||
|
|
|
@ -28,7 +28,8 @@ struct StorageBufferAddr {
|
|||
/// Block iterator to a global memory instruction and the storage buffer it uses
|
||||
struct StorageInst {
|
||||
StorageBufferAddr storage_buffer;
|
||||
IR::Block::iterator inst;
|
||||
IR::Inst* inst;
|
||||
IR::Block* block;
|
||||
};
|
||||
|
||||
/// Bias towards a certain range of constant buffers when looking for storage buffers
|
||||
|
@ -41,7 +42,7 @@ struct Bias {
|
|||
using StorageBufferSet =
|
||||
boost::container::flat_set<StorageBufferAddr, std::less<StorageBufferAddr>,
|
||||
boost::container::small_vector<StorageBufferAddr, 16>>;
|
||||
using StorageInstVector = boost::container::small_vector<StorageInst, 32>;
|
||||
using StorageInstVector = boost::container::small_vector<StorageInst, 24>;
|
||||
|
||||
/// Returns true when the instruction is a global memory instruction
|
||||
bool IsGlobalMemory(const IR::Inst& inst) {
|
||||
|
@ -109,23 +110,22 @@ bool MeetsBias(const StorageBufferAddr& storage_buffer, const Bias& bias) noexce
|
|||
}
|
||||
|
||||
/// Discards a global memory operation, reads return zero and writes are ignored
|
||||
void DiscardGlobalMemory(IR::Block& block, IR::Block::iterator inst) {
|
||||
void DiscardGlobalMemory(IR::Block& block, IR::Inst& inst) {
|
||||
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
const IR::Value zero{u32{0}};
|
||||
switch (inst->Opcode()) {
|
||||
switch (inst.Opcode()) {
|
||||
case IR::Opcode::LoadGlobalS8:
|
||||
case IR::Opcode::LoadGlobalU8:
|
||||
case IR::Opcode::LoadGlobalS16:
|
||||
case IR::Opcode::LoadGlobalU16:
|
||||
case IR::Opcode::LoadGlobal32:
|
||||
inst->ReplaceUsesWith(zero);
|
||||
inst.ReplaceUsesWith(zero);
|
||||
break;
|
||||
case IR::Opcode::LoadGlobal64:
|
||||
inst->ReplaceUsesWith(IR::Value{
|
||||
&*block.PrependNewInst(inst, IR::Opcode::CompositeConstructU32x2, {zero, zero})});
|
||||
inst.ReplaceUsesWith(IR::Value{ir.CompositeConstruct(zero, zero)});
|
||||
break;
|
||||
case IR::Opcode::LoadGlobal128:
|
||||
inst->ReplaceUsesWith(IR::Value{&*block.PrependNewInst(
|
||||
inst, IR::Opcode::CompositeConstructU32x4, {zero, zero, zero, zero})});
|
||||
inst.ReplaceUsesWith(IR::Value{ir.CompositeConstruct(zero, zero, zero, zero)});
|
||||
break;
|
||||
case IR::Opcode::WriteGlobalS8:
|
||||
case IR::Opcode::WriteGlobalU8:
|
||||
|
@ -134,11 +134,10 @@ void DiscardGlobalMemory(IR::Block& block, IR::Block::iterator inst) {
|
|||
case IR::Opcode::WriteGlobal32:
|
||||
case IR::Opcode::WriteGlobal64:
|
||||
case IR::Opcode::WriteGlobal128:
|
||||
inst->Invalidate();
|
||||
inst.Invalidate();
|
||||
break;
|
||||
default:
|
||||
throw LogicError("Invalid opcode to discard its global memory operation {}",
|
||||
inst->Opcode());
|
||||
throw LogicError("Invalid opcode to discard its global memory operation {}", inst.Opcode());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,8 +231,8 @@ std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias)
|
|||
}
|
||||
|
||||
/// Collects the storage buffer used by a global memory instruction and the instruction itself
|
||||
void CollectStorageBuffers(IR::Block& block, IR::Block::iterator inst,
|
||||
StorageBufferSet& storage_buffer_set, StorageInstVector& to_replace) {
|
||||
void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageBufferSet& storage_buffer_set,
|
||||
StorageInstVector& to_replace) {
|
||||
// NVN puts storage buffers in a specific range, we have to bias towards these addresses to
|
||||
// avoid getting false positives
|
||||
static constexpr Bias nvn_bias{
|
||||
|
@ -241,19 +240,13 @@ void CollectStorageBuffers(IR::Block& block, IR::Block::iterator inst,
|
|||
.offset_begin{0x110},
|
||||
.offset_end{0x610},
|
||||
};
|
||||
// First try to find storage buffers in the NVN address
|
||||
const IR::U64 addr{inst->Arg(0)};
|
||||
if (addr.IsImmediate()) {
|
||||
// Immediate addresses can't be lowered to a storage buffer
|
||||
DiscardGlobalMemory(block, inst);
|
||||
return;
|
||||
}
|
||||
// Track the low address of the instruction
|
||||
const std::optional<LowAddrInfo> low_addr_info{TrackLowAddress(addr.InstRecursive())};
|
||||
const std::optional<LowAddrInfo> low_addr_info{TrackLowAddress(&inst)};
|
||||
if (!low_addr_info) {
|
||||
DiscardGlobalMemory(block, inst);
|
||||
return;
|
||||
}
|
||||
// First try to find storage buffers in the NVN address
|
||||
const IR::U32 low_addr{low_addr_info->value};
|
||||
std::optional<StorageBufferAddr> storage_buffer{Track(low_addr, &nvn_bias)};
|
||||
if (!storage_buffer) {
|
||||
|
@ -269,21 +262,22 @@ void CollectStorageBuffers(IR::Block& block, IR::Block::iterator inst,
|
|||
storage_buffer_set.insert(*storage_buffer);
|
||||
to_replace.push_back(StorageInst{
|
||||
.storage_buffer{*storage_buffer},
|
||||
.inst{inst},
|
||||
.inst{&inst},
|
||||
.block{&block},
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the offset in indices (not bytes) for an equivalent storage instruction
|
||||
IR::U32 StorageOffset(IR::Block& block, IR::Block::iterator inst, StorageBufferAddr buffer) {
|
||||
IR::IREmitter ir{block, inst};
|
||||
IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer) {
|
||||
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
IR::U32 offset;
|
||||
if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&*inst)}) {
|
||||
if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) {
|
||||
offset = low_addr->value;
|
||||
if (low_addr->imm_offset != 0) {
|
||||
offset = ir.IAdd(offset, ir.Imm32(low_addr->imm_offset));
|
||||
}
|
||||
} else {
|
||||
offset = ir.ConvertU(32, IR::U64{inst->Arg(0)});
|
||||
offset = ir.ConvertU(32, IR::U64{inst.Arg(0)});
|
||||
}
|
||||
// Subtract the least significant 32 bits from the guest offset. The result is the storage
|
||||
// buffer offset in bytes.
|
||||
|
@ -292,25 +286,27 @@ IR::U32 StorageOffset(IR::Block& block, IR::Block::iterator inst, StorageBufferA
|
|||
}
|
||||
|
||||
/// Replace a global memory load instruction with its storage buffer equivalent
|
||||
void ReplaceLoad(IR::Block& block, IR::Block::iterator inst, const IR::U32& storage_index,
|
||||
void ReplaceLoad(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
|
||||
const IR::U32& offset) {
|
||||
const IR::Opcode new_opcode{GlobalToStorage(inst->Opcode())};
|
||||
const IR::Value value{&*block.PrependNewInst(inst, new_opcode, {storage_index, offset})};
|
||||
inst->ReplaceUsesWith(value);
|
||||
const IR::Opcode new_opcode{GlobalToStorage(inst.Opcode())};
|
||||
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
const IR::Value value{&*block.PrependNewInst(it, new_opcode, {storage_index, offset})};
|
||||
inst.ReplaceUsesWith(value);
|
||||
}
|
||||
|
||||
/// Replace a global memory write instruction with its storage buffer equivalent
|
||||
void ReplaceWrite(IR::Block& block, IR::Block::iterator inst, const IR::U32& storage_index,
|
||||
void ReplaceWrite(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
|
||||
const IR::U32& offset) {
|
||||
const IR::Opcode new_opcode{GlobalToStorage(inst->Opcode())};
|
||||
block.PrependNewInst(inst, new_opcode, {storage_index, offset, inst->Arg(1)});
|
||||
inst->Invalidate();
|
||||
const IR::Opcode new_opcode{GlobalToStorage(inst.Opcode())};
|
||||
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
block.PrependNewInst(it, new_opcode, {storage_index, offset, inst.Arg(1)});
|
||||
inst.Invalidate();
|
||||
}
|
||||
|
||||
/// Replace a global memory instruction with its storage buffer equivalent
|
||||
void Replace(IR::Block& block, IR::Block::iterator inst, const IR::U32& storage_index,
|
||||
void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
|
||||
const IR::U32& offset) {
|
||||
switch (inst->Opcode()) {
|
||||
switch (inst.Opcode()) {
|
||||
case IR::Opcode::LoadGlobalS8:
|
||||
case IR::Opcode::LoadGlobalU8:
|
||||
case IR::Opcode::LoadGlobalS16:
|
||||
|
@ -328,26 +324,44 @@ void Replace(IR::Block& block, IR::Block::iterator inst, const IR::U32& storage_
|
|||
case IR::Opcode::WriteGlobal128:
|
||||
return ReplaceWrite(block, inst, storage_index, offset);
|
||||
default:
|
||||
throw InvalidArgument("Invalid global memory opcode {}", inst->Opcode());
|
||||
throw InvalidArgument("Invalid global memory opcode {}", inst.Opcode());
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void GlobalMemoryToStorageBufferPass(IR::Block& block) {
|
||||
void GlobalMemoryToStorageBufferPass(IR::Program& program) {
|
||||
StorageBufferSet storage_buffers;
|
||||
StorageInstVector to_replace;
|
||||
|
||||
for (IR::Block::iterator inst{block.begin()}; inst != block.end(); ++inst) {
|
||||
if (!IsGlobalMemory(*inst)) {
|
||||
continue;
|
||||
for (IR::Function& function : program.functions) {
|
||||
for (IR::Block* const block : function.post_order_blocks) {
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
if (!IsGlobalMemory(inst)) {
|
||||
continue;
|
||||
}
|
||||
CollectStorageBuffers(*block, inst, storage_buffers, to_replace);
|
||||
}
|
||||
}
|
||||
CollectStorageBuffers(block, inst, storage_buffers, to_replace);
|
||||
}
|
||||
for (const auto [storage_buffer, inst] : to_replace) {
|
||||
const auto it{storage_buffers.find(storage_buffer)};
|
||||
const IR::U32 storage_index{IR::Value{static_cast<u32>(storage_buffers.index_of(it))}};
|
||||
const IR::U32 offset{StorageOffset(block, inst, storage_buffer)};
|
||||
Replace(block, inst, storage_index, offset);
|
||||
Info& info{program.info};
|
||||
u32 storage_index{};
|
||||
for (const StorageBufferAddr& storage_buffer : storage_buffers) {
|
||||
info.storage_buffers_descriptors.push_back({
|
||||
.cbuf_index{storage_buffer.index},
|
||||
.cbuf_offset{storage_buffer.offset},
|
||||
.count{1},
|
||||
});
|
||||
info.storage_buffers[storage_index] = &info.storage_buffers_descriptors.back();
|
||||
++storage_index;
|
||||
}
|
||||
for (const StorageInst& storage_inst : to_replace) {
|
||||
const StorageBufferAddr storage_buffer{storage_inst.storage_buffer};
|
||||
const auto it{storage_buffers.find(storage_inst.storage_buffer)};
|
||||
const IR::U32 index{IR::Value{static_cast<u32>(storage_buffers.index_of(it))}};
|
||||
IR::Block* const block{storage_inst.block};
|
||||
IR::Inst* const inst{storage_inst.inst};
|
||||
const IR::U32 offset{StorageOffset(*block, *inst, storage_buffer)};
|
||||
Replace(*block, *inst, index, offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/function.h"
|
||||
#include "shader_recompiler/frontend/ir/program.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
|
@ -18,9 +19,10 @@ void PostOrderInvoke(Func&& func, IR::Function& function) {
|
|||
}
|
||||
}
|
||||
|
||||
void CollectShaderInfoPass(IR::Program& program);
|
||||
void ConstantPropagationPass(IR::Block& block);
|
||||
void DeadCodeEliminationPass(IR::Block& block);
|
||||
void GlobalMemoryToStorageBufferPass(IR::Block& block);
|
||||
void GlobalMemoryToStorageBufferPass(IR::Program& program);
|
||||
void IdentityRemovalPass(IR::Function& function);
|
||||
void SsaRewritePass(std::span<IR::Block* const> post_order_blocks);
|
||||
void VerificationPass(const IR::Function& function);
|
||||
|
|
|
@ -67,8 +67,8 @@ int main() {
|
|||
ObjectPool<IR::Inst> inst_pool;
|
||||
ObjectPool<IR::Block> block_pool;
|
||||
|
||||
// FileEnvironment env{"D:\\Shaders\\Database\\Oninaki\\CS8F146B41DB6BD826.bin"};
|
||||
FileEnvironment env{"D:\\Shaders\\shader.bin"};
|
||||
FileEnvironment env{"D:\\Shaders\\Database\\Oninaki\\CS8F146B41DB6BD826.bin"};
|
||||
// FileEnvironment env{"D:\\Shaders\\shader.bin"};
|
||||
block_pool.ReleaseContents();
|
||||
inst_pool.ReleaseContents();
|
||||
flow_block_pool.ReleaseContents();
|
||||
|
|
|
@ -6,23 +6,40 @@
|
|||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
namespace Shader {
|
||||
|
||||
struct Info {
|
||||
struct ConstantBuffer {
|
||||
static constexpr size_t MAX_CBUFS{18};
|
||||
static constexpr size_t MAX_SSBOS{16};
|
||||
|
||||
struct ConstantBufferDescriptor {
|
||||
u32 index;
|
||||
u32 count;
|
||||
};
|
||||
|
||||
struct {
|
||||
bool workgroup_id{};
|
||||
bool local_invocation_id{};
|
||||
bool fp16{};
|
||||
bool fp64{};
|
||||
} uses;
|
||||
struct StorageBufferDescriptor {
|
||||
u32 cbuf_index;
|
||||
u32 cbuf_offset;
|
||||
u32 count;
|
||||
};
|
||||
|
||||
std::array<18
|
||||
bool uses_workgroup_id{};
|
||||
bool uses_local_invocation_id{};
|
||||
bool uses_fp16{};
|
||||
bool uses_fp64{};
|
||||
|
||||
u32 constant_buffer_mask{};
|
||||
|
||||
std::array<ConstantBufferDescriptor*, MAX_CBUFS> constant_buffers{};
|
||||
boost::container::static_vector<ConstantBufferDescriptor, MAX_CBUFS>
|
||||
constant_buffer_descriptors;
|
||||
|
||||
std::array<StorageBufferDescriptor*, MAX_SSBOS> storage_buffers{};
|
||||
boost::container::static_vector<StorageBufferDescriptor, MAX_SSBOS> storage_buffers_descriptors;
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
|
Loading…
Reference in a new issue