Indentation and syntax highlighting for embedded GLSL

One way to get at least some support for embedded GLSL from most editors is to abuse the preprocessor:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#define QUOTE(...) #__VA_ARGS__

void foo() {
    char shader[] = QUOTE(
        void main() {
            gl_FragmentColor = vec4(1.0, 0.0, 0.0, 0.0);
        }
    );

    // shader contains:
    // "void main() { gl_FragmentColor = vec4(1.0, 0.0, 0.0, 0.0); }"
}


Most editors will sort of work as far as indentation and syntax-highlighting is concerned inside the embedded block.

Of course, we're dealing with C++ so everything comes at a cost. In this case, we lose all newlines and indentation in the string at runtime and we lose the ability to embed preprocessor directives.

We can recover newlines with a poor man's formatter, and also add some magic for embedded preprocessor directives while we're at it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void cleanup(char* text) {
    for (; text[0]; ++text) {
        switch (text[0]) {
            case '$': {
                if (text[1] == ' ') {
                    text[0] = ' ';
                    text[1] = '\n';
                } else {
                    text[0] = '#';
                }
                break;
            }
            case '{':
            case '}':
            case ';': {
                if (text[1] == ' ') {
                    text[1] = '\n';
                }
                break;
            }
        }
    }
}


Now we can do stuff like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#define QUOTE(...) #__VA_ARGS__

void foo() {
    char shader[] = QUOTE(
        $version 420$
        void main() {
            gl_FragmentColor = vec4(1.0, 0.0, 0.0, 0.0);
        }
    );
    cleanup(shader);

    // shader contains:
    //     "#version 420 \n"
    //     "void main() {\n"
    //     "gl_FragmentColor = vec4(1.0, 0.0, 0.0, 0.0);\n"
    //     "}"
}


If you can't get enough of ugly preprocessor tricks you can go even further by doing quasiquotation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#define QUOTE(...) #__VA_ARGS__
#define QUASIQUOTE(...) QUOTE(__VA_ARGS__)

struct mat4 {
    float m[4][4];
};

// shared parameter value
#define TWEAK_PARAM 0.5

// shared code structure
#define TRANSFORM_PARAMS(kind) \
    kind TransformParams {\
        mat4 world; \
        mat4 projection; \
    }

TRANSFORM_PARAMS(struct);

void foo() {
    char shader[] = QUASIQUOTE(
        $version 420$
        TRANSFORM_PARAMS(layout(std140) uniform) params;
        void main() {
            // params.world, params.projection are available for use here
            gl_FragmentColor = vec4(1.0, TWEAK_PARAM, 0.0, 0.0);
        }
    );
    cleanup(shader);

    // shader contains:
    //     "#version 420 \n"
    //     "layout(std140) uniform TransformParams {\n"
    //     "mat4 world;\n"
    //     "mat4 projection;\n"
    //     "}\n"
    //     "params;\n"
    //     "void main() {\n"
    //     "gl_FragmentColor = vec4(1.0, 0.5, 0.0, 0.0);\n"
    //     "}\n"

    TransformParams params;
    // fill params with data, upload as uniform block, etc, etc (be careful with padding though)
}


This allows us to share macros between the host and the embedded code. Whether that's a good idea or not we'll never know.
Casey tried this - the indentation was still messed up. Not for GLSL code inside string, but for code going after it.