Search code examples
androidarraysperformancelibgdxglsl

GLSL : Accessing an array in a for-loop hinders performance


Okay, so I'm developing an android app for a game I'm making (with LibGDX). And I have a fragment shader and I noticed that I had ~41 FPS. I was playing around with the code to see where the problem was, and I saw that changing how I accessed an array from arrayName[i] to arrayName[0] increased the performance back to 60 FPS, even though the loop only iterated once in this specific instance.
Here's the code :

#version 300 es

precision highp float;

uniform sampler2D u_texture;

in vec2 vTexCoord0;

struct BlackHole {
    vec2 position;
    float radius;
    float deformRadius;
};

uniform vec2 screenSize;
uniform vec3 cameraPos;
uniform float cameraZoom;

uniform BlackHole blackHole[4];
uniform int count;

out vec4 fragColor;

void main() {
    vec2 pos = vTexCoord0;

    bool doSample = true;

    for (int i = 0; i < count; i++) {
        BlackHole hole = blackHole[i];  // <-------- blackHole is the array, and changing from [i] to [0]
        vec2 position = (hole.position - cameraPos.xy) / cameraZoom + screenSize*0.5;
        float radius = hole.radius / cameraZoom;
        float deformRadius = hole.deformRadius / cameraZoom;

        vec2 deltaPos = vec2(position.x - gl_FragCoord.x, position.y - gl_FragCoord.y);
        float dist = length(deltaPos);

        if (dist <= radius) {
            fragColor = vec4(0, 0, 0, 1);
            doSample = false;
            break;
        } else if (dist <= radius + 1.0) {
            fragColor = vec4(1);
            doSample = false;
        } else if (dist <= deformRadius) {lensing
            float distToEdge = deformRadius - dist;
            pos += distToEdge * normalize(deltaPos) / screenSize;
        }
    }

    if (doSample)
        fragColor = texture(u_texture, pos);
}

In this specific case, "count" is 1.
Is this just an intrinsic property of GLSL? Or is there some fix to it – the highest value of "count" would be 4, so I could expand it out and not use a for loop, but I feel like that isn't a very good solution.
So, does anyone know why this is happening and/or a way to fix it?


Solution

  • See the GLSL ES 3.0 specification, page 140, "12.30 Dynamic Indexing":

    For GLSL ES 1.00, support of dynamic indexing of arrays, vectors and matrices was not mandated because it was not directly supported by some implementations. Software solutions (via program transforms) exist for a subset of cases but lead to poor performance.

    Note that OpenGL ES 3.0 is still not supported by all devices. Around 50% of all Android devices support it at this moment. The actual implementation of the driver/compiler might not yet be as optimized though. So the actual result and performance of your code is likely to vary from device to device.

    Try to avoid using dynamic branches and loops (it wouldn't even compile for GLSL ES below 3.0). If you know that your loop is at maximum 4 times executed, then using a macro to define that value:

    #define COUNT 4
    ...
    uniform BlackHole blackHole[COUNT];
    ...
        for (int i = 0; i < COUNT; i++) {
    

    If you then only need to have it loop 2 or 3 times while you compiled it for 4 times, then just put in values in the remaining items so that it will result as if those items weren't there (e.g. set the radius to zero). This is also how the libgdx default shader works.

    The same goes for branching in your shader. You have quite some if and else in your code. Try to remove those. I havent looked at your code in depth, but it looks like you could modify it to using e.g. a smoothstep instead of branching.

    A general tip: use a shader editor that shows, in realtime, the impact of the code that you write. For example the PowerVR shader editor or the Adreno shader editor. This will help you a lot.