The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1524 lines
47KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. #if JUCE_IOS
  21. struct AppInactivityCallback // NB: this is a duplicate of an internal declaration in juce_core
  22. {
  23. virtual ~AppInactivityCallback() {}
  24. virtual void appBecomingInactive() = 0;
  25. };
  26. extern Array<AppInactivityCallback*> appBecomingInactiveCallbacks;
  27. // On iOS, all GL calls will crash when the app is running in the background, so
  28. // this prevents them from happening (which some messy locking behaviour)
  29. struct iOSBackgroundProcessCheck : public AppInactivityCallback
  30. {
  31. iOSBackgroundProcessCheck() { isBackgroundProcess(); appBecomingInactiveCallbacks.add (this); }
  32. ~iOSBackgroundProcessCheck() override { appBecomingInactiveCallbacks.removeAllInstancesOf (this); }
  33. bool isBackgroundProcess()
  34. {
  35. const bool b = Process::isForegroundProcess();
  36. isForeground.set (b ? 1 : 0);
  37. return ! b;
  38. }
  39. void appBecomingInactive() override
  40. {
  41. int counter = 2000;
  42. while (--counter > 0 && isForeground.get() != 0)
  43. Thread::sleep (1);
  44. }
  45. private:
  46. Atomic<int> isForeground;
  47. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSBackgroundProcessCheck)
  48. };
  49. #endif
  50. #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE
  51. extern JUCE_API double getScaleFactorForWindow (HWND);
  52. #endif
  53. static bool contextHasTextureNpotFeature()
  54. {
  55. if (getOpenGLVersion() >= Version (2))
  56. return true;
  57. // If the version is < 2, we can't use the newer extension-checking API
  58. // so we have to use glGetString
  59. const auto* extensionsBegin = glGetString (GL_EXTENSIONS);
  60. if (extensionsBegin == nullptr)
  61. return false;
  62. const auto* extensionsEnd = findNullTerminator (extensionsBegin);
  63. const std::string extensionsString (extensionsBegin, extensionsEnd);
  64. const auto stringTokens = StringArray::fromTokens (extensionsString.c_str(), false);
  65. return stringTokens.contains ("GL_ARB_texture_non_power_of_two");
  66. }
  67. //==============================================================================
  68. class OpenGLContext::CachedImage : public CachedComponentImage,
  69. private ThreadPoolJob
  70. {
  71. struct AreaAndScale
  72. {
  73. Rectangle<int> area;
  74. double scale;
  75. auto tie() const { return std::tie (area, scale); }
  76. auto operator== (const AreaAndScale& other) const { return tie() == other.tie(); }
  77. auto operator!= (const AreaAndScale& other) const { return tie() != other.tie(); }
  78. };
  79. class LockedAreaAndScale
  80. {
  81. public:
  82. auto get() const
  83. {
  84. const ScopedLock lock (mutex);
  85. return data;
  86. }
  87. template <typename Fn>
  88. void set (const AreaAndScale& d, Fn&& ifDifferent)
  89. {
  90. const auto old = [&]
  91. {
  92. const ScopedLock lock (mutex);
  93. return std::exchange (data, d);
  94. }();
  95. if (old != d)
  96. ifDifferent();
  97. }
  98. private:
  99. CriticalSection mutex;
  100. AreaAndScale data { {}, 1.0 };
  101. };
  102. public:
  103. CachedImage (OpenGLContext& c, Component& comp,
  104. const OpenGLPixelFormat& pixFormat, void* contextToShare)
  105. : ThreadPoolJob ("OpenGL Rendering"),
  106. context (c),
  107. component (comp)
  108. {
  109. nativeContext.reset (new NativeContext (component, pixFormat, contextToShare,
  110. c.useMultisampling, c.versionRequired));
  111. if (nativeContext->createdOk())
  112. context.nativeContext = nativeContext.get();
  113. else
  114. nativeContext.reset();
  115. }
  116. ~CachedImage() override
  117. {
  118. stop();
  119. }
  120. //==============================================================================
  121. void start()
  122. {
  123. if (nativeContext != nullptr)
  124. {
  125. #if JUCE_MAC
  126. cvDisplayLinkWrapper = std::make_unique<CVDisplayLinkWrapper> (*this);
  127. cvDisplayLinkWrapper->updateActiveDisplay();
  128. #endif
  129. renderThread = std::make_unique<ThreadPool> (1);
  130. resume();
  131. }
  132. }
  133. void stop()
  134. {
  135. if (renderThread != nullptr)
  136. {
  137. // make sure everything has finished executing
  138. destroying = true;
  139. if (workQueue.size() > 0)
  140. {
  141. if (! renderThread->contains (this))
  142. resume();
  143. while (workQueue.size() != 0)
  144. Thread::sleep (20);
  145. }
  146. pause();
  147. renderThread.reset();
  148. }
  149. #if JUCE_MAC
  150. cvDisplayLinkWrapper = nullptr;
  151. #endif
  152. hasInitialised = false;
  153. }
  154. //==============================================================================
  155. void pause()
  156. {
  157. signalJobShouldExit();
  158. messageManagerLock.abort();
  159. if (renderThread != nullptr)
  160. {
  161. repaintEvent.signal();
  162. renderThread->removeJob (this, true, -1);
  163. }
  164. }
  165. void resume()
  166. {
  167. if (renderThread != nullptr)
  168. renderThread->addJob (this, false);
  169. }
  170. //==============================================================================
  171. void paint (Graphics&) override
  172. {
  173. updateViewportSize();
  174. }
  175. bool invalidateAll() override
  176. {
  177. validArea.clear();
  178. triggerRepaint();
  179. return false;
  180. }
  181. bool invalidate (const Rectangle<int>& area) override
  182. {
  183. validArea.subtract (area.toFloat().transformedBy (transform).getSmallestIntegerContainer());
  184. triggerRepaint();
  185. return false;
  186. }
  187. void releaseResources() override
  188. {
  189. stop();
  190. }
  191. void triggerRepaint()
  192. {
  193. needsUpdate = 1;
  194. repaintEvent.signal();
  195. }
  196. //==============================================================================
  197. bool ensureFrameBufferSize (Rectangle<int> viewportArea)
  198. {
  199. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  200. auto fbW = cachedImageFrameBuffer.getWidth();
  201. auto fbH = cachedImageFrameBuffer.getHeight();
  202. if (fbW != viewportArea.getWidth() || fbH != viewportArea.getHeight() || ! cachedImageFrameBuffer.isValid())
  203. {
  204. if (! cachedImageFrameBuffer.initialise (context, viewportArea.getWidth(), viewportArea.getHeight()))
  205. return false;
  206. validArea.clear();
  207. JUCE_CHECK_OPENGL_ERROR
  208. }
  209. return true;
  210. }
  211. void clearRegionInFrameBuffer (const RectangleList<int>& list)
  212. {
  213. glClearColor (0, 0, 0, 0);
  214. glEnable (GL_SCISSOR_TEST);
  215. auto previousFrameBufferTarget = OpenGLFrameBuffer::getCurrentFrameBufferTarget();
  216. cachedImageFrameBuffer.makeCurrentRenderingTarget();
  217. auto imageH = cachedImageFrameBuffer.getHeight();
  218. for (auto& r : list)
  219. {
  220. glScissor (r.getX(), imageH - r.getBottom(), r.getWidth(), r.getHeight());
  221. glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  222. }
  223. glDisable (GL_SCISSOR_TEST);
  224. context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, previousFrameBufferTarget);
  225. JUCE_CHECK_OPENGL_ERROR
  226. }
  227. bool renderFrame()
  228. {
  229. MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
  230. auto isUpdatingTestValue = true;
  231. auto isUpdating = needsUpdate.compare_exchange_strong (isUpdatingTestValue, false);
  232. if (context.renderComponents && isUpdating)
  233. {
  234. // This avoids hogging the message thread when doing intensive rendering.
  235. if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter())
  236. Thread::sleep (2);
  237. while (! shouldExit())
  238. {
  239. doWorkWhileWaitingForLock (false);
  240. if (mmLock.retryLock())
  241. break;
  242. }
  243. if (shouldExit())
  244. return false;
  245. }
  246. if (! context.makeActive())
  247. return false;
  248. NativeContext::Locker locker (*nativeContext);
  249. JUCE_CHECK_OPENGL_ERROR
  250. doWorkWhileWaitingForLock (true);
  251. const auto currentAreaAndScale = areaAndScale.get();
  252. const auto viewportArea = currentAreaAndScale.area;
  253. if (context.renderer != nullptr)
  254. {
  255. glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
  256. context.currentRenderScale = currentAreaAndScale.scale;
  257. context.renderer->renderOpenGL();
  258. clearGLError();
  259. bindVertexArray();
  260. }
  261. if (context.renderComponents)
  262. {
  263. if (isUpdating)
  264. {
  265. paintComponent (currentAreaAndScale);
  266. if (! hasInitialised)
  267. return false;
  268. messageManagerLock.exit();
  269. lastMMLockReleaseTime = Time::getMillisecondCounter();
  270. }
  271. glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
  272. drawComponentBuffer();
  273. }
  274. context.swapBuffers();
  275. OpenGLContext::deactivateCurrentContext();
  276. return true;
  277. }
  278. void updateViewportSize()
  279. {
  280. JUCE_ASSERT_MESSAGE_THREAD
  281. if (auto* peer = component.getPeer())
  282. {
  283. #if JUCE_MAC
  284. const auto displayScale = Desktop::getInstance().getGlobalScaleFactor() * [this]
  285. {
  286. if (auto* wrapper = cvDisplayLinkWrapper.get())
  287. if (wrapper->updateActiveDisplay())
  288. nativeContext->setNominalVideoRefreshPeriodS (wrapper->getNominalVideoRefreshPeriodS());
  289. if (auto* view = getCurrentView())
  290. {
  291. if ([view respondsToSelector: @selector (backingScaleFactor)])
  292. return [(id) view backingScaleFactor];
  293. if (auto* window = [view window])
  294. return [window backingScaleFactor];
  295. }
  296. return areaAndScale.get().scale;
  297. }();
  298. #else
  299. const auto displayScale = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds())->scale;
  300. #endif
  301. auto localBounds = component.getLocalBounds();
  302. auto newArea = peer->getComponent().getLocalArea (&component, localBounds).withZeroOrigin() * displayScale;
  303. #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE
  304. auto newScale = getScaleFactorForWindow (nativeContext->getNativeHandle());
  305. auto desktopScale = Desktop::getInstance().getGlobalScaleFactor();
  306. if (! approximatelyEqual (1.0f, desktopScale))
  307. newScale *= desktopScale;
  308. #else
  309. auto newScale = displayScale;
  310. #endif
  311. areaAndScale.set ({ newArea, newScale }, [&]
  312. {
  313. // Transform is only accessed when the message manager is locked
  314. transform = AffineTransform::scale ((float) newArea.getWidth() / (float) localBounds.getWidth(),
  315. (float) newArea.getHeight() / (float) localBounds.getHeight());
  316. nativeContext->updateWindowPosition (peer->getAreaCoveredBy (component));
  317. invalidateAll();
  318. });
  319. }
  320. }
  321. void bindVertexArray() noexcept
  322. {
  323. if (shouldUseCustomVAO())
  324. if (vertexArrayObject != 0)
  325. context.extensions.glBindVertexArray (vertexArrayObject);
  326. }
  327. void checkViewportBounds()
  328. {
  329. auto screenBounds = component.getTopLevelComponent()->getScreenBounds();
  330. if (lastScreenBounds != screenBounds)
  331. {
  332. updateViewportSize();
  333. lastScreenBounds = screenBounds;
  334. }
  335. }
  336. void paintComponent (const AreaAndScale& currentAreaAndScale)
  337. {
  338. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  339. // you mustn't set your own cached image object when attaching a GL context!
  340. jassert (get (component) == this);
  341. if (! ensureFrameBufferSize (currentAreaAndScale.area))
  342. return;
  343. RectangleList<int> invalid (currentAreaAndScale.area);
  344. invalid.subtract (validArea);
  345. validArea = currentAreaAndScale.area;
  346. if (! invalid.isEmpty())
  347. {
  348. clearRegionInFrameBuffer (invalid);
  349. {
  350. std::unique_ptr<LowLevelGraphicsContext> g (createOpenGLGraphicsContext (context, cachedImageFrameBuffer));
  351. g->clipToRectangleList (invalid);
  352. g->addTransform (transform);
  353. paintOwner (*g);
  354. JUCE_CHECK_OPENGL_ERROR
  355. }
  356. if (! context.isActive())
  357. context.makeActive();
  358. }
  359. JUCE_CHECK_OPENGL_ERROR
  360. }
  361. void drawComponentBuffer()
  362. {
  363. if (contextRequiresTexture2DEnableDisable())
  364. glEnable (GL_TEXTURE_2D);
  365. #if JUCE_WINDOWS
  366. // some stupidly old drivers are missing this function, so try to at least avoid a crash here,
  367. // but if you hit this assertion you may want to have your own version check before using the
  368. // component rendering stuff on such old drivers.
  369. jassert (context.extensions.glActiveTexture != nullptr);
  370. if (context.extensions.glActiveTexture != nullptr)
  371. #endif
  372. {
  373. context.extensions.glActiveTexture (GL_TEXTURE0);
  374. }
  375. glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID());
  376. bindVertexArray();
  377. const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight());
  378. context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight(), false);
  379. glBindTexture (GL_TEXTURE_2D, 0);
  380. JUCE_CHECK_OPENGL_ERROR
  381. }
  382. void paintOwner (LowLevelGraphicsContext& llgc)
  383. {
  384. Graphics g (llgc);
  385. #if JUCE_ENABLE_REPAINT_DEBUGGING
  386. #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
  387. if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
  388. #endif
  389. {
  390. g.saveState();
  391. }
  392. #endif
  393. JUCE_TRY
  394. {
  395. component.paintEntireComponent (g, false);
  396. }
  397. JUCE_CATCH_EXCEPTION
  398. #if JUCE_ENABLE_REPAINT_DEBUGGING
  399. #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
  400. if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
  401. #endif
  402. {
  403. // enabling this code will fill all areas that get repainted with a colour overlay, to show
  404. // clearly when things are being repainted.
  405. g.restoreState();
  406. static Random rng;
  407. g.fillAll (Colour ((uint8) rng.nextInt (255),
  408. (uint8) rng.nextInt (255),
  409. (uint8) rng.nextInt (255),
  410. (uint8) 0x50));
  411. }
  412. #endif
  413. }
  414. void handleResize()
  415. {
  416. updateViewportSize();
  417. #if JUCE_MAC
  418. if (hasInitialised)
  419. {
  420. [nativeContext->view update];
  421. renderFrame();
  422. }
  423. #endif
  424. }
  425. //==============================================================================
  426. JobStatus runJob() override
  427. {
  428. {
  429. // Allow the message thread to finish setting-up the context before using it.
  430. MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
  431. do
  432. {
  433. if (shouldExit())
  434. return ThreadPoolJob::jobHasFinished;
  435. } while (! mmLock.retryLock());
  436. }
  437. if (! initialiseOnThread())
  438. {
  439. hasInitialised = false;
  440. return ThreadPoolJob::jobHasFinished;
  441. }
  442. hasInitialised = true;
  443. while (! shouldExit())
  444. {
  445. #if JUCE_IOS
  446. if (backgroundProcessCheck.isBackgroundProcess())
  447. {
  448. repaintEvent.wait (300);
  449. repaintEvent.reset();
  450. continue;
  451. }
  452. #endif
  453. if (shouldExit())
  454. break;
  455. #if JUCE_MAC
  456. if (context.continuousRepaint)
  457. {
  458. repaintEvent.wait (-1);
  459. renderFrame();
  460. }
  461. else
  462. #endif
  463. if (! renderFrame())
  464. repaintEvent.wait (5); // failed to render, so avoid a tight fail-loop.
  465. else if (! context.continuousRepaint && ! shouldExit())
  466. repaintEvent.wait (-1);
  467. repaintEvent.reset();
  468. }
  469. hasInitialised = false;
  470. context.makeActive();
  471. shutdownOnThread();
  472. OpenGLContext::deactivateCurrentContext();
  473. return ThreadPoolJob::jobHasFinished;
  474. }
  475. bool initialiseOnThread()
  476. {
  477. // On android, this can get called twice, so drop any previous state.
  478. associatedObjectNames.clear();
  479. associatedObjects.clear();
  480. cachedImageFrameBuffer.release();
  481. context.makeActive();
  482. if (! nativeContext->initialiseOnRenderThread (context))
  483. return false;
  484. #if JUCE_ANDROID
  485. // On android the context may be created in initialiseOnRenderThread
  486. // and we therefore need to call makeActive again
  487. context.makeActive();
  488. #endif
  489. gl::loadFunctions();
  490. if (shouldUseCustomVAO())
  491. {
  492. context.extensions.glGenVertexArrays (1, &vertexArrayObject);
  493. bindVertexArray();
  494. }
  495. #if JUCE_DEBUG
  496. if (getOpenGLVersion() >= Version { 4, 3 } && glDebugMessageCallback != nullptr)
  497. {
  498. glEnable (GL_DEBUG_OUTPUT);
  499. glDebugMessageCallback ([] (GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message, const void*)
  500. {
  501. // This may reiterate issues that are also flagged by JUCE_CHECK_OPENGL_ERROR.
  502. // The advantage of this callback is that it will catch *all* errors, even if we
  503. // forget to check manually.
  504. DBG ("OpenGL DBG message: " << message);
  505. jassertfalse;
  506. }, nullptr);
  507. }
  508. #endif
  509. const auto currentViewportArea = areaAndScale.get().area;
  510. glViewport (0, 0, currentViewportArea.getWidth(), currentViewportArea.getHeight());
  511. nativeContext->setSwapInterval (1);
  512. #if ! JUCE_OPENGL_ES
  513. JUCE_CHECK_OPENGL_ERROR
  514. shadersAvailable = OpenGLShaderProgram::getLanguageVersion() > 0;
  515. clearGLError();
  516. #endif
  517. textureNpotSupported = contextHasTextureNpotFeature();
  518. if (context.renderer != nullptr)
  519. context.renderer->newOpenGLContextCreated();
  520. #if JUCE_MAC
  521. jassert (cvDisplayLinkWrapper != nullptr);
  522. nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS());
  523. #endif
  524. return true;
  525. }
  526. void shutdownOnThread()
  527. {
  528. if (context.renderer != nullptr)
  529. context.renderer->openGLContextClosing();
  530. if (vertexArrayObject != 0)
  531. context.extensions.glDeleteVertexArrays (1, &vertexArrayObject);
  532. associatedObjectNames.clear();
  533. associatedObjects.clear();
  534. cachedImageFrameBuffer.release();
  535. nativeContext->shutdownOnRenderThread();
  536. }
  537. /* Returns true if the context requires a non-zero vertex array object (VAO) to be bound.
  538. If the context is a compatibility context, we can just pretend that VAOs don't exist,
  539. and use the default VAO all the time instead. This provides a more consistent experience
  540. in user code, which might make calls (like glVertexPointer()) that only work when VAO 0 is
  541. bound in OpenGL 3.2+.
  542. */
  543. bool shouldUseCustomVAO() const
  544. {
  545. #if JUCE_OPENGL_ES
  546. return false;
  547. #else
  548. clearGLError();
  549. GLint mask = 0;
  550. glGetIntegerv (GL_CONTEXT_PROFILE_MASK, &mask);
  551. // The context isn't aware of the profile mask, so it pre-dates the core profile
  552. if (glGetError() == GL_INVALID_ENUM)
  553. return false;
  554. // Also assumes a compatibility profile if the mask is completely empty for some reason
  555. return (mask & (GLint) GL_CONTEXT_CORE_PROFILE_BIT) != 0;
  556. #endif
  557. }
  558. //==============================================================================
  559. struct BlockingWorker : public OpenGLContext::AsyncWorker
  560. {
  561. BlockingWorker (OpenGLContext::AsyncWorker::Ptr && workerToUse)
  562. : originalWorker (std::move (workerToUse))
  563. {}
  564. void operator() (OpenGLContext& calleeContext)
  565. {
  566. if (originalWorker != nullptr)
  567. (*originalWorker) (calleeContext);
  568. finishedSignal.signal();
  569. }
  570. void block() noexcept { finishedSignal.wait(); }
  571. OpenGLContext::AsyncWorker::Ptr originalWorker;
  572. WaitableEvent finishedSignal;
  573. };
  574. bool doWorkWhileWaitingForLock (bool contextIsAlreadyActive)
  575. {
  576. bool contextActivated = false;
  577. for (OpenGLContext::AsyncWorker::Ptr work = workQueue.removeAndReturn (0);
  578. work != nullptr && (! shouldExit()); work = workQueue.removeAndReturn (0))
  579. {
  580. if ((! contextActivated) && (! contextIsAlreadyActive))
  581. {
  582. if (! context.makeActive())
  583. break;
  584. contextActivated = true;
  585. }
  586. NativeContext::Locker locker (*nativeContext);
  587. (*work) (context);
  588. clearGLError();
  589. }
  590. if (contextActivated)
  591. OpenGLContext::deactivateCurrentContext();
  592. return shouldExit();
  593. }
  594. void execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock, bool calledFromDestructor = false)
  595. {
  596. if (calledFromDestructor || ! destroying)
  597. {
  598. if (shouldBlock)
  599. {
  600. auto blocker = new BlockingWorker (std::move (workerToUse));
  601. OpenGLContext::AsyncWorker::Ptr worker (*blocker);
  602. workQueue.add (worker);
  603. messageManagerLock.abort();
  604. context.triggerRepaint();
  605. blocker->block();
  606. }
  607. else
  608. {
  609. workQueue.add (std::move (workerToUse));
  610. messageManagerLock.abort();
  611. context.triggerRepaint();
  612. }
  613. }
  614. else
  615. {
  616. jassertfalse; // you called execute AFTER you detached your OpenGLContext
  617. }
  618. }
  619. //==============================================================================
  620. static CachedImage* get (Component& c) noexcept
  621. {
  622. return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
  623. }
  624. //==============================================================================
  625. friend class NativeContext;
  626. std::unique_ptr<NativeContext> nativeContext;
  627. OpenGLContext& context;
  628. Component& component;
  629. OpenGLFrameBuffer cachedImageFrameBuffer;
  630. RectangleList<int> validArea;
  631. Rectangle<int> lastScreenBounds;
  632. AffineTransform transform;
  633. GLuint vertexArrayObject = 0;
  634. LockedAreaAndScale areaAndScale;
  635. StringArray associatedObjectNames;
  636. ReferenceCountedArray<ReferenceCountedObject> associatedObjects;
  637. WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent { true };
  638. #if JUCE_OPENGL_ES
  639. bool shadersAvailable = true;
  640. #else
  641. bool shadersAvailable = false;
  642. #endif
  643. bool textureNpotSupported = false;
  644. std::atomic<bool> hasInitialised { false }, needsUpdate { true }, destroying { false };
  645. uint32 lastMMLockReleaseTime = 0;
  646. #if JUCE_MAC
  647. NSView* getCurrentView() const
  648. {
  649. if (auto* peer = component.getPeer())
  650. return static_cast<NSView*> (peer->getNativeHandle());
  651. return nullptr;
  652. }
  653. NSScreen* getCurrentScreen() const
  654. {
  655. JUCE_ASSERT_MESSAGE_THREAD;
  656. if (auto* view = getCurrentView())
  657. if (auto* window = [view window])
  658. return [window screen];
  659. return nullptr;
  660. }
  661. struct CVDisplayLinkWrapper
  662. {
  663. explicit CVDisplayLinkWrapper (CachedImage& cachedImageIn)
  664. : cachedImage (cachedImageIn),
  665. continuousRepaint (cachedImageIn.context.continuousRepaint.load())
  666. {
  667. CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
  668. CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
  669. CVDisplayLinkStart (displayLink);
  670. }
  671. double getNominalVideoRefreshPeriodS() const
  672. {
  673. const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink);
  674. if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
  675. return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
  676. return 0.0;
  677. }
  678. /* Returns true if updated, or false otherwise. */
  679. bool updateActiveDisplay()
  680. {
  681. auto* oldScreen = std::exchange (currentScreen, cachedImage.getCurrentScreen());
  682. if (oldScreen == currentScreen)
  683. return false;
  684. for (NSScreen* screen in [NSScreen screens])
  685. if (screen == currentScreen)
  686. if (NSNumber* number = [[screen deviceDescription] objectForKey: @"NSScreenNumber"])
  687. CVDisplayLinkSetCurrentCGDisplay (displayLink, [number unsignedIntValue]);
  688. return true;
  689. }
  690. ~CVDisplayLinkWrapper()
  691. {
  692. CVDisplayLinkStop (displayLink);
  693. CVDisplayLinkRelease (displayLink);
  694. }
  695. static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
  696. CVOptionFlags, CVOptionFlags*, void* displayLinkContext)
  697. {
  698. auto* self = reinterpret_cast<CVDisplayLinkWrapper*> (displayLinkContext);
  699. if (self->continuousRepaint)
  700. self->cachedImage.repaintEvent.signal();
  701. return kCVReturnSuccess;
  702. }
  703. CachedImage& cachedImage;
  704. const bool continuousRepaint;
  705. CVDisplayLinkRef displayLink;
  706. NSScreen* currentScreen = nullptr;
  707. };
  708. std::unique_ptr<CVDisplayLinkWrapper> cvDisplayLinkWrapper;
  709. #endif
  710. std::unique_ptr<ThreadPool> renderThread;
  711. ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue;
  712. MessageManager::Lock messageManagerLock;
  713. #if JUCE_IOS
  714. iOSBackgroundProcessCheck backgroundProcessCheck;
  715. #endif
  716. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage)
  717. };
  718. //==============================================================================
  719. class OpenGLContext::Attachment : public ComponentMovementWatcher,
  720. private Timer
  721. {
  722. public:
  723. Attachment (OpenGLContext& c, Component& comp)
  724. : ComponentMovementWatcher (&comp), context (c)
  725. {
  726. if (canBeAttached (comp))
  727. attach();
  728. }
  729. ~Attachment() override
  730. {
  731. detach();
  732. }
  733. void detach()
  734. {
  735. auto& comp = *getComponent();
  736. stop();
  737. comp.setCachedComponentImage (nullptr);
  738. context.nativeContext = nullptr;
  739. }
  740. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  741. {
  742. auto& comp = *getComponent();
  743. if (isAttached (comp) != canBeAttached (comp))
  744. componentVisibilityChanged();
  745. if (comp.getWidth() > 0 && comp.getHeight() > 0
  746. && context.nativeContext != nullptr)
  747. {
  748. if (auto* c = CachedImage::get (comp))
  749. c->handleResize();
  750. if (auto* peer = comp.getTopLevelComponent()->getPeer())
  751. context.nativeContext->updateWindowPosition (peer->getAreaCoveredBy (comp));
  752. }
  753. }
  754. using ComponentMovementWatcher::componentMovedOrResized;
  755. void componentPeerChanged() override
  756. {
  757. detach();
  758. componentVisibilityChanged();
  759. }
  760. void componentVisibilityChanged() override
  761. {
  762. auto& comp = *getComponent();
  763. if (canBeAttached (comp))
  764. {
  765. if (isAttached (comp))
  766. comp.repaint(); // (needed when windows are un-minimised)
  767. else
  768. attach();
  769. }
  770. else
  771. {
  772. detach();
  773. }
  774. }
  775. using ComponentMovementWatcher::componentVisibilityChanged;
  776. #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
  777. void componentBeingDeleted (Component& c) override
  778. {
  779. /* You must call detach() or delete your OpenGLContext to remove it
  780. from a component BEFORE deleting the component that it is using!
  781. */
  782. jassertfalse;
  783. ComponentMovementWatcher::componentBeingDeleted (c);
  784. }
  785. #endif
  786. void update()
  787. {
  788. auto& comp = *getComponent();
  789. if (canBeAttached (comp))
  790. start();
  791. else
  792. stop();
  793. }
  794. private:
  795. OpenGLContext& context;
  796. bool canBeAttached (const Component& comp) noexcept
  797. {
  798. return (! context.overrideCanAttach) && comp.getWidth() > 0 && comp.getHeight() > 0 && isShowingOrMinimised (comp);
  799. }
  800. static bool isShowingOrMinimised (const Component& c)
  801. {
  802. if (! c.isVisible())
  803. return false;
  804. if (auto* p = c.getParentComponent())
  805. return isShowingOrMinimised (*p);
  806. return c.getPeer() != nullptr;
  807. }
  808. static bool isAttached (const Component& comp) noexcept
  809. {
  810. return comp.getCachedComponentImage() != nullptr;
  811. }
  812. void attach()
  813. {
  814. auto& comp = *getComponent();
  815. auto* newCachedImage = new CachedImage (context, comp,
  816. context.openGLPixelFormat,
  817. context.contextToShareWith);
  818. comp.setCachedComponentImage (newCachedImage);
  819. start();
  820. }
  821. void stop()
  822. {
  823. stopTimer();
  824. auto& comp = *getComponent();
  825. #if JUCE_MAC
  826. [[(NSView*) comp.getWindowHandle() window] disableScreenUpdatesUntilFlush];
  827. #endif
  828. if (auto* oldCachedImage = CachedImage::get (comp))
  829. oldCachedImage->stop(); // (must stop this before detaching it from the component)
  830. }
  831. void start()
  832. {
  833. auto& comp = *getComponent();
  834. if (auto* cachedImage = CachedImage::get (comp))
  835. {
  836. cachedImage->start(); // (must wait until this is attached before starting its thread)
  837. cachedImage->updateViewportSize();
  838. startTimer (400);
  839. }
  840. }
  841. void timerCallback() override
  842. {
  843. if (auto* cachedImage = CachedImage::get (*getComponent()))
  844. cachedImage->checkViewportBounds();
  845. }
  846. };
  847. //==============================================================================
  848. OpenGLContext::OpenGLContext()
  849. {
  850. }
  851. OpenGLContext::~OpenGLContext()
  852. {
  853. detach();
  854. }
  855. void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse) noexcept
  856. {
  857. // This method must not be called when the context has already been attached!
  858. // Call it before attaching your context, or use detach() first, before calling this!
  859. jassert (nativeContext == nullptr);
  860. renderer = rendererToUse;
  861. }
  862. void OpenGLContext::setComponentPaintingEnabled (bool shouldPaintComponent) noexcept
  863. {
  864. // This method must not be called when the context has already been attached!
  865. // Call it before attaching your context, or use detach() first, before calling this!
  866. jassert (nativeContext == nullptr);
  867. renderComponents = shouldPaintComponent;
  868. }
  869. void OpenGLContext::setContinuousRepainting (bool shouldContinuouslyRepaint) noexcept
  870. {
  871. continuousRepaint = shouldContinuouslyRepaint;
  872. #if JUCE_MAC
  873. if (auto* component = getTargetComponent())
  874. {
  875. detach();
  876. attachment.reset (new Attachment (*this, *component));
  877. }
  878. #endif
  879. triggerRepaint();
  880. }
  881. void OpenGLContext::setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept
  882. {
  883. // This method must not be called when the context has already been attached!
  884. // Call it before attaching your context, or use detach() first, before calling this!
  885. jassert (nativeContext == nullptr);
  886. openGLPixelFormat = preferredPixelFormat;
  887. }
  888. void OpenGLContext::setTextureMagnificationFilter (OpenGLContext::TextureMagnificationFilter magFilterMode) noexcept
  889. {
  890. texMagFilter = magFilterMode;
  891. }
  892. void OpenGLContext::setNativeSharedContext (void* nativeContextToShareWith) noexcept
  893. {
  894. // This method must not be called when the context has already been attached!
  895. // Call it before attaching your context, or use detach() first, before calling this!
  896. jassert (nativeContext == nullptr);
  897. contextToShareWith = nativeContextToShareWith;
  898. }
  899. void OpenGLContext::setMultisamplingEnabled (bool b) noexcept
  900. {
  901. // This method must not be called when the context has already been attached!
  902. // Call it before attaching your context, or use detach() first, before calling this!
  903. jassert (nativeContext == nullptr);
  904. useMultisampling = b;
  905. }
  906. void OpenGLContext::setOpenGLVersionRequired (OpenGLVersion v) noexcept
  907. {
  908. versionRequired = v;
  909. }
  910. void OpenGLContext::attachTo (Component& component)
  911. {
  912. component.repaint();
  913. if (getTargetComponent() != &component)
  914. {
  915. detach();
  916. attachment.reset (new Attachment (*this, component));
  917. }
  918. }
  919. void OpenGLContext::detach()
  920. {
  921. if (auto* a = attachment.get())
  922. {
  923. a->detach(); // must detach before nulling our pointer
  924. attachment.reset();
  925. }
  926. nativeContext = nullptr;
  927. }
  928. bool OpenGLContext::isAttached() const noexcept
  929. {
  930. return nativeContext != nullptr;
  931. }
  932. Component* OpenGLContext::getTargetComponent() const noexcept
  933. {
  934. return attachment != nullptr ? attachment->getComponent() : nullptr;
  935. }
  936. OpenGLContext* OpenGLContext::getContextAttachedTo (Component& c) noexcept
  937. {
  938. if (auto* ci = CachedImage::get (c))
  939. return &(ci->context);
  940. return nullptr;
  941. }
  942. static ThreadLocalValue<OpenGLContext*> currentThreadActiveContext;
  943. OpenGLContext* OpenGLContext::getCurrentContext()
  944. {
  945. return currentThreadActiveContext.get();
  946. }
  947. bool OpenGLContext::makeActive() const noexcept
  948. {
  949. auto& current = currentThreadActiveContext.get();
  950. if (nativeContext != nullptr && nativeContext->makeActive())
  951. {
  952. current = const_cast<OpenGLContext*> (this);
  953. return true;
  954. }
  955. current = nullptr;
  956. return false;
  957. }
  958. bool OpenGLContext::isActive() const noexcept
  959. {
  960. return nativeContext != nullptr && nativeContext->isActive();
  961. }
  962. void OpenGLContext::deactivateCurrentContext()
  963. {
  964. NativeContext::deactivateCurrentContext();
  965. currentThreadActiveContext.get() = nullptr;
  966. }
  967. void OpenGLContext::triggerRepaint()
  968. {
  969. if (auto* cachedImage = getCachedImage())
  970. cachedImage->triggerRepaint();
  971. }
  972. void OpenGLContext::swapBuffers()
  973. {
  974. if (nativeContext != nullptr)
  975. nativeContext->swapBuffers();
  976. }
  977. unsigned int OpenGLContext::getFrameBufferID() const noexcept
  978. {
  979. return nativeContext != nullptr ? nativeContext->getFrameBufferID() : 0;
  980. }
  981. bool OpenGLContext::setSwapInterval (int numFramesPerSwap)
  982. {
  983. return nativeContext != nullptr && nativeContext->setSwapInterval (numFramesPerSwap);
  984. }
  985. int OpenGLContext::getSwapInterval() const
  986. {
  987. return nativeContext != nullptr ? nativeContext->getSwapInterval() : 0;
  988. }
  989. void* OpenGLContext::getRawContext() const noexcept
  990. {
  991. return nativeContext != nullptr ? nativeContext->getRawContext() : nullptr;
  992. }
  993. OpenGLContext::CachedImage* OpenGLContext::getCachedImage() const noexcept
  994. {
  995. if (auto* comp = getTargetComponent())
  996. return CachedImage::get (*comp);
  997. return nullptr;
  998. }
  999. bool OpenGLContext::areShadersAvailable() const
  1000. {
  1001. auto* c = getCachedImage();
  1002. return c != nullptr && c->shadersAvailable;
  1003. }
  1004. bool OpenGLContext::isTextureNpotSupported() const
  1005. {
  1006. auto* c = getCachedImage();
  1007. return c != nullptr && c->textureNpotSupported;
  1008. }
  1009. ReferenceCountedObject* OpenGLContext::getAssociatedObject (const char* name) const
  1010. {
  1011. jassert (name != nullptr);
  1012. auto* c = getCachedImage();
  1013. // This method must only be called from an openGL rendering callback.
  1014. jassert (c != nullptr && nativeContext != nullptr);
  1015. jassert (getCurrentContext() != nullptr);
  1016. auto index = c->associatedObjectNames.indexOf (name);
  1017. return index >= 0 ? c->associatedObjects.getUnchecked (index).get() : nullptr;
  1018. }
  1019. void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObject* newObject)
  1020. {
  1021. jassert (name != nullptr);
  1022. if (auto* c = getCachedImage())
  1023. {
  1024. // This method must only be called from an openGL rendering callback.
  1025. jassert (nativeContext != nullptr);
  1026. jassert (getCurrentContext() != nullptr);
  1027. const int index = c->associatedObjectNames.indexOf (name);
  1028. if (index >= 0)
  1029. {
  1030. if (newObject != nullptr)
  1031. {
  1032. c->associatedObjects.set (index, newObject);
  1033. }
  1034. else
  1035. {
  1036. c->associatedObjectNames.remove (index);
  1037. c->associatedObjects.remove (index);
  1038. }
  1039. }
  1040. else if (newObject != nullptr)
  1041. {
  1042. c->associatedObjectNames.add (name);
  1043. c->associatedObjects.add (newObject);
  1044. }
  1045. }
  1046. }
  1047. void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; }
  1048. size_t OpenGLContext::getImageCacheSize() const noexcept { return imageCacheMaxSize; }
  1049. void OpenGLContext::execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock)
  1050. {
  1051. if (auto* c = getCachedImage())
  1052. c->execute (std::move (workerToUse), shouldBlock);
  1053. else
  1054. jassertfalse; // You must have attached the context to a component
  1055. }
  1056. //==============================================================================
  1057. struct DepthTestDisabler
  1058. {
  1059. DepthTestDisabler() noexcept
  1060. {
  1061. glGetBooleanv (GL_DEPTH_TEST, &wasEnabled);
  1062. if (wasEnabled)
  1063. glDisable (GL_DEPTH_TEST);
  1064. }
  1065. ~DepthTestDisabler() noexcept
  1066. {
  1067. if (wasEnabled)
  1068. glEnable (GL_DEPTH_TEST);
  1069. }
  1070. GLboolean wasEnabled;
  1071. };
  1072. //==============================================================================
  1073. void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
  1074. const Rectangle<int>& anchorPosAndTextureSize,
  1075. const int contextWidth, const int contextHeight,
  1076. bool flippedVertically)
  1077. {
  1078. if (contextWidth <= 0 || contextHeight <= 0)
  1079. return;
  1080. JUCE_CHECK_OPENGL_ERROR
  1081. glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  1082. glEnable (GL_BLEND);
  1083. DepthTestDisabler depthDisabler;
  1084. if (areShadersAvailable())
  1085. {
  1086. struct OverlayShaderProgram : public ReferenceCountedObject
  1087. {
  1088. OverlayShaderProgram (OpenGLContext& context)
  1089. : program (context), builder (program), params (program)
  1090. {}
  1091. static const OverlayShaderProgram& select (OpenGLContext& context)
  1092. {
  1093. static const char programValueID[] = "juceGLComponentOverlayShader";
  1094. OverlayShaderProgram* program = static_cast<OverlayShaderProgram*> (context.getAssociatedObject (programValueID));
  1095. if (program == nullptr)
  1096. {
  1097. program = new OverlayShaderProgram (context);
  1098. context.setAssociatedObject (programValueID, program);
  1099. }
  1100. program->program.use();
  1101. return *program;
  1102. }
  1103. struct ProgramBuilder
  1104. {
  1105. ProgramBuilder (OpenGLShaderProgram& prog)
  1106. {
  1107. prog.addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
  1108. "attribute " JUCE_HIGHP " vec2 position;"
  1109. "uniform " JUCE_HIGHP " vec2 screenSize;"
  1110. "uniform " JUCE_HIGHP " float textureBounds[4];"
  1111. "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
  1112. "varying " JUCE_HIGHP " vec2 texturePos;"
  1113. "void main()"
  1114. "{"
  1115. JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
  1116. "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
  1117. "texturePos = (position - vec2 (textureBounds[0], textureBounds[1])) / vec2 (textureBounds[2], textureBounds[3]);"
  1118. "texturePos = vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y);"
  1119. "}"));
  1120. prog.addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (
  1121. "uniform sampler2D imageTexture;"
  1122. "varying " JUCE_HIGHP " vec2 texturePos;"
  1123. "void main()"
  1124. "{"
  1125. "gl_FragColor = texture2D (imageTexture, texturePos);"
  1126. "}"));
  1127. prog.link();
  1128. }
  1129. };
  1130. struct Params
  1131. {
  1132. Params (OpenGLShaderProgram& prog)
  1133. : positionAttribute (prog, "position"),
  1134. screenSize (prog, "screenSize"),
  1135. imageTexture (prog, "imageTexture"),
  1136. textureBounds (prog, "textureBounds"),
  1137. vOffsetAndScale (prog, "vOffsetAndScale")
  1138. {}
  1139. void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds, bool flipVertically) const
  1140. {
  1141. const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() };
  1142. textureBounds.set (m, 4);
  1143. imageTexture.set (0);
  1144. screenSize.set (targetWidth, targetHeight);
  1145. vOffsetAndScale.set (flipVertically ? 0.0f : 1.0f,
  1146. flipVertically ? 1.0f : -1.0f);
  1147. }
  1148. OpenGLShaderProgram::Attribute positionAttribute;
  1149. OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale;
  1150. };
  1151. OpenGLShaderProgram program;
  1152. ProgramBuilder builder;
  1153. Params params;
  1154. };
  1155. auto left = (GLshort) targetClipArea.getX();
  1156. auto top = (GLshort) targetClipArea.getY();
  1157. auto right = (GLshort) targetClipArea.getRight();
  1158. auto bottom = (GLshort) targetClipArea.getBottom();
  1159. const GLshort vertices[] = { left, bottom, right, bottom, left, top, right, top };
  1160. auto& program = OverlayShaderProgram::select (*this);
  1161. program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat(), flippedVertically);
  1162. GLuint vertexBuffer = 0;
  1163. extensions.glGenBuffers (1, &vertexBuffer);
  1164. extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
  1165. extensions.glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
  1166. auto index = (GLuint) program.params.positionAttribute.attributeID;
  1167. extensions.glVertexAttribPointer (index, 2, GL_SHORT, GL_FALSE, 4, nullptr);
  1168. extensions.glEnableVertexAttribArray (index);
  1169. JUCE_CHECK_OPENGL_ERROR
  1170. if (extensions.glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  1171. {
  1172. glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
  1173. extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
  1174. extensions.glUseProgram (0);
  1175. extensions.glDisableVertexAttribArray (index);
  1176. extensions.glDeleteBuffers (1, &vertexBuffer);
  1177. }
  1178. else
  1179. {
  1180. clearGLError();
  1181. }
  1182. }
  1183. else
  1184. {
  1185. jassert (attachment == nullptr); // Running on an old graphics card!
  1186. }
  1187. JUCE_CHECK_OPENGL_ERROR
  1188. }
  1189. #if JUCE_ANDROID
  1190. EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
  1191. EGLDisplay OpenGLContext::NativeContext::config;
  1192. void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject> holder)
  1193. {
  1194. ignoreUnused (holder);
  1195. if (auto* cachedImage = CachedImage::get (component))
  1196. {
  1197. if (auto* pool = cachedImage->renderThread.get())
  1198. {
  1199. if (! pool->contains (cachedImage))
  1200. {
  1201. cachedImage->resume();
  1202. cachedImage->context.triggerRepaint();
  1203. }
  1204. }
  1205. }
  1206. }
  1207. void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef<jobject> holder)
  1208. {
  1209. ignoreUnused (holder);
  1210. // unlike the name suggests this will be called just before the
  1211. // surface is destroyed. We need to pause the render thread.
  1212. if (auto* cachedImage = CachedImage::get (component))
  1213. {
  1214. cachedImage->pause();
  1215. if (auto* threadPool = cachedImage->renderThread.get())
  1216. threadPool->waitForJobToFinish (cachedImage, -1);
  1217. }
  1218. }
  1219. #endif
  1220. } // namespace juce