Search code examples
opengltexturesframebufferopengl-4feedback-loop

OpenGL Reading from a texture unit currently bound to a framebuffer


I've encountered an issue trying to read data from a texture unit currently attached to the draw framebuffer. The errors are removed as long as i use a glTextureBarrier between the draw calls. However I'm trying to eliminate draw calls so this is an unoptimal solution. The OpenGL 4.5 specification (section 9.3 Feedback Loops Between Textures and the Framebuffer) states that

The mechanisms for attaching textures to a framebuffer object do not prevent a one- or two-dimensional texture level, a face of a cube map texture level, or a layer of a two-dimensional array or three-dimensional texture from being attached to the draw framebuffer while the same texture is bound to a texture unit. While this condition holds, texturing operations accessing that image will produce unde-fined results, as described at the end of section 8.14. ... Specifically, the values of rendered fragments are undefined if any shader stage fetches texels and the same texels are written via fragment shader outputs, even if the reads and writes are not in the same draw call, unless any of the following exceptions apply:

The reads and writes are from/to disjoint sets of texels (after accounting for texture filtering rules).

There is only a single read and write of each texel, and the read is in the fragment shader invocation that writes the same texel (e.g. using texelFetch2D(sampler, ivec2(gl_FragCoord.xy), 0);).

If a texel has been written, then in order to safely read the result a texel fetch must be in a subsequent draw call separated by the command void TextureBarrier( void ); TextureBarrier will guarantee that writes have completed and caches have been invalidated before subsequent draw calls are executed.

I do this to 4 other textures without problems. For these textures I only do one read and one write from the same texel, so they are covered by the 2nd exception. The texture causing the issue requires some filtering, thus I need more than one read and from different texels before writing. I figured this could be allowed through the 1st exception, so I put them in an array texture alternating which layer was read and written to. The idea was that this would create a setting where the reads and writes are from/to disjoint set of texels. It didn't help.

I also tried to do a glTextureBarrier only every other draw call to see if it was the third draw call that was causing the issue. This gave different (Still wrong) results from when I used barriers all the time and when i didn't.

The draw calls are drawing one point in 0,0,0 that is exploded to a fullscreen quad in a geometry shader.

Update

I am raytracing through volume data.

Framebuffer Setup

    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rayPositionTexture, 0);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, rayDirectionTexture, 0);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, IORTexture, 0);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, resultTexture, 0);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT4, rayOffsetTexture, 0, 0);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT5, rayOffsetTexture, 0, 1);

Shader

...
uniform sampler2D RayPositionTexture;
uniform sampler2D RayDirectionTexture;
uniform sampler2DArray RayOffsetTexture;
uniform sampler2D IORTexture;
uniform sampler2D ColorTexture;
...
flat in uint offsetIndex;
layout(location = 0) out vec4  outRayPosition;
layout(location = 1) out vec4  outRayDirection;
layout(location = 2) out float outIOR;
layout(location = 3) out vec4  outColor;
layout(location = 4) out vec4  outRayOffsetA;
layout(location = 5) out vec4  outRayOffsetB;
...
vec3 filteredOffset(ivec2 Pixel)
{
    vec3 Offset = vec3(0.0);
    float TotalWeight = 0.0;
    for(int i = -KernelRadius; i <= KernelRadius; i++)
    {
        for (int j = -KernelRadius; j <= KernelRadius; j++)
        {
            ivec2 SampleOffset = ivec2(i,j);
            ivec3 SampleLocation = ivec3(Pixel + SampleOffset, offsetIndex);
            vec3  Sample = texelFetch(RayOffsetTexture, SampleLocation, 0).xyz;
            float Weight = KernelRadius > 0 ? gauss(SampleOffset) : 1.0f;

            Offset += Sample * Weight;
            TotalWeight += Weight;
        }
    }
    Offset = Offset / TotalWeight;
    return Offset;
}
...
        //if (offsetIndex == 1)
        outRayOffsetA = vec4(RayDirection.xyz - RayDirectionNew, 0.0);
        //else
        outRayOffsetB = vec4(RayDirection.xyz - RayDirectionNew, 0.0);
        outIOR = IOR;
    } else {
        // if (offsetIndex == 1)
        outRayOffsetA = vec4(0.0);
        // else
        outRayOffsetB = vec4(0.0);
        outIOR = PreviousIOR;
        //imageStore(VolumeBackTexture, Pixel, vec4(1.0));
    }
    outColor = Color;
...

Draw calls

GLenum drawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 };
glDrawBuffers(6, drawBuffers);

// Run shader
glBindVertexArray(pointVAO);
float distance = 0.0f;
for (int i = 0; distance < 1.73205080757f; i++)
{
    glTextureBarrier();
    glDrawArrays(GL_POINTS, i, 1); 
    distance += fUnitStep;
}
glBindVertexArray(0);

The above without the texture barrier gives the same results (wrong result) as

glBindVertexArray(pointVAO);
glDrawArrays(GL_POINTS, 0, int(std::ceil(1.73205080757f / fUnitStep)));
glBindVertexArray(0); 

Solution

  • To answer this, the TextureBarrier is required here because I wanted to read from texels that had just been drawn. This can only be done safely if the previous draw call has completed and texture caches have been invalidated, which is exactly what TextureBarrier guarantees.