diff --git a/include/eepp/graphics/renderer/opengl.hpp b/include/eepp/graphics/renderer/opengl.hpp index e7dab3b47..30d2477cc 100644 --- a/include/eepp/graphics/renderer/opengl.hpp +++ b/include/eepp/graphics/renderer/opengl.hpp @@ -148,19 +148,6 @@ 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 ) #ifndef GL_GLEXT_PROTOTYPES @@ -410,6 +397,19 @@ typedef char GLchar; #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 + #ifndef GL_MULTISAMPLE #define GL_MULTISAMPLE 0x809D #endif diff --git a/include/eepp/graphics/renderer/renderergl3.hpp b/include/eepp/graphics/renderer/renderergl3.hpp index ab04da4af..772cf35fd 100644 --- a/include/eepp/graphics/renderer/renderergl3.hpp +++ b/include/eepp/graphics/renderer/renderergl3.hpp @@ -87,6 +87,8 @@ class EE_API RendererGL3 : public RendererGLShader { void planeStateCheck( bool tryEnable ); void reloadShader( ShaderProgram* Shader ); + ShaderProgramPtr createSubpixelDualSourceShader(); + bool canUseSubpixelDualSourceShader() const; }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/renderer/renderergl3cp.hpp b/include/eepp/graphics/renderer/renderergl3cp.hpp index 86be0c1ff..54a460490 100644 --- a/include/eepp/graphics/renderer/renderergl3cp.hpp +++ b/include/eepp/graphics/renderer/renderergl3cp.hpp @@ -96,6 +96,8 @@ class EE_API RendererGL3CP : public RendererGLShader { void reloadShader( ShaderProgram* Shader ); void allocateBuffers( const Uint32& size ); + ShaderProgramPtr createSubpixelDualSourceShader(); + bool canUseSubpixelDualSourceShader() const; }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/renderer/renderergles2.hpp b/include/eepp/graphics/renderer/renderergles2.hpp index 3760a27e1..7345ed3d3 100644 --- a/include/eepp/graphics/renderer/renderergles2.hpp +++ b/include/eepp/graphics/renderer/renderergles2.hpp @@ -98,6 +98,8 @@ class EE_API RendererGLES2 : public RendererGLShader { void reloadShader( ShaderProgram* Shader ); void checkLocalShader(); + ShaderProgramPtr createSubpixelDualSourceShader(); + bool canUseSubpixelDualSourceShader() const; }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/renderer/rendererglshader.hpp b/include/eepp/graphics/renderer/rendererglshader.hpp index 0d19f8baa..67df1abb9 100644 --- a/include/eepp/graphics/renderer/rendererglshader.hpp +++ b/include/eepp/graphics/renderer/rendererglshader.hpp @@ -57,6 +57,14 @@ class EE_API RendererGLShader : public Renderer { bool setTextureColorMode( Int32 mode ); protected: + bool drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ); + ShaderProgramPtr createSubpixelDualSourceShader( const std::string& vertexShader, + const std::string& fragmentShader, + bool bindFragmentOutputs ); + static const char* subpixelDualSourceFragmentShaderGLSL130(); + virtual ShaderProgramPtr createSubpixelDualSourceShader() = 0; + virtual bool canUseSubpixelDualSourceShader() const = 0; + Private::MatrixStack* mStack; int mProjectionMatrix_id; // cpu-side hook to shader uniform int mModelViewMatrix_id; // cpu-side hook to shader uniform @@ -67,6 +75,8 @@ class EE_API RendererGLShader : public Renderer { unsigned int mCurrentMode; ShaderProgram* mCurShader; ShaderProgram* mShaderPrev; + ShaderProgramPtr mSubpixelDualSourceShader; + bool mSubpixelDualSourceShaderInitializationAttempted{ false }; void updateMatrix(); }; diff --git a/include/eepp/graphics/renderer/rendererhelper.hpp b/include/eepp/graphics/renderer/rendererhelper.hpp index f0058c549..22234a6ef 100644 --- a/include/eepp/graphics/renderer/rendererhelper.hpp +++ b/include/eepp/graphics/renderer/rendererhelper.hpp @@ -34,6 +34,7 @@ enum GraphicsLibraryExtension { EEGL_EXT_blend_subtract, EEGL_ARB_ES3_compatibility, EEGL_ARB_blend_func_extended, + EEGL_EXT_blend_func_extended, }; /// Graphics Library Renderer version available. diff --git a/src/eepp/graphics/renderer/renderer.cpp b/src/eepp/graphics/renderer/renderer.cpp index d760493e3..e1ef66947 100644 --- a/src/eepp/graphics/renderer/renderer.cpp +++ b/src/eepp/graphics/renderer/renderer.cpp @@ -262,6 +262,7 @@ void Renderer::init() { writeExtension( EEGL_EXT_blend_subtract, GLEW_EXT_blend_subtract ); writeExtension( EEGL_ARB_blend_func_extended, GLEW_ARB_blend_func_extended || GLEW_VERSION_3_3 ); + writeExtension( EEGL_EXT_blend_func_extended, GLEW_EXT_blend_func_extended ); } else #endif { @@ -301,6 +302,10 @@ void Renderer::init() { writeExtension( EEGL_ARB_blend_func_extended, !is_es && ( glVersion >= 330 || isExtension( "GL_ARB_blend_func_extended" ) ) ); + writeExtension( EEGL_EXT_blend_func_extended, + is_es && ( isExtension( "GL_EXT_blend_func_extended" ) || + isExtension( "GL_WEBGL_blend_func_extended" ) || + isExtension( "WEBGL_blend_func_extended" ) ) ); } // NVIDIA added support for GL_OES_compressed_ETC1_RGB8_texture in desktop GPUs @@ -587,6 +592,14 @@ bool Renderer::bindFragDataLocationIndexed( unsigned int program, unsigned int c return true; } #endif + static pglBindFragDataLocationIndexed bindFragDataLocationIndexedEXT = NULL; + if ( NULL == bindFragDataLocationIndexedEXT ) + bindFragDataLocationIndexedEXT = + (pglBindFragDataLocationIndexed)getProcAddress( "glBindFragDataLocationIndexedEXT" ); + if ( NULL != bindFragDataLocationIndexedEXT ) { + bindFragDataLocationIndexedEXT( program, colorNumber, index, name ); + return true; + } return false; } diff --git a/src/eepp/graphics/renderer/renderergl3.cpp b/src/eepp/graphics/renderer/renderergl3.cpp index 2d3235a5a..06913833a 100644 --- a/src/eepp/graphics/renderer/renderergl3.cpp +++ b/src/eepp/graphics/renderer/renderergl3.cpp @@ -115,6 +115,21 @@ void RendererGL3::reloadCurrentShader() { reloadShader( mCurShader ); } +ShaderProgramPtr RendererGL3::createSubpixelDualSourceShader() { + std::string vertexShader = mBaseVertexShader; + String::replaceAll( vertexShader, "#version 120", "#version 130" ); + return RendererGLShader::createSubpixelDualSourceShader( + vertexShader, subpixelDualSourceFragmentShaderGLSL130(), true ); +} + +bool RendererGL3::canUseSubpixelDualSourceShader() const { + for ( Int32 state : mPlanesStates ) { + if ( state != 0 ) + return false; + } + return true; +} + void RendererGL3::reloadShader( ShaderProgram* Shader ) { mCurShader = NULL; diff --git a/src/eepp/graphics/renderer/renderergl3cp.cpp b/src/eepp/graphics/renderer/renderergl3cp.cpp index e22ef6687..384892991 100644 --- a/src/eepp/graphics/renderer/renderergl3cp.cpp +++ b/src/eepp/graphics/renderer/renderergl3cp.cpp @@ -147,6 +147,32 @@ void RendererGL3CP::reloadCurrentShader() { reloadShader( mCurShader ); } +ShaderProgramPtr RendererGL3CP::createSubpixelDualSourceShader() { + static const char fragmentShader[] = R"(#version 330 +uniform sampler2D textureUnit0; +in vec4 dgl_Color; +in vec4 dgl_TexCoord[1]; +out vec4 dgl_FragColor; +out vec4 dgl_FragCoverage; +void main() { + vec3 coverage = texture( textureUnit0, dgl_TexCoord[0].xy ).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 ); +} +)"; + return RendererGLShader::createSubpixelDualSourceShader( mBaseVertexShader, fragmentShader, + true ); +} + +bool RendererGL3CP::canUseSubpixelDualSourceShader() const { + for ( Int32 state : mPlanesStates ) { + if ( state != 0 ) + return false; + } + return true; +} + void RendererGL3CP::reloadShader( ShaderProgram* Shader ) { mCurShader = NULL; diff --git a/src/eepp/graphics/renderer/renderergles2.cpp b/src/eepp/graphics/renderer/renderergles2.cpp index 6a96f9621..e485ed359 100644 --- a/src/eepp/graphics/renderer/renderergles2.cpp +++ b/src/eepp/graphics/renderer/renderergles2.cpp @@ -148,6 +148,35 @@ void RendererGLES2::reloadCurrentShader() { reloadShader( mCurShader ); } +ShaderProgramPtr RendererGLES2::createSubpixelDualSourceShader() { +#ifdef EE_GLES2 + static const char fragmentShader[] = R"(#extension GL_EXT_blend_func_extended : require +precision mediump float; +precision lowp int; +uniform sampler2D textureUnit0; +varying vec4 dgl_Color; +varying mediump vec4 dgl_TexCoord[1]; +void main() { + vec3 coverage = texture2D( textureUnit0, dgl_TexCoord[0].xy ).rgb; + float meanCoverage = dot( coverage, vec3( 1.0 / 3.0 ) ); + gl_FragColor = vec4( dgl_Color.rgb, dgl_Color.a * meanCoverage ); + gl_SecondaryFragColorEXT = vec4( dgl_Color.a * coverage, 0.0 ); +} +)"; + return RendererGLShader::createSubpixelDualSourceShader( mBaseVertexShader, fragmentShader, + false ); +#else + std::string vertexShader = mBaseVertexShader; + String::replaceAll( vertexShader, "#version 120", "#version 130" ); + return RendererGLShader::createSubpixelDualSourceShader( + vertexShader, subpixelDualSourceFragmentShaderGLSL130(), true ); +#endif +} + +bool RendererGLES2::canUseSubpixelDualSourceShader() const { + return !mClippingEnabled && !mPointSpriteEnabled; +} + void RendererGLES2::reloadShader( ShaderProgram* Shader ) { mCurShader = NULL; diff --git a/src/eepp/graphics/renderer/rendererglshader.cpp b/src/eepp/graphics/renderer/rendererglshader.cpp index 7894db4e7..809a7e42c 100644 --- a/src/eepp/graphics/renderer/rendererglshader.cpp +++ b/src/eepp/graphics/renderer/rendererglshader.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -24,6 +25,65 @@ RendererGLShader::~RendererGLShader() { eeSAFE_DELETE( mStack ); } +const char* RendererGLShader::subpixelDualSourceFragmentShaderGLSL130() { + return R"(#version 130 +uniform sampler2D textureUnit0; +varying vec4 dgl_Color; +varying vec4 dgl_TexCoord[1]; +out vec4 dgl_FragColor; +out vec4 dgl_FragCoverage; +void main() { + vec3 coverage = texture2D( textureUnit0, dgl_TexCoord[0].xy ).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 RendererGLShader::createSubpixelDualSourceShader( + const std::string& vertexShader, const std::string& fragmentShader, bool bindFragmentOutputs ) { + ShaderProgramPtr shader = ShaderProgram::New( "eepp-subpixel-dual-source-text" ); + const bool shaderEnsure = Shader::ensure(); + Shader::ensure( false ); + ShaderPtr vs( eeNew( VertexShader, ( vertexShader.c_str(), vertexShader.size() ) ), + ResourceDeleter() ); + ShaderPtr fs( eeNew( FragmentShader, ( fragmentShader.c_str(), fragmentShader.size() ) ), + ResourceDeleter() ); + Shader::ensure( shaderEnsure ); + if ( !shader || !vs->isValid() || !fs->isValid() ) + return {}; + shader->addShader( std::move( vs ) ); + shader->addShader( std::move( fs ) ); + if ( bindFragmentOutputs && + ( !shader->bindFragDataLocationIndexed( 0, 0, "dgl_FragColor" ) || + !shader->bindFragDataLocationIndexed( 0, 1, "dgl_FragCoverage" ) ) ) + return {}; + return shader->link() ? shader : ShaderProgramPtr{}; +} + +bool RendererGLShader::drawSubpixelDualSourceArrays( unsigned int mode, int first, int count ) { + if ( !isExtension( EEGL_ARB_blend_func_extended ) && + !isExtension( EEGL_EXT_blend_func_extended ) ) + return false; + if ( !canUseSubpixelDualSourceShader() ) + return false; + if ( !mSubpixelDualSourceShader && !mSubpixelDualSourceShaderInitializationAttempted ) { + mSubpixelDualSourceShaderInitializationAttempted = true; + mSubpixelDualSourceShader = createSubpixelDualSourceShader(); + } + if ( !mSubpixelDualSourceShader ) + return false; + ShaderProgram* previousShader = mCurShader; + const BlendMode previousBlendMode = BlendMode::getPreBlendFunc(); + 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 ); + setShader( previousShader ); + return true; +} + bool RendererGLShader::setTextureColorMode( Int32 mode ) { if ( mTextureColorMode_id == -1 ) return false; diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 277f927f5..a50b5c98a 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -1811,11 +1811,11 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl const bool allowSubpixel = effect == BlendMode::Alpha() && rotation == 0.f && scale == 1.f; Uint32 alloc = numvert * sizeof( VertexCoords ); - Uint32 allocC = numvert * GLi->quadVertex(); if ( 0 != mFontStyleConfig.OutlineThickness ) { GLi->colorPointer( 4, GL_UNSIGNED_BYTE, 0, - reinterpret_cast( outlineColors.data() ), allocC ); + reinterpret_cast( outlineColors.data() ), + outlineColors.size() * sizeof( Color ) ); GLi->texCoordPointer( 2, GL_FP, sizeof( VertexCoords ), reinterpret_cast( &mOutlineVertices[0] ), alloc ); GLi->vertexPointer( 2, GL_FP, sizeof( VertexCoords ), @@ -1826,7 +1826,7 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl } GLi->colorPointer( 4, GL_UNSIGNED_BYTE, 0, reinterpret_cast( colors.data() ), - allocC ); + colors.size() * sizeof( Color ) ); GLi->texCoordPointer( 2, GL_FP, sizeof( VertexCoords ), reinterpret_cast( &mVertices[0] ), alloc ); GLi->vertexPointer( 2, GL_FP, sizeof( VertexCoords ), diff --git a/src/tests/unit_tests/fontrendering_tests.cpp b/src/tests/unit_tests/fontrendering_tests.cpp index 24ea763e2..506ac2505 100644 --- a/src/tests/unit_tests/fontrendering_tests.cpp +++ b/src/tests/unit_tests/fontrendering_tests.cpp @@ -167,6 +167,59 @@ UTEST( FontRendering, subpixelCoverageCompositesPerChannel ) { "Subpixel text did not update a transparent target's alpha" ); } +#if EE_PLATFORM == EE_PLATFORM_LINUX +UTEST( FontRendering, subpixelCoverageAllRenderers ) { + struct RendererCase { + GraphicsLibraryVersion version; + const char* name; + }; + static constexpr RendererCase renderers[] = { { GLv_2, "OpenGL 2" }, + { GLv_3, "OpenGL 3" }, + { GLv_3CP, "OpenGL 3 Core" }, + { GLv_ES2, "OpenGL ES 2" } }; + + for ( const RendererCase& renderer : renderers ) { + UIApplication app( + WindowSettings( 320, 96, renderer.name, WindowStyle::Default, WindowBackend::Default, + 32 ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ), + ContextSettings( false, 0, 0, renderer.version ) ); + ASSERT_TRUE_MSG( app.getWindow() && app.getWindow()->isOpen(), renderer.name ); + ResourceScope& scope = *app.getUI()->getResourceScope(); + FontTrueTypePtr font = FontTrueType::New( renderer.name, scope ); + ASSERT_TRUE_MSG( + font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/NotoSans-Regular.ttf" ), + renderer.name ); + font->setAntialiasing( FontAntialiasing::Subpixel ); + + EE::Window::Window* window = app.getWindow(); + window->setClearColor( Color::White ); + window->clear(); + Text::draw( String( "Direct LCD" ), { 8.f, 4.f }, font.get(), 24, Color::Black ); + Text retained( "Retained LCD", font.get(), 24 ); + retained.setFillColor( Color::Black ); + retained.draw( 8.f, 44.f ); + + Image image = window->getFrontBufferImage(); + auto hasColoredCoverage = [&image]( Uint32 top, Uint32 bottom ) { + for ( Uint32 y = top; y < bottom; ++y ) { + for ( Uint32 x = 0; x < image.getWidth(); ++x ) { + const Color pixel = image.getPixel( x, y ); + 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; + } + } + return false; + }; + EXPECT_TRUE_MSG( hasColoredCoverage( 0, 40 ), renderer.name ); + EXPECT_TRUE_MSG( hasColoredCoverage( 40, image.getHeight() ), renderer.name ); + } +} +#endif + UTEST( FontRendering, scaledSubpixelGlyphAtlas ) { UIApplication app( WindowSettings( 256, 64, "eepp - Scaled Subpixel Glyph Atlas", VisualTestWindowStyle,