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

OpenGLContext: Swap buffers on the main thread

This change fixes an issue where opening multiple OpenGLContexts on
certain versions of macOS (observed on 10.13) could cause a deadlock.

The issue can be reproduced by:
- Attaching an OpenGL context to the AudioPluginDemo editor
- Opening multiple copies of the editor simultaneously in a plugin host.
  I tested with Live 10.

I also observed the issue in a standalone app that opened new windows
containing OpenGLContexts on a timer.
This commit is contained in:
reuk 2022-08-26 14:14:18 +01:00
parent 322aa64459
commit 8ec8e36f5c
5 changed files with 150 additions and 37 deletions

View file

@ -299,7 +299,11 @@ public:
void surfaceDestroyed (LocalRef<jobject> holder) override;
//==============================================================================
struct Locker { Locker (NativeContext&) {} };
struct Locker
{
explicit Locker (NativeContext& ctx) : lock (ctx.mutex) {}
const ScopedLock lock;
};
Component& component;
@ -390,6 +394,7 @@ private:
}
//==============================================================================
CriticalSection mutex;
bool hasInitialised = false;
GlobalRef surfaceView;

View file

@ -198,9 +198,14 @@ public:
int getSwapInterval() const noexcept { return swapFrames; }
struct Locker { Locker (NativeContext&) {} };
struct Locker
{
explicit Locker (NativeContext& ctx) : lock (ctx.mutex) {}
const ScopedLock lock;
};
private:
CriticalSection mutex;
Component& component;
JuceGLView* view = nil;
CAEAGLLayer* glLayer = nil;

View file

@ -280,7 +280,11 @@ public:
context->triggerRepaint();
}
struct Locker { Locker (NativeContext&) {} };
struct Locker
{
explicit Locker (NativeContext& ctx) : lock (ctx.mutex) {}
const ScopedLock lock;
};
private:
bool tryChooseVisual (const OpenGLPixelFormat& format, const std::vector<GLint>& optionalAttribs)
@ -313,6 +317,7 @@ private:
static constexpr int embeddedWindowEventMask = ExposureMask | StructureNotifyMask;
CriticalSection mutex;
Component& component;
GLXContext renderContext = {};
Window embeddedWindow = {};

View file

@ -146,7 +146,11 @@ public:
context->triggerRepaint();
}
struct Locker { Locker (NativeContext&) {} };
struct Locker
{
explicit Locker (NativeContext& ctx) : lock (ctx.mutex) {}
const ScopedLock lock;
};
HWND getNativeHandle()
{
@ -366,6 +370,7 @@ private:
HWND hwnd;
};
CriticalSection mutex;
std::unique_ptr<DummyComponent> dummyComponent;
std::unique_ptr<ComponentPeer> nativeWindow;
std::unique_ptr<ScopedThreadDPIAwarenessSetter> threadAwarenessSetter;

View file

@ -191,6 +191,7 @@ public:
void pause()
{
signalJobShouldExit();
contextsWaitingForFlush->cancelFlush (this);
messageManagerLock.abort();
if (renderThread != nullptr)
@ -281,8 +282,7 @@ public:
{
MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
auto isUpdatingTestValue = true;
auto isUpdating = needsUpdate.compare_exchange_strong (isUpdatingTestValue, false);
const auto isUpdating = needsUpdate.exchange (false);
if (context.renderComponents && isUpdating)
{
@ -305,45 +305,49 @@ public:
if (! context.makeActive())
return false;
NativeContext::Locker locker (*nativeContext);
JUCE_CHECK_OPENGL_ERROR
doWorkWhileWaitingForLock (true);
const auto currentAreaAndScale = areaAndScale.get();
const auto viewportArea = currentAreaAndScale.area;
if (context.renderer != nullptr)
{
glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
context.currentRenderScale = currentAreaAndScale.scale;
context.renderer->renderOpenGL();
clearGLError();
NativeContext::Locker locker (*nativeContext);
bindVertexArray();
}
JUCE_CHECK_OPENGL_ERROR
if (context.renderComponents)
{
if (isUpdating)
doWorkWhileWaitingForLock (true);
const auto currentAreaAndScale = areaAndScale.get();
const auto viewportArea = currentAreaAndScale.area;
if (context.renderer != nullptr)
{
paintComponent (currentAreaAndScale);
glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
context.currentRenderScale = currentAreaAndScale.scale;
context.renderer->renderOpenGL();
clearGLError();
if (! hasInitialised)
return false;
messageManagerLock.exit();
lastMMLockReleaseTime = Time::getMillisecondCounter();
bindVertexArray();
}
glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
drawComponentBuffer();
if (context.renderComponents)
{
if (isUpdating)
{
paintComponent (currentAreaAndScale);
if (! hasInitialised)
return false;
messageManagerLock.exit();
lastMMLockReleaseTime = Time::getMillisecondCounter();
}
glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
drawComponentBuffer();
}
OpenGLContext::deactivateCurrentContext();
}
context.swapBuffers();
if (! shouldExit())
contextsWaitingForFlush->flush (this);
OpenGLContext::deactivateCurrentContext();
return true;
}
@ -769,6 +773,95 @@ public:
return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
}
//==============================================================================
class ContextsWaitingForFlush : private AsyncUpdater
{
public:
/* Ask to swap the CachedImage's buffers on the main thread.
Will block until the buffers have been swapped, or until the swap has been cancelled.
*/
void flush (CachedImage* ctx)
{
{
const std::lock_guard<std::mutex> lock (mutex);
if (find (ctx) == contexts.cend())
contexts.push_back (ctx);
}
if (MessageManager::getInstance()->isThisTheMessageThread())
{
handleAsyncUpdate();
}
else
{
triggerAsyncUpdate();
std::unique_lock<std::mutex> lock { mutex };
condvar.wait (lock, [this, ctx] { return find (ctx) == contexts.cend(); });
}
}
/* When a context is destroyed, this function must be called so that flush() can return. */
void cancelFlush (CachedImage* ctx)
{
const std::lock_guard<std::mutex> lock (mutex);
const auto iter = find (ctx);
if (iter != contexts.cend())
{
contexts.erase (iter);
condvar.notify_all();
}
}
~ContextsWaitingForFlush() override
{
cancelPendingUpdate();
// There definitely shouldn't still be active CachedImages if this object is being destroyed!
jassert (contexts.empty());
}
private:
std::mutex mutex;
std::condition_variable condvar;
std::vector<CachedImage*> contexts;
/* Precondition: mutex is locked. */
std::vector<CachedImage*>::const_iterator find (CachedImage* ctx) const
{
// Linear search here because the number of OpenGL contexts probably won't
// ever be bigger than double/triple digits
return std::find (contexts.cbegin(), contexts.cend(), ctx);
}
/* Swaps the buffers for each of the pending contexts, then notifies all rendering threads
that they may continue.
*/
void handleAsyncUpdate() override
{
std::unique_lock<std::mutex> lock (mutex);
for (auto* ctx : contexts)
{
auto& native = *ctx->nativeContext;
OpenGLContext::NativeContext::Locker nativeContextLocker (native);
native.makeActive();
native.swapBuffers();
native.deactivateCurrentContext();
}
contexts.clear();
condvar.notify_all();
}
};
SharedResourcePointer<ContextsWaitingForFlush> contextsWaitingForFlush;
//==============================================================================
friend class NativeContext;
std::unique_ptr<NativeContext> nativeContext;
@ -1345,8 +1438,8 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
struct BuiltProgram : public OpenGLShaderProgram
{
explicit BuiltProgram (OpenGLContext& context)
: OpenGLShaderProgram (context)
explicit BuiltProgram (OpenGLContext& ctx)
: OpenGLShaderProgram (ctx)
{
addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
"attribute " JUCE_HIGHP " vec2 position;"