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.

1439 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. renderThread->setThreadPriorities (9);
  96. resume();
  97. }
  98. }
  99. void stop()
  100. {
  101. if (renderThread != nullptr)
  102. {
  103. // make sure everything has finished executing
  104. destroying = true;
  105. if (workQueue.size() > 0)
  106. {
  107. if (! renderThread->contains (this))
  108. resume();
  109. while (workQueue.size() != 0)
  110. Thread::sleep (20);
  111. }
  112. pause();
  113. renderThread.reset();
  114. }
  115. hasInitialised = false;
  116. }
  117. //==============================================================================
  118. void pause()
  119. {
  120. signalJobShouldExit();
  121. messageManagerLock.abort();
  122. if (renderThread != nullptr)
  123. {
  124. repaintEvent.signal();
  125. renderThread->removeJob (this, true, -1);
  126. }
  127. }
  128. void resume()
  129. {
  130. if (renderThread != nullptr)
  131. renderThread->addJob (this, false);
  132. }
  133. //==============================================================================
  134. void paint (Graphics&) override
  135. {
  136. updateViewportSize (false);
  137. }
  138. bool invalidateAll() override
  139. {
  140. validArea.clear();
  141. triggerRepaint();
  142. return false;
  143. }
  144. bool invalidate (const Rectangle<int>& area) override
  145. {
  146. validArea.subtract (area.toFloat().transformedBy (transform).getSmallestIntegerContainer());
  147. triggerRepaint();
  148. return false;
  149. }
  150. void releaseResources() override
  151. {
  152. stop();
  153. }
  154. void triggerRepaint()
  155. {
  156. needsUpdate = 1;
  157. repaintEvent.signal();
  158. }
  159. //==============================================================================
  160. bool ensureFrameBufferSize()
  161. {
  162. auto fbW = cachedImageFrameBuffer.getWidth();
  163. auto fbH = cachedImageFrameBuffer.getHeight();
  164. if (fbW != viewportArea.getWidth() || fbH != viewportArea.getHeight() || ! cachedImageFrameBuffer.isValid())
  165. {
  166. if (! cachedImageFrameBuffer.initialise (context, viewportArea.getWidth(), viewportArea.getHeight()))
  167. return false;
  168. validArea.clear();
  169. JUCE_CHECK_OPENGL_ERROR
  170. }
  171. return true;
  172. }
  173. void clearRegionInFrameBuffer (const RectangleList<int>& list)
  174. {
  175. glClearColor (0, 0, 0, 0);
  176. glEnable (GL_SCISSOR_TEST);
  177. auto previousFrameBufferTarget = OpenGLFrameBuffer::getCurrentFrameBufferTarget();
  178. cachedImageFrameBuffer.makeCurrentRenderingTarget();
  179. auto imageH = cachedImageFrameBuffer.getHeight();
  180. for (auto& r : list)
  181. {
  182. glScissor (r.getX(), imageH - r.getBottom(), r.getWidth(), r.getHeight());
  183. glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  184. }
  185. glDisable (GL_SCISSOR_TEST);
  186. context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, previousFrameBufferTarget);
  187. JUCE_CHECK_OPENGL_ERROR
  188. }
  189. bool renderFrame()
  190. {
  191. MessageManager::Lock::ScopedTryLockType mmLock (messageManagerLock, false);
  192. auto isUpdatingTestValue = true;
  193. auto isUpdating = needsUpdate.compare_exchange_strong (isUpdatingTestValue, false);
  194. if (context.renderComponents && isUpdating)
  195. {
  196. // This avoids hogging the message thread when doing intensive rendering.
  197. if (lastMMLockReleaseTime + 1 >= Time::getMillisecondCounter())
  198. Thread::sleep (2);
  199. while (! shouldExit())
  200. {
  201. doWorkWhileWaitingForLock (false);
  202. if (mmLock.retryLock())
  203. break;
  204. }
  205. if (shouldExit())
  206. return false;
  207. }
  208. if (! context.makeActive())
  209. return false;
  210. NativeContext::Locker locker (*nativeContext);
  211. JUCE_CHECK_OPENGL_ERROR
  212. doWorkWhileWaitingForLock (true);
  213. if (context.renderer != nullptr)
  214. {
  215. glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
  216. context.currentRenderScale = scale;
  217. context.renderer->renderOpenGL();
  218. clearGLError();
  219. bindVertexArray();
  220. }
  221. if (context.renderComponents)
  222. {
  223. if (isUpdating)
  224. {
  225. paintComponent();
  226. if (! hasInitialised)
  227. return false;
  228. messageManagerLock.exit();
  229. lastMMLockReleaseTime = Time::getMillisecondCounter();
  230. }
  231. glViewport (0, 0, viewportArea.getWidth(), viewportArea.getHeight());
  232. drawComponentBuffer();
  233. }
  234. context.swapBuffers();
  235. OpenGLContext::deactivateCurrentContext();
  236. return true;
  237. }
  238. void updateViewportSize (bool canTriggerUpdate)
  239. {
  240. JUCE_ASSERT_MESSAGE_THREAD
  241. if (auto* peer = component.getPeer())
  242. {
  243. #if JUCE_MAC
  244. const auto displayScale = [this]
  245. {
  246. if (auto* wrapper = cvDisplayLinkWrapper.get())
  247. if (wrapper->updateActiveDisplay())
  248. nativeContext->setNominalVideoRefreshPeriodS (wrapper->getNominalVideoRefreshPeriodS());
  249. if (auto* view = getCurrentView())
  250. {
  251. if ([view respondsToSelector: @selector (backingScaleFactor)])
  252. return [(id) view backingScaleFactor];
  253. if (auto* window = [view window])
  254. return [window backingScaleFactor];
  255. }
  256. return scale;
  257. }();
  258. #else
  259. const auto displayScale = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds())->scale;
  260. #endif
  261. auto localBounds = component.getLocalBounds();
  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();
  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. double scale = 1.0;
  564. AffineTransform transform;
  565. GLuint vertexArrayObject = 0;
  566. StringArray associatedObjectNames;
  567. ReferenceCountedArray<ReferenceCountedObject> associatedObjects;
  568. WaitableEvent canPaintNowFlag, finishedPaintingFlag, repaintEvent { true };
  569. #if JUCE_OPENGL_ES
  570. bool shadersAvailable = true;
  571. #else
  572. bool shadersAvailable = false;
  573. #endif
  574. bool textureNpotSupported = false;
  575. std::atomic<bool> hasInitialised { false }, needsUpdate { true }, destroying { false };
  576. uint32 lastMMLockReleaseTime = 0;
  577. #if JUCE_MAC
  578. NSView* getCurrentView() const
  579. {
  580. if (auto* peer = component.getPeer())
  581. return static_cast<NSView*> (peer->getNativeHandle());
  582. return nullptr;
  583. }
  584. NSScreen* getCurrentScreen() const
  585. {
  586. if (auto* view = getCurrentView())
  587. if (auto* window = [view window])
  588. return [window screen];
  589. return nullptr;
  590. }
  591. struct CVDisplayLinkWrapper
  592. {
  593. explicit CVDisplayLinkWrapper (CachedImage& cachedImageIn)
  594. : cachedImage (cachedImageIn),
  595. continuousRepaint (cachedImageIn.context.continuousRepaint.load())
  596. {
  597. CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
  598. CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
  599. CVDisplayLinkStart (displayLink);
  600. }
  601. double getNominalVideoRefreshPeriodS() const
  602. {
  603. const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink);
  604. if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
  605. return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
  606. return 0.0;
  607. }
  608. /* Returns true if updated, or false otherwise. */
  609. bool updateActiveDisplay()
  610. {
  611. auto* oldScreen = std::exchange (currentScreen, cachedImage.getCurrentScreen());
  612. if (oldScreen == currentScreen)
  613. return false;
  614. for (NSScreen* screen in [NSScreen screens])
  615. if (screen == currentScreen)
  616. if (NSNumber* number = [[screen deviceDescription] objectForKey: @"NSScreenNumber"])
  617. CVDisplayLinkSetCurrentCGDisplay (displayLink, [number unsignedIntValue]);
  618. return true;
  619. }
  620. ~CVDisplayLinkWrapper()
  621. {
  622. CVDisplayLinkStop (displayLink);
  623. CVDisplayLinkRelease (displayLink);
  624. }
  625. static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
  626. CVOptionFlags, CVOptionFlags*, void* displayLinkContext)
  627. {
  628. auto* self = reinterpret_cast<CVDisplayLinkWrapper*> (displayLinkContext);
  629. if (self->continuousRepaint)
  630. self->cachedImage.repaintEvent.signal();
  631. return kCVReturnSuccess;
  632. }
  633. CachedImage& cachedImage;
  634. const bool continuousRepaint;
  635. CVDisplayLinkRef displayLink;
  636. NSScreen* currentScreen = nullptr;
  637. };
  638. std::unique_ptr<CVDisplayLinkWrapper> cvDisplayLinkWrapper;
  639. #endif
  640. std::unique_ptr<ThreadPool> renderThread;
  641. ReferenceCountedArray<OpenGLContext::AsyncWorker, CriticalSection> workQueue;
  642. MessageManager::Lock messageManagerLock;
  643. #if JUCE_IOS
  644. iOSBackgroundProcessCheck backgroundProcessCheck;
  645. #endif
  646. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedImage)
  647. };
  648. //==============================================================================
  649. class OpenGLContext::Attachment : public ComponentMovementWatcher,
  650. private Timer
  651. {
  652. public:
  653. Attachment (OpenGLContext& c, Component& comp)
  654. : ComponentMovementWatcher (&comp), context (c)
  655. {
  656. if (canBeAttached (comp))
  657. attach();
  658. }
  659. ~Attachment() override
  660. {
  661. detach();
  662. }
  663. void detach()
  664. {
  665. auto& comp = *getComponent();
  666. stop();
  667. comp.setCachedComponentImage (nullptr);
  668. context.nativeContext = nullptr;
  669. }
  670. void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override
  671. {
  672. auto& comp = *getComponent();
  673. if (isAttached (comp) != canBeAttached (comp))
  674. componentVisibilityChanged();
  675. if (comp.getWidth() > 0 && comp.getHeight() > 0
  676. && context.nativeContext != nullptr)
  677. {
  678. if (auto* c = CachedImage::get (comp))
  679. c->handleResize();
  680. if (auto* peer = comp.getTopLevelComponent()->getPeer())
  681. context.nativeContext->updateWindowPosition (peer->getAreaCoveredBy (comp));
  682. }
  683. }
  684. using ComponentMovementWatcher::componentMovedOrResized;
  685. void componentPeerChanged() override
  686. {
  687. detach();
  688. componentVisibilityChanged();
  689. }
  690. void componentVisibilityChanged() override
  691. {
  692. auto& comp = *getComponent();
  693. if (canBeAttached (comp))
  694. {
  695. if (isAttached (comp))
  696. comp.repaint(); // (needed when windows are un-minimised)
  697. else
  698. attach();
  699. }
  700. else
  701. {
  702. detach();
  703. }
  704. }
  705. using ComponentMovementWatcher::componentVisibilityChanged;
  706. #if JUCE_DEBUG || JUCE_LOG_ASSERTIONS
  707. void componentBeingDeleted (Component& c) override
  708. {
  709. /* You must call detach() or delete your OpenGLContext to remove it
  710. from a component BEFORE deleting the component that it is using!
  711. */
  712. jassertfalse;
  713. ComponentMovementWatcher::componentBeingDeleted (c);
  714. }
  715. #endif
  716. void update()
  717. {
  718. auto& comp = *getComponent();
  719. if (canBeAttached (comp))
  720. start();
  721. else
  722. stop();
  723. }
  724. private:
  725. OpenGLContext& context;
  726. bool canBeAttached (const Component& comp) noexcept
  727. {
  728. return (! context.overrideCanAttach) && comp.getWidth() > 0 && comp.getHeight() > 0 && isShowingOrMinimised (comp);
  729. }
  730. static bool isShowingOrMinimised (const Component& c)
  731. {
  732. if (! c.isVisible())
  733. return false;
  734. if (auto* p = c.getParentComponent())
  735. return isShowingOrMinimised (*p);
  736. return c.getPeer() != nullptr;
  737. }
  738. static bool isAttached (const Component& comp) noexcept
  739. {
  740. return comp.getCachedComponentImage() != nullptr;
  741. }
  742. void attach()
  743. {
  744. auto& comp = *getComponent();
  745. auto* newCachedImage = new CachedImage (context, comp,
  746. context.openGLPixelFormat,
  747. context.contextToShareWith);
  748. comp.setCachedComponentImage (newCachedImage);
  749. start();
  750. }
  751. void stop()
  752. {
  753. stopTimer();
  754. auto& comp = *getComponent();
  755. #if JUCE_MAC
  756. [[(NSView*) comp.getWindowHandle() window] disableScreenUpdatesUntilFlush];
  757. #endif
  758. if (auto* oldCachedImage = CachedImage::get (comp))
  759. oldCachedImage->stop(); // (must stop this before detaching it from the component)
  760. }
  761. void start()
  762. {
  763. auto& comp = *getComponent();
  764. if (auto* cachedImage = CachedImage::get (comp))
  765. {
  766. cachedImage->start(); // (must wait until this is attached before starting its thread)
  767. cachedImage->updateViewportSize (true);
  768. startTimer (400);
  769. }
  770. }
  771. void timerCallback() override
  772. {
  773. if (auto* cachedImage = CachedImage::get (*getComponent()))
  774. cachedImage->checkViewportBounds();
  775. }
  776. };
  777. //==============================================================================
  778. OpenGLContext::OpenGLContext()
  779. {
  780. }
  781. OpenGLContext::~OpenGLContext()
  782. {
  783. detach();
  784. }
  785. void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse) noexcept
  786. {
  787. // This method must not be called when the context has already been attached!
  788. // Call it before attaching your context, or use detach() first, before calling this!
  789. jassert (nativeContext == nullptr);
  790. renderer = rendererToUse;
  791. }
  792. void OpenGLContext::setComponentPaintingEnabled (bool shouldPaintComponent) noexcept
  793. {
  794. // This method must not be called when the context has already been attached!
  795. // Call it before attaching your context, or use detach() first, before calling this!
  796. jassert (nativeContext == nullptr);
  797. renderComponents = shouldPaintComponent;
  798. }
  799. void OpenGLContext::setContinuousRepainting (bool shouldContinuouslyRepaint) noexcept
  800. {
  801. continuousRepaint = shouldContinuouslyRepaint;
  802. #if JUCE_MAC
  803. if (auto* component = getTargetComponent())
  804. {
  805. detach();
  806. attachment.reset (new Attachment (*this, *component));
  807. }
  808. #endif
  809. triggerRepaint();
  810. }
  811. void OpenGLContext::setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept
  812. {
  813. // This method must not be called when the context has already been attached!
  814. // Call it before attaching your context, or use detach() first, before calling this!
  815. jassert (nativeContext == nullptr);
  816. openGLPixelFormat = preferredPixelFormat;
  817. }
  818. void OpenGLContext::setTextureMagnificationFilter (OpenGLContext::TextureMagnificationFilter magFilterMode) noexcept
  819. {
  820. texMagFilter = magFilterMode;
  821. }
  822. void OpenGLContext::setNativeSharedContext (void* nativeContextToShareWith) noexcept
  823. {
  824. // This method must not be called when the context has already been attached!
  825. // Call it before attaching your context, or use detach() first, before calling this!
  826. jassert (nativeContext == nullptr);
  827. contextToShareWith = nativeContextToShareWith;
  828. }
  829. void OpenGLContext::setMultisamplingEnabled (bool b) noexcept
  830. {
  831. // This method must not be called when the context has already been attached!
  832. // Call it before attaching your context, or use detach() first, before calling this!
  833. jassert (nativeContext == nullptr);
  834. useMultisampling = b;
  835. }
  836. void OpenGLContext::setOpenGLVersionRequired (OpenGLVersion v) noexcept
  837. {
  838. versionRequired = v;
  839. }
  840. void OpenGLContext::attachTo (Component& component)
  841. {
  842. component.repaint();
  843. if (getTargetComponent() != &component)
  844. {
  845. detach();
  846. attachment.reset (new Attachment (*this, component));
  847. }
  848. }
  849. void OpenGLContext::detach()
  850. {
  851. if (auto* a = attachment.get())
  852. {
  853. a->detach(); // must detach before nulling our pointer
  854. attachment.reset();
  855. }
  856. nativeContext = nullptr;
  857. }
  858. bool OpenGLContext::isAttached() const noexcept
  859. {
  860. return nativeContext != nullptr;
  861. }
  862. Component* OpenGLContext::getTargetComponent() const noexcept
  863. {
  864. return attachment != nullptr ? attachment->getComponent() : nullptr;
  865. }
  866. OpenGLContext* OpenGLContext::getContextAttachedTo (Component& c) noexcept
  867. {
  868. if (auto* ci = CachedImage::get (c))
  869. return &(ci->context);
  870. return nullptr;
  871. }
  872. static ThreadLocalValue<OpenGLContext*> currentThreadActiveContext;
  873. OpenGLContext* OpenGLContext::getCurrentContext()
  874. {
  875. return currentThreadActiveContext.get();
  876. }
  877. bool OpenGLContext::makeActive() const noexcept
  878. {
  879. auto& current = currentThreadActiveContext.get();
  880. if (nativeContext != nullptr && nativeContext->makeActive())
  881. {
  882. current = const_cast<OpenGLContext*> (this);
  883. return true;
  884. }
  885. current = nullptr;
  886. return false;
  887. }
  888. bool OpenGLContext::isActive() const noexcept
  889. {
  890. return nativeContext != nullptr && nativeContext->isActive();
  891. }
  892. void OpenGLContext::deactivateCurrentContext()
  893. {
  894. NativeContext::deactivateCurrentContext();
  895. currentThreadActiveContext.get() = nullptr;
  896. }
  897. void OpenGLContext::triggerRepaint()
  898. {
  899. if (auto* cachedImage = getCachedImage())
  900. cachedImage->triggerRepaint();
  901. }
  902. void OpenGLContext::swapBuffers()
  903. {
  904. if (nativeContext != nullptr)
  905. nativeContext->swapBuffers();
  906. }
  907. unsigned int OpenGLContext::getFrameBufferID() const noexcept
  908. {
  909. return nativeContext != nullptr ? nativeContext->getFrameBufferID() : 0;
  910. }
  911. bool OpenGLContext::setSwapInterval (int numFramesPerSwap)
  912. {
  913. return nativeContext != nullptr && nativeContext->setSwapInterval (numFramesPerSwap);
  914. }
  915. int OpenGLContext::getSwapInterval() const
  916. {
  917. return nativeContext != nullptr ? nativeContext->getSwapInterval() : 0;
  918. }
  919. void* OpenGLContext::getRawContext() const noexcept
  920. {
  921. return nativeContext != nullptr ? nativeContext->getRawContext() : nullptr;
  922. }
  923. OpenGLContext::CachedImage* OpenGLContext::getCachedImage() const noexcept
  924. {
  925. if (auto* comp = getTargetComponent())
  926. return CachedImage::get (*comp);
  927. return nullptr;
  928. }
  929. bool OpenGLContext::areShadersAvailable() const
  930. {
  931. auto* c = getCachedImage();
  932. return c != nullptr && c->shadersAvailable;
  933. }
  934. bool OpenGLContext::isTextureNpotSupported() const
  935. {
  936. auto* c = getCachedImage();
  937. return c != nullptr && c->textureNpotSupported;
  938. }
  939. ReferenceCountedObject* OpenGLContext::getAssociatedObject (const char* name) const
  940. {
  941. jassert (name != nullptr);
  942. auto* c = getCachedImage();
  943. // This method must only be called from an openGL rendering callback.
  944. jassert (c != nullptr && nativeContext != nullptr);
  945. jassert (getCurrentContext() != nullptr);
  946. auto index = c->associatedObjectNames.indexOf (name);
  947. return index >= 0 ? c->associatedObjects.getUnchecked (index).get() : nullptr;
  948. }
  949. void OpenGLContext::setAssociatedObject (const char* name, ReferenceCountedObject* newObject)
  950. {
  951. jassert (name != nullptr);
  952. if (auto* c = getCachedImage())
  953. {
  954. // This method must only be called from an openGL rendering callback.
  955. jassert (nativeContext != nullptr);
  956. jassert (getCurrentContext() != nullptr);
  957. const int index = c->associatedObjectNames.indexOf (name);
  958. if (index >= 0)
  959. {
  960. if (newObject != nullptr)
  961. {
  962. c->associatedObjects.set (index, newObject);
  963. }
  964. else
  965. {
  966. c->associatedObjectNames.remove (index);
  967. c->associatedObjects.remove (index);
  968. }
  969. }
  970. else if (newObject != nullptr)
  971. {
  972. c->associatedObjectNames.add (name);
  973. c->associatedObjects.add (newObject);
  974. }
  975. }
  976. }
  977. void OpenGLContext::setImageCacheSize (size_t newSize) noexcept { imageCacheMaxSize = newSize; }
  978. size_t OpenGLContext::getImageCacheSize() const noexcept { return imageCacheMaxSize; }
  979. void OpenGLContext::execute (OpenGLContext::AsyncWorker::Ptr workerToUse, bool shouldBlock)
  980. {
  981. if (auto* c = getCachedImage())
  982. c->execute (std::move (workerToUse), shouldBlock);
  983. else
  984. jassertfalse; // You must have attached the context to a component
  985. }
  986. //==============================================================================
  987. struct DepthTestDisabler
  988. {
  989. DepthTestDisabler() noexcept
  990. {
  991. glGetBooleanv (GL_DEPTH_TEST, &wasEnabled);
  992. if (wasEnabled)
  993. glDisable (GL_DEPTH_TEST);
  994. }
  995. ~DepthTestDisabler() noexcept
  996. {
  997. if (wasEnabled)
  998. glEnable (GL_DEPTH_TEST);
  999. }
  1000. GLboolean wasEnabled;
  1001. };
  1002. //==============================================================================
  1003. void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
  1004. const Rectangle<int>& anchorPosAndTextureSize,
  1005. const int contextWidth, const int contextHeight,
  1006. bool flippedVertically)
  1007. {
  1008. if (contextWidth <= 0 || contextHeight <= 0)
  1009. return;
  1010. JUCE_CHECK_OPENGL_ERROR
  1011. glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
  1012. glEnable (GL_BLEND);
  1013. DepthTestDisabler depthDisabler;
  1014. if (areShadersAvailable())
  1015. {
  1016. struct OverlayShaderProgram : public ReferenceCountedObject
  1017. {
  1018. OverlayShaderProgram (OpenGLContext& context)
  1019. : program (context), builder (program), params (program)
  1020. {}
  1021. static const OverlayShaderProgram& select (OpenGLContext& context)
  1022. {
  1023. static const char programValueID[] = "juceGLComponentOverlayShader";
  1024. OverlayShaderProgram* program = static_cast<OverlayShaderProgram*> (context.getAssociatedObject (programValueID));
  1025. if (program == nullptr)
  1026. {
  1027. program = new OverlayShaderProgram (context);
  1028. context.setAssociatedObject (programValueID, program);
  1029. }
  1030. program->program.use();
  1031. return *program;
  1032. }
  1033. struct ProgramBuilder
  1034. {
  1035. ProgramBuilder (OpenGLShaderProgram& prog)
  1036. {
  1037. prog.addVertexShader (OpenGLHelpers::translateVertexShaderToV3 (
  1038. "attribute " JUCE_HIGHP " vec2 position;"
  1039. "uniform " JUCE_HIGHP " vec2 screenSize;"
  1040. "uniform " JUCE_HIGHP " float textureBounds[4];"
  1041. "uniform " JUCE_HIGHP " vec2 vOffsetAndScale;"
  1042. "varying " JUCE_HIGHP " vec2 texturePos;"
  1043. "void main()"
  1044. "{"
  1045. JUCE_HIGHP " vec2 scaled = position / (0.5 * screenSize.xy);"
  1046. "gl_Position = vec4 (scaled.x - 1.0, 1.0 - scaled.y, 0, 1.0);"
  1047. "texturePos = (position - vec2 (textureBounds[0], textureBounds[1])) / vec2 (textureBounds[2], textureBounds[3]);"
  1048. "texturePos = vec2 (texturePos.x, vOffsetAndScale.x + vOffsetAndScale.y * texturePos.y);"
  1049. "}"));
  1050. prog.addFragmentShader (OpenGLHelpers::translateFragmentShaderToV3 (
  1051. "uniform sampler2D imageTexture;"
  1052. "varying " JUCE_HIGHP " vec2 texturePos;"
  1053. "void main()"
  1054. "{"
  1055. "gl_FragColor = texture2D (imageTexture, texturePos);"
  1056. "}"));
  1057. prog.link();
  1058. }
  1059. };
  1060. struct Params
  1061. {
  1062. Params (OpenGLShaderProgram& prog)
  1063. : positionAttribute (prog, "position"),
  1064. screenSize (prog, "screenSize"),
  1065. imageTexture (prog, "imageTexture"),
  1066. textureBounds (prog, "textureBounds"),
  1067. vOffsetAndScale (prog, "vOffsetAndScale")
  1068. {}
  1069. void set (const float targetWidth, const float targetHeight, const Rectangle<float>& bounds, bool flipVertically) const
  1070. {
  1071. const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() };
  1072. textureBounds.set (m, 4);
  1073. imageTexture.set (0);
  1074. screenSize.set (targetWidth, targetHeight);
  1075. vOffsetAndScale.set (flipVertically ? 0.0f : 1.0f,
  1076. flipVertically ? 1.0f : -1.0f);
  1077. }
  1078. OpenGLShaderProgram::Attribute positionAttribute;
  1079. OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale;
  1080. };
  1081. OpenGLShaderProgram program;
  1082. ProgramBuilder builder;
  1083. Params params;
  1084. };
  1085. auto left = (GLshort) targetClipArea.getX();
  1086. auto top = (GLshort) targetClipArea.getY();
  1087. auto right = (GLshort) targetClipArea.getRight();
  1088. auto bottom = (GLshort) targetClipArea.getBottom();
  1089. const GLshort vertices[] = { left, bottom, right, bottom, left, top, right, top };
  1090. auto& program = OverlayShaderProgram::select (*this);
  1091. program.params.set ((float) contextWidth, (float) contextHeight, anchorPosAndTextureSize.toFloat(), flippedVertically);
  1092. GLuint vertexBuffer = 0;
  1093. extensions.glGenBuffers (1, &vertexBuffer);
  1094. extensions.glBindBuffer (GL_ARRAY_BUFFER, vertexBuffer);
  1095. extensions.glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
  1096. auto index = (GLuint) program.params.positionAttribute.attributeID;
  1097. extensions.glVertexAttribPointer (index, 2, GL_SHORT, GL_FALSE, 4, nullptr);
  1098. extensions.glEnableVertexAttribArray (index);
  1099. JUCE_CHECK_OPENGL_ERROR
  1100. if (extensions.glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  1101. {
  1102. glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
  1103. extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
  1104. extensions.glUseProgram (0);
  1105. extensions.glDisableVertexAttribArray (index);
  1106. extensions.glDeleteBuffers (1, &vertexBuffer);
  1107. }
  1108. else
  1109. {
  1110. clearGLError();
  1111. }
  1112. }
  1113. else
  1114. {
  1115. jassert (attachment == nullptr); // Running on an old graphics card!
  1116. }
  1117. JUCE_CHECK_OPENGL_ERROR
  1118. }
  1119. #if JUCE_ANDROID
  1120. EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY;
  1121. EGLDisplay OpenGLContext::NativeContext::config;
  1122. void OpenGLContext::NativeContext::surfaceCreated (LocalRef<jobject> holder)
  1123. {
  1124. ignoreUnused (holder);
  1125. if (auto* cachedImage = CachedImage::get (component))
  1126. {
  1127. if (auto* pool = cachedImage->renderThread.get())
  1128. {
  1129. if (! pool->contains (cachedImage))
  1130. {
  1131. cachedImage->resume();
  1132. cachedImage->context.triggerRepaint();
  1133. }
  1134. }
  1135. }
  1136. }
  1137. void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef<jobject> holder)
  1138. {
  1139. ignoreUnused (holder);
  1140. // unlike the name suggests this will be called just before the
  1141. // surface is destroyed. We need to pause the render thread.
  1142. if (auto* cachedImage = CachedImage::get (component))
  1143. {
  1144. cachedImage->pause();
  1145. if (auto* threadPool = cachedImage->renderThread.get())
  1146. threadPool->waitForJobToFinish (cachedImage, -1);
  1147. }
  1148. }
  1149. #endif
  1150. } // namespace juce