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.

1504 lines
46KB

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