4
$\begingroup$

I've written a shader that writes depth to each face of a cube map, as part of rendering cube map shadows for a realtime 3D engine I'm writing with OpenGL and C++.

The shader transforms each vertex into world space in the vertex shader, then transforms polygons to each of the cube map's 6 faces in the geometry shader, and then writes depth to the cube map in the fragment shader.

The shader works correctly, but I receive the following error message when rendering with it for the first time:

Error:  OpenGL Error Callback:
Source:
Type: PERFORMANCE
id: 131218
Severity: MEDIUM
Message: Program/shader state performance warning: Vertex shader in program 89 is being recompiled based on GL state.

After that, the shader functions perfectly and I'm able to render depth to a cube map, and use it to render shadows for my point lights.

The shader has a vertex, geometry, and fragment shader component:

Vertex:

#version 430 core

layout (location = 0) in vec3 in_position;

uniform mat4 in_model;

void main()
{
    gl_Position = in_model * vec4(in_position.xyz, 1.0);
}

Geometry

#version 430 core

layout (triangles) in;
layout (triangle_strip, max_vertices = 18) out;

uniform mat4 shadowCamCubeMap_vp[6];

out vec4 FragPos;

void main()
{
    for(int currentCubeFace = 0; currentCubeFace < 6; currentCubeFace++)
    {
        gl_Layer = currentCubeFace; // Set the cube map face we're rendering to

        for(int currentVert = 0; currentVert < 3; currentVert++)
        {               
                FragPos = gl_in[currentVert].gl_Position; // World pos from vert shader

                gl_Position = shadowCamCubeMap_vp[currentCubeFace] * gl_in[currentVert].gl_Position;

            EmitVertex();
        }    
        EndPrimitive();
    }
}

Fragment:

#version 430 core

uniform vec3 lightWorldPos; 

uniform float shadowCam_near;
uniform float shadowCam_far;

in vec4 FragPos;

void main()
{
    float lightDistance = length(FragPos.xyz - lightWorldPos);

    lightDistance = lightDistance / shadowCam_far;

    gl_FragDepth = lightDistance;
} 

If I delete the geometry shader and just compile the vertex and fragment shaders together, the issue still occurs.

If I try and debug the program in RenderDoc, the error does not occur (!!).

I'm scratching my head here. The vertex shader looks near identical to other vertex shaders I'm using.

I've poured over every google result I could find, but still don't have any idea what's going on here. One thread suggested that, possibly, some part of the OpenGL state was compiled into the program when the shader was first loaded. Then, at some point the state was changed, triggering a recompile when the shader is first used to render something... But it didn't offer any solutions and I'm not really sure if/how this could be the case.

I'd appreciate any help I can get here!

EDIT: I have another 2D depth shader that I use to write depth for a key light that doesn't suffer this issue. If I completely replace the cube map depth shader code with the functional key light depth shader (and delete the geometry shader), the issue still persists.

If I don't render the keylight at all (making the cube map depth shader the only depth shader that gets executed), the issue persists.

The only difference between the cube and 2D depth map shaders is that the cube map depth shader is being rendered with cube map textures bound... Is it possible I'm screwing up their initialization somehow?

Here is my cube map setup:

bool RenderTexture::BufferCubeMap(RenderTexture** cubeFaceRTs)
{
    // Bind Texture:
    glBindTexture(GL_TEXTURE_CUBE_MAP, this->textureID);
    if (!glIsTexture(this->textureID))
    {
        glGenTextures(1, &this->textureID);
        glBindTexture(GL_TEXTURE_CUBE_MAP, this->textureID);

        if (! glIsTexture(this->textureID))
        {
            glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
            return false;
        }
    }

    // Generate faces:
    for (int i = 0; i < CUBE_MAP_COUNT; i++)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT32F, this->width, this->height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    }

    // Set texture params:
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);  

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    // Bind sampler:
    glBindSampler(12, this->samplerID);
    if (!glIsSampler(this->samplerID))
    {
        glGenSamplers(1, &this->samplerID);
        glBindSampler(12, this->samplerID);
    }

    // Set sampler params:
    glSamplerParameteri(this->samplerID, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glSamplerParameteri(this->samplerID, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);      
    glSamplerParameteri(this->samplerID, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    glSamplerParameteri(this->samplerID, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glSamplerParameteri(this->samplerID, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Bind framebuffer:
    glBindFramebuffer(GL_FRAMEBUFFER, this->frameBufferObject);
    if (!glIsFramebuffer(this->frameBufferObject))
    {
        glGenFramebuffers(1, &this->frameBufferObject);
        glBindFramebuffer(GL_FRAMEBUFFER, this->frameBufferObject);
        if (!glIsFramebuffer(this->frameBufferObject))
        {
            return false;
        }   
    }

    // Attach framebuffer as a cube map render buffer:
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, this->textureID, 0);

    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);

    // Ensure all of the textures have the correct information stored in them:
    for (int i = 0; i < 6; i++)
    {
        if (cubeFaceRTs[i] != this)
        {
            cubeFaceRTs[i]->textureID           = this->textureID;
            cubeFaceRTs[i]->samplerID           = this->samplerID;
            cubeFaceRTs[i]->frameBufferObject   = this->frameBufferObject;
        }
    }

    bool result = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
    if (!result)
    {
        LOG_ERROR("Cube map framebuffer is not complete!");
    }

    // Cleanup:
    glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
    glBindSampler(12, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0); 

    return result;
}

Finally, the cube map texture is bound with:

void RenderTexture::Bind(GLuint const& shaderReference)
{

        glActiveTexture(GL_TEXTURE0 + 12);
        glBindTexture(GL_TEXTURE_CUBE_MAP, this->textureID);
        glBindSampler(12, this->samplerID);
}

I've edited my code to make it as brief and readable as possible, hopefully I didn't make any typos...

$\endgroup$
4
$\begingroup$

This is an example of implementation details leaking through the abstraction. Parts of the GL state are often implemented via native GPU ISA instructions that the driver injects into user-defined shaders, such as fetching the geometry from vertex and index buffers, or interpolating VS outputs in the subsequent pipeline stages.

Since we're looking at the GPU driver's black box here, it's difficult to know which state change exactly is triggering this behaviour. But my guess would be that inserting a geometry shader stage into the pipeline is to blame. GS are notorious for poor performance due to the unpredictable workload (of course, we give it an upper bound, but still), and as such, the driver may choose to look for optimisation opportunities, looking at VS & GS together. This is what I think might be triggering a recompile. I just noticed that you've tested removing it from the pipeline and the problem persists. The other hypothesis I can offer is that you're overwriting fragment depth. The driver possibly needs to resolve this with the currently bound depth write state, and/or decide what to do about early depth tests (see the documentation about gl_fragDepth).

As long as you only get this once at application start, it should be completely safe to ignore. The driver will put the compiled shader in its cache and use it on subsequent occasions.

As to why you aren't getting it in RenderDoc - it suppresses debug messages while linking shaders and programs in the initial OpenGL state bring-up (i.e. state that existed before the captured frame), see its source code.

$\endgroup$
  • $\begingroup$ Thank you for the thoughtful reply. What you're saying makes sense, but it's bugging me that my other depth shaders don't have this issue. I tried copy/pasting the content of a shader that doesn't suffer this issue into the cube map depth shader (and deleting the geometry shader), and the problem persists, suggesting that it's not the shader text that's the issue. The only difference is that this shader is used with cube map textures attached. I've updated my original question to display my buffer code... Am I missing something obvious? $\endgroup$ – b1skit Aug 25 at 18:51
  • 1
    $\begingroup$ @b1skit Are the other shaders also writing to a 32F texture? One thing that can cause recompiles is the precision of the output format. Some hardware needs a different instruction to export at full 32F precision versus 8- or 16-bit. $\endgroup$ – Nathan Reed Aug 27 at 4:34
  • $\begingroup$ It seems like @b1skit has narrowed it down to being a cube map. Assuming that the driver conflates the vertex and geometry stages, the recompilation might depend on gl_Layer: it can be used to point at cube faces, as well as texture array slots. Perhaps that's it - different native ISA code for export based on whether a single texture or an array is bound? In any case, I don't think you're missing anything "obvious". It's a black box, you'd need to ask your IHV for a concrete explanation.¯_(ツ)_/¯ $\endgroup$ – IneQuation Aug 27 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.