The 2025 Wheel Reinvention Jam just concluded. See the results.

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.