Merge pull request #3686 from wwylele/glvtx-shader-gen
gl_shader_gen: generate programmable vs/gs and fixed gs
This commit is contained in:
commit
be5777f3de
@ -1266,7 +1266,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetShader() {
|
||||
auto config = GLShader::PicaShaderConfig::BuildFromRegs(Pica::g_state.regs);
|
||||
auto config = GLShader::PicaFSConfig::BuildFromRegs(Pica::g_state.regs);
|
||||
shader_program_manager->UseFragmentShader(config);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/bit_set.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
@ -14,6 +15,7 @@
|
||||
#include "video_core/regs_rasterizer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
|
||||
@ -22,6 +24,7 @@ using Pica::LightingRegs;
|
||||
using Pica::RasterizerRegs;
|
||||
using Pica::TexturingRegs;
|
||||
using TevStageConfig = TexturingRegs::TevStageConfig;
|
||||
using VSOutputAttributes = RasterizerRegs::VSOutputAttributes;
|
||||
|
||||
namespace GLShader {
|
||||
|
||||
@ -92,8 +95,8 @@ out gl_PerVertex {
|
||||
return out;
|
||||
}
|
||||
|
||||
PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
|
||||
PicaShaderConfig res;
|
||||
PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) {
|
||||
PicaFSConfig res;
|
||||
|
||||
auto& state = res.state;
|
||||
|
||||
@ -219,6 +222,59 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
|
||||
return res;
|
||||
}
|
||||
|
||||
void PicaShaderConfigCommon::Init(const Pica::ShaderRegs& regs, Pica::Shader::ShaderSetup& setup) {
|
||||
program_hash = setup.GetProgramCodeHash();
|
||||
swizzle_hash = setup.GetSwizzleDataHash();
|
||||
main_offset = regs.main_offset;
|
||||
sanitize_mul = false; // TODO (wwylele): stubbed now. Should sync with user settings
|
||||
|
||||
num_outputs = 0;
|
||||
output_map.fill(16);
|
||||
|
||||
for (int reg : Common::BitSet<u32>(regs.output_mask)) {
|
||||
output_map[reg] = num_outputs++;
|
||||
}
|
||||
}
|
||||
|
||||
void PicaGSConfigCommonRaw::Init(const Pica::Regs& regs) {
|
||||
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) {
|
||||
std::array<VSOutputAttributes::Semantic, 4> semantics = {
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_x,
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_y,
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_z,
|
||||
regs.rasterizer.vs_output_attributes[attrib].map_w};
|
||||
for (u32 comp = 0; comp < 4; ++comp) {
|
||||
const auto semantic = semantics[comp];
|
||||
if (static_cast<size_t>(semantic) < 24) {
|
||||
semantic_maps[static_cast<size_t>(semantic)] = {attrib, comp};
|
||||
} else if (semantic != VSOutputAttributes::INVALID) {
|
||||
NGLOG_ERROR(Render_OpenGL, "Invalid/unknown semantic id: {}",
|
||||
static_cast<u32>(semantic));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PicaGSConfigRaw::Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup) {
|
||||
PicaShaderConfigCommon::Init(regs.gs, setup);
|
||||
PicaGSConfigCommonRaw::Init(regs);
|
||||
|
||||
num_inputs = regs.gs.max_input_attribute_index + 1;
|
||||
input_map.fill(16);
|
||||
|
||||
for (u32 attr = 0; attr < num_inputs; ++attr) {
|
||||
input_map[regs.gs.GetRegisterForAttribute(attr)] = attr;
|
||||
}
|
||||
|
||||
attributes_per_vertex = regs.pipeline.vs_outmap_total_minus_1_a + 1;
|
||||
|
||||
gs_output_attributes = num_outputs;
|
||||
}
|
||||
|
||||
/// Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code)
|
||||
static bool IsPassThroughTevStage(const TevStageConfig& stage) {
|
||||
return (stage.color_op == TevStageConfig::Operation::Replace &&
|
||||
@ -230,7 +286,7 @@ static bool IsPassThroughTevStage(const TevStageConfig& stage) {
|
||||
stage.GetColorMultiplier() == 1 && stage.GetAlphaMultiplier() == 1);
|
||||
}
|
||||
|
||||
static std::string SampleTexture(const PicaShaderConfig& config, unsigned texture_unit) {
|
||||
static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_unit) {
|
||||
const auto& state = config.state;
|
||||
switch (texture_unit) {
|
||||
case 0:
|
||||
@ -274,7 +330,7 @@ static std::string SampleTexture(const PicaShaderConfig& config, unsigned textur
|
||||
}
|
||||
|
||||
/// Writes the specified TEV stage source component(s)
|
||||
static void AppendSource(std::string& out, const PicaShaderConfig& config,
|
||||
static void AppendSource(std::string& out, const PicaFSConfig& config,
|
||||
TevStageConfig::Source source, const std::string& index_name) {
|
||||
const auto& state = config.state;
|
||||
using Source = TevStageConfig::Source;
|
||||
@ -317,7 +373,7 @@ static void AppendSource(std::string& out, const PicaShaderConfig& config,
|
||||
}
|
||||
|
||||
/// Writes the color components to use for the specified TEV stage color modifier
|
||||
static void AppendColorModifier(std::string& out, const PicaShaderConfig& config,
|
||||
static void AppendColorModifier(std::string& out, const PicaFSConfig& config,
|
||||
TevStageConfig::ColorModifier modifier,
|
||||
TevStageConfig::Source source, const std::string& index_name) {
|
||||
using ColorModifier = TevStageConfig::ColorModifier;
|
||||
@ -375,7 +431,7 @@ static void AppendColorModifier(std::string& out, const PicaShaderConfig& config
|
||||
}
|
||||
|
||||
/// Writes the alpha component to use for the specified TEV stage alpha modifier
|
||||
static void AppendAlphaModifier(std::string& out, const PicaShaderConfig& config,
|
||||
static void AppendAlphaModifier(std::string& out, const PicaFSConfig& config,
|
||||
TevStageConfig::AlphaModifier modifier,
|
||||
TevStageConfig::Source source, const std::string& index_name) {
|
||||
using AlphaModifier = TevStageConfig::AlphaModifier;
|
||||
@ -540,7 +596,7 @@ static void AppendAlphaTestCondition(std::string& out, FramebufferRegs::CompareF
|
||||
}
|
||||
|
||||
/// Writes the code to emulate the specified TEV stage
|
||||
static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsigned index) {
|
||||
static void WriteTevStage(std::string& out, const PicaFSConfig& config, unsigned index) {
|
||||
const auto stage =
|
||||
static_cast<const TexturingRegs::TevStageConfig>(config.state.tev_stages[index]);
|
||||
if (!IsPassThroughTevStage(stage)) {
|
||||
@ -598,7 +654,7 @@ static void WriteTevStage(std::string& out, const PicaShaderConfig& config, unsi
|
||||
}
|
||||
|
||||
/// Writes the code to emulate fragment lighting
|
||||
static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
|
||||
static void WriteLighting(std::string& out, const PicaFSConfig& config) {
|
||||
const auto& lighting = config.state.lighting;
|
||||
|
||||
// Define lighting globals
|
||||
@ -994,7 +1050,7 @@ void AppendProcTexCombineAndMap(std::string& out, ProcTexCombiner combiner,
|
||||
out += "ProcTexLookupLUT(" + map_lut + ", " + combined + ")";
|
||||
}
|
||||
|
||||
void AppendProcTexSampler(std::string& out, const PicaShaderConfig& config) {
|
||||
void AppendProcTexSampler(std::string& out, const PicaFSConfig& config) {
|
||||
// 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
|
||||
@ -1121,7 +1177,7 @@ float ProcTexNoiseCoef(vec2 x) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string GenerateFragmentShader(const PicaShaderConfig& config, bool separable_shader) {
|
||||
std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_shader) {
|
||||
const auto& state = config.state;
|
||||
|
||||
std::string out = "#version 330 core\n";
|
||||
@ -1327,4 +1383,296 @@ void main() {
|
||||
return out;
|
||||
}
|
||||
|
||||
boost::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
|
||||
const PicaVSConfig& config,
|
||||
bool separable_shader) {
|
||||
std::string out = "#version 330 core\n";
|
||||
if (separable_shader) {
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n";
|
||||
}
|
||||
|
||||
out += Pica::Shader::Decompiler::GetCommonDeclarations();
|
||||
|
||||
std::array<bool, 16> used_regs{};
|
||||
auto get_input_reg = [&](u32 reg) -> std::string {
|
||||
ASSERT(reg < 16);
|
||||
used_regs[reg] = true;
|
||||
return "vs_in_reg" + std::to_string(reg);
|
||||
};
|
||||
|
||||
auto get_output_reg = [&](u32 reg) -> std::string {
|
||||
ASSERT(reg < 16);
|
||||
if (config.state.output_map[reg] < config.state.num_outputs) {
|
||||
return "vs_out_attr" + std::to_string(config.state.output_map[reg]);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
auto program_source_opt = Pica::Shader::Decompiler::DecompileProgram(
|
||||
setup.program_code, setup.swizzle_data, config.state.main_offset, get_input_reg,
|
||||
get_output_reg, config.state.sanitize_mul, false);
|
||||
|
||||
if (!program_source_opt)
|
||||
return boost::none;
|
||||
|
||||
std::string& program_source = program_source_opt.get();
|
||||
|
||||
out += R"(
|
||||
#define uniforms vs_uniforms
|
||||
layout (std140) uniform vs_config {
|
||||
pica_uniforms uniforms;
|
||||
};
|
||||
|
||||
)";
|
||||
// input attributes declaration
|
||||
for (std::size_t i = 0; i < used_regs.size(); ++i) {
|
||||
if (used_regs[i]) {
|
||||
out += "layout(location = " + std::to_string(i) + ") in vec4 vs_in_reg" +
|
||||
std::to_string(i) + ";\n";
|
||||
}
|
||||
}
|
||||
out += "\n";
|
||||
|
||||
// output attributes declaration
|
||||
for (u32 i = 0; i < config.state.num_outputs; ++i) {
|
||||
out += (separable_shader ? "layout(location = " + std::to_string(i) + ")" : std::string{}) +
|
||||
" out vec4 vs_out_attr" + std::to_string(i) + ";\n";
|
||||
}
|
||||
|
||||
out += "\nvoid main() {\n";
|
||||
for (u32 i = 0; i < config.state.num_outputs; ++i) {
|
||||
out += " vs_out_attr" + std::to_string(i) + " = vec4(0.0, 0.0, 0.0, 1.0);\n";
|
||||
}
|
||||
out += "\n exec_shader();\n}\n\n";
|
||||
|
||||
out += program_source;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::string GetGSCommonSource(const PicaGSConfigCommonRaw& config, bool separable_shader) {
|
||||
std::string out = GetVertexInterfaceDeclaration(true, separable_shader);
|
||||
out += UniformBlockDef;
|
||||
out += Pica::Shader::Decompiler::GetCommonDeclarations();
|
||||
|
||||
out += '\n';
|
||||
for (u32 i = 0; i < config.vs_output_attributes; ++i) {
|
||||
out += (separable_shader ? "layout(location = " + std::to_string(i) + ")" : std::string{}) +
|
||||
" in vec4 vs_out_attr" + std::to_string(i) + "[];\n";
|
||||
}
|
||||
|
||||
out += R"(
|
||||
#define uniforms gs_uniforms
|
||||
layout (std140) uniform gs_config {
|
||||
pica_uniforms uniforms;
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
)";
|
||||
out += " vec4 attributes[" + std::to_string(config.gs_output_attributes) + "];\n";
|
||||
out += "};\n\n";
|
||||
|
||||
auto semantic = [&config](VSOutputAttributes::Semantic slot_semantic) -> std::string {
|
||||
u32 slot = static_cast<u32>(slot_semantic);
|
||||
u32 attrib = config.semantic_maps[slot].attribute_index;
|
||||
u32 comp = config.semantic_maps[slot].component_index;
|
||||
if (attrib < config.gs_output_attributes) {
|
||||
return "vtx.attributes[" + std::to_string(attrib) + "]." + "xyzw"[comp];
|
||||
}
|
||||
return "0.0";
|
||||
};
|
||||
|
||||
out += "vec4 GetVertexQuaternion(Vertex vtx) {\n";
|
||||
out += " return vec4(" + semantic(VSOutputAttributes::QUATERNION_X) + ", " +
|
||||
semantic(VSOutputAttributes::QUATERNION_Y) + ", " +
|
||||
semantic(VSOutputAttributes::QUATERNION_Z) + ", " +
|
||||
semantic(VSOutputAttributes::QUATERNION_W) + ");\n";
|
||||
out += "}\n\n";
|
||||
|
||||
out += "void EmitVtx(Vertex vtx, bool quats_opposite) {\n";
|
||||
out += " vec4 vtx_pos = vec4(" + semantic(VSOutputAttributes::POSITION_X) + ", " +
|
||||
semantic(VSOutputAttributes::POSITION_Y) + ", " +
|
||||
semantic(VSOutputAttributes::POSITION_Z) + ", " +
|
||||
semantic(VSOutputAttributes::POSITION_W) + ");\n";
|
||||
out += " gl_Position = vtx_pos;\n";
|
||||
out += " gl_ClipDistance[0] = -vtx_pos.z;\n"; // fixed PICA clipping plane z <= 0
|
||||
out += " gl_ClipDistance[1] = dot(clip_coef, vtx_pos);\n\n";
|
||||
|
||||
out += " vec4 vtx_quat = GetVertexQuaternion(vtx);\n";
|
||||
out += " normquat = mix(vtx_quat, -vtx_quat, bvec4(quats_opposite));\n\n";
|
||||
|
||||
out += " vec4 vtx_color = vec4(" + semantic(VSOutputAttributes::COLOR_R) + ", " +
|
||||
semantic(VSOutputAttributes::COLOR_G) + ", " + semantic(VSOutputAttributes::COLOR_B) +
|
||||
", " + semantic(VSOutputAttributes::COLOR_A) + ");\n";
|
||||
out += " primary_color = min(abs(vtx_color), vec4(1.0));\n\n";
|
||||
|
||||
out += " texcoord0 = vec2(" + semantic(VSOutputAttributes::TEXCOORD0_U) + ", " +
|
||||
semantic(VSOutputAttributes::TEXCOORD0_V) + ");\n";
|
||||
out += " texcoord1 = vec2(" + semantic(VSOutputAttributes::TEXCOORD1_U) + ", " +
|
||||
semantic(VSOutputAttributes::TEXCOORD1_V) + ");\n\n";
|
||||
|
||||
out += " texcoord0_w = " + semantic(VSOutputAttributes::TEXCOORD0_W) + ";\n";
|
||||
out += " view = vec3(" + semantic(VSOutputAttributes::VIEW_X) + ", " +
|
||||
semantic(VSOutputAttributes::VIEW_Y) + ", " + semantic(VSOutputAttributes::VIEW_Z) +
|
||||
");\n\n";
|
||||
|
||||
out += " texcoord2 = vec2(" + semantic(VSOutputAttributes::TEXCOORD2_U) + ", " +
|
||||
semantic(VSOutputAttributes::TEXCOORD2_V) + ");\n\n";
|
||||
|
||||
out += " EmitVertex();\n";
|
||||
out += "}\n";
|
||||
|
||||
out += R"(
|
||||
bool AreQuaternionsOpposite(vec4 qa, vec4 qb) {
|
||||
return (dot(qa, qb) < 0.0);
|
||||
}
|
||||
|
||||
void EmitPrim(Vertex vtx0, Vertex vtx1, Vertex vtx2) {
|
||||
EmitVtx(vtx0, false);
|
||||
EmitVtx(vtx1, AreQuaternionsOpposite(GetVertexQuaternion(vtx0), GetVertexQuaternion(vtx1)));
|
||||
EmitVtx(vtx2, AreQuaternionsOpposite(GetVertexQuaternion(vtx0), GetVertexQuaternion(vtx2)));
|
||||
EndPrimitive();
|
||||
}
|
||||
)";
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool separable_shader) {
|
||||
std::string out = "#version 330 core\n";
|
||||
if (separable_shader) {
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
}
|
||||
|
||||
out += R"(
|
||||
layout(triangles) in;
|
||||
layout(triangle_strip, max_vertices = 3) out;
|
||||
|
||||
)";
|
||||
|
||||
out += GetGSCommonSource(config.state, separable_shader);
|
||||
|
||||
out += R"(
|
||||
void main() {
|
||||
Vertex prim_buffer[3];
|
||||
)";
|
||||
for (u32 vtx = 0; vtx < 3; ++vtx) {
|
||||
out += " prim_buffer[" + std::to_string(vtx) + "].attributes = vec4[" +
|
||||
std::to_string(config.state.gs_output_attributes) + "](";
|
||||
for (u32 i = 0; i < config.state.vs_output_attributes; ++i) {
|
||||
out += std::string(i == 0 ? "" : ", ") + "vs_out_attr" + std::to_string(i) + "[" +
|
||||
std::to_string(vtx) + "]";
|
||||
}
|
||||
out += ");\n";
|
||||
}
|
||||
out += " EmitPrim(prim_buffer[0], prim_buffer[1], prim_buffer[2]);\n";
|
||||
out += "}\n";
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
|
||||
const PicaGSConfig& config,
|
||||
bool separable_shader) {
|
||||
std::string out = "#version 330 core\n";
|
||||
if (separable_shader) {
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n";
|
||||
}
|
||||
|
||||
if (config.state.num_inputs % config.state.attributes_per_vertex != 0)
|
||||
return boost::none;
|
||||
|
||||
switch (config.state.num_inputs / config.state.attributes_per_vertex) {
|
||||
case 1:
|
||||
out += "layout(points) in;\n";
|
||||
break;
|
||||
case 2:
|
||||
out += "layout(lines) in;\n";
|
||||
break;
|
||||
case 4:
|
||||
out += "layout(lines_adjacency) in;\n";
|
||||
break;
|
||||
case 3:
|
||||
out += "layout(triangles) in;\n";
|
||||
break;
|
||||
case 6:
|
||||
out += "layout(triangles_adjacency) in;\n";
|
||||
break;
|
||||
default:
|
||||
return boost::none;
|
||||
}
|
||||
out += "layout(triangle_strip, max_vertices = 30) out;\n\n";
|
||||
|
||||
out += GetGSCommonSource(config.state, separable_shader);
|
||||
|
||||
auto get_input_reg = [&](u32 reg) -> std::string {
|
||||
ASSERT(reg < 16);
|
||||
u32 attr = config.state.input_map[reg];
|
||||
if (attr < config.state.num_inputs) {
|
||||
return "vs_out_attr" + std::to_string(attr % config.state.attributes_per_vertex) + "[" +
|
||||
std::to_string(attr / config.state.attributes_per_vertex) + "]";
|
||||
}
|
||||
return "vec4(0.0, 0.0, 0.0, 1.0)";
|
||||
};
|
||||
|
||||
auto get_output_reg = [&](u32 reg) -> std::string {
|
||||
ASSERT(reg < 16);
|
||||
if (config.state.output_map[reg] < config.state.num_outputs) {
|
||||
return "output_buffer.attributes[" + std::to_string(config.state.output_map[reg]) + "]";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
auto program_source_opt = Pica::Shader::Decompiler::DecompileProgram(
|
||||
setup.program_code, setup.swizzle_data, config.state.main_offset, get_input_reg,
|
||||
get_output_reg, config.state.sanitize_mul, true);
|
||||
|
||||
if (!program_source_opt)
|
||||
return boost::none;
|
||||
|
||||
std::string& program_source = program_source_opt.get();
|
||||
|
||||
out += R"(
|
||||
Vertex output_buffer;
|
||||
Vertex prim_buffer[3];
|
||||
uint vertex_id = 0u;
|
||||
bool prim_emit = false;
|
||||
bool winding = false;
|
||||
|
||||
void setemit(uint vertex_id_, bool prim_emit_, bool winding_) {
|
||||
vertex_id = vertex_id_;
|
||||
prim_emit = prim_emit_;
|
||||
winding = winding_;
|
||||
}
|
||||
|
||||
void emit() {
|
||||
prim_buffer[vertex_id] = output_buffer;
|
||||
|
||||
if (prim_emit) {
|
||||
if (winding) {
|
||||
EmitPrim(prim_buffer[1], prim_buffer[0], prim_buffer[2]);
|
||||
winding = false;
|
||||
} else {
|
||||
EmitPrim(prim_buffer[0], prim_buffer[1], prim_buffer[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
)";
|
||||
for (u32 i = 0; i < config.state.num_outputs; ++i) {
|
||||
out +=
|
||||
" output_buffer.attributes[" + std::to_string(i) + "] = vec4(0.0, 0.0, 0.0, 1.0);\n";
|
||||
}
|
||||
|
||||
// execute shader
|
||||
out += "\n exec_shader();\n\n";
|
||||
|
||||
out += "}\n\n";
|
||||
|
||||
out += program_source;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace GLShader
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/hash.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
@ -47,7 +48,7 @@ struct TevStageConfigRaw {
|
||||
}
|
||||
};
|
||||
|
||||
struct PicaShaderConfigState {
|
||||
struct PicaFSConfigState {
|
||||
Pica::FramebufferRegs::CompareFunc alpha_test_func;
|
||||
Pica::RasterizerRegs::ScissorMode scissor_test_mode;
|
||||
Pica::TexturingRegs::TextureConfig::TextureType texture0_type;
|
||||
@ -112,17 +113,17 @@ struct PicaShaderConfigState {
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains all state used to generate the GLSL shader program that emulates the current
|
||||
* Pica register configuration. This struct is used as a cache key for generated GLSL shader
|
||||
* 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 gl_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 PicaShaderConfig : Common::HashableStruct<PicaShaderConfigState> {
|
||||
struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> {
|
||||
|
||||
/// Construct a PicaShaderConfig with the given Pica register configuration.
|
||||
static PicaShaderConfig BuildFromRegs(const Pica::Regs& regs);
|
||||
/// Construct a PicaFSConfig with the given Pica register configuration.
|
||||
static PicaFSConfig BuildFromRegs(const Pica::Regs& regs);
|
||||
|
||||
bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
|
||||
return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));
|
||||
@ -133,6 +134,79 @@ struct PicaShaderConfig : Common::HashableStruct<PicaShaderConfigState> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains common information to identify a GL vertex/geometry shader generated from
|
||||
* PICA vertex/geometry shader.
|
||||
*/
|
||||
struct PicaShaderConfigCommon {
|
||||
void Init(const Pica::ShaderRegs& regs, Pica::Shader::ShaderSetup& setup);
|
||||
|
||||
u64 program_hash;
|
||||
u64 swizzle_hash;
|
||||
u32 main_offset;
|
||||
bool sanitize_mul;
|
||||
|
||||
u32 num_outputs;
|
||||
|
||||
// output_map[output register index] -> output attribute index
|
||||
std::array<u32, 16> output_map;
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains information to identify a GL vertex shader generated from PICA vertex
|
||||
* shader.
|
||||
*/
|
||||
struct PicaVSConfig : Common::HashableStruct<PicaShaderConfigCommon> {
|
||||
explicit PicaVSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup) {
|
||||
state.Init(regs.vs, setup);
|
||||
}
|
||||
};
|
||||
|
||||
struct PicaGSConfigCommonRaw {
|
||||
void Init(const Pica::Regs& regs);
|
||||
|
||||
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 information to identify a GL geometry shader generated from PICA no-geometry
|
||||
* shader pipeline
|
||||
*/
|
||||
struct PicaFixedGSConfig : Common::HashableStruct<PicaGSConfigCommonRaw> {
|
||||
explicit PicaFixedGSConfig(const Pica::Regs& regs) {
|
||||
state.Init(regs);
|
||||
}
|
||||
};
|
||||
|
||||
struct PicaGSConfigRaw : PicaShaderConfigCommon, PicaGSConfigCommonRaw {
|
||||
void Init(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setup);
|
||||
|
||||
u32 num_inputs;
|
||||
u32 attributes_per_vertex;
|
||||
|
||||
// input_map[input register index] -> input attribute index
|
||||
std::array<u32, 16> input_map;
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct contains information to identify a GL geometry shader generated from PICA geometry
|
||||
* shader.
|
||||
*/
|
||||
struct PicaGSConfig : Common::HashableStruct<PicaGSConfigRaw> {
|
||||
explicit PicaGSConfig(const Pica::Regs& regs, Pica::Shader::ShaderSetup& setups) {
|
||||
state.Init(regs, setups);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the GLSL vertex shader program source code that accepts vertices from software shader
|
||||
* and directly passes them to the fragment shader.
|
||||
@ -141,6 +215,29 @@ struct PicaShaderConfig : Common::HashableStruct<PicaShaderConfigState> {
|
||||
*/
|
||||
std::string GenerateTrivialVertexShader(bool separable_shader);
|
||||
|
||||
/**
|
||||
* Generates the GLSL vertex shader program source code for the given VS program
|
||||
* @returns String of the shader source code; boost::none on failure
|
||||
*/
|
||||
boost::optional<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 geometry shader program source code for the given GS program and its
|
||||
* configuration
|
||||
* @returns String of the shader source code; boost::none on failure
|
||||
*/
|
||||
boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
|
||||
const PicaGSConfig& 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
|
||||
@ -148,14 +245,35 @@ std::string GenerateTrivialVertexShader(bool separable_shader);
|
||||
* @param separable_shader generates shader that can be used for separate shader object
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
std::string GenerateFragmentShader(const PicaShaderConfig& config, bool separable_shader);
|
||||
std::string GenerateFragmentShader(const PicaFSConfig& config, bool separable_shader);
|
||||
|
||||
} // namespace GLShader
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<GLShader::PicaShaderConfig> {
|
||||
size_t operator()(const GLShader::PicaShaderConfig& k) const {
|
||||
struct hash<GLShader::PicaFSConfig> {
|
||||
size_t operator()(const GLShader::PicaFSConfig& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<GLShader::PicaVSConfig> {
|
||||
size_t operator()(const GLShader::PicaVSConfig& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<GLShader::PicaFixedGSConfig> {
|
||||
size_t operator()(const GLShader::PicaFixedGSConfig& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<GLShader::PicaGSConfig> {
|
||||
size_t operator()(const GLShader::PicaGSConfig& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
@ -23,6 +24,8 @@ static void SetShaderUniformBlockBinding(GLuint shader, const char* name, Unifor
|
||||
static void SetShaderUniformBlockBindings(GLuint shader) {
|
||||
SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common,
|
||||
sizeof(UniformData));
|
||||
SetShaderUniformBlockBinding(shader, "vs_config", UniformBindings::VS, sizeof(VSUniformData));
|
||||
SetShaderUniformBlockBinding(shader, "gs_config", UniformBindings::GS, sizeof(GSUniformData));
|
||||
}
|
||||
|
||||
static void SetShaderSamplerBinding(GLuint shader, const char* name,
|
||||
@ -57,6 +60,21 @@ static void SetShaderSamplerBindings(GLuint shader) {
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
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 ? GL_TRUE : GL_FALSE}; });
|
||||
std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i),
|
||||
[](const auto& value) -> GLuvec4 {
|
||||
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) -> GLvec4 {
|
||||
return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(),
|
||||
value.w.ToFloat32()};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An object representing a shader program staging. It can be either a shader object or a program
|
||||
* object, depending on whether separable program is used.
|
||||
@ -128,13 +146,70 @@ private:
|
||||
std::unordered_map<KeyConfigType, OGLShaderStage> shaders;
|
||||
};
|
||||
|
||||
// This is a cache designed for shaders translated from PICA shaders. The first cache matches the
|
||||
// config structure like a normal cache does. On cache miss, the second cache matches the generated
|
||||
// GLSL code. The configuration is like this because there might be leftover code in the PICA shader
|
||||
// program buffer from the previous shader, which is hashed into the config, resulting several
|
||||
// different config values from the same shader program.
|
||||
template <typename KeyConfigType,
|
||||
boost::optional<std::string> (*CodeGenerator)(const Pica::Shader::ShaderSetup&,
|
||||
const KeyConfigType&, bool),
|
||||
GLenum ShaderType>
|
||||
class ShaderDoubleCache {
|
||||
public:
|
||||
explicit ShaderDoubleCache(bool separable) : separable(separable) {}
|
||||
GLuint Get(const KeyConfigType& key, const Pica::Shader::ShaderSetup& setup) {
|
||||
auto map_it = shader_map.find(key);
|
||||
if (map_it == shader_map.end()) {
|
||||
auto program_opt = CodeGenerator(setup, key, separable);
|
||||
if (!program_opt) {
|
||||
shader_map[key] = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string& program = program_opt.get();
|
||||
auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable});
|
||||
OGLShaderStage& cached_shader = iter->second;
|
||||
if (new_shader) {
|
||||
cached_shader.Create(program.c_str(), ShaderType);
|
||||
}
|
||||
shader_map[key] = &cached_shader;
|
||||
return cached_shader.GetHandle();
|
||||
}
|
||||
|
||||
if (map_it->second == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return map_it->second->GetHandle();
|
||||
}
|
||||
|
||||
private:
|
||||
bool separable;
|
||||
std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map;
|
||||
std::unordered_map<std::string, OGLShaderStage> shader_cache;
|
||||
};
|
||||
|
||||
using ProgrammableVertexShaders =
|
||||
ShaderDoubleCache<GLShader::PicaVSConfig, &GLShader::GenerateVertexShader, GL_VERTEX_SHADER>;
|
||||
|
||||
using ProgrammableGeometryShaders =
|
||||
ShaderDoubleCache<GLShader::PicaGSConfig, &GLShader::GenerateGeometryShader,
|
||||
GL_GEOMETRY_SHADER>;
|
||||
|
||||
using FixedGeometryShaders =
|
||||
ShaderCache<GLShader::PicaFixedGSConfig, &GLShader::GenerateFixedGeometryShader,
|
||||
GL_GEOMETRY_SHADER>;
|
||||
|
||||
using FragmentShaders =
|
||||
ShaderCache<GLShader::PicaShaderConfig, &GLShader::GenerateFragmentShader, GL_FRAGMENT_SHADER>;
|
||||
ShaderCache<GLShader::PicaFSConfig, &GLShader::GenerateFragmentShader, GL_FRAGMENT_SHADER>;
|
||||
|
||||
class ShaderProgramManager::Impl {
|
||||
public:
|
||||
explicit Impl(bool separable)
|
||||
: separable(separable), trivial_vertex_shader(separable), fragment_shaders(separable) {
|
||||
: separable(separable), programmable_vertex_shaders(separable),
|
||||
trivial_vertex_shader(separable), programmable_geometry_shaders(separable),
|
||||
fixed_geometry_shaders(separable), fragment_shaders(separable) {
|
||||
if (separable)
|
||||
pipeline.Create();
|
||||
}
|
||||
@ -165,8 +240,12 @@ public:
|
||||
|
||||
ShaderTuple current;
|
||||
|
||||
ProgrammableVertexShaders programmable_vertex_shaders;
|
||||
TrivialVertexShader trivial_vertex_shader;
|
||||
|
||||
ProgrammableGeometryShaders programmable_geometry_shaders;
|
||||
FixedGeometryShaders fixed_geometry_shaders;
|
||||
|
||||
FragmentShaders fragment_shaders;
|
||||
|
||||
bool separable;
|
||||
@ -179,15 +258,37 @@ ShaderProgramManager::ShaderProgramManager(bool separable)
|
||||
|
||||
ShaderProgramManager::~ShaderProgramManager() = default;
|
||||
|
||||
bool ShaderProgramManager::UseProgrammableVertexShader(const GLShader::PicaVSConfig& config,
|
||||
const Pica::Shader::ShaderSetup setup) {
|
||||
GLuint handle = impl->programmable_vertex_shaders.Get(config, setup);
|
||||
if (handle == 0)
|
||||
return false;
|
||||
impl->current.vs = handle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderProgramManager::UseTrivialVertexShader() {
|
||||
impl->current.vs = impl->trivial_vertex_shader.Get();
|
||||
}
|
||||
|
||||
bool ShaderProgramManager::UseProgrammableGeometryShader(const GLShader::PicaGSConfig& config,
|
||||
const Pica::Shader::ShaderSetup setup) {
|
||||
GLuint handle = impl->programmable_geometry_shaders.Get(config, setup);
|
||||
if (handle == 0)
|
||||
return false;
|
||||
impl->current.gs = handle;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderProgramManager::UseFixedGeometryShader(const GLShader::PicaFixedGSConfig& config) {
|
||||
impl->current.gs = impl->fixed_geometry_shaders.Get(config);
|
||||
}
|
||||
|
||||
void ShaderProgramManager::UseTrivialGeometryShader() {
|
||||
impl->current.gs = 0;
|
||||
}
|
||||
|
||||
void ShaderProgramManager::UseFragmentShader(const GLShader::PicaShaderConfig& config) {
|
||||
void ShaderProgramManager::UseFragmentShader(const GLShader::PicaFSConfig& config) {
|
||||
impl->current.fs = impl->fragment_shaders.Get(config);
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/pica_to_gl.h"
|
||||
|
||||
enum class UniformBindings : GLuint { Common };
|
||||
enum class UniformBindings : GLuint { Common, VS, GS };
|
||||
|
||||
struct LightSrc {
|
||||
alignas(16) GLvec3 specular_0;
|
||||
@ -53,17 +53,57 @@ static_assert(
|
||||
static_assert(sizeof(UniformData) < 16384,
|
||||
"UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/// Uniform struct for the Uniform Buffer Object that contains PICA vertex/geometry shader uniforms.
|
||||
// NOTE: the same rule from UniformData also applies here.
|
||||
struct PicaUniformsData {
|
||||
void SetFromRegs(const Pica::ShaderRegs& regs, const Pica::Shader::ShaderSetup& setup);
|
||||
|
||||
struct BoolAligned {
|
||||
alignas(16) GLint b;
|
||||
};
|
||||
|
||||
std::array<BoolAligned, 16> bools;
|
||||
alignas(16) std::array<GLuvec4, 4> i;
|
||||
alignas(16) std::array<GLvec4, 96> f;
|
||||
};
|
||||
|
||||
struct VSUniformData {
|
||||
PicaUniformsData uniforms;
|
||||
};
|
||||
static_assert(
|
||||
sizeof(VSUniformData) == 1856,
|
||||
"The size of the VSUniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(VSUniformData) < 16384,
|
||||
"VSUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
struct GSUniformData {
|
||||
PicaUniformsData uniforms;
|
||||
};
|
||||
static_assert(
|
||||
sizeof(GSUniformData) == 1856,
|
||||
"The size of the GSUniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(GSUniformData) < 16384,
|
||||
"GSUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/// A class that manage different shader stages and configures them with given config data.
|
||||
class ShaderProgramManager {
|
||||
public:
|
||||
explicit ShaderProgramManager(bool separable);
|
||||
~ShaderProgramManager();
|
||||
|
||||
bool UseProgrammableVertexShader(const GLShader::PicaVSConfig& config,
|
||||
const Pica::Shader::ShaderSetup setup);
|
||||
|
||||
void UseTrivialVertexShader();
|
||||
|
||||
bool UseProgrammableGeometryShader(const GLShader::PicaGSConfig& config,
|
||||
const Pica::Shader::ShaderSetup setup);
|
||||
|
||||
void UseFixedGeometryShader(const GLShader::PicaFixedGSConfig& config);
|
||||
|
||||
void UseTrivialGeometryShader();
|
||||
|
||||
void UseFragmentShader(const GLShader::PicaShaderConfig& config);
|
||||
void UseFragmentShader(const GLShader::PicaFSConfig& config);
|
||||
|
||||
void ApplyTo(OpenGLState& state);
|
||||
|
||||
|
@ -19,6 +19,10 @@ using GLvec2 = std::array<GLfloat, 2>;
|
||||
using GLvec3 = std::array<GLfloat, 3>;
|
||||
using GLvec4 = std::array<GLfloat, 4>;
|
||||
|
||||
using GLuvec2 = std::array<GLuint, 2>;
|
||||
using GLuvec3 = std::array<GLuint, 3>;
|
||||
using GLuvec4 = std::array<GLuint, 4>;
|
||||
|
||||
namespace PicaToGL {
|
||||
|
||||
inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) {
|
||||
|
Loading…
Reference in New Issue
Block a user