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.

1489 lines
45KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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 (false);
  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 (bool canTriggerUpdate)
  279. {
  280. JUCE_ASSERT_MESSAGE_THREAD
  281. if (auto* peer = component.getPeer())
  282. {
  283. #if JUCE_MAC
  284. const auto displayScale = [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. if (canTriggerUpdate)
  318. invalidateAll();
  319. });
  320. }
  321. }
  322. void bindVertexArray() noexcept
  323. {
  324. if (openGLVersion.major >= 3)
  325. if (vertexArrayObject != 0)
  326. context.extensions.glBindVertexArray (vertexArrayObject);
  327. }
  328. void checkViewportBounds()
  329. {
  330. auto screenBounds = component.getTopLevelComponent()->getScreenBounds();
  331. if (lastScreenBounds != screenBounds)
  332. {
  333. updateViewportSize (true);
  334. lastScreenBounds = screenBounds;
  335. }
  336. }
  337. void paintComponent (const AreaAndScale& currentAreaAndScale)
  338. {
  339. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  340. // you mustn't set your own cached image object when attaching a GL context!
  341. jassert (get (component) == this);
  342. if (! ensureFrameBufferSize (currentAreaAndScale.area))
  343. return;
  344. RectangleList<int> invalid (currentAreaAndScale.area);
  345. invalid.subtract (validArea);
  346. validArea = currentAreaAndScale.area;
  347. if (! invalid.isEmpty())
  348. {
  349. clearRegionInFrameBuffer (invalid);
  350. {
  351. std::unique_ptr<LowLevelGraphicsContext> g (createOpenGLGraphicsContext (context, cachedImageFrameBuffer));
  352. g->clipToRectangleList (invalid);
  353. g->addTransform (transform);
  354. paintOwner (*g);
  355. JUCE_CHECK_OPENGL_ERROR
  356. }
  357. if (! context.isActive())
  358. context.makeActive();
  359. }
  360. JUCE_CHECK_OPENGL_ERROR
  361. }
  362. void drawComponentBuffer()
  363. {
  364. #if ! JUCE_ANDROID
  365. glEnable (GL_TEXTURE_2D);
  366. clearGLError();
  367. #endif
  368. #if JUCE_WINDOWS
  369. // some stupidly old drivers are missing this function, so try to at least avoid a crash here,
  370. // but if you hit this assertion you may want to have your own version check before using the
  371. // component rendering stuff on such old drivers.
  372. jassert (context.extensions.glActiveTexture != nullptr);
  373. if (context.extensions.glActiveTexture != nullptr)
  374. #endif
  375. context.extensions.glActiveTexture (GL_TEXTURE0);
  376. glBindTexture (GL_TEXTURE_2D, cachedImageFrameBuffer.getTextureID());
  377. bindVertexArray();
  378. const Rectangle<int> cacheBounds (cachedImageFrameBuffer.getWidth(), cachedImageFrameBuffer.getHeight());
  379. context.copyTexture (cacheBounds, cacheBounds, cacheBounds.getWidth(), cacheBounds.getHeight(), false);
  380. glBindTexture (GL_TEXTURE_2D, 0);
  381. JUCE_CHECK_OPENGL_ERROR
  382. }
  383. void paintOwner (LowLevelGraphicsContext& llgc)
  384. {
  385. Graphics g (llgc);
  386. #if JUCE_ENABLE_REPAINT_DEBUGGING
  387. #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
  388. if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
  389. #endif
  390. {
  391. g.saveState();
  392. }
  393. #endif
  394. JUCE_TRY
  395. {
  396. component.paintEntireComponent (g, false);
  397. }
  398. JUCE_CATCH_EXCEPTION
  399. #if JUCE_ENABLE_REPAINT_DEBUGGING
  400. #ifdef JUCE_IS_REPAINT_DEBUGGING_ACTIVE
  401. if (JUCE_IS_REPAINT_DEBUGGING_ACTIVE)
  402. #endif
  403. {
  404. // enabling this code will fill all areas that get repainted with a colour overlay, to show
  405. // clearly when things are being repainted.
  406. g.restoreState();
  407. static Random rng;
  408. g.fillAll (Colour ((uint8) rng.nextInt (255),
  409. (uint8) rng.nextInt (255),
  410. (uint8) rng.nextInt (255),
  411. (uint8) 0x50));
  412. }
  413. #endif
  414. }
  415. void handleResize()
  416. {
  417. updateViewportSize (true);
  418. #if JUCE_MAC
  419. if (hasInitialised)
  420. {
  421. [nativeContext->view update];
  422. renderFrame();
  423. }
  424. #endif
  425. }
  426. //==============================================================================
  427. JobStatus runJob() override
  428. {
  429. {
  430. // Allow the message thread to finish setting-up the context before using it.
  431. MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
  432. do
  433. {
  434. if (shouldExit())
  435. return ThreadPoolJob::jobHasFinished;
  436. } while (! mmLock.retryLock());
  437. }
  438. if (! initialiseOnThread())
  439. {
  440. hasInitialised = false;
  441. return ThreadPoolJob::jobHasFinished;
  442. }
  443. hasInitialised = true;
  444. while (! shouldExit())
  445. {
  446. #if JUCE_IOS
  447. if (backgroundProcessCheck.isBackgroundProcess())
  448. {
  449. repaintEvent.wait (300);
  450. repaintEvent.reset();
  451. continue;
  452. }
  453. #endif
  454. if (shouldExit())
  455. break;
  456. #if JUCE_MAC
  457. if (context.continuousRepaint)
  458. {
  459. repaintEvent.wait (-1);
  460. renderFrame();
  461. }
  462. else
  463. #endif
  464. if (! renderFrame())
  465. repaintEvent.wait (5); // failed to render, so avoid a tight fail-loop.
  466. else if (! context.continuousRepaint && ! shouldExit())
  467. repaintEvent.wait (-1);
  468. repaintEvent.reset();
  469. }
  470. hasInitialised = false;
  471. context.makeActive();
  472. shutdownOnThread();
  473. OpenGLContext::deactivateCurrentContext();
  474. return ThreadPoolJob::jobHasFinished;
  475. }
  476. bool initialiseOnThread()
  477. {
  478. // On android, this can get called twice, so drop any previous state.
  479. associatedObjectNames.clear();
  480. associatedObjects.clear();
  481. cachedImageFrameBuffer.release();
  482. context.makeActive();
  483. if (! nativeContext->initialiseOnRenderThread (context))
  484. return false;
  485. #if JUCE_ANDROID
  486. // On android the context may be created in initialiseOnRenderThread
  487. // and we therefore need to call makeActive again
  488. context.makeActive();
  489. #endif
  490. gl::loadFunctions();
  491. openGLVersion = getOpenGLVersion();
  492. if (openGLVersion.major >= 3)
  493. {
  494. context.extensions.glGenVertexArrays (1, &vertexArrayObject);
  495. bindVertexArray();
  496. }
  497. const auto currentViewportArea = areaAndScale.get().area;
  498. glViewport (0, 0, currentViewportArea.getWidth(), currentViewportArea.getHeight());
  499. nativeContext->setSwapInterval (1);
  500. #if ! JUCE_OPENGL_ES
  501. JUCE_CHECK_OPENGL_ERROR
  502. shadersAvailable = OpenGLShaderProgram::getLanguageVersion() > 0;
  503. clearGLError();
  504. #endif
  505. textureNpotSupported = contextHasTextureNpotFeature();
  506. if (context.renderer != nullptr)
  507. context.renderer->newOpenGLContextCreated();
  508. #if JUCE_MAC
  509. jassert (cvDisplayLinkWrapper != nullptr);
  510. nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS());
  511. #endif
  512. return true;
  513. }
  514. void shutdownOnThread()
  515. {
  516. if (context.renderer != nullptr)
  517. context.renderer->openGLContextClosing();
  518. if (vertexArrayObject != 0)
  519. context.extensions.glDeleteVertexArrays (1, &vertexArrayObject);
  520. associatedObjectNames.clear();
  521. associatedObjects.clear();
  522. cachedImageFrameBuffer.release();
  523. nativeContext->shutdownOnRenderThread();
  524. }
  525. //==============================================================================
  526. struct BlockingWorker : public OpenGLContext::AsyncWorker
  527. {
  528. BlockingWorker (OpenGLContext::AsyncWorker::Ptr && workerToUse)
  529. : originalWorker (std::move (workerToUse))
  530. {}
  531. void operator() (OpenGLContext& calleeContext)
  532. {
  533. if (originalWorker != nullptr)
  534. (*originalWorker) (calleeContext);
  535. finishedSignal.signal();
  536. }
  537. void block() noexcept { finishedSignal.wait(); }
  538. OpenGLContext::AsyncWorker::Ptr originalWorker;
  539. WaitableEvent finishedSignal;
  540. };
  541. bool doWorkWhileWaitingForLock (bool contextIsAlreadyActive)
  542. {
  543. bool contextActivated = false;
  544. for (OpenGLContext::AsyncWorker::Ptr work = workQueue.removeAndReturn (0);
  545. work != nullptr && (! shouldExit()); work = workQueue.removeAndReturn (0))
  546. {
  547. if ((! contextActivated) && (! contextIsAlreadyActive))
  548. {
  549. if (! context.makeActive())
  550. break;
  551. contextActivated = true;
  552. }
  553. NativeContext::Locker locker (*nativeContext);
  554. (*work) (context);
  555. clearGLError();
  556. }
  557. if (contextActivated)
  558. OpenGLContext::deactivateCurrentContext();
  559. return shouldExit();
  560. }
  561. void execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock, bool calledFromDestructor = false)
  562. {
  563. if (calledFromDestructor || ! destroying)
  564. {
  565. if (shouldBlock)
  566. {
  567. auto blocker = new BlockingWorker (std::move (workerToUse));
  568. OpenGLContext::AsyncWorker::Ptr worker (*blocker);
  569. workQueue.add (worker);
  570. messageManagerLock.abort();
  571. context.triggerRepaint();
  572. blocker->block();
  573. }
  574. else
  575. {
  576. workQueue.add (std::move (workerToUse));
  577. messageManagerLock.abort();
  578. context.triggerRepaint();
  579. }
  580. }
  581. else
  582. {
  583. jassertfalse; // you called execute AFTER you detached your OpenGLContext
  584. }
  585. }
  586. //==============================================================================
  587. static CachedImage* get (Component& c) noexcept
  588. {
  589. return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
  590. }
  591. //==============================================================================
  592. friend class NativeContext;
  593. std::unique_ptr<NativeContext> nativeContext;
  594. OpenGLContext& context;
  595. Component& component;
  596. Version openGLVersion;
  597. OpenGLFrameBuffer cachedImageFrameBuffer;
  598. RectangleList<int> validArea;
  599. Rectangle<int> lastScreenBounds;
  600. AffineTransform transform;
  601. GLuint vertexArrayObject = 0;
  602. LockedAreaAndScale areaAndScale;
  603. StringArray associatedObjectNames;
  604. ReferenceCountedArray<ReferenceCountedObject> associatedObjects;
  605. WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent { true };
  606. #if JUCE_OPENGL_ES
  607. bool shadersAvailable = true;
  608. #else
  609. bool shadersAvailable = false;
  610. #endif
  611. bool textureNpotSupported = false;
  612. std::atomic<bool> hasInitialised { false }, needsUpdate { true }, destroying { false };
  613. uint32 lastMMLockReleaseTime = 0;
  614. #if JUCE_MAC
  615. NSView* getCurrentView() const
  616. {
  617. if (auto* peer = component.getPeer())
  618. return static_cast<NSView*> (peer->getNativeHandle());
  619. return nullptr;
  620. }
  621. NSScreen* getCurrentScreen() const
  622. {
  623. JUCE_ASSERT_MESSAGE_THREAD;
  624. if (auto* view = getCurrentView())
  625. if (auto* window = [view window])
  626. return [window screen];
  627. return nullptr;
  628. }
  629. struct CVDisplayLinkWrapper
  630. {
  631. explicit CVDisplayLinkWrapper (CachedImage& cachedImageIn)
  632. : cachedImage (cachedImageIn),
  633. continuousRepaint (cachedImageIn.context.continuousRepaint.load())
  634. {
  635. CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
  636. CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
  637. CVDisplayLinkStart (displayLink);
  638. }
  639. double getNominalVideoRefreshPeriodS() const
  640. {
  641. const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink);
  642. if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
  643. return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
  644. return 0.0;
  645. }
  646. /* Returns true if updated, or false otherwise. */
  647. bool updateActiveDisplay()
  648. {
  649. auto* oldScreen = std::exchange (currentScreen, cachedImage.getCurrentScreen());
  650. if (oldScreen == currentScreen)
  651. return false;
  652. for (NSScreen* screen in [NSScreen screens])
  653. if (screen == currentScreen)
  654. if (NSNumber* number = [[screen deviceDescription] objectForKey: @"NSScreenNumber"])
  655. CVDisplayLinkSetCurrentCGDisplay (displayLink, [number unsignedIntValue]);
  656. return true;
  657. }
  658. ~CVDisplayLinkWrapper()
  659. {
  660. CVDisplayLinkStop (displayLink);
  661. CVDisplayLinkRelease (displayLink);
  662. }
  663. static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
  664. CVOptionFlags, CVOptionFlags*, void* displayLinkContext)
  665. {
  666. auto* self = reinterpret_cast<CVDisplayLinkWrapper*> (displayLinkContext);
  667. if (self->continuousRepaint)
  668. self->cachedImage.repaintEvent.signal();
  669. return kCVReturnSuccess;
  670. }
  671. CachedImage& cachedImage;
  672. const bool continuousRepaint;
  673. CVDisplayLinkRef displayLink;
  674. NSScreen* currentScreen = nullptr;
  675. };
  676. std::unique_ptr<CVDisplayLinkWrapper> cvDisplayLinkWrapper;
  677. #endif
  678. std::unique_ptr<ThreadPool> renderThread;
  679. ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue;
  680. MessageManager::Lock messageManagerLock;
  681. #if JUCE_IOS
  682. iOSBackgroundProcessCheck backgroundProcessCheck;
  683. #endif
  684. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage)
  685. };
  686. //==============================================================================
  687. class OpenGLContext::Attachment : public ComponentMovementWatcher,
  688. private Timer
  689. {
  690. public:
  691. Attachment (OpenGLContext& c, Component& comp)
  692. : ComponentMovementWatcher (&comp), context (c)
  693. {
  694. if (canBeAttached (comp))
  695. attach();
  696. }
  697. ~Attachment() override
  698. {
  699. detach();
  700. }
  701. void detach()
  702. {
  703. auto& comp = *getComponent();
  704. stop();
  705. comp.setCachedComponentImage (nullptr);
  706. context.nativeContext = nullptr;
  707. }
  708. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  709. {
  710. auto& comp = *getComponent();
  711. if (isAttached (comp) != canBeAttached (comp))
  712. componentVisibilityChanged();
  713. if (comp.getWidth() > 0 && comp.getHeight() > 0
  714. && context.nativeContext != nullptr)
  715. {
  716. if (auto* c = CachedImage::get (comp))
  717. c->handleResize();
  718. if (auto* peer = comp.getTopLevelComponent()->getPeer())
  719. context.nativeContext->updateWindowPosition (peer->getAreaCoveredBy (comp));
  720. }
  721. }
  722. using ComponentMovementWatcher::componentMovedOrResized;
  723. void componentPeerChanged() override
  724. {
  725. detach();
  726. componentVisibilityChanged();
  727. }
  728. void componentVisibilityChanged() override
  729. {
  730. auto& comp = *getComponent();
  731. if (canBeAttached (comp))
  732. {
  733. if (isAttached (comp))
  734. comp.repaint(); // (needed when windows are un-minimised)
  735. else
  736. attach();
  737. }
  738. else
  739. {
  740. detach();
  741. }
  742. }
  743. using ComponentMovementWatcher::componentVisibilityChanged;
  744. #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
  745. void componentBeingDeleted (Component& c) override
  746. {
  747. /* You must call detach() or delete your OpenGLContext to remove it
  748. from a component BEFORE deleting the component that it is using!
  749. */
  750. jassertfalse;
  751. ComponentMovementWatcher::componentBeingDeleted (c);
  752. }
  753. #endif
  754. void update()
  755. {
  756. auto& comp = *getComponent();
  757. if (canBeAttached (comp))
  758. start();
  759. else
  760. stop();
  761. }
  762. private:
  763. OpenGLContext& context;
  764. bool canBeAttached (const Component& comp) noexcept
  765. {
  766. return (! context.overrideCanAttach) && comp.getWidth() > 0 && comp.getHeight() > 0 && isShowingOrMinimised (comp);
  767. }
  768. static bool isShowingOrMinimised (const Component& c)
  769. {
  770. if (! c.isVisible())
  771. return false;
  772. if (auto* p = c.getParentComponent())
  773. return isShowingOrMinimised (*p);
  774. return c.getPeer() != nullptr;
  775. }
  776. static bool isAttached (const Component& comp) noexcept
  777. {
  778. return comp.getCachedComponentImage() != nullptr;
  779. }
  780. void attach()
  781. {
  782. auto& comp = *getComponent();
  783. auto* newCachedImage = new CachedImage (context, comp,
  784. context.openGLPixelFormat,
  785. context.contextToShareWith);
  786. comp.setCachedComponentImage (newCachedImage);
  787. start();
  788. }
  789. void stop()
  790. {
  791. stopTimer();
  792. auto& comp = *getComponent();
  793. #if JUCE_MAC
  794. [[(NSView*) comp.getWindowHandle() window] disableScreenUpdatesUntilFlush];
  795. #endif
  796. if (auto* oldCachedImage = CachedImage::get (comp))
  797. oldCachedImage->stop(); // (must stop this before detaching it from the component)
  798. }
  799. void start()
  800. {
  801. auto& comp = *getComponent();
  802. if (auto* cachedImage = CachedImage::get (comp))
  803. {
  804. cachedImage->start(); // (must wait until this is attached before starting its thread)
  805. cachedImage->updateViewportSize (true);
  806. startTimer (400);
  807. }
  808. }
  809. void timerCallback() override
  810. {
  811. if (auto* cachedImage = CachedImage::get (*getComponent()))
  812. cachedImage->checkViewportBounds();
  813. }
  814. };
  815. //==============================================================================
  816. OpenGLContext::OpenGLContext()
  817. {
  818. }
  819. OpenGLContext::~OpenGLContext()
  820. {
  821. detach();
  822. }
  823. void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse) noexcept
  824. {
  825. // This method must not be called when the context has already been attached!
  826. // Call it before attaching your context, or use detach() first, before calling this!
  827. jassert (nativeContext == nullptr);
  828. renderer = rendererToUse;
  829. }
  830. void OpenGLContext::setComponentPaintingEnabled (bool shouldPaintComponent) noexcept
  831. {
  832. // This method must not be called when the context has already been attached!
  833. // Call it before attaching your context, or use detach() first, before calling this!
  834. jassert (nativeContext == nullptr);
  835. renderComponents = shouldPaintComponent;
  836. }
  837. void OpenGLContext::setContinuousRepainting (bool shouldContinuouslyRepaint) noexcept
  838. {
  839. continuousRepaint = shouldContinuouslyRepaint;
  840. #if JUCE_MAC
  841. if (auto* component = getTargetComponent())
  842. {
  843. detach();
  844. attachment.reset (new Attachment (*this, *component));
  845. }
  846. #endif
  847. triggerRepaint();
  848. }
  849. void OpenGLContext::setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept
  850. {
  851. // This method must not be called when the context has already been attached!
  852. // Call it before attaching your context, or use detach() first, before calling this!
  853. jassert (nativeContext == nullptr);
  854. openGLPixelFormat = preferredPixelFormat;
  855. }
  856. void OpenGLContext::setTextureMagnificationFilter (OpenGLContext::TextureMagnificationFilter magFilterMode) noexcept
  857. {
  858. texMagFilter = magFilterMode;
  859. }
  860. void OpenGLContext::setNativeSharedContext (void* nativeContextToShareWith) noexcept
  861. {
  862. // This method must not be called when the context has already been attached!
  863. // Call it before attaching your context, or use detach() first, before calling this!
  864. jassert (nativeContext == nullptr);
  865. contextToShareWith = nativeContextToShareWith;
  866. }
  867. void OpenGLContext::setMultisamplingEnabled (bool b) noexcept
  868. {
  869. // This method must not be called when the context has already been attached!
  870. // Call it before attaching your context, or use detach() first, before calling this!
  871. jassert (nativeContext == nullptr);
  872. useMultisampling = b;
  873. }
  874. void OpenGLContext::setOpenGLVersionRequired (OpenGLVersion v) noexcept
  875. {
  876. versionRequired = v;
  877. }
  878. void OpenGLContext::attachTo (Component& component)
  879. {
  880. component.repaint();
  881. if (getTargetComponent() != &component)
  882. {
  883. detach();
  884. attachment.reset (new Attachment (*this, component));
  885. }
  886. }
  887. void OpenGLContext::detach()
  888. {
  889. if (auto* a = attachment.get())
  890. {
  891. a->detach(); // must detach before nulling our pointer
  892. attachment.reset();
  893. }
  894. nativeContext = nullptr;
  895. }
  896. bool OpenGLContext::isAttached() const noexcept
  897. {
  898. return nativeContext != nullptr;
  899. }
  900. Component* OpenGLContext::getTargetComponent() const noexcept
  901. {
  902. return attachment != nullptr ? attachment->getComponent() : nullptr;
  903. }
  904. OpenGLContext* OpenGLContext::getContextAttachedTo (Component& c) noexcept
  905. {
  906. if (auto* ci = CachedImage::get (c))
  907. return &(ci->context);
  908. return nullptr;
  909. }
  910. static ThreadLocalValue<OpenGLContext*> currentThreadActiveContext;
  911. OpenGLContext* OpenGLContext::getCurrentContext()
  912. {
  913. return currentThreadActiveContext.get();
  914. }
  915. bool OpenGLContext::makeActive() const noexcept
  916. {
  917. auto& current = currentThreadActiveContext.get();
  918. if (nativeContext != nullptr && nativeContext->makeActive())
  919. {
  920. current = const_cast<OpenGLContext*> (this);
  921. return true;
  922. }
  923. current = nullptr;
  924. return false;
  925. }
  926. bool OpenGLContext::isActive() const noexcept
  927. {
  928. return nativeContext != nullptr && nativeContext->isActive();
  929. }
  930. void OpenGLContext::deactivateCurrentContext()
  931. {
  932. NativeContext::deactivateCurrentContext();
  933. currentThreadActiveContext.get() = nullptr;
  934. }
  935. void OpenGLContext::triggerRepaint()
  936. {
  937. if (auto* cachedImage = getCachedImage())
  938. cachedImage->triggerRepaint();
  939. }
  940. void OpenGLContext::swapBuffers()
  941. {
  942. if (nativeContext != nullptr)
  943. nativeContext->swapBuffers();
  944. }
  945. unsigned int OpenGLContext::getFrameBufferID() const noexcept
  946. {
  947. return nativeContext != nullptr ? nativeContext->getFrameBufferID() : 0;
  948. }
  949. bool OpenGLContext::setSwapInterval (int numFramesPerSwap)
  950. {
  951. return nativeContext != nullptr && nativeContext->setSwapInterval (numFramesPerSwap);
  952. }
  953. int OpenGLContext::getSwapInterval() const
  954. {
  955. return nativeContext != nullptr ? nativeContext->getSwapInterval() : 0;
  956. }
  957. void* OpenGLContext::getRawContext() const noexcept
  958. {
  959. return nativeContext != nullptr ? nativeContext->getRawContext() : nullptr;
  960. }
  961. OpenGLContext::CachedImage* OpenGLContext::getCachedImage() const noexcept
  962. {
  963. if (auto* comp = getTargetComponent())
  964. return CachedImage::get (*comp);
  965. return nullptr;
  966. }
  967. bool OpenGLContext::areShadersAvailable() const
  968. {
  969. auto* c = getCachedImage();
  970. return c != nullptr && c->shadersAvailable;
  971. }
  972. bool OpenGLContext::isTextureNpotSupported() const
  973. {
  974. auto* c = getCachedImage();
  975. return c != nullptr && c->textureNpotSupported;
  976. }
  977. ReferenceCountedObject* OpenGLContext::getAssociatedObject (const char* name) const
  978. {
  979. jassert (name != nullptr);
  980. auto* c = getCachedImage();
  981. // This method must only be called from an openGL rendering callback.
  982. jassert (c != nullptr && nativeContext != nullptr);
  983. jassert (getCurrentContext() != nullptr);
  984. auto index = c->associatedObjectNames.indexOf (name);
  985. return index >= 0 ? c->associatedObjects.getUnchecked (index).get() : nullptr;
  986. }
  987. void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObject* newObject)
  988. {
  989. jassert (name != nullptr);
  990. if (auto* c = getCachedImage())
  991. {
  992. // This method must only be called from an openGL rendering callback.
  993. jassert (nativeContext != nullptr);
  994. jassert (getCurrentContext() != nullptr);
  995. const int index = c->associatedObjectNames.indexOf (name);
  996. if (index >= 0)
  997. {
  998. if (newObject != nullptr)
  999. {
  1000. c->associatedObjects.set (index, newObject);
  1001. }
  1002. else
  1003. {
  1004. c->associatedObjectNames.remove (index);
  1005. c->associatedObjects.remove (index);
  1006. }
  1007. }
  1008. else if (newObject != nullptr)
  1009. {
  1010. c->associatedObjectNames.add (name);
  1011. c->associatedObjects.add (newObject);
  1012. }
  1013. }
  1014. }
  1015. void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; }
  1016. size_t OpenGLContext::getImageCacheSize() const noexcept { return imageCacheMaxSize; }
  1017. void OpenGLContext::execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock)
  1018. {
  1019. if (auto* c = getCachedImage())
  1020. c->execute (std::move (workerToUse), shouldBlock);
  1021. else
  1022. jassertfalse; // You must have attached the context to a component
  1023. }
  1024. //==============================================================================
  1025. struct DepthTestDisabler
  1026. {
  1027. DepthTestDisabler() noexcept
  1028. {
  1029. glGetBooleanv (GL_DEPTH_TEST, &wasEnabled);
  1030. if (wasEnabled)
  1031. glDisable (GL_DEPTH_TEST);
  1032. }
  1033. ~DepthTestDisabler() noexcept
  1034. {
  1035. if (wasEnabled)
  1036. glEnable (GL_DEPTH_TEST);
  1037. }
  1038. GLboolean wasEnabled;
  1039. };
  1040. //==============================================================================
  1041. void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
  1042. const Rectangle<int>& anchorPosAndTextureSize,
  1043. const int contextWidth, const int contextHeight,
  1044. bool flippedVertically)
  1045. {
  1046. if (contextWidth <= 0 || contextHeight <= 0)
  1047. return;
  1048. JUCE_CHECK_OPENGL_ERROR
  1049. glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  1050. glEnable (GL_BLEND);
  1051. DepthTestDisabler depthDisabler;
  1052. if (areShadersAvailable())
  1053. {
  1054. struct OverlayShaderProgram : public ReferenceCountedObject
  1055. {
  1056. OverlayShaderProgram (OpenGLContext& context)
  1057. : program (context), builder (program), params (program)
  1058. {}
  1059. static const OverlayShaderProgram& select (OpenGLContext& context)
  1060. {
  1061. static const char programValueID[] = "juceGLComponentOverlayShader";
  1062. OverlayShaderProgram* program = static_cast<OverlayShaderProgram*> (context.getAssociatedObject (programValueID));
  1063. if (program == nullptr)
  1064. {
  1065. program = new OverlayShaderProgram (context);
  1066. context.setAssociatedObject (programValueID, program);
  1067. }
  1068. program->program.use();
  1069. return *program;
  1070. }
  1071. struct ProgramBuilder
  1072. {
  1073. ProgramBuilder (OpenGLShaderProgram& prog)
  1074. {
  1075. prog.addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
  1076. "attribute " JUCE_HIGHP " vec2 position;"
  1077. "uniform " JUCE_HIGHP " vec2 screenSize;"
  1078. "uniform " JUCE_HIGHP " float textureBounds[4];"
  1079. "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
  1080. "varying " JUCE_HIGHP " vec2 texturePos;"
  1081. "void main()"
  1082. "{"
  1083. JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
  1084. "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
  1085. "texturePos = (position - vec2 (textureBounds[0], textureBounds[1])) / vec2 (textureBounds[2], textureBounds[3]);"
  1086. "texturePos = vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y);"
  1087. "}"));
  1088. prog.addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (
  1089. "uniform sampler2D imageTexture;"
  1090. "varying " JUCE_HIGHP " vec2 texturePos;"
  1091. "void main()"
  1092. "{"
  1093. "gl_FragColor = texture2D (imageTexture, texturePos);"
  1094. "}"));
  1095. prog.link();
  1096. }
  1097. };
  1098. struct Params
  1099. {
  1100. Params (OpenGLShaderProgram& prog)
  1101. : positionAttribute (prog, "position"),
  1102. screenSize (prog, "screenSize"),
  1103. imageTexture (prog, "imageTexture"),
  1104. textureBounds (prog, "textureBounds"),
  1105. vOffsetAndScale (prog, "vOffsetAndScale")
  1106. {}
  1107. void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds, bool flipVertically) const
  1108. {
  1109. const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() };
  1110. textureBounds.set (m, 4);
  1111. imageTexture.set (0);
  1112. screenSize.set (targetWidth, targetHeight);
  1113. vOffsetAndScale.set (flipVertically ? 0.0f : 1.0f,
  1114. flipVertically ? 1.0f : -1.0f);
  1115. }
  1116. OpenGLShaderProgram::Attribute positionAttribute;
  1117. OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale;
  1118. };
  1119. OpenGLShaderProgram program;
  1120. ProgramBuilder builder;
  1121. Params params;
  1122. };
  1123. auto left = (GLshort) targetClipArea.getX();
  1124. auto top = (GLshort) targetClipArea.getY();
  1125. auto right = (GLshort) targetClipArea.getRight();
  1126. auto bottom = (GLshort) targetClipArea.getBottom();
  1127. const GLshort vertices[] = { left, bottom, right, bottom, left, top, right, top };
  1128. auto& program = OverlayShaderProgram::select (*this);
  1129. program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat(), flippedVertically);
  1130. GLuint vertexBuffer = 0;
  1131. extensions.glGenBuffers (1, &vertexBuffer);
  1132. extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
  1133. extensions.glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
  1134. auto index = (GLuint) program.params.positionAttribute.attributeID;
  1135. extensions.glVertexAttribPointer (index, 2, GL_SHORT, GL_FALSE, 4, nullptr);
  1136. extensions.glEnableVertexAttribArray (index);
  1137. JUCE_CHECK_OPENGL_ERROR
  1138. if (extensions.glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  1139. {
  1140. glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
  1141. extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
  1142. extensions.glUseProgram (0);
  1143. extensions.glDisableVertexAttribArray (index);
  1144. extensions.glDeleteBuffers (1, &vertexBuffer);
  1145. }
  1146. else
  1147. {
  1148. clearGLError();
  1149. }
  1150. }
  1151. else
  1152. {
  1153. jassert (attachment == nullptr); // Running on an old graphics card!
  1154. }
  1155. JUCE_CHECK_OPENGL_ERROR
  1156. }
  1157. #if JUCE_ANDROID
  1158. EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
  1159. EGLDisplay OpenGLContext::NativeContext::config;
  1160. void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject> holder)
  1161. {
  1162. ignoreUnused (holder);
  1163. if (auto* cachedImage = CachedImage::get (component))
  1164. {
  1165. if (auto* pool = cachedImage->renderThread.get())
  1166. {
  1167. if (! pool->contains (cachedImage))
  1168. {
  1169. cachedImage->resume();
  1170. cachedImage->context.triggerRepaint();
  1171. }
  1172. }
  1173. }
  1174. }
  1175. void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef<jobject> holder)
  1176. {
  1177. ignoreUnused (holder);
  1178. // unlike the name suggests this will be called just before the
  1179. // surface is destroyed. We need to pause the render thread.
  1180. if (auto* cachedImage = CachedImage::get (component))
  1181. {
  1182. cachedImage->pause();
  1183. if (auto* threadPool = cachedImage->renderThread.get())
  1184. threadPool->waitForJobToFinish (cachedImage, -1);
  1185. }
  1186. }
  1187. #endif
  1188. } // namespace juce