PDA

View Full Version : Managing several versions in a single shader file



gsourima
01-22-2015, 09:45 AM
... aka Dealing with the C0204 error :-)

Hi everyone!

To startup, just a little bit of context... I use since several years shaders that use #define directives to adapt to the underlying hardware, #defines that are generally passed manually to the compiler by concatenation to the shader content. For instance:
#define SOME_FLAG // This line may be added dynamically regarding current hardware

#ifdef SOME_FLAG
#version 120
[...]
#else
#version 150
[...]
#endif

[...]

Another typical exemple is for cross GL / GLES shaders, the GL_ES define being brought by the driver, according to the spec:
#ifdef GL_ES
precision mediump float;
[...]
#else
#version 150
[...]
#endif

[...]

1 - The problem

The thing is, this code is invalid (from what I understand), since the #version directive shall be the first one to appear (cf. "The #version​ directive must appear before anything else in a shader (https://www.opengl.org/wiki/Core_Language_(GLSL)#Version)"). I works with NVIDIA cards and drivers that were pretty compliant until recently, but it seems that on recent drivers those shaders no longer compile, and throw the error C0204: version directive must be first statement and may not be repeated.

2 - The solution

Actually I do not ask for help to get a solution to this issue, I just need to build a pre-compilation system that extracts and concatenates the proper portions of code to make it valid regarding version declaration (and I have to deal with the inconsistent line numbers in error logs :p). Another faster fix is to never define the version in the shader files but always dynamically as a pre-concatenated string before compilation.

2 - The real question

No, the real question is: Why?
Why does the version declaration needs to be the first directive? I mean, the general use of shaders is to define everything in one file, so what's the point of having access to the __VERSION__ definition, since we normally already wrote it and there can be the only one? And what's the point of defining implicitly (again according to the spec) GL_ES since we're supposed to know it already?

So yes, my question is rather: can someone explain me the point of this particular element of the spec? I'm sure there's a pretty good reason to it, I just don't understand it. And when you don't understand a standard, you don't use it properly :)

Thanks guys!

mhagain
01-22-2015, 09:54 AM
I don't have an answer but just to add that something similar also happens with D3D shaders. In D3D-land the Shader Model (broadly equivalent for the purposes of this discussion) must be specified as a parameter to the API call that does the compilation, so once again it's something that must be known up-front before (the rest of) the shader may be compiled.

Alfonse Reinheart
01-22-2015, 10:05 AM
I works with NVIDIA cards and drivers that were pretty compliant until recently

If your NVIDIA drivers allowed that to compile, then they were non-compliant, since they accepted something that is stated by the specification to give a compile error. The word you're looking for is "forgiving".


Another faster fix is to never define the version in the shader files but always dynamically as a pre-concatenated string before compilation.

That would generally be the best way to go about it.


Why does the version declaration needs to be the first directive?

Because the version could change more or less anything about how to interpret the rest of the shader. It could add, remove, or modify the meaning of #define, #ifdef, etc. And the only way to do that is to ensure that nothing else comes before it. Otherwise, you'd have to define two languages: what the text means before the version declaration, and what the text means after it.


I mean, the general use of shaders is to define everything in one file, so what's the point of having access to the __VERSION__ definition, since we normally already wrote it and there can be the only one?

That is certainly one way to use shaders. But it is also not the only way to use them. And, shader code examples found online aside, I rather doubt that it is the "general" way they are used.

If you want to support multiple versions of OpenGL, with varying feature-sets, all from a single shader file, then you're going to need a variable preamble in order to do so. You're going to need to be able to prefix this shader with not just version declarations but extension declarations too. That preamble will have to be generated from code, based on the current version and so forth.

This is why glShaderSource and glCreateShaderProgramv​ take multiple strings, not just one. That makes it easy to slip a code-generated preamble in front of a file you've loaded from disk, without having to do string concatenation.


And what's the point of defining implicitly (again according to the spec) GL_ES since we're supposed to know it already?

Your C/C++/etc code knows it already; your GLSL shader code may or may not.

gsourima
01-22-2015, 10:08 AM
@mhagain:

At least passing the version dynamically through the API rather than within the shader itself makes more sense to me: you write shaders without knowing the run-time version, and as you're supposed to test dynamically
available extensions and so on regarding hardware, you pass the adequate shader model version only at run-time...

@Alfonse Reinheart:

Thanks a lot for your answer.


The word you're looking for is "forgiving"
Absolutely! That's what I meant, that they were sweet to understand and silently deal with my mistakes :)

This seem to match with mhagain's answer: a good practice would be to set the version only at run-time... I think I get now why this should be the first directive (it actually tells the compiler which grammar to use), however I'll remain skeptical on a modification of the behavior of #define's related directives from one version to another :)

Anyway, thanks a lot for your complete answer, that helps a lot!

Alfonse Reinheart
01-22-2015, 10:44 AM
At least passing the version dynamically through the API rather than within the shader itself makes more sense to me

For your use case, perhaps. But your use case is not everyone's.

Consider an application where the user writes the shader code. Maybe some kind of modeling app. Now, most OpenGL implementations will accept a fairly wide variety of OpenGL versions. So it would be rather unfortunate if an application that let you write your own shaders refused to accept shaders from different versions. Or worse still, couldn't accept higher GLSL versions that retain backwards compatibility and interface-compatibility.

If the shader version were passed through the API, then such an application would need the shader writer to provide two pieces of information separately: the shader itself and the version. It couldn't just be a filename, or at least, not without defining a file format, which would by definition be non-standard.

By doing it the OpenGL way, the application writer has a choice. If you want the version number (and extension info) to be specified by the application, then you simply have a universal preamble that is prepended to your shaders, through the convenient APIs that allow this easily. And if you want the version number to be specified by the shader writer, then that's easy enough, since it's part of the language.


and as you're supposed to test dynamically available extensions and so on regarding hardware, you pass the adequate shader model version only at run-time...

That's highly dangerous. It would be too easy to use an extension by accident, without realizing your shader was relying on non-core behavior. And therefore, if you take your shader to an OpenGL implementation that doesn't provide that extension, it fails to work even though it's compiled under the same core version.

gsourima
01-26-2015, 02:44 AM
For your use case, perhaps. But your use case is not everyone's.

You're right, and actually I must have been tired last time, since that's what I do: I let users write their own shaders.

The only clean solution I see is to write a light higher language that encapsulates GLSL to deal with several versions, extensions, etc., and extract relevant parts before feeding them to the compiler. The versions that would be described in such higher lvl language could then be tested at run-time so that the proper one is selected.

Anyway, thanks again for your insights Alfonse.

Alfonse Reinheart
01-26-2015, 08:26 AM
Clean solution to what, exactly? If users are passing you their shaders, then it's up to your code to introspect those shaders (https://www.opengl.org/wiki/Program_Introspection) and figure out how to interact with them. Why do you need to make up your own shading language on top of that?

More importantly, what does that have to do with versoning at all?

glararan
01-29-2015, 02:43 PM
May I ask for full solution? I can't handle this error with #version in my project. Only solution for me is downgrade from 347.09. Thanks.

Alfonse Reinheart
01-29-2015, 03:12 PM
A "full solution" to what, exactly? You're not the same poster as the OP, and any "full solution" would have to be tailored to your specific conditions and needs. And it's not clear what those conditions and needs are... since you haven't told us.

glararan
01-31-2015, 10:38 AM
How to fix problem when shader compilation result with "C0204: version directive must be first statement and may not be repeated", my shader files have defined #version as first line and its not repeating, so there is some change in drivers, cause it works with older one

Alfonse Reinheart
01-31-2015, 11:17 AM
OK, that's a completely different problem from what the OP asked about. As clearly shown in the OP, his problem is that his shader files have not "defined #version as first line", but NVIDIA's compiler was accepting them anyway.

Your problem sounds like a driver bug. You should report it to NVIDIA (along with a reproducible test case). Or, at the very least, make a post in the "Drivers" forum here (again, with a reproducible test case).

glararan
02-01-2015, 03:50 PM
Yes its sound like driver bug, but other programs have to compile shaders aswell or not? And they works so there is kind of dark magic I suppose...

Alfonse Reinheart
02-01-2015, 05:23 PM
Or you're doing something wrong. But sadly, nobody has the clairvoyance needed to know what your problem is based solely on your description of it.

glararan
02-03-2015, 04:30 AM
I have to say this... since driver update it was working, I just recompiled program when I get new drivers.. so what can u say on this?

Alfonse Reinheart
02-03-2015, 08:47 AM
I have to say this... since driver update it was working, I just recompiled program when I get new drivers.. so what can u say on this?

Say on what? You're saying that, since you updated your drivers, it is now working. And since your code is working... what is there to say about anything? Furthermore, just because your code is working does not mean that it is conformant to the spec.

Especially if you're compiling on NVIDIA platforms...

My point is that we can't tell what's right or wrong just from a description. Since descriptions can be, you know, inaccurate.