0

I'm writing an Android application that utilizes opengl to perform some changes to the camera output. I've written my code in such a way that I finally figured out what is causing the performance issue.

#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES sTexture;
uniform vec4 vColor;
const int MAX_COLORS = 6;
uniform float vHues[MAX_COLORS];
uniform float vOffsets[MAX_COLORS];
varying vec2 v_CamTexCoordinate;

float rgb2hue(vec4 c) {
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return abs(q.z + (q.w - q.y) / (6.0 * d + e));
}

bool isInRange(float a1,float a2, float vOffset) {
    if(a2 < 0.0) {
        return false;
    } else if(abs(a1-a2) < vOffset) {
        return true;
    } else if( a1 > a2) {
        return (1.0 - a1 + a2) < vOffset;
    } else {
        return (1.0 + a1 - a2) < vOffset;
    }
}

vec4 getGrey(vec4 c) {
    float grey = (c.r + c.g + c.b) / 3.0;
    return vec4(grey, grey, grey, c.a);
}

void main() {
    vec4 c = texture2D(sTexture, v_CamTexCoordinate);
    bool hasColor = vHues[0] >= 0.0;
    float hue = rgb2hue(c);
    vec4 test = getGrey(c);
    for(int i=0; i < MAX_COLORS; i++) {
       if(isInRange(hue, vHues[i], vOffsets[i])) {
          //If I uncomment this line the performance gets terrible
          //test = c;
       }
    }
    gl_FragColor = test;
}

There is a significant hit to performance (a lot of frames are skipped) when I uncomment the line above. I basically want to use the original color sometimes and a different color depending on some condition. It works but is there a more efficient way to do this? Also, why does this perform so poorly?

DroidT
  • 3,168
  • 3
  • 31
  • 29

2 Answers2

1

When you comment out that line, you're almost certainly allowing the compiler to remove the entire loop and you're reducing the code snippet to:

gl_FragColor = getNewColor(color);

I think we'd need to see the value of MAX_COLORS, vColors and the isValid function before we'd have an idea why performance is so poor.

Generally, conditionals in fragment shaders on OpenGLES are bad news. If it's possible then replacing your loop with some sort of lookup table (i.e. a texture you can sample) would solve your performance problem.

Whether it's feasible to do so depends on the problem you're trying to solve and there isn't enough context.

EDIT: OK, now that more information is posted, I can see that your hue comparison function (isInRange) is 1 dimensional, and would be well suited to be changed to a lookup table. You should look toward replacing the entire loop with a texture lookup. So that this:

vec4 test = getGrey(c);
for(int i=0; i < MAX_COLORS; i++) {
   if(isInRange(hue, vHues[i], vOffsets[i])) {
      //If I uncomment this line the performance gets terrible
      //test = c;
   }
}
gl_FragColor = test;

Would become something like this:

gl_FragColor = mix(getGrey(c), c, texture1d(sLookupTableTex, hue).r);

Constructing such a lookup table will no doubt be fiddly, and if vHues and vOffsets is changing all the time, then the lookup table will need to change all the time also which will have it's own performance implications, but I think the gains from replacing the loop and all the conditionals with a texture lookup will be enormous.

Columbo
  • 6,648
  • 4
  • 19
  • 30
  • Hey @Columbo. I've add all of the fragment shader code. Thanks. – DroidT Feb 27 '16 at 20:29
  • Thanks for the update. I'm having a hard time finding info on how to create a texture lookup table for this. Would you be able to provide an example that I can follow or point me in the right direction so I can try this solution? – DroidT Feb 28 '16 at 18:46
  • Here's a relevant stack overflow question. The answer says that texture1d isn't available on OpenGLES so you might just have to use 2D texture with dimensions something like 256x1 instead: http://stackoverflow.com/questions/24176767/how-to-make-a-1d-lut-in-c-for-glsl – Columbo Feb 28 '16 at 22:14
1

If statements and braching in general are a bad idea because your GPU can't optimise properly. See Efficiency of branching in shaders for more details. You should try avoiding the branch, even if it means more calculation.

The following code should do the trick

//summarise your isInRange function
bool isInRange = a2 >= 0.0 && ((abs(a1-a2) < vOffset) || (1.0 - max(a1, a2) + min(a1, a2)) < vOffset); 

//this will be 0.0 if false and 1.0 if true
float isInRangef = float(isInRange);

//multiply by the float condition to set the value
gl_FragColor = isInRangef * c + (1.0 - isInRangef) * getGrey(c);

As a general formula

  • convert the bool you're branching on to a float condition
  • set your target value as (condition)*valueIfTrue + (1.0 - condition)*valueIfFalse
Community
  • 1
  • 1
Swifter
  • 211
  • 1
  • 5