| @@ -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() | |||
| @@ -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<BlockingMessage>; | |||
| ReferenceCountedObjectPtr<BlockingMessage> 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) | |||
| }; | |||
| @@ -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<OpenGLContext::AsyncWorker::Ptr&&> (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<OpenGLContext::AsyncWorker::Ptr&&> (workerToUse)) : nullptr); | |||
| OpenGLContext::AsyncWorker::Ptr worker = (blocker != nullptr ? blocker : static_cast<OpenGLContext::AsyncWorker::Ptr&&> (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<CachedImage*> (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> nativeContext; | |||
| @@ -526,10 +619,11 @@ public: | |||
| WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent; | |||
| bool shadersAvailable, hasInitialised; | |||
| Atomic<int> needsUpdate; | |||
| Atomic<int> needsUpdate, destroying; | |||
| uint32 lastMMLockReleaseTime; | |||
| ScopedPointer<ThreadPool> renderThread; | |||
| ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> 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<OpenGLContext::AsyncWorker::Ptr&&> (workerToUse), shouldBlock); | |||
| else | |||
| jassertfalse; // You must have attached the context to a component | |||
| } | |||
| //============================================================================== | |||
| struct DepthTestDisabler | |||
| { | |||
| @@ -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 <typename T> | |||
| 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<AsyncWorker> Ptr; | |||
| virtual void operator() (OpenGLContext&) = 0; | |||
| virtual ~AsyncWorker() {} | |||
| }; | |||
| template <typename T> | |||
| 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 <typename T> | |||
| void OpenGLContext::executeOnGLThread (T&& f, bool shouldBlock) { execute (new AsyncWorkerFunctor<T> (f), shouldBlock); } | |||
| #endif | |||