Merge branch 'specialized-shaderz' into specialized-shaders-2

This commit is contained in:
wheremyfoodat 2024-05-12 23:20:12 +03:00
commit 798c651a17
6 changed files with 652 additions and 49 deletions

View file

@ -0,0 +1,355 @@
#include "PICA/shader_gen.hpp"
using namespace PICA;
using namespace PICA::ShaderGen;
std::string FragmentGenerator::getVertexShader(const PICARegs& regs) {
std::string ret = "";
switch (api) {
case API::GL: ret += "#version 410 core"; break;
case API::GLES: ret += "#version 300 es"; break;
default: break;
}
if (api == API::GLES) {
ret += R"(
#define USING_GLES 1
precision mediump int;
precision mediump float;
)";
}
ret += R"(
layout(location = 0) in vec4 a_coords;
layout(location = 1) in vec4 a_quaternion;
layout(location = 2) in vec4 a_vertexColour;
layout(location = 3) in vec2 a_texcoord0;
layout(location = 4) in vec2 a_texcoord1;
layout(location = 5) in float a_texcoord0_w;
layout(location = 6) in vec3 a_view;
layout(location = 7) in vec2 a_texcoord2;
out vec3 v_normal;
out vec3 v_tangent;
out vec3 v_bitangent;
out vec4 v_colour;
out vec3 v_texcoord0;
out vec2 v_texcoord1;
out vec3 v_view;
out vec2 v_texcoord2;
flat out vec4 v_textureEnvColor[6];
flat out vec4 v_textureEnvBufferColor;
//out float gl_ClipDistance[2];
vec4 abgr8888ToVec4(uint abgr) {
const float scale = 1.0 / 255.0;
return scale * vec4(float(abgr & 0xffu), float((abgr >> 8) & 0xffu), float((abgr >> 16) & 0xffu), float(abgr >> 24));
}
vec3 rotateVec3ByQuaternion(vec3 v, vec4 q) {
vec3 u = q.xyz;
float s = q.w;
return 2.0 * dot(u, v) * u + (s * s - dot(u, u)) * v + 2.0 * s * cross(u, v);
}
void main() {
gl_Position = a_coords;
vec4 colourAbs = abs(a_vertexColour);
v_colour = min(colourAbs, vec4(1.f));
// Flip y axis of UVs because OpenGL uses an inverted y for texture sampling compared to the PICA
v_texcoord0 = vec3(a_texcoord0.x, 1.0 - a_texcoord0.y, a_texcoord0_w);
v_texcoord1 = vec2(a_texcoord1.x, 1.0 - a_texcoord1.y);
v_texcoord2 = vec2(a_texcoord2.x, 1.0 - a_texcoord2.y);
v_view = a_view;
v_normal = normalize(rotateVec3ByQuaternion(vec3(0.0, 0.0, 1.0), a_quaternion));
v_tangent = normalize(rotateVec3ByQuaternion(vec3(1.0, 0.0, 0.0), a_quaternion));
v_bitangent = normalize(rotateVec3ByQuaternion(vec3(0.0, 1.0, 0.0), a_quaternion));
}
)";
return ret;
}
std::string FragmentGenerator::generate(const PICARegs& regs) {
std::string ret = "";
switch (api) {
case API::GL: ret += "#version 410 core"; break;
case API::GLES: ret += "#version 300 es"; break;
default: break;
}
bool unimplementedFlag = false;
if (api == API::GLES) {
ret += R"(
#define USING_GLES 1
precision mediump int;
precision mediump float;
)";
}
// Input and output attributes
ret += R"(
in vec3 v_tangent;
in vec3 v_normal;
in vec3 v_bitangent;
in vec4 v_colour;
in vec3 v_texcoord0;
in vec2 v_texcoord1;
in vec3 v_view;
in vec2 v_texcoord2;
flat in vec4 v_textureEnvColor[6];
flat in vec4 v_textureEnvBufferColor;
out vec4 fragColor;
uniform sampler2D u_tex0;
uniform sampler2D u_tex1;
uniform sampler2D u_tex2;
// GLES doesn't support sampler1DArray, as such we'll have to change how we handle lighting later
#ifndef USING_GLES
uniform sampler1DArray u_tex_lighting_lut;
#endif
vec4 tevSources[16];
vec4 tevNextPreviousBuffer;
)";
// Emit main function for fragment shader
// When not initialized, source 13 is set to vec4(0.0) and 15 is set to the vertex colour
ret += R"(
void main() {
tevSources[0] = v_colour;
tevSources[13] = vec4(0.0); // Previous buffer colour
tevSources[15] = v_colour; // Previous combiner
vec4 combinerOutput = v_colour; // Last TEV output
)";
ret += R"(
vec3 colorOp1 = vec3(0.0);
vec3 colorOp2 = vec3(0.0);
vec3 colorOp3 = vec3(0.0);
float alphaOp1 = 0.0;
float alphaOp2 = 0.0;
float alphaOp3 = 0.0;
)";
textureConfig = regs[InternalRegs::TexUnitCfg];
for (int i = 0; i < 6; i++) {
compileTEV(ret, i, regs);
}
ret += "fragColor = combinerOutput;\n";
ret += "}"; // End of main function
ret += "\n\n\n\n\n\n\n\n\n\n\n\n\n";
return ret;
}
void FragmentGenerator::compileTEV(std::string& shader, int stage, const PICARegs& regs) {
// Base address for each TEV stage's configuration
static constexpr std::array<u32, 6> ioBases = {
InternalRegs::TexEnv0Source, InternalRegs::TexEnv1Source, InternalRegs::TexEnv2Source,
InternalRegs::TexEnv3Source, InternalRegs::TexEnv4Source, InternalRegs::TexEnv5Source,
};
const u32 ioBase = ioBases[stage];
TexEnvConfig tev(regs[ioBase], regs[ioBase + 1], regs[ioBase + 2], regs[ioBase + 3], regs[ioBase + 4]);
if (!tev.isPassthroughStage()) {
// Get color operands
shader += "colorOp1 = ";
getColorOperand(shader, tev.colorSource1, tev.colorOperand1, stage);
shader += ";\ncolorOp2 = ";
getColorOperand(shader, tev.colorSource2, tev.colorOperand2, stage);
shader += ";\ncolorOp3 = ";
getColorOperand(shader, tev.colorSource3, tev.colorOperand3, stage);
shader += ";\nvec3 outputColor" + std::to_string(stage) + " = ";
getColorOperation(shader, tev.colorOp);
shader += ";\n";
if (tev.colorOp == TexEnvConfig::Operation::Dot3RGBA) {
// Dot3 RGBA also writes to the alpha component so we don't need to do anything more
shader += "float outputAlpha" + std::to_string(stage) + " = colorOutput" + std::to_string(stage) + ".x;\n";
} else {
// Get alpha operands
shader += "alphaOp1 = ";
getAlphaOperand(shader, tev.alphaSource1, tev.alphaOperand1, stage);
shader += ";\nalphaOp2 = ";
getAlphaOperand(shader, tev.alphaSource2, tev.alphaOperand2, stage);
shader += ";\nalphaOp3 = ";
getAlphaOperand(shader, tev.alphaSource3, tev.alphaOperand3, stage);
shader += ";\nfloat outputAlpha" + std::to_string(stage) + " = ";
getAlphaOperation(shader, tev.alphaOp);
// Clamp the alpha value to [0.0, 1.0]
shader += ";\nclamp(outputAlpha" + std::to_string(stage) + ", 0.0, 1.0);\n";
}
shader += "combinerOutput = vec4(clamp(outputColor" + std::to_string(stage) + " * " + std::to_string(tev.getColorScale()) +
".0, vec3(0.0), vec3(1.0)), clamp(outputAlpha" + std::to_string(stage) + " * " + std::to_string(tev.getAlphaScale()) +
".0, 0.0, 1.0));\n";
}
}
void FragmentGenerator::getColorOperand(std::string& shader, TexEnvConfig::Source source, TexEnvConfig::ColorOperand color, int index) {
using OperandType = TexEnvConfig::ColorOperand;
// For inverting operands, add the 1.0 - x subtraction
if (color == OperandType::OneMinusSourceColor || color == OperandType::OneMinusSourceRed || color == OperandType::OneMinusSourceGreen ||
color == OperandType::OneMinusSourceBlue || color == OperandType::OneMinusSourceAlpha) {
shader += "vec3(1.0, 1.0, 1.0) - ";
}
switch (color) {
case OperandType::SourceColor:
case OperandType::OneMinusSourceColor:
getSource(shader, source, index);
shader += ".rgb";
break;
case OperandType::SourceRed:
case OperandType::OneMinusSourceRed:
getSource(shader, source, index);
shader += ".rrr";
break;
case OperandType::SourceGreen:
case OperandType::OneMinusSourceGreen:
getSource(shader, source, index);
shader += ".ggg";
break;
case OperandType::SourceBlue:
case OperandType::OneMinusSourceBlue:
getSource(shader, source, index);
shader += ".bbb";
break;
case OperandType::SourceAlpha:
case OperandType::OneMinusSourceAlpha:
getSource(shader, source, index);
shader += ".aaa";
break;
default:
shader += "vec3(1.0, 1.0, 1.0)";
Helpers::warn("FragmentGenerator: Invalid TEV color operand");
break;
}
}
void FragmentGenerator::getAlphaOperand(std::string& shader, TexEnvConfig::Source source, TexEnvConfig::AlphaOperand color, int index) {
using OperandType = TexEnvConfig::AlphaOperand;
// For inverting operands, add the 1.0 - x subtraction
if (color == OperandType::OneMinusSourceRed || color == OperandType::OneMinusSourceGreen || color == OperandType::OneMinusSourceBlue ||
color == OperandType::OneMinusSourceAlpha) {
shader += "1.0 - ";
}
switch (color) {
case OperandType::SourceRed:
case OperandType::OneMinusSourceRed:
getSource(shader, source, index);
shader += ".r";
break;
case OperandType::SourceGreen:
case OperandType::OneMinusSourceGreen:
getSource(shader, source, index);
shader += ".g";
break;
case OperandType::SourceBlue:
case OperandType::OneMinusSourceBlue:
getSource(shader, source, index);
shader += ".b";
break;
case OperandType::SourceAlpha:
case OperandType::OneMinusSourceAlpha:
getSource(shader, source, index);
shader += ".a";
break;
default:
shader += "1.0";
Helpers::warn("FragmentGenerator: Invalid TEV color operand");
break;
}
}
void FragmentGenerator::getSource(std::string& shader, TexEnvConfig::Source source, int index) {
switch (source) {
case TexEnvConfig::Source::PrimaryColor: shader += "v_colour"; break;
case TexEnvConfig::Source::Texture0: shader += "texture(u_tex0, v_texcoord0.xy)"; break;
case TexEnvConfig::Source::Texture1: shader += "texture(u_tex1, v_texcoord1)"; break;
case TexEnvConfig::Source::Texture2: {
// If bit 13 in texture config is set then we use the texcoords for texture 1, otherwise for texture 2
if (Helpers::getBit<13>(textureConfig)) {
shader += "texture(u_tex2, v_texcoord1)";
} else {
shader += "texture(u_tex2, v_texcoord2)";
}
break;
}
case TexEnvConfig::Source::Previous: shader += "combinerOutput"; break;
default:
Helpers::warn("Unimplemented TEV source: %d", static_cast<int>(source));
shader += "vec4(1.0, 1.0, 1.0, 1.0)";
break;
}
}
void FragmentGenerator::getColorOperation(std::string& shader, TexEnvConfig::Operation op) {
switch (op) {
case TexEnvConfig::Operation::Replace: shader += "colorOp1"; break;
case TexEnvConfig::Operation::Add: shader += "colorOp1 + colorOp2"; break;
case TexEnvConfig::Operation::AddSigned: shader += "clamp(colorOp1 + colorOp2 - 0.5, 0.0, 1.0);"; break;
case TexEnvConfig::Operation::Subtract: shader += "colorOp1 - colorOp2"; break;
case TexEnvConfig::Operation::Modulate: shader += "colorOp1 * colorOp2"; break;
case TexEnvConfig::Operation::Lerp: shader += "colorOp1 * colorOp3 + colorOp2 * (vec3(1.0) - colorOp3)"; break;
case TexEnvConfig::Operation::AddMultiply: shader += "min(colorOp1 + colorOp2, vec3(1.0)) * colorOp3"; break;
case TexEnvConfig::Operation::MultiplyAdd: shader += "colorOp1 * colorOp2 + colorOp3"; break;
case TexEnvConfig::Operation::Dot3RGB:
case TexEnvConfig::Operation::Dot3RGBA: shader += "vec3(4.0 * dot(colorOp1 - 0.5, colorOp2 - 0.5))"; break;
default:
Helpers::warn("FragmentGenerator: Unimplemented color op");
shader += "vec3(1.0)";
break;
}
}
void FragmentGenerator::getAlphaOperation(std::string& shader, TexEnvConfig::Operation op) {
switch (op) {
case TexEnvConfig::Operation::Replace: shader += "alphaOp1"; break;
case TexEnvConfig::Operation::Add: shader += "alphaOp1 + alphaOp2"; break;
case TexEnvConfig::Operation::AddSigned: shader += "clamp(alphaOp1 + alphaOp2 - 0.5, 0.0, 1.0);"; break;
case TexEnvConfig::Operation::Subtract: shader += "alphaOp1 - alphaOp2"; break;
case TexEnvConfig::Operation::Modulate: shader += "alphaOp1 * alphaOp2"; break;
case TexEnvConfig::Operation::Lerp: shader += "alphaOp1 * alphaOp3 + alphaOp2 * (1.0 - alphaOp3)"; break;
case TexEnvConfig::Operation::AddMultiply: shader += "min(alphaOp1 + alphaOp2, 1.0) * alphaOp3"; break;
case TexEnvConfig::Operation::MultiplyAdd: shader += "alphaOp1 * alphaOp2 + alphaOp3"; break;
case TexEnvConfig::Operation::Dot3RGB:
case TexEnvConfig::Operation::Dot3RGBA: shader += "vec3(4.0 * dot(alphaOp1 - 0.5, alphaOp2 - 0.5))"; break;
default:
Helpers::warn("FragmentGenerator: Unimplemented alpha op");
shader += "1.0";
break;
}
}

View file

@ -38,12 +38,16 @@ void RendererGL::reset() {
oldDepthOffset = 0.0; // Default depth offset to 0
oldDepthmapEnable = false; // Enable w buffering
glUniform1f(depthScaleLoc, oldDepthScale);
glUniform1f(depthOffsetLoc, oldDepthOffset);
glUniform1i(depthmapEnableLoc, oldDepthmapEnable);
glUniform1f(ubershaderData.depthScaleLoc, oldDepthScale);
glUniform1f(ubershaderData.depthOffsetLoc, oldDepthOffset);
glUniform1i(ubershaderData.depthmapEnableLoc, oldDepthmapEnable);
gl.useProgram(oldProgram); // Switch to old GL program
}
#ifdef __ANDROID__
fragShaderGen.setTarget(PICA::ShaderGen::API::GLES, PICA::ShaderGen::Language::GLSL);
#endif
}
void RendererGL::initGraphicsContextInternal() {
@ -59,16 +63,16 @@ void RendererGL::initGraphicsContextInternal() {
triangleProgram.create({vert, frag});
gl.useProgram(triangleProgram);
textureEnvSourceLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvSource");
textureEnvOperandLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvOperand");
textureEnvCombinerLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvCombiner");
textureEnvColorLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvColor");
textureEnvScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvScale");
ubershaderData.textureEnvSourceLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvSource");
ubershaderData.textureEnvOperandLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvOperand");
ubershaderData.textureEnvCombinerLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvCombiner");
ubershaderData.textureEnvColorLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvColor");
ubershaderData.textureEnvScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_textureEnvScale");
depthScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_depthScale");
depthOffsetLoc = OpenGL::uniformLocation(triangleProgram, "u_depthOffset");
depthmapEnableLoc = OpenGL::uniformLocation(triangleProgram, "u_depthmapEnable");
picaRegLoc = OpenGL::uniformLocation(triangleProgram, "u_picaRegs");
ubershaderData.depthScaleLoc = OpenGL::uniformLocation(triangleProgram, "u_depthScale");
ubershaderData.depthOffsetLoc = OpenGL::uniformLocation(triangleProgram, "u_depthOffset");
ubershaderData.depthmapEnableLoc = OpenGL::uniformLocation(triangleProgram, "u_depthmapEnable");
ubershaderData.picaRegLoc = OpenGL::uniformLocation(triangleProgram, "u_picaRegs");
// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
glUniform1i(OpenGL::uniformLocation(triangleProgram, "u_tex0"), 0);
@ -289,9 +293,11 @@ void RendererGL::setupStencilTest(bool stencilEnable) {
glStencilOp(stencilOps[stencilFailOp], stencilOps[depthFailOp], stencilOps[passOp]);
}
void RendererGL::setupTextureEnvState() {
// TODO: Only update uniforms when the TEV config changed. Use an UBO potentially.
if (!usingUbershader) {
return;
}
static constexpr std::array<u32, 6> ioBases = {
PICA::InternalRegs::TexEnv0Source, PICA::InternalRegs::TexEnv1Source, PICA::InternalRegs::TexEnv2Source,
@ -314,11 +320,11 @@ void RendererGL::setupTextureEnvState() {
textureEnvScaleRegs[i] = regs[ioBase + 4];
}
glUniform1uiv(textureEnvSourceLoc, 6, textureEnvSourceRegs);
glUniform1uiv(textureEnvOperandLoc, 6, textureEnvOperandRegs);
glUniform1uiv(textureEnvCombinerLoc, 6, textureEnvCombinerRegs);
glUniform1uiv(textureEnvColorLoc, 6, textureEnvColourRegs);
glUniform1uiv(textureEnvScaleLoc, 6, textureEnvScaleRegs);
glUniform1uiv(ubershaderData.textureEnvSourceLoc, 6, textureEnvSourceRegs);
glUniform1uiv(ubershaderData.textureEnvOperandLoc, 6, textureEnvOperandRegs);
glUniform1uiv(ubershaderData.textureEnvCombinerLoc, 6, textureEnvCombinerRegs);
glUniform1uiv(ubershaderData.textureEnvColorLoc, 6, textureEnvColourRegs);
glUniform1uiv(ubershaderData.textureEnvScaleLoc, 6, textureEnvScaleRegs);
}
void RendererGL::bindTexturesToSlots() {
@ -389,11 +395,17 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
OpenGL::Triangle,
};
if (usingUbershader) {
gl.useProgram(triangleProgram);
} else {
OpenGL::Program& program = getSpecializedShader();
gl.useProgram(program);
}
const auto primitiveTopology = primTypes[static_cast<usize>(primType)];
gl.disableScissor();
gl.bindVBO(vbo);
gl.bindVAO(vao);
gl.useProgram(triangleProgram);
gl.enableClipPlane(0); // Clipping plane 0 is always enabled
if (regs[PICA::InternalRegs::ClipEnable] & 1) {
@ -419,27 +431,31 @@ void RendererGL::drawVertices(PICA::PrimType primType, std::span<const Vertex> v
const bool depthMapEnable = regs[PICA::InternalRegs::DepthmapEnable] & 1;
// Update depth uniforms
if (oldDepthScale != depthScale) {
oldDepthScale = depthScale;
glUniform1f(depthScaleLoc, depthScale);
}
if (usingUbershader) {
if (oldDepthScale != depthScale) {
oldDepthScale = depthScale;
glUniform1f(ubershaderData.depthScaleLoc, depthScale);
}
if (oldDepthOffset != depthOffset) {
oldDepthOffset = depthOffset;
glUniform1f(depthOffsetLoc, depthOffset);
}
if (oldDepthOffset != depthOffset) {
oldDepthOffset = depthOffset;
glUniform1f(ubershaderData.depthOffsetLoc, depthOffset);
}
if (oldDepthmapEnable != depthMapEnable) {
oldDepthmapEnable = depthMapEnable;
glUniform1i(depthmapEnableLoc, depthMapEnable);
if (oldDepthmapEnable != depthMapEnable) {
oldDepthmapEnable = depthMapEnable;
glUniform1i(ubershaderData.depthmapEnableLoc, depthMapEnable);
}
}
setupTextureEnvState();
bindTexturesToSlots();
// Upload PICA Registers as a single uniform. The shader needs access to the rasterizer registers (for depth, starting from index 0x48)
// The texturing and the fragment lighting registers. Therefore we upload them all in one go to avoid multiple slow uniform updates
glUniform1uiv(picaRegLoc, 0x200 - 0x48, &regs[0x48]);
if (usingUbershader) {
// Upload PICA Registers as a single uniform. The shader needs access to the rasterizer registers (for depth, starting from index 0x48)
// The texturing and the fragment lighting registers. Therefore we upload them all in one go to avoid multiple slow uniform updates
glUniform1uiv(ubershaderData.picaRegLoc, 0x200 - 0x48, &regs[0x48]);
}
if (gpu.lightingLUTDirty) {
updateLightingLUT();
@ -778,6 +794,43 @@ std::optional<ColourBuffer> RendererGL::getColourBuffer(u32 addr, PICA::ColorFmt
return colourBufferCache.add(sampleBuffer);
}
OpenGL::Program& RendererGL::getSpecializedShader() {
PICA::FragmentConfig fsConfig;
fsConfig.texUnitConfig = regs[InternalRegs::TexUnitCfg];
fsConfig.texEnvUpdateBuffer = regs[InternalRegs::TexEnvUpdateBuffer];
fsConfig.texEnvBufferColor = regs[InternalRegs::TexEnvBufferColor];
// Set up TEV stages
std::memcpy(&fsConfig.tevConfigs[0 * 5], &regs[InternalRegs::TexEnv0Source], 5 * sizeof(u32));
std::memcpy(&fsConfig.tevConfigs[1 * 5], &regs[InternalRegs::TexEnv1Source], 5 * sizeof(u32));
std::memcpy(&fsConfig.tevConfigs[2 * 5], &regs[InternalRegs::TexEnv2Source], 5 * sizeof(u32));
std::memcpy(&fsConfig.tevConfigs[3 * 5], &regs[InternalRegs::TexEnv3Source], 5 * sizeof(u32));
std::memcpy(&fsConfig.tevConfigs[4 * 5], &regs[InternalRegs::TexEnv4Source], 5 * sizeof(u32));
std::memcpy(&fsConfig.tevConfigs[5 * 5], &regs[InternalRegs::TexEnv5Source], 5 * sizeof(u32));
OpenGL::Program& program = shaderCache[fsConfig];
if (!program.exists()) {
printf("Creating specialized shader\n");
std::string vs = fragShaderGen.getVertexShader(regs);
std::string fs = fragShaderGen.generate(regs);
std::cout << vs << "\n\n" << fs << "\n";
OpenGL::Shader vertShader({vs.c_str(), vs.size()}, OpenGL::Vertex);
OpenGL::Shader fragShader({fs.c_str(), fs.size()}, OpenGL::Fragment);
program.create({vertShader, fragShader});
gl.useProgram(program);
// Init sampler objects. Texture 0 goes in texture unit 0, texture 1 in TU 1, texture 2 in TU 2, and the light maps go in TU 3
glUniform1i(OpenGL::uniformLocation(program, "u_tex0"), 0);
glUniform1i(OpenGL::uniformLocation(program, "u_tex1"), 1);
glUniform1i(OpenGL::uniformLocation(program, "u_tex2"), 2);
glUniform1i(OpenGL::uniformLocation(program, "u_tex_lighting_lut"), 3);
}
return program;
}
void RendererGL::screenshot(const std::string& name) {
constexpr uint width = 400;
constexpr uint height = 2 * 240;