**SOLVED** OpenGL issue with Mali 400 in Galaxy S3 and Note 2


#1

This has been discussed before in another topic but it was clumped together with a Juce demo issue and was not directly addressed.

 

 

On Mali MP400 devices (at least using Samsung hardware) there are sections of the screen that are rendered jaggy. It seems that the top portion of the screen is good to about 1/4 of the way. Then it is slightly jaggy until about 1/2 way down the screen then good again. At 3/4 of the way down things get really jaggy. This is on all apps, including the demo.

 

Most devices work great (out of the 15+ devices I currently own) from old to new however any of the Mali MP400 devices show this issue. Does anyone have a clue what is going on here?

 

Image 1 shows the mostly good area. Image 2 shows it just slightly off (on the top) and Image 3 shows the terrible area. That red box is draggable and you can actually watch it go bad while dragging from one section to another.

 

All works great on Mac, Windows, and iOS so this is just an Android issue. Of course I cannot test Mali on those platforms...

 

Michael

 

 


#2

Does anyone have a clue what is going on here?

Nope!

It's some kind of scaling artifact, but I really can't think what it might be doing..

 


#3


I have a fix for this issue. It had nothing to do with scaling, sorry Jules :-)


The Mali 400MP chip seems to have rather poor floating point accuracy while in the fragment shader. I was rather surprised on how poorly it did do. Luckily the fix is quite simply and my 10k + users are not reporting any issues. In fact a few of them have made very positive statements.


Keep in mind that I am not on the latest code however I did download the latest Juce and made these changes under the main branch and things work good.


In juce_OpenGLContext.cpp::copyTexture I have added an Android specific shader (this shader is fully tested on all iOS devices however I have not tested it on desktop class GPU's).
Replace:
    struct ProgramBuilder
            {
                ProgramBuilder (OpenGLShaderProgram& prog)
                {
                    prog.addShader ("attribute " JUCE_HIGHP " vec2 position;"
                                    "uniform " JUCE_HIGHP " vec2 screenSize;"
                                    "varying " JUCE_HIGHP " vec2 pixelPos;"
                                    "void main()"
                                    "{"
                                       "pixelPos = position;"
                                       JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
                                       "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
                                    "}",
                                    GL_VERTEX_SHADER);
                    
                    prog.addShader ("uniform sampler2D imageTexture;"
                                    "uniform " JUCE_HIGHP " float textureBounds[4];"
                                    "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
                                    "varying " JUCE_HIGHP " vec2 pixelPos;"
                                    "void main()"
                                    "{"
                    JUCE_HIGHP " vec2 texturePos = (pixelPos - vec2 (textureBounds[0], textureBounds[1]))"
                    "/ vec2 (textureBounds[2], textureBounds[3]);"
                    "gl_FragColor = texture2D (imageTexture, vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y));"
                                    "}",
                                    GL_FRAGMENT_SHADER);
                    prog.link();
                }
            };




with a dual purpose version:

#if defined(JUCE_IOS) || defined(JUCE_ANDROID)
            
            struct ProgramBuilder
            {
                ProgramBuilder (OpenGLShaderProgram& prog)
                {
                    prog.addShader ("attribute " JUCE_HIGHP " vec2 position;"
                                    "uniform " JUCE_HIGHP " vec2 screenSize;"
                                    "uniform " JUCE_HIGHP " float textureBounds[4];"
                                    "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
                                    "varying " JUCE_HIGHP " vec2 texturePos;"
                                    "void main()"
                                    "{"
                                    JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
                                    "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
                    "texturePos = (position - vec2 (textureBounds[0], textureBounds[1])) / vec2 (textureBounds[2], textureBounds[3]);"
                                    "texturePos = vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y);"
                    "}",
                                    GL_VERTEX_SHADER);
                    
                    prog.addShader ("uniform sampler2D imageTexture;"
                                    "varying " JUCE_HIGHP " vec2 texturePos;"
                                    "void main()"
                                    "{"
                                    "gl_FragColor = texture2D (imageTexture, texturePos);"
                                    "}",
                                    GL_FRAGMENT_SHADER);
                    prog.link();
                }
            };
#else
            
            
            struct ProgramBuilder
            {
                ProgramBuilder (OpenGLShaderProgram& prog)
                {
                    prog.addShader ("attribute " JUCE_HIGHP " vec2 position;"
                                    "uniform " JUCE_HIGHP " vec2 screenSize;"
                                    "varying " JUCE_HIGHP " vec2 pixelPos;"
                                    "void main()"
                                    "{"
                                    "pixelPos = position;"
                                    JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
                                    "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
                                    "}",
                                    GL_VERTEX_SHADER);
                    
                    prog.addShader ("uniform sampler2D imageTexture;"
                                    "uniform " JUCE_HIGHP " float textureBounds[4];"
                                    "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
                                    "varying " JUCE_HIGHP " vec2 pixelPos;"
                                    "void main()"
                                    "{"
                    JUCE_HIGHP " vec2 texturePos = (pixelPos - vec2 (textureBounds[0], textureBounds[1]))"
                    "/ vec2 (textureBounds[2], textureBounds[3]);"
                    "gl_FragColor = texture2D (imageTexture, vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y));"
                                    "}",
                                    GL_FRAGMENT_SHADER);
                    prog.link();
                }
            };
            
#endif


This code changes moves the main calculations from the Fragment Shader to the Vertex Shader and uses a 'varying' to pass in the texture position. This change alone will fix roughly 70% of the redraw issues on the Mali chipset (Galaxy S2, Galaxy S3, and Note 1/2) however we also have to do similar things for the generic image blitting routines.
This takes place in juce_OpenGLGraphicsContext.cpp. We need to change 'ShaderBase' and 'ShaderProgramHolder' to handle a separate shader being passed in for the Vertex Shader. Change the constructor on each to:
    ShaderProgramHolder (OpenGLContext& context, const char* fragmentShader, const char * vertexShader = NULL)
            : program (context)
        {
            JUCE_CHECK_OPENGL_ERROR
            if (vertexShader != NULL)
            {
                program.addVertexShader(OpenGLHelpers::translateVertexShaderToV3 (vertexShader));
            }
            else
            {
                program.addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
                               "attribute vec2 position;"
                               "attribute vec4 colour;"
                               "uniform vec4 screenBounds;"
                               "varying " JUCE_MEDIUMP " vec4 frontColour;"
                               "varying " JUCE_HIGHP " vec2 pixelPos;"
                               "void main()"
                               "{"
                               " frontColour = colour;"
                               " vec2 adjustedPos = position - screenBounds.xy;"
                               " pixelPos = adjustedPos;"
                               
                               " vec2 scaledPos = adjustedPos / screenBounds.zw;"
                               " gl_Position = vec4 (scaledPos.x - 1.0, 1.0 - scaledPos.y, 0, 1.0);"
                               "}"));
            }
            if (! program.addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (fragmentShader)))
                lastError = program.getLastError();
            program.link();
            JUCE_CHECK_OPENGL_ERROR
        }
        ShaderBase (OpenGLContext& context, const char* fragmentShader, const char * vertexShader = NULL)
            : ShaderProgramHolder (context, fragmentShader, vertexShader),
              positionAttribute (program, "position"),
              colourAttribute (program, "colour"),
              screenBounds (program, "screenBounds")
        {}
Basically those 2 functions allow shaders down the chain to implement their own Vertex shader. Maybe not the best way to handle it but it works for my case as well as other shaders I have written. Then ImageProgram needs to be replaced with this dual-purpose one as well:
#if defined(JUCE_IOS) || defined(JUCE_ANDROID)
    
    struct ImageProgram  : public ShaderBase
    {
        ImageProgram (OpenGLContext& context)
        : ShaderBase (context, "uniform sampler2D imageTexture;"
                      JUCE_DECLARE_VARYING_COLOUR
                      JUCE_DECLARE_SWIZZLE_FUNCTION
                      "varying " JUCE_HIGHP " vec2 texturePos;"
                      "void main()"
                      "{"
                      "gl_FragColor = frontColour.a * " JUCE_GET_IMAGE_PIXEL ";"
                      "}",
                      "uniform " JUCE_MEDIUMP " vec2 imageLimits;"
                      JUCE_DECLARE_MATRIX_UNIFORM
                      "attribute vec2 position;"
                      "attribute vec4 colour;"
                      "uniform vec4 screenBounds;"
                      "varying " JUCE_MEDIUMP " vec4 frontColour;"
                      "varying " JUCE_HIGHP " vec2 texturePos;"
                      "void main()"
                      "{"
                      " frontColour = colour;"
                      " vec2 adjustedPos = position - screenBounds.xy;"
                      " vec2 pixelPos = adjustedPos;"
                      " texturePos = clamp (" JUCE_MATRIX_TIMES_FRAGCOORD ", vec2 (0, 0), imageLimits);"
                      " vec2 scaledPos = adjustedPos / screenBounds.zw;"
                      " gl_Position = vec4 (scaledPos.x - 1.0, 1.0 - scaledPos.y, 0, 1.0);"
                      "}"
                      ),
        imageParams (program)
        {
            
            
        }
        
        ImageParams imageParams;
    };
    
    
#else
    
    struct ImageProgram  : public ShaderBase
    {
        ImageProgram (OpenGLContext& context)
        : ShaderBase (context, JUCE_DECLARE_IMAGE_UNIFORMS JUCE_DECLARE_SWIZZLE_FUNCTION
                      "void main()"
                      "{"
                      JUCE_CLAMP_TEXTURE_COORD
                      "gl_FragColor = frontColour.a * " JUCE_GET_IMAGE_PIXEL ";"
                      "}"),
        imageParams (program)
        {}
        
        ImageParams imageParams;
    };
    
#endif

These changes will give crystal clear rendering on the Mali 400MP chipset as well as every other one I have tested on. All of this while actually speeding up the rendering pipeline on iOS and Android by 5% on average.

Would love feedback on progressing this further and making the Android OpenGL rendering system even better.
Michael

    
 


#4

Wow! Thanks very much, I'll get onto that asap!


#5

FYI I've managed to turn this into a version that works on all platforms - would be good if you could give it a test on your machine!


#6

Works great on all my iOS and Android devices (including the S2, S3, Note, Note 2). I only have 1 Mac and Windows machine and it works on both of those as well (I do not have OpenGL enabled for my Mac and Windows products just yet).

 

Thanks for getting the current codebase updated! :-)

 

 

Michael