diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 8bc3a802be..e8810e0b5b 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -122,6 +122,216 @@ 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 { @@ -163,8 +373,6 @@ public: } #endif - createCVDisplayLink(); - if (isSharedWindow) { window = [viewToAttachTo window]; @@ -242,9 +450,6 @@ public: ~NSViewComponentPeer() override { - displayLink.reset(); - dispatchSource.reset(); - scopedObservers.clear(); setOwner (view, nullptr); @@ -1200,11 +1405,6 @@ public: setNeedsDisplayRectangles(); } - void windowDidChangeScreen() - { - updateCVDisplayLinkScreen(); - } - void viewMovedToWindow() { if (isSharedWindow) @@ -1224,9 +1424,6 @@ public: windowObservers.emplace_back (view, dismissModalsSelector, NSWindowWillMiniaturizeNotification, currentWindow); windowObservers.emplace_back (view, becomeKeySelector, NSWindowDidBecomeKeyNotification, currentWindow); windowObservers.emplace_back (view, resignKeySelector, NSWindowDidResignKeyNotification, currentWindow); - windowObservers.emplace_back (view, @selector (windowDidChangeScreen:), NSWindowDidChangeScreenNotification, currentWindow); - - updateCVDisplayLinkScreen(); } } @@ -1618,6 +1815,54 @@ public: static const SEL resignKeySelector; private: + /* 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) + { + 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] + { + const auto num = ComponentPeer::getNumPeers(); + + 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(); + }; + + dispatch_source_set_event_handler (dispatchSource.get(), ^() { callback(); }); + + if (@available (macOS 10.12, *)) + dispatch_activate (dispatchSource.get()); + else + dispatch_resume (dispatchSource.get()); + + return [dispatch = std::shared_ptr (std::move (dispatchSource))] + { + dispatch_source_merge_data (dispatch.get(), 1); + }; + } + + struct ConcreteDisplayLinks + { + ConcreteDisplayLinks() + : connection (links.registerFactory (asyncRepaintFactory)) + {} + + PerScreenDisplayLinks links; + PerScreenDisplayLinks::Connection connection; + }; + + SharedResourcePointer sharedDisplayLinks; + static NSView* createViewInstance(); static NSWindow* createWindowInstance(); @@ -1784,91 +2029,6 @@ private: } //============================================================================== - void updateCVDisplayLinkScreen() - { - const auto viewDisplayID = (CGDirectDisplayID) [[window.screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue]; - const auto result = CVDisplayLinkSetCurrentCGDisplay (displayLink->get(), viewDisplayID); - jassertquiet (result == kCVReturnSuccess); - } - - void createCVDisplayLink() - { - dispatchSource.reset (dispatch_source_create (DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue())); - dispatch_source_set_event_handler (dispatchSource.get(), ^(){ setNeedsDisplayRectangles(); }); - - if (@available (macOS 10.12, *)) - dispatch_activate (dispatchSource.get()); - else - dispatch_resume (dispatchSource.get()); - - displayLink.emplace (this); - } - - struct DisplayLinkDestructor - { - void operator() (CVDisplayLinkRef ptr) const - { - if (ptr != nullptr) - CVDisplayLinkRelease (ptr); - } - }; - - class ScopedDisplayLink - { - public: - explicit ScopedDisplayLink (NSViewComponentPeer* owner) - { - const auto callback = [] (CVDisplayLinkRef, - const CVTimeStamp*, - const CVTimeStamp*, - CVOptionFlags, - CVOptionFlags*, - void* context) -> int - { - dispatch_source_merge_data (static_cast (context)->dispatchSource.get(), 1); - return kCVReturnSuccess; - }; - - const auto callbackResult = CVDisplayLinkSetOutputCallback (link.get(), callback, owner); - jassertquiet (callbackResult == kCVReturnSuccess); - - const auto startResult = CVDisplayLinkStart (link.get()); - jassertquiet (startResult == kCVReturnSuccess); - } - - ~ScopedDisplayLink() - { - CVDisplayLinkStop (link.get()); - } - - CVDisplayLinkRef get() const { return link.get(); } - - private: - std::unique_ptr, DisplayLinkDestructor> link - { - [] - { - CVDisplayLinkRef ptr = nullptr; - const auto result = CVDisplayLinkCreateWithActiveCGDisplays (&ptr); - jassertquiet (result == kCVReturnSuccess); - jassertquiet (ptr != nullptr); - return ptr; - }() - }; - }; - - struct DispatchSourceDestructor - { - void operator() (dispatch_source_t ptr) const - { - if (ptr != nullptr) - dispatch_source_cancel (ptr); - } - }; - - Optional displayLink; - std::unique_ptr, DispatchSourceDestructor> dispatchSource; - int numFramesToSkipMetalRenderer = 0; std::unique_ptr> metalRenderer; @@ -2012,12 +2172,6 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper> } }); - addMethod (@selector (windowDidChangeScreen:), [] (id self, SEL, NSNotification*) - { - if (auto* p = getOwner (self)) - p->windowDidChangeScreen(); - }); - addMethod (@selector (wantsDefaultClipping), [] (id, SEL) { return YES; }); // (this is the default, but may want to customise it in future) addMethod (@selector (worksWhenModal), [] (id self, SEL)