From 322aa644595dde82c0f2330c22af92ce1ab91af9 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 24 Aug 2022 21:54:34 +0100 Subject: [PATCH] OpenGLContext: Share CVDisplayLinks with NSViewComponentPeer --- modules/juce_gui_basics/juce_gui_basics.cpp | 1 + .../native/juce_mac_NSViewComponentPeer.mm | 280 ++-------------- .../native/juce_mac_PerScreenDisplayLinks.h | 299 ++++++++++++++++++ modules/juce_opengl/native/juce_OpenGL_osx.h | 2 +- .../juce_opengl/opengl/juce_OpenGLContext.cpp | 141 +++------ 5 files changed, 387 insertions(+), 336 deletions(-) create mode 100644 modules/juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index 50553e409e..cff1a633d9 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -290,6 +290,7 @@ namespace juce #else #include "native/accessibility/juce_mac_Accessibility.mm" + #include "native/juce_mac_PerScreenDisplayLinks.h" #include "native/juce_mac_NSViewComponentPeer.mm" #include "native/juce_mac_Windowing.mm" #include "native/juce_mac_MainMenu.mm" diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index ce386b8e46..2e27ee0e7a 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -122,216 +122,6 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept constexpr int extendedKeyModifier = 0x30000; -//============================================================================== -struct DisplayLinkDestructor -{ - void operator() (CVDisplayLinkRef ptr) const - { - if (ptr != nullptr) - CVDisplayLinkRelease (ptr); - } -}; - -/* Manages the lifetime of a CVDisplayLinkRef for a single display, and automatically starts and - stops it. -*/ -class ScopedDisplayLink -{ -public: - ScopedDisplayLink (NSScreen* screen, std::function callback) - : ScopedDisplayLink ((CGDirectDisplayID) [[screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue], - std::move (callback)) - {} - - ScopedDisplayLink (CGDirectDisplayID display, std::function onCallbackIn) - : link ([display] - { - CVDisplayLinkRef ptr = nullptr; - const auto result = CVDisplayLinkCreateWithCGDisplay (display, &ptr); - jassertquiet (result == kCVReturnSuccess); - jassertquiet (ptr != nullptr); - return ptr; - }()), - onCallback (std::move (onCallbackIn)) - { - const auto callback = [] (CVDisplayLinkRef, - const CVTimeStamp*, - const CVTimeStamp*, - CVOptionFlags, - CVOptionFlags*, - void* context) -> int - { - static_cast (context)->onCallback(); - return kCVReturnSuccess; - }; - - const auto callbackResult = CVDisplayLinkSetOutputCallback (link.get(), callback, this); - jassertquiet (callbackResult == kCVReturnSuccess); - - const auto startResult = CVDisplayLinkStart (link.get()); - jassertquiet (startResult == kCVReturnSuccess); - } - - ~ScopedDisplayLink() - { - CVDisplayLinkStop (link.get()); - } - -private: - std::unique_ptr, DisplayLinkDestructor> link; - std::function onCallback; -}; - -struct DispatchSourceDestructor -{ - void operator() (dispatch_source_t ptr) const - { - if (ptr != nullptr) - dispatch_source_cancel (ptr); - } -}; - -/* Holds a DisplayLinkRepaintDriver for each screen. When the screen configuration changes, the - DisplayLinkRepaintDrivers will be recreated automatically to match the new configuration. -*/ -class PerScreenDisplayLinks -{ -public: - PerScreenDisplayLinks() - { - refreshScreens(); - } - - using RefreshCallback = std::function; - using Factory = std::function; - - class Connection - { - public: - Connection() = default; - Connection (PerScreenDisplayLinks& linksIn, std::list::const_iterator it) - : links (&linksIn), iter (it) - {} - - ~Connection() - { - if (links != nullptr) - { - const ScopedLock lock (links->mutex); - links->factories.erase (iter); - links->refreshScreens(); - } - } - - Connection (const Connection&) = delete; - Connection& operator= (const Connection&) = delete; - - Connection (Connection&& other) noexcept - : links (std::exchange (other.links, nullptr)), iter (other.iter) - {} - - Connection& operator= (Connection&& other) noexcept - { - Connection { std::move (other) }.swap (*this); - return *this; - } - - private: - void swap (Connection& other) noexcept - { - std::swap (other.links, links); - std::swap (other.iter, iter); - } - - PerScreenDisplayLinks* links = nullptr; - std::list::const_iterator iter; - }; - - Connection registerFactory (Factory factory) - { - const ScopedLock lock (mutex); - factories.push_front (std::move (factory)); - refreshScreens(); - return { *this, factories.begin() }; - } - -private: - /* Screen configuration changes can be observed via the NotificationCenter, only - Objective-C objects can receive notifications. - - This class will be used to create an object that will listen for a screen change - notification, and forward it to the PerScreenDisplayLinks instance that owns the - ScreenObserverObject. - */ - struct ScreenObserverObject : public ObjCClass - { - ScreenObserverObject() : ObjCClass ("JUCEScreenObserver_") - { - addIvar ("owner"); - addMethod (@selector (screensDidChange:), [] (id self, SEL, NSNotification*) { getOwner (self)->refreshScreens(); }); - registerClass(); - } - - static PerScreenDisplayLinks* getOwner (id self) - { - return getIvar (self, "owner"); - } - }; - - void refreshScreens() - { - const ScopedLock lock (mutex); - - links.clear(); - - for (NSScreen* screen in [NSScreen screens]) - { - std::vector callbacks; - - for (auto& factory : factories) - callbacks.push_back (factory (screen)); - - links.emplace_back (screen, [callbacks = std::move (callbacks)] - { - for (const auto& callback : callbacks) - callback(); - }); - } - } - - static SEL getSelector() - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - return @selector (screensDidChange:); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - - static ObjCClass& getClass() - { - static ScreenObserverObject observer; - return observer; - } - - CriticalSection mutex; - std::list factories; - std::list links; - - NSUniquePtr observerObject - { - [this] - { - auto* result = getClass().createInstance(); - object_setInstanceVariable (result, "owner", this); - return result; - }() - }; - - ScopedNotificationCenterObserver observer { observerObject.get(), - getSelector(), - NSApplicationDidChangeScreenParametersNotification, - nullptr }; -}; - //============================================================================== class NSViewComponentPeer : public ComponentPeer { @@ -1815,54 +1605,54 @@ public: static const SEL resignKeySelector; private: + // Note: the OpenGLContext also has a SharedResourcePointer to + // avoid unnecessarily duplicating display-link threads. + SharedResourcePointer sharedDisplayLinks; + /* Creates a function object that can be called from an arbitrary thread (probably a CVLink thread). When called, this function object will trigger a call to setNeedsDisplayRectangles as soon as possible on the main thread, for any peers currently on the provided NSScreen. */ - static std::function asyncRepaintFactory (NSScreen* screen) + PerScreenDisplayLinks::Connection connection { - using DispatchSource = std::remove_pointer_t; - std::unique_ptr dispatchSource; - dispatchSource.reset (dispatch_source_create (DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue())); - jassert (dispatchSource != nullptr); - - const auto callback = [screen] + sharedDisplayLinks->registerFactory ([this] (auto* screen) { - const auto num = ComponentPeer::getNumPeers(); + struct DispatchSourceDestructor + { + void operator() (dispatch_source_t ptr) const + { + if (ptr != nullptr) + dispatch_source_cancel (ptr); + } + }; - for (auto i = 0; i < num; ++i) - if (auto* peer = static_cast (ComponentPeer::getPeer (i))) - if (auto* peerView = peer->view) - if (auto* peerWindow = [peerView window]) - if (screen == [peerWindow screen]) - peer->setNeedsDisplayRectangles(); - }; + using DispatchSource = std::remove_pointer_t; + std::unique_ptr dispatchSource; + dispatchSource.reset (dispatch_source_create (DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue())); + jassert (dispatchSource != nullptr); - dispatch_source_set_event_handler (dispatchSource.get(), ^() { callback(); }); + const auto callback = [this, screen] + { + if (auto* peerView = this->view) + if (auto* peerWindow = [peerView window]) + if (screen == [peerWindow screen]) + this->setNeedsDisplayRectangles(); + }; - if (@available (macOS 10.12, *)) - dispatch_activate (dispatchSource.get()); - else - dispatch_resume (dispatchSource.get()); + dispatch_source_set_event_handler (dispatchSource.get(), ^() { callback(); }); - return [dispatch = std::shared_ptr (std::move (dispatchSource))] - { - dispatch_source_merge_data (dispatch.get(), 1); - }; - } + if (@available (macOS 10.12, *)) + dispatch_activate (dispatchSource.get()); + else + dispatch_resume (dispatchSource.get()); - struct ConcreteDisplayLinks - { - ConcreteDisplayLinks() - : connection (links.registerFactory (asyncRepaintFactory)) - {} - - PerScreenDisplayLinks links; - PerScreenDisplayLinks::Connection connection; + return [dispatch = std::shared_ptr (std::move (dispatchSource))] + { + dispatch_source_merge_data (dispatch.get(), 1); + }; + }) }; - SharedResourcePointer sharedDisplayLinks; - static NSView* createViewInstance(); static NSWindow* createWindowInstance(); diff --git a/modules/juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h b/modules/juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h new file mode 100644 index 0000000000..0461c26ad4 --- /dev/null +++ b/modules/juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h @@ -0,0 +1,299 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/* + Forwards NSNotificationCenter callbacks to a std::function. +*/ +class FunctionNotificationCenterObserver +{ +public: + FunctionNotificationCenterObserver (NSNotificationName notificationName, + id objectToObserve, + std::function callback) + : onNotification (std::move (callback)), + observer (observerObject.get(), getSelector(), notificationName, objectToObserve) + {} + +private: + struct ObserverClass + { + ObserverClass() + { + klass.addIvar ("owner"); + + klass.addMethod (getSelector(), [] (id self, SEL, NSNotification*) + { + getIvar (self, "owner")->onNotification(); + }); + + klass.registerClass(); + } + + NSObject* createInstance() const { return klass.createInstance(); } + + private: + ObjCClass klass { "JUCEObserverClass_" }; + }; + + static SEL getSelector() + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + return @selector (notificationFired:); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + + std::function onNotification; + + NSUniquePtr observerObject + { + [this] + { + static ObserverClass observerClass; + auto* result = observerClass.createInstance(); + object_setInstanceVariable (result, "owner", this); + return result; + }() + }; + + ScopedNotificationCenterObserver observer; + + // Instances can't be copied or moved, because 'this' is stored as a member of the ObserverClass + // object. + JUCE_DECLARE_NON_COPYABLE (FunctionNotificationCenterObserver) + JUCE_DECLARE_NON_MOVEABLE (FunctionNotificationCenterObserver) +}; + +//============================================================================== +/* + Manages the lifetime of a CVDisplayLinkRef for a single display, and automatically starts and + stops it. +*/ +class ScopedDisplayLink +{ +public: + ScopedDisplayLink (NSScreen* screenIn, std::function onCallbackIn) + : screen (screenIn), + link ([display = (CGDirectDisplayID) [[screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue]] + { + CVDisplayLinkRef ptr = nullptr; + const auto result = CVDisplayLinkCreateWithCGDisplay (display, &ptr); + jassertquiet (result == kCVReturnSuccess); + jassertquiet (ptr != nullptr); + return ptr; + }()), + onCallback (std::move (onCallbackIn)) + { + const auto callback = [] (CVDisplayLinkRef, + const CVTimeStamp*, + const CVTimeStamp*, + CVOptionFlags, + CVOptionFlags*, + void* context) -> int + { + static_cast (context)->onCallback(); + return kCVReturnSuccess; + }; + + const auto callbackResult = CVDisplayLinkSetOutputCallback (link.get(), callback, this); + jassertquiet (callbackResult == kCVReturnSuccess); + + const auto startResult = CVDisplayLinkStart (link.get()); + jassertquiet (startResult == kCVReturnSuccess); + } + + ~ScopedDisplayLink() noexcept + { + if (link != nullptr) + CVDisplayLinkStop (link.get()); + } + + NSScreen* getScreen() const { return screen; } + + double getNominalVideoRefreshPeriodS() const + { + const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (link.get()); + + if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0) + return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale; + + return 0.0; + } + +private: + struct DisplayLinkDestructor + { + void operator() (CVDisplayLinkRef ptr) const + { + if (ptr != nullptr) + CVDisplayLinkRelease (ptr); + } + }; + + NSScreen* screen = nullptr; + std::unique_ptr, DisplayLinkDestructor> link; + std::function onCallback; + + // Instances can't be copied or moved, because 'this' is passed as context to + // CVDisplayLinkSetOutputCallback + JUCE_DECLARE_NON_COPYABLE (ScopedDisplayLink) + JUCE_DECLARE_NON_MOVEABLE (ScopedDisplayLink) +}; + +//============================================================================== +/* + Holds a ScopedDisplayLink for each screen. When the screen configuration changes, the + ScopedDisplayLinks will be recreated automatically to match the new configuration. +*/ +class PerScreenDisplayLinks +{ +public: + PerScreenDisplayLinks() + { + refreshScreens(); + } + + using RefreshCallback = std::function; + using Factory = std::function; + + /* + Automatically unregisters a CVDisplayLink callback factory when ~Connection() is called. + */ + class Connection + { + public: + Connection() = default; + + Connection (PerScreenDisplayLinks& linksIn, std::list::const_iterator it) + : links (&linksIn), iter (it) {} + + ~Connection() noexcept + { + if (links != nullptr) + links->unregisterFactory (iter); + } + + Connection (const Connection&) = delete; + Connection& operator= (const Connection&) = delete; + + Connection (Connection&& other) noexcept + : links (std::exchange (other.links, nullptr)), iter (other.iter) {} + + Connection& operator= (Connection&& other) noexcept + { + Connection { std::move (other) }.swap (*this); + return *this; + } + + private: + void swap (Connection& other) noexcept + { + std::swap (other.links, links); + std::swap (other.iter, iter); + } + + PerScreenDisplayLinks* links = nullptr; + std::list::const_iterator iter; + }; + + /* Stores the provided factory for as long as the returned Connection remains alive. + + Whenever the screen configuration changes, the factory function will be called for each + screen. The RefreshCallback returned by the factory will be called every time that screen's + display link callback fires. + */ + JUCE_NODISCARD Connection registerFactory (Factory factory) + { + const ScopedLock lock (mutex); + factories.push_front (std::move (factory)); + refreshScreens(); + return { *this, factories.begin() }; + } + + double getNominalVideoRefreshPeriodSForScreen (NSScreen* screen) const + { + const ScopedLock lock (mutex); + + for (const auto& link : links) + if (link.getScreen() == screen) + return link.getNominalVideoRefreshPeriodS(); + + return 0.0; + } + +private: + void unregisterFactory (std::list::const_iterator iter) + { + const ScopedLock lock (mutex); + factories.erase (iter); + refreshScreens(); + } + + void refreshScreens() + { + auto newLinks = [&] + { + std::list result; + + for (NSScreen* screen in [NSScreen screens]) + { + std::vector callbacks; + + for (auto& factory : factories) + callbacks.push_back (factory (screen)); + + // This is the callback that will actually fire in response to this screen's display + // link callback. + result.emplace_back (screen, [callbacks = std::move (callbacks)] + { + for (const auto& callback : callbacks) + callback(); + }); + } + + return result; + }(); + + const ScopedLock lock (mutex); + links = std::move (newLinks); + } + + CriticalSection mutex; + // This is a list rather than a vector so that the iterators are stable, even when items are + // added/removed from the list. This is important because Connection objects store an iterator + // internally, and may be created/destroyed arbitrarily. + std::list factories; + // This is a list rather than a vector because ScopedDisplayLink is non-moveable. + std::list links; + + FunctionNotificationCenterObserver observer { NSApplicationDidChangeScreenParametersNotification, + nullptr, + [this] { refreshScreens(); } }; +}; + +} // namespace juce diff --git a/modules/juce_opengl/native/juce_OpenGL_osx.h b/modules/juce_opengl/native/juce_OpenGL_osx.h index 89afda20a9..25a03daa94 100644 --- a/modules/juce_opengl/native/juce_OpenGL_osx.h +++ b/modules/juce_opengl/native/juce_OpenGL_osx.h @@ -123,7 +123,7 @@ public: void shutdownOnRenderThread() { deactivateCurrentContext(); } bool createdOk() const noexcept { return getRawContext() != nullptr; } - void* getRawContext() const noexcept { return static_cast (renderContext); } + NSOpenGLContext* getRawContext() const noexcept { return renderContext; } GLuint getFrameBufferID() const noexcept { return 0; } bool makeActive() const noexcept diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index 79bf388080..220f5a1991 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -23,6 +23,10 @@ ============================================================================== */ +#if JUCE_MAC + #include +#endif + namespace juce { @@ -155,11 +159,6 @@ public: { if (nativeContext != nullptr) { - #if JUCE_MAC - cvDisplayLinkWrapper = std::make_unique (*this); - cvDisplayLinkWrapper->updateActiveDisplay(); - #endif - renderThread = std::make_unique (1); resume(); } @@ -185,10 +184,6 @@ public: renderThread.reset(); } - #if JUCE_MAC - cvDisplayLinkWrapper = nullptr; - #endif - hasInitialised = false; } @@ -359,12 +354,10 @@ public: if (auto* peer = component.getPeer()) { #if JUCE_MAC + updateScreen(); + const auto displayScale = Desktop::getInstance().getGlobalScaleFactor() * [this] { - if (auto* wrapper = cvDisplayLinkWrapper.get()) - if (wrapper->updateActiveDisplay()) - nativeContext->setNominalVideoRefreshPeriodS (wrapper->getNominalVideoRefreshPeriodS()); - if (auto* view = getCurrentView()) { if ([view respondsToSelector: @selector (backingScaleFactor)]) @@ -651,11 +644,6 @@ public: if (context.renderer != nullptr) context.renderer->newOpenGLContextCreated(); - #if JUCE_MAC - jassert (cvDisplayLinkWrapper != nullptr); - nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS()); - #endif - return true; } @@ -811,84 +799,67 @@ public: #if JUCE_MAC NSView* getCurrentView() const { + JUCE_ASSERT_MESSAGE_THREAD; + if (auto* peer = component.getPeer()) return static_cast (peer->getNativeHandle()); return nullptr; } + NSWindow* getCurrentWindow() const + { + JUCE_ASSERT_MESSAGE_THREAD; + + if (auto* view = getCurrentView()) + return [view window]; + + return nullptr; + } + NSScreen* getCurrentScreen() const { JUCE_ASSERT_MESSAGE_THREAD; - if (auto* view = getCurrentView()) - if (auto* window = [view window]) - return [window screen]; + if (auto* window = getCurrentWindow()) + return [window screen]; return nullptr; } - struct CVDisplayLinkWrapper + void updateScreen() { - explicit CVDisplayLinkWrapper (CachedImage& cachedImageIn) - : cachedImage (cachedImageIn), - continuousRepaint (cachedImageIn.context.continuousRepaint.load()) + const auto screen = getCurrentScreen(); + lastScreen = screen; + + const auto newRefreshPeriod = sharedDisplayLinks->getNominalVideoRefreshPeriodSForScreen (screen); + + if (newRefreshPeriod != 0.0 && std::exchange (refreshPeriod, newRefreshPeriod) != newRefreshPeriod) + nativeContext->setNominalVideoRefreshPeriodS (newRefreshPeriod); + } + + std::atomic lastScreen { nullptr }; + double refreshPeriod = 0.0; + + FunctionNotificationCenterObserver observer { NSWindowDidChangeScreenNotification, + getCurrentWindow(), + [this] { updateScreen(); } }; + + // Note: the NSViewComponentPeer also has a SharedResourcePointer to + // avoid unnecessarily duplicating display-link threads. + SharedResourcePointer sharedDisplayLinks; + + PerScreenDisplayLinks::Connection connection { sharedDisplayLinks->registerFactory ([this] (auto* screen) + { + return [this, screen] { - CVDisplayLinkCreateWithActiveCGDisplays (&displayLink); - CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this); - CVDisplayLinkStart (displayLink); - } - - double getNominalVideoRefreshPeriodS() const - { - const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink); - - if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0) - return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale; - - return 0.0; - } - - /* Returns true if updated, or false otherwise. */ - bool updateActiveDisplay() - { - auto* oldScreen = std::exchange (currentScreen, cachedImage.getCurrentScreen()); - - if (oldScreen == currentScreen) - return false; - - for (NSScreen* screen in [NSScreen screens]) - if (screen == currentScreen) - if (NSNumber* number = [[screen deviceDescription] objectForKey: @"NSScreenNumber"]) - CVDisplayLinkSetCurrentCGDisplay (displayLink, [number unsignedIntValue]); - - return true; - } - - ~CVDisplayLinkWrapper() - { - CVDisplayLinkStop (displayLink); - CVDisplayLinkRelease (displayLink); - } - - static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*, - CVOptionFlags, CVOptionFlags*, void* displayLinkContext) - { - auto* self = reinterpret_cast (displayLinkContext); - - if (self->continuousRepaint) - self->cachedImage.repaintEvent.signal(); - - return kCVReturnSuccess; - } - - CachedImage& cachedImage; - const bool continuousRepaint; - CVDisplayLinkRef displayLink; - NSScreen* currentScreen = nullptr; - }; - - std::unique_ptr cvDisplayLinkWrapper; + // Note: check against lastScreen rather than trying to access the component's peer here, + // because this function is not called on the main thread. + if (context.continuousRepaint) + if (screen == lastScreen) + repaintEvent.signal(); + }; + }) }; #endif std::unique_ptr renderThread; @@ -984,16 +955,6 @@ public: } #endif - void update() - { - auto& comp = *getComponent(); - - if (canBeAttached (comp)) - start(); - else - stop(); - } - private: OpenGLContext& context;