Let’s say we have something like this, juce::OpenGLContext is used for accelerated drawing.
juce::Image framebuffer;
void someInitFunction()
{
// Create an OpenGL Framebuffer wrapped by a regular image
framebuffer = juce::Image(juce::OpenGLImageType().create(juce::Image::ARGB, newWidth, newHeight, true)
}
void paint(Graphics& g) override
{
// Draw Stuff with the normal context
g.fill..
g.draw..
// NOW! We also want to draw into the framebuffer
{
juce::Graphics fg(framebuffer);
fg.fill..
fg.draw..
}
g.draw..
}
The big problem here, by using a nested juce::Graphics and an OpenGL framebuffer, we change the state of the previous juce::Graphics. *This leads to corruption and and a wrong GL state!
With the recent JUCE OpenGL function change, and the possibility to call most state functions without a context, it should be an easy thing to avoid this problem. Just use a scoped state saver that records the current GL state and restores it if the scope is left.
void paint(Graphics& g) override
{
// Draw Stuff with the normal context
g.fill..
g.draw..
// Works!
{
gl::ScopedState ss(glContext); // <-- The savior
juce::Graphics fg(framebuffer);
fg.fill..
fg.draw
}
g.draw..
}
It’s now possible to draw into the framebuffer and change the GL state without corrupting the other draw calls.
What is gl::ScopedState ?
It could be something like this:
//==============================================================================
forcedinline static GLint getInteger(GLenum state) noexcept
{
GLint value = 0;
glGetIntegerv(state, &value);
return value;
}
//==============================================================================
struct ViewportState
{
ViewportState()
{
glGetIntegerv (GL_VIEWPORT, viewport.data());
}
~ViewportState()
{
glViewport (viewport[0], viewport[1], viewport[2], viewport[3]);
}
private:
std::array<GLint, 4> viewport;
};
//==============================================================================
struct BlendState
{
BlendState()
{
enabled = glIsEnabled(GL_BLEND);
src = (GLenum)getInteger(GL_BLEND_SRC);
dst = (GLenum)getInteger(GL_BLEND_DST);
}
~BlendState()
{
if (enabled)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
glBlendFunc (src, dst);
}
GLboolean enabled;
GLenum src;
GLenum dst;
};
//==============================================================================
struct TextureState
{
enum
{
maxBoundTextures = 4
};
TextureState()
{
activeUnit = static_cast<GLuint>((GLenum)getInteger(GL_ACTIVE_TEXTURE) - GL_TEXTURE0);
for (int i = 0; i < maxBoundTextures; ++i)
{
glActiveTexture(GL_TEXTURE0 + (GLenum) i);
glGetIntegerv (GL_TEXTURE_BINDING_2D, &boundIndices[i]);
}
}
~TextureState()
{
for (int i = 0; i < maxBoundTextures; ++i)
{
glActiveTexture(GL_TEXTURE0 + (GLenum) i);
glBindTexture(GL_TEXTURE_2D, boundIndices[i]);
}
glActiveTexture(GL_TEXTURE0 + (GLenum)activeUnit);
}
GLint boundIndices[maxBoundTextures];
GLuint activeUnit;
};
//==============================================================================
struct VertexAttribute
{
VertexAttribute() { }
~VertexAttribute()
{
glVertexAttribPointer(index, size, type, normalized, stride, pointer);
if (enabled)
glEnableVertexAttribArray(index);
else
glDisableVertexAttribArray(index);
}
void load()
{
enabled = static_cast<GLboolean>(getVertexAttributeInteger(GL_VERTEX_ATTRIB_ARRAY_ENABLED));
buffer = static_cast<GLuint>(getVertexAttributeInteger(GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING));
size = getVertexAttributeInteger(GL_VERTEX_ATTRIB_ARRAY_SIZE);
type = static_cast<GLenum>(getVertexAttributeInteger(GL_VERTEX_ATTRIB_ARRAY_TYPE));
normalized = static_cast<GLboolean>(getVertexAttributeInteger(GL_VERTEX_ATTRIB_ARRAY_NORMALIZED));
stride = static_cast<GLsizei>(getVertexAttributeInteger(GL_VERTEX_ATTRIB_ARRAY_STRIDE));
void* attribPointer = nullptr;
glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &attribPointer);
pointer = attribPointer;
}
forcedinline GLint getVertexAttributeInteger(GLenum state) noexcept
{
GLint value = 0;
glGetVertexAttribiv(index, state, &value);
return value;
}
GLuint index;
GLuint buffer;
GLint size;
GLenum type;
GLboolean normalized;
GLsizei stride;
const void* pointer;
GLboolean enabled;
};
//==============================================================================
struct ScopedState
{
ScopedState(juce::OpenGLContext& context_) : context(context_)
{
depthEnabled = glIsEnabled(GL_DEPTH_TEST);
boundArrayBuffer = getInteger(GL_ARRAY_BUFFER_BINDING);
boundElementArrayBuffer = getInteger(GL_ELEMENT_ARRAY_BUFFER_BINDING);
boundFramebuffer = getInteger(GL_DRAW_FRAMEBUFFER_BINDING);
currentProgram = getInteger(GL_CURRENT_PROGRAM);
vertexAttrib0.index = 0;
vertexAttrib0.load();
vertexAttrib1.index = 1;
vertexAttrib1.load();
vertexAttrib2.index = 2;
vertexAttrib2.load();
}
~ScopedState()
{
if (depthEnabled)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
glBindBuffer(GL_ARRAY_BUFFER, boundArrayBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, boundElementArrayBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, boundFramebuffer);
glUseProgram(currentProgram);
}
juce::OpenGLContext& context;
GLboolean depthEnabled;
GLuint boundArrayBuffer;
GLuint boundElementArrayBuffer;
GLuint boundFramebuffer;
GLuint currentProgram;
ViewportState viewport;
BlendState blend;
TextureState textures;
VertexAttribute vertexAttrib0;
VertexAttribute vertexAttrib1;
VertexAttribute vertexAttrib2;
};
I tested it, and it works! It’s really helpful and allows you to inject custom shaders into the regular juce::Component painting, instead of using the OpenGLRenderer and reinventing the wheel to draw stuff.
Now, the thing is. It is a bit hacky. Ideally, this should be provided by the juce_opengl module, and should be called in the implementation of the OpenGLGraphicsContext IF a user decides to inject custom GL calls or shaders.
Any thoughts?

