From fb4159c43653eba287874564b31a2c3385abede8 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 14 May 2025 17:24:56 +0100 Subject: [PATCH] OpenGL: Fix state restoration when drawing into a temporary nested context Fixes a regression introduced in bd26d79b178441eaebc6a8577a112ce2d0081293 The issue was observed in the DemoRunner when enabling the OpenGL renderer and then switching to the LookAndFeel V1. The cause of the problem was the creation of a secondary OpenGL-backed Graphics instance in the DropShadowEffect. This temporary context could modify the OpenGL context state without restoring the state appropriately on destruction. As a result, when the outer long-lived OpenGL context resumed drawing, properties such as the viewport, bound shader, shader uniform values, and bound framebuffer could all be incorrect. --- .../opengl/juce_OpenGLGraphicsContext.cpp | 112 ++++++++++++++++-- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp index 8cb49b1c3e..8fcb1babc7 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLGraphicsContext.cpp @@ -217,7 +217,6 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImageList) }; - //============================================================================== struct Target { @@ -359,7 +358,7 @@ private: //============================================================================== struct ShaderPrograms final : public ReferenceCountedObject { - ShaderPrograms (OpenGLContext& context) + explicit ShaderPrograms (OpenGLContext& context) : solidColourProgram (context), solidColourMasked (context), radialGradient (context), @@ -428,6 +427,13 @@ struct ShaderPrograms final : public ReferenceCountedObject screenBounds (program, "screenBounds") {} + Rectangle get2DBounds() const + { + GLfloat params[4]{}; + glGetUniformfv (program.getProgramID(), screenBounds.uniformID, params); + return { params[0], params[1], 2 * params[2], 2 * params[3] }; + } + void set2DBounds (Rectangle bounds) { screenBounds.set (bounds.getX(), bounds.getY(), 0.5f * bounds.getWidth(), 0.5f * bounds.getHeight()); @@ -452,6 +458,35 @@ struct ShaderPrograms final : public ReferenceCountedObject std::function onShaderActivated; }; + /* If the shader currently bound to the active context is owned by ShaderPrograms, this returns + the specific shader that is currently bound, or nullptr if none of the shaders match. + */ + ShaderBase* findActiveShader() + { + GLint program{}; + glGetIntegerv (GL_CURRENT_PROGRAM, &program); + + ShaderBase* ptrs[] { &solidColourProgram, + &solidColourMasked, + &radialGradient, + &radialGradientMasked, + &linearGradient1, + &linearGradient1Masked, + &linearGradient2, + &linearGradient2Masked, + &image, + &imageMasked, + &tiledImage, + &tiledImageMasked, + ©Texture, + &maskTexture }; + + const auto iter = std::find_if (std::begin (ptrs), + std::end (ptrs), + [&] (auto* x) { return (GLint) x->program.getProgramID() == program; }); + return iter != std::end (ptrs) ? *iter : nullptr; + } + struct MaskedShaderParams { MaskedShaderParams (OpenGLShaderProgram& program) @@ -1422,21 +1457,30 @@ struct StateHelpers //============================================================================== struct CurrentShader { - CurrentShader (OpenGLContext& c) noexcept : context (c) + explicit CurrentShader (OpenGLContext& c) + : context (c) { - auto programValueID = "GraphicsContextPrograms"; - programs = static_cast (context.getAssociatedObject (programValueID)); - - if (programs == nullptr) - { - programs = new ShaderPrograms (context); - context.setAssociatedObject (programValueID, programs.get()); - } } ~CurrentShader() { jassert (activeShader == nullptr); + + if (initialShader == nullptr) + return; + + initialShader->program.use(); + + // If there are multiple VAOs, then normally binding the previous VAO would also restore + // the shader attributes that were last used with that VAO. If there's just a single + // global VAO, we need to reset the attributes manually. + if (! TraitsVAO::shouldUseCustomVAO()) + initialShader->bindAttributes(); + + if (initialShader->onShaderActivated) + initialShader->onShaderActivated (initialShader->program); + + initialShader->set2DBounds (initialBounds); } void setShader (Rectangle bounds, ShaderQuadQueue& quadQueue, ShaderPrograms::ShaderBase& shader) @@ -1480,17 +1524,56 @@ struct StateHelpers } } + static constexpr auto programValueID = "GraphicsContextPrograms"; + OpenGLContext& context; - ShaderPrograms::Ptr programs; + ShaderPrograms::Ptr programs = std::invoke ([&] + { + if (ShaderPrograms::Ptr result { static_cast (context.getAssociatedObject (programValueID)) }) + return result; + + ShaderPrograms::Ptr newPrograms = new ShaderPrograms (context); + context.setAssociatedObject (programValueID, newPrograms.get()); + return newPrograms; + }); private: + // We store the original shader and bounds so that we can restore the previous + // when the CurrentShader is destroyed. + // Note that we do *not* set the active shader and bounds to their previous values. + // If a CurrentShader has been constructed, there's a good chance that a new VAO has + // also been constructed, in which case we'll need to call bindAttributes() the first + // time that a shader is used in this new VAO. + + ShaderPrograms::ShaderBase* initialShader = programs->findActiveShader(); ShaderPrograms::ShaderBase* activeShader = nullptr; + Rectangle initialBounds = initialShader != nullptr + ? initialShader->get2DBounds() + : Rectangle{}; Rectangle currentBounds; CurrentShader& operator= (const CurrentShader&); }; }; +//============================================================================== +class ViewportRestorer +{ +public: + ViewportRestorer() + { + glGetIntegerv (GL_VIEWPORT, bounds); + } + + ~ViewportRestorer() + { + glViewport (bounds[0], bounds[1], bounds[2], bounds[3]); + } + +private: + GLint bounds[4]; +}; + //============================================================================== struct GLState { @@ -1697,6 +1780,7 @@ struct GLState private: GLuint previousFrameBufferTarget; SavedBinding savedVAOBinding; + ViewportRestorer viewportRestorer; }; //============================================================================== @@ -1829,7 +1913,7 @@ private: //============================================================================== struct ShaderContext final : public RenderingHelpers::StackBasedLowLevelGraphicsContext { - ShaderContext (const Target& target) : glState (target) + explicit ShaderContext (const Target& target) : glState (target) { stack.initialise (new SavedState (&glState)); } @@ -1869,6 +1953,8 @@ struct NonShaderContext final : public LowLevelGraphicsSoftwareRenderer clearGLError(); #endif + ViewportRestorer viewportRestorer; + OpenGLTexture texture; texture.loadImage (image); texture.bind();