From the specification:
To use any built-in input or output in the gl_PerVertex and gl_PerFragment blocks in separable program objects, shader code must redeclare those blocks prior to use. A separable program will fail to link if:
* any shader uses a built-in block member not found in the redeclaration of that block.
This makes no sense and makes using this extension harder for no reason. Being forced to redeclare blocks doesn't give the compiler any information that it doesn't already know, since it can plainly see the name when you use it in your shader.
Yes, it would be nice to avoid this. There are two interrelated issues with doing so:
1) The goal is to declare a solid interface, independent of what's used, that will be shared across stages. Different stages may use different subsets of this interface.
2) The compiler does not have all compilation units at any one time that might be using this interface, and the point of Separate Shader Objects is to avoid a link step.
Without hurting performance by introducing a heavier weight link step and possibly without eliminating multiple compilation units per stage, using a "declare before you use" model works. Any other ideas?
> Any other ideas?
The problem to tackle is make sure that values which are read in stage N were written in stage M to avoid undefined behavior. As Alfonse pointed out, the compiler sees all built-ins used in a single program anyway, so what could be done is to store with the program object the read/write accesses to any built-in in question and store that information in, say, an interface record. This can be done at compile-time (and read-accesses could be optimized out in vertex shader records). The interface necesseray for a subsequent stage to work properly is then already implicitly defined, i.e. built-ins read in stage N must have a matching write-access in a preceding program's interface record (unless some default value can be provided by definition or via the API).
Now, assuming linkage is much more involved than simply matching at most a few built-in names and RW-properties, we can move the problem to the point when a separable program is bound to a pipeline: glUseProgramStages() will check if *all* interface requirements are met between the current and preceding stages (if any), or generate an INVALID_OPERATION. If debug output is enabled, additional information should be generated.
> As Alfonse pointed out, the compiler sees all built-ins used in a single program anyway,
The compiler only sees one compilation unit at a time. A program has multiple stages, and each stage has multiple compilation units. The compiler sees one of the latter at a time. By the time it sees shader N uses a field for the first time, shader N-1 is already compiled.
With SSO, the goal is that there is no work done at the link step, so the linker also does not deal with the whole program.
(In reply to comment #3)
> > As Alfonse pointed out, the compiler sees all built-ins used in a single program anyway,
> The compiler only sees one compilation unit at a time. A program has
> multiple stages, and each stage has multiple compilation units. The
> compiler sees one of the latter at a time. By the time it sees shader N
> uses a field for the first time, shader N-1 is already compiled.
> With SSO, the goal is that there is no work done at the link step, so the
> linker also does not deal with the whole program.
Doesn't the ability to create separate programs from more than one shader object work against this goal? There is a lot of language in the spec that clearly indicates that it is perfectly valid to create a "separate" program containing, for example, a VS and a GS linked together. The spec says that the linkage between the VS and GS *must* be validated at link-time.
So if you are linking shader objects into a separate program, the link step will still have to do some work. Since the goal of doing no work has already failed, is there a problem with adding a bit more work?
If you're using glCompileShaderProgram, then there is no need for an explicit declaration, since the compiler can detect during compilation what built-in outputs are being written to.
And if you're linking multiple objects together, the linking process is already going to have to do work in order to create the program. So having it scan each shader object for built-in outputs(or have the compilation of a shader object simply store any built-in outputs) should not be particularly difficult, compared to the rest of the linking process. So again, the compiler/linker can work all of these things out.