diff --git a/CMakeLists.txt b/CMakeLists.txt index 88a92f8a..91dafe9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(ENABLE_OPENGL) set(RENDERER_GL_INCLUDE_FILES third_party/opengl/opengl.hpp include/renderer_gl/renderer_gl.hpp include/renderer_gl/textures.hpp include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp - include/renderer_gl/gl_state.hpp + include/renderer_gl/gl_state.hpp include/renderer_gl/gl_driver.hpp ) set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp diff --git a/include/PICA/pica_frag_config.hpp b/include/PICA/pica_frag_config.hpp index 5d5f8420..7b63a7b5 100644 --- a/include/PICA/pica_frag_config.hpp +++ b/include/PICA/pica_frag_config.hpp @@ -17,6 +17,7 @@ namespace PICA { // enable == off means a CompareFunction of Always BitField<0, 3, CompareFunction> alphaTestFunction; BitField<3, 1, u32> depthMapEnable; + BitField<4, 4, LogicOpMode> logicOpMode; }; }; @@ -214,6 +215,10 @@ namespace PICA { (alphaTestConfig & 1) ? static_cast(alphaTestFunction) : PICA::CompareFunction::Always; outConfig.depthMapEnable = regs[InternalRegs::DepthmapEnable] & 1; + // Shows if blending is enabled. If it is not enabled, then logic ops are enabled instead + const bool blendingEnabled = (regs[InternalRegs::ColourOperation] & (1 << 8)) != 0; + outConfig.logicOpMode = blendingEnabled ? LogicOpMode::Copy : LogicOpMode(Helpers::getBits<0, 4>(regs[InternalRegs::LogicOp])); + texConfig.texUnitConfig = regs[InternalRegs::TexUnitCfg]; texConfig.texEnvUpdateBuffer = regs[InternalRegs::TexEnvUpdateBuffer]; diff --git a/include/PICA/regs.hpp b/include/PICA/regs.hpp index 636e8f7c..3185d350 100644 --- a/include/PICA/regs.hpp +++ b/include/PICA/regs.hpp @@ -396,6 +396,25 @@ namespace PICA { GreaterOrEqual = 7, }; + enum class LogicOpMode : u32 { + Clear = 0, + And = 1, + ReverseAnd = 2, + Copy = 3, + Set = 4, + InvertedCopy = 5, + Nop = 6, + Invert = 7, + Nand = 8, + Or = 9, + Nor = 10, + Xor = 11, + Equiv = 12, + InvertedAnd = 13, + ReverseOr = 14, + InvertedOr = 15, + }; + enum class FogMode : u32 { Disabled = 0, Fog = 5, diff --git a/include/PICA/shader_gen.hpp b/include/PICA/shader_gen.hpp index 215e5adb..f938b558 100644 --- a/include/PICA/shader_gen.hpp +++ b/include/PICA/shader_gen.hpp @@ -25,10 +25,11 @@ namespace PICA::ShaderGen { bool isSamplerEnabled(u32 environmentID, u32 lutID); void compileFog(std::string& shader, const PICA::FragmentConfig& config); + void compileLogicOps(std::string& shader, const PICA::FragmentConfig& config); public: FragmentGenerator(API api, Language language) : api(api), language(language) {} - std::string generate(const PICA::FragmentConfig& config); + std::string generate(const PICA::FragmentConfig& config, void* driverInfo = nullptr); std::string getDefaultVertexShader(); void setTarget(API api, Language language) { diff --git a/include/renderer_gl/gl_driver.hpp b/include/renderer_gl/gl_driver.hpp new file mode 100644 index 00000000..c8a1bf85 --- /dev/null +++ b/include/renderer_gl/gl_driver.hpp @@ -0,0 +1,9 @@ +#pragma once + +// Information about our OpenGL/OpenGL ES driver that we should keep track of +// Stuff like whether specific extensions are supported, and potentially things like OpenGL context information +namespace OpenGL { + struct Driver { + bool supportsFbFetch = false; + }; +} // namespace OpenGL \ No newline at end of file diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 42b8bba1..07069536 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -12,6 +12,7 @@ #include "PICA/pica_vertex.hpp" #include "PICA/regs.hpp" #include "PICA/shader_gen.hpp" +#include "gl_driver.hpp" #include "gl_state.hpp" #include "helpers.hpp" #include "logger.hpp" @@ -82,6 +83,7 @@ class RendererGL final : public Renderer { OpenGL::Program& getSpecializedShader(); PICA::ShaderGen::FragmentGenerator fragShaderGen; + OpenGL::Driver driverInfo; MAKE_LOG_FUNCTION(log, rendererLogger) void setupBlending(); diff --git a/src/core/PICA/shader_gen_glsl.cpp b/src/core/PICA/shader_gen_glsl.cpp index 69f74930..37d52924 100644 --- a/src/core/PICA/shader_gen_glsl.cpp +++ b/src/core/PICA/shader_gen_glsl.cpp @@ -1,6 +1,10 @@ #include "PICA/pica_frag_config.hpp" #include "PICA/regs.hpp" #include "PICA/shader_gen.hpp" + +// We can include the driver headers here since they shouldn't have any actual API-specific code +#include "renderer_gl/gl_driver.hpp" + using namespace PICA; using namespace PICA::ShaderGen; @@ -94,7 +98,7 @@ std::string FragmentGenerator::getDefaultVertexShader() { return ret; } -std::string FragmentGenerator::generate(const FragmentConfig& config) { +std::string FragmentGenerator::generate(const FragmentConfig& config, void* driverInfo) { std::string ret = ""; switch (api) { @@ -103,6 +107,14 @@ std::string FragmentGenerator::generate(const FragmentConfig& config) { default: break; } + // For GLES we need to enable & use the framebuffer fetch extension in order to emulate logic ops + const bool emitLogicOps = api == API::GLES && config.outConfig.logicOpMode != PICA::LogicOpMode::Copy && driverInfo != nullptr && + static_cast(driverInfo)->supportsFbFetch; + + if (emitLogicOps) { + ret += "\n#extension GL_EXT_shader_framebuffer_fetch : require\n"; + } + bool unimplementedFlag = false; if (api == API::GLES) { ret += R"( @@ -192,10 +204,13 @@ std::string FragmentGenerator::generate(const FragmentConfig& config) { } compileFog(ret, config); - applyAlphaTest(ret, config); - ret += "fragColor = combinerOutput;\n}"; // End of main function + if (!emitLogicOps) { + ret += "fragColor = combinerOutput;\n}"; // End of main function + } else { + compileLogicOps(ret, config); + } return ret; } @@ -671,3 +686,28 @@ void FragmentGenerator::compileFog(std::string& shader, const PICA::FragmentConf shader += "float fog_factor = clamp(value.r + value.g * delta, 0.0, 1.0);"; shader += "combinerOutput.rgb = mix(fog_color, combinerOutput.rgb, fog_factor);"; } + +void FragmentGenerator::compileLogicOps(std::string& shader, const PICA::FragmentConfig& config) { + if (api != API::GLES) [[unlikely]] { + Helpers::warn("Shadergen: Unsupported API for compileLogicOps"); + shader += "fragColor = combinerOutput;\n}"; // End of main function + + return; + } + + shader += "fragColor = "; + switch (config.outConfig.logicOpMode) { + case PICA::LogicOpMode::Copy: shader += "combinerOutput"; break; + case PICA::LogicOpMode::Nop: shader += "gl_LastFragData[0]"; break; + case PICA::LogicOpMode::Clear: shader += "vec4(0.0)"; break; + case PICA::LogicOpMode::Set: shader += "vec4(uintBitsToFloat(0xFFFFFFFFu))"; break; + case PICA::LogicOpMode::InvertedCopy: shader += "vec4(uvec4(combinerOutput * 255.0) ^ uvec4(0xFFu)) * (1.0 / 255.0)"; break; + + default: + shader += "combinerOutput"; + Helpers::warn("Shadergen: Unimplemented logic op mode"); + break; + } + + shader += ";\n}"; // End of main function +} \ No newline at end of file diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 5146370a..7c916122 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -167,6 +167,9 @@ void RendererGL::initGraphicsContextInternal() { reset(); + // Populate our driver info structure + driverInfo.supportsFbFetch = GLAD_GL_EXT_shader_framebuffer_fetch != 0; + // Initialize the default vertex shader used with shadergen std::string defaultShadergenVSSource = fragShaderGen.getDefaultVertexShader(); defaultShadergenVs.create({defaultShadergenVSSource.c_str(), defaultShadergenVSSource.size()}, OpenGL::Vertex); @@ -839,12 +842,16 @@ OpenGL::Program& RendererGL::getSpecializedShader() { constexpr uint uboBlockBinding = 2; PICA::FragmentConfig fsConfig(regs); + // If we're not on GLES, ignore the logic op configuration and don't generate redundant shaders for it, since we use hw logic ops +#ifndef USING_GLES + fsConfig.outConfig.logicOpMode = PICA::LogicOpMode(0); +#endif CachedProgram& programEntry = shaderCache[fsConfig]; OpenGL::Program& program = programEntry.program; if (!program.exists()) { - std::string fs = fragShaderGen.generate(fsConfig); + std::string fs = fragShaderGen.generate(fsConfig, &driverInfo); OpenGL::Shader fragShader({fs.c_str(), fs.size()}, OpenGL::Fragment); program.create({defaultShadergenVs, fragShader});