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:
parent
322aa64459
commit
8ec8e36f5c
5 changed files with 150 additions and 37 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue