Godot's flavor of GLSL shaders shouldn't clash with standard GLSL, to allow better recognition by third-party software #10880
Replies: 4 comments 6 replies
-
I like the idea of using I would not go the Note that in any case, we should keep support for the existing syntax as it's possible to use it in projects now that the rendering compositor was merged. The same applies to the |
Beta Was this translation helpful? Give feedback.
-
I found this discussion after filing (then closing) godotengine/godot#99840, because I hit the same problem. In the absence of builtin support for "godot style GLSL", writing compute shaders is currently a bit painful in external editors. In particular, even for compute shaders with a single section, the GLSL spec says that My ideas were all worse than this, so +1 to using ifdefs! I'm willing to write a formal proposal for this, and send the C++ patches to implement it. Requirements:
Two open questions: how do we make it efficient for Godot to identify which shader stages exist in a file? And, how do we handle the My proposal for both: use #pragma statements. These are the standard way to provide compiler-specific information, and explicitly supported in the GLSL standard. For example: #version 450
#pragma gd_shader_stages(compute) // or gd_shader_stages(vertex, fragment) for example
#pragma gd_shader_versions(standard, dithered)
#ifdef GD_STAGE_COMPUTE
...
#ifdef GD_VERSION_DITHERED
... The above is equivalent to this in current format: #[versions]
standard = "";
dithered = "#define GD_VERSION_DITHERED";
#[compute]
#version 450
...
#ifdef GD_VERSION_DITHERED
... This makes the section parser trivial: scan the start of the file for Third-party tools with explicit Godot support can also parse these #pragmas and give a great experience. Tools that don't support Godot explicitly can still be used, by manually defining the desired We can make the parser simpler by saying that the Godot pragmas must appear after the For example, with this rule, this compute shader is valid: #version 450
#pragma gd_shader_stages(compute)
layout(local_size_x = 2, local_size_y = 1, local_size_z = 1) in;
void main() {} But this shader is not: #version 450
layout(local_size_x = 2, local_size_y = 1, local_size_z = 1) in;
#pragma gd_shader_stages(compute) // ERROR: Godot pragma not at the top of the file
void main() {} What do you think? |
Beta Was this translation helpful? Give feedback.
-
I wanted to see how hard it would be to implement this, and, uh, I accidentally implemented it 😅 My local Godot build supports the above syntax and seems to be happy with "new-style" GLSL shaders. The implementation can process both the current format and the proposed new one, so all existing shader source keeps working. The syntax I implemented with some explanations: // GLSL spec says the version must come first, only comments and
// empty lines allowed before
#version 450
// Tell Godot what shader stages this file contains.
// Same as the existing format, compute shaders cannot be mixed
// with others, but vertex/fragment/tessellation shaders can be mixed.
#pragma gd_shader_stages(vertex, fragment)
// Optionally, complex shaders can declare multiple variants.
// If you don't specify this, you get one compiled shader per stage.
#pragma gd_shader_versions(lines, triangles)
// Now the shader code! Let's start with some stuff common to all stages, maybe an include and a shared parameter
#include "common_defs.glsl"
layout(set=0, binding=0) uniform Parameters {
vec3 world_size;
int grid_size;
} params;
// Vertex shader. Each stage turns into a GD_SHADER_<stagename> preprocessor symbol
#ifdef GD_STAGE_VERTEX
layout(set=0, binding=1) restrict buffer Positions {
vec3 data[];
} positions;
void main() {
// Pretend these helpers are in the #include, this is just a nonsense demo shader...
do_setup_common();
// Similar to stages, each version becomes a GD_VERSION_<name> symbol
#ifdef GD_VERSION_LINES
process_into_lines();
#endif
#ifdef GD_VERSION_TRIANGLES
process_into_triangles();
#endif
}
#endif // GD_STAGE_VERTEX
// And then the fragment shader.
#ifdef GD_STAGE_FRAGMENT
void main() {
do_setup_common();
adjust_frag();
}
#endif // GD_STAGE_FRAGMENT One nice thing about this format: if you just need one shader stage and no variants, you don't need to use ifdefs at all. Then the shader looks like a completely generic GLSL file with one extra pragma to tell Godot the shader type: #version 450
#pragma gd_shader_stages(compute)
void main() {
// blah blah compute shader
} For a more complex example, I grabbed a random shader from Godot's source tree, Code still needs cleaning up and tests, but I guess it's maybe time to write a formal proposal 😅 |
Beta Was this translation helpful? Give feedback.
-
I filed a proposal at #11274 for this, since it's been a month and there is no fundamental disagreement, even if the details might change. There is nothing new in the proposal compared to this discussion, just more details on the prototype implementation and answering the proposal template questions. |
Beta Was this translation helpful? Give feedback.
-
Godot currently recognizes
*.glsl
files, since it uses GLSL (e.g. for compute shaders).However, the syntax used is not standard GLSL. It actually uses merged stages, tagged under the Godot-specific tokens:
#[vertex]
,#[fragment]
,#[tesselation_control]
(sic),#[tesselation_evaluation]
(sic),#[compute]
Note that "tesselation" (with a single L) is used, which seems to be a typo. Double L doesn't work.
If you pass one such file to e.g.
glslang -E
it will complain about the syntax (invalid directive). This complicates compatibility with external tools such as IDE plugins. In VSCode, for example, there's a plugin that supports GLSL files, but it won't compile with this token (since it uses the official glslang tool for validation).It seems you can currently workaround this by using
#include
in the joint GLSL and get tool support on included files.But there's a few more alternatives to improve compatibility with external tools, and avoid clashing the format.
Option 1: Change Godot GLSL extension to something else, e.g.
*.godot-glsl
or*.joint-glsl
The advantage of this option is that you can use the same syntax. Something similar occurred in the past with #2488 where
*.shader
was changed to*.gdshader
which is more explicit about what the format means, and helps playing nice with external tools. (Alternative possible name might be*.gdglsl
, if it's not too confusing.)You would still have both options of keeping all stages on a
*.godot-glsl
or having just the special tokens in it and#include
each stage from a separate file with external tool support as normal.Then external tools will be able to either ignore the unsupported Godot-GLSL format, or properly support it.
Option 2: Change the syntax to be conformant to standard GLSL
If you're going to call this file a
*.glsl
, then I think it should use syntax that better matches the standard.One option would be to use
#if
directives instead to specify the compilation stage.Just for example's sake, here's some alternative ways this could be implemented:
Internally, Godot would just compile the file multiple times, varying the implicit defined macro for each stage.
Then, on external tools that allow specifying macro settings per-file/glob/project, e.g. via command-line arguments to the glslang validator like
--define-macro STAGE_compute=1
, you may be able to configure it at least to support Godot compute shaders via some*.comp.glsl
pattern or project-specific IDE settings.Additional: Recognize extensions like
*.comp.glsl
too without requiring the special#[compute]
tokenBesides one of the previous options, you could make Godot editor recognize this extension (maybe also
*.comp
?) making the token optional in this case. The advantage is using external files from the web here without any sort of editing.It also makes sense because you cannot mix
#[compute]
with other stages in the same file anyway.Beta Was this translation helpful? Give feedback.
All reactions