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