Compare commits

...

1 Commits

Author SHA1 Message Date
5cdc08f6ea Vertex spirv 2023-01-05 17:00:43 +02:00
9 changed files with 1489 additions and 37 deletions

View File

@ -221,7 +221,7 @@ public:
int scope = 0;
private:
public:
void AddExpression(std::string_view text) {
if (!text.empty()) {
shader_source.append(static_cast<std::size_t>(scope) * 4, ' ');
@ -816,6 +816,7 @@ private:
}
void Generate() {
bool dump = false;
if (sanitize_mul) {
#ifdef ANDROID
// Use a cheaper sanitize_mul on Android, as mobile GPUs struggle here
@ -884,6 +885,8 @@ private:
u32 compile_end = CompileRange(label, next_label);
if (compile_end > next_label && compile_end != PROGRAM_END) {
// This happens only when there is a label inside a IF/LOOP block
dump = true;
LOG_INFO(Render_OpenGL, "compile_end: {}", compile_end);
shader.AddLine("{{ jmp_to = {}u; break; }}", compile_end);
labels.emplace(compile_end);
}
@ -906,6 +909,10 @@ private:
DEBUG_ASSERT(shader.scope == 0);
}
if (dump) {
LOG_INFO(Render_OpenGL, "{}", shader.shader_source);
}
}
private:

View File

@ -239,9 +239,17 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::Regs& regs,
config.state.emulated_attrib_locations[location] = is_supported ? 0 : emulated_attrib_loc++;
}
auto [handle, result] =
programmable_vertex_shaders.Get(config, setup, vk::ShaderStageFlagBits::eVertex,
instance.GetDevice(), ShaderOptimization::High);
vk::ShaderModule handle{};
if (Settings::values.spirv_shader_gen.GetValue()) {
std::optional<std::vector<u32>> code;
std::tie(handle, code) = programmable_vertex_shaders_spv.Get(config, setup,
instance.GetDevice());
} else {
std::optional<std::string> code;
std::tie(handle, code) = programmable_vertex_shaders.Get(config, setup, vk::ShaderStageFlagBits::eVertex,
instance.GetDevice(), ShaderOptimization::High);
}
if (!handle) {
LOG_ERROR(Render_Vulkan, "Failed to retrieve programmable vertex shader");
return false;

View File

@ -9,6 +9,7 @@
#include "common/hash.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/regs.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/renderer_vulkan/vk_shader_gen_spv.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/shader/shader_cache.h"
@ -111,7 +112,12 @@ struct PipelineInfo {
* Vulkan specialized PICA shader caches
*/
using ProgrammableVertexShaders = Pica::Shader::ShaderDoubleCache<PicaVSConfig, vk::ShaderModule,
&Compile, &GenerateVertexShader>;
std::string, &Compile,
&GenerateVertexShader>;
using ProgrammableVertexShadersSPV = Pica::Shader::ShaderDoubleCache<PicaVSConfig, vk::ShaderModule,
std::vector<u32>, &CompileSPV,
&GenerateVertexShaderSPV>;
using FixedGeometryShaders = Pica::Shader::ShaderCache<PicaFixedGSConfig, vk::ShaderModule,
&Compile, &GenerateFixedGeometryShader>;
@ -219,6 +225,7 @@ private:
std::array<vk::ShaderModule, MAX_SHADER_STAGES> current_shaders;
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
ProgrammableVertexShaders programmable_vertex_shaders;
ProgrammableVertexShadersSPV programmable_vertex_shaders_spv;
FixedGeometryShaders fixed_geometry_shaders;
FragmentShadersGLSL fragment_shaders_glsl;
FragmentShadersSPV fragment_shaders_spv;

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,351 @@
#ifndef VK_SHADER_DECOMPILER_H
#define VK_SHADER_DECOMPILER_H
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
class vk_shader_decompiler
{
#include <exception>
#include <map>
#include <set>
#include <optional>
#include <sirit/sirit.h>
#include <nihstro/shader_bytecode.h>
#include "video_core/renderer_vulkan/vk_shader_gen.h"
namespace Vulkan {
using Sirit::Id;
constexpr u32 PROGRAM_END = Pica::Shader::MAX_PROGRAM_CODE_LENGTH;
class DecompileFail : public std::runtime_error {
public:
vk_shader_decompiler();
using std::runtime_error::runtime_error;
};
#endif // VK_SHADER_DECOMPILER_H
/// Describes the behaviour of code path of a given entry point and a return point.
enum class ExitMethod {
Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
AlwaysReturn, ///< All code paths reach the return point.
Conditional, ///< Code path reaches the return point or an END instruction conditionally.
AlwaysEnd, ///< All code paths reach a END instruction.
};
/// A label is an offset into the code assigned to the SPIR-V lavel
struct Label {
u32 label;
mutable Id spv_label;
Label operator+(u32 other) const {
return Label{.label = label + other, .spv_label = spv_label};
}
bool operator<(const Label& other) const {
return label < other.label;
}
};
struct SpirvParams {
Id jmp_to; ///< Temporary holding the current jump target
Id while_label; ///< Label to the beginning of the while loop
Id switch_label; ///< Label to the beginning of the switch statement
Id switch_merge_block; ///< Label to the merge block of the switch statement
std::array<Id, 3> vars; ///< Available function variables used for LOOP
u32 used_vars = 0;
};
/// A subroutine is a range of code refereced by a CALL, IF or LOOP instruction.
struct Subroutine {
u32 begin; ///< Entry point of the subroutine.
u32 end; ///< Return point of the subroutine.
ExitMethod exit_method; ///< Exit method of the subroutine.
std::set<u32> labels; ///< Addresses refereced by JMP instructions.
mutable Id function; ///< Function label of the subroutine
bool operator<(const Subroutine& rhs) const {
return std::tie(begin, end) < std::tie(rhs.begin, rhs.end);
}
};
/// Analyzes shader code and produces a set of subroutines.
class ControlFlowAnalyzer {
public:
ControlFlowAnalyzer(const Pica::Shader::ProgramCode& program_code, u32 main_offset);
[[nodiscard]] std::set<Subroutine> MoveSubroutines() {
return std::move(subroutines);
}
private:
/// Adds and analyzes a new subroutine if it is not added yet.
const Subroutine& AddSubroutine(u32 begin, u32 end);
/// Merges exit method of two parallel branches.
ExitMethod ParallelExit(ExitMethod a, ExitMethod b);
/// Cascades exit method of two blocks of code.
ExitMethod SeriesExit(ExitMethod a, ExitMethod b);
/// Scans a range of code for labels and determines the exit method.
ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels);
private:
const Pica::Shader::ProgramCode& program_code;
std::set<Subroutine> subroutines;
std::map<std::pair<u32, u32>, ExitMethod> exit_method_map;
};
class VertexModule : public Sirit::Module {
struct VectorIds {
/// Returns the type id of the vector with the provided size
[[nodiscard]] constexpr Id Get(u32 size) const {
return ids[size - 2];
}
std::array<Id, 3> ids;
};
public:
VertexModule(const Pica::Shader::ShaderSetup& setup,
const PicaVSConfig& config);
~VertexModule();
void Generate();
private:
/// Gets the Subroutine object corresponding to the specified address.
const Subroutine& GetSubroutine(u32 begin, u32 end) const;
/// Generates code to evaluate a shader control flow instruction
Id EvaluateCondition(nihstro::Instruction::FlowControlType flow_control);
/// Generates code representing a source register.
Id GetSourceRegister(const SourceRegister& source_reg, u32 address_register_index);
/// Generates code representing a destination register.
Id GetDestRegister(const DestRegister& dest_reg);
/// Returns the pointer type of the destination register.
Id GetDestPointer(const DestRegister& dest_reg);
/// Attemps to sanitize multiplication result to match PICA expected behaviour.
Id SanitizeMul(Id lhs, Id rhs);
/**
* Adds code that calls a subroutine.
* @param subroutine the subroutine to call.
*/
void CallSubroutine(const Subroutine& subroutine);
/**
* Writes code that does an assignment operation.
* @param swizzle the swizzle data of the current instruction.
* @param reg the destination register code.
* @param value the code representing the value to assign.
* @param storage_class storage specifier of reg.
* @param value_num_components number of components of the value to assign.
*/
void SetDest(const nihstro::SwizzlePattern& swizzle, Id reg, Id value,
Id reg_pointer, u32 dest_num_components, u32 value_num_components);
/**
* Compiles a single instruction from PICA to GLSL.
* @param offset the offset of the PICA shader instruction.
* @return the offset of the next instruction to execute. Usually it is the current offset + 1.
* If the current instruction is IF or LOOP, the next instruction is after the IF or LOOP block.
* If the current instruction always terminates the program, returns PROGRAM_END.
*/
u32 CompileInstr(u32 offset);
/**
* Compiles a range of instructions from PICA to GLSL.
* @param begin the offset of the starting instruction.
* @param end the offset where the compilation should stop (exclusive).
* @return the offset of the next instruction to compile. PROGRAM_END if the program terminates.
*/
u32 CompileRange(u32 begin, u32 end);
private:
/// Returns an id of the attribute type
Id AttribType(u32 index) const {
switch (config.state.attrib_types[index]) {
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
return vec_ids.Get(4);
case Pica::PipelineRegs::VertexAttributeFormat::BYTE:
case Pica::PipelineRegs::VertexAttributeFormat::SHORT:
return ivec_ids.Get(4);
case Pica::PipelineRegs::VertexAttributeFormat::UBYTE:
return uvec_ids.Get(4);
default:
UNREACHABLE();
}
return Id{};
}
/// Returns the attribute casted to float
Id AttribCast(u32 index, Id typed_reg) {
switch (config.state.attrib_types[index]) {
case Pica::PipelineRegs::VertexAttributeFormat::FLOAT:
break;
case Pica::PipelineRegs::VertexAttributeFormat::BYTE:
case Pica::PipelineRegs::VertexAttributeFormat::SHORT:
return OpConvertSToF(ivec_ids.Get(4), typed_reg);
case Pica::PipelineRegs::VertexAttributeFormat::UBYTE:
return OpConvertUToF(uvec_ids.Get(4), typed_reg);
default:
UNREACHABLE();
}
return typed_reg;
}
/// Loads the member specified from the vs_uniforms uniform struct
template <typename... Ids>
[[nodiscard]] Id GetVsUniformMember(Id type, Ids... ids) {
const Id uniform_ptr{TypePointer(spv::StorageClass::Uniform, type)};
return OpLoad(type, OpAccessChain(uniform_ptr, vs_uniforms, ids...));
}
/// Generates code representing a bool uniform
Id GetUniformBool(u32 index) {
const Id value{GetVsUniformMember(u32_id, ConstU32(0u), ConstU32(index))};
return OpINotEqual(bool_id, value, ConstU32(0u));
}
/// Defines a input variable
[[nodiscard]] Id DefineInput(Id type, u32 location) {
const Id input_id{DefineVar(type, spv::StorageClass::Input)};
Decorate(input_id, spv::Decoration::Location, location);
return input_id;
}
/// Defines a input variable
[[nodiscard]] Id DefineOutput(Id type, u32 location) {
const Id output_id{DefineVar(type, spv::StorageClass::Output)};
Decorate(output_id, spv::Decoration::Location, location);
return output_id;
}
/// Defines a uniform constant variable
[[nodiscard]] Id DefineUniformConst(Id type, u32 set, u32 binding, bool readonly = false) {
const Id uniform_id{DefineVar(type, spv::StorageClass::UniformConstant)};
Decorate(uniform_id, spv::Decoration::DescriptorSet, set);
Decorate(uniform_id, spv::Decoration::Binding, binding);
if (readonly) {
Decorate(uniform_id, spv::Decoration::NonWritable);
}
return uniform_id;
}
template <bool global = true>
[[nodiscard]] Id DefineVar(Id type, spv::StorageClass storage_class) {
const Id pointer_type_id{TypePointer(storage_class, type)};
return global ? AddGlobalVariable(pointer_type_id, storage_class)
: AddLocalVariable(pointer_type_id, storage_class);
}
/// Returns the id of a signed integer constant of value
[[nodiscard]] Id ConstBool(bool value) {
return value ? ConstantTrue(bool_id) : ConstantFalse(bool_id);
}
template <typename... Args>
[[nodiscard]] Id ConstBool(Args&&... values) {
constexpr u32 size = static_cast<u32>(sizeof...(values));
static_assert(size >= 2);
const std::array constituents{ConstBool(values)...};
const Id type = size <= 4 ? bvec_ids.Get(size) : TypeArray(bool_id, ConstU32(size));
return ConstantComposite(type, constituents);
}
/// Returns the id of a signed integer constant of value
[[nodiscard]] Id ConstU32(u32 value) {
return Constant(u32_id, value);
}
template <typename... Args>
[[nodiscard]] Id ConstU32(Args&&... values) {
constexpr u32 size = static_cast<u32>(sizeof...(values));
static_assert(size >= 2);
const std::array constituents{Constant(u32_id, values)...};
const Id type = size <= 4 ? uvec_ids.Get(size) : TypeArray(u32_id, ConstU32(size));
return ConstantComposite(type, constituents);
}
/// Returns the id of a signed integer constant of value
[[nodiscard]] Id ConstS32(s32 value) {
return Constant(i32_id, value);
}
template <typename... Args>
[[nodiscard]] Id ConstS32(Args&&... values) {
constexpr u32 size = static_cast<u32>(sizeof...(values));
static_assert(size >= 2);
const std::array constituents{Constant(i32_id, values)...};
const Id type = size <= 4 ? ivec_ids.Get(size) : TypeArray(i32_id, ConstU32(size));
return ConstantComposite(type, constituents);
}
/// Returns the id of a float constant of value
[[nodiscard]] Id ConstF32(f32 value) {
return Constant(f32_id, value);
}
template <typename... Args>
[[nodiscard]] Id ConstF32(Args... values) {
constexpr u32 size = static_cast<u32>(sizeof...(values));
static_assert(size >= 2);
const std::array constituents{Constant(f32_id, values)...};
const Id type = size <= 4 ? vec_ids.Get(size) : TypeArray(f32_id, ConstU32(size));
return ConstantComposite(type, constituents);
}
void DefineArithmeticTypes();
void DefineEntryPoint();
void DefineUniformStructs();
void DefineInterface();
public:
Id void_id{};
Id bool_id{};
Id f32_id{};
Id i32_id{};
Id u32_id{};
VectorIds vec_ids{};
VectorIds ivec_ids{};
VectorIds uvec_ids{};
VectorIds bvec_ids{};
private:
const PicaVSConfig& config;
const Pica::Shader::ProgramCode& program_code;
const Pica::Shader::SwizzleData& swizzle_data;
u32 main_offset;
bool sanitize_mul;
std::set<Subroutine> subroutines;
/**
* PICA input registers are float but vulkan doesn't have the
* ability to cast integer attributes to float. Thus they are
* manually cast if needed
**/
std::array<Id, 16> input_typed_regs{};
std::array<Id, 16> input_regs{};
std::array<bool, 16> used_regs{};
std::array<Id, 16> output_regs{};
std::array<Id, 16> tmp_regs{};
Id vs_uniforms{};
Id conditional_code{};
Id address_registers{};
};
/**
* Generates the SPIRV vertex shader program source code for the given VS program
* @returns String of the shader source code; boost::none on failure
*/
std::optional<std::vector<u32>> GenerateVertexShaderSPV(const Pica::Shader::ShaderSetup& setup,
const PicaVSConfig& config);
} // namespace Vulkan

View File

@ -1676,10 +1676,11 @@ std::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup&
std::string& program_source = program_source_opt->code;
out += R"(
#define uniforms vs_uniforms
layout (set = 0, binding = 0, std140) uniform vs_config {
pica_uniforms uniforms;
};
bool b[16];
uvec4 i[4];
vec4 f[96];
} uniforms;
)";
if (!config.state.use_geometry_shader) {
@ -1824,7 +1825,6 @@ layout (set = 0, binding = 0, std140) uniform vs_config {
out += program_source;
LOG_INFO(Render_Vulkan, "{}", out);
return out;
}

View File

@ -2,11 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/microprofile.h"
#include "core/core.h"
#include "video_core/regs.h"
#include "video_core/renderer_vulkan/vk_shader_gen_spv.h"
#include "video_core/shader/shader_uniforms.h"
using Pica::FramebufferRegs;
using Pica::LightingRegs;

View File

@ -12,20 +12,20 @@ namespace Vulkan {
using Sirit::Id;
struct VectorIds {
/// Returns the type id of the vector with the provided size
[[nodiscard]] constexpr Id Get(u32 size) const {
return ids[size - 2];
}
std::array<Id, 3> ids;
};
class FragmentModule : public Sirit::Module {
static constexpr u32 NUM_TEV_STAGES = 6;
static constexpr u32 NUM_LIGHTS = 8;
static constexpr u32 NUM_LIGHTING_SAMPLERS = 24;
struct VectorIds {
/// Returns the type id of the vector with the provided size
[[nodiscard]] constexpr Id Get(u32 size) const {
return ids[size - 2];
}
std::array<Id, 3> ids;
};
public:
FragmentModule(const PicaFSConfig& config);
~FragmentModule();

View File

@ -11,8 +11,8 @@
namespace Pica::Shader {
template <typename ShaderType>
using ShaderCacheResult = std::pair<ShaderType, std::optional<std::string>>;
template <typename ShaderType, typename ShaderBinary>
using ShaderCacheResult = std::pair<ShaderType, std::optional<ShaderBinary>>;
template <typename KeyType, typename ShaderType, auto ModuleCompiler, auto CodeGenerator>
class ShaderCache {
@ -50,7 +50,8 @@ public:
* program buffer from the previous shader, which is hashed into the config, resulting several
* different config values from the same shader program.
*/
template <typename KeyType, typename ShaderType, auto ModuleCompiler, auto CodeGenerator>
template <typename KeyType, typename ShaderType, typename ShaderBinary,
auto ModuleCompiler, auto CodeGenerator>
class ShaderDoubleCache {
public:
ShaderDoubleCache() = default;
@ -58,7 +59,7 @@ public:
template <typename... Args>
auto Get(const KeyType& key, const Pica::Shader::ShaderSetup& setup, Args&&... args)
-> ShaderCacheResult<ShaderType> {
-> ShaderCacheResult<ShaderType, ShaderBinary> {
if (auto map_iter = shader_map.find(key); map_iter == shader_map.end()) {
auto code = CodeGenerator(setup, key);
if (!code) {
@ -66,7 +67,7 @@ public:
return std::make_pair(ShaderType{}, std::nullopt);
}
std::string& program = code.value();
const ShaderBinary& program = code.value();
auto [iter, new_shader] = shader_cache.emplace(program, ShaderType{});
auto& shader = iter->second;
@ -81,7 +82,7 @@ public:
}
}
void Inject(const KeyType& key, std::string decomp, ShaderType&& program) {
void Inject(const KeyType& key, ShaderBinary&& decomp, ShaderType&& program) {
const auto iter = shader_cache.emplace(std::move(decomp), std::move(program)).first;
auto& cached_shader = iter->second;
@ -90,7 +91,16 @@ public:
public:
std::unordered_map<KeyType, ShaderType*> shader_map;
std::unordered_map<std::string, ShaderType> shader_cache;
std::unordered_map<ShaderBinary, ShaderType> shader_cache;
};
} // namespace Pica::Shader
namespace std {
template <>
struct hash<std::vector<u32>> {
std::size_t operator()(const std::vector<u32>& code) const noexcept {
return Common::ComputeHash64(code.data(), code.size() * sizeof(u32));
}
};
} // namespace std