I'm experimenting with GLSL (in iOS) and I wrote a simple shader that takes a colour value and parameters for two circles (center
, radius
, and edgeSmoothing
). It is drawn using a single quad over the entire screen, the shader uses gl_FragCoord
and determines if each point is inside or outside the circles - it calculates an alpha of 1.0 inside the circles, smoothly shading down to 0.0 outside radius + edgeSmoothing
, then it applies a mirror-style clamp to alpha (triangle wave to get an even-odd fill-rule effect) and sets gl_FragColor = mix(vec4(0.0), color, alpha);
.
This works fine but I want 10 circles in 5 different colours, so I call glUniform
for all the shader uniforms and glDrawElements
to draw the quad five separate times (with the different colours and circle parameters), and my blend mode is additive so the different colours add up nicely to give the patterns I want, perfect!
Remember, this is an experiment, so I'm trying to learn about GL and GLSL more than draw the circles.
Now I think it will be much more efficient to draw the quad just once and pass in the parameters for all 10 circles into uniform arrays (centers[10]
, radii[10]
, etc.), looping through them in the GLSL and adding up the colours they produce in the shader. So I write this shader and refactor my code to pass in all the circle parameters at once. I get the correct result (the output looks exactly the same) but my frame-rate drops from 15fps to about 3fps - it's five times slower!!
The shader code now has loops, but uses the same maths to calculate the alpha value for each pair of circles. Why is this so much slower? Surely I'm doing less work than filling the whole screen five times and GL doing the additive blending five times (i.e. reading pixel values, blending, and writing back)? Now I'm just calculating the accumulated colour and filling the whole screen just once?
Can anyone explain why what I thought would be an optimisation had the opposite effect?
Update: Paste this code into ShaderToy to see what I'm talking about.
#ifdef GL_ES
precision highp float;
#endif
uniform float time;
void main(void)
{
float r, d2, a0, a1, a2;
vec2 pos, mid, offset;
vec4 bg, fg;
bg = vec4(.20, .20, .40, 1.0);
fg = vec4(.90, .50, .10, 1.0);
mid = vec2(256.0, 192.0);
// Circle 0
pos = gl_FragCoord.xy - mid;
d2 = dot(pos, pos);
r = 160.0;
a0 = smoothstep(r * r, (r + 1.0) * (r + 1.0), d2);
// Circle 1
offset = vec2(110.0 * sin(iGlobalTime*0.8), 110.0 * cos(iGlobalTime));
pos = gl_FragCoord.xy - mid + offset;
d2 = dot(pos, pos);
r = 80.0;
a1 = smoothstep(r * r, (r + 1.0) * (r + 1.0), d2);
// Circle 2
offset = vec2(100.0 * sin(iGlobalTime*1.1), -100.0 * cos(iGlobalTime*0.7));
pos = gl_FragCoord.xy - mid + offset;
d2 = dot(pos, pos);
r = 80.0;
a2 = smoothstep(r * r, (r + 1.0) * (r + 1.0), d2);
// Calculate the final alpha
float a = a0 + a1 + a2;
a = abs(mod(a, 2.0) - 1.0);
gl_FragColor = mix(bg, fg, a);
}