mirror of
https://github.com/PabloMK7/citra.git
synced 2025-09-12 05:40:04 +00:00
video_core: Abstract shader generators. (#6990)
* video_core: Abstract shader generators. * shader: Extract common generator structures and move generators to specific namespaces. * shader: Minor fixes and clean-up.
This commit is contained in:
parent
1492d73ccb
commit
50f22d1f59
35 changed files with 1374 additions and 3344 deletions
958
src/video_core/shader/generator/glsl_shader_decompiler.cpp
Normal file
958
src/video_core/shader/generator/glsl_shader_decompiler.cpp
Normal file
|
@ -0,0 +1,958 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <fmt/format.h>
|
||||
#include <nihstro/shader_bytecode.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader/generator/glsl_shader_decompiler.h"
|
||||
|
||||
namespace Pica::Shader::Generator::GLSL {
|
||||
|
||||
using nihstro::DestRegister;
|
||||
using nihstro::Instruction;
|
||||
using nihstro::OpCode;
|
||||
using nihstro::RegisterType;
|
||||
using nihstro::SourceRegister;
|
||||
using nihstro::SwizzlePattern;
|
||||
|
||||
constexpr u32 PROGRAM_END = Pica::Shader::MAX_PROGRAM_CODE_LENGTH;
|
||||
|
||||
class DecompileFail : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/// 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 subroutine is a range of code refereced by a CALL, IF or LOOP instruction.
|
||||
struct Subroutine {
|
||||
/// Generates a name suitable for GLSL source code.
|
||||
std::string GetName() const {
|
||||
return "sub_" + std::to_string(begin) + "_" + std::to_string(end);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
: program_code(program_code) {
|
||||
|
||||
// Recursively finds all subroutines.
|
||||
const Subroutine& program_main = AddSubroutine(main_offset, PROGRAM_END);
|
||||
if (program_main.exit_method != ExitMethod::AlwaysEnd)
|
||||
throw DecompileFail("Program does not always end");
|
||||
}
|
||||
|
||||
std::set<Subroutine> MoveSubroutines() {
|
||||
return std::move(subroutines);
|
||||
}
|
||||
|
||||
private:
|
||||
const Pica::Shader::ProgramCode& program_code;
|
||||
std::set<Subroutine> subroutines;
|
||||
std::map<std::pair<u32, u32>, ExitMethod> exit_method_map;
|
||||
|
||||
/// Adds and analyzes a new subroutine if it is not added yet.
|
||||
const Subroutine& AddSubroutine(u32 begin, u32 end) {
|
||||
auto iter = subroutines.find(Subroutine{begin, end});
|
||||
if (iter != subroutines.end())
|
||||
return *iter;
|
||||
|
||||
Subroutine subroutine{begin, end};
|
||||
subroutine.exit_method = Scan(begin, end, subroutine.labels);
|
||||
if (subroutine.exit_method == ExitMethod::Undetermined)
|
||||
throw DecompileFail("Recursive function detected");
|
||||
return *subroutines.insert(std::move(subroutine)).first;
|
||||
}
|
||||
|
||||
/// Merges exit method of two parallel branches.
|
||||
static ExitMethod ParallelExit(ExitMethod a, ExitMethod b) {
|
||||
if (a == ExitMethod::Undetermined) {
|
||||
return b;
|
||||
}
|
||||
if (b == ExitMethod::Undetermined) {
|
||||
return a;
|
||||
}
|
||||
if (a == b) {
|
||||
return a;
|
||||
}
|
||||
return ExitMethod::Conditional;
|
||||
}
|
||||
|
||||
/// Cascades exit method of two blocks of code.
|
||||
static ExitMethod SeriesExit(ExitMethod a, ExitMethod b) {
|
||||
// This should be handled before evaluating b.
|
||||
DEBUG_ASSERT(a != ExitMethod::AlwaysEnd);
|
||||
|
||||
if (a == ExitMethod::Undetermined) {
|
||||
return ExitMethod::Undetermined;
|
||||
}
|
||||
|
||||
if (a == ExitMethod::AlwaysReturn) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (b == ExitMethod::Undetermined || b == ExitMethod::AlwaysEnd) {
|
||||
return ExitMethod::AlwaysEnd;
|
||||
}
|
||||
|
||||
return ExitMethod::Conditional;
|
||||
}
|
||||
|
||||
/// Scans a range of code for labels and determines the exit method.
|
||||
ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) {
|
||||
auto [iter, inserted] =
|
||||
exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined);
|
||||
ExitMethod& exit_method = iter->second;
|
||||
if (!inserted)
|
||||
return exit_method;
|
||||
|
||||
for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
|
||||
const Instruction instr = {program_code[offset]};
|
||||
switch (instr.opcode.Value()) {
|
||||
case OpCode::Id::END: {
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
}
|
||||
case OpCode::Id::JMPC:
|
||||
case OpCode::Id::JMPU: {
|
||||
labels.insert(instr.flow_control.dest_offset);
|
||||
ExitMethod no_jmp = Scan(offset + 1, end, labels);
|
||||
ExitMethod jmp = Scan(instr.flow_control.dest_offset, end, labels);
|
||||
return exit_method = ParallelExit(no_jmp, jmp);
|
||||
}
|
||||
case OpCode::Id::CALL: {
|
||||
auto& call = AddSubroutine(instr.flow_control.dest_offset,
|
||||
instr.flow_control.dest_offset +
|
||||
instr.flow_control.num_instructions);
|
||||
if (call.exit_method == ExitMethod::AlwaysEnd)
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
ExitMethod after_call = Scan(offset + 1, end, labels);
|
||||
return exit_method = SeriesExit(call.exit_method, after_call);
|
||||
}
|
||||
case OpCode::Id::LOOP: {
|
||||
auto& loop = AddSubroutine(offset + 1, instr.flow_control.dest_offset + 1);
|
||||
if (loop.exit_method == ExitMethod::AlwaysEnd)
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
ExitMethod after_loop = Scan(instr.flow_control.dest_offset + 1, end, labels);
|
||||
return exit_method = SeriesExit(loop.exit_method, after_loop);
|
||||
}
|
||||
case OpCode::Id::CALLC:
|
||||
case OpCode::Id::CALLU: {
|
||||
auto& call = AddSubroutine(instr.flow_control.dest_offset,
|
||||
instr.flow_control.dest_offset +
|
||||
instr.flow_control.num_instructions);
|
||||
ExitMethod after_call = Scan(offset + 1, end, labels);
|
||||
return exit_method = SeriesExit(
|
||||
ParallelExit(call.exit_method, ExitMethod::AlwaysReturn), after_call);
|
||||
}
|
||||
case OpCode::Id::IFU:
|
||||
case OpCode::Id::IFC: {
|
||||
auto& if_sub = AddSubroutine(offset + 1, instr.flow_control.dest_offset);
|
||||
ExitMethod else_method;
|
||||
if (instr.flow_control.num_instructions != 0) {
|
||||
auto& else_sub = AddSubroutine(instr.flow_control.dest_offset,
|
||||
instr.flow_control.dest_offset +
|
||||
instr.flow_control.num_instructions);
|
||||
else_method = else_sub.exit_method;
|
||||
} else {
|
||||
else_method = ExitMethod::AlwaysReturn;
|
||||
}
|
||||
|
||||
ExitMethod both = ParallelExit(if_sub.exit_method, else_method);
|
||||
if (both == ExitMethod::AlwaysEnd)
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
ExitMethod after_call =
|
||||
Scan(instr.flow_control.dest_offset + instr.flow_control.num_instructions, end,
|
||||
labels);
|
||||
return exit_method = SeriesExit(both, after_call);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return exit_method = ExitMethod::AlwaysReturn;
|
||||
}
|
||||
};
|
||||
|
||||
class ShaderWriter {
|
||||
public:
|
||||
// Forwards all arguments directly to libfmt.
|
||||
// Note that all formatting requirements for fmt must be
|
||||
// obeyed when using this function. (e.g. {{ must be used
|
||||
// printing the character '{' is desirable. Ditto for }} and '}',
|
||||
// etc).
|
||||
template <typename... Args>
|
||||
void AddLine(fmt::format_string<Args...> text, Args&&... args) {
|
||||
AddExpression(fmt::format(text, std::forward<Args>(args)...));
|
||||
AddNewLine();
|
||||
}
|
||||
|
||||
void AddNewLine() {
|
||||
DEBUG_ASSERT(scope >= 0);
|
||||
shader_source += '\n';
|
||||
}
|
||||
|
||||
std::string MoveResult() {
|
||||
return std::move(shader_source);
|
||||
}
|
||||
|
||||
int scope = 0;
|
||||
|
||||
private:
|
||||
void AddExpression(std::string_view text) {
|
||||
if (!text.empty()) {
|
||||
shader_source.append(static_cast<std::size_t>(scope) * 4, ' ');
|
||||
}
|
||||
shader_source += text;
|
||||
}
|
||||
|
||||
std::string shader_source;
|
||||
};
|
||||
|
||||
/// An adaptor for getting swizzle pattern string from nihstro interfaces.
|
||||
template <SwizzlePattern::Selector (SwizzlePattern::*getter)(int) const>
|
||||
std::string GetSelectorSrc(const SwizzlePattern& pattern) {
|
||||
std::string out;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
switch ((pattern.*getter)(i)) {
|
||||
case SwizzlePattern::Selector::x:
|
||||
out += 'x';
|
||||
break;
|
||||
case SwizzlePattern::Selector::y:
|
||||
out += 'y';
|
||||
break;
|
||||
case SwizzlePattern::Selector::z:
|
||||
out += 'z';
|
||||
break;
|
||||
case SwizzlePattern::Selector::w:
|
||||
out += 'w';
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
constexpr auto GetSelectorSrc1 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc1>;
|
||||
constexpr auto GetSelectorSrc2 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc2>;
|
||||
constexpr auto GetSelectorSrc3 = GetSelectorSrc<&SwizzlePattern::GetSelectorSrc3>;
|
||||
|
||||
class GLSLGenerator {
|
||||
public:
|
||||
GLSLGenerator(const std::set<Subroutine>& subroutines,
|
||||
const Pica::Shader::ProgramCode& program_code,
|
||||
const Pica::Shader::SwizzleData& swizzle_data, u32 main_offset,
|
||||
const RegGetter& inputreg_getter, const RegGetter& outputreg_getter,
|
||||
bool sanitize_mul)
|
||||
: subroutines(subroutines), program_code(program_code), swizzle_data(swizzle_data),
|
||||
main_offset(main_offset), inputreg_getter(inputreg_getter),
|
||||
outputreg_getter(outputreg_getter), sanitize_mul(sanitize_mul) {
|
||||
|
||||
Generate();
|
||||
}
|
||||
|
||||
std::string MoveShaderCode() {
|
||||
return shader.MoveResult();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Gets the Subroutine object corresponding to the specified address.
|
||||
const Subroutine& GetSubroutine(u32 begin, u32 end) const {
|
||||
auto iter = subroutines.find(Subroutine{begin, end});
|
||||
ASSERT(iter != subroutines.end());
|
||||
return *iter;
|
||||
}
|
||||
|
||||
/// Generates condition evaluation code for the flow control instruction.
|
||||
static std::string EvaluateCondition(Instruction::FlowControlType flow_control) {
|
||||
using Op = Instruction::FlowControlType::Op;
|
||||
|
||||
const std::string_view result_x =
|
||||
flow_control.refx.Value() ? "conditional_code.x" : "!conditional_code.x";
|
||||
const std::string_view result_y =
|
||||
flow_control.refy.Value() ? "conditional_code.y" : "!conditional_code.y";
|
||||
|
||||
switch (flow_control.op) {
|
||||
case Op::JustX:
|
||||
return std::string(result_x);
|
||||
case Op::JustY:
|
||||
return std::string(result_y);
|
||||
case Op::Or:
|
||||
case Op::And: {
|
||||
const std::string_view and_or = flow_control.op == Op::Or ? "any" : "all";
|
||||
std::string bvec;
|
||||
if (flow_control.refx.Value() && flow_control.refy.Value()) {
|
||||
bvec = "conditional_code";
|
||||
} else if (!flow_control.refx.Value() && !flow_control.refy.Value()) {
|
||||
bvec = "not(conditional_code)";
|
||||
} else {
|
||||
bvec = fmt::format("bvec2({}, {})", result_x, result_y);
|
||||
}
|
||||
return fmt::format("{}({})", and_or, bvec);
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates code representing a source register.
|
||||
std::string GetSourceRegister(const SourceRegister& source_reg,
|
||||
u32 address_register_index) const {
|
||||
const u32 index = static_cast<u32>(source_reg.GetIndex());
|
||||
|
||||
switch (source_reg.GetRegisterType()) {
|
||||
case RegisterType::Input:
|
||||
return inputreg_getter(index);
|
||||
case RegisterType::Temporary:
|
||||
return fmt::format("reg_tmp{}", index);
|
||||
case RegisterType::FloatUniform:
|
||||
if (address_register_index != 0) {
|
||||
return fmt::format("get_offset_register({}, address_registers.{})", index,
|
||||
"xyz"[address_register_index - 1]);
|
||||
}
|
||||
return fmt::format("uniforms.f[{}]", index);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates code representing a destination register.
|
||||
std::string GetDestRegister(const DestRegister& dest_reg) const {
|
||||
const u32 index = static_cast<u32>(dest_reg.GetIndex());
|
||||
|
||||
switch (dest_reg.GetRegisterType()) {
|
||||
case RegisterType::Output:
|
||||
return outputreg_getter(index);
|
||||
case RegisterType::Temporary:
|
||||
return fmt::format("reg_tmp{}", index);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates code representing a bool uniform
|
||||
std::string GetUniformBool(u32 index) const {
|
||||
return fmt::format("uniforms.b[{}]", index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds code that calls a subroutine.
|
||||
* @param subroutine the subroutine to call.
|
||||
*/
|
||||
void CallSubroutine(const Subroutine& subroutine) {
|
||||
if (subroutine.exit_method == ExitMethod::AlwaysEnd) {
|
||||
shader.AddLine("{}();", subroutine.GetName());
|
||||
shader.AddLine("return true;");
|
||||
} else if (subroutine.exit_method == ExitMethod::Conditional) {
|
||||
shader.AddLine("if ({}()) {{ return true; }}", subroutine.GetName());
|
||||
} else {
|
||||
shader.AddLine("{}();", subroutine.GetName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 dest_num_components number of components of the destination register.
|
||||
* @param value_num_components number of components of the value to assign.
|
||||
*/
|
||||
void SetDest(const SwizzlePattern& swizzle, std::string_view reg, std::string_view value,
|
||||
u32 dest_num_components, u32 value_num_components) {
|
||||
u32 dest_mask_num_components = 0;
|
||||
std::string dest_mask_swizzle = ".";
|
||||
|
||||
for (u32 i = 0; i < dest_num_components; ++i) {
|
||||
if (swizzle.DestComponentEnabled(static_cast<int>(i))) {
|
||||
dest_mask_swizzle += "xyzw"[i];
|
||||
++dest_mask_num_components;
|
||||
}
|
||||
}
|
||||
|
||||
if (reg.empty() || dest_mask_num_components == 0) {
|
||||
return;
|
||||
}
|
||||
DEBUG_ASSERT(value_num_components >= dest_num_components || value_num_components == 1);
|
||||
|
||||
const std::string dest =
|
||||
fmt::format("{}{}", reg, dest_num_components != 1 ? dest_mask_swizzle : "");
|
||||
|
||||
std::string src{value};
|
||||
if (value_num_components == 1) {
|
||||
if (dest_mask_num_components != 1) {
|
||||
src = fmt::format("vec{}({})", dest_mask_num_components, value);
|
||||
}
|
||||
} else if (value_num_components != dest_mask_num_components) {
|
||||
src = fmt::format("({}){}", value, dest_mask_swizzle);
|
||||
}
|
||||
|
||||
shader.AddLine("{} = {};", dest, src);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const Instruction instr = {program_code[offset]};
|
||||
|
||||
std::size_t swizzle_offset =
|
||||
instr.opcode.Value().GetInfo().type == OpCode::Type::MultiplyAdd
|
||||
? instr.mad.operand_desc_id
|
||||
: instr.common.operand_desc_id;
|
||||
const SwizzlePattern swizzle = {swizzle_data[swizzle_offset]};
|
||||
|
||||
shader.AddLine("// {}: {}", offset, instr.opcode.Value().GetInfo().name);
|
||||
|
||||
switch (instr.opcode.Value().GetInfo().type) {
|
||||
case OpCode::Type::Arithmetic: {
|
||||
const bool is_inverted =
|
||||
(0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed));
|
||||
|
||||
std::string src1 = swizzle.negate_src1 ? "-" : "";
|
||||
src1 += GetSourceRegister(instr.common.GetSrc1(is_inverted),
|
||||
!is_inverted * instr.common.address_register_index);
|
||||
src1 += "." + GetSelectorSrc1(swizzle);
|
||||
|
||||
std::string src2 = swizzle.negate_src2 ? "-" : "";
|
||||
src2 += GetSourceRegister(instr.common.GetSrc2(is_inverted),
|
||||
is_inverted * instr.common.address_register_index);
|
||||
src2 += "." + GetSelectorSrc2(swizzle);
|
||||
|
||||
std::string dest_reg = GetDestRegister(instr.common.dest.Value());
|
||||
|
||||
switch (instr.opcode.Value().EffectiveOpCode()) {
|
||||
case OpCode::Id::ADD: {
|
||||
SetDest(swizzle, dest_reg, fmt::format("{} + {}", src1, src2), 4, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::MUL: {
|
||||
if (sanitize_mul) {
|
||||
SetDest(swizzle, dest_reg, fmt::format("sanitize_mul({}, {})", src1, src2), 4,
|
||||
4);
|
||||
} else {
|
||||
SetDest(swizzle, dest_reg, fmt::format("{} * {}", src1, src2), 4, 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::FLR: {
|
||||
SetDest(swizzle, dest_reg, fmt::format("floor({})", src1), 4, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::MAX: {
|
||||
if (sanitize_mul) {
|
||||
SetDest(swizzle, dest_reg,
|
||||
fmt::format("mix({1}, {0}, greaterThan({0}, {1}))", src1, src2), 4, 4);
|
||||
} else {
|
||||
SetDest(swizzle, dest_reg, fmt::format("max({}, {})", src1, src2), 4, 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::MIN: {
|
||||
if (sanitize_mul) {
|
||||
SetDest(swizzle, dest_reg,
|
||||
fmt::format("mix({1}, {0}, lessThan({0}, {1}))", src1, src2), 4, 4);
|
||||
} else {
|
||||
SetDest(swizzle, dest_reg, fmt::format("min({}, {})", src1, src2), 4, 4);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::DP3:
|
||||
case OpCode::Id::DP4:
|
||||
case OpCode::Id::DPH:
|
||||
case OpCode::Id::DPHI: {
|
||||
OpCode::Id opcode = instr.opcode.Value().EffectiveOpCode();
|
||||
std::string dot;
|
||||
if (opcode == OpCode::Id::DP3) {
|
||||
if (sanitize_mul) {
|
||||
dot = fmt::format("dot(vec3(sanitize_mul({}, {})), vec3(1.0))", src1, src2);
|
||||
} else {
|
||||
dot = fmt::format("dot(vec3({}), vec3({}))", src1, src2);
|
||||
}
|
||||
} else {
|
||||
if (sanitize_mul) {
|
||||
const std::string src1_ =
|
||||
(opcode == OpCode::Id::DPH || opcode == OpCode::Id::DPHI)
|
||||
? fmt::format("vec4({}.xyz, 1.0)", src1)
|
||||
: std::move(src1);
|
||||
|
||||
dot = fmt::format("dot(sanitize_mul({}, {}), vec4(1.0))", src1_, src2);
|
||||
} else {
|
||||
dot = fmt::format("dot({}, {})", src1, src2);
|
||||
}
|
||||
}
|
||||
|
||||
SetDest(swizzle, dest_reg, dot, 4, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::RCP: {
|
||||
if (!sanitize_mul) {
|
||||
// When accurate multiplication is OFF, NaN are not really handled. This is a
|
||||
// workaround to cheaply avoid NaN. Fixes graphical issues in Ocarina of Time.
|
||||
shader.AddLine("if ({}.x != 0.0)", src1);
|
||||
}
|
||||
SetDest(swizzle, dest_reg, fmt::format("(1.0 / {}.x)", src1), 4, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::RSQ: {
|
||||
if (!sanitize_mul) {
|
||||
// When accurate multiplication is OFF, NaN are not really handled. This is a
|
||||
// workaround to cheaply avoid NaN. Fixes graphical issues in Ocarina of Time.
|
||||
shader.AddLine("if ({}.x > 0.0)", src1);
|
||||
}
|
||||
SetDest(swizzle, dest_reg, fmt::format("inversesqrt({}.x)", src1), 4, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::MOVA: {
|
||||
SetDest(swizzle, "address_registers", fmt::format("ivec2({})", src1), 2, 2);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::MOV: {
|
||||
SetDest(swizzle, dest_reg, src1, 4, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::SGE:
|
||||
case OpCode::Id::SGEI: {
|
||||
SetDest(swizzle, dest_reg,
|
||||
fmt::format("vec4(greaterThanEqual({}, {}))", src1, src2), 4, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::SLT:
|
||||
case OpCode::Id::SLTI: {
|
||||
SetDest(swizzle, dest_reg, fmt::format("vec4(lessThan({}, {}))", src1, src2), 4, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::CMP: {
|
||||
using CompareOp = Instruction::Common::CompareOpType::Op;
|
||||
const std::map<CompareOp, std::pair<std::string_view, std::string_view>> cmp_ops{
|
||||
{CompareOp::Equal, {"==", "equal"}},
|
||||
{CompareOp::NotEqual, {"!=", "notEqual"}},
|
||||
{CompareOp::LessThan, {"<", "lessThan"}},
|
||||
{CompareOp::LessEqual, {"<=", "lessThanEqual"}},
|
||||
{CompareOp::GreaterThan, {">", "greaterThan"}},
|
||||
{CompareOp::GreaterEqual, {">=", "greaterThanEqual"}},
|
||||
};
|
||||
|
||||
const CompareOp op_x = instr.common.compare_op.x.Value();
|
||||
const CompareOp op_y = instr.common.compare_op.y.Value();
|
||||
|
||||
if (cmp_ops.find(op_x) == cmp_ops.end()) {
|
||||
LOG_ERROR(HW_GPU, "Unknown compare mode {:x}", op_x);
|
||||
} else if (cmp_ops.find(op_y) == cmp_ops.end()) {
|
||||
LOG_ERROR(HW_GPU, "Unknown compare mode {:x}", op_y);
|
||||
} else if (op_x != op_y) {
|
||||
shader.AddLine("conditional_code.x = {}.x {} {}.x;", src1,
|
||||
cmp_ops.find(op_x)->second.first, src2);
|
||||
shader.AddLine("conditional_code.y = {}.y {} {}.y;", src1,
|
||||
cmp_ops.find(op_y)->second.first, src2);
|
||||
} else {
|
||||
shader.AddLine("conditional_code = {}(vec2({}), vec2({}));",
|
||||
cmp_ops.find(op_x)->second.second, src1, src2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::EX2: {
|
||||
SetDest(swizzle, dest_reg, fmt::format("exp2({}.x)", src1), 4, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::LG2: {
|
||||
SetDest(swizzle, dest_reg, fmt::format("log2({}.x)", src1), 4, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
LOG_ERROR(HW_GPU, "Unhandled arithmetic instruction: 0x{:02x} ({}): 0x{:08x}",
|
||||
(int)instr.opcode.Value().EffectiveOpCode(),
|
||||
instr.opcode.Value().GetInfo().name, instr.hex);
|
||||
throw DecompileFail("Unhandled instruction");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Type::MultiplyAdd: {
|
||||
if ((instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD) ||
|
||||
(instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI)) {
|
||||
bool is_inverted = (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI);
|
||||
|
||||
std::string src1 = swizzle.negate_src1 ? "-" : "";
|
||||
src1 += GetSourceRegister(instr.mad.GetSrc1(is_inverted), 0);
|
||||
src1 += "." + GetSelectorSrc1(swizzle);
|
||||
|
||||
std::string src2 = swizzle.negate_src2 ? "-" : "";
|
||||
src2 += GetSourceRegister(instr.mad.GetSrc2(is_inverted),
|
||||
!is_inverted * instr.mad.address_register_index);
|
||||
src2 += "." + GetSelectorSrc2(swizzle);
|
||||
|
||||
std::string src3 = swizzle.negate_src3 ? "-" : "";
|
||||
src3 += GetSourceRegister(instr.mad.GetSrc3(is_inverted),
|
||||
is_inverted * instr.mad.address_register_index);
|
||||
src3 += "." + GetSelectorSrc3(swizzle);
|
||||
|
||||
std::string dest_reg =
|
||||
(instr.mad.dest.Value() < 0x10)
|
||||
? outputreg_getter(static_cast<u32>(instr.mad.dest.Value().GetIndex()))
|
||||
: (instr.mad.dest.Value() < 0x20)
|
||||
? "reg_tmp" + std::to_string(instr.mad.dest.Value().GetIndex())
|
||||
: "";
|
||||
|
||||
if (sanitize_mul) {
|
||||
SetDest(swizzle, dest_reg,
|
||||
fmt::format("sanitize_mul({}, {}) + {}", src1, src2, src3), 4, 4);
|
||||
} else {
|
||||
SetDest(swizzle, dest_reg, fmt::format("{} * {} + {}", src1, src2, src3), 4, 4);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(HW_GPU, "Unhandled multiply-add instruction: 0x{:02x} ({}): 0x{:08x}",
|
||||
(int)instr.opcode.Value().EffectiveOpCode(),
|
||||
instr.opcode.Value().GetInfo().name, instr.hex);
|
||||
throw DecompileFail("Unhandled instruction");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
switch (instr.opcode.Value()) {
|
||||
case OpCode::Id::END: {
|
||||
shader.AddLine("return true;");
|
||||
offset = PROGRAM_END - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::JMPC:
|
||||
case OpCode::Id::JMPU: {
|
||||
std::string condition;
|
||||
if (instr.opcode.Value() == OpCode::Id::JMPC) {
|
||||
condition = EvaluateCondition(instr.flow_control);
|
||||
} else {
|
||||
bool invert_test = instr.flow_control.num_instructions & 1;
|
||||
condition = (invert_test ? "!" : "") +
|
||||
GetUniformBool(instr.flow_control.bool_uniform_id);
|
||||
}
|
||||
|
||||
shader.AddLine("if ({}) {{", condition);
|
||||
++shader.scope;
|
||||
shader.AddLine("{{ jmp_to = {}u; break; }}",
|
||||
instr.flow_control.dest_offset.Value());
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}");
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::CALL:
|
||||
case OpCode::Id::CALLC:
|
||||
case OpCode::Id::CALLU: {
|
||||
std::string condition;
|
||||
if (instr.opcode.Value() == OpCode::Id::CALLC) {
|
||||
condition = EvaluateCondition(instr.flow_control);
|
||||
} else if (instr.opcode.Value() == OpCode::Id::CALLU) {
|
||||
condition = GetUniformBool(instr.flow_control.bool_uniform_id);
|
||||
}
|
||||
|
||||
if (condition.empty()) {
|
||||
shader.AddLine("{{");
|
||||
} else {
|
||||
shader.AddLine("if ({}) {{", condition);
|
||||
}
|
||||
++shader.scope;
|
||||
|
||||
auto& call_sub = GetSubroutine(instr.flow_control.dest_offset,
|
||||
instr.flow_control.dest_offset +
|
||||
instr.flow_control.num_instructions);
|
||||
|
||||
CallSubroutine(call_sub);
|
||||
if (instr.opcode.Value() == OpCode::Id::CALL &&
|
||||
call_sub.exit_method == ExitMethod::AlwaysEnd) {
|
||||
offset = PROGRAM_END - 1;
|
||||
}
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}");
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::NOP: {
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::IFC:
|
||||
case OpCode::Id::IFU: {
|
||||
std::string condition;
|
||||
if (instr.opcode.Value() == OpCode::Id::IFC) {
|
||||
condition = EvaluateCondition(instr.flow_control);
|
||||
} else {
|
||||
condition = GetUniformBool(instr.flow_control.bool_uniform_id);
|
||||
}
|
||||
|
||||
const u32 if_offset = offset + 1;
|
||||
const u32 else_offset = instr.flow_control.dest_offset;
|
||||
const u32 endif_offset =
|
||||
instr.flow_control.dest_offset + instr.flow_control.num_instructions;
|
||||
|
||||
shader.AddLine("if ({}) {{", condition);
|
||||
++shader.scope;
|
||||
|
||||
auto& if_sub = GetSubroutine(if_offset, else_offset);
|
||||
CallSubroutine(if_sub);
|
||||
offset = else_offset - 1;
|
||||
|
||||
if (instr.flow_control.num_instructions != 0) {
|
||||
--shader.scope;
|
||||
shader.AddLine("}} else {{");
|
||||
++shader.scope;
|
||||
|
||||
auto& else_sub = GetSubroutine(else_offset, endif_offset);
|
||||
CallSubroutine(else_sub);
|
||||
offset = endif_offset - 1;
|
||||
|
||||
if (if_sub.exit_method == ExitMethod::AlwaysEnd &&
|
||||
else_sub.exit_method == ExitMethod::AlwaysEnd) {
|
||||
offset = PROGRAM_END - 1;
|
||||
}
|
||||
}
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}");
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::LOOP: {
|
||||
const std::string int_uniform =
|
||||
fmt::format("uniforms.i[{}]", instr.flow_control.int_uniform_id.Value());
|
||||
|
||||
shader.AddLine("address_registers.z = int({}.y);", int_uniform);
|
||||
|
||||
const std::string loop_var = fmt::format("loop{}", offset);
|
||||
shader.AddLine(
|
||||
"for (uint {} = 0u; {} <= {}.x; address_registers.z += int({}.z), ++{}) {{",
|
||||
loop_var, loop_var, int_uniform, int_uniform, loop_var);
|
||||
++shader.scope;
|
||||
|
||||
auto& loop_sub = GetSubroutine(offset + 1, instr.flow_control.dest_offset + 1);
|
||||
CallSubroutine(loop_sub);
|
||||
offset = instr.flow_control.dest_offset;
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}");
|
||||
|
||||
if (loop_sub.exit_method == ExitMethod::AlwaysEnd) {
|
||||
offset = PROGRAM_END - 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OpCode::Id::EMIT:
|
||||
case OpCode::Id::SETEMIT:
|
||||
LOG_ERROR(HW_GPU, "Geometry shader operation detected in vertex shader");
|
||||
break;
|
||||
|
||||
default: {
|
||||
LOG_ERROR(HW_GPU, "Unhandled instruction: 0x{:02x} ({}): 0x{:08x}",
|
||||
(int)instr.opcode.Value().EffectiveOpCode(),
|
||||
instr.opcode.Value().GetInfo().name, instr.hex);
|
||||
throw DecompileFail("Unhandled instruction");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
u32 program_counter;
|
||||
for (program_counter = begin; program_counter < (begin > end ? PROGRAM_END : end);) {
|
||||
program_counter = CompileInstr(program_counter);
|
||||
}
|
||||
return program_counter;
|
||||
}
|
||||
|
||||
void Generate() {
|
||||
if (sanitize_mul) {
|
||||
#ifdef ANDROID
|
||||
// Use a cheaper sanitize_mul on Android, as mobile GPUs struggle here
|
||||
// This seems to be sufficient at least for Ocarina of Time and Attack on Titan accurate
|
||||
// multiplication bugs
|
||||
shader.AddLine(
|
||||
"#define sanitize_mul(lhs, rhs) mix(lhs * rhs, vec4(0.0), isnan(lhs * rhs))");
|
||||
#else
|
||||
shader.AddLine("vec4 sanitize_mul(vec4 lhs, vec4 rhs) {{");
|
||||
++shader.scope;
|
||||
shader.AddLine("vec4 product = lhs * rhs;");
|
||||
shader.AddLine("return mix(product, mix(mix(vec4(0.0), product, isnan(rhs)), product, "
|
||||
"isnan(lhs)), isnan(product));");
|
||||
--shader.scope;
|
||||
shader.AddLine("}}\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
shader.AddLine("vec4 get_offset_register(int base_index, int offset) {{");
|
||||
++shader.scope;
|
||||
shader.AddLine("int fixed_offset = offset >= -128 && offset <= 127 ? offset : 0;");
|
||||
shader.AddLine("uint index = uint((base_index + fixed_offset) & 0x7F);");
|
||||
shader.AddLine("return index < 96u ? uniforms.f[index] : vec4(1.0);");
|
||||
--shader.scope;
|
||||
shader.AddLine("}}\n");
|
||||
|
||||
// Add declarations for registers
|
||||
shader.AddLine("bvec2 conditional_code = bvec2(false);");
|
||||
shader.AddLine("ivec3 address_registers = ivec3(0);");
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
shader.AddLine("vec4 reg_tmp{} = vec4(0.0, 0.0, 0.0, 1.0);", i);
|
||||
}
|
||||
shader.AddNewLine();
|
||||
|
||||
// Add declarations for all subroutines
|
||||
for (const auto& subroutine : subroutines) {
|
||||
shader.AddLine("bool {}();", subroutine.GetName());
|
||||
}
|
||||
shader.AddNewLine();
|
||||
|
||||
// Add the main entry point
|
||||
shader.AddLine("bool exec_shader() {{");
|
||||
++shader.scope;
|
||||
CallSubroutine(GetSubroutine(main_offset, PROGRAM_END));
|
||||
--shader.scope;
|
||||
shader.AddLine("}}\n");
|
||||
|
||||
// Add definitions for all subroutines
|
||||
for (const auto& subroutine : subroutines) {
|
||||
std::set<u32> labels = subroutine.labels;
|
||||
|
||||
shader.AddLine("bool {}() {{", subroutine.GetName());
|
||||
++shader.scope;
|
||||
|
||||
if (labels.empty()) {
|
||||
if (CompileRange(subroutine.begin, subroutine.end) != PROGRAM_END) {
|
||||
shader.AddLine("return false;");
|
||||
}
|
||||
} else {
|
||||
labels.insert(subroutine.begin);
|
||||
shader.AddLine("uint jmp_to = {}u;", subroutine.begin);
|
||||
shader.AddLine("while (true) {{");
|
||||
++shader.scope;
|
||||
|
||||
shader.AddLine("switch (jmp_to) {{");
|
||||
|
||||
for (auto label : labels) {
|
||||
shader.AddLine("case {}u: {{", label);
|
||||
++shader.scope;
|
||||
|
||||
auto next_it = labels.lower_bound(label + 1);
|
||||
u32 next_label = next_it == labels.end() ? subroutine.end : *next_it;
|
||||
|
||||
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
|
||||
shader.AddLine("{{ jmp_to = {}u; break; }}", compile_end);
|
||||
labels.emplace(compile_end);
|
||||
}
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}");
|
||||
}
|
||||
|
||||
shader.AddLine("default: return false;");
|
||||
shader.AddLine("}}");
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}");
|
||||
|
||||
shader.AddLine("return false;");
|
||||
}
|
||||
|
||||
--shader.scope;
|
||||
shader.AddLine("}}\n");
|
||||
|
||||
DEBUG_ASSERT(shader.scope == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::set<Subroutine>& subroutines;
|
||||
const Pica::Shader::ProgramCode& program_code;
|
||||
const Pica::Shader::SwizzleData& swizzle_data;
|
||||
const u32 main_offset;
|
||||
const RegGetter& inputreg_getter;
|
||||
const RegGetter& outputreg_getter;
|
||||
const bool sanitize_mul;
|
||||
|
||||
ShaderWriter shader;
|
||||
};
|
||||
|
||||
std::string DecompileProgram(const Pica::Shader::ProgramCode& program_code,
|
||||
const Pica::Shader::SwizzleData& swizzle_data, u32 main_offset,
|
||||
const RegGetter& inputreg_getter, const RegGetter& outputreg_getter,
|
||||
bool sanitize_mul) {
|
||||
|
||||
try {
|
||||
auto subroutines = ControlFlowAnalyzer(program_code, main_offset).MoveSubroutines();
|
||||
GLSLGenerator generator(subroutines, program_code, swizzle_data, main_offset,
|
||||
inputreg_getter, outputreg_getter, sanitize_mul);
|
||||
return generator.MoveShaderCode();
|
||||
} catch (const DecompileFail& exception) {
|
||||
LOG_INFO(HW_GPU, "Shader decompilation failed: {}", exception.what());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Pica::Shader::Generator::GLSL
|
21
src/video_core/shader/generator/glsl_shader_decompiler.h
Normal file
21
src/video_core/shader/generator/glsl_shader_decompiler.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
namespace Pica::Shader::Generator::GLSL {
|
||||
|
||||
using RegGetter = std::function<std::string(u32)>;
|
||||
|
||||
std::string DecompileProgram(const Pica::Shader::ProgramCode& program_code,
|
||||
const Pica::Shader::SwizzleData& swizzle_data, u32 main_offset,
|
||||
const RegGetter& inputreg_getter, const RegGetter& outputreg_getter,
|
||||
bool sanitize_mul);
|
||||
|
||||
} // namespace Pica::Shader::Generator::GLSL
|
1977
src/video_core/shader/generator/glsl_shader_gen.cpp
Normal file
1977
src/video_core/shader/generator/glsl_shader_gen.cpp
Normal file
File diff suppressed because it is too large
Load diff
57
src/video_core/shader/generator/glsl_shader_gen.h
Normal file
57
src/video_core/shader/generator/glsl_shader_gen.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/shader/generator/shader_gen.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
// High precision may or may not be supported in GLES3. If it isn't, use medium precision instead.
|
||||
static constexpr char fragment_shader_precision_OES[] = R"(
|
||||
#if GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp int;
|
||||
precision highp float;
|
||||
precision highp samplerBuffer;
|
||||
precision highp uimage2D;
|
||||
#else
|
||||
precision mediump int;
|
||||
precision mediump float;
|
||||
precision mediump samplerBuffer;
|
||||
precision mediump uimage2D;
|
||||
#endif // GL_FRAGMENT_PRECISION_HIGH
|
||||
#endif
|
||||
)";
|
||||
|
||||
namespace Pica::Shader::Generator::GLSL {
|
||||
|
||||
/**
|
||||
* Generates the GLSL vertex shader program source code that accepts vertices from software shader
|
||||
* and directly passes them to the fragment shader.
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
std::string GenerateTrivialVertexShader(bool use_clip_planes, bool separable_shader);
|
||||
|
||||
/**
|
||||
* Generates the GLSL vertex shader program source code for the given VS program
|
||||
* @returns String of the shader source code; empty on failure
|
||||
*/
|
||||
std::string GenerateVertexShader(const Pica::Shader::ShaderSetup& setup, const PicaVSConfig& config,
|
||||
bool separable_shader);
|
||||
|
||||
/**
|
||||
* Generates the GLSL fixed geometry shader program source code for non-GS PICA pipeline
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader);
|
||||
|
||||
/**
|
||||
* Generates the GLSL fragment shader program source code for the current Pica state
|
||||
* @param config ShaderCacheKey object generated for the current Pica state, used for the shader
|
||||
* configuration (NOTE: Use state in this struct only, not the Pica registers!)
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_shader);
|
||||
|
||||
} // namespace Pica::Shader::Generator::GLSL
|
281
src/video_core/shader/generator/shader_gen.cpp
Normal file
281
src/video_core/shader/generator/shader_gen.cpp
Normal file
|
@ -0,0 +1,281 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/bit_set.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/shader/generator/shader_gen.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Pica::Shader::Generator {
|
||||
|
||||
PicaFSConfig::PicaFSConfig(const Pica::Regs& regs, bool has_fragment_shader_interlock,
|
||||
bool emulate_logic_op, bool emulate_custom_border_color,
|
||||
bool emulate_blend_minmax_factor, bool use_custom_normal_map) {
|
||||
state.scissor_test_mode.Assign(regs.rasterizer.scissor_test.mode);
|
||||
|
||||
state.depthmap_enable.Assign(regs.rasterizer.depthmap_enable);
|
||||
|
||||
state.alpha_test_func.Assign(regs.framebuffer.output_merger.alpha_test.enable
|
||||
? regs.framebuffer.output_merger.alpha_test.func.Value()
|
||||
: Pica::FramebufferRegs::CompareFunc::Always);
|
||||
|
||||
state.texture0_type.Assign(regs.texturing.texture0.type);
|
||||
|
||||
state.texture2_use_coord1.Assign(regs.texturing.main_config.texture2_use_coord1 != 0);
|
||||
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
for (u32 tex_index = 0; tex_index < 3; tex_index++) {
|
||||
const auto config = pica_textures[tex_index].config;
|
||||
state.texture_border_color[tex_index].enable_s.Assign(
|
||||
emulate_custom_border_color &&
|
||||
config.wrap_s == Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
|
||||
state.texture_border_color[tex_index].enable_t.Assign(
|
||||
emulate_custom_border_color &&
|
||||
config.wrap_t == Pica::TexturingRegs::TextureConfig::WrapMode::ClampToBorder);
|
||||
}
|
||||
|
||||
// Emulate logic op in the shader if not supported. This is mostly for mobile GPUs
|
||||
const bool needs_emulate_logic_op =
|
||||
emulate_logic_op && !regs.framebuffer.output_merger.alphablend_enable;
|
||||
|
||||
state.emulate_logic_op.Assign(needs_emulate_logic_op);
|
||||
if (needs_emulate_logic_op) {
|
||||
state.logic_op.Assign(regs.framebuffer.output_merger.logic_op);
|
||||
} else {
|
||||
state.logic_op.Assign(Pica::FramebufferRegs::LogicOp::NoOp);
|
||||
}
|
||||
|
||||
// Copy relevant tev stages fields.
|
||||
// We don't sync const_color here because of the high variance, it is a
|
||||
// shader uniform instead.
|
||||
const auto& tev_stages = regs.texturing.GetTevStages();
|
||||
DEBUG_ASSERT(state.tev_stages.size() == tev_stages.size());
|
||||
for (std::size_t i = 0; i < tev_stages.size(); i++) {
|
||||
const auto& tev_stage = tev_stages[i];
|
||||
state.tev_stages[i].sources_raw = tev_stage.sources_raw;
|
||||
state.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw;
|
||||
state.tev_stages[i].ops_raw = tev_stage.ops_raw;
|
||||
state.tev_stages[i].scales_raw = tev_stage.scales_raw;
|
||||
if (tev_stage.color_op == Pica::TexturingRegs::TevStageConfig::Operation::Dot3_RGBA) {
|
||||
state.tev_stages[i].sources_raw &= 0xFFF;
|
||||
state.tev_stages[i].modifiers_raw &= 0xFFF;
|
||||
state.tev_stages[i].ops_raw &= 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
state.fog_mode.Assign(regs.texturing.fog_mode);
|
||||
state.fog_flip.Assign(regs.texturing.fog_flip != 0);
|
||||
|
||||
state.combiner_buffer_input.Assign(
|
||||
regs.texturing.tev_combiner_buffer_input.update_mask_rgb.Value() |
|
||||
regs.texturing.tev_combiner_buffer_input.update_mask_a.Value() << 4);
|
||||
|
||||
// Fragment lighting
|
||||
state.lighting.enable.Assign(!regs.lighting.disable);
|
||||
if (state.lighting.enable) {
|
||||
state.lighting.src_num.Assign(regs.lighting.max_light_index + 1);
|
||||
|
||||
for (u32 light_index = 0; light_index < state.lighting.src_num; ++light_index) {
|
||||
const u32 num = regs.lighting.light_enable.GetNum(light_index);
|
||||
const auto& light = regs.lighting.light[num];
|
||||
state.lighting.light[light_index].num.Assign(num);
|
||||
state.lighting.light[light_index].directional.Assign(light.config.directional != 0);
|
||||
state.lighting.light[light_index].two_sided_diffuse.Assign(
|
||||
light.config.two_sided_diffuse != 0);
|
||||
state.lighting.light[light_index].geometric_factor_0.Assign(
|
||||
light.config.geometric_factor_0 != 0);
|
||||
state.lighting.light[light_index].geometric_factor_1.Assign(
|
||||
light.config.geometric_factor_1 != 0);
|
||||
state.lighting.light[light_index].dist_atten_enable.Assign(
|
||||
!regs.lighting.IsDistAttenDisabled(num));
|
||||
state.lighting.light[light_index].spot_atten_enable.Assign(
|
||||
!regs.lighting.IsSpotAttenDisabled(num));
|
||||
state.lighting.light[light_index].shadow_enable.Assign(
|
||||
!regs.lighting.IsShadowDisabled(num));
|
||||
}
|
||||
|
||||
state.lighting.lut_d0.enable.Assign(regs.lighting.config1.disable_lut_d0 == 0);
|
||||
if (state.lighting.lut_d0.enable) {
|
||||
state.lighting.lut_d0.abs_input.Assign(regs.lighting.abs_lut_input.disable_d0 == 0);
|
||||
state.lighting.lut_d0.type.Assign(regs.lighting.lut_input.d0.Value());
|
||||
state.lighting.lut_d0.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
|
||||
}
|
||||
|
||||
state.lighting.lut_d1.enable.Assign(regs.lighting.config1.disable_lut_d1 == 0);
|
||||
if (state.lighting.lut_d1.enable) {
|
||||
state.lighting.lut_d1.abs_input.Assign(regs.lighting.abs_lut_input.disable_d1 == 0);
|
||||
state.lighting.lut_d1.type.Assign(regs.lighting.lut_input.d1.Value());
|
||||
state.lighting.lut_d1.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
|
||||
}
|
||||
|
||||
// this is a dummy field due to lack of the corresponding register
|
||||
state.lighting.lut_sp.enable.Assign(1);
|
||||
state.lighting.lut_sp.abs_input.Assign(regs.lighting.abs_lut_input.disable_sp == 0);
|
||||
state.lighting.lut_sp.type.Assign(regs.lighting.lut_input.sp.Value());
|
||||
state.lighting.lut_sp.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.sp);
|
||||
|
||||
state.lighting.lut_fr.enable.Assign(regs.lighting.config1.disable_lut_fr == 0);
|
||||
if (state.lighting.lut_fr.enable) {
|
||||
state.lighting.lut_fr.abs_input.Assign(regs.lighting.abs_lut_input.disable_fr == 0);
|
||||
state.lighting.lut_fr.type.Assign(regs.lighting.lut_input.fr.Value());
|
||||
state.lighting.lut_fr.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
|
||||
}
|
||||
|
||||
state.lighting.lut_rr.enable.Assign(regs.lighting.config1.disable_lut_rr == 0);
|
||||
if (state.lighting.lut_rr.enable) {
|
||||
state.lighting.lut_rr.abs_input.Assign(regs.lighting.abs_lut_input.disable_rr == 0);
|
||||
state.lighting.lut_rr.type.Assign(regs.lighting.lut_input.rr.Value());
|
||||
state.lighting.lut_rr.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
|
||||
}
|
||||
|
||||
state.lighting.lut_rg.enable.Assign(regs.lighting.config1.disable_lut_rg == 0);
|
||||
if (state.lighting.lut_rg.enable) {
|
||||
state.lighting.lut_rg.abs_input.Assign(regs.lighting.abs_lut_input.disable_rg == 0);
|
||||
state.lighting.lut_rg.type.Assign(regs.lighting.lut_input.rg.Value());
|
||||
state.lighting.lut_rg.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
|
||||
}
|
||||
|
||||
state.lighting.lut_rb.enable.Assign(regs.lighting.config1.disable_lut_rb == 0);
|
||||
if (state.lighting.lut_rb.enable) {
|
||||
state.lighting.lut_rb.abs_input.Assign(regs.lighting.abs_lut_input.disable_rb == 0);
|
||||
state.lighting.lut_rb.type.Assign(regs.lighting.lut_input.rb.Value());
|
||||
state.lighting.lut_rb.scale =
|
||||
regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
|
||||
}
|
||||
|
||||
state.lighting.config.Assign(regs.lighting.config0.config);
|
||||
state.lighting.enable_primary_alpha.Assign(regs.lighting.config0.enable_primary_alpha);
|
||||
state.lighting.enable_secondary_alpha.Assign(regs.lighting.config0.enable_secondary_alpha);
|
||||
state.lighting.bump_mode.Assign(regs.lighting.config0.bump_mode);
|
||||
state.lighting.bump_selector.Assign(regs.lighting.config0.bump_selector);
|
||||
state.lighting.bump_renorm.Assign(regs.lighting.config0.disable_bump_renorm == 0);
|
||||
state.lighting.clamp_highlights.Assign(regs.lighting.config0.clamp_highlights != 0);
|
||||
|
||||
state.lighting.enable_shadow.Assign(regs.lighting.config0.enable_shadow != 0);
|
||||
if (state.lighting.enable_shadow) {
|
||||
state.lighting.shadow_primary.Assign(regs.lighting.config0.shadow_primary != 0);
|
||||
state.lighting.shadow_secondary.Assign(regs.lighting.config0.shadow_secondary != 0);
|
||||
state.lighting.shadow_invert.Assign(regs.lighting.config0.shadow_invert != 0);
|
||||
state.lighting.shadow_alpha.Assign(regs.lighting.config0.shadow_alpha != 0);
|
||||
state.lighting.shadow_selector.Assign(regs.lighting.config0.shadow_selector);
|
||||
}
|
||||
}
|
||||
|
||||
state.proctex.enable.Assign(regs.texturing.main_config.texture3_enable);
|
||||
if (state.proctex.enable) {
|
||||
state.proctex.coord.Assign(regs.texturing.main_config.texture3_coordinates);
|
||||
state.proctex.u_clamp.Assign(regs.texturing.proctex.u_clamp);
|
||||
state.proctex.v_clamp.Assign(regs.texturing.proctex.v_clamp);
|
||||
state.proctex.color_combiner.Assign(regs.texturing.proctex.color_combiner);
|
||||
state.proctex.alpha_combiner.Assign(regs.texturing.proctex.alpha_combiner);
|
||||
state.proctex.separate_alpha.Assign(regs.texturing.proctex.separate_alpha);
|
||||
state.proctex.noise_enable.Assign(regs.texturing.proctex.noise_enable);
|
||||
state.proctex.u_shift.Assign(regs.texturing.proctex.u_shift);
|
||||
state.proctex.v_shift.Assign(regs.texturing.proctex.v_shift);
|
||||
state.proctex.lut_width = regs.texturing.proctex_lut.width;
|
||||
state.proctex.lut_offset0 = regs.texturing.proctex_lut_offset.level0;
|
||||
state.proctex.lut_offset1 = regs.texturing.proctex_lut_offset.level1;
|
||||
state.proctex.lut_offset2 = regs.texturing.proctex_lut_offset.level2;
|
||||
state.proctex.lut_offset3 = regs.texturing.proctex_lut_offset.level3;
|
||||
state.proctex.lod_min = regs.texturing.proctex_lut.lod_min;
|
||||
state.proctex.lod_max = regs.texturing.proctex_lut.lod_max;
|
||||
state.proctex.lut_filter.Assign(regs.texturing.proctex_lut.filter);
|
||||
}
|
||||
|
||||
const auto alpha_eq = regs.framebuffer.output_merger.alpha_blending.blend_equation_a.Value();
|
||||
const auto rgb_eq = regs.framebuffer.output_merger.alpha_blending.blend_equation_rgb.Value();
|
||||
if (emulate_blend_minmax_factor && regs.framebuffer.output_merger.alphablend_enable) {
|
||||
if (rgb_eq == Pica::FramebufferRegs::BlendEquation::Max ||
|
||||
rgb_eq == Pica::FramebufferRegs::BlendEquation::Min) {
|
||||
state.rgb_blend.emulate_blending = true;
|
||||
state.rgb_blend.eq = rgb_eq;
|
||||
state.rgb_blend.src_factor =
|
||||
regs.framebuffer.output_merger.alpha_blending.factor_source_rgb;
|
||||
state.rgb_blend.dst_factor =
|
||||
regs.framebuffer.output_merger.alpha_blending.factor_dest_rgb;
|
||||
}
|
||||
if (alpha_eq == Pica::FramebufferRegs::BlendEquation::Max ||
|
||||
alpha_eq == Pica::FramebufferRegs::BlendEquation::Min) {
|
||||
state.alpha_blend.emulate_blending = true;
|
||||
state.alpha_blend.eq = alpha_eq;
|
||||
state.alpha_blend.src_factor =
|
||||
regs.framebuffer.output_merger.alpha_blending.factor_source_a;
|
||||
state.alpha_blend.dst_factor =
|
||||
regs.framebuffer.output_merger.alpha_blending.factor_dest_a;
|
||||
}
|
||||
}
|
||||
|
||||
state.shadow_rendering.Assign(regs.framebuffer.output_merger.fragment_operation_mode ==
|
||||
Pica::FramebufferRegs::FragmentOperationMode::Shadow);
|
||||
state.shadow_texture_orthographic.Assign(regs.texturing.shadow.orthographic != 0);
|
||||
|
||||
// We only need fragment shader interlock when shadow rendering.
|
||||
state.use_fragment_shader_interlock.Assign(state.shadow_rendering &&
|
||||
has_fragment_shader_interlock);
|
||||
state.use_custom_normal_map.Assign(use_custom_normal_map);
|
||||
}
|
||||
|
||||
void PicaGSConfigState::Init(const Pica::Regs& regs, bool use_clip_planes_) {
|
||||
use_clip_planes = use_clip_planes_;
|
||||
|
||||
vs_output_attributes = Common::BitSet<u32>(regs.vs.output_mask).Count();
|
||||
gs_output_attributes = vs_output_attributes;
|
||||
|
||||
semantic_maps.fill({16, 0});
|
||||
for (u32 attrib = 0; attrib < regs.rasterizer.vs_output_total; ++attrib) {
|
||||
const std::array semantics{
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_x.Value(),
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_y.Value(),
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_z.Value(),
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_w.Value(),
|
||||
};
|
||||
for (u32 comp = 0; comp < 4; ++comp) {
|
||||
const auto semantic = semantics[comp];
|
||||
if (static_cast<std::size_t>(semantic) < 24) {
|
||||
semantic_maps[static_cast<std::size_t>(semantic)] = {attrib, comp};
|
||||
} else if (semantic != Pica::RasterizerRegs::VSOutputAttributes::INVALID) {
|
||||
LOG_ERROR(Render, "Invalid/unknown semantic id: {}", semantic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PicaVSConfigState::Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup,
|
||||
bool use_clip_planes_, bool use_geometry_shader_) {
|
||||
use_clip_planes = use_clip_planes_;
|
||||
use_geometry_shader = use_geometry_shader_;
|
||||
|
||||
program_hash = setup.GetProgramCodeHash();
|
||||
swizzle_hash = setup.GetSwizzleDataHash();
|
||||
main_offset = regs.vs.main_offset;
|
||||
sanitize_mul = VideoCore::g_hw_shader_accurate_mul;
|
||||
|
||||
num_outputs = 0;
|
||||
load_flags.fill(AttribLoadFlags::Float);
|
||||
output_map.fill(16);
|
||||
|
||||
for (int reg : Common::BitSet<u32>(regs.vs.output_mask)) {
|
||||
output_map[reg] = num_outputs++;
|
||||
}
|
||||
|
||||
if (!use_geometry_shader_) {
|
||||
gs_state.Init(regs, use_clip_planes_);
|
||||
}
|
||||
}
|
||||
|
||||
PicaVSConfig::PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup,
|
||||
bool use_clip_planes_, bool use_geometry_shader_) {
|
||||
state.Init(regs, setup, use_clip_planes_, use_geometry_shader_);
|
||||
}
|
||||
|
||||
PicaFixedGSConfig::PicaFixedGSConfig(const Pica::Regs& regs, bool use_clip_planes_) {
|
||||
state.Init(regs, use_clip_planes_);
|
||||
}
|
||||
|
||||
} // namespace Pica::Shader::Generator
|
260
src/video_core/shader/generator/shader_gen.h
Normal file
260
src/video_core/shader/generator/shader_gen.h
Normal file
|
@ -0,0 +1,260 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/hash.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
namespace Pica::Shader::Generator {
|
||||
|
||||
enum ProgramType : u32 {
|
||||
VS = 0,
|
||||
GS = 2,
|
||||
FS = 1,
|
||||
};
|
||||
|
||||
enum Attributes {
|
||||
ATTRIBUTE_POSITION,
|
||||
ATTRIBUTE_COLOR,
|
||||
ATTRIBUTE_TEXCOORD0,
|
||||
ATTRIBUTE_TEXCOORD1,
|
||||
ATTRIBUTE_TEXCOORD2,
|
||||
ATTRIBUTE_TEXCOORD0_W,
|
||||
ATTRIBUTE_NORMQUAT,
|
||||
ATTRIBUTE_VIEW,
|
||||
};
|
||||
|
||||
// Doesn't include const_color because we don't sync it, see comment in BuildFromRegs()
|
||||
struct TevStageConfigRaw {
|
||||
u32 sources_raw;
|
||||
u32 modifiers_raw;
|
||||
u32 ops_raw;
|
||||
u32 scales_raw;
|
||||
explicit operator Pica::TexturingRegs::TevStageConfig() const noexcept {
|
||||
return {
|
||||
.sources_raw = sources_raw,
|
||||
.modifiers_raw = modifiers_raw,
|
||||
.ops_raw = ops_raw,
|
||||
.const_color = 0,
|
||||
.scales_raw = scales_raw,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
struct PicaFSConfigState {
|
||||
union {
|
||||
BitField<0, 3, Pica::FramebufferRegs::CompareFunc> alpha_test_func;
|
||||
BitField<3, 2, Pica::RasterizerRegs::ScissorMode> scissor_test_mode;
|
||||
BitField<5, 3, Pica::TexturingRegs::TextureConfig::TextureType> texture0_type;
|
||||
BitField<8, 1, u32> texture2_use_coord1;
|
||||
BitField<9, 8, u32> combiner_buffer_input;
|
||||
BitField<17, 1, Pica::RasterizerRegs::DepthBuffering> depthmap_enable;
|
||||
BitField<18, 3, Pica::TexturingRegs::FogMode> fog_mode;
|
||||
BitField<21, 1, u32> fog_flip;
|
||||
BitField<22, 1, u32> emulate_logic_op;
|
||||
BitField<23, 4, Pica::FramebufferRegs::LogicOp> logic_op;
|
||||
BitField<27, 1, u32> shadow_rendering;
|
||||
BitField<28, 1, u32> shadow_texture_orthographic;
|
||||
BitField<29, 1, u32> use_fragment_shader_interlock;
|
||||
BitField<30, 1, u32> use_custom_normal_map;
|
||||
};
|
||||
|
||||
union {
|
||||
BitField<0, 1, u32> enable_s;
|
||||
BitField<1, 1, u32> enable_t;
|
||||
} texture_border_color[3];
|
||||
|
||||
std::array<TevStageConfigRaw, 6> tev_stages;
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 3, u16> num;
|
||||
BitField<3, 1, u16> directional;
|
||||
BitField<4, 1, u16> two_sided_diffuse;
|
||||
BitField<5, 1, u16> dist_atten_enable;
|
||||
BitField<6, 1, u16> spot_atten_enable;
|
||||
BitField<7, 1, u16> geometric_factor_0;
|
||||
BitField<8, 1, u16> geometric_factor_1;
|
||||
BitField<9, 1, u16> shadow_enable;
|
||||
} light[8];
|
||||
|
||||
union {
|
||||
BitField<0, 1, u32> enable;
|
||||
BitField<1, 4, u32> src_num;
|
||||
BitField<5, 2, Pica::LightingRegs::LightingBumpMode> bump_mode;
|
||||
BitField<7, 2, u32> bump_selector;
|
||||
BitField<9, 1, u32> bump_renorm;
|
||||
BitField<10, 1, u32> clamp_highlights;
|
||||
BitField<11, 4, Pica::LightingRegs::LightingConfig> config;
|
||||
BitField<15, 1, u32> enable_primary_alpha;
|
||||
BitField<16, 1, u32> enable_secondary_alpha;
|
||||
BitField<17, 1, u32> enable_shadow;
|
||||
BitField<18, 1, u32> shadow_primary;
|
||||
BitField<19, 1, u32> shadow_secondary;
|
||||
BitField<20, 1, u32> shadow_invert;
|
||||
BitField<21, 1, u32> shadow_alpha;
|
||||
BitField<22, 2, u32> shadow_selector;
|
||||
};
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 1, u32> enable;
|
||||
BitField<1, 1, u32> abs_input;
|
||||
BitField<2, 3, Pica::LightingRegs::LightingLutInput> type;
|
||||
};
|
||||
float scale;
|
||||
} lut_d0, lut_d1, lut_sp, lut_fr, lut_rr, lut_rg, lut_rb;
|
||||
} lighting;
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 1, u32> enable;
|
||||
BitField<1, 2, u32> coord;
|
||||
BitField<3, 3, Pica::TexturingRegs::ProcTexClamp> u_clamp;
|
||||
BitField<6, 3, Pica::TexturingRegs::ProcTexClamp> v_clamp;
|
||||
BitField<9, 4, Pica::TexturingRegs::ProcTexCombiner> color_combiner;
|
||||
BitField<13, 4, Pica::TexturingRegs::ProcTexCombiner> alpha_combiner;
|
||||
BitField<17, 3, Pica::TexturingRegs::ProcTexFilter> lut_filter;
|
||||
BitField<20, 1, u32> separate_alpha;
|
||||
BitField<21, 1, u32> noise_enable;
|
||||
BitField<22, 2, Pica::TexturingRegs::ProcTexShift> u_shift;
|
||||
BitField<24, 2, Pica::TexturingRegs::ProcTexShift> v_shift;
|
||||
};
|
||||
s32 lut_width;
|
||||
s32 lut_offset0;
|
||||
s32 lut_offset1;
|
||||
s32 lut_offset2;
|
||||
s32 lut_offset3;
|
||||
u8 lod_min;
|
||||
u8 lod_max;
|
||||
} proctex;
|
||||
|
||||
struct {
|
||||
bool emulate_blending;
|
||||
Pica::FramebufferRegs::BlendEquation eq;
|
||||
Pica::FramebufferRegs::BlendFactor src_factor;
|
||||
Pica::FramebufferRegs::BlendFactor dst_factor;
|
||||
} rgb_blend, alpha_blend;
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains all state used to generate the GLSL fragment shader that emulates the
|
||||
* current Pica register configuration. This struct is used as a cache key for generated GLSL shader
|
||||
* programs. The functions in glsl_shader_gen.cpp should retrieve state from this struct only, not
|
||||
* by directly accessing Pica registers. This should reduce the risk of bugs in shader generation
|
||||
* where Pica state is not being captured in the shader cache key, thereby resulting in (what should
|
||||
* be) two separate shaders sharing the same key.
|
||||
*/
|
||||
struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> {
|
||||
PicaFSConfig(const Pica::Regs& regs, bool has_fragment_shader_interlock, bool emulate_logic_op,
|
||||
bool emulate_custom_border_color, bool emulate_blend_minmax_factor,
|
||||
bool use_custom_normal_map = false);
|
||||
|
||||
[[nodiscard]] bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
|
||||
return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool TevStageUpdatesCombinerBufferAlpha(unsigned stage_index) const {
|
||||
return (stage_index < 4) && ((state.combiner_buffer_input >> 4) & (1 << stage_index));
|
||||
}
|
||||
};
|
||||
|
||||
enum class AttribLoadFlags {
|
||||
Float = 1 << 0,
|
||||
Sint = 1 << 1,
|
||||
Uint = 1 << 2,
|
||||
ZeroW = 1 << 3,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AttribLoadFlags)
|
||||
|
||||
/**
|
||||
* This struct contains common information to identify a GLSL geometry shader generated from
|
||||
* PICA geometry shader.
|
||||
*/
|
||||
struct PicaGSConfigState {
|
||||
void Init(const Pica::Regs& regs, bool use_clip_planes_);
|
||||
|
||||
bool use_clip_planes;
|
||||
|
||||
u32 vs_output_attributes;
|
||||
u32 gs_output_attributes;
|
||||
|
||||
struct SemanticMap {
|
||||
u32 attribute_index;
|
||||
u32 component_index;
|
||||
};
|
||||
|
||||
// semantic_maps[semantic name] -> GS output attribute index + component index
|
||||
std::array<SemanticMap, 24> semantic_maps;
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains common information to identify a GLSL vertex shader generated from
|
||||
* PICA vertex shader.
|
||||
*/
|
||||
struct PicaVSConfigState {
|
||||
void Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup, bool use_clip_planes_,
|
||||
bool use_geometry_shader_);
|
||||
|
||||
bool use_clip_planes;
|
||||
bool use_geometry_shader;
|
||||
|
||||
u64 program_hash;
|
||||
u64 swizzle_hash;
|
||||
u32 main_offset;
|
||||
bool sanitize_mul;
|
||||
|
||||
u32 num_outputs;
|
||||
// Load operations to apply to the input vertex data
|
||||
std::array<AttribLoadFlags, 16> load_flags;
|
||||
|
||||
// output_map[output register index] -> output attribute index
|
||||
std::array<u32, 16> output_map;
|
||||
|
||||
PicaGSConfigState gs_state;
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains information to identify a GL vertex shader generated from PICA vertex
|
||||
* shader.
|
||||
*/
|
||||
struct PicaVSConfig : Common::HashableStruct<PicaVSConfigState> {
|
||||
explicit PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup,
|
||||
bool use_clip_planes_, bool use_geometry_shader_);
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains information to identify a GL geometry shader generated from PICA no-geometry
|
||||
* shader pipeline
|
||||
*/
|
||||
struct PicaFixedGSConfig : Common::HashableStruct<PicaGSConfigState> {
|
||||
explicit PicaFixedGSConfig(const Pica::Regs& regs, bool use_clip_planes_);
|
||||
};
|
||||
|
||||
} // namespace Pica::Shader::Generator
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<Pica::Shader::Generator::PicaFSConfig> {
|
||||
std::size_t operator()(const Pica::Shader::Generator::PicaFSConfig& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<Pica::Shader::Generator::PicaVSConfig> {
|
||||
std::size_t operator()(const Pica::Shader::Generator::PicaVSConfig& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<Pica::Shader::Generator::PicaFixedGSConfig> {
|
||||
std::size_t operator()(const Pica::Shader::Generator::PicaFixedGSConfig& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
} // namespace std
|
26
src/video_core/shader/generator/shader_uniforms.cpp
Normal file
26
src/video_core/shader/generator/shader_uniforms.cpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "video_core/shader/generator/shader_uniforms.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
namespace Pica::Shader::Generator {
|
||||
|
||||
void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs,
|
||||
const Pica::Shader::ShaderSetup& setup) {
|
||||
std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools),
|
||||
[](bool value) -> BoolAligned { return {value ? 1 : 0}; });
|
||||
std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i),
|
||||
[](const auto& value) -> Common::Vec4u {
|
||||
return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()};
|
||||
});
|
||||
std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f),
|
||||
[](const auto& value) -> Common::Vec4f {
|
||||
return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(),
|
||||
value.w.ToFloat32()};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Pica::Shader::Generator
|
|
@ -12,10 +12,10 @@ struct ShaderRegs;
|
|||
}
|
||||
|
||||
namespace Pica::Shader {
|
||||
|
||||
struct ShaderSetup;
|
||||
}
|
||||
|
||||
enum class UniformBindings : u32 { Common, VS, GS };
|
||||
namespace Pica::Shader::Generator {
|
||||
|
||||
struct LightSrc {
|
||||
alignas(16) Common::Vec3f specular_0;
|
||||
|
@ -34,7 +34,7 @@ struct LightSrc {
|
|||
* the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
||||
* Not following that rule will cause problems on some AMD drivers.
|
||||
*/
|
||||
struct UniformData {
|
||||
struct FSUniformData {
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
|
@ -53,7 +53,6 @@ struct UniformData {
|
|||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
alignas(4) bool enable_clip1;
|
||||
alignas(16) Common::Vec4i lighting_lut_offset[LightingRegs::NumLightingSampler / 4];
|
||||
alignas(16) Common::Vec3f fog_color;
|
||||
alignas(8) Common::Vec2f proctex_noise_f;
|
||||
|
@ -65,13 +64,12 @@ struct UniformData {
|
|||
alignas(16) Common::Vec4f tev_combiner_buffer_color;
|
||||
alignas(16) Common::Vec3f tex_lod_bias;
|
||||
alignas(16) Common::Vec4f tex_border_color[3];
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
alignas(16) Common::Vec4f blend_color;
|
||||
};
|
||||
|
||||
static_assert(sizeof(UniformData) == 0x540,
|
||||
static_assert(sizeof(FSUniformData) == 0x530,
|
||||
"The size of the UniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384,
|
||||
static_assert(sizeof(FSUniformData) < 16384,
|
||||
"UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/**
|
||||
|
@ -91,13 +89,20 @@ struct PicaUniformsData {
|
|||
};
|
||||
|
||||
struct VSUniformData {
|
||||
PicaUniformsData uniforms;
|
||||
bool enable_clip1;
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
};
|
||||
static_assert(sizeof(VSUniformData) == 1856,
|
||||
static_assert(sizeof(VSUniformData) == 32,
|
||||
"The size of the VSUniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(VSUniformData) < 16384,
|
||||
"VSUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
std::string BuildShaderUniformDefinitions(const std::string& extra_layout_parameters = "");
|
||||
struct VSPicaUniformData {
|
||||
alignas(16) PicaUniformsData uniforms;
|
||||
};
|
||||
static_assert(sizeof(VSPicaUniformData) == 1856,
|
||||
"The size of the VSPicaUniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(VSPicaUniformData) < 16384,
|
||||
"VSPicaUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
} // namespace Pica::Shader
|
||||
} // namespace Pica::Shader::Generator
|
1559
src/video_core/shader/generator/spv_shader_gen.cpp
Normal file
1559
src/video_core/shader/generator/spv_shader_gen.cpp
Normal file
File diff suppressed because it is too large
Load diff
294
src/video_core/shader/generator/spv_shader_gen.h
Normal file
294
src/video_core/shader/generator/spv_shader_gen.h
Normal file
|
@ -0,0 +1,294 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <sirit/sirit.h>
|
||||
|
||||
#include "video_core/shader/generator/shader_gen.h"
|
||||
|
||||
namespace Core {
|
||||
class TelemetrySession;
|
||||
}
|
||||
|
||||
namespace Pica::Shader::Generator::SPIRV {
|
||||
|
||||
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;
|
||||
static constexpr u32 NUM_TEX_UNITS = 4;
|
||||
static constexpr u32 NUM_NON_PROC_TEX_UNITS = 3;
|
||||
|
||||
public:
|
||||
explicit FragmentModule(Core::TelemetrySession& telemetry, const PicaFSConfig& config);
|
||||
~FragmentModule();
|
||||
|
||||
/// Emits SPIR-V bytecode corresponding to the provided pica fragment configuration
|
||||
void Generate();
|
||||
|
||||
private:
|
||||
/// Undos the host perspective transformation and applies the PICA one
|
||||
void WriteDepth();
|
||||
|
||||
/// Emits code to emulate the scissor rectangle
|
||||
void WriteScissor();
|
||||
|
||||
/// Writes the code to emulate fragment lighting
|
||||
void WriteLighting();
|
||||
|
||||
/// Writes the code to emulate fog
|
||||
void WriteFog();
|
||||
|
||||
/// Writes the code to emulate gas rendering
|
||||
void WriteGas();
|
||||
|
||||
/// Writes the code to emulate the specified TEV stage
|
||||
void WriteTevStage(s32 index);
|
||||
|
||||
/// Defines the basic texture sampling functions for a unit
|
||||
void DefineTexSampler(u32 texture_unit);
|
||||
|
||||
/// Function for sampling the procedurally generated texture unit.
|
||||
Id ProcTexSampler();
|
||||
|
||||
/// Writes the if-statement condition used to evaluate alpha testing.
|
||||
void WriteAlphaTestCondition(Pica::FramebufferRegs::CompareFunc func);
|
||||
|
||||
/// Samples the current fragment texel from shadow plane
|
||||
[[nodiscard]] Id SampleShadow();
|
||||
|
||||
[[nodiscard]] Id AppendProcTexShiftOffset(Id v, Pica::TexturingRegs::ProcTexShift mode,
|
||||
Pica::TexturingRegs::ProcTexClamp clamp_mode);
|
||||
|
||||
[[nodiscard]] Id AppendProcTexClamp(Id var, Pica::TexturingRegs::ProcTexClamp mode);
|
||||
|
||||
[[nodiscard]] Id AppendProcTexCombineAndMap(Pica::TexturingRegs::ProcTexCombiner combiner, Id u,
|
||||
Id v, Id offset);
|
||||
|
||||
/// Rounds the provided variable to the nearest 1/255th
|
||||
[[nodiscard]] Id Byteround(Id variable_id, u32 size = 1);
|
||||
|
||||
/// LUT sampling uitlity
|
||||
/// For NoiseLUT/ColorMap/AlphaMap, coord=0.0 is lut[0], coord=127.0/128.0 is lut[127] and
|
||||
/// coord=1.0 is lut[127]+lut_diff[127]. For other indices, the result is interpolated using
|
||||
/// value entries and difference entries.
|
||||
[[nodiscard]] Id ProcTexLookupLUT(Id offset, Id coord);
|
||||
|
||||
/// Generates random noise with proctex
|
||||
[[nodiscard]] Id ProcTexNoiseCoef(Id x);
|
||||
|
||||
/// Samples a color value from the rgba texture lut
|
||||
[[nodiscard]] Id SampleProcTexColor(Id lut_coord, Id level);
|
||||
|
||||
/// Lookups the lighting LUT at the provided lut_index
|
||||
[[nodiscard]] Id LookupLightingLUT(Id lut_index, Id index, Id delta);
|
||||
|
||||
/// Writes the specified TEV stage source component(s)
|
||||
[[nodiscard]] Id AppendSource(Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||
|
||||
/// Writes the color components to use for the specified TEV stage color modifier
|
||||
[[nodiscard]] Id AppendColorModifier(
|
||||
Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
|
||||
Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||
|
||||
/// Writes the alpha component to use for the specified TEV stage alpha modifier
|
||||
[[nodiscard]] Id AppendAlphaModifier(
|
||||
Pica::TexturingRegs::TevStageConfig::AlphaModifier modifier,
|
||||
Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||
|
||||
/// Writes the combiner function for the color components for the specified TEV stage operation
|
||||
[[nodiscard]] Id AppendColorCombiner(Pica::TexturingRegs::TevStageConfig::Operation operation);
|
||||
|
||||
/// Writes the combiner function for the alpha component for the specified TEV stage operation
|
||||
[[nodiscard]] Id AppendAlphaCombiner(Pica::TexturingRegs::TevStageConfig::Operation operation);
|
||||
|
||||
private:
|
||||
/// Creates a constant array of integers
|
||||
template <typename... T>
|
||||
void InitTableS32(Id table, T... elems) {
|
||||
const Id table_const{ConstS32(elems...)};
|
||||
OpStore(table, table_const);
|
||||
};
|
||||
|
||||
/// Loads the member specified from the shader_data uniform struct
|
||||
template <typename... Ids>
|
||||
[[nodiscard]] Id GetShaderDataMember(Id type, Ids... ids) {
|
||||
const Id uniform_ptr{TypePointer(spv::StorageClass::Uniform, type)};
|
||||
return OpLoad(type, OpAccessChain(uniform_ptr, shader_data_id, ids...));
|
||||
}
|
||||
|
||||
/// Pads the provided vector by inserting args at the end
|
||||
template <typename... Args>
|
||||
[[nodiscard]] Id PadVectorF32(Id vector, Id pad_type_id, Args&&... args) {
|
||||
return OpCompositeConstruct(pad_type_id, vector, ConstF32(args...));
|
||||
}
|
||||
|
||||
/// 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 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();
|
||||
Id CompareShadow(Id pixel, Id z);
|
||||
|
||||
private:
|
||||
Core::TelemetrySession& telemetry;
|
||||
PicaFSConfig config;
|
||||
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{};
|
||||
|
||||
Id image2d_id{};
|
||||
Id image_cube_id{};
|
||||
Id image_buffer_id{};
|
||||
Id image_r32_id{};
|
||||
Id sampler_id{};
|
||||
Id shader_data_id{};
|
||||
|
||||
Id primary_color_id{};
|
||||
Id texcoord_id[NUM_NON_PROC_TEX_UNITS]{};
|
||||
Id texcoord0_w_id{};
|
||||
Id normquat_id{};
|
||||
Id view_id{};
|
||||
Id color_id{};
|
||||
|
||||
Id gl_frag_coord_id{};
|
||||
Id gl_frag_depth_id{};
|
||||
Id depth{};
|
||||
|
||||
Id tex0_id{};
|
||||
Id tex1_id{};
|
||||
Id tex2_id{};
|
||||
Id tex_cube_id{};
|
||||
Id texture_buffer_lut_lf_id{};
|
||||
Id texture_buffer_lut_rg_id{};
|
||||
Id texture_buffer_lut_rgba_id{};
|
||||
Id shadow_texture_px_id{};
|
||||
|
||||
Id texture_buffer_lut_lf{};
|
||||
Id texture_buffer_lut_rg{};
|
||||
Id texture_buffer_lut_rgba{};
|
||||
|
||||
Id rounded_primary_color{};
|
||||
Id primary_fragment_color{};
|
||||
Id secondary_fragment_color{};
|
||||
Id combiner_buffer{};
|
||||
Id next_combiner_buffer{};
|
||||
Id last_tex_env_out{};
|
||||
|
||||
Id color_results_1{};
|
||||
Id color_results_2{};
|
||||
Id color_results_3{};
|
||||
Id alpha_results_1{};
|
||||
Id alpha_results_2{};
|
||||
Id alpha_results_3{};
|
||||
|
||||
Id sample_tex_unit_func[NUM_TEX_UNITS]{};
|
||||
Id noise1d_table{};
|
||||
Id noise2d_table{};
|
||||
Id lut_offsets{};
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the SPIR-V fragment shader program source code for the current Pica state
|
||||
* @param config ShaderCacheKey object generated for the current Pica state, used for the shader
|
||||
* configuration (NOTE: Use state in this struct only, not the Pica registers!)
|
||||
* @param separable_shader generates shader that can be used for separate shader object
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
std::vector<u32> GenerateFragmentShader(const PicaFSConfig& config);
|
||||
|
||||
} // namespace Pica::Shader::Generator::SPIRV
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "video_core/shader/shader.h"
|
||||
#include "video_core/shader/shader_uniforms.h"
|
||||
|
||||
namespace Pica::Shader {
|
||||
|
||||
void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs,
|
||||
const Pica::Shader::ShaderSetup& setup) {
|
||||
std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools),
|
||||
[](bool value) -> BoolAligned { return {value ? 1 : 0}; });
|
||||
std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i),
|
||||
[](const auto& value) -> Common::Vec4u {
|
||||
return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()};
|
||||
});
|
||||
std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f),
|
||||
[](const auto& value) -> Common::Vec4f {
|
||||
return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(),
|
||||
value.w.ToFloat32()};
|
||||
});
|
||||
}
|
||||
|
||||
constexpr std::string_view UniformBlockDefFormat = R"(
|
||||
#define NUM_TEV_STAGES 6
|
||||
#define NUM_LIGHTS 8
|
||||
#define NUM_LIGHTING_SAMPLERS 24
|
||||
struct LightSrc {{
|
||||
vec3 specular_0;
|
||||
vec3 specular_1;
|
||||
vec3 diffuse;
|
||||
vec3 ambient;
|
||||
vec3 position;
|
||||
vec3 spot_direction;
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
}};
|
||||
layout ({}std140) uniform shader_data {{
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
float depth_offset;
|
||||
float shadow_bias_constant;
|
||||
float shadow_bias_linear;
|
||||
int scissor_x1;
|
||||
int scissor_y1;
|
||||
int scissor_x2;
|
||||
int scissor_y2;
|
||||
int fog_lut_offset;
|
||||
int proctex_noise_lut_offset;
|
||||
int proctex_color_map_offset;
|
||||
int proctex_alpha_map_offset;
|
||||
int proctex_lut_offset;
|
||||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
bool enable_clip1;
|
||||
ivec4 lighting_lut_offset[NUM_LIGHTING_SAMPLERS / 4];
|
||||
vec3 fog_color;
|
||||
vec2 proctex_noise_f;
|
||||
vec2 proctex_noise_a;
|
||||
vec2 proctex_noise_p;
|
||||
vec3 lighting_global_ambient;
|
||||
LightSrc light_src[NUM_LIGHTS];
|
||||
vec4 const_color[NUM_TEV_STAGES];
|
||||
vec4 tev_combiner_buffer_color;
|
||||
vec3 tex_lod_bias;
|
||||
vec4 tex_border_color[3];
|
||||
vec4 clip_coef;
|
||||
vec4 blend_color;
|
||||
}};
|
||||
)";
|
||||
|
||||
std::string BuildShaderUniformDefinitions(const std::string& extra_layout_parameters) {
|
||||
return fmt::format(UniformBlockDefFormat, extra_layout_parameters);
|
||||
}
|
||||
|
||||
} // namespace Pica::Shader
|
Loading…
Add table
Add a link
Reference in a new issue