OpenGL - ScopedState Idea

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?

3 Likes

We something similar to that ViewportState in our in-house codebase for quite a bit of time. Works great. However, looking at your code it seems a bit hacky to take the address of the first struct member to fill the whole struct, although it will possibly work. Why not using an array like

class ViewportState
{
public:
    ViewportState()
    {
        glGetIntegerv (GL_VIEWPORT, viewport.data());
    }

    ~ViewportState()
    {
        glViewport (viewport[0], viewport[1], viewport[2], viewport[3]);
    }

private:
    std::array<GLint, 4> viewport;
};
1 Like

Yes, probably safer. I will change it.

I took a quick glance over your OpenGLRealtimeVisualization4JUCE repository on github.
Since it’s related, do you have experience injecting custom shaders into the regular component painting? I mean without relying on the OpenGLRenderer::render() stuff? Or have you at least considered it?

Something like:

void paint(Graphics& g) override
{
  gl::ScopedState ss(glContext);

  {
    ExtendedGraphics eg(glContext);
    eg.drawCustomStuff(image, parameters);
  }
}

Where ExtendedGraphics is a custom shader that could apply all kinds of visual effects. Like Blur, or BlendModes?

No, I never considered something like that.

My repository was my initial project to get started with GL back then and I did some more GL work later, but for quite a while now I haven’t implemented any GL stuff myself anymore because I’ve become more focused on non-GUI related stuff in the meantime, so I’m maybe not the best person to discuss such advanced GL stuff with :wink: