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.

1420 lines
44KB

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