diff --git a/extras/JuceDemo/Source/MainDemoWindow.cpp b/extras/JuceDemo/Source/MainDemoWindow.cpp index d7f106f46d..0073be314a 100644 --- a/extras/JuceDemo/Source/MainDemoWindow.cpp +++ b/extras/JuceDemo/Source/MainDemoWindow.cpp @@ -519,6 +519,7 @@ private: { public: DemoOpenGLComp (Component* contentComp) + : OpenGLComponent (useBackgroundThread | allowSubComponents) { addAndMakeVisible (contentComp); } diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 4d298d8217..56bf46c7f6 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -1062,7 +1062,7 @@ public: Window editorWnd = (Window) editorComp->getWindowHandle(); XReparentWindow (display, editorWnd, hostWindow, 0, 0); #else - hostWindow = attachComponentToWindowRef (editorComp, (WindowRef) ptr); + hostWindow = attachComponentToWindowRef (editorComp, ptr); #endif editorComp->setVisible (true); diff --git a/modules/juce_events/messages/juce_MessageManager.cpp b/modules/juce_events/messages/juce_MessageManager.cpp index bdb8c925fe..32b8caa911 100644 --- a/modules/juce_events/messages/juce_MessageManager.cpp +++ b/modules/juce_events/messages/juce_MessageManager.cpp @@ -241,64 +241,58 @@ private: //============================================================================== MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) - : locked (false) + : blockingMessage(), locked (attemptLock (threadToCheck, nullptr)) { - init (threadToCheck, nullptr); } MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) - : locked (false) + : blockingMessage(), locked (attemptLock (nullptr, jobToCheckForExitSignal)) { - init (nullptr, jobToCheckForExitSignal); } -void MessageManagerLock::init (Thread* const threadToCheck, ThreadPoolJob* const job) +bool MessageManagerLock::attemptLock (Thread* const threadToCheck, ThreadPoolJob* const job) { - if (MessageManager::instance != nullptr) + if (MessageManager::instance == nullptr) + return false; + + if (MessageManager::instance->currentThreadHasLockedMessageManager()) + return true; + + if (threadToCheck == nullptr && job == nullptr) { - if (MessageManager::instance->currentThreadHasLockedMessageManager()) + MessageManager::instance->lockingLock.enter(); + } + else + { + while (! MessageManager::instance->lockingLock.tryEnter()) { - locked = true; // either we're on the message thread, or this is a re-entrant call. - } - else - { - if (threadToCheck == nullptr && job == nullptr) - { - MessageManager::instance->lockingLock.enter(); - } - else - { - while (! MessageManager::instance->lockingLock.tryEnter()) - { - if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) - || (job != nullptr && job->shouldExit())) - return; + if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) + || (job != nullptr && job->shouldExit())) + return false; - Thread::sleep (1); - } - } - - blockingMessage = new BlockingMessage(); - blockingMessage->post(); - - while (! blockingMessage->lockedEvent.wait (20)) - { - if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) - || (job != nullptr && job->shouldExit())) - { - blockingMessage->releaseEvent.signal(); - blockingMessage = nullptr; - MessageManager::instance->lockingLock.exit(); - return; - } - } - - jassert (MessageManager::instance->threadWithLock == 0); - - MessageManager::instance->threadWithLock = Thread::getCurrentThreadId(); - locked = true; + Thread::sleep (1); } } + + blockingMessage = new BlockingMessage(); + blockingMessage->post(); + + while (! blockingMessage->lockedEvent.wait (20)) + { + if ((threadToCheck != nullptr && threadToCheck->threadShouldExit()) + || (job != nullptr && job->shouldExit())) + { + blockingMessage->releaseEvent.signal(); + blockingMessage = nullptr; + MessageManager::instance->lockingLock.exit(); + return false; + } + } + + jassert (MessageManager::instance->threadWithLock == 0); + + MessageManager::instance->threadWithLock = Thread::getCurrentThreadId(); + return true; } MessageManagerLock::~MessageManagerLock() noexcept diff --git a/modules/juce_events/messages/juce_MessageManager.h b/modules/juce_events/messages/juce_MessageManager.h index 075f6ef95d..6a0342dd96 100644 --- a/modules/juce_events/messages/juce_MessageManager.h +++ b/modules/juce_events/messages/juce_MessageManager.h @@ -273,7 +273,7 @@ public: @endcode */ - MessageManagerLock (Thread* threadToCheckForExitSignal = 0); + MessageManagerLock (Thread* threadToCheckForExitSignal = nullptr); //============================================================================== /** This has the same behaviour as the other constructor, but takes a ThreadPoolJob @@ -304,7 +304,7 @@ private: ReferenceCountedObjectPtr blockingMessage; bool locked; - void init (Thread* thread, ThreadPoolJob* job); + bool attemptLock (Thread*, ThreadPoolJob*); JUCE_DECLARE_NON_COPYABLE (MessageManagerLock); }; diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp index 0acc702404..4afa12ed60 100644 --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp @@ -549,9 +549,9 @@ void ComponentPeer::clearMaskedRegion() maskedRegion.clear(); } -void ComponentPeer::addMaskedRegion (int x, int y, int w, int h) +void ComponentPeer::addMaskedRegion (const Rectangle& area) { - maskedRegion.add (x, y, w, h); + maskedRegion.add (area); } //============================================================================== diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/modules/juce_gui_basics/windows/juce_ComponentPeer.h index 0e9b0db796..351134d773 100644 --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -334,7 +334,7 @@ public: The masked region is cleared each time before a paint happens, so a component will have to make sure it calls this every time it's painted. */ - void addMaskedRegion (int x, int y, int w, int h); + void addMaskedRegion (const Rectangle& area); //============================================================================== /** Returns the number of currently-active peers. diff --git a/modules/juce_opengl/native/juce_ios_OpenGLComponent.mm b/modules/juce_opengl/native/juce_ios_OpenGLComponent.mm index c4ba375703..859114cbc9 100644 --- a/modules/juce_opengl/native/juce_ios_OpenGLComponent.mm +++ b/modules/juce_opengl/native/juce_ios_OpenGLComponent.mm @@ -230,6 +230,8 @@ OpenGLContext* OpenGLComponent::createContext() void OpenGLComponent::updateEmbeddedPosition (const Rectangle& bounds) { + const ScopedLock sl (contextLock); + if (context != nullptr) static_cast (context.get())->updateWindowPosition (bounds); } diff --git a/modules/juce_opengl/native/juce_linux_OpenGLComponent.cpp b/modules/juce_opengl/native/juce_linux_OpenGLComponent.cpp index 8be9ed127a..ee369bd472 100644 --- a/modules/juce_opengl/native/juce_linux_OpenGLComponent.cpp +++ b/modules/juce_opengl/native/juce_linux_OpenGLComponent.cpp @@ -194,6 +194,8 @@ OpenGLContext* OpenGLComponent::createContext() void OpenGLComponent::updateEmbeddedPosition (const Rectangle& bounds) { + const ScopedLock sl (contextLock); + if (context != nullptr) { Window embeddedWindow = static_cast (context.get())->embeddedWindow; diff --git a/modules/juce_opengl/native/juce_win32_OpenGLComponent.cpp b/modules/juce_opengl/native/juce_win32_OpenGLComponent.cpp index 43440d58a6..b7948de617 100644 --- a/modules/juce_opengl/native/juce_win32_OpenGLComponent.cpp +++ b/modules/juce_opengl/native/juce_win32_OpenGLComponent.cpp @@ -282,6 +282,8 @@ void* OpenGLComponent::getNativeWindowHandle() const void OpenGLComponent::updateEmbeddedPosition (const Rectangle& bounds) { + const ScopedLock sl (contextLock); + if (context != nullptr) { ComponentPeer* peer = static_cast (context.get())->nativeWindow; diff --git a/modules/juce_opengl/opengl/juce_OpenGLComponent.cpp b/modules/juce_opengl/opengl/juce_OpenGLComponent.cpp index 8dbdd3d94b..974f56a87d 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLComponent.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLComponent.cpp @@ -114,6 +114,108 @@ OpenGLContext* OpenGLContext::getCurrentContext() return nullptr; } +//============================================================================== +class OpenGLComponent::OpenGLCachedComponentImage : public CachedComponentImage, + public Timer // N.B. using a Timer rather than an AsyncUpdater + // to avoid scheduling problems on Windows +{ +public: + OpenGLCachedComponentImage (OpenGLComponent& owner_) + : owner (owner_) + { + } + + void paint (Graphics&) + { + ComponentPeer* const peer = owner.getPeer(); + + if (peer != nullptr) + peer->addMaskedRegion (owner.getScreenBounds() - peer->getScreenPosition()); + + if (owner.isUsingDedicatedThread()) + { + if (peer != nullptr && owner.isShowing()) + { + #if ! JUCE_LINUX + owner.updateContext(); + #endif + + if (! owner.threadStarted) + { + owner.threadStarted = true; + owner.startRenderThread(); + } + } + } + else + { + owner.updateContext(); + + if (isTimerRunning()) + timerCallback(); + } + } + + void invalidateAll() + { + validArea.clear(); + triggerRepaint(); + } + + void invalidate (const Rectangle& area) + { + validArea.subtract (area); + triggerRepaint(); + } + + void releaseResources() + { + frameBuffer.release(); + } + + void timerCallback() + { + stopTimer(); + + owner.performRender(); + owner.releaseAsRenderingTarget(); + } + + void triggerRepaint() + { + owner.needToRepaint = true; + + if (! owner.isUsingDedicatedThread()) + startTimer (1000 / 70); + } + + OpenGLFrameBuffer& getFrameBuffer (int width, int height) + { + const int fbW = frameBuffer.getWidth(); + const int fbH = frameBuffer.getHeight(); + + if (fbW < width + || fbH < height + || fbW > width + 128 + || fbH > height + 128 + || ! frameBuffer.isValid()) + { + frameBuffer.initialise (width, height); + validArea.clear(); + } + + return frameBuffer; + } + + RectangleList validArea; + +private: + OpenGLComponent& owner; + OpenGLFrameBuffer frameBuffer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLCachedComponentImage); +}; + //============================================================================== class OpenGLComponent::OpenGLComponentWatcher : public ComponentMovementWatcher { @@ -174,11 +276,11 @@ public: { const uint32 startOfRendering = Time::getMillisecondCounter(); - if (! owner.renderAndSwapBuffers()) + if (! owner.performRender()) break; const int elapsed = (int) (Time::getMillisecondCounter() - startOfRendering); - Thread::sleep (jmax (1, 20 - elapsed)); + Thread::sleep (jmax (1, (1000 / 60) - elapsed)); } #if JUCE_LINUX @@ -213,176 +315,18 @@ void OpenGLComponent::stopRenderThread() #endif } -//============================================================================== -class OpenGLComponent::OpenGLCachedComponentImage : public CachedComponentImage, - public Timer // N.B. using a Timer rather than an AsyncUpdater - // to avoid scheduling problems on Windows -{ -public: - OpenGLCachedComponentImage (OpenGLComponent& owner_) - : owner (owner_) - { - } - - void paint (Graphics&) - { - ComponentPeer* const peer = owner.getPeer(); - - if (peer != nullptr) - { - const Point topLeft (owner.getScreenPosition() - peer->getScreenPosition()); - peer->addMaskedRegion (topLeft.x, topLeft.y, owner.getWidth(), owner.getHeight()); - } - - if (owner.isUsingDedicatedThread()) - { - if (peer != nullptr && owner.isShowing()) - { - #if ! JUCE_LINUX - owner.updateContext(); - #endif - - if (! owner.threadStarted) - { - owner.threadStarted = true; - owner.startRenderThread(); - } - } - } - else - { - owner.updateContext(); - - if (isTimerRunning()) - timerCallback(); - } - } - - void invalidateAll() - { - validArea.clear(); - triggerRepaint(); - } - - void invalidate (const Rectangle& area) - { - validArea.subtract (area); - triggerRepaint(); - } - - void releaseResources() - { - frameBuffer.release(); - } - - void timerCallback() - { - stopTimer(); - - if (! owner.makeCurrentRenderingTarget()) - return; - - const Rectangle bounds (owner.getLocalBounds()); - - const int fbW = frameBuffer.getWidth(); - const int fbH = frameBuffer.getHeight(); - - if (fbW < bounds.getWidth() - || fbH < bounds.getHeight() - || fbW > bounds.getWidth() + 128 - || fbH > bounds.getHeight() + 128 - || ! frameBuffer.isValid()) - { - frameBuffer.initialise (bounds.getWidth(), bounds.getHeight()); - validArea.clear(); - } - - owner.renderOpenGL(); - - if ((owner.flags & allowSubComponents) != 0) - { - { - RectangleList invalid (bounds); - invalid.subtract (validArea); - validArea = bounds; - - if (! invalid.isEmpty()) - { - OpenGLGraphicsContext g (frameBuffer); - g.clipToRectangleList (invalid); - - g.setFill (Colours::transparentBlack); - g.fillRect (bounds, true); - g.setFill (Colours::black); - - paintOwner (g); - } - } - - owner.makeCurrentRenderingTarget(); - OpenGLHelpers::prepareFor2D (bounds.getWidth(), bounds.getHeight()); - glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glEnable (GL_BLEND); - glColor4f (1.0f, 1.0f, 1.0f, owner.getAlpha()); - frameBuffer.drawAt (0, (float) (bounds.getHeight() - frameBuffer.getHeight())); - } - - owner.swapBuffers(); - owner.releaseAsRenderingTarget(); - } - - void triggerRepaint() - { - if (! owner.isUsingDedicatedThread()) - startTimer (1000 / 70); - } - -private: - void paintOwner (OpenGLGraphicsContext& glRenderer) - { - Graphics g (&glRenderer); - - #if JUCE_ENABLE_REPAINT_DEBUGGING - g.saveState(); - #endif - - JUCE_TRY - { - owner.paintEntireComponent (g, false); - } - JUCE_CATCH_EXCEPTION - - #if JUCE_ENABLE_REPAINT_DEBUGGING - // enabling this code will fill all areas that get repainted with a colour overlay, to show - // clearly when things are being repainted. - g.restoreState(); - - static Random rng; - g.fillAll (Colour ((uint8) rng.nextInt (255), - (uint8) rng.nextInt (255), - (uint8) rng.nextInt (255), - (uint8) 0x50)); - #endif - } - - OpenGLComponent& owner; - OpenGLFrameBuffer frameBuffer; - RectangleList validArea; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLCachedComponentImage); -}; - //============================================================================== OpenGLComponent::OpenGLComponent (const int flags_) : flags (flags_), contextToShareListsWith (nullptr), needToUpdateViewport (true), needToDeleteContext (false), - threadStarted (false) + threadStarted (false), + needToRepaint (true) { setOpaque (true); + setCachedComponentImage (cachedImage = new OpenGLCachedComponentImage (*this)); componentWatcher = new OpenGLComponentWatcher (*this); - setCachedComponentImage (new OpenGLCachedComponentImage (*this)); } OpenGLComponent::~OpenGLComponent() @@ -492,10 +436,7 @@ void OpenGLComponent::updateContextPosition() Component* const topComp = getTopLevelComponent(); if (topComp->getPeer() != nullptr) - { - const ScopedLock sl (contextLock); updateEmbeddedPosition (topComp->getLocalArea (this, getLocalBounds())); - } } } @@ -508,7 +449,24 @@ void OpenGLComponent::stopBackgroundThread() } } -bool OpenGLComponent::renderAndSwapBuffers() +void OpenGLComponent::triggerRepaint() +{ + // you mustn't set your own cached image object for an OpenGLComponent! + jassert (dynamic_cast (getCachedComponentImage()) == cachedImage); + + cachedImage->triggerRepaint(); +} + +void OpenGLComponent::paint (Graphics&) +{ +} + +unsigned int OpenGLComponent::getFrameBufferID() const +{ + return context != nullptr ? context->getFrameBufferID() : 0; +} + +bool OpenGLComponent::performRender() { const ScopedLock sl (contextLock); @@ -528,28 +486,80 @@ bool OpenGLComponent::renderAndSwapBuffers() } renderOpenGL(); + + if (needToRepaint && (flags & allowSubComponents) != 0) + { + needToRepaint = false; + + contextLock.exit(); // (MM must be locked before the context lock) + MessageManagerLock mmLock (renderThread); + contextLock.enter(); + + if (! mmLock.lockWasGained()) + return false; + + // you mustn't set your own cached image object for an OpenGLComponent! + jassert (dynamic_cast (getCachedComponentImage()) == cachedImage); + + const Rectangle bounds (getLocalBounds()); + OpenGLFrameBuffer& frameBuffer = cachedImage->getFrameBuffer (bounds.getWidth(), bounds.getHeight()); + + { + RectangleList invalid (bounds); + invalid.subtract (cachedImage->validArea); + cachedImage->validArea = bounds; + + if (! invalid.isEmpty()) + { + OpenGLGraphicsContext g (frameBuffer); + g.clipToRectangleList (invalid); + + g.setFill (Colours::transparentBlack); + g.fillRect (bounds, true); + g.setFill (Colours::black); + + paintSelf (g); + + makeCurrentRenderingTarget(); + } + } + + OpenGLHelpers::prepareFor2D (bounds.getWidth(), bounds.getHeight()); + glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_BLEND); + glColor4f (1.0f, 1.0f, 1.0f, getAlpha()); + frameBuffer.drawAt (0, (float) (bounds.getHeight() - frameBuffer.getHeight())); + } + swapBuffers(); } return true; } -void OpenGLComponent::triggerRepaint() +void OpenGLComponent::paintSelf (OpenGLGraphicsContext& glRenderer) { - OpenGLCachedComponentImage* const c - = dynamic_cast (getCachedComponentImage()); + Graphics g (&glRenderer); - jassert (c != nullptr); // you mustn't set your own cached image object for an OpenGLComponent! + #if JUCE_ENABLE_REPAINT_DEBUGGING + g.saveState(); + #endif - if (c != nullptr) - c->triggerRepaint(); -} - -void OpenGLComponent::paint (Graphics&) -{ -} - -unsigned int OpenGLComponent::getFrameBufferID() const -{ - return context != nullptr ? context->getFrameBufferID() : 0; + JUCE_TRY + { + paintEntireComponent (g, false); + } + JUCE_CATCH_EXCEPTION + + #if JUCE_ENABLE_REPAINT_DEBUGGING + // enabling this code will fill all areas that get repainted with a colour overlay, to show + // clearly when things are being repainted. + g.restoreState(); + + static Random rng; + g.fillAll (Colour ((uint8) rng.nextInt (255), + (uint8) rng.nextInt (255), + (uint8) rng.nextInt (255), + (uint8) 0x50)); + #endif } diff --git a/modules/juce_opengl/opengl/juce_OpenGLComponent.h b/modules/juce_opengl/opengl/juce_OpenGLComponent.h index c0a866505f..dc0c1a1a21 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLComponent.h +++ b/modules/juce_opengl/opengl/juce_OpenGLComponent.h @@ -34,6 +34,8 @@ typedef Component OpenGLBaseType; #endif +class OpenGLGraphicsContext; + //============================================================================== /** A component that contains an OpenGL canvas. @@ -202,12 +204,6 @@ public: void triggerRepaint(); //============================================================================== - /** Calls the rendering callback, and swaps the buffers afterwards. - This is called automatically by paint() when the component needs to be rendered. - Returns true if the operation succeeded. - */ - virtual bool renderAndSwapBuffers(); - /** This returns a critical section that can be used to lock the current context. Because the context that is used by this component can change, e.g. when the @@ -269,10 +265,11 @@ private: CriticalSection contextLock; OpenGLPixelFormat preferredPixelFormat; - bool needToUpdateViewport, needToDeleteContext, threadStarted; + bool needToUpdateViewport, needToDeleteContext, threadStarted, needToRepaint; class OpenGLCachedComponentImage; friend class OpenGLCachedComponentImage; + OpenGLCachedComponentImage* cachedImage; OpenGLContext* createContext(); void updateContext(); @@ -280,6 +277,10 @@ private: void stopBackgroundThread(); void recreateContextAsync(); void updateEmbeddedPosition (const Rectangle&); + bool performRender(); + void paintSelf (OpenGLGraphicsContext&); + + int renderAndSwapBuffers(); // (This method has been deprecated) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLComponent); }; diff --git a/modules/juce_video/native/juce_win32_DirectShowComponent.cpp b/modules/juce_video/native/juce_win32_DirectShowComponent.cpp index 3286d55e32..69c0bce74e 100644 --- a/modules/juce_video/native/juce_win32_DirectShowComponent.cpp +++ b/modules/juce_video/native/juce_win32_DirectShowComponent.cpp @@ -809,10 +809,7 @@ void DirectShowComponent::paint (Graphics& g) ComponentPeer* const peer = getPeer(); if (peer != nullptr) - { - const Point topLeft (getScreenPosition() - peer->getScreenPosition()); - peer->addMaskedRegion (topLeft.getX(), topLeft.getY(), getWidth(), getHeight()); - } + peer->addMaskedRegion (getScreenBounds() - peer->getScreenPosition()); } else {