Single pass subpixel rendering in all backends.

Build fixes.
This commit is contained in:
Martín Lucas Golini
2026-07-26 01:44:31 -03:00
parent fac6c4ab05
commit ce3a10a3bd
13 changed files with 229 additions and 16 deletions
+13
View File
@@ -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;
}
@@ -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;
@@ -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;
@@ -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;
@@ -1,3 +1,4 @@
#include <eepp/graphics/blendmode.hpp>
#include <eepp/graphics/renderer/openglext.hpp>
#include <eepp/graphics/renderer/rendererglshader.hpp>
#include <eepp/graphics/renderer/rendererstackhelper.hpp>
@@ -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<Shader>() );
ShaderPtr fs( eeNew( FragmentShader, ( fragmentShader.c_str(), fragmentShader.size() ) ),
ResourceDeleter<Shader>() );
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;
+3 -3
View File
@@ -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<const char*>( outlineColors.data() ), allocC );
reinterpret_cast<const char*>( outlineColors.data() ),
outlineColors.size() * sizeof( Color ) );
GLi->texCoordPointer( 2, GL_FP, sizeof( VertexCoords ),
reinterpret_cast<char*>( &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<const char*>( colors.data() ),
allocC );
colors.size() * sizeof( Color ) );
GLi->texCoordPointer( 2, GL_FP, sizeof( VertexCoords ),
reinterpret_cast<char*>( &mVertices[0] ), alloc );
GLi->vertexPointer( 2, GL_FP, sizeof( VertexCoords ),
@@ -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<Int32>( pixel.r ) - pixel.g;
const Int32 greenBlueDelta = static_cast<Int32>( 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,