Reputation: 754
I'll leave the question up as a cautionary tale to others. The short answer is that uniform variables in GLSL need to be bound to the current program after glUseProgram. They don't find their way magically to shaders on their own. I added a glUniformMatrix4fv(gWorldLocation, 1, GL_FALSE, glm::value_ptr(projection)); after each glUseProgram and things improved.
~
I'm drawing a floor plan. It's a ortho projection. In the rendering callback, first I paint the floor (z=0) with a pattern, and for the moment I'm filling the whole screen with that pattern. All good. Now it's time to paint the walls, which are just just a lot of skinny rectangles cut into triangles, because apparently drawing thick lines is supposed to be that way.
Originally I did both floor and walls with one fragment shader. I'd draw the floor triangles, set a flag for the shader to see, and then draw the skinny wall triangles. The fragment shader honored the flag by using a different color, and I got floors and walls atop drawn to my satisfaction.
But as I add features I'm going to have more and more things to draw over the floor, so I decided to break the fragment shader apart - one for floors, one for walls, and there will be others for other features. I call glUSeProgram() for the floor, draw my triangles, then call glUseProgram() [and here be dragons it seems] to set the wall shaders up and draw the walls (at z=0.5, just to be sure). Everything broke, and in an interesting way: if I comment out the 2nd glUseProgram and associated draws, I can see the floor. If I put the 2nd Use and Draw ops back in, I see only the walls. And if I draw walls but no-op the wall shader (void main() {return;}) I get nothing drawn at all. With testing I determined that just calling the 2nd glUSeProgram() does the damage. Even if I draw no walls and have a no-op fragment shader after that, the floor is gone and window is blank. So it's not the wall fragment shader going off the rails.
I have a wild theory. I'm guessing this is all asynchronous, and the 2nd UseProgram blows away whatever is in progress with the first one (maybe nothing has even started drawing yet?) If that's true I need some way to say "ok, wait for the first program to finish everywhere". If that's false, I'm at a total loss because I don't see anything that suggests the glUSeProgram() is going to wipe the slate.
Where did I go wrong? I could in theory do everything in one fragment shader, but it would be immense and I know you aren't supposed to need to do that.
Here's the code in the render callback; the #if 01 just indicates what I switch off and on to try things.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); //start clean
mapData.selectShader(mapData.programDMFloor); //does glUseProgram (see below)
glEnableVertexAttribArray(0); //needed, no idea why?
glBindBuffer(GL_ARRAY_BUFFER, VBOS); //2 triangles (that for now cover the window)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO); //indexed draw for these
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //paint the window with the floor
glDisableVertexAttribArray(0); //needed, no idea why?
//If we stop here, we see the floor pattern as expected. But..!
#if 01
mapData.selectShader(mapData.programDMWall);
//The floor is now gone (or maybe never happened)
//this draws the walls
glEnableVertexAttribArray(0); //needed, don't know why
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //no indexed draw for these
glBindBuffer(GL_ARRAY_BUFFER, mapData.wallBuffer_); //Select a lot of skinny triangles
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
//std::cout << "Wall draw " << mapData.triangles_.size() * 3 << " floats from buf " << mapData.wallBuffer_ << '\n';
glDrawArrays(GL_TRIANGLES, 0, mapData.triangles_.size() * 3); //no indexed draw for this one
glDisableVertexAttribArray(0);
//we see the walls, but no floor!
#else
//we see the floor, and of course no walls.
//Need both!
#endif
glutSwapBuffers();
and as for mapData.selectShader(), it's just
void selectShader(GLuint ShaderProgram) //in Map::
{
glUseProgram(ShaderProgram);
//All the shaders should be sharing gWorld, so I probably don't need
// to set this over and over, since it's not going to change. But it needs
// to follow a glUseProgram so for now it's here. Needed each time?
gWorldLocation = glGetUniformLocation(ShaderProgram, "gWorld");
assert(gWorldLocation != 0xFFFFFFFF);
}
How is this actually supposed to work?
Addendum: a .vs for the Walls:
#version 430
//buffer indexes
#define BI_LIGHTS 0
#define BI_WALLS 1
#define BI_WALLWALK 2
#define BI_DOORS 3
#define BI_WALLINDEXES 4
layout (location = 0) in vec3 Position;
uniform mat4 gWorld;
layout (std430, binding=BI_WALLINDEXES) buffer wallIndexesSkip
{
unsigned int wallIndexes[];
};
out vec4 Color; //unused; the fragment shader calculates all colors
//but maybe we pass the initial floor texture here someday
flat out unsigned int wallIndex;
void main()
{
gl_Position = gWorld * vec4(Position, 1.0);
//We must the fragment shader which wall it should NOT use to
// occlude walls, otherwise the wall currently being drawing
// will be occluded by itself!
wallIndex = wallIndexes[gl_VertexID/9];
}
and the beginning of a .fs with declarations:
#version 430
#define BI_LIGHTS 0
#define BI_WALLS 1
#define BI_WALLWALK 2
#define BI_DOORS 3
#define BI_WALLINDEXES 4
#define AMB_NONE 0
#define AMB_SUNLIGHT 1
#define AMB_CLOUDY 2
#define AMB_RAIN 3
#define AMB_FOG 4
#define AMB_DIM 5
#define AMB_TEST 6
#define LIT_NONE 0
#define LIT_MAGICAL 1
#define LIT_FIRE 2
#define LIT_INFRAVISION 3
layout (std430, binding=BI_LIGHTS) buffer lights
{
unsigned int lightCount;
//12 bytes wasted in this packing
vec4 lightData[]; //x, y, radius, typeflag
};
layout (std430, binding=BI_WALLS) buffer walls
{
vec2 wp[];
};
layout (std430, binding=BI_WALLWALK) buffer wallRuns
{
// 0 this wall is ok
//>0 number to skip including this one
//0xffffffff marks end
unsigned int skipWalls[];
};
layout (std430, binding=BI_DOORS) buffer doors
{
unsigned int doorCount;
//12 bytes wasted in this packing
vec4 dp[]; //hinge x, hinge y, end x, end y
};
flat in unsigned int wallIndex;
in vec4 Color;
out vec4 FragColor;
Upvotes: 0
Views: 83
Reputation: 754
I'll leave the question up as a cautionary tale to others. The short answer is that uniform variables in GLSL need to be bound to the current program after glUseProgram. They don't find their way magically to shaders on their own. I added a glUniformMatrix4fv(gWorldLocation, 1, GL_FALSE, glm::value_ptr(projection)); after each glUseProgram and things improved.
Kudos to the folk in the comments, who zeroed in on the problem in record time.
Upvotes: 0