0
\$\begingroup\$

I have implemented a two pass Gaussian blur shader in GLSL like this:

...
const float[] weights = float[](0.382928, 0.241732, 0.060598, 0.005977, 0.000229);
vec2 offset = vec2(1.0f) / vec2(textureSize(image, 0));
vec3 result = texture(image, vec3(io_textureCoordinates, layer)).rgb * weights[0];
if(horizontal) {
  for(int i = 1; i < weights.length(); i++){
    result += texture(image, vec3(io_textureCoordinates + vec2(offset.x * float(i), 0.0f), layer)).rgb * weights[i];
    result += texture(image, vec3(io_textureCoordinates - vec2(offset.x * float(i), 0.0f), layer)).rgb * weights[i];
  }
} else {
  for(int i = 1; i < weights.length(); i++){
    result += texture(image, vec3(io_textureCoordinates + vec2(0.0f, offset.y * float(i)), layer)).rgb * weights[i];
    result += texture(image, vec3(io_textureCoordinates - vec2(0.0f, offset.y * float(i)), layer)).rgb * weights[i];
  }
}
...

Where variable horizontal and layer are uniforms (the image is a texture2DArray, but this is not that important). I use a 9x9 kernel (variable weights, the first weight is the center) and first I blur the image horizontally, then I change the horizontal uniform to false and rerun the shader to blur the image vertically. It works fine, but the performance is really bad. I don't know why, because I don't do anything special. The variable horizontal is a uniform, so it remains the same for every pixel in a draw call, so each shader core executes the the same path for every pixel.

Then I changed the code a little bit, like this:

...
const float[] weights = float[](0.382928, 0.241732, 0.060598, 0.005977, 0.000229);
vec2 offset = vec2(1.0f) / vec2(textureSize(image, 0));
vec3 result = texture(image, vec3(io_textureCoordinates, layer)).rgb * weights[0];
vec2 ofs = horizontal ? vec2(offset.x, 0) : vec2(0, offset.y);    
for(int i = 1; i < weights.length(); i++){
  result += texture(image, vec3(io_textureCoordinates + ofs * float(i), layer)).rgb * weights[i];
  result += texture(image, vec3(io_textureCoordinates - ofs * float(i), layer)).rgb * weights[i];
}
...

It's almost the same, I just use horizontal in another place, but the performance is much better. Can someone explain what is the difference between the two shaders what causes a big difference in performance? I thought that maybe the first version executes both sides of the if-else, which means two times more texture sampling, however the second version is much faster even if I execute it two times more.

I'm using WebGL 2 and Nvidia GTX 1050M.

\$\endgroup\$
  • \$\begingroup\$ Texture lookups inside conditionals are terrible for performance. \$\endgroup\$ – Maximus Minimus Jul 31 at 5:35
  • \$\begingroup\$ But what is the reason? The condition is based on a uniform and it can't cause divergence. \$\endgroup\$ – racz16 Jul 31 at 9:40
1
\$\begingroup\$
vec2 ofs = horizontal ? vec2(offset.x, 0) : vec2(0, offset.y);  

Simple ternary expressions like this can be compiled to a conditional move instruction, with no branching. So your second version eliminates all overhead that might be needed to support the branch.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ You might be right, but does it make the second shader two times faster (now I measured the time with a query) then the first shader? In the second it can be just one conditional move. But I think in the first, it's just two conditional jumps. I don't see how can it cause that great difference in performance, considering that it always jumps to the same place (in one draw call). Maybe i'm wrong, I'm not an assembly expert. \$\endgroup\$ – racz16 Aug 1 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.