text: /*
    ==============================================================================
  
     This file is part of the JUCE library.
     Copyright (c) 2017 - ROLI Ltd.
  
     JUCE is an open source library subject to commercial or open-source
     licensing.
  
     By using JUCE, you agree to the terms of both the JUCE 5 End-User License
     Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
     27th April 2017).
  
     End User License Agreement: www.juce.com/juce-5-licence
     Privacy Policy: www.juce.com/juce-5-privacy-policy
  
     Or: You may also use this code under the terms of the GPL v3 (see
     www.gnu.org/licenses).
  
     JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
     EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
     DISCLAIMED.
  
    ==============================================================================
  */
  
  namespace juce
  {
  
  #if JUCE_IOS
  struct AppInactivityCallback // NB: this is a duplicate of an internal declaration in juce_core
  {
      virtual ~AppInactivityCallback() {}
      virtual void appBecomingInactive() = 0;
  };
  
  extern Array<AppInactivityCallback*> appBecomingInactiveCallbacks;
  
  // On iOS, all GL calls will crash when the app is running in the background, so
  // this prevents them from happening (which some messy locking behaviour)
  struct iOSBackgroundProcessCheck  : public AppInactivityCallback
  {
      iOSBackgroundProcessCheck()     { isBackgroundProcess(); appBecomingInactiveCallbacks.add (this); }
      ~iOSBackgroundProcessCheck()    { appBecomingInactiveCallbacks.removeAllInstancesOf (this); }
  
      bool isBackgroundProcess()
      {
          const bool b = Process::isForegroundProcess();
          isForeground.set (b ? 1 : 0);
          return ! b;
      }
  
      void appBecomingInactive() override
      {
          int counter = 2000;
  
          while (--counter > 0 && isForeground.get() != 0)
              Thread::sleep (1);
      }
  
  private:
      Atomic<int> isForeground;
  
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSBackgroundProcessCheck)
  };
  
  #endif
  
  //==============================================================================
  class OpenGLContext::CachedImage  : public CachedComponentImage,
                                      private ThreadPoolJob
  {
  public:
      CachedImage (OpenGLContext& c, Component& comp,
                   const OpenGLPixelFormat& pixFormat, void* contextToShare)
          : ThreadPoolJob ("OpenGL Rendering"),
            context (c), component (comp)
      {
          nativeContext.reset (new NativeContext (component, pixFormat, contextToShare,
                                                  c.useMultisampling, c.versionRequired));
  
          if (nativeContext->createdOk())
              context.nativeContext = nativeContext.get();
          else
              nativeContext.reset();
      }
  
      ~CachedImage()
      {
          stop();
      }
  
      //==============================================================================
      void start()
      {
          if (nativeContext != nullptr)
          {
              renderThread.reset (new ThreadPool (1));
              resume();
          }
      }
  
      void stop()
      {
          if (renderThread != nullptr)
          {
              // make sure everything has finished executing
              destroying.set (1);
  
              if (workQueue.size() > 0)
              {
                  if (! renderThread->contains (this))
                      resume();
  
                  while (workQueue.size() != 0)
                      Thread::sleep (20);
              }
  
              pause();
              renderThread.reset();
          }
  
          hasInitialised = false;
      }
  
      //==============================================================================
      void pause()
      {
          signalJobShouldExit();
          messageManagerLock.abort();
  
          if (renderThread != nullptr)
          {
              repaintEvent.signal();
              renderThread->removeJob (this, true, -1);
          }
      }
  
      void resume()
      {
          if (renderThread != nullptr)
              renderThread->addJob (this, false);
      }
  
      //==============================================================================
      void paint (Graphics&) override
      {
          updateViewportSize (false);
      }
  
      bool invalidateAll() override
      {
          validArea.clear();
          triggerRepaint();
          return false;
      }
  
      bool invalidate (const Rectangle<int>& area) override
      {
          validArea.subtract (area.toFloat().transformedBy (transform).getSmallestIntegerContainer());
          triggerRepaint();
          return false;
      }
  
      void releaseResources() override
      {
          stop();
      }
  
      void triggerRepaint()
      {
          needsUpdate = 1;
          repaintEvent.signal();
      }
  
      //==============================================================================
      bool ensureFrameBufferSize()
      {
          auto fbW = cachedImageFrameBuffer.getWidth();
          auto fbH = cachedImageFrameBuffer.getHeight();
  
          if (fbW != viewportArea.getWidth() || fbH != viewportArea.getHeight() || ! cachedImageFrameBuffer.isValid())
          {
              if (! cachedImageFrameBuffer.initialise (context, viewportArea.getWidth(), viewportArea.getHeight()))
                  return false;
  
              validArea.clear();
              JUCE_CHECK_OPENGL_ERROR
          }
  
          return true;
      }
  
      void clearRegionInFrameBuffer (const RectangleList<int>& list)
      {
          glClearColor (0, 0, 0, 0);
          glEnable (GL_SCISSOR_TEST);
  
          auto previousFrameBufferTarget = OpenGLFrameBuffer::getCurrentFrameBufferTarget();
          cachedImageFrameBuffer.makeCurrentRenderingTarget();
          auto imageH = cachedImageFrameBuffer.getHeight();
  
          for (auto& r : list)
          {
              glScissor (r.getX(), imageH - r.getBottom(), r.getWidth(), r.getHeight());
              glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
          }
  
          glDisable (GL_SCISSOR_TEST);
          context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, previousFrameBufferTarget);
          JUCE_CHECK_OPENGL_ERROR
      }
  
      bool renderFrame()
      {
          MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
          const bool isUpdating = needsUpdate.compareAndSetBool (0, 1);
  
          if (context.renderComponents && isUpdating)
          {
              // This avoids hogging the message thread when doing intensive rendering.
              if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter())
                  Thread::sleep (2);
  
              while (! shouldExit())
              {
                  doWorkWhileWaitingForLock (false);
  
                  if (mmLock.retryLock())
                      break;
              }
  
              if (shouldExit())
                  return false;
          }
  
          if (! context.makeActive())
              return false;
  
          NativeContext::Locker locker (*nativeContext);
  
          JUCE_CHECK_OPENGL_ERROR
  
          doWorkWhileWaitingForLock (true);
  
          if (context.renderer != nullptr)
          {
              glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
              context.currentRenderScale = scale;
              context.renderer->renderOpenGL();
              clearGLError();
  
              bindVertexArray();
          }
  
          if (context.renderComponents)
          {
              if (isUpdating)
              {
                  paintComponent();
  
                  if (! hasInitialised)
                      return false;
  
                  messageManagerLock.exit();
                  lastMMLockReleaseTime = Time::getMillisecondCounter();
              }
  
              glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
              drawComponentBuffer();
          }
  
          context.swapBuffers();
  
          OpenGLContext::deactivateCurrentContext();
          return true;
      }
  
      void updateViewportSize (bool canTriggerUpdate)
      {
          if (auto* peer = component.getPeer())
          {
              lastScreenBounds = component.getTopLevelComponent()->getScreenBounds();
  
              auto newScale = Desktop::getInstance().getDisplays()
                                .getDisplayContaining (lastScreenBounds.getCentre()).scale;
  
              auto localBounds = component.getLocalBounds();
  
              auto newArea = peer->getComponent().getLocalArea (&component, localBounds)
                                                 .withZeroOrigin()
                               * newScale;
  
              if (scale != newScale || viewportArea != newArea)
              {
                  scale = newScale;
                  viewportArea = newArea;
                  transform = AffineTransform::scale ((float) newArea.getRight()  / (float) localBounds.getRight(),
                                                      (float) newArea.getBottom() / (float) localBounds.getBottom());
  
                  if (canTriggerUpdate)
                      invalidateAll();
              }
          }
      }
  
      void bindVertexArray() noexcept
      {
         #if JUCE_OPENGL3
          if (vertexArrayObject != 0)
              context.extensions.glBindVertexArray (vertexArrayObject);
         #endif
      }
  
      void checkViewportBounds()
      {
          auto screenBounds = component.getTopLevelComponent()->getScreenBounds();
  
          if (lastScreenBounds != screenBounds)
              updateViewportSize (true);
      }
  
      void paintComponent()
      {
          // you mustn't set your own cached image object when attaching a GL context!
          jassert (get (component) == this);
  
          if (! ensureFrameBufferSize())
              return;
  
          RectangleList<int> invalid (viewportArea);
          invalid.subtract (validArea);
          validArea = viewportArea;
  
          if (! invalid.isEmpty())
          {
              clearRegionInFrameBuffer (invalid);
  
              {
                  std::unique_ptr<LowLevelGraphicsContext> g (createOpenGLGraphicsContext (context, cachedImageFrameBuffer));
                  g->clipToRectangleList (invalid);
                  g->addTransform (transform);
  
                  paintOwner (*g);
                  JUCE_CHECK_OPENGL_ERROR
              }
  
              if (! context.isActive())
                  context.makeActive();
          }
  
          JUCE_CHECK_OPENGL_ERROR
      }
  
      void drawComponentBuffer()
      {
         #if ! JUCE_ANDROID
          glEnable (GL_TEXTURE_2D);
          clearGLError();
         #endif
  
         #if JUCE_WINDOWS
          // some stupidly old drivers are missing this function, so try to at least avoid a crash here,
          // but if you hit this assertion you may want to have your own version check before using the
          // component rendering stuff on such old drivers.
          jassert (context.extensions.glActiveTexture != nullptr);
          if (context.extensions.glActiveTexture != nullptr)
         #endif
              context.extensions.glActiveTexture (GL_TEXTURE0);
  
          glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID());
          bindVertexArray();
  
          const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight());
          context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight(), false);
          glBindTexture (GL_TEXTURE_2D, 0);
          JUCE_CHECK_OPENGL_ERROR
      }
  
      void paintOwner (LowLevelGraphicsContext& llgc)
      {
          Graphics g (llgc);
  
        #if JUCE_ENABLE_REPAINT_DEBUGGING
         #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
          if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
         #endif
          {
              g.saveState();
          }
         #endif
  
          JUCE_TRY
          {
              component.paintEntireComponent (g, false);
          }
          JUCE_CATCH_EXCEPTION
  
        #if JUCE_ENABLE_REPAINT_DEBUGGING
         #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
          if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
         #endif
          {
              // 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 handleResize()
      {
          updateViewportSize (true);
  
         #if JUCE_MAC
          if (hasInitialised)
          {
              [nativeContext->view update];
              renderFrame();
          }
         #endif
      }
  
      //==============================================================================
      JobStatus runJob() override
      {
          {
              // Allow the message thread to finish setting-up the context before using it..
              MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
  
              do
              {
                  if (shouldExit())
                      return ThreadPoolJob::jobHasFinished;
  
              } while (! mmLock.retryLock());
          }
  
          if (! initialiseOnThread())
          {
              hasInitialised = false;
  
              return ThreadPoolJob::jobHasFinished;
          }
  
          hasInitialised = true;
  
          while (! shouldExit())
          {
             #if JUCE_IOS
              if (backgroundProcessCheck.isBackgroundProcess())
              {
                  repaintEvent.wait (300);
                  continue;
              }
             #endif
  
              if (shouldExit())
                  break;
  
              if (! renderFrame())
                  repaintEvent.wait (5); // failed to render, so avoid a tight fail-loop.
              else if (! context.continuousRepaint && ! shouldExit())
                  repaintEvent.wait (-1);
          }
  
          hasInitialised = false;
          context.makeActive();
          shutdownOnThread();
          OpenGLContext::deactivateCurrentContext();
  
          return ThreadPoolJob::jobHasFinished;
      }
  
      bool initialiseOnThread()
      {
          // On android, this can get called twice, so drop any previous state..
          associatedObjectNames.clear();
          associatedObjects.clear();
          cachedImageFrameBuffer.release();
  
          context.makeActive();
  
          if (! nativeContext->initialiseOnRenderThread (context))
              return false;
  
         #if JUCE_ANDROID
          // On android the context may be created in initialiseOnRenderThread
          // and we therefore need to call makeActive again
          context.makeActive();
         #endif
  
          context.extensions.initialise();
  
         #if JUCE_OPENGL3
          if (OpenGLShaderProgram::getLanguageVersion() > 1.2)
          {
              context.extensions.glGenVertexArrays (1, &vertexArrayObject);
              bindVertexArray();
          }
         #endif
  
          glViewport (0, 0, component.getWidth(), component.getHeight());
  
          nativeContext->setSwapInterval (1);
  
         #if ! JUCE_OPENGL_ES
          JUCE_CHECK_OPENGL_ERROR
          shadersAvailable = OpenGLShaderProgram::getLanguageVersion() > 0;
          clearGLError();
         #endif
  
          if (context.renderer != nullptr)
              context.renderer->newOpenGLContextCreated();
  
          return true;
      }
  
      void shutdownOnThread()
      {
          if (context.renderer != nullptr)
              context.renderer->openGLContextClosing();
  
         #if JUCE_OPENGL3
          if (vertexArrayObject != 0)
              context.extensions.glDeleteVertexArrays (1, &vertexArrayObject);
         #endif
  
          associatedObjectNames.clear();
          associatedObjects.clear();
          cachedImageFrameBuffer.release();
          nativeContext->shutdownOnRenderThread();
      }
  
      //==============================================================================
      struct BlockingWorker  : public 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() noexcept  { 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)
          {
              if (shouldBlock)
              {
                  auto blocker = new BlockingWorker (static_cast<OpenGLContext::AsyncWorker::Ptr&&> (workerToUse));
                  OpenGLContext::AsyncWorker::Ptr worker (*blocker);
                  workQueue.add (worker);
  
                  messageManagerLock.abort();
                  context.triggerRepaint();
  
                  blocker->block();
              }
              else
              {
                  workQueue.add (static_cast<OpenGLContext::AsyncWorker::Ptr&&> (workerToUse));
  
                  messageManagerLock.abort();
                  context.triggerRepaint();
              }
          }
          else
          {
              jassertfalse; // you called execute AFTER you detached your openglcontext
          }
      }
  
      //==============================================================================
      static CachedImage* get (Component& c) noexcept
      {
          return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
      }
  
      //==============================================================================
      friend class NativeContext;
      std::unique_ptr<NativeContext> nativeContext;
  
      OpenGLContext& context;
      Component& component;
  
      OpenGLFrameBuffer cachedImageFrameBuffer;
      RectangleList<int> validArea;
      Rectangle<int> viewportArea, lastScreenBounds;
      double scale = 1.0;
      AffineTransform transform;
     #if JUCE_OPENGL3
      GLuint vertexArrayObject = 0;
     #endif
  
      StringArray associatedObjectNames;
      ReferenceCountedArray<ReferenceCountedObject> associatedObjects;
  
      WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent;
     #if JUCE_OPENGL_ES
      bool shadersAvailable = true;
     #else
      bool shadersAvailable = false;
     #endif
      bool hasInitialised = false;
      Atomic<int> needsUpdate { 1 }, destroying;
      uint32 lastMMLockReleaseTime = 0;
  
      std::unique_ptr<ThreadPool> renderThread;
      ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue;
      MessageManager::Lock messageManagerLock;
  
     #if JUCE_IOS
      iOSBackgroundProcessCheck backgroundProcessCheck;
     #endif
  
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage)
  };
  
  //==============================================================================
  class OpenGLContext::Attachment  : public ComponentMovementWatcher,
                                     private Timer
  {
  public:
      Attachment (OpenGLContext& c, Component& comp)
         : ComponentMovementWatcher (&comp), context (c)
      {
          if (canBeAttached (comp))
              attach();
      }
  
      ~Attachment()
      {
          detach();
      }
  
      void detach()
      {
          auto& comp = *getComponent();
          stop();
          comp.setCachedComponentImage (nullptr);
          context.nativeContext = nullptr;
      }
  
      void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
      {
          auto& comp = *getComponent();
  
          if (isAttached (comp) != canBeAttached (comp))
              componentVisibilityChanged();
  
          if (comp.getWidth() > 0 && comp.getHeight() > 0
               && context.nativeContext != nullptr)
          {
              if (auto* c = CachedImage::get (comp))
                  c->handleResize();
  
              if (auto* peer = comp.getTopLevelComponent()->getPeer())
                  context.nativeContext->updateWindowPosition (peer->getAreaCoveredBy (comp));
          }
      }
  
      void componentPeerChanged() override
      {
          detach();
          componentVisibilityChanged();
      }
  
      void componentVisibilityChanged() override
      {
          auto& comp = *getComponent();
  
          if (canBeAttached (comp))
          {
              if (isAttached (comp))
                  comp.repaint(); // (needed when windows are un-minimised)
              else
                  attach();
          }
          else
          {
              detach();
          }
      }
  
     #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
      void componentBeingDeleted (Component& c) override
      {
          /* You must call detach() or delete your OpenGLContext to remove it
             from a component BEFORE deleting the component that it is using!
          */
          jassertfalse;
  
          ComponentMovementWatcher::componentBeingDeleted (c);
      }
     #endif
  
      void update()
      {
          auto& comp = *getComponent();
  
          if (canBeAttached (comp))
              start();
          else
              stop();
      }
  
  private:
      OpenGLContext& context;
  
      bool canBeAttached (const Component& comp) noexcept
      {
          return (! context.overrideCanAttach) && comp.getWidth() > 0 && comp.getHeight() > 0 && isShowingOrMinimised (comp);
      }
  
      static bool isShowingOrMinimised (const Component& c)
      {
          if (! c.isVisible())
              return false;
  
          if (auto* p = c.getParentComponent())
              return isShowingOrMinimised (*p);
  
          return c.getPeer() != nullptr;
      }
  
      static bool isAttached (const Component& comp) noexcept
      {
          return comp.getCachedComponentImage() != nullptr;
      }
  
      void attach()
      {
          auto& comp = *getComponent();
          auto* newCachedImage = new CachedImage (context, comp,
                                                  context.openGLPixelFormat,
                                                  context.contextToShareWith);
          comp.setCachedComponentImage (newCachedImage);
  
          start();
      }
  
      void stop()
      {
          stopTimer();
  
          auto& comp = *getComponent();
  
         #if JUCE_MAC
          [[(NSView*) comp.getWindowHandle() window] disableScreenUpdatesUntilFlush];
         #endif
  
          if (auto* oldCachedImage = CachedImage::get (comp))
              oldCachedImage->stop(); // (must stop this before detaching it from the component)
      }
  
      void start()
      {
          auto& comp = *getComponent();
  
          if (auto* cachedImage = CachedImage::get (comp))
          {
              cachedImage->start(); // (must wait until this is attached before starting its thread)
              cachedImage->updateViewportSize (true);
  
              startTimer (400);
          }
      }
  
      void timerCallback() override
      {
          if (auto* cachedImage = CachedImage::get (*getComponent()))
              cachedImage->checkViewportBounds();
      }
  };
  
  //==============================================================================
  OpenGLContext::OpenGLContext()
  {
  }
  
  OpenGLContext::~OpenGLContext()
  {
      detach();
  }
  
  void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse) noexcept
  {
      // This method must not be called when the context has already been attached!
      // Call it before attaching your context, or use detach() first, before calling this!
      jassert (nativeContext == nullptr);
  
      renderer = rendererToUse;
  }
  
  void OpenGLContext::setComponentPaintingEnabled (bool shouldPaintComponent) noexcept
  {
      // This method must not be called when the context has already been attached!
      // Call it before attaching your context, or use detach() first, before calling this!
      jassert (nativeContext == nullptr);
  
      renderComponents = shouldPaintComponent;
  }
  
  void OpenGLContext::setContinuousRepainting (bool shouldContinuouslyRepaint) noexcept
  {
      continuousRepaint = shouldContinuouslyRepaint;
      triggerRepaint();
  }
  
  void OpenGLContext::setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept
  {
      // This method must not be called when the context has already been attached!
      // Call it before attaching your context, or use detach() first, before calling this!
      jassert (nativeContext == nullptr);
  
      openGLPixelFormat = preferredPixelFormat;
  }
  
  void OpenGLContext::setTextureMagnificationFilter (OpenGLContext::TextureMagnificationFilter magFilterMode) noexcept
  {
      texMagFilter = magFilterMode;
  }
  
  void OpenGLContext::setNativeSharedContext (void* nativeContextToShareWith) noexcept
  {
      // This method must not be called when the context has already been attached!
      // Call it before attaching your context, or use detach() first, before calling this!
      jassert (nativeContext == nullptr);
  
      contextToShareWith = nativeContextToShareWith;
  }
  
  void OpenGLContext::setMultisamplingEnabled (bool b) noexcept
  {
      // This method must not be called when the context has already been attached!
      // Call it before attaching your context, or use detach() first, before calling this!
      jassert (nativeContext == nullptr);
  
      useMultisampling = b;
  }
  
  void OpenGLContext::setOpenGLVersionRequired (OpenGLVersion v) noexcept
  {
      versionRequired = v;
  }
  
  void OpenGLContext::attachTo (Component& component)
  {
      component.repaint();
  
      if (getTargetComponent() != &component)
      {
          detach();
          attachment.reset (new Attachment (*this, component));
      }
  }
  
  void OpenGLContext::detach()
  {
      if (auto* a = attachment.get())
      {
          a->detach(); // must detach before nulling our pointer
          attachment.reset();
      }
  
      nativeContext = nullptr;
  }
  
  bool OpenGLContext::isAttached() const noexcept
  {
      return nativeContext != nullptr;
  }
  
  Component* OpenGLContext::getTargetComponent() const noexcept
  {
      return attachment != nullptr ? attachment->getComponent() : nullptr;
  }
  
  OpenGLContext* OpenGLContext::getContextAttachedTo (Component& c) noexcept
  {
      if (auto* ci = CachedImage::get (c))
          return &(ci->context);
  
      return nullptr;
  }
  
  static ThreadLocalValue<OpenGLContext*> currentThreadActiveContext;
  
  OpenGLContext* OpenGLContext::getCurrentContext()
  {
      return currentThreadActiveContext.get();
  }
  
  bool OpenGLContext::makeActive() const noexcept
  {
      auto& current = currentThreadActiveContext.get();
  
      if (nativeContext != nullptr && nativeContext->makeActive())
      {
          current = const_cast<OpenGLContext*> (this);
          return true;
      }
  
      current = nullptr;
      return false;
  }
  
  bool OpenGLContext::isActive() const noexcept
  {
      return nativeContext != nullptr && nativeContext->isActive();
  }
  
  void OpenGLContext::deactivateCurrentContext()
  {
      NativeContext::deactivateCurrentContext();
      currentThreadActiveContext.get() = nullptr;
  }
  
  void OpenGLContext::triggerRepaint()
  {
      if (auto* cachedImage = getCachedImage())
          cachedImage->triggerRepaint();
  }
  
  void OpenGLContext::swapBuffers()
  {
      if (nativeContext != nullptr)
          nativeContext->swapBuffers();
  }
  
  unsigned int OpenGLContext::getFrameBufferID() const noexcept
  {
      return nativeContext != nullptr ? nativeContext->getFrameBufferID() : 0;
  }
  
  bool OpenGLContext::setSwapInterval (int numFramesPerSwap)
  {
      return nativeContext != nullptr && nativeContext->setSwapInterval (numFramesPerSwap);
  }
  
  int OpenGLContext::getSwapInterval() const
  {
      return nativeContext != nullptr ? nativeContext->getSwapInterval() : 0;
  }
  
  void* OpenGLContext::getRawContext() const noexcept
  {
      return nativeContext != nullptr ? nativeContext->getRawContext() : nullptr;
  }
  
  OpenGLContext::CachedImage* OpenGLContext::getCachedImage() const noexcept
  {
      if (auto* comp = getTargetComponent())
          return CachedImage::get (*comp);
  
      return nullptr;
  }
  
  bool OpenGLContext::areShadersAvailable() const
  {
      auto* c = getCachedImage();
      return c != nullptr && c->shadersAvailable;
  }
  
  ReferenceCountedObject* OpenGLContext::getAssociatedObject (const char* name) const
  {
      jassert (name != nullptr);
  
      auto* c = getCachedImage();
  
      // This method must only be called from an openGL rendering callback.
      jassert (c != nullptr && nativeContext != nullptr);
      jassert (getCurrentContext() != nullptr);
  
      auto index = c->associatedObjectNames.indexOf (name);
      return index >= 0 ? c->associatedObjects.getUnchecked (index).get() : nullptr;
  }
  
  void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObject* newObject)
  {
      jassert (name != nullptr);
  
      if (auto* c = getCachedImage())
      {
          // This method must only be called from an openGL rendering callback.
          jassert (nativeContext != nullptr);
          jassert (getCurrentContext() != nullptr);
  
          const int index = c->associatedObjectNames.indexOf (name);
  
          if (index >= 0)
          {
              if (newObject != nullptr)
              {
                  c->associatedObjects.set (index, newObject);
              }
              else
              {
                  c->associatedObjectNames.remove (index);
                  c->associatedObjects.remove (index);
              }
          }
          else if (newObject != nullptr)
          {
              c->associatedObjectNames.add (name);
              c->associatedObjects.add (newObject);
          }
      }
  }
  
  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 (auto* c = getCachedImage())
          c->execute (static_cast<OpenGLContext::AsyncWorker::Ptr&&> (workerToUse), shouldBlock);
      else
          jassertfalse; // You must have attached the context to a component
  }
  
  //==============================================================================
  struct DepthTestDisabler
  {
      DepthTestDisabler() noexcept
      {
          glGetBooleanv (GL_DEPTH_TEST, &wasEnabled);
  
          if (wasEnabled)
              glDisable (GL_DEPTH_TEST);
      }
  
      ~DepthTestDisabler() noexcept
      {
          if (wasEnabled)
              glEnable (GL_DEPTH_TEST);
      }
  
      GLboolean wasEnabled;
  };
  
  //==============================================================================
  void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
                                   const Rectangle<int>& anchorPosAndTextureSize,
                                   const int contextWidth, const int contextHeight,
                                   bool flippedVertically)
  {
      if (contextWidth <= 0 || contextHeight <= 0)
          return;
  
      JUCE_CHECK_OPENGL_ERROR
      glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
      glEnable (GL_BLEND);
  
      DepthTestDisabler depthDisabler;
  
      if (areShadersAvailable())
      {
          struct OverlayShaderProgram  : public ReferenceCountedObject
          {
              OverlayShaderProgram (OpenGLContext& context)
                  : program (context), builder (program), params (program)
              {}
  
              static const OverlayShaderProgram& select (OpenGLContext& context)
              {
                  static const char programValueID[] = "juceGLComponentOverlayShader";
                  OverlayShaderProgram* program = static_cast<OverlayShaderProgram*> (context.getAssociatedObject (programValueID));
  
                  if (program == nullptr)
                  {
                      program = new OverlayShaderProgram (context);
                      context.setAssociatedObject (programValueID, program);
                  }
  
                  program->program.use();
                  return *program;
              }
  
              struct ProgramBuilder
              {
                  ProgramBuilder (OpenGLShaderProgram& prog)
                  {
                      prog.addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
                          "attribute " JUCE_HIGHP " vec2 position;"
                          "uniform " JUCE_HIGHP " vec2 screenSize;"
                          "uniform " JUCE_HIGHP " float textureBounds[4];"
                          "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
                          "varying " JUCE_HIGHP " vec2 texturePos;"
                          "void main()"
                          "{"
                            JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
                            "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
                            "texturePos = (position - vec2 (textureBounds[0], textureBounds[1])) / vec2 (textureBounds[2], textureBounds[3]);"
                            "texturePos = vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y);"
                          "}"));
  
                      prog.addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (
                          "uniform sampler2D imageTexture;"
                          "varying " JUCE_HIGHP " vec2 texturePos;"
                          "void main()"
                          "{"
                            "gl_FragColor = texture2D (imageTexture, texturePos);"
                          "}"));
  
                      prog.link();
                  }
              };
  
              struct Params
              {
                  Params (OpenGLShaderProgram& prog)
                      : positionAttribute (prog, "position"),
                        screenSize (prog, "screenSize"),
                        imageTexture (prog, "imageTexture"),
                        textureBounds (prog, "textureBounds"),
                        vOffsetAndScale (prog, "vOffsetAndScale")
                  {}
  
                  void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds, bool flipVertically) const
                  {
                      const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() };
                      textureBounds.set (m, 4);
                      imageTexture.set (0);
                      screenSize.set (targetWidth, targetHeight);
  
                      vOffsetAndScale.set (flipVertically ? 0.0f : 1.0f,
                                           flipVertically ? 1.0f : -1.0f);
                  }
  
                  OpenGLShaderProgram::Attribute positionAttribute;
                  OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale;
              };
  
              OpenGLShaderProgram program;
              ProgramBuilder builder;
              Params params;
          };
  
          auto left   = (GLshort) targetClipArea.getX();
          auto top    = (GLshort) targetClipArea.getY();
          auto right  = (GLshort) targetClipArea.getRight();
          auto bottom = (GLshort) targetClipArea.getBottom();
          const GLshort vertices[] = { left, bottom, right, bottom, left, top, right, top };
  
          auto& program = OverlayShaderProgram::select (*this);
          program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat(), flippedVertically);
  
          GLuint vertexBuffer = 0;
          extensions.glGenBuffers (1, &vertexBuffer);
          extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
          extensions.glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
  
          auto index = (GLuint) program.params.positionAttribute.attributeID;
          extensions.glVertexAttribPointer (index, 2, GL_SHORT, GL_FALSE, 4, 0);
          extensions.glEnableVertexAttribArray (index);
          JUCE_CHECK_OPENGL_ERROR
  
          glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
  
          extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
          extensions.glUseProgram (0);
          extensions.glDisableVertexAttribArray (index);
          extensions.glDeleteBuffers (1, &vertexBuffer);
      }
      else
      {
          jassert (attachment == nullptr); // Running on an old graphics card!
      }
  
      JUCE_CHECK_OPENGL_ERROR
  }
  
  #if JUCE_ANDROID
  EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
  EGLDisplay OpenGLContext::NativeContext::config;
  
  void OpenGLContext::NativeContext::surfaceCreated (jobject holder)
  {
      ignoreUnused (holder);
  
      if (auto* cachedImage = CachedImage::get (component))
      {
          if (auto* pool = cachedImage->renderThread.get())
          {
              if (! pool->contains (cachedImage))
              {
                  cachedImage->resume();
                  cachedImage->context.triggerRepaint();
              }
          }
      }
  }
  
  void OpenGLContext::NativeContext::surfaceDestroyed (jobject holder)
  {
      ignoreUnused (holder);
  
      // unlike the name suggests this will be called just before the
      // surface is destroyed. We need to pause the render thread.
      if (auto* cachedImage = CachedImage::get (component))
      {
          cachedImage->pause();
  
          if (auto* threadPool = cachedImage->renderThread.get())
              threadPool->waitForJobToFinish (cachedImage, -1);
      }
  }
  #endif
  
  } // namespace juce
  