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.

1511 lines
46KB

  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 (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 = 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. if (canTriggerUpdate)
  318. invalidateAll();
  319. });
  320. }
  321. }
  322. void bindVertexArray() noexcept
  323. {
  324. if (shouldUseCustomVAO())
  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. if (shouldUseCustomVAO())
  492. {
  493. context.extensions.glGenVertexArrays (1, &vertexArrayObject);
  494. bindVertexArray();
  495. }
  496. const auto currentViewportArea = areaAndScale.get().area;
  497. glViewport (0, 0, currentViewportArea.getWidth(), currentViewportArea.getHeight());
  498. nativeContext->setSwapInterval (1);
  499. #if ! JUCE_OPENGL_ES
  500. JUCE_CHECK_OPENGL_ERROR
  501. shadersAvailable = OpenGLShaderProgram::getLanguageVersion() > 0;
  502. clearGLError();
  503. #endif
  504. textureNpotSupported = contextHasTextureNpotFeature();
  505. if (context.renderer != nullptr)
  506. context.renderer->newOpenGLContextCreated();
  507. #if JUCE_MAC
  508. jassert (cvDisplayLinkWrapper != nullptr);
  509. nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS());
  510. #endif
  511. return true;
  512. }
  513. void shutdownOnThread()
  514. {
  515. if (context.renderer != nullptr)
  516. context.renderer->openGLContextClosing();
  517. if (vertexArrayObject != 0)
  518. context.extensions.glDeleteVertexArrays (1, &vertexArrayObject);
  519. associatedObjectNames.clear();
  520. associatedObjects.clear();
  521. cachedImageFrameBuffer.release();
  522. nativeContext->shutdownOnRenderThread();
  523. }
  524. /* Returns true if the context requires a non-zero vertex array object (VAO) to be bound.
  525. If the context is a compatibility context, we can just pretend that VAOs don't exist,
  526. and use the default VAO all the time instead. This provides a more consistent experience
  527. in user code, which might make calls (like glVertexPointer()) that only work when VAO 0 is
  528. bound in OpenGL 3.2+.
  529. */
  530. bool shouldUseCustomVAO() const
  531. {
  532. #if JUCE_OPENGL_ES
  533. return false;
  534. #else
  535. clearGLError();
  536. GLint mask = 0;
  537. glGetIntegerv (GL_CONTEXT_PROFILE_MASK, &mask);
  538. // The context isn't aware of the profile mask, so it pre-dates the core profile
  539. if (glGetError() == GL_INVALID_ENUM)
  540. return false;
  541. // Also assumes a compatibility profile if the mask is completely empty for some reason
  542. return (mask & (GLint) GL_CONTEXT_CORE_PROFILE_BIT) != 0;
  543. #endif
  544. }
  545. //==============================================================================
  546. struct BlockingWorker : public OpenGLContext::AsyncWorker
  547. {
  548. BlockingWorker (OpenGLContext::AsyncWorker::Ptr && workerToUse)
  549. : originalWorker (std::move (workerToUse))
  550. {}
  551. void operator() (OpenGLContext& calleeContext)
  552. {
  553. if (originalWorker != nullptr)
  554. (*originalWorker) (calleeContext);
  555. finishedSignal.signal();
  556. }
  557. void block() noexcept { finishedSignal.wait(); }
  558. OpenGLContext::AsyncWorker::Ptr originalWorker;
  559. WaitableEvent finishedSignal;
  560. };
  561. bool doWorkWhileWaitingForLock (bool contextIsAlreadyActive)
  562. {
  563. bool contextActivated = false;
  564. for (OpenGLContext::AsyncWorker::Ptr work = workQueue.removeAndReturn (0);
  565. work != nullptr && (! shouldExit()); work = workQueue.removeAndReturn (0))
  566. {
  567. if ((! contextActivated) && (! contextIsAlreadyActive))
  568. {
  569. if (! context.makeActive())
  570. break;
  571. contextActivated = true;
  572. }
  573. NativeContext::Locker locker (*nativeContext);
  574. (*work) (context);
  575. clearGLError();
  576. }
  577. if (contextActivated)
  578. OpenGLContext::deactivateCurrentContext();
  579. return shouldExit();
  580. }
  581. void execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock, bool calledFromDestructor = false)
  582. {
  583. if (calledFromDestructor || ! destroying)
  584. {
  585. if (shouldBlock)
  586. {
  587. auto blocker = new BlockingWorker (std::move (workerToUse));
  588. OpenGLContext::AsyncWorker::Ptr worker (*blocker);
  589. workQueue.add (worker);
  590. messageManagerLock.abort();
  591. context.triggerRepaint();
  592. blocker->block();
  593. }
  594. else
  595. {
  596. workQueue.add (std::move (workerToUse));
  597. messageManagerLock.abort();
  598. context.triggerRepaint();
  599. }
  600. }
  601. else
  602. {
  603. jassertfalse; // you called execute AFTER you detached your OpenGLContext
  604. }
  605. }
  606. //==============================================================================
  607. static CachedImage* get (Component& c) noexcept
  608. {
  609. return dynamic_cast<CachedImage*> (c.getCachedComponentImage());
  610. }
  611. //==============================================================================
  612. friend class NativeContext;
  613. std::unique_ptr<NativeContext> nativeContext;
  614. OpenGLContext& context;
  615. Component& component;
  616. OpenGLFrameBuffer cachedImageFrameBuffer;
  617. RectangleList<int> validArea;
  618. Rectangle<int> lastScreenBounds;
  619. AffineTransform transform;
  620. GLuint vertexArrayObject = 0;
  621. LockedAreaAndScale areaAndScale;
  622. StringArray associatedObjectNames;
  623. ReferenceCountedArray<ReferenceCountedObject> associatedObjects;
  624. WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent { true };
  625. #if JUCE_OPENGL_ES
  626. bool shadersAvailable = true;
  627. #else
  628. bool shadersAvailable = false;
  629. #endif
  630. bool textureNpotSupported = false;
  631. std::atomic<bool> hasInitialised { false }, needsUpdate { true }, destroying { false };
  632. uint32 lastMMLockReleaseTime = 0;
  633. #if JUCE_MAC
  634. NSView* getCurrentView() const
  635. {
  636. if (auto* peer = component.getPeer())
  637. return static_cast<NSView*> (peer->getNativeHandle());
  638. return nullptr;
  639. }
  640. NSScreen* getCurrentScreen() const
  641. {
  642. JUCE_ASSERT_MESSAGE_THREAD;
  643. if (auto* view = getCurrentView())
  644. if (auto* window = [view window])
  645. return [window screen];
  646. return nullptr;
  647. }
  648. struct CVDisplayLinkWrapper
  649. {
  650. explicit CVDisplayLinkWrapper (CachedImage& cachedImageIn)
  651. : cachedImage (cachedImageIn),
  652. continuousRepaint (cachedImageIn.context.continuousRepaint.load())
  653. {
  654. CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
  655. CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
  656. CVDisplayLinkStart (displayLink);
  657. }
  658. double getNominalVideoRefreshPeriodS() const
  659. {
  660. const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink);
  661. if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
  662. return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
  663. return 0.0;
  664. }
  665. /* Returns true if updated, or false otherwise. */
  666. bool updateActiveDisplay()
  667. {
  668. auto* oldScreen = std::exchange (currentScreen, cachedImage.getCurrentScreen());
  669. if (oldScreen == currentScreen)
  670. return false;
  671. for (NSScreen* screen in [NSScreen screens])
  672. if (screen == currentScreen)
  673. if (NSNumber* number = [[screen deviceDescription] objectForKey: @"NSScreenNumber"])
  674. CVDisplayLinkSetCurrentCGDisplay (displayLink, [number unsignedIntValue]);
  675. return true;
  676. }
  677. ~CVDisplayLinkWrapper()
  678. {
  679. CVDisplayLinkStop (displayLink);
  680. CVDisplayLinkRelease (displayLink);
  681. }
  682. static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
  683. CVOptionFlags, CVOptionFlags*, void* displayLinkContext)
  684. {
  685. auto* self = reinterpret_cast<CVDisplayLinkWrapper*> (displayLinkContext);
  686. if (self->continuousRepaint)
  687. self->cachedImage.repaintEvent.signal();
  688. return kCVReturnSuccess;
  689. }
  690. CachedImage& cachedImage;
  691. const bool continuousRepaint;
  692. CVDisplayLinkRef displayLink;
  693. NSScreen* currentScreen = nullptr;
  694. };
  695. std::unique_ptr<CVDisplayLinkWrapper> cvDisplayLinkWrapper;
  696. #endif
  697. std::unique_ptr<ThreadPool> renderThread;
  698. ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue;
  699. MessageManager::Lock messageManagerLock;
  700. #if JUCE_IOS
  701. iOSBackgroundProcessCheck backgroundProcessCheck;
  702. #endif
  703. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage)
  704. };
  705. //==============================================================================
  706. class OpenGLContext::Attachment : public ComponentMovementWatcher,
  707. private Timer
  708. {
  709. public:
  710. Attachment (OpenGLContext& c, Component& comp)
  711. : ComponentMovementWatcher (&comp), context (c)
  712. {
  713. if (canBeAttached (comp))
  714. attach();
  715. }
  716. ~Attachment() override
  717. {
  718. detach();
  719. }
  720. void detach()
  721. {
  722. auto& comp = *getComponent();
  723. stop();
  724. comp.setCachedComponentImage (nullptr);
  725. context.nativeContext = nullptr;
  726. }
  727. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  728. {
  729. auto& comp = *getComponent();
  730. if (isAttached (comp) != canBeAttached (comp))
  731. componentVisibilityChanged();
  732. if (comp.getWidth() > 0 && comp.getHeight() > 0
  733. && context.nativeContext != nullptr)
  734. {
  735. if (auto* c = CachedImage::get (comp))
  736. c->handleResize();
  737. if (auto* peer = comp.getTopLevelComponent()->getPeer())
  738. context.nativeContext->updateWindowPosition (peer->getAreaCoveredBy (comp));
  739. }
  740. }
  741. using ComponentMovementWatcher::componentMovedOrResized;
  742. void componentPeerChanged() override
  743. {
  744. detach();
  745. componentVisibilityChanged();
  746. }
  747. void componentVisibilityChanged() override
  748. {
  749. auto& comp = *getComponent();
  750. if (canBeAttached (comp))
  751. {
  752. if (isAttached (comp))
  753. comp.repaint(); // (needed when windows are un-minimised)
  754. else
  755. attach();
  756. }
  757. else
  758. {
  759. detach();
  760. }
  761. }
  762. using ComponentMovementWatcher::componentVisibilityChanged;
  763. #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
  764. void componentBeingDeleted (Component& c) override
  765. {
  766. /* You must call detach() or delete your OpenGLContext to remove it
  767. from a component BEFORE deleting the component that it is using!
  768. */
  769. jassertfalse;
  770. ComponentMovementWatcher::componentBeingDeleted (c);
  771. }
  772. #endif
  773. void update()
  774. {
  775. auto& comp = *getComponent();
  776. if (canBeAttached (comp))
  777. start();
  778. else
  779. stop();
  780. }
  781. private:
  782. OpenGLContext& context;
  783. bool canBeAttached (const Component& comp) noexcept
  784. {
  785. return (! context.overrideCanAttach) && comp.getWidth() > 0 && comp.getHeight() > 0 && isShowingOrMinimised (comp);
  786. }
  787. static bool isShowingOrMinimised (const Component& c)
  788. {
  789. if (! c.isVisible())
  790. return false;
  791. if (auto* p = c.getParentComponent())
  792. return isShowingOrMinimised (*p);
  793. return c.getPeer() != nullptr;
  794. }
  795. static bool isAttached (const Component& comp) noexcept
  796. {
  797. return comp.getCachedComponentImage() != nullptr;
  798. }
  799. void attach()
  800. {
  801. auto& comp = *getComponent();
  802. auto* newCachedImage = new CachedImage (context, comp,
  803. context.openGLPixelFormat,
  804. context.contextToShareWith);
  805. comp.setCachedComponentImage (newCachedImage);
  806. start();
  807. }
  808. void stop()
  809. {
  810. stopTimer();
  811. auto& comp = *getComponent();
  812. #if JUCE_MAC
  813. [[(NSView*) comp.getWindowHandle() window] disableScreenUpdatesUntilFlush];
  814. #endif
  815. if (auto* oldCachedImage = CachedImage::get (comp))
  816. oldCachedImage->stop(); // (must stop this before detaching it from the component)
  817. }
  818. void start()
  819. {
  820. auto& comp = *getComponent();
  821. if (auto* cachedImage = CachedImage::get (comp))
  822. {
  823. cachedImage->start(); // (must wait until this is attached before starting its thread)
  824. cachedImage->updateViewportSize (true);
  825. startTimer (400);
  826. }
  827. }
  828. void timerCallback() override
  829. {
  830. if (auto* cachedImage = CachedImage::get (*getComponent()))
  831. cachedImage->checkViewportBounds();
  832. }
  833. };
  834. //==============================================================================
  835. OpenGLContext::OpenGLContext()
  836. {
  837. }
  838. OpenGLContext::~OpenGLContext()
  839. {
  840. detach();
  841. }
  842. void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse) noexcept
  843. {
  844. // This method must not be called when the context has already been attached!
  845. // Call it before attaching your context, or use detach() first, before calling this!
  846. jassert (nativeContext == nullptr);
  847. renderer = rendererToUse;
  848. }
  849. void OpenGLContext::setComponentPaintingEnabled (bool shouldPaintComponent) 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. renderComponents = shouldPaintComponent;
  855. }
  856. void OpenGLContext::setContinuousRepainting (bool shouldContinuouslyRepaint) noexcept
  857. {
  858. continuousRepaint = shouldContinuouslyRepaint;
  859. #if JUCE_MAC
  860. if (auto* component = getTargetComponent())
  861. {
  862. detach();
  863. attachment.reset (new Attachment (*this, *component));
  864. }
  865. #endif
  866. triggerRepaint();
  867. }
  868. void OpenGLContext::setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept
  869. {
  870. // This method must not be called when the context has already been attached!
  871. // Call it before attaching your context, or use detach() first, before calling this!
  872. jassert (nativeContext == nullptr);
  873. openGLPixelFormat = preferredPixelFormat;
  874. }
  875. void OpenGLContext::setTextureMagnificationFilter (OpenGLContext::TextureMagnificationFilter magFilterMode) noexcept
  876. {
  877. texMagFilter = magFilterMode;
  878. }
  879. void OpenGLContext::setNativeSharedContext (void* nativeContextToShareWith) noexcept
  880. {
  881. // This method must not be called when the context has already been attached!
  882. // Call it before attaching your context, or use detach() first, before calling this!
  883. jassert (nativeContext == nullptr);
  884. contextToShareWith = nativeContextToShareWith;
  885. }
  886. void OpenGLContext::setMultisamplingEnabled (bool b) noexcept
  887. {
  888. // This method must not be called when the context has already been attached!
  889. // Call it before attaching your context, or use detach() first, before calling this!
  890. jassert (nativeContext == nullptr);
  891. useMultisampling = b;
  892. }
  893. void OpenGLContext::setOpenGLVersionRequired (OpenGLVersion v) noexcept
  894. {
  895. versionRequired = v;
  896. }
  897. void OpenGLContext::attachTo (Component& component)
  898. {
  899. component.repaint();
  900. if (getTargetComponent() != &component)
  901. {
  902. detach();
  903. attachment.reset (new Attachment (*this, component));
  904. }
  905. }
  906. void OpenGLContext::detach()
  907. {
  908. if (auto* a = attachment.get())
  909. {
  910. a->detach(); // must detach before nulling our pointer
  911. attachment.reset();
  912. }
  913. nativeContext = nullptr;
  914. }
  915. bool OpenGLContext::isAttached() const noexcept
  916. {
  917. return nativeContext != nullptr;
  918. }
  919. Component* OpenGLContext::getTargetComponent() const noexcept
  920. {
  921. return attachment != nullptr ? attachment->getComponent() : nullptr;
  922. }
  923. OpenGLContext* OpenGLContext::getContextAttachedTo (Component& c) noexcept
  924. {
  925. if (auto* ci = CachedImage::get (c))
  926. return &(ci->context);
  927. return nullptr;
  928. }
  929. static ThreadLocalValue<OpenGLContext*> currentThreadActiveContext;
  930. OpenGLContext* OpenGLContext::getCurrentContext()
  931. {
  932. return currentThreadActiveContext.get();
  933. }
  934. bool OpenGLContext::makeActive() const noexcept
  935. {
  936. auto& current = currentThreadActiveContext.get();
  937. if (nativeContext != nullptr && nativeContext->makeActive())
  938. {
  939. current = const_cast<OpenGLContext*> (this);
  940. return true;
  941. }
  942. current = nullptr;
  943. return false;
  944. }
  945. bool OpenGLContext::isActive() const noexcept
  946. {
  947. return nativeContext != nullptr && nativeContext->isActive();
  948. }
  949. void OpenGLContext::deactivateCurrentContext()
  950. {
  951. NativeContext::deactivateCurrentContext();
  952. currentThreadActiveContext.get() = nullptr;
  953. }
  954. void OpenGLContext::triggerRepaint()
  955. {
  956. if (auto* cachedImage = getCachedImage())
  957. cachedImage->triggerRepaint();
  958. }
  959. void OpenGLContext::swapBuffers()
  960. {
  961. if (nativeContext != nullptr)
  962. nativeContext->swapBuffers();
  963. }
  964. unsigned int OpenGLContext::getFrameBufferID() const noexcept
  965. {
  966. return nativeContext != nullptr ? nativeContext->getFrameBufferID() : 0;
  967. }
  968. bool OpenGLContext::setSwapInterval (int numFramesPerSwap)
  969. {
  970. return nativeContext != nullptr && nativeContext->setSwapInterval (numFramesPerSwap);
  971. }
  972. int OpenGLContext::getSwapInterval() const
  973. {
  974. return nativeContext != nullptr ? nativeContext->getSwapInterval() : 0;
  975. }
  976. void* OpenGLContext::getRawContext() const noexcept
  977. {
  978. return nativeContext != nullptr ? nativeContext->getRawContext() : nullptr;
  979. }
  980. OpenGLContext::CachedImage* OpenGLContext::getCachedImage() const noexcept
  981. {
  982. if (auto* comp = getTargetComponent())
  983. return CachedImage::get (*comp);
  984. return nullptr;
  985. }
  986. bool OpenGLContext::areShadersAvailable() const
  987. {
  988. auto* c = getCachedImage();
  989. return c != nullptr && c->shadersAvailable;
  990. }
  991. bool OpenGLContext::isTextureNpotSupported() const
  992. {
  993. auto* c = getCachedImage();
  994. return c != nullptr && c->textureNpotSupported;
  995. }
  996. ReferenceCountedObject* OpenGLContext::getAssociatedObject (const char* name) const
  997. {
  998. jassert (name != nullptr);
  999. auto* c = getCachedImage();
  1000. // This method must only be called from an openGL rendering callback.
  1001. jassert (c != nullptr && nativeContext != nullptr);
  1002. jassert (getCurrentContext() != nullptr);
  1003. auto index = c->associatedObjectNames.indexOf (name);
  1004. return index >= 0 ? c->associatedObjects.getUnchecked (index).get() : nullptr;
  1005. }
  1006. void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObject* newObject)
  1007. {
  1008. jassert (name != nullptr);
  1009. if (auto* c = getCachedImage())
  1010. {
  1011. // This method must only be called from an openGL rendering callback.
  1012. jassert (nativeContext != nullptr);
  1013. jassert (getCurrentContext() != nullptr);
  1014. const int index = c->associatedObjectNames.indexOf (name);
  1015. if (index >= 0)
  1016. {
  1017. if (newObject != nullptr)
  1018. {
  1019. c->associatedObjects.set (index, newObject);
  1020. }
  1021. else
  1022. {
  1023. c->associatedObjectNames.remove (index);
  1024. c->associatedObjects.remove (index);
  1025. }
  1026. }
  1027. else if (newObject != nullptr)
  1028. {
  1029. c->associatedObjectNames.add (name);
  1030. c->associatedObjects.add (newObject);
  1031. }
  1032. }
  1033. }
  1034. void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; }
  1035. size_t OpenGLContext::getImageCacheSize() const noexcept { return imageCacheMaxSize; }
  1036. void OpenGLContext::execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock)
  1037. {
  1038. if (auto* c = getCachedImage())
  1039. c->execute (std::move (workerToUse), shouldBlock);
  1040. else
  1041. jassertfalse; // You must have attached the context to a component
  1042. }
  1043. //==============================================================================
  1044. struct DepthTestDisabler
  1045. {
  1046. DepthTestDisabler() noexcept
  1047. {
  1048. glGetBooleanv (GL_DEPTH_TEST, &wasEnabled);
  1049. if (wasEnabled)
  1050. glDisable (GL_DEPTH_TEST);
  1051. }
  1052. ~DepthTestDisabler() noexcept
  1053. {
  1054. if (wasEnabled)
  1055. glEnable (GL_DEPTH_TEST);
  1056. }
  1057. GLboolean wasEnabled;
  1058. };
  1059. //==============================================================================
  1060. void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
  1061. const Rectangle<int>& anchorPosAndTextureSize,
  1062. const int contextWidth, const int contextHeight,
  1063. bool flippedVertically)
  1064. {
  1065. if (contextWidth <= 0 || contextHeight <= 0)
  1066. return;
  1067. JUCE_CHECK_OPENGL_ERROR
  1068. glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  1069. glEnable (GL_BLEND);
  1070. DepthTestDisabler depthDisabler;
  1071. if (areShadersAvailable())
  1072. {
  1073. struct OverlayShaderProgram : public ReferenceCountedObject
  1074. {
  1075. OverlayShaderProgram (OpenGLContext& context)
  1076. : program (context), builder (program), params (program)
  1077. {}
  1078. static const OverlayShaderProgram& select (OpenGLContext& context)
  1079. {
  1080. static const char programValueID[] = "juceGLComponentOverlayShader";
  1081. OverlayShaderProgram* program = static_cast<OverlayShaderProgram*> (context.getAssociatedObject (programValueID));
  1082. if (program == nullptr)
  1083. {
  1084. program = new OverlayShaderProgram (context);
  1085. context.setAssociatedObject (programValueID, program);
  1086. }
  1087. program->program.use();
  1088. return *program;
  1089. }
  1090. struct ProgramBuilder
  1091. {
  1092. ProgramBuilder (OpenGLShaderProgram& prog)
  1093. {
  1094. prog.addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
  1095. "attribute " JUCE_HIGHP " vec2 position;"
  1096. "uniform " JUCE_HIGHP " vec2 screenSize;"
  1097. "uniform " JUCE_HIGHP " float textureBounds[4];"
  1098. "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
  1099. "varying " JUCE_HIGHP " vec2 texturePos;"
  1100. "void main()"
  1101. "{"
  1102. JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
  1103. "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
  1104. "texturePos = (position - vec2 (textureBounds[0], textureBounds[1])) / vec2 (textureBounds[2], textureBounds[3]);"
  1105. "texturePos = vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y);"
  1106. "}"));
  1107. prog.addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (
  1108. "uniform sampler2D imageTexture;"
  1109. "varying " JUCE_HIGHP " vec2 texturePos;"
  1110. "void main()"
  1111. "{"
  1112. "gl_FragColor = texture2D (imageTexture, texturePos);"
  1113. "}"));
  1114. prog.link();
  1115. }
  1116. };
  1117. struct Params
  1118. {
  1119. Params (OpenGLShaderProgram& prog)
  1120. : positionAttribute (prog, "position"),
  1121. screenSize (prog, "screenSize"),
  1122. imageTexture (prog, "imageTexture"),
  1123. textureBounds (prog, "textureBounds"),
  1124. vOffsetAndScale (prog, "vOffsetAndScale")
  1125. {}
  1126. void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds, bool flipVertically) const
  1127. {
  1128. const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() };
  1129. textureBounds.set (m, 4);
  1130. imageTexture.set (0);
  1131. screenSize.set (targetWidth, targetHeight);
  1132. vOffsetAndScale.set (flipVertically ? 0.0f : 1.0f,
  1133. flipVertically ? 1.0f : -1.0f);
  1134. }
  1135. OpenGLShaderProgram::Attribute positionAttribute;
  1136. OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale;
  1137. };
  1138. OpenGLShaderProgram program;
  1139. ProgramBuilder builder;
  1140. Params params;
  1141. };
  1142. auto left = (GLshort) targetClipArea.getX();
  1143. auto top = (GLshort) targetClipArea.getY();
  1144. auto right = (GLshort) targetClipArea.getRight();
  1145. auto bottom = (GLshort) targetClipArea.getBottom();
  1146. const GLshort vertices[] = { left, bottom, right, bottom, left, top, right, top };
  1147. auto& program = OverlayShaderProgram::select (*this);
  1148. program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat(), flippedVertically);
  1149. GLuint vertexBuffer = 0;
  1150. extensions.glGenBuffers (1, &vertexBuffer);
  1151. extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
  1152. extensions.glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
  1153. auto index = (GLuint) program.params.positionAttribute.attributeID;
  1154. extensions.glVertexAttribPointer (index, 2, GL_SHORT, GL_FALSE, 4, nullptr);
  1155. extensions.glEnableVertexAttribArray (index);
  1156. JUCE_CHECK_OPENGL_ERROR
  1157. if (extensions.glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  1158. {
  1159. glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
  1160. extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
  1161. extensions.glUseProgram (0);
  1162. extensions.glDisableVertexAttribArray (index);
  1163. extensions.glDeleteBuffers (1, &vertexBuffer);
  1164. }
  1165. else
  1166. {
  1167. clearGLError();
  1168. }
  1169. }
  1170. else
  1171. {
  1172. jassert (attachment == nullptr); // Running on an old graphics card!
  1173. }
  1174. JUCE_CHECK_OPENGL_ERROR
  1175. }
  1176. #if JUCE_ANDROID
  1177. EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
  1178. EGLDisplay OpenGLContext::NativeContext::config;
  1179. void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject> holder)
  1180. {
  1181. ignoreUnused (holder);
  1182. if (auto* cachedImage = CachedImage::get (component))
  1183. {
  1184. if (auto* pool = cachedImage->renderThread.get())
  1185. {
  1186. if (! pool->contains (cachedImage))
  1187. {
  1188. cachedImage->resume();
  1189. cachedImage->context.triggerRepaint();
  1190. }
  1191. }
  1192. }
  1193. }
  1194. void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef<jobject> holder)
  1195. {
  1196. ignoreUnused (holder);
  1197. // unlike the name suggests this will be called just before the
  1198. // surface is destroyed. We need to pause the render thread.
  1199. if (auto* cachedImage = CachedImage::get (component))
  1200. {
  1201. cachedImage->pause();
  1202. if (auto* threadPool = cachedImage->renderThread.get())
  1203. threadPool->waitForJobToFinish (cachedImage, -1);
  1204. }
  1205. }
  1206. #endif
  1207. } // namespace juce