1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

OpenGL: Fix state restoration when drawing into a temporary nested context

Fixes a regression introduced in
bd26d79b17

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.
This commit is contained in:
reuk 2025-05-14 17:24:56 +01:00
parent 00836d1e94
commit fb4159c436
No known key found for this signature in database

View file

@ -217,7 +217,6 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImageList) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImageList)
}; };
//============================================================================== //==============================================================================
struct Target struct Target
{ {
@ -359,7 +358,7 @@ private:
//============================================================================== //==============================================================================
struct ShaderPrograms final : public ReferenceCountedObject struct ShaderPrograms final : public ReferenceCountedObject
{ {
ShaderPrograms (OpenGLContext& context) explicit ShaderPrograms (OpenGLContext& context)
: solidColourProgram (context), : solidColourProgram (context),
solidColourMasked (context), solidColourMasked (context),
radialGradient (context), radialGradient (context),
@ -428,6 +427,13 @@ struct ShaderPrograms final : public ReferenceCountedObject
screenBounds (program, "screenBounds") screenBounds (program, "screenBounds")
{} {}
Rectangle<float> 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<float> bounds) void set2DBounds (Rectangle<float> bounds)
{ {
screenBounds.set (bounds.getX(), bounds.getY(), 0.5f * bounds.getWidth(), 0.5f * bounds.getHeight()); 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<void (OpenGLShaderProgram&)> onShaderActivated; std::function<void (OpenGLShaderProgram&)> 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,
&copyTexture,
&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 struct MaskedShaderParams
{ {
MaskedShaderParams (OpenGLShaderProgram& program) MaskedShaderParams (OpenGLShaderProgram& program)
@ -1422,21 +1457,30 @@ struct StateHelpers
//============================================================================== //==============================================================================
struct CurrentShader struct CurrentShader
{ {
CurrentShader (OpenGLContext& c) noexcept : context (c) explicit CurrentShader (OpenGLContext& c)
: context (c)
{ {
auto programValueID = "GraphicsContextPrograms";
programs = static_cast<ShaderPrograms*> (context.getAssociatedObject (programValueID));
if (programs == nullptr)
{
programs = new ShaderPrograms (context);
context.setAssociatedObject (programValueID, programs.get());
}
} }
~CurrentShader() ~CurrentShader()
{ {
jassert (activeShader == nullptr); 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<int> bounds, ShaderQuadQueue& quadQueue, ShaderPrograms::ShaderBase& shader) void setShader (Rectangle<int> bounds, ShaderQuadQueue& quadQueue, ShaderPrograms::ShaderBase& shader)
@ -1480,17 +1524,56 @@ struct StateHelpers
} }
} }
static constexpr auto programValueID = "GraphicsContextPrograms";
OpenGLContext& context; OpenGLContext& context;
ShaderPrograms::Ptr programs; ShaderPrograms::Ptr programs = std::invoke ([&]
{
if (ShaderPrograms::Ptr result { static_cast<ShaderPrograms*> (context.getAssociatedObject (programValueID)) })
return result;
ShaderPrograms::Ptr newPrograms = new ShaderPrograms (context);
context.setAssociatedObject (programValueID, newPrograms.get());
return newPrograms;
});
private: 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; ShaderPrograms::ShaderBase* activeShader = nullptr;
Rectangle<float> initialBounds = initialShader != nullptr
? initialShader->get2DBounds()
: Rectangle<float>{};
Rectangle<int> currentBounds; Rectangle<int> currentBounds;
CurrentShader& operator= (const CurrentShader&); 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 struct GLState
{ {
@ -1697,6 +1780,7 @@ struct GLState
private: private:
GLuint previousFrameBufferTarget; GLuint previousFrameBufferTarget;
SavedBinding<TraitsVAO> savedVAOBinding; SavedBinding<TraitsVAO> savedVAOBinding;
ViewportRestorer viewportRestorer;
}; };
//============================================================================== //==============================================================================
@ -1829,7 +1913,7 @@ private:
//============================================================================== //==============================================================================
struct ShaderContext final : public RenderingHelpers::StackBasedLowLevelGraphicsContext<SavedState> struct ShaderContext final : public RenderingHelpers::StackBasedLowLevelGraphicsContext<SavedState>
{ {
ShaderContext (const Target& target) : glState (target) explicit ShaderContext (const Target& target) : glState (target)
{ {
stack.initialise (new SavedState (&glState)); stack.initialise (new SavedState (&glState));
} }
@ -1869,6 +1953,8 @@ struct NonShaderContext final : public LowLevelGraphicsSoftwareRenderer
clearGLError(); clearGLError();
#endif #endif
ViewportRestorer viewportRestorer;
OpenGLTexture texture; OpenGLTexture texture;
texture.loadImage (image); texture.loadImage (image);
texture.bind(); texture.bind();