Is it possible to have SKSL shaders with a more recent version of SGSL?

19 views
Skip to first unread message

Loïc Baumann

unread,
Jul 12, 2024, 8:44:27 AM (10 days ago) Jul 12
to skia-discuss
Hi there,

If I'm not mistaken, even when I bind Skia to an OpenGL 4.0+ backend the SKSL shaders are still compiled with a lower (and old) version of SGSL.

I assume it may be due to limitations on some (old) platforms, but in my case, I do not care about cross-platform, and it would change my life to be able to compile SKSL shaders with a more recent version of SGSL.

Is this possible? If not, is there a plan to get there at some point? Multiple compilation profiles would solve this.

For instance, I'm currently limited with this error:
"index expression must be constant"

Which forces me to unroll all the code manually to use constant indices...

Thanks !

John Stiles

unread,
Jul 12, 2024, 8:49:29 AM (10 days ago) Jul 12
to skia-d...@googlegroups.com
Not at this time, sorry. Skia runtime shaders only support GLSL ES2. 

The most relevant limitation is the software backend; runtime effects do work on CPU surfaces, and the CPU engine doesn't support every ES3 feature yet. 

--
You received this message because you are subscribed to the Google Groups "skia-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to skia-discuss...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/skia-discuss/ff86f31f-98a2-44b2-a18c-cff85fef8820n%40googlegroups.com.

Loïc Baumann

unread,
Jul 12, 2024, 10:28:12 AM (10 days ago) Jul 12
to skia-discuss
Thanks for the answer.
I understand this might make things harder to support on the CPU side, but it is not something to consider supporting multiple profiles and enable ES3 on hardware only?
I know this is one additional code path, but hardware accelerated UI would benefit so much of this.

John Stiles

unread,
Jul 12, 2024, 10:44:41 AM (10 days ago) Jul 12
to skia-d...@googlegroups.com
I agree and definitely see the value-add here, but the team unfortunately hasn't had resources to focus on prioritizing ES3 runtime effect support. In practice, most runtime effects can be reworked into an ES2-compatible equivalent without any visible compromise, and the amount of work required to fully finish ES3 support has been fairly daunting. (Also, for better or worse, the Android min spec still allows ES2-only devices to exist and get certification.)

What are you trying to do specifically? I would be curious to see what your shader is doing that is insurmountable in ES2.


Loïc Baumann

unread,
Jul 12, 2024, 11:09:16 AM (10 days ago) Jul 12
to skia-discuss
For the UI engine I'm writing, I want every control to use SDF shapes for the rendering, it gives several advantages.
So I have this Shader that I feed with a stream of float which contains commands to execute, like: translate, rotate, create a circle, create a box, give round edge of 4 pixels to the box, to a union compound operation, etc.

Ideally, each command would be variable in size, the first entry contains the ID of the command, then I take the x float I need to process it. Unfortunately (if I'm not mistaken), the only way I can process this array of float is through a for-loop where the index is incremented only in the loop itself.

I solved this by taking an arbitrary size of 4 floats for all commands, but it's not ideal, but it works.

Now I can perform recursive compound operations like: Union of (Union of (Circle, Box), Box), so I need to use a stack to store data, and I need to index into this stack based on a non-constant value.
So yes, I can unroll the code and do "if level == 0, then stack[0]", same for level 1, etc. And that's what I did... But again, far from ideal.

SKSL code below:

SDF01.png

uniform float4[50] shapeData;
const float MAGIC_NUMBER = 1000000.0;

float ProcessSDF(vec2 pos)
{
    // Distance
    float d = 0.0;

    vec2[10] queue;
    int queueIndex = 0;
    mat3 transform = mat3(1.0);

    for (int i=0; i<25; i++)
    {
        float4 bank = shapeData[i];
        int token = int(bank.x);

        switch(token){
            // End of data stream
            case 0:
            {
                break;
            }
            // Circle
            case 1:
            {
                vec3 txp = inverse(transform) * vec3(pos, 1.0);
                d = sdCircle(txp.xy, bank.y);
                break;
            }

            // Box
            case 2:
            {
                vec3 txp = inverse(transform) * vec3(pos, 1.0);
                d = sdBox(txp.xy, bank.yz);
                break;
            }
            // Rounded
            case 3:
            {
                d -= bank.y;
                break;
            }
            // Onion
            case 4:
            {
                d = abs(d) - bank.y;
                break;
            }
            // Translate
            case 5:
            {
                mat3 t = mat3(
                1.0, 0.0, 0.0,
                0.0, 1.0, 0.0,
                bank.y, bank.z, 1.0);
                transform = t * transform;
                break;
            }
            // Rotate
            case 6:
            {
                mat3 r = mat3(
                cos(bank.y), -sin(bank.y), 0.0,
                sin(bank.y), cos(bank.y), 0.0,
                0.0, 0.0, 1.0);
                transform = r * transform;
                break;
            }
            // Scale
            case 7:
            {
                // TODO !!!
                break;
            }

            // Compound
            case 8:
            {
                // Cursing the lack of non constant array indexation in this version of GLSL
                if (queueIndex == 0){
                    queue[0] = vec2(bank.y, MAGIC_NUMBER);
                    queueIndex++;
                }
                else if (queueIndex == 1){
                    queue[1] = vec2(bank.y, MAGIC_NUMBER);
                    queueIndex++;
                }
                break;
            }
            // EndShape
            case -1:
            {
                transform = mat3(1.0);

                // Are we in a compound operation?
                if (queueIndex > 0){
                    if (queueIndex  == 1){
                        // We processed the first shape of the compound
                        if (queue[0].y == MAGIC_NUMBER){
                            queue[0].y = d;
                        }
                        else {
                            // We processed the second (and subsequents) shape of the compound
                            float shape1d = queue[0].y;
                            float shape2d = d;
                            d = compoundOperation(shape1d, shape2d, queue[0].x);
                            queue[0].y = d;
                        }
                    }
                    else if (queueIndex == 2){
                        // We processed the first shape of the compound
                        if (queue[1].y == MAGIC_NUMBER){
                            queue[1].y = d;
                        }
                        else {
                            // We processed the second (and subsequents) shape of the compound
                            float shape1d = queue[1].y;
                            float shape2d = d;
                            d = compoundOperation(shape1d, shape2d, queue[1].x);
                            queue[1].y = d;
                        }
                    }
                }
                break;
            }
            // EndCompound
            case -2:
            {
                // If the queue is not empty we need to finalize the root compound operation
                if (queueIndex == 1){
                    float shape1d = queue[0].y;
                    float shape2d = d;
                    d = compoundOperation(shape1d, shape2d, queue[0].x);
                }
                --queueIndex;
                break;
            }
        }
    }
    return d;
}

John Stiles

unread,
Jul 12, 2024, 3:28:55 PM (9 days ago) Jul 12
to skia-d...@googlegroups.com
Thanks, this was an interesting example of ES3 capabilities that are difficult to mimic in ES2. Unfortunately, I think your best course of action is to eliminate the queue concept entirely, or change how the data is preprocessed and sent to the shader so that dynamic queueing isn't needed. I know that's easier said than done.

It doesn't directly assist in this scenario, but you can also fake dynamically-indexed array reads (but not writes) by stashing the data into a 1D texture and then sampling/evaluating it.


Loïc Baumann

unread,
Jul 12, 2024, 5:25:00 PM (9 days ago) Jul 12
to skia-discuss
Well, I've started using Shader with the first versions 22 years ago, so I know the workaround, but sampling a texture is dead slow compared to the array, it's also always tricky (point sampling, addressing the right texel position).
So I'd really like to avoid this. The other solution is to autogenerate the shader with a precompiler to match the input struct and I won't do that too, too complex.
Right now I can get things working out with, it's not ideal but it works.
It's just a bit frustrating because nowadays we can do so much with shaders, even on 5+ year old smartphones, so I was expecting more possibilities.

But I must admit the shader feature is really easy to use, and I've never seen that before, kudos for that, it makes it accessible to everyone and that's great! :)

Thanks for the help !

Loïc Baumann

unread,
Jul 12, 2024, 5:46:54 PM (9 days ago) Jul 12
to skia-discuss
Just find out that I won't be able to use  dFdx,  dFdy, and fwidth too... bye-bye anti-alias. 😭

John Stiles

unread,
Jul 12, 2024, 5:48:13 PM (9 days ago) Jul 12
to skia-d...@googlegroups.com
To be honest, those are also the ES3 functions that make the CPU implementation particularly difficult. 

Reply all
Reply to author
Forward
0 new messages