Default sampler indices AND block buffer bindings

This has been asked before for samplers, so I thought I’d toss in my two cents.

Consider the following:


uniform samplerBuffer data;

layout(std140) uniform transform
{
    mat4 modelview;
    mat4 projection;
};

layout(location = 0) in vec4 position;
layout(location = 1) in vec4 color;
layout(location = 2) in vec4 instance_offset;

layout(location = 0) out vec4 fs_color;

void main()
{
    ...

Setting sampler image unit indices and block buffer locations through the API requires knowledge of the symbols within the calling application, an implementation-defined linkage descriptor that has to match the variable names, a mapping of the declaration order to unit indices and buffer locations, or an augmented-variable-name convention (e.g. “samplerBuffer data_iu0”). These requirements obviously increase client code complexity and the overall difficulty in using GLSL.

Since shader interfaces (formerly attributes and varyings) provide a means of explicitly setting their stream indices (locations) within the shader source itself, I can’t imagine a reason for not providing this functionality for other types like samplers and uniform blocks.

To accomplish this, I’m implementing a preprocessor that parses (and cleans) layout qualifiers with an extended syntax, records the identifier names associated with the declarations, and sets their values when the program is linked.

The extended syntax would be used as follows:


layout(location = 0) uniform samplerBuffer data;

layout(location = 0, std140) uniform transform
{
    mat4 modelview;
    mat4 projection;
};

layout(location = 0) in vec4 position;
layout(location = 1) in vec4 color;
layout(location = 2) in vec4 instance_offset;

layout(location = 0) out vec4 fs_color;

void main()
{
    ...

The sampler declaration could be made more intuitive:


layout(image_unit = 0) uniform samplerBuffer data;

Or, since samplers are required to be declared uniform anyway, the following won’t conflict with the layout qualifier’s action on uniforms (though it really shouldn’t in the previous case):


layout(image_unit = 0) samplerBuffer data;

These extensions, as far as I can tell, would not interfere with the language as it is (aside from dropping “uniform” from sampler declarations), and changing the sampler image unit bindings or block location bindings with the API would override the declaration (as it does already with attributes and fragment outputs).

Since the GLSL compiler links uniform blocks by name, the “location” layout qualifier id must be present on all declarations of the block in all shaders linked, and must have the same value. The same rule applies to samplers.

Personally, I think it should be possible to eliminate the required use of string literals by the client just to set up a shader for what could otherwise be a fixed shader interface (e.g. color textures bound to unit 1, so shaders are designed accordingly).

What you are really saying is to specify which texture unit that the sampler samples from… essentially the value of the uniform is what texture unit from which to sample and that value can change. So a better (and this was posted earlier too) is


uniform samplerBuffer data=0;

to indicate the initially sample from texture unit 0 unless glUniform1i is called on the uniform “data”.

Using layout to initialize where a uniform block sources from makes me… nervous… since it is kind of inconsistent… the same mentality, the value of the uniform is from where to source (i.e. what buffer object bounding point). One idea is:


layout(std140) uniform transform
{
    mat4 modelview;
    mat4 projection;
}=0;

but that would have the syntax meaning something completely different that the same syntax of C… which would be an epic what the heck moment for many.

Perhaps in the interest of visibility, one could have instead:


uniform sampler2D data = gl_TextureUnit(0); //source from texture unit 0 initially
layout(std140) uniform transform
{
    mat4 modelview;
    mat4 projection;
}=gl_UniformBuffer(0); //source from GL_UNIFORM_BUFFER, index=0

I agree that the “location” syntax is awkward.

I have learned to embrace the awkward.

In fact, let us all embrace the fulfillingly awkward future of GLSL:


#version "FourEightZero"  // because strings are always better
#pragma enable(__super_features__)
#pragma super_enable(__super_include__)

#super_include "backslash\super_conditional_macros.yay"
#super_include "simplicity_is_evil"

__super_layout__(unitIndex = 0) sampler2Dbuffer data;
__super_layout__(location = 0) uniform transform
{
    // have to tell the compiler that these are at global
    // scope, because they really do affect the entire planet
    // (but just the client's planet, not the universe)
    __global__ mat4 projection;
    __global__ mat4 modelView;
};

#if __SUPER_CONDITIONAL_VENDOR_MACRO____NVIDIA__
__super_layout__(location = 0) vec4 position;
__super_layout__(location = 1) vec4 color;

__super_layout__(location = 0) vec4 fs_color;

#elif ...

// 1000 lines of conditionals and __super_layout__ later...

#endif

void _super_main()
{
    fs_color = camelCasedLibraryFunction(data,some_index);

    // many more lines of superAlphabetSoupAwesomeness later...

    __superCertainty(20.0); // try for a 20% chance of this working
    // (have to use 2.05% with ATI and 0.21% with intel junk)

    gl_SuperPositionOut = modelView*projection*position;
}

Obviously an exaggeration, as the “#version” directive would eliminate the possibility that any code written for previous versions of GLSL would break with the new compiler, and hence eliminate the need for the eye-gouging underscore decoration… or would it?

– Back to sanity…

My objective with this request was to suggest something the ARB might find “palatable” in the sense that it uses an existing language feature and doesn’t require one to make non-trivial changes to a GLSL parser (we are already using “layout” with uniform buffers, seeing “location” in there wouldn’t be a big surprise).

Samplers are a weird issue, since they don’t actually appear to do the “sampling”, the “texture” function does. It would have been better had they called samplers “images” (as in EXT_shader_image_load_store), or better “imageIndex” (coping with the camel casing here), so it appears to function like an index.

Below is a more precise example of my gripe:


uniform sampler2D ctex; // it actually DOES the sampling?

...

void main()
{
    // Lookout! I'm going to make a texture out of this!
    vec4 c = texture(ctex,coords); // wat?
    ....

If the lookup functions were called “sample” or “texel” instead, the initialization and usage syntax would (to me) make more sense:


// for imageXX, we use layout becuase images aren't integers
layout(location = 0) uniform image2D ctex;

...

void main()
{
    vec4 c = sample(ctex,coords);
    ...


// this is ok-looking, because an index is usually an integer
uniform image2Dindex ctex = 0;
uniform imageBufferIndex bimg = 0;

// this is not so good-looking, because "...Index" implies something to do with multisampling
uniform image2DMultisampleIndex cmstex = 0;

// perhaps better would be this:
uniform multisampleImage2Dindex cmstex2 = 0;

// but still terrible looking

...

The idea is that GLSL could move up to where HLSL has been for a while by just providing a means to initialize things like samplers (e.g. selecting a sampler register). I would be happy with it, even if it causes half of my editor window to be consumed by “layout(blah…)”.

Using layout to initialize where a uniform block sources from makes me… nervous… since it is kind of inconsistent… the same mentality, the value of the uniform is from where to source (i.e. what buffer object bounding point).

The difference between samplers and uniform blocks is that samplers are themselves uniforms. They are set by the glUniform* family of functions, while uniform blocks are not themselves uniforms (they contain uniforms). Samplers are basically const uniforms that are set in the shader. So it makes sense to initialize them with equals.

Such syntax would have the semantic effect of any uniform initializer. Applying it to uniform blocks isn’t natural.

At the same time, layout(location) is used for things that are set at link time. The input attributes and output bindings, as well as the (with separate_program_objects) inter-program linkage. It is not used to set values that can are changed later, the way uniform block binding locations are.

So neither syntax is reasonable for uniform block bindings. It would be better to have its own syntax, like “layout(binding=#)” or something.

The difference between samplers and uniform blocks is that samplers are themselves uniforms. They are set by the glUniform* family of functions, while uniform blocks are not themselves uniforms (they contain uniforms). Samplers are basically const uniforms that are set in the shader. So it makes sense to initialize them with equals.

Stop right there, lets make all be clear on this. A uniform block specifies a layout of how memory is accessed from a buffer object, that is it. That the GL API lets you query how that layout is with an API that looks like fetching uniform locations does not change this. From the GL API, one changes from what binding point a uniform block sources (and from the GL API one changes what is bound to each binding point). Right now, all uses of layout specify jazz that cannot be changed (location for ins/outs, std/whatever for uniform blocks, geometry shader ins/outs, etc). It smells highly inconsistent to specify a value in layout that can be changed.

It makes more sense that layout means jazz that cannot be changed. Hence, my suggestion. Additionally, it reinforces that the data is being sourced from a binding point in GL.

It makes more sense that layout means jazz that cannot be changed.

I know; I said that. In the very post you responded to (though not the section you quoted). Here’s the pertinent section:

It makes more sense that layout means jazz that cannot be changed. Hence, my suggestion.

The point of the section you quoted was to show that your suggestion looks like initialization. And initialization is a concept that works for variables.

Samplers are uniforms, and uniforms are variables, even though they are constant variables. Uniform blocks are not themselves uniforms or variables. Therefore, it does not make sense to initialize them or use syntax that is consistent with initialization.

My point was that neither initialization nor layout location makes sense as good syntax. Hence suggesting “layout(binding)” instead.

Coming from a DirectX background, it absolutely boggles my mind that this is how things work in GL(SL) – that the client code has to know what name a particular shader uses for a sampler in order to bind a texture to that sampler.

Ditto for the whole attribute location thing too, I realize this has been solved in GLSL 1.3, but it should have been part of the original spec IMO.