Shader Performance on Mobile

I’m rendering circles in a 2D project using Quad+MeshRenderer objects with a custom shader that takes care of producing the circle shape and fill color. The circles are relatively large (~500px diameter) and may partially overlap each other. There’s usually less than 10 of them on screen at a time.

My initial plan was to fill them with some fancy procedural effect, but I’m finding that I can’t even do very simple shading without running into performance issues on an iPhone 4s. I’m wondering if I’m doing something obvious wrong or if it’s expected that this class of hardware can’t render this type of workload at 60Hz. I’m estimating that it’s shading somewhere between 0.5M - 1.0M pixels per frame. They’re all alpha blended and Z writes are turned off.

Here’s the most simplified version of the shader that still produces stutter – it attempts to darken the circles toward the centers, taking into account two neighbor (overlapping) circles. FWIW, I’ve tried putting “lowp” pretty much everywhere, without much success, perf was still bad.

uniform float _Radius;
uniform vec4 _Center;
uniform vec4 _Color;
uniform vec4 _Left;
uniform vec4 _Right;

varying vec4 world_pos;

#ifdef VERTEX
    void main()
    {
        world_pos = _Object2World * gl_Vertex;
        gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
    }
#endif

#ifdef FRAGMENT

    // compute squared length
    float sql( vec2 v )
    {
        return v.x * v.x + v.y * v.y;
    }

    void main()
    {
        vec2 p = vec2( world_pos.x, world_pos.y );
        vec2 c = vec2( _Center.x, _Center.y );
        vec2 d = c - p;

        gl_FragColor.rgb = _Color.rgb;

        float sqld = sql( d );
        float rsquared = _Radius * _Radius;

        // compute minimum squared dist to relevant circle centers
        float mind = min( min( sql(_Left.xy-p) , sql(_Right.xy-p) ), sqld );

        // darken based on dist
        gl_FragColor.rgb *= 0.3 + 0.7 * mind / rsquared;

        // produce shape by setting alpha to 0 outside circle radius
        gl_FragColor.a =  3.0 * (rsquared - sqld );

    }
#endif

Thanks for any tips!

One thing you could do is to use texture coordinates to define the distance from the center, where 0,0 in tex coords is the center of the circle, and the outer edges would then be 0.5 or 1 or whatever you want… set up the tex coords on the quad and then the hardware will interpolate them. This gives you kind of a coordinate within the mesh without you having to deal with world position and center … although maybe you have some subtle reason for needing to use those. But anyway… then you can just use the tex coordinate to find the distance from 0,0 to that coordinate. This makes things a bit simpler than having to use a square root because when you’re dealing with numbers below 1 it seems you can just do xx + yy and get a useful value without having to sqrt it. You also don’t have to deal with a radius. I do this in my gradient shader pack.

Another way to change it up and make it perhaps even easier is to, basically, precalculate all of the distances by creating a circle mesh which is made of many triangles, in a fan like arrangement, around the center… then at each vertex on the outside of the cirlce, encode the texture coordinate to store the distance, which could just be in the x coordinate, e.g. set all of them to 1, and then set the vertices at the center of the circle all to x=0. Then you just need to look up the tex coord x value to get the distance without any further calculations, and can simply output this value multiplied by some color to color it. It of course uses more triangles but most likely that isn’t a problem unless you have a tonne of circles. Then there are almost no calculations, including not having to find the squares. So long as you have enough triangles you probably wont be able to notice that you’re seeing a circle made from flat edges. The circular mesh will also cut down on fill rate by not having fragments outside of the circle.

Quick question, are you sure is the shader slowing things down? ( got a profiler printout? )

How are you for instance generating the quad shapes? ( generating the mesh every frame? )
You mention that they only partially overlap, how much overlap are we talking about on the whole screen?