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:
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;
}