I'm trying to access an array of structs in a glsl fragment shader passed from a geometry shader, and I'm having odd failures with any attempt to access certain indices over an arbitrary number. I've already discovered you apparently can't access an array by variable in a fragment shader, but it fails even using an int literal.
I've reduced the shader down a simplified version that shows the problem. This receives a series of quads (triangle strips) from the geometry shader and renders red rectangles, with a smaller sub-rectangle in the middle being rendered yellow if the relative fragment falls into an arbitrary bounding box passed along from the vertex.
#version 400 core
struct SpriteLayer {
ivec2 texPos;
ivec2 offset;
ivec2 dim;
int texNum;
uint flags;
uint color;
};
in vec2 lmUV;
flat in int frag_numLayers;
flat in uint frag_totalColor;
in vec2 pixel;
flat in SpriteLayer spriteLayer[6];
out vec4 color;
uniform sampler2D texLightmap;
uniform sampler2D texBase[16];
uniform int numTextures;
uniform vec4 ambientLight;
uniform vec2 randomSeed;
void main() {
color = vec4(1f, 0f, 0f, 1f);
SpriteLayer lay = spriteLayer[1]; // works
//SpriteLayer lay = spriteLayer[5]; // fails if preceding line replaced with this
if (
(pixel.x >= lay.offset.x) &&
(pixel.x < lay.offset.x + lay.dim.x) &&
(pixel.y >= lay.offset.y) &&
(pixel.y < lay.offset.y + lay.dim.y)
) {
color.g = 1f;
}
}
I checked GL_MAX_FRAGMENT_INPUT_COMPONENTS and my environment reports 128. Unless I'm very mistaken in how that works, I only count around 60 total input components here? Just to check I also tried padding the struct out, but no change. Despite being an array of 6 structs, when I access elements spriteLayer[0] through [3], the shader renders, but when I change the code to access spriteLayer[5] it fails, with no compilation error and no rendering whatsoever, with my program unable to set the uniforms. Trying to access spriteLayer[4] sometimes succeeds and sometimes fails depending on how I restructure the code, yet this quirk doesn't seem dependent on the number of input components at all (!?). Same problem with direct access to the array without the intermediary SpriteLayer variable. What's going on here?
Related
In my application I add two lights. One at (0,0,2) and the second one at (2,0,0). Here's what I get (the x,y,z axes are represented respectively by the red, green & blue lines):
Notice how only the first light is working and the second is not. I made my application core-profile compliant to inspect the buffers with various tools like RenderDoc and NSight and both show me that the second light's data is present in the buffer (picture taken while running Nsight):
The positions seem to be correctly transfered to the gpu memory buffer. Here's the implementation of my fragment shader that uses a SSBO to handle multiple lights in my application:
#version 430
struct Light {
vec3 position;
vec3 color;
float intensity;
float attenuation;
float radius;
};
layout (std140, binding = 0) uniform CameraInfo {
mat4 ProjectionView;
vec3 eye;
};
layout (std430, binding = 1) readonly buffer LightsData {
Light lights[];
};
uniform vec3 ambient_light_color;
uniform float ambient_light_intensity;
in vec3 ex_FragPos;
in vec4 ex_Color;
in vec3 ex_Normal;
out vec4 out_Color;
void main(void)
{
// Basic ambient light
vec3 ambient_light = ambient_light_color * ambient_light_intensity;
int i;
vec3 diffuse = vec3(0.0,0.0,0.0);
vec3 specular = vec3(0.0,0.0,0.0);
for (i = 0; i < lights.length(); ++i) {
Light wLight = lights[i];
// Basic diffuse light
vec3 norm = normalize(ex_Normal); // in this project the normals are all normalized anyway...
vec3 lightDir = normalize(wLight.position - ex_FragPos);
float diff = max(dot(norm, lightDir), 0.0);
diffuse += diff * wLight.color;
// Basic specular light
vec3 viewDir = normalize(eye - ex_FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
specular += wLight.intensity * spec * wLight.color;
}
out_Color = ex_Color * vec4(specular + diffuse + ambient_light,1.0);
}
Note that I've read the section 7.6.2.2 of the OpenGL 4.5 spec and that, if I understood correctly, my alignment should follow the size of the biggest member of my struct, which is a vec3 and my struct size is 36 bytes so everything should be fine here. I also tried different std version (e.g. std140) and adding some padding, but nothing fixes the issue with the second light. In my C++ code, I have those definitions to add the lights in my application:
light_module.h/.cc:
struct Light {
glm::f32vec3 position;
glm::f32vec3 color;
float intensity;
float attenuation;
float radius;
};
...
constexpr GLuint LIGHTS_SSBO_BINDING_POINT = 1U;
std::vector<Light> _Lights;
...
void AddLight(const Light &light) {
// Add to _Lights
_Lights.push_back(light);
UpdateSSBOBlockData(
LIGHTS_SSBO_BINDING_POINT, _Lights.size()* sizeof(Light),
static_cast<void*>(_Lights.data()), GL_DYNAMIC_DRAW);
}
shader_module.h/.cc:
using SSBOCapacity = GLuint;
using BindingPoint = GLuint;
using ID = GLuint;
std::map<BindingPoint, std::pair<ID, SSBOCapacity> > SSBO_list;
...
void UpdateSSBOBlockData(GLuint a_unBindingPoint,
GLuint a_unSSBOSize, void* a_pData, GLenum a_eUsage) {
auto SSBO = SSBO_list.find(a_unBindingPoint);
if (SSBO != SSBO_list.end()) {
GLuint unSSBOID = SSBO->second.first;
glBindBuffer(GL_SHADER_STORAGE_BUFFER, unSSBOID);
glBufferData(GL_SHADER_STORAGE_BUFFER, a_unSSBOSize, a_pData, a_eUsage);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); //unbind
}
else
// error handling...
}
Basically, I'm trying to update/reallocate the SSBO size with glBufferData each time a light is added in my app.
Now, since I'm having issues processing the second light data, I changed my fragment shader code to only execute the second light in my SSBO array by forcing i = 1 and looping until i < 2, but I get the following errors:
(50) : error C1068: ... or possible array index out of bounds
(50) : error C5025: lvalue in field access too complex
(56) : error C1068: ... or possible array index out of bounds
(56) : error C5025: lvalue in field access too complex
Lines 50 and 56 refer to diffuse += diff * wLight.color; and specular += wLight.intensity * spec * wLight.color; respectively. Is there really an out of bounds access even if I add my lights before the first draw call? Why is the shader compiling correctly when I'm using lights.length() instead of 2?
Finally, I've added a simple if (i == 1) in my for-loop to see if lights.length() is equal to 2, but it doesn't go in it. Yet the initial size of my buffer is 0 and then I add a light that sets the buffer size to 36 bytes and we can see that the first light works fine. Why is the update/reallocate not working the second time?
So what I did was to add some padding at the end of the declaration of my struct on the C++ side only. The padding required was float[3] or 12 bytes, which sums up to 48 bytes. I'm still not sure why this is required, since the specifications state (as highlighted in this post)
If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its
members, and rounded up to the base alignment of a vec4. The
individual members of this sub-structure are then assigned offsets by
applying this set of rules recursively, where the base offset of the
first member of the sub-structure is equal to the aligned offset of
the structure. The structure may have padding at the end; the base
offset of the member following the sub-structure is rounded up to the
next multiple of the base alignment of the structure.
[...]
When using the std430 storage layout, shader storage blocks will be
laid out in buffer storage identically to uniform and shader storage
blocks using the std140 layout, except that the base alignment and
stride of arrays of scalars and vectors in rule 4 and of structures in
rule 9 are not rounded up a multiple of the base alignment of a vec4.
My guess is that structures such as vec3 and glm::f32vec3 defined by glm are recursively rounded up to vec4 when using std430 and therefore my struct must follow the alignment of a vec4. If anyone can confirm this, it would be interesting since the linked post above deals with vec4 directly and not vec3.
Picture with both lights working :
EDIT:
After more investigation, it turns out that the last 3 fields of the Light struct (intensity, attenuation and radius) were not usable. I fixed this by changing the position and color from glm::f32vec3 to glm::vec4 instead. More information can be found in a similar post. I also left a single float for padding, because of the alignment mentioned earlier.
I want to incorporate a custom attribute that varies per vertex. In this case it is assigned to location=4 ... but nothing happens, the other four attributes vary properly except that one. At the bottom, I added a test to produce a specific color if it encounters the value '1' (which I know exists in the buffer, because I queried the buffer earlier). Attribute 4 is stuck at the first value of its array and never moves.
Am I missing a setting ? (something to be enabled maybe ?) or is it that openGL only varies a handful attributes but nothing else ?
#version 330 //for openGL 3.3
//uniform variables stay constant for the whole glDraw call
uniform mat4 ProjViewModelMatrix;
uniform vec4 DefaultColor; //x=-1 signifies no default color
//non-uniform variables get fed per vertex from the buffers
layout (location=0) in vec3 coords; //feeding from attribute=0 of the main code
layout (location=1) in vec4 color; //per vertex color, feeding from attribute=1 of the main code
layout (location=2) in vec3 normals; //per vertex normals
layout (location=3) in vec2 UVcoord; //texture coordinates
layout (location=4) in int vertexTexUnit;//per vertex texture unit index
//Output
out vec4 thisColor;
out vec2 vertexUVcoord;
flat out int TexUnitIdx;
void main ()
{
vertexUVcoord = UVcoord;
TexUnitIdx=vertexTexUnit;
if (DefaultColor.x==-1) {thisColor = color;} //If no default color is set, use per vertex colors
else {thisColor = DefaultColor;}
gl_Position = ProjViewModelMatrix * vec4(coords,1.0); //This outputs the position to the graphics card.
//TESTING
if (vertexTexUnit==1) thisColor=vec4(1,1,0,1); //Never receives value of 1, but the buffer does contain such values
}
Because the vertexTexUnit attribute is an integer, you must use glVertexAttribIPointer() instead of glVertexAttribPointer().
You can use vertex attributes for whatever you want. OpenGL doesn't know or care what you're using them for.
I have the following fragment shader:
#version 330 core
layout (location = 0) out vec4 color;
uniform vec4 colour;
uniform vec2 light_pos;
in DATA
{
vec4 position;
vec2 texCoord;
float tid;
vec4 color;
} fs_in;
uniform sampler2D textures[32];
void main()
{
float intensity = 1.0 / length(fs_in.position.xy - light_pos);
vec4 texColor = fs_in.color;
if (fs_in.tid > 0.0)
{
int tid = int(fs_in.tid + 0.5);
texColor = texture(textures[tid], fs_in.texCoord);
}
color = texColor * intensity;
}
If I run my program, I get opengl error 1282, which is invalid operation. If I don't use the texture(), so I write texCoord = vec4(...) it works perfectly. I'm always passing in tid (texture ID) as 0 (no texture) so that part shouldn't even run. I've set the textures uniform to some placeholder, but as far as I know this shouldn't even matter. What could cause the invalid operation then?
Your shader compilation has most likely failed. Make sure that you always check the compile status after trying to compile the shader, using:
GLint val = GL_FALSE;
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &val);
if (val != GL_TRUE)
{
// compilation failed
}
In your case, the shader is illegal because you're trying to access an array of samplers with a variable index:
texColor = texture(textures[tid], fs_in.texCoord);
This is not supported in GLSL 3.30. From the spec (emphasis added):
Samplers aggregated into arrays within a shader (using square brackets [ ]) can only be indexed with integral constant expressions (see section 4.3.3 “Constant Expressions”).
This restriction is relaxed in later OpenGL versions. For example, from the GLSL 4.50 spec:
When aggregated into arrays within a shader, samplers can only be indexed with a dynamically uniform integral expression, otherwise results are undefined.
This change was introduced in GLSL 4.00. But it would still not be sufficient for your case, since you're trying to use an in variable as the index, which is not dynamically uniform.
If your textures are all the same size, you may want to consider using an array texture instead. That will allow you to sample one of the layers in the array texture based on a dynamically calculated index.
I know this solution is late, but if it helps anybody..
As per Cherno's video, this should work. He however uses the attribute 'fs_in.tid' as a 'GL_BYTE' in the gl_VertexAttribPointer fo the texture index, for some reason regarding casting 1.0f always got converted to 0.0f and hence did not work.
Changing GL_BYTE to GL_FLOAT resolved this issue for me.
Regarding the 'opengl error 1282', its a very common mistake I faced. I used to forget to call glUseProgram(ShaderID) before setting any of the uniforms. Because of this the uniforms even though not being used/set at the time can cause an error, namely '1282'. This could be one of the solutions, this solved it for me.
I need to output 24 indices per fragment in a shader. I already reached the maximum amount of rendertargets because I'm using four other rendertargets for my gbuffer. So I tried to output the data with an SSBO, indexing it with the gl_FragCoord of the pixel. The problem is, that it needs to be depth correct. So I tried to use layout(early_fragment_tests) in; and watched over the indices. I can see strange per pixel errors on some spots now and it looks like the indices from the triangles below are coming through plus it stops when I'm moving the camera closer to those spots.
I double checked the indexing of the ssbo and it's correct + the indices should be the same for a whole triangle, but the flickering is per pixel. So I think the depth test works only for the rasterized per pixel output and not for the whole fragment shader code. Could it be the problem or does somebody know if the depth test should stop the whole processing of the fragment? If that's not the case, even a separate depth pre pass couldn't help me.
Here is a fragment shader example:
#version 440 core
layout(early_fragment_tests) in;
layout(location = 0) uniform sampler2D texSampler;
#include "../Header/MaterialData.glslh"
#include "../Header/CameraUBO.glslh"
layout(location = 3) uniform uint screenWidth;//horizontal screen resolution in pixel
layout(location = 0) out vec4 fsout_color;
layout(location = 1) out vec4 fsout_normal;
layout(location = 2) out vec4 fsout_material;
coherent layout(std430, binding = 3) buffer frameCacheIndexBuffer
{
uvec4 globalCachesIndices[];
};
in vec3 gsout_normal;
in vec2 gsout_texCoord;
flat in uvec4 gsout_cacheIndices[6];
flat in uint gsout_instanceIndex;
void main()
{
uint frameBufferIndex = 6 * (uint(gl_FragCoord.x) + uint(gl_FragCoord.y) * screenWidth);
for(uint i = 0; i < 6; i++)
{
globalCachesIndices[frameBufferIndex + i] = gsout_cacheIndices[i];//only the closest fragment should output
}
fsout_normal = vec4(gsout_normal * 0.5f + 0.5f, 0);
fsout_color = vec4(texture(texSampler, gsout_texCoord));
MaterialData thisMaterial = material[materialIndex[gsout_instanceIndex]];
fsout_material = vec4(thisMaterial.diffuseStrength,
thisMaterial.specularStrength,
thisMaterial.ambientStrength,
thisMaterial.specularExponent);
}
Have a strange issue with my glsl shader. It renders nothing (eg black screen) and makes my glDrawElements cast a GL_INVALID_OPERATION. The shader in use is shown bellow. When I comment out the line with v = texture3D(texVol,pos).r; and replace it with v = 0.4; it outputs what is expected (orange-like color) and no gl errors is generated.
uniform sampler2D texBack;
uniform sampler3D texVol;
uniform vec3 texSize;
uniform vec2 winSize;
uniform float iso;
varying vec3 inCoords;
vec4 raytrace(in vec3 entryPoint,in vec3 exitPoint){
vec3 dir = exitPoint - entryPoint;
vec3 pos = entryPoint;
vec4 color = vec4(0.0,0.0,0.0,0.0);
int steps = int(2.0*length(texSize));
dir = dir * (1.0/steps);
vec3 n;
float v,m=0.0,avg=0.0,avg2=0.0;
for(int i = 0;i<steps || i < 2500;i++){
v = texture3D(texVol,pos).r;
m = max(v,m);
avg += v;
pos += dir;
}
return vec4(avg/steps,m,0,1);
}
void main()
{
vec2 texCoord = gl_FragCoord.xy/winSize;
vec3 exitPoint = texture2D(texBack,texCoord).xyz;
gl_FragColor = raytrace(inCoords,exitPoint);
}
I am using an VBO for rendering a color cube as entry and exist point for my rays. They are stored in FBOs and they look ok when I render them directly to the screen.
I have tried chaning to glBegin/glEnd and draw the cube with quads and then I get the same errors.
I cant find what I am doing wrong and now I need your help. Why is my texture3D generating GL_INVALID_OPERATION?
Note:
I have enabled both 2d and 3d textures.
Edit:
I've just uploaded the whole project to github. browse to for more code https://github.com/r-englund/rGraphicsLibrary
This is tested on both Intel HD 3000 and Nvidia GT550m
According to OpenGL specification glDrawElements() generates GL_INVALID_OPERATION in the following cases:
If a geometry shader is active and mode is incompatible with the input primitive type of the geometry shader in the currently installed program object.
If a non-zero buffer object name is bound to an enabled array or the element array and the buffer object's data store is currently mapped.
This means the problem has nothing to do with your fragment shader. If you don't use geometry shaders, you should fix the buffer objects accordingly.
It looks like your are not providing additional relevant information in your question.