You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While a common way to use GLSL, editing bare text files or embedded strings is not a scalable way to write programs.
11
14
12
-
Life is so much better when you get instant feedback on the errors you make.
15
+
This article will show how to make programming in GLSL more tolerable with linting and various means of code reuse.
13
16
14
17
<!-- truncate -->
15
18
16
-
## Visual Studio Code
19
+
## GLSL Linters
20
+
21
+
My only requirement for a GLSL linter is that it prevents me from being surprised by shader compilation errors when I run my program. That means, in addition to reporting errors it finds, it should support features like `#include` and allow me to set defines at compile-time.
22
+
23
+
### Visual Studio Code
24
+
17
25
-[GLSL Lint](https://marketplace.visualstudio.com/items?itemName=dtoplak.vscode-glsllint): provides syntax highlighting and error detection.
18
-
- Requires glslangValidator from [the Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) or compiled yourself. The path to the executable must be supplied in the extension settings.
19
-
- Compiler flags are supplied as a list in the extension settings.
20
-
- I use `-C` (show multiple errors), `--glsl-version 460` (automatically sets the shader `#version`), and `--P #extension GL_GOOGLE_include_directive` (so I don't have to write that in every file).
21
-
- OpenGL users should add `--target-env opengl` for it to use OpenGL semantics.
22
-
- File extensions can be associated with specific shader stages in the extension settings. By default, it will detect common file extensions like .vert, .frag, and .comp. It also understands .frag.glsl, etc. automatically.
23
-
- Supports my convoluted `#include` hierarchies.
26
+
- Requires glslangValidator from [the Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) or compiled yourself. The path to the executable must be supplied in the extension settings.
27
+
- Compiler flags are supplied as a list in the extension settings.
28
+
- I use `-C` (show multiple errors), `--glsl-version 460` (automatically sets the shader `#version`), and `--P #extension GL_GOOGLE_include_directive` (so I don't have to write that in every file).
29
+
- OpenGL users should add `--target-env opengl` for it to use OpenGL semantics.
30
+
- Each "part" of an argument separated by a space needs to be provided as a separate item of the list:
31
+
- File extensions can be associated with specific shader stages in the extension settings. By default, it will detect common file extensions like .vert, .frag, and .comp. It also understands .frag.glsl, etc. automatically.
32
+
- Supports my convoluted `#include` hierarchies.
24
33
-[Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens): makes errors easier to read. Not specifically related to GLSL, but still useful to have.
25
34
26
-
## Visual Studio
35
+
Note that Visual Studio Code requires restarting for changes to extension settings to take effect.
36
+
37
+
Here is an example of arguments provided to GLSL Lint:
38
+
39
+

40
+
41
+
### Visual Studio
42
+
27
43
-[GLSL language integration (for VS 2022)](https://marketplace.visualstudio.com/items?itemName=DanielScherzer.GLSL2022): provides syntax highlighting and error detection.
28
-
- Similar to GLSL Lint for Visual Studio Code in terms of features.
29
-
- Can use an external compiler, such as glslangValidator.
30
-
- `%VULKAN_SDK%/Bin/glslangValidator.exe`
31
-
- Compiler flags are supplied as a single string
32
-
- I use these flags `-C --target-env vulkan1.3 --P "#extension GL_GOOGLE_include_directive: enable" --glsl-version 460`
33
-
- I haven't had much luck with this extension understanding my setup for `#include`, but it could be (and probably is) a skill issue on my part.
44
+
- Similar to GLSL Lint for Visual Studio Code in terms of features.
45
+
- Can use an external compiler, such as glslangValidator.
46
+
-`%VULKAN_SDK%/Bin/glslangValidator.exe`
47
+
- Compiler flags are supplied as a single string
48
+
- I use these flags `-C --target-env vulkan1.3 --P "#extension GL_GOOGLE_include_directive: enable" --glsl-version 460`
49
+
- I haven't had much luck with this extension understanding my setup for `#include`, but it could be (and probably is) a skill issue on my part.
34
50
-[inline_glsl](https://marketplace.visualstudio.com/items?itemName=kristian-r.inlineglsl): provides syntax highlighting and error detection inside C strings containing GLSL.
35
-
- Requires annotating C strings containing GLSL with a comment.
36
-
- Has no extension settings.
37
-
- Only supports OpenGL GLSL.
38
-
- Does not use an external GLSL compiler.
51
+
- Requires annotating C strings containing GLSL with a comment.
52
+
- Has no extension settings.
53
+
- Only supports OpenGL GLSL.
54
+
- Does not use an external GLSL compiler.
39
55
40
-
# Code Reuse
56
+
## Code Reuse
57
+
58
+
### `#include` support
41
59
42
-
## `#include` support
43
60
OpenGL GLSL doesn't support `#include` (GL_ARB_shading_language_include barely counts), so we must find an external way to support it. There are a few viable ways to gain support for `#include` in your shaders if you're using OpenGL, in increasing effort:
44
61
45
62
-[stb_include.h] provides a simple interface for expanding includes in shader sources without evaluating other preprocessor directives (which means it expands `#include` directives in inactive preprocessor blocks).
46
-
- [My fork of it](https://github.com/nothings/stb/pull/1336) fixes the const-incorrect API, which can annoyingly require `const_cast` to use in C++.
47
-
- [My super secret forbidden fork of it](https://github.com/JuanDiegoMontoya/Frogfood/blob/main/vendor/stb_include.h) fixes nested includes and some other minor issues, but introduces a C++17 standard library include (`<filesystem>`) for implementer convenience.
63
+
-[My fork of it](https://github.com/nothings/stb/pull/1336) fixes the const-incorrect API, which can annoyingly require `const_cast` to use in C++.
64
+
-[My super secret forbidden fork of it](https://github.com/JuanDiegoMontoya/Frogfood/blob/main/vendor/stb_include.h) fixes nested includes and some other minor issues, but introduces a C++17 standard library include (`<filesystem>`) for implementer convenience.
65
+
-[ARB_shading_language_include](https://registry.khronos.org/OpenGL/extensions/ARB/ARB_shading_language_include.txt) extends OpenGL by letting the user define a virtual filesystem that will be searched when include directives are found.
48
66
- Writing your own preprocessor?!?
49
-
- Just make sure it supports nested includes.
67
+
- Just make sure it supports nested includes.
68
+
- Even if you don't use its virtual filesystem, `ARB_shading_language_include` adds support for C-style `#line` (containing a string) which can improve error messages when used.
50
69
-[glslang](https://github.com/KhronosGroup/glslang) and [shaderc](https://github.com/google/shaderc) (whose shader compiler is essentially a glslang wrapper) provide C++ interfaces for processing, compiling, and linking shaders.
51
-
- If you want to supply shaders to OpenGL with `glShaderSource` (and let's face it: you should), you can first use glslang to only preprocess the shader, which will expand `#include`s and other directives, then return a string. glslang explicitly warns against this usage as it's not officially supported, but it worked on my machine! The downside to this approach is that errors reported by the driver will be in the "wrong" place after expanding includes. This can be mitigated by using a linter (see above) or fully compiling and linking the shader with glslang, which itself will report errors in the correct places.
52
-
- If you want to supply shaders to OpenGL with `glShaderBinary` (you don't), glslang can compile shaders into a SPIR-V binary with little more code than it takes to preprocess them.
53
-
- Both glslang and shaderc require writing an include handler to tell the compiler where to find included files. This gets a bit hairy with nested includes. I'm surprised there isn't a default implementation, so [here's a link to my hacky one](https://github.com/JuanDiegoMontoya/Frogfood/blob/main/src/Fvog/Shader2.cpp#L70-L116).
70
+
- If you want to supply shaders to OpenGL with `glShaderSource` (and let's face it: you should), you can first use glslang to only preprocess the shader, which will expand `#include`s and other directives, then return a string. glslang explicitly warns against this usage as it's not officially supported, but it worked on my machine! The downside to this approach is that errors reported by the driver will be in the "wrong" place after expanding includes. This can be mitigated by using a linter (see above) or fully compiling and linking the shader with glslang, which itself will report errors in the correct places.
71
+
- If you want to supply shaders to OpenGL with `glShaderBinary` (you don't), glslang can compile shaders into a SPIR-V binary with little more code than it takes to preprocess them.
72
+
- Both glslang and shaderc require writing an include handler to tell the compiler where to find included files. This gets a bit hairy with nested includes. I'm surprised there isn't a default implementation, so [here's a link to my hacky one](https://github.com/JuanDiegoMontoya/Frogfood/blob/main/src/Fvog/Shader2.cpp#L70-L116).
54
73
55
-
### Vulkan
74
+
####Vulkan
56
75
57
76
Since Vulkan GLSL users are likely to be already using glslang in some fashion (be it the CLI or API), adding support for `#include` is straightforward. CLI users don't have to do anything. API users must write an include handler as outlined above. In either case, I suggest adding `#extension GL_GOOGLE_include_directive : enable` to the preamble to preserve your fingers (`--P` in the CLI, `TShader::setPreamble` in the API).
58
-
## Sharing Code Between the Device and Host
77
+
78
+
shaderc automatically enables `GL_GOOGLE_include_directive`, so its users do not need to enable it themselves.
79
+
80
+
### Sharing Code Between the Device and Host
81
+
59
82
Writing the same structure definitions for buffers in both C or C++ and GLSL is annoying and prone to error.
60
-
### My Solution
83
+
84
+
#### My Solution
85
+
61
86
is to abuse macros. It also requires shading language includes.
62
87
63
88
The first step is to write a "shared header" of common definitions that can be understood by both GLSL and C++. This file is split into sections that are only compiled in one language or the other:
89
+
64
90
```glsl
65
91
#ifndef COMMON_H
66
92
#define COMMON_H
@@ -76,36 +102,38 @@ The first step is to write a "shared header" of common definitions that can be u
76
102
77
103
#define FROG_UINT32 uint
78
104
#define FROG_VEC4 vec4
105
+
#define alignas(x)
79
106
80
107
#endif
81
108
82
109
#endif // COMMON_H
83
110
```
84
111
85
112
The second step is to put structure definitions in a file that both languages understand. This can be a separate header, or in the body of the shader itself. For an example of the latter:
113
+
86
114
```glsl
87
115
// Common section
88
116
#include "Common.h"
89
117
90
118
struct Args
91
119
{
92
120
#ifdef __cplusplus // Simple constructor to initialize with safe defaults
93
-
Args() : dimensions(0, 0), samples(1) {}
121
+
Args() : dimensions(0, 0), samples(1) {}
94
122
#endif
95
-
FROG_IVEC2 dimensions;
96
-
FROG_UINT32 samples;
123
+
FROG_IVEC2 dimensions;
124
+
FROG_UINT32 samples;
97
125
};
98
126
99
127
#ifndef __cplusplus // GLSL-only section
100
128
101
129
layout(binding = 0) readonly buffer ArgsBuffer
102
130
{
103
-
Args args;
131
+
Args args;
104
132
};
105
133
106
134
void main()
107
135
{
108
-
// Do some shadery things
136
+
// Do some shadery things
109
137
}
110
138
111
139
#endif
@@ -114,4 +142,28 @@ void main()
114
142
C++ source files can include this file to see the definition of `Args`.
115
143
116
144
Note that including shader sources like this only works if the `#version` directive can be omitted (e.g. if it's part of the preamble). It cannot be placed into a preprocessor block due to wacky GLSL rules:
117
-
> The `#version` directive must occur in a shader before anything else, except for comments and white space.
145
+
> The `#version` directive must occur in a shader before anything else, except for comments and white space.
146
+
147
+
As usual, care must be taken to ensure the host and device impementations don't disagree in your structs' packing. [`alignas`](https://en.cppreference.com/w/cpp/language/alignas) can be used to force a particular alignment on the host, or explicit padding words can be added where the device would otherwise implicitly assume there to be padding.
148
+
149
+
```glsl
150
+
struct Frog // With alignas
151
+
{
152
+
alignas(16) FROG_VEC3 position;
153
+
alignas(16) FROG_VEC3 direction;
154
+
};
155
+
```
156
+
157
+
```glsl
158
+
struct Frog // With explicit padding
159
+
{
160
+
FROG_VEC3 position;
161
+
FROG_UINT32 _padding00;
162
+
FROG_VEC3 direction;
163
+
FROG_UINT32 _padding01;
164
+
};
165
+
```
166
+
167
+
If GLSL's packing rules confuse you, I suggest reading about them in [the spec](https://registry.khronos.org/OpenGL/specs/gl/glspec46.core.pdf#page=168) (7.6.2.2 Standard Uniform Block Layout). Beware sources that aren't the spec, as they are often wrong!
0 commit comments