Part of output not visible when 2D drawing with OpenGL context

Hidiho,

some parts of the 2D graphics I draw using juce::Graphics with an internal OpenGL graphics context are not visible on screen. It happens both when drawing directly to the screen by creating a LowLevelGraphicsContext with juce::createOpenGLGraphicsContext and passing it to a Graphics constructor, and also when doing offscreen rendering by creating an Image with OpenGLImageType and passing that to a Graphics constructor (and drawing the image afterward using drawImage).

What I observe is that the “last part of the last primitive” is missing. For testing I’m drawing 4 differently colored ellipses. When I resize the window such that whole primitives are being culled away, only the last visible one is affected. I hope the following screenshots make this more clear.



When I switch the primitive from using fillEllipse to fillRect I do not see any output at all.

I dug a little bit into the code and checked which OpenGL calls are being made with RenderDoc. It seems that the primitives on the OpenGL side are drawn in batches of 1536 using glDrawElements, and for some reason the draw command of the last batch is not issued.

I suspect that there might be a flush of the quad queue missing or getting lost somewhere. If I wrap my rendering calls in a begin/endTransparencyLayer, which will force a flush as far as I can see, the problems disappear.

If it helps I can provide a minimal example to reproduce the problem.

Thank you and best regards,
Patric

That would definitely be helpful, and should allow us to take a look at this more quickly.

Hi reuk, I updated the example here: GitHub - drlight-code/juce-opengl-minimal

In the stripped down example, I can currently only observe the problem when rendering offscreen. In my application, where more things are being drawn, I could however also observe missing primitive parts using the “onscreen” method.

You will find three boolean variables to test the different variants, drawOnScreen, drawRect and useLayer. I currently observe the following:

useLayer == false && drawOnScreen == false:
missing parts of ellipse if drawRect is false
no output at all if drawRect it true

(useLayer || drawOnScreen) == true:
works as expected

I hope this helps, please ask if there should be anything unclear.

Thank you!

When drawing to an OpenGL-backed context, the image isn’t guaranteed to be completely drawn until the Graphics object has been destroyed. I recommend trying to limit the lifetimes of temporary Graphics instances so that they have been destroyed before reading the context’s image:

When I introduce a local scope to make sure the gFBO Graphics object’s lifetime ends before I draw the image, I get an invalid operation error from OpenGL.

GL_INVALID_OPERATION error generated. Target buffer must be bound and not overlapped with mapping range.

I updated the working example to include the different cases that I tried, see GLComponent.cc:79 for the enum that switches between the drawing modes. Directly rendering to screen works, drawing into the Image with OpenGLImageType in two different ways behaves as described above (i.e. seeing the whole output only when using a transparency layer), drawing the image after the Graphics object lifetime ends leads to GL_INVALID_OPERATION.

I experimented a bit and indeed, a forced flush() on the glState of the ShaderContext, right after I draw my content and before I draw the image, resolves the issue for me. It in turn flushes the QuadQueue and leads to a complete output image.

To clarify, the issue itself is not “resolved”, I just realize that statement was confusing. I just found that the source of the problem is indeed a missing flush of the quad queue. I hacked this into the JUCE implementation for testing, and it would have to be properly integrated into the graphics context implementation, such that a flush will be issued e.g. before the Image that wraps the FBO is read. AFAICT the flush operation is not exposed in the public API and should probably not be required to be explicitly performed by the user.

Please make sure you’re running the latest JUCE develop branch. There was a commit that improved book-keeping of bound VAOs and buffers recently:

Also, I think the manual FBO manipulation you’re doing is unnecessary, and is likely to lead to problems.

On my system, and on develop, this renders correctly and doesn’t raise any GL errors:

{
    juce::Graphics g (*_imageBlend);
    drawBlobs (g);
}

graphics.get().drawImage (*_imageBlend, _boundsRender.toFloat());

When creating a Graphics instance that draws into a GL-backed Image, the Graphics instance automatically saves the previously-bound FBO and binds the Image’s FBO. When the Graphics instance is destroyed, it re-binds the previously-bound FBO.

Manually binding a different framebuffer before creating the Graphics instance will mean that the wrong framebuffer will be bound when the Graphics instance goes out of scope.

{
    auto fbo = juce::OpenGLImageType::getFrameBufferFrom(*_imageBlend); 
    fbo->makeCurrentAndClear();  

    // Saves the framebuffer ID of imageBlend as the previous framebuffer 
    juce::Graphics gFBO{*_imageBlend}; 
}
// ~Graphics re-binds the previous framebuffer, which is _imageBlend's!

You can get around this by making sure you rebind the correct framebuffer when Graphics goes out of scope. Again, this should rarely be necessary because juce::Graphics already does something very similar internally.

const auto lastFramebuffer = juce::OpenGLFrameBuffer::getCurrentFrameBufferTarget();
... // Mess around with framebuffer state
// Put the last framebuffer back before rendering anything else with JUCE
juce::gl::glBindFramebuffer (GL_FRAMEBUFFER, lastFramebuffer);

Additionally, this line will cause the partial-drawing issue you were seeing:

{  
    ...
    juce::Graphics gFBO{*_imageBlend}; 
    drawBlobs(gFBO);  
    fbo->releaseAsRenderingTarget();  // Problematic!
}

As explained above, the image isn’t guaranteed to be completely drawn until the Graphics context has been destroyed. Unbinding the rendering target before the Graphics destructor has run means that any pending draw operations that are flushed during the destructor won’t be drawn to the correct target. You can fix this by just removing that line.

Wonderful, thanks for the help and thorough explanation!

That commit indeed came a day after I observed the problem and opened this thread. I should have checked back in the meantime!

Switching to the latest develop as of today I can confirm that the issue is indeed resolved. It is no longer necessary to bind the FBO explicitly and using the local scope for the Graphics object does lead to a complete draw!

I just checked back, when I don’t bind the FBO myself using the version from June 5th, I do not see any output at all, that’s why I figured binding (and unbinding) it myself is necessary, which in turn led to the problem with the scoped Graphics object.

Thank you! <3