From fac6c4ab05a416a8c39583abd5afb178c5815e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 26 Jul 2026 01:02:06 -0300 Subject: [PATCH] Single pass subpixel rendering in OpenGL 2 context. Fix unit-tests build issue. --- include/eepp/graphics/renderer/opengl.hpp | 12 ++++ include/eepp/graphics/renderer/renderer.hpp | 5 ++ include/eepp/graphics/renderer/renderergl.hpp | 4 ++ .../eepp/graphics/renderer/rendererhelper.hpp | 1 + include/eepp/graphics/shaderprogram.hpp | 9 +++ src/eepp/graphics/renderer/openglext.hpp | 2 + src/eepp/graphics/renderer/renderer.cpp | 27 ++++++++ src/eepp/graphics/renderer/renderergl.cpp | 68 +++++++++++++++++++ src/eepp/graphics/shaderprogram.cpp | 11 +++ src/tests/unit_tests/fontrendering_tests.cpp | 6 +- 10 files changed, 143 insertions(+), 2 deletions(-) diff --git a/include/eepp/graphics/renderer/opengl.hpp b/include/eepp/graphics/renderer/opengl.hpp index 6eaaba95f..e7dab3b47 100644 --- a/include/eepp/graphics/renderer/opengl.hpp +++ b/include/eepp/graphics/renderer/opengl.hpp @@ -148,6 +148,18 @@ typedef char GLchar; #ifndef GL_NUM_EXTENSIONS #define GL_NUM_EXTENSIONS 0x821D #endif +#ifndef GL_SRC1_COLOR +#define GL_SRC1_COLOR 0x88F9 +#endif +#ifndef GL_ONE_MINUS_SRC1_COLOR +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#endif +#ifndef GL_ONE +#define GL_ONE 1 +#endif +#ifndef GL_ONE_MINUS_SRC_ALPHA +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#endif #else //! Mobile platform ( Android / iPhone / Maemo ) diff --git a/include/eepp/graphics/renderer/renderer.hpp b/include/eepp/graphics/renderer/renderer.hpp index fdda462b9..5e0fcc834 100644 --- a/include/eepp/graphics/renderer/renderer.hpp +++ b/include/eepp/graphics/renderer/renderer.hpp @@ -111,6 +111,9 @@ class EE_API Renderer { void blendEquationSeparate( unsigned int modeRGB, unsigned int modeAlpha ); + bool bindFragDataLocationIndexed( unsigned int program, unsigned int colorNumber, + unsigned int index, const char* name ); + void blitFrameBuffer( int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, unsigned int mask, unsigned int filter ); @@ -362,6 +365,8 @@ class EE_API Renderer { void waitForIdle(); protected: + virtual bool drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ); + static const Vector3ff& textureColorChannel( Int32 mode ); static Renderer* sSingleton; diff --git a/include/eepp/graphics/renderer/renderergl.hpp b/include/eepp/graphics/renderer/renderergl.hpp index f3d2a9da0..c168c5d97 100644 --- a/include/eepp/graphics/renderer/renderergl.hpp +++ b/include/eepp/graphics/renderer/renderergl.hpp @@ -89,12 +89,16 @@ class EE_API RendererGL : public Renderer { protected: bool ensureSubpixelShader(); + bool ensureSubpixelDualSourceShader(); + bool drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ); ShaderProgramPtr mSubpixelShader; + ShaderProgramPtr mSubpixelDualSourceShader; ShaderProgram* mPreviousShader{ nullptr }; Int32 mSubpixelChannelLoc{ -1 }; bool mUsingSubpixelShader{ false }; bool mSubpixelShaderInitializationAttempted{ false }; + bool mSubpixelDualSourceShaderInitializationAttempted{ false }; }; #endif diff --git a/include/eepp/graphics/renderer/rendererhelper.hpp b/include/eepp/graphics/renderer/rendererhelper.hpp index 2218f179a..f0058c549 100644 --- a/include/eepp/graphics/renderer/rendererhelper.hpp +++ b/include/eepp/graphics/renderer/rendererhelper.hpp @@ -33,6 +33,7 @@ enum GraphicsLibraryExtension { EEGL_EXT_blend_minmax, EEGL_EXT_blend_subtract, EEGL_ARB_ES3_compatibility, + EEGL_ARB_blend_func_extended, }; /// Graphics Library Renderer version available. diff --git a/include/eepp/graphics/shaderprogram.hpp b/include/eepp/graphics/shaderprogram.hpp index cdd0ee409..901c48408 100644 --- a/include/eepp/graphics/shaderprogram.hpp +++ b/include/eepp/graphics/shaderprogram.hpp @@ -56,6 +56,9 @@ class EE_API ShaderProgram { /** Add a vector of shaders */ void addShaders( const std::vector& shaders ); + /** Bind a fragment output to a color number and source index before linking. */ + bool bindFragDataLocationIndexed( Uint32 colorNumber, Uint32 index, const char* name ); + virtual bool link(); /** @return If the shader program is valid */ @@ -160,6 +163,12 @@ class EE_API ShaderProgram { std::vector mShaders; std::map mUniformLocations; std::map mAttributeLocations; + struct FragmentOutputBinding { + Uint32 colorNumber; + Uint32 index; + std::string name; + }; + std::vector mFragmentOutputBindings; ShaderProgramReloadCb mReloadCb; diff --git a/src/eepp/graphics/renderer/openglext.hpp b/src/eepp/graphics/renderer/openglext.hpp index 340005955..58752516e 100644 --- a/src/eepp/graphics/renderer/openglext.hpp +++ b/src/eepp/graphics/renderer/openglext.hpp @@ -44,6 +44,8 @@ typedef void( APIENTRY* pglBlendFuncSeparate )( GLenum sfactorRGB, GLenum dfacto typedef void( APIENTRY* pglDiscardFramebufferEXT )( GLenum target, GLsizei numAttachments, const GLenum* attachments ); typedef void( APIENTRY* pglBlendEquationSeparate )( GLenum modeRGB, GLenum modeAlpha ); +typedef void( APIENTRY* pglBindFragDataLocationIndexed )( GLuint program, GLuint colorNumber, + GLuint index, const GLchar* name ); typedef void( APIENTRY* pglBlitFramebufferEXT )( GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter ); diff --git a/src/eepp/graphics/renderer/renderer.cpp b/src/eepp/graphics/renderer/renderer.cpp index 0c21d2d1f..d760493e3 100644 --- a/src/eepp/graphics/renderer/renderer.cpp +++ b/src/eepp/graphics/renderer/renderer.cpp @@ -260,6 +260,8 @@ void Renderer::init() { writeExtension( EEGL_EXT_blend_func_separate, GLEW_EXT_blend_func_separate ); writeExtension( EEGL_EXT_blend_minmax, GLEW_EXT_blend_minmax ); writeExtension( EEGL_EXT_blend_subtract, GLEW_EXT_blend_subtract ); + writeExtension( EEGL_ARB_blend_func_extended, + GLEW_ARB_blend_func_extended || GLEW_VERSION_3_3 ); } else #endif { @@ -296,6 +298,9 @@ void Renderer::init() { glVersion >= 140 || isExtension( "GL_EXT_blend_minmax" ) ); writeExtension( EEGL_EXT_blend_subtract, glVersion >= 140 || isExtension( "GL_EXT_blend_subtract" ) ); + writeExtension( EEGL_ARB_blend_func_extended, + !is_es && + ( glVersion >= 330 || isExtension( "GL_ARB_blend_func_extended" ) ) ); } // NVIDIA added support for GL_OES_compressed_ETC1_RGB8_texture in desktop GPUs @@ -477,6 +482,9 @@ void Renderer::drawArrays( unsigned int mode, int first, int count ) { } bool Renderer::drawSubpixelArrays( unsigned int mode, int first, int count ) { + if ( drawSubpixelDualSourceArrays( mode, first, count ) ) + return true; + if ( !setTextureColorMode( 1 ) ) return false; @@ -497,6 +505,10 @@ bool Renderer::drawSubpixelArrays( unsigned int mode, int first, int count ) { return true; } +bool Renderer::drawSubpixelDualSourceArrays( unsigned int, int, int ) { + return false; +} + bool Renderer::drawSubpixelFallbackArrays( unsigned int mode, int first, int count ) { if ( !setTextureColorMode( 4 ) ) return false; @@ -563,6 +575,21 @@ void Renderer::blendEquationSeparate( unsigned int modeRGB, unsigned int modeAlp eeglBlendEquationSeparate( modeRGB, modeAlpha ); } +bool Renderer::bindFragDataLocationIndexed( unsigned int program, unsigned int colorNumber, + unsigned int index, const char* name ) { +#ifndef EE_GLES + static pglBindFragDataLocationIndexed bindFragDataLocationIndexed = NULL; + if ( NULL == bindFragDataLocationIndexed ) + bindFragDataLocationIndexed = + (pglBindFragDataLocationIndexed)getProcAddress( "glBindFragDataLocationIndexed" ); + if ( NULL != bindFragDataLocationIndexed ) { + bindFragDataLocationIndexed( program, colorNumber, index, name ); + return true; + } +#endif + return false; +} + void Renderer::blitFrameBuffer( int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, unsigned int mask, unsigned int filter ) { static pglBlitFramebufferEXT eeglBlitFramebufferEXT = NULL; diff --git a/src/eepp/graphics/renderer/renderergl.cpp b/src/eepp/graphics/renderer/renderergl.cpp index 76b0b544f..1d51a1309 100644 --- a/src/eepp/graphics/renderer/renderergl.cpp +++ b/src/eepp/graphics/renderer/renderergl.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -107,6 +108,73 @@ void main() { #endif } +bool RendererGL::ensureSubpixelDualSourceShader() { +#ifndef EE_GLES1 + if ( mSubpixelDualSourceShader ) + return true; + if ( mSubpixelDualSourceShaderInitializationAttempted || + !isExtension( EEGL_ARB_blend_func_extended ) || !shadersSupported() ) + return false; + mSubpixelDualSourceShaderInitializationAttempted = true; + static const char vertexShader[] = R"( +#version 130 +varying vec4 dgl_Color; +varying vec2 dgl_TexCoord; +void main() { + gl_Position = ftransform(); + gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex; + dgl_Color = gl_Color; + dgl_TexCoord = ( gl_TextureMatrix[0] * gl_MultiTexCoord0 ).xy; +} +)"; + static const char fragmentShader[] = R"( +#version 130 +uniform sampler2D textureUnit0; +varying vec4 dgl_Color; +varying vec2 dgl_TexCoord; +out vec4 dgl_FragColor; +out vec4 dgl_FragCoverage; +void main() { + vec3 coverage = texture2D( textureUnit0, dgl_TexCoord ).rgb; + float meanCoverage = dot( coverage, vec3( 1.0 / 3.0 ) ); + dgl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * meanCoverage ); + dgl_FragCoverage = vec4( dgl_Color.a * coverage, 0.0 ); +} +)"; + ShaderProgramPtr shader = ShaderProgram::New( "eepp-subpixel-dual-source-text" ); + ShaderPtr vs( eeNew( VertexShader, ( vertexShader, sizeof( vertexShader ) - 1 ) ), + ResourceDeleter() ); + ShaderPtr fs( eeNew( FragmentShader, ( fragmentShader, sizeof( fragmentShader ) - 1 ) ), + ResourceDeleter() ); + if ( shader && vs->isValid() && fs->isValid() ) { + shader->addShader( std::move( vs ) ); + shader->addShader( std::move( fs ) ); + if ( shader->bindFragDataLocationIndexed( 0, 0, "dgl_FragColor" ) && + shader->bindFragDataLocationIndexed( 0, 1, "dgl_FragCoverage" ) && shader->link() ) { + mSubpixelDualSourceShader = std::move( shader ); + Renderer::setShader( mSubpixelDualSourceShader.get() ); + mSubpixelDualSourceShader->setUniform( "textureUnit0", 0 ); + Renderer::setShader( mPreviousShader ); + } + } + return !!mSubpixelDualSourceShader; +#else + return false; +#endif +} + +bool RendererGL::drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ) { + if ( !ensureSubpixelDualSourceShader() ) + return false; + const BlendMode previousBlendMode = BlendMode::getPreBlendFunc(); + Renderer::setShader( mSubpixelDualSourceShader.get() ); + blendFuncSeparate( GL_SRC1_COLOR, GL_ONE_MINUS_SRC1_COLOR, GL_ONE, GL_ONE_MINUS_SRC_ALPHA ); + drawArrays( mode, first, count ); + BlendMode::setMode( previousBlendMode, true ); + Renderer::setShader( mPreviousShader ); + return true; +} + void RendererGL::setShader( ShaderProgram* shader ) { if ( !mUsingSubpixelShader ) mPreviousShader = shader; diff --git a/src/eepp/graphics/shaderprogram.cpp b/src/eepp/graphics/shaderprogram.cpp index 3396ccea3..5e3810834 100644 --- a/src/eepp/graphics/shaderprogram.cpp +++ b/src/eepp/graphics/shaderprogram.cpp @@ -231,8 +231,19 @@ void ShaderProgram::addShaders( const std::vector& shaders ) { addShader( shader ); } +bool ShaderProgram::bindFragDataLocationIndexed( Uint32 colorNumber, Uint32 index, + const char* name ) { + if ( !GLi->bindFragDataLocationIndexed( getHandler(), colorNumber, index, name ) ) + return false; + mFragmentOutputBindings.push_back( { colorNumber, index, name } ); + return true; +} + bool ShaderProgram::link() { #ifdef EE_SHADERS_SUPPORTED + for ( const auto& binding : mFragmentOutputBindings ) + GLi->bindFragDataLocationIndexed( getHandler(), binding.colorNumber, binding.index, + binding.name.c_str() ); GLi->linkProgram( getHandler() ); Int32 linked; diff --git a/src/tests/unit_tests/fontrendering_tests.cpp b/src/tests/unit_tests/fontrendering_tests.cpp index c13dd3ba8..24ea763e2 100644 --- a/src/tests/unit_tests/fontrendering_tests.cpp +++ b/src/tests/unit_tests/fontrendering_tests.cpp @@ -130,8 +130,10 @@ UTEST( FontRendering, subpixelCoverageCompositesPerChannel ) { for ( Uint32 y = top; y < bottom; ++y ) { for ( Uint32 x = 0; x < image.getWidth(); ++x ) { Color pixel = image.getPixel( x, y ); - if ( eeabs( static_cast( pixel.r ) - static_cast( pixel.g ) ) > 3 || - eeabs( static_cast( pixel.g ) - static_cast( pixel.b ) ) > 3 ) + const Int32 redGreenDelta = static_cast( pixel.r ) - pixel.g; + const Int32 greenBlueDelta = static_cast( pixel.g ) - pixel.b; + if ( ( redGreenDelta < 0 ? -redGreenDelta : redGreenDelta ) > 3 || + ( greenBlueDelta < 0 ? -greenBlueDelta : greenBlueDelta ) > 3 ) return true; } }