gl_shader_gen: generate programmable vs/gs and fixed gs

This commit is contained in:
wwylele 2018-04-23 22:20:30 +03:00
parent 8186820d16
commit 191b29e402
5 changed files with 632 additions and 25 deletions

View file

@ -1265,7 +1265,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);
}

View file

@ -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

View file

@ -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();
}
};

View file

@ -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);
}

View file

@ -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);