| @@ -287,16 +287,24 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) | 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) | 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; | MessageManager* const mm = MessageManager::instance; | ||||
| @@ -306,7 +314,7 @@ bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob | |||||
| if (mm->currentThreadHasLockedMessageManager()) | if (mm->currentThreadHasLockedMessageManager()) | ||||
| return true; | return true; | ||||
| if (threadToCheck == nullptr && job == nullptr) | |||||
| if (bailOutChecker == nullptr) | |||||
| { | { | ||||
| mm->lockingLock.enter(); | mm->lockingLock.enter(); | ||||
| } | } | ||||
| @@ -314,8 +322,7 @@ bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob | |||||
| { | { | ||||
| while (! mm->lockingLock.tryEnter()) | while (! mm->lockingLock.tryEnter()) | ||||
| { | { | ||||
| if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) | |||||
| || (job != nullptr && job->shouldExit())) | |||||
| if (bailOutChecker->shouldAbortAcquiringLock()) | |||||
| return false; | return false; | ||||
| Thread::yield(); | Thread::yield(); | ||||
| @@ -332,8 +339,7 @@ bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob | |||||
| while (! blockingMessage->lockedEvent.wait (20)) | while (! blockingMessage->lockedEvent.wait (20)) | ||||
| { | { | ||||
| if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) | |||||
| || (job != nullptr && job->shouldExit())) | |||||
| if (bailOutChecker != nullptr && bailOutChecker->shouldAbortAcquiringLock()) | |||||
| { | { | ||||
| blockingMessage->releaseEvent.signal(); | blockingMessage->releaseEvent.signal(); | ||||
| blockingMessage = nullptr; | 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(); | ||||
| JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() | JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() | ||||
| @@ -35,6 +35,10 @@ class ThreadPoolJob; | |||||
| class ActionListener; | class ActionListener; | ||||
| class ActionBroadcaster; | class ActionBroadcaster; | ||||
| //============================================================================== | |||||
| #if JUCE_MODULE_AVAILABLE_juce_opengl | |||||
| class OpenGLContext; | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| /** See MessageManager::callFunctionOnMessageThread() for use of this function type. */ | /** See MessageManager::callFunctionOnMessageThread() for use of this function type. */ | ||||
| @@ -312,6 +316,22 @@ public: | |||||
| */ | */ | ||||
| MessageManagerLock (ThreadPoolJob* jobToCheckForExitSignal); | 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. | /** Releases the current thread's lock on the message manager. | ||||
| @@ -331,9 +351,22 @@ private: | |||||
| class BlockingMessage; | class BlockingMessage; | ||||
| friend class ReferenceCountedObjectPtr<BlockingMessage>; | friend class ReferenceCountedObjectPtr<BlockingMessage>; | ||||
| ReferenceCountedObjectPtr<BlockingMessage> 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 locked; | ||||
| bool attemptLock (Thread*, ThreadPoolJob*); | |||||
| //============================================================================== | |||||
| bool attemptLock (BailOutChecker*); | |||||
| JUCE_DECLARE_NON_COPYABLE (MessageManagerLock) | JUCE_DECLARE_NON_COPYABLE (MessageManagerLock) | ||||
| }; | }; | ||||
| @@ -110,6 +110,10 @@ public: | |||||
| { | { | ||||
| if (renderThread != nullptr) | if (renderThread != nullptr) | ||||
| { | { | ||||
| // make sure everything has finished executing | |||||
| destroying.set (1); | |||||
| execute (new DoNothingWorker(), true, true); | |||||
| pause(); | pause(); | ||||
| renderThread = nullptr; | renderThread = nullptr; | ||||
| } | } | ||||
| @@ -207,11 +211,13 @@ public: | |||||
| if (context.renderComponents && isUpdating) | if (context.renderComponents && isUpdating) | ||||
| { | { | ||||
| MessageLockWorker worker (*this); | |||||
| // This avoids hogging the message thread when doing intensive rendering. | // This avoids hogging the message thread when doing intensive rendering. | ||||
| if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter()) | if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter()) | ||||
| Thread::sleep (2); | 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()) | if (! mmLock->lockWasGained()) | ||||
| return false; | return false; | ||||
| @@ -225,6 +231,8 @@ public: | |||||
| JUCE_CHECK_OPENGL_ERROR | JUCE_CHECK_OPENGL_ERROR | ||||
| doWorkWhileWaitingForLock (true); | |||||
| if (context.renderer != nullptr) | if (context.renderer != nullptr) | ||||
| { | { | ||||
| glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight()); | glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight()); | ||||
| @@ -501,12 +509,97 @@ public: | |||||
| nativeContext->shutdownOnRenderThread(); | 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 | static CachedImage* get (Component& c) noexcept | ||||
| { | { | ||||
| return dynamic_cast<CachedImage*> (c.getCachedComponentImage()); | 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; | ScopedPointer<NativeContext> nativeContext; | ||||
| @@ -526,10 +619,11 @@ public: | |||||
| WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent; | WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent; | ||||
| bool shadersAvailable, hasInitialised; | bool shadersAvailable, hasInitialised; | ||||
| Atomic<int> needsUpdate; | |||||
| Atomic<int> needsUpdate, destroying; | |||||
| uint32 lastMMLockReleaseTime; | uint32 lastMMLockReleaseTime; | ||||
| ScopedPointer<ThreadPool> renderThread; | ScopedPointer<ThreadPool> renderThread; | ||||
| ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue; | |||||
| #if JUCE_IOS | #if JUCE_IOS | ||||
| iOSBackgroundProcessCheck backgroundProcessCheck; | iOSBackgroundProcessCheck backgroundProcessCheck; | ||||
| @@ -909,6 +1003,14 @@ void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObjec | |||||
| void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; } | void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; } | ||||
| size_t OpenGLContext::getImageCacheSize() const noexcept { return imageCacheMaxSize; } | 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 | struct DepthTestDisabler | ||||
| { | { | ||||
| @@ -217,6 +217,26 @@ public: | |||||
| */ | */ | ||||
| int getSwapInterval() const; | 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. | /** Returns the scale factor used by the display that is being rendered. | ||||
| @@ -288,7 +308,33 @@ private: | |||||
| size_t imageCacheMaxSize; | size_t imageCacheMaxSize; | ||||
| bool renderComponents, useMultisampling, continuousRepaint; | 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; | CachedImage* getCachedImage() const noexcept; | ||||
| void execute (AsyncWorker::Ptr, bool); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLContext) | 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 | |||||