mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
OpenGLContext: Share CVDisplayLinks with NSViewComponentPeer
This commit is contained in:
parent
20584cf201
commit
322aa64459
5 changed files with 387 additions and 336 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<void()> callback)
|
||||
: ScopedDisplayLink ((CGDirectDisplayID) [[screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue],
|
||||
std::move (callback))
|
||||
{}
|
||||
|
||||
ScopedDisplayLink (CGDirectDisplayID display, std::function<void()> 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<const ScopedDisplayLink*> (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<std::remove_pointer_t<CVDisplayLinkRef>, DisplayLinkDestructor> link;
|
||||
std::function<void()> 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<void()>;
|
||||
using Factory = std::function<RefreshCallback (NSScreen*)>;
|
||||
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
Connection() = default;
|
||||
Connection (PerScreenDisplayLinks& linksIn, std::list<Factory>::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<Factory>::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<NSObject>
|
||||
{
|
||||
ScreenObserverObject() : ObjCClass ("JUCEScreenObserver_")
|
||||
{
|
||||
addIvar<PerScreenDisplayLinks*> ("owner");
|
||||
addMethod (@selector (screensDidChange:), [] (id self, SEL, NSNotification*) { getOwner (self)->refreshScreens(); });
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static PerScreenDisplayLinks* getOwner (id self)
|
||||
{
|
||||
return getIvar<PerScreenDisplayLinks*> (self, "owner");
|
||||
}
|
||||
};
|
||||
|
||||
void refreshScreens()
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
|
||||
links.clear();
|
||||
|
||||
for (NSScreen* screen in [NSScreen screens])
|
||||
{
|
||||
std::vector<RefreshCallback> 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<NSObject>& getClass()
|
||||
{
|
||||
static ScreenObserverObject observer;
|
||||
return observer;
|
||||
}
|
||||
|
||||
CriticalSection mutex;
|
||||
std::list<Factory> factories;
|
||||
std::list<ScopedDisplayLink> links;
|
||||
|
||||
NSUniquePtr<NSObject> 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<PerScreenDisplayLinks> to
|
||||
// avoid unnecessarily duplicating display-link threads.
|
||||
SharedResourcePointer<PerScreenDisplayLinks> 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<void()> asyncRepaintFactory (NSScreen* screen)
|
||||
PerScreenDisplayLinks::Connection connection
|
||||
{
|
||||
using DispatchSource = std::remove_pointer_t<dispatch_source_t>;
|
||||
std::unique_ptr<DispatchSource, DispatchSourceDestructor> 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<NSViewComponentPeer*> (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<dispatch_source_t>;
|
||||
std::unique_ptr<DispatchSource, DispatchSourceDestructor> 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<DispatchSource> (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<DispatchSource> (std::move (dispatchSource))]
|
||||
{
|
||||
dispatch_source_merge_data (dispatch.get(), 1);
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
SharedResourcePointer<ConcreteDisplayLinks> sharedDisplayLinks;
|
||||
|
||||
static NSView* createViewInstance();
|
||||
static NSWindow* createWindowInstance();
|
||||
|
||||
|
|
|
|||
299
modules/juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h
Normal file
299
modules/juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h
Normal file
|
|
@ -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<void()>.
|
||||
*/
|
||||
class FunctionNotificationCenterObserver
|
||||
{
|
||||
public:
|
||||
FunctionNotificationCenterObserver (NSNotificationName notificationName,
|
||||
id objectToObserve,
|
||||
std::function<void()> callback)
|
||||
: onNotification (std::move (callback)),
|
||||
observer (observerObject.get(), getSelector(), notificationName, objectToObserve)
|
||||
{}
|
||||
|
||||
private:
|
||||
struct ObserverClass
|
||||
{
|
||||
ObserverClass()
|
||||
{
|
||||
klass.addIvar<FunctionNotificationCenterObserver*> ("owner");
|
||||
|
||||
klass.addMethod (getSelector(), [] (id self, SEL, NSNotification*)
|
||||
{
|
||||
getIvar<FunctionNotificationCenterObserver*> (self, "owner")->onNotification();
|
||||
});
|
||||
|
||||
klass.registerClass();
|
||||
}
|
||||
|
||||
NSObject* createInstance() const { return klass.createInstance(); }
|
||||
|
||||
private:
|
||||
ObjCClass<NSObject> klass { "JUCEObserverClass_" };
|
||||
};
|
||||
|
||||
static SEL getSelector()
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
return @selector (notificationFired:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
std::function<void()> onNotification;
|
||||
|
||||
NSUniquePtr<NSObject> 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<void()> 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<const ScopedDisplayLink*> (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<std::remove_pointer_t<CVDisplayLinkRef>, DisplayLinkDestructor> link;
|
||||
std::function<void()> 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<void()>;
|
||||
using Factory = std::function<RefreshCallback (NSScreen*)>;
|
||||
|
||||
/*
|
||||
Automatically unregisters a CVDisplayLink callback factory when ~Connection() is called.
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
Connection() = default;
|
||||
|
||||
Connection (PerScreenDisplayLinks& linksIn, std::list<Factory>::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<Factory>::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<Factory>::const_iterator iter)
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
factories.erase (iter);
|
||||
refreshScreens();
|
||||
}
|
||||
|
||||
void refreshScreens()
|
||||
{
|
||||
auto newLinks = [&]
|
||||
{
|
||||
std::list<ScopedDisplayLink> result;
|
||||
|
||||
for (NSScreen* screen in [NSScreen screens])
|
||||
{
|
||||
std::vector<RefreshCallback> 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<Factory> factories;
|
||||
// This is a list rather than a vector because ScopedDisplayLink is non-moveable.
|
||||
std::list<ScopedDisplayLink> links;
|
||||
|
||||
FunctionNotificationCenterObserver observer { NSApplicationDidChangeScreenParametersNotification,
|
||||
nullptr,
|
||||
[this] { refreshScreens(); } };
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -123,7 +123,7 @@ public:
|
|||
void shutdownOnRenderThread() { deactivateCurrentContext(); }
|
||||
|
||||
bool createdOk() const noexcept { return getRawContext() != nullptr; }
|
||||
void* getRawContext() const noexcept { return static_cast<void*> (renderContext); }
|
||||
NSOpenGLContext* getRawContext() const noexcept { return renderContext; }
|
||||
GLuint getFrameBufferID() const noexcept { return 0; }
|
||||
|
||||
bool makeActive() const noexcept
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_MAC
|
||||
#include <juce_gui_basics/native/juce_mac_PerScreenDisplayLinks.h>
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
|
|
@ -155,11 +159,6 @@ public:
|
|||
{
|
||||
if (nativeContext != nullptr)
|
||||
{
|
||||
#if JUCE_MAC
|
||||
cvDisplayLinkWrapper = std::make_unique<CVDisplayLinkWrapper> (*this);
|
||||
cvDisplayLinkWrapper->updateActiveDisplay();
|
||||
#endif
|
||||
|
||||
renderThread = std::make_unique<ThreadPool> (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<NSView*> (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<NSScreen*> lastScreen { nullptr };
|
||||
double refreshPeriod = 0.0;
|
||||
|
||||
FunctionNotificationCenterObserver observer { NSWindowDidChangeScreenNotification,
|
||||
getCurrentWindow(),
|
||||
[this] { updateScreen(); } };
|
||||
|
||||
// Note: the NSViewComponentPeer also has a SharedResourcePointer<PerScreenDisplayLinks> to
|
||||
// avoid unnecessarily duplicating display-link threads.
|
||||
SharedResourcePointer<PerScreenDisplayLinks> 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<CVDisplayLinkWrapper*> (displayLinkContext);
|
||||
|
||||
if (self->continuousRepaint)
|
||||
self->cachedImage.repaintEvent.signal();
|
||||
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
CachedImage& cachedImage;
|
||||
const bool continuousRepaint;
|
||||
CVDisplayLinkRef displayLink;
|
||||
NSScreen* currentScreen = nullptr;
|
||||
};
|
||||
|
||||
std::unique_ptr<CVDisplayLinkWrapper> 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<ThreadPool> renderThread;
|
||||
|
|
@ -984,16 +955,6 @@ public:
|
|||
}
|
||||
#endif
|
||||
|
||||
void update()
|
||||
{
|
||||
auto& comp = *getComponent();
|
||||
|
||||
if (canBeAttached (comp))
|
||||
start();
|
||||
else
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
OpenGLContext& context;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue