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.

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