diff --git a/modules/juce_opengl/native/juce_OpenGL_android.h b/modules/juce_opengl/native/juce_OpenGL_android.h index d6a642c55f..20fbb741ec 100644 --- a/modules/juce_opengl/native/juce_OpenGL_android.h +++ b/modules/juce_opengl/native/juce_OpenGL_android.h @@ -299,7 +299,11 @@ public: void surfaceDestroyed (LocalRef 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; diff --git a/modules/juce_opengl/native/juce_OpenGL_ios.h b/modules/juce_opengl/native/juce_OpenGL_ios.h index c908ddf27a..98c83c4692 100644 --- a/modules/juce_opengl/native/juce_OpenGL_ios.h +++ b/modules/juce_opengl/native/juce_OpenGL_ios.h @@ -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; diff --git a/modules/juce_opengl/native/juce_OpenGL_linux_X11.h b/modules/juce_opengl/native/juce_OpenGL_linux_X11.h index 8236ac1588..f4fe4d20b8 100644 --- a/modules/juce_opengl/native/juce_OpenGL_linux_X11.h +++ b/modules/juce_opengl/native/juce_OpenGL_linux_X11.h @@ -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& optionalAttribs) @@ -313,6 +317,7 @@ private: static constexpr int embeddedWindowEventMask = ExposureMask | StructureNotifyMask; + CriticalSection mutex; Component& component; GLXContext renderContext = {}; Window embeddedWindow = {}; diff --git a/modules/juce_opengl/native/juce_OpenGL_win32.h b/modules/juce_opengl/native/juce_OpenGL_win32.h index b38a745734..8833dcccdc 100644 --- a/modules/juce_opengl/native/juce_OpenGL_win32.h +++ b/modules/juce_opengl/native/juce_OpenGL_win32.h @@ -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; std::unique_ptr nativeWindow; std::unique_ptr threadAwarenessSetter; diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index 220f5a1991..9154ad33b4 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -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 (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 lock (mutex); + + if (find (ctx) == contexts.cend()) + contexts.push_back (ctx); + } + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + handleAsyncUpdate(); + } + else + { + triggerAsyncUpdate(); + + std::unique_lock 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 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 contexts; + + /* Precondition: mutex is locked. */ + std::vector::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 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; + //============================================================================== friend class NativeContext; std::unique_ptr nativeContext; @@ -1345,8 +1438,8 @@ void OpenGLContext::copyTexture (const Rectangle& 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;"