/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #if JUCE_ANDROID void triggerAndroidOpenGLRepaint (OpenGLContext*); #endif class OpenGLComponent::OpenGLCachedComponentImage : public CachedComponentImage, public Thread, public Timer // N.B. using a Timer rather than an AsyncUpdater // to avoid scheduling problems on Windows { public: OpenGLCachedComponentImage (OpenGLComponent& owner_, bool renderComponents_) : Thread ("OpenGL Rendering"), owner (owner_), needToRepaint (true), renderComponents (renderComponents_) {} 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 owner.startRenderThread(); } } else { owner.updateContext(); #if JUCE_ANDROID triggerAndroidOpenGLRepaint (owner.getCurrentContext()); #else if (isTimerRunning()) timerCallback(); #endif } } void invalidateAll() { validArea.clear(); triggerRepaint(); } void invalidate (const Rectangle& area) { validArea.subtract (area); triggerRepaint(); } void releaseResources() { owner.makeCurrentRenderingTarget(); cachedImageFrameBuffer.release(); owner.releaseAsRenderingTarget(); } //============================================================================== void timerCallback() { stopTimer(); renderFrame(); owner.releaseAsRenderingTarget(); } void triggerRepaint() { needToRepaint = true; #if JUCE_ANDROID triggerAndroidOpenGLRepaint (owner.getCurrentContext()); #else if (! owner.isUsingDedicatedThread()) startTimer (1000 / 70); #endif } void updateContextPosition() { if (owner.getWidth() > 0 && owner.getHeight() > 0) { Component* const topComp = owner.getTopLevelComponent(); if (topComp->getPeer() != nullptr) { const Rectangle bounds (topComp->getLocalArea (&owner, owner.getLocalBounds())); const ScopedLock sl (owner.contextLock); if (owner.context != nullptr) owner.context->updateWindowPosition (bounds); } } } //============================================================================== void ensureFrameBufferSize (int width, int height) { const int fbW = cachedImageFrameBuffer.getWidth(); const int fbH = cachedImageFrameBuffer.getHeight(); if (fbW != width || fbH != height || ! cachedImageFrameBuffer.isValid()) { jassert (owner.getCurrentContext() != nullptr); cachedImageFrameBuffer.initialise (*owner.getCurrentContext(), width, height); validArea.clear(); JUCE_CHECK_OPENGL_ERROR } } void clearRegionInFrameBuffer (const RectangleList& list) { glClearColor (0, 0, 0, 0); glEnable (GL_SCISSOR_TEST); const GLuint previousFrameBufferTarget = OpenGLFrameBuffer::getCurrentFrameBufferTarget(); cachedImageFrameBuffer.makeCurrentRenderingTarget(); for (RectangleList::Iterator i (list); i.next();) { const Rectangle& r = *i.getRectangle(); glScissor (r.getX(), owner.getHeight() - r.getBottom(), r.getWidth(), r.getHeight()); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } glDisable (GL_SCISSOR_TEST); owner.getCurrentContext()->extensions.glBindFramebuffer (GL_FRAMEBUFFER, previousFrameBufferTarget); JUCE_CHECK_OPENGL_ERROR } bool renderFrame() { const ScopedLock sl (owner.contextLock); #if JUCE_LINUX owner.updateContext(); #endif OpenGLContext* const context = owner.getCurrentContext(); if (context != nullptr) { if (! context->makeActive()) return false; JUCE_CHECK_OPENGL_ERROR glViewport (0, 0, owner.getWidth(), owner.getHeight()); owner.renderOpenGL(); JUCE_CHECK_OPENGL_ERROR if (renderComponents) paintComponent (context); context->swapBuffers(); } return true; } void paintComponent (OpenGLContext* const context) { jassert (context != nullptr); owner.contextLock.exit(); // (MM must be locked before the context lock) MessageManagerLock mmLock (this); owner.contextLock.enter(); if (! mmLock.lockWasGained()) return; // you mustn't set your own cached image object for an OpenGLComponent! jassert (dynamic_cast (owner.getCachedComponentImage()) == this); const Rectangle bounds (owner.getLocalBounds()); ensureFrameBufferSize (bounds.getWidth(), bounds.getHeight()); if (needToRepaint) { needToRepaint = false; RectangleList invalid (bounds); invalid.subtract (validArea); validArea = bounds; if (! invalid.isEmpty()) { clearRegionInFrameBuffer (invalid); { ScopedPointer g (createOpenGLGraphicsContext (*context, cachedImageFrameBuffer)); g->clipToRectangleList (invalid); paintOwner (*g); JUCE_CHECK_OPENGL_ERROR } context->makeActive(); } } JUCE_CHECK_OPENGL_ERROR #if ! JUCE_ANDROID glEnable (GL_TEXTURE_2D); #endif context->extensions.glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID()); jassert (bounds.getPosition() == Point()); context->copyTexture (bounds, bounds, context->getWidth(), context->getHeight()); glBindTexture (GL_TEXTURE_2D, 0); JUCE_CHECK_OPENGL_ERROR } void paintOwner (LowLevelGraphicsContext& context) { Graphics g (&context); #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 } //============================================================================== void run() { initialise(); while (! threadShouldExit()) { const uint32 frameRenderStartTime = Time::getMillisecondCounter(); if (renderFrame()) waitForNextFrame (frameRenderStartTime); } shutdown(); } void initialise() { #if JUCE_LINUX MessageManagerLock mml (this); if (mml.lockWasGained()) { owner.updateContext(); updateContextPosition(); } #endif } void shutdown() { #if JUCE_LINUX owner.deleteContext(); #endif } void waitForNextFrame (const uint32 frameRenderStartTime) { const int defaultFPS = 60; const int elapsed = (int) (Time::getMillisecondCounter() - frameRenderStartTime); Thread::sleep (jmax (1, (1000 / defaultFPS) - elapsed)); } //============================================================================== RectangleList validArea; OpenGLComponent& owner; OpenGLFrameBuffer cachedImageFrameBuffer; bool needToRepaint; const bool renderComponents; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLCachedComponentImage); }; //============================================================================== class OpenGLComponent::OpenGLComponentWatcher : public ComponentMovementWatcher { public: OpenGLComponentWatcher (OpenGLComponent& owner_) : ComponentMovementWatcher (&owner_), owner (owner_) { } void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) { if (owner.cachedImage != nullptr) owner.cachedImage->updateContextPosition(); } void componentPeerChanged() { owner.recreateContextAsync(); } void componentVisibilityChanged() { if (owner.isShowing()) owner.triggerRepaint(); else owner.stopRenderThread(); } private: OpenGLComponent& owner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLComponentWatcher); }; //============================================================================== OpenGLComponent::OpenGLComponent (const int flags_) #if JUCE_ANDROID : flags (flags_ & ~useBackgroundThread), #else : flags (flags_), #endif contextToShareListsWith (nullptr), needToDeleteContext (false), cachedImage (nullptr) { setOpaque (true); triggerRepaint(); componentWatcher = new OpenGLComponentWatcher (*this); } OpenGLComponent::~OpenGLComponent() { if (isUsingDedicatedThread()) { /* If you're using a background thread, then your sub-class MUST call stopRenderThread() in its destructor! Otherwise, the thread could still be running while your sub-class isbeing destroyed, and so may make a call to your subclass's renderOpenGL() method when it no longer exists! */ jassert (! getGLCachedImage()->isThreadRunning()); stopRenderThread(); } else { deleteContext(); } componentWatcher = nullptr; } void OpenGLComponent::setPixelFormat (const OpenGLPixelFormat& formatToUse) { if (! (preferredPixelFormat == formatToUse)) { const ScopedLock sl (contextLock); preferredPixelFormat = formatToUse; recreateContextAsync(); } } void OpenGLComponent::shareWith (OpenGLContext* c) { if (contextToShareListsWith != c) { const ScopedLock sl (contextLock); contextToShareListsWith = c; recreateContextAsync(); } } void OpenGLComponent::startRenderThread() { getGLCachedImage()->startThread (6); } void OpenGLComponent::stopRenderThread() { getGLCachedImage()->stopThread (5000); #if ! JUCE_LINUX deleteContext(); #endif } void OpenGLComponent::recreateContextAsync() { const ScopedLock sl (contextLock); needToDeleteContext = true; repaint(); } bool OpenGLComponent::makeCurrentRenderingTarget() { return context != nullptr && context->makeActive(); } void OpenGLComponent::releaseAsRenderingTarget() { if (context != nullptr) context->makeInactive(); } void OpenGLComponent::swapBuffers() { if (context != nullptr) context->swapBuffers(); } void OpenGLComponent::updateContext() { if (needToDeleteContext) deleteContext(); if (context == nullptr) { const ScopedLock sl (contextLock); if (context == nullptr) { context = createContext(); if (context != nullptr) { #if JUCE_LINUX if (! isUsingDedicatedThread()) #endif getGLCachedImage()->updateContextPosition(); if (context->makeActive()) { newOpenGLContextCreated(); context->makeInactive(); } } } } } void OpenGLComponent::deleteContext() { const ScopedLock sl (contextLock); if (context != nullptr) { if (context->makeActive()) { cachedImage = nullptr; setCachedComponentImage (nullptr); releaseOpenGLContext(); context->makeInactive(); } context = nullptr; } needToDeleteContext = false; } bool OpenGLComponent::rebuildContext() { needToDeleteContext = true; updateContext(); return context != nullptr && context->makeActive(); } OpenGLComponent::OpenGLCachedComponentImage* OpenGLComponent::getGLCachedImage() { // you mustn't set your own cached image object for an OpenGLComponent! jassert (cachedImage == nullptr || dynamic_cast (getCachedComponentImage()) == cachedImage); if (cachedImage == nullptr) setCachedComponentImage (cachedImage = new OpenGLCachedComponentImage (*this, (flags & allowSubComponents) != 0)); return cachedImage; } void OpenGLComponent::triggerRepaint() { getGLCachedImage()->triggerRepaint(); } void OpenGLComponent::newOpenGLContextCreated() {} void OpenGLComponent::releaseOpenGLContext() {} void OpenGLComponent::paint (Graphics&) {}