What is the intended behavior of putting a sampler2D in a struct? (GLSL 3.30)

I’ve been trying out LearnOpenGL’s “Lighting maps” tutorial on diffuse maps and specular maps, and the source involves putting sampler2D’s in a struct in a fragment shader to access the diffuse and specular map texture objects created/used in the tutorial. When I wrote my fragment shader code and tried to compile it in my program with glShaderSource and glCompileShader, I got an error from a glGetShaderInfoLog call saying that “samplers cannot be structure or block members”. Here’s what my fragment shader source looks like when I rewrote a dummy struct to isolate the error:

ctner.fshader (a fragment shader for the cube container rendered in the ‘Lighting maps’ tutorial)

#version 330 core
...
struct TestStruct {
	sampler2D test_sampler;
};

uniform TestStruct test_instance;
...
void (main) {
...
}

And here was the output I got from glGetShaderInfoLog (I used the “f” postfix in my source to make my floats explicit, so I’m okay with the “f” warnings):

WARNING: 0:29: Only GLSL version > 110 allows postfix “F” or “f” for float
WARNING: 0:30: Only GLSL version > 110 allows postfix “F” or “f” for float
ERROR: 0:37: ‘structure’ : samplers cannot be structure or block members
WARNING: 0:59: Only GLSL version > 110 allows postfix “F” or “f” for float
WARNING: 0:67: Only GLSL version > 110 allows postfix “F” or “f” for float
WARNING: 0:71: Only GLSL version > 110 allows postfix “F” or “f” for float

This result came from running my program using a Intel HD Graphics 4000 graphics card (glGetString(GL_VENDOR) -> “Intel”, glGetString(GL_RENDERER) -> “Intel(R) HD Graphics 4000”). My laptop has switchable graphics, and when I ran my program on the AMD card I had (GL_VENDOR -> “ATI Technologies Inc.”, GL_RENDERER -> “AMD Radeon HD 7570M/HD 7670M Graphics”, I didn’t get any errors surprisingly.

What is intended behavior of putting a sampler2D in a struct, according to the GLSL 3.30 spec? I’m not sure, considering it breaks on my Intel card and runs on my AMD card. There’s also a comment thread in the “Lighting maps” tutorial started by user Peter, where people mentioned that putting a sampler2D in a struct works on some cards and breaks on others. I read 4.1.7, and it mentions this about samplers:

They can only be declared as function parameters or uniform variables (see section 4.3.5 “Uniform” ). Except for array indexing, structure field selection, and parentheses, samplers are not allowed to be operands in expressions.

I assume since the spec mentions that a sampler2D can be an operand in a structure field selection (only if the struct is uniform, since a sampler can only be a uniform variable, if not a function parameter), it was intended to be allowed inside a struct definition, but since my Intel card explicitly returned an error about not allowing that, I’m thinking I may have interpreted it wrong.

I’ve been trying out LearnOpenGL’s “Lighting maps” tutorial

Learn OpenGL.org has no “lighting maps” tutorial. So what exactly were you looking at?

What is intended behavior of putting a sampler2D in a struct, according to the GLSL 3.30 spec?

The intent could have been to use bindless texturing. However, since I can’t track down the full shader code, I can’t be certain of that. That is an extension, so users would have to use a #extension declaration to get access to the feature in GLSL. If the shader doesn’t have such a declaration, even AMD’s compiler should error out. If the implementation doesn’t support it, then the compiler should have errored out when the #extension statement was processed.

In the table of contents on the left of the LearnOpenGL home page, there’s a link called “Lighting maps” under the section called “Lighting”. Here’s the link to the Lighting maps tutorial.

In that tutorial, I read through the “Diffuse maps” section. The source is linked at the end. The line ‘Shader lightingShader(“4.1.lighting_maps.vs”, “4.1.lighting_maps.fs”);’ references the fragment shader source I was referring to.

Here’s the link to that fragment shader. The source defines a sampler2D called ‘diffuse’ in a struct called ‘Material’, and then later instantiates a uniform instance of the Material struct, something like below:

#version 330 core
...
struct Material {
    sampler2D diffuse;
    vec3 specular;    
    float shininess;
};
...
uniform Material material;
...
void main() {
  ...
}

========

That’s interesting. Never heard of “bindless textures” or “#extension”. Will look more into it.

[QUOTE=DragonautX;1289162]
I assume since the spec mentions that a sampler2D can be an operand in a structure field selection (only if the struct is uniform, since a sampler can only be a uniform variable, if not a function parameter), it was intended to be allowed inside a struct definition, but since my Intel card explicitly returned an error about not allowing that, I’m thinking I may have interpreted it wrong.[/QUOTE]
Samplers are allowed to be structure members. They aren’t allowed to be members of interface blocks (unless you’re using bindless textures). It appears that Intel’s GLSL compiler treats those things as being more similar than they really are.

The key point is that you can only assign to sampler variables using glUniform1i() or glUniform1iv(). This is so that the implementation can perform the necessary operations. Whilst a sampler variable is assigned using an integer, a sampler isn’t just an integer, so assignment isn’t just a matter of storing an integer in uniform memory.

But this doesn’t preclude having them as structure members; if you declare a uniform variable of structure type, on the client side each member is a separate uniform variable (specifically, each member of primitive type is a separate variable, so if you have nested structures, the “leaf” members are variables; the structures themselves aren’t).

With bindless textures, a texture handle is just a (64-bit) integer, so you can have texture handles in uniform blocks; the dereferencing is performed by the GPU. But while bindless textures have been around for a while, they still aren’t a core feature of any OpenGL version.

Ok, thanks. My AMD card’s implementation is correct then.

Thanks for the explanation, especially about bindless textures. For getting working code on my Intel card, I’ll just keep uniforms as separate global variables. I’ll think about trying the bindless texture extension in the future. Will just stick to normal sampler variables for now.

Would it be right to send a bug report about not allowing a sampler in a struct to Intel? It seems like an important feature to allow a sampler in a struct. Even though my card is around 2012 I think, was thinking it’d be right to inform the vendor about it.