diff --git a/modules/juce_events/messages/juce_MessageManager.cpp b/modules/juce_events/messages/juce_MessageManager.cpp index de042358cb..afaf570cae 100644 --- a/modules/juce_events/messages/juce_MessageManager.cpp +++ b/modules/juce_events/messages/juce_MessageManager.cpp @@ -287,16 +287,24 @@ public: //============================================================================== MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) - : blockingMessage(), locked (attemptLock (threadToCheck, nullptr)) + : blockingMessage(), checker (threadToCheck, nullptr), + locked (attemptLock (threadToCheck != nullptr ? &checker : nullptr)) { } MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) - : blockingMessage(), locked (attemptLock (nullptr, jobToCheckForExitSignal)) + : blockingMessage(), checker (nullptr, jobToCheckForExitSignal), + locked (attemptLock (jobToCheckForExitSignal != nullptr ? &checker : nullptr)) { } -bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob* const job) +MessageManagerLock::MessageManagerLock (BailOutChecker& bailOutChecker) + : blockingMessage(), checker (nullptr, nullptr), + locked (attemptLock (&bailOutChecker)) +{ +} + +bool MessageManagerLock::attemptLock (BailOutChecker* bailOutChecker) { MessageManager* const mm = MessageManager::instance; @@ -306,7 +314,7 @@ bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob if (mm->currentThreadHasLockedMessageManager()) return true; - if (threadToCheck == nullptr && job == nullptr) + if (bailOutChecker == nullptr) { mm->lockingLock.enter(); } @@ -314,8 +322,7 @@ bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob { while (! mm->lockingLock.tryEnter()) { - if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) - || (job != nullptr && job->shouldExit())) + if (bailOutChecker->shouldAbortAcquiringLock()) return false; Thread::yield(); @@ -332,8 +339,7 @@ bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob while (! blockingMessage->lockedEvent.wait (20)) { - if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) - || (job != nullptr && job->shouldExit())) + if (bailOutChecker != nullptr && bailOutChecker->shouldAbortAcquiringLock()) { blockingMessage->releaseEvent.signal(); blockingMessage = nullptr; @@ -367,6 +373,19 @@ MessageManagerLock::~MessageManagerLock() noexcept } } +//============================================================================== +MessageManagerLock::ThreadChecker::ThreadChecker (Thread* const threadToUse, + ThreadPoolJob* const threadJobToUse) + : threadToCheck (threadToUse), job (threadJobToUse) +{ +} + +bool MessageManagerLock::ThreadChecker::shouldAbortAcquiringLock() +{ + return (threadToCheck != nullptr && threadToCheck->threadShouldExit()) + || (job != nullptr && job->shouldExit()); +} + //============================================================================== JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() diff --git a/modules/juce_events/messages/juce_MessageManager.h b/modules/juce_events/messages/juce_MessageManager.h index 762b075ebd..f83c2c2a5d 100644 --- a/modules/juce_events/messages/juce_MessageManager.h +++ b/modules/juce_events/messages/juce_MessageManager.h @@ -35,6 +35,10 @@ class ThreadPoolJob; class ActionListener; class ActionBroadcaster; +//============================================================================== +#if JUCE_MODULE_AVAILABLE_juce_opengl +class OpenGLContext; +#endif //============================================================================== /** See MessageManager::callFunctionOnMessageThread() for use of this function type. */ @@ -312,6 +316,22 @@ public: */ MessageManagerLock (ThreadPoolJob* jobToCheckForExitSignal); + //============================================================================== + struct BailOutChecker + { + virtual ~BailOutChecker() {} + + /** Return true if acquiring the lock should be aborted. */ + virtual bool shouldAbortAcquiringLock() = 0; + }; + + /** This is an abstraction of the other constructors. You can pass this constructor + a functor which is periodically checked if attempting the lock should be aborted. + + See the MessageManagerLock (Thread*) constructor for details on how this works. + */ + MessageManagerLock (BailOutChecker&); + //============================================================================== /** Releases the current thread's lock on the message manager. @@ -331,9 +351,22 @@ private: class BlockingMessage; friend class ReferenceCountedObjectPtr; ReferenceCountedObjectPtr blockingMessage; + + struct ThreadChecker : BailOutChecker + { + ThreadChecker (Thread* const, ThreadPoolJob* const); + bool shouldAbortAcquiringLock() override; + + Thread* const threadToCheck; + ThreadPoolJob* const job; + }; + + //============================================================================== + ThreadChecker checker; bool locked; - bool attemptLock (Thread*, ThreadPoolJob*); + //============================================================================== + bool attemptLock (BailOutChecker*); JUCE_DECLARE_NON_COPYABLE (MessageManagerLock) }; diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index 6fd4feb91c..c1e421097c 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -110,6 +110,10 @@ public: { if (renderThread != nullptr) { + // make sure everything has finished executing + destroying.set (1); + execute (new DoNothingWorker(), true, true); + pause(); renderThread = nullptr; } @@ -207,11 +211,13 @@ public: if (context.renderComponents && isUpdating) { + MessageLockWorker worker (*this); + // This avoids hogging the message thread when doing intensive rendering. if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter()) Thread::sleep (2); - mmLock = new MessageManagerLock (this); // need to acquire this before locking the context. + mmLock = new MessageManagerLock (worker); // need to acquire this before locking the context. if (! mmLock->lockWasGained()) return false; @@ -225,6 +231,8 @@ public: JUCE_CHECK_OPENGL_ERROR + doWorkWhileWaitingForLock (true); + if (context.renderer != nullptr) { glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight()); @@ -501,12 +509,97 @@ public: nativeContext->shutdownOnRenderThread(); } + //============================================================================== + struct MessageLockWorker : MessageManagerLock::BailOutChecker + { + MessageLockWorker (CachedImage& cachedImageRequestingLock) + : owner (cachedImageRequestingLock) + { + } + + bool shouldAbortAcquiringLock() override { return owner.doWorkWhileWaitingForLock (false); } + + CachedImage& owner; + JUCE_DECLARE_NON_COPYABLE(MessageLockWorker) + }; + + //============================================================================== + struct BlockingWorker : OpenGLContext::AsyncWorker + { + BlockingWorker (OpenGLContext::AsyncWorker::Ptr && workerToUse) + : originalWorker (static_cast (workerToUse)) + {} + + void operator() (OpenGLContext& calleeContext) + { + if (originalWorker != nullptr) + (*originalWorker) (calleeContext); + + finishedSignal.signal(); + } + + void block() { finishedSignal.wait (); } + + OpenGLContext::AsyncWorker::Ptr originalWorker; + WaitableEvent finishedSignal; + }; + + bool doWorkWhileWaitingForLock (bool contextIsAlreadyActive) + { + bool contextActivated = false; + + for (OpenGLContext::AsyncWorker::Ptr work = workQueue.removeAndReturn (0); + work != nullptr && (! shouldExit()); work = workQueue.removeAndReturn (0)) + { + if ((! contextActivated) && (! contextIsAlreadyActive)) + { + if (! context.makeActive()) + break; + + contextActivated = true; + } + + NativeContext::Locker locker (*nativeContext); + + (*work) (context); + clearGLError(); + } + + if (contextActivated) + OpenGLContext::deactivateCurrentContext(); + + return shouldExit(); + } + + void execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock, bool calledFromDestructor = false) + { + if (calledFromDestructor || destroying.get() == 0) + { + BlockingWorker* blocker = (shouldBlock ? new BlockingWorker (static_cast (workerToUse)) : nullptr); + OpenGLContext::AsyncWorker::Ptr worker = (blocker != nullptr ? blocker : static_cast (workerToUse)); + workQueue.add (worker); + + if (blocker != nullptr) + blocker->block(); + } + else + jassertfalse; // you called execute AFTER you detached your openglcontext + } + //============================================================================== static CachedImage* get (Component& c) noexcept { return dynamic_cast (c.getCachedComponentImage()); } + //============================================================================== + // used to push no work on to the gl thread to easily block + struct DoNothingWorker : OpenGLContext::AsyncWorker + { + DoNothingWorker() {} + void operator() (OpenGLContext&) override {} + }; + //============================================================================== ScopedPointer nativeContext; @@ -526,10 +619,11 @@ public: WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent; bool shadersAvailable, hasInitialised; - Atomic needsUpdate; + Atomic needsUpdate, destroying; uint32 lastMMLockReleaseTime; ScopedPointer renderThread; + ReferenceCountedArray workQueue; #if JUCE_IOS iOSBackgroundProcessCheck backgroundProcessCheck; @@ -909,6 +1003,14 @@ void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObjec void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; } size_t OpenGLContext::getImageCacheSize() const noexcept { return imageCacheMaxSize; } +void OpenGLContext::execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock) +{ + if (CachedImage* const c = getCachedImage()) + c->execute (static_cast (workerToUse), shouldBlock); + else + jassertfalse; // You must have attached the context to a component +} + //============================================================================== struct DepthTestDisabler { diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.h b/modules/juce_opengl/opengl/juce_OpenGLContext.h index c1c8fb8f76..3775920ced 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.h +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.h @@ -217,6 +217,26 @@ public: */ int getSwapInterval() const; + //============================================================================== + /** Execute a lambda, function or functor on the OpenGL thread with an active + context. + + This method will attempt to execute functor on the OpenGL thread. If + blockUntilFinished is true then the method will block until the functor + has finished executing. + + This function can only be called if the context is attached to a component. + Otherwise, this function will assert. + + This function is useful when you need to excute house-keeping tasks such + as allocating, deallocating textures or framebuffers. As such, the functor + will execute without locking the message thread. Therefore, it is not + intended for any drawing commands or GUI code. Any GUI code should be + executed in the OpenGLRenderer::renderOpenGL callback instead. + */ + template + void executeOnGLThread (T&& functor, bool blockUntilFinished); + //============================================================================== /** Returns the scale factor used by the display that is being rendered. @@ -288,7 +308,33 @@ private: size_t imageCacheMaxSize; bool renderComponents, useMultisampling, continuousRepaint; + //============================================================================== + struct AsyncWorker : ReferenceCountedObject + { + typedef ReferenceCountedObjectPtr Ptr; + virtual void operator() (OpenGLContext&) = 0; + virtual ~AsyncWorker() {} + }; + + template + struct AsyncWorkerFunctor : AsyncWorker + { + AsyncWorkerFunctor (T functorToUse) : functor (functorToUse) {} + void operator() (OpenGLContext& callerContext) override { functor (callerContext); } + T functor; + + JUCE_DECLARE_NON_COPYABLE(AsyncWorkerFunctor) + }; + + //============================================================================== CachedImage* getCachedImage() const noexcept; + void execute (AsyncWorker::Ptr, bool); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLContext) }; + +//============================================================================== +#ifndef DOXYGEN +template +void OpenGLContext::executeOnGLThread (T&& f, bool shouldBlock) { execute (new AsyncWorkerFunctor (f), shouldBlock); } +#endif