mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
macOS/iOS: Sync repaint request rate to screen FPS and remove repaint throttling in plug-ins
This commit is contained in:
parent
89a67ec556
commit
04e7014d0f
2 changed files with 126 additions and 27 deletions
|
|
@ -105,16 +105,28 @@ enum class MouseEventFlags
|
||||||
|
|
||||||
using namespace juce;
|
using namespace juce;
|
||||||
|
|
||||||
|
struct CADisplayLinkDeleter
|
||||||
|
{
|
||||||
|
void operator() (CADisplayLink* displayLink) const noexcept
|
||||||
|
{
|
||||||
|
[displayLink invalidate];
|
||||||
|
[displayLink release];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@interface JuceUIView : UIView <UITextViewDelegate>
|
@interface JuceUIView : UIView <UITextViewDelegate>
|
||||||
{
|
{
|
||||||
@public
|
@public
|
||||||
UIViewComponentPeer* owner;
|
UIViewComponentPeer* owner;
|
||||||
UITextView* hiddenTextView;
|
UITextView* hiddenTextView;
|
||||||
|
std::unique_ptr<CADisplayLink, CADisplayLinkDeleter> displayLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame;
|
- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame;
|
||||||
- (void) dealloc;
|
- (void) dealloc;
|
||||||
|
|
||||||
|
- (void) displayLinkCallback: (CADisplayLink*) dl;
|
||||||
|
|
||||||
- (void) drawRect: (CGRect) r;
|
- (void) drawRect: (CGRect) r;
|
||||||
|
|
||||||
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event;
|
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event;
|
||||||
|
|
@ -224,6 +236,8 @@ public:
|
||||||
void setIcon (const Image& newIcon) override;
|
void setIcon (const Image& newIcon) override;
|
||||||
StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); }
|
StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); }
|
||||||
|
|
||||||
|
void displayLinkCallback();
|
||||||
|
|
||||||
void drawRect (CGRect);
|
void drawRect (CGRect);
|
||||||
bool canBecomeKeyWindow();
|
bool canBecomeKeyWindow();
|
||||||
|
|
||||||
|
|
@ -301,6 +315,8 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RectangleList<float> deferredRepaints;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
|
||||||
};
|
};
|
||||||
|
|
@ -453,6 +469,11 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
|
||||||
[super initWithFrame: frame];
|
[super initWithFrame: frame];
|
||||||
owner = peer;
|
owner = peer;
|
||||||
|
|
||||||
|
displayLink.reset ([CADisplayLink displayLinkWithTarget: self
|
||||||
|
selector: @selector (displayLinkCallback:)]);
|
||||||
|
[displayLink.get() addToRunLoop: [NSRunLoop mainRunLoop]
|
||||||
|
forMode: NSDefaultRunLoopMode];
|
||||||
|
|
||||||
hiddenTextView = [[UITextView alloc] initWithFrame: CGRectZero];
|
hiddenTextView = [[UITextView alloc] initWithFrame: CGRectZero];
|
||||||
[self addSubview: hiddenTextView];
|
[self addSubview: hiddenTextView];
|
||||||
hiddenTextView.delegate = self;
|
hiddenTextView.delegate = self;
|
||||||
|
|
@ -487,9 +508,18 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
|
||||||
[hiddenTextView removeFromSuperview];
|
[hiddenTextView removeFromSuperview];
|
||||||
[hiddenTextView release];
|
[hiddenTextView release];
|
||||||
|
|
||||||
|
displayLink = nullptr;
|
||||||
|
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
- (void) displayLinkCallback: (CADisplayLink*) dl
|
||||||
|
{
|
||||||
|
if (owner != nullptr)
|
||||||
|
owner->displayLinkCallback();
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
- (void) drawRect: (CGRect) r
|
- (void) drawRect: (CGRect) r
|
||||||
{
|
{
|
||||||
|
|
@ -1137,6 +1167,15 @@ void UIViewComponentPeer::globalFocusChanged (Component*)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void UIViewComponentPeer::displayLinkCallback()
|
||||||
|
{
|
||||||
|
for (const auto& r : deferredRepaints)
|
||||||
|
[view setNeedsDisplayInRect: convertToCGRect (r)];
|
||||||
|
|
||||||
|
deferredRepaints.clear();
|
||||||
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void UIViewComponentPeer::drawRect (CGRect r)
|
void UIViewComponentPeer::drawRect (CGRect r)
|
||||||
{
|
{
|
||||||
|
|
@ -1201,9 +1240,12 @@ void Desktop::allowedOrientationsChanged()
|
||||||
void UIViewComponentPeer::repaint (const Rectangle<int>& area)
|
void UIViewComponentPeer::repaint (const Rectangle<int>& area)
|
||||||
{
|
{
|
||||||
if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread())
|
if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread())
|
||||||
|
{
|
||||||
(new AsyncRepaintMessage (this, area))->post();
|
(new AsyncRepaintMessage (this, area))->post();
|
||||||
else
|
return;
|
||||||
[view setNeedsDisplayInRect: convertToCGRect (area)];
|
}
|
||||||
|
|
||||||
|
deferredRepaints.add (area.toFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIViewComponentPeer::performAnyPendingRepaintsNow()
|
void UIViewComponentPeer::performAnyPendingRepaintsNow()
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,7 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept
|
||||||
constexpr int extendedKeyModifier = 0x30000;
|
constexpr int extendedKeyModifier = 0x30000;
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class NSViewComponentPeer : public ComponentPeer,
|
class NSViewComponentPeer : public ComponentPeer
|
||||||
private Timer
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo)
|
NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo)
|
||||||
|
|
@ -145,6 +144,8 @@ public:
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
createCVDisplayLink();
|
||||||
|
|
||||||
if (isSharedWindow)
|
if (isSharedWindow)
|
||||||
{
|
{
|
||||||
window = [viewToAttachTo window];
|
window = [viewToAttachTo window];
|
||||||
|
|
@ -239,6 +240,9 @@ public:
|
||||||
|
|
||||||
~NSViewComponentPeer() override
|
~NSViewComponentPeer() override
|
||||||
{
|
{
|
||||||
|
CVDisplayLinkStop (displayLink);
|
||||||
|
dispatch_source_cancel (displaySource);
|
||||||
|
|
||||||
[notificationCenter removeObserver: view];
|
[notificationCenter removeObserver: view];
|
||||||
setOwner (view, nullptr);
|
setOwner (view, nullptr);
|
||||||
|
|
||||||
|
|
@ -779,6 +783,10 @@ public:
|
||||||
[notificationCenter removeObserver: view
|
[notificationCenter removeObserver: view
|
||||||
name: NSWindowDidBecomeKeyNotification
|
name: NSWindowDidBecomeKeyNotification
|
||||||
object: currentWindow];
|
object: currentWindow];
|
||||||
|
|
||||||
|
[notificationCenter removeObserver: view
|
||||||
|
name: NSWindowDidChangeScreenNotification
|
||||||
|
object: currentWindow];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSharedWindow && [view window] == window && newWindow == nullptr)
|
if (isSharedWindow && [view window] == window && newWindow == nullptr)
|
||||||
|
|
@ -1016,43 +1024,31 @@ public:
|
||||||
// a few when there's a lot of activity.
|
// a few when there's a lot of activity.
|
||||||
// As a work around for this, we use a RectangleList to do our own coalescing of regions before
|
// As a work around for this, we use a RectangleList to do our own coalescing of regions before
|
||||||
// asynchronously asking the OS to repaint them.
|
// asynchronously asking the OS to repaint them.
|
||||||
deferredRepaints.add ((float) area.getX(), (float) area.getY(),
|
deferredRepaints.add (area.toFloat());
|
||||||
(float) area.getWidth(), (float) area.getHeight());
|
}
|
||||||
|
|
||||||
if (isTimerRunning())
|
static bool shouldThrottleRepaint()
|
||||||
|
{
|
||||||
|
return areAnyWindowsInLiveResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNeedsDisplayRectangles()
|
||||||
|
{
|
||||||
|
if (deferredRepaints.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto now = Time::getMillisecondCounter();
|
auto now = Time::getMillisecondCounter();
|
||||||
auto msSinceLastRepaint = (lastRepaintTime >= now) ? now - lastRepaintTime
|
auto msSinceLastRepaint = (lastRepaintTime >= now) ? now - lastRepaintTime
|
||||||
: (std::numeric_limits<uint32>::max() - lastRepaintTime) + now;
|
: (std::numeric_limits<uint32>::max() - lastRepaintTime) + now;
|
||||||
|
|
||||||
static uint32 minimumRepaintInterval = 1000 / 30; // 30fps
|
constexpr uint32 minimumRepaintInterval = 1000 / 30; // 30fps
|
||||||
|
|
||||||
// When windows are being resized, artificially throttling high-frequency repaints helps
|
// When windows are being resized, artificially throttling high-frequency repaints helps
|
||||||
// to stop the event queue getting clogged, and keeps everything working smoothly.
|
// to stop the event queue getting clogged, and keeps everything working smoothly.
|
||||||
// For some reason Logic also needs this throttling to record parameter events correctly.
|
// For some reason Logic also needs this throttling to record parameter events correctly.
|
||||||
if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
|
if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
|
||||||
{
|
|
||||||
startTimer (static_cast<int> (minimumRepaintInterval - msSinceLastRepaint));
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
setNeedsDisplayRectangles();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool shouldThrottleRepaint()
|
|
||||||
{
|
|
||||||
return areAnyWindowsInLiveResize() || ! JUCEApplication::isStandaloneApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
void timerCallback() override
|
|
||||||
{
|
|
||||||
setNeedsDisplayRectangles();
|
|
||||||
stopTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setNeedsDisplayRectangles()
|
|
||||||
{
|
|
||||||
for (auto& i : deferredRepaints)
|
for (auto& i : deferredRepaints)
|
||||||
[view setNeedsDisplayInRect: makeNSRect (i)];
|
[view setNeedsDisplayInRect: makeNSRect (i)];
|
||||||
|
|
||||||
|
|
@ -1163,6 +1159,11 @@ public:
|
||||||
handleMovedOrResized();
|
handleMovedOrResized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void windowDidChangeScreen()
|
||||||
|
{
|
||||||
|
updateCVDisplayLinkScreen();
|
||||||
|
}
|
||||||
|
|
||||||
void viewMovedToWindow()
|
void viewMovedToWindow()
|
||||||
{
|
{
|
||||||
if (isSharedWindow)
|
if (isSharedWindow)
|
||||||
|
|
@ -1197,6 +1198,13 @@ public:
|
||||||
selector: resignKeySelector
|
selector: resignKeySelector
|
||||||
name: NSWindowDidResignKeyNotification
|
name: NSWindowDidResignKeyNotification
|
||||||
object: currentWindow];
|
object: currentWindow];
|
||||||
|
|
||||||
|
[notificationCenter addObserver: view
|
||||||
|
selector: @selector (windowDidChangeScreen:)
|
||||||
|
name: NSWindowDidChangeScreenNotification
|
||||||
|
object: currentWindow];
|
||||||
|
|
||||||
|
updateCVDisplayLinkScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1786,6 +1794,48 @@ private:
|
||||||
[window setMaxFullScreenContentSize: NSMakeSize (100000, 100000)];
|
[window setMaxFullScreenContentSize: NSMakeSize (100000, 100000)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onDisplaySourceCallback()
|
||||||
|
{
|
||||||
|
setNeedsDisplayRectangles();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDisplayLinkCallback()
|
||||||
|
{
|
||||||
|
dispatch_source_merge_data (displaySource, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
|
||||||
|
CVOptionFlags, CVOptionFlags*, void* context)
|
||||||
|
{
|
||||||
|
static_cast<NSViewComponentPeer*> (context)->onDisplayLinkCallback();
|
||||||
|
return kCVReturnSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateCVDisplayLinkScreen()
|
||||||
|
{
|
||||||
|
auto viewDisplayID = (CGDirectDisplayID) [window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue];
|
||||||
|
auto result = CVDisplayLinkSetCurrentCGDisplay (displayLink, viewDisplayID);
|
||||||
|
jassertquiet (result == kCVReturnSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createCVDisplayLink()
|
||||||
|
{
|
||||||
|
displaySource = dispatch_source_create (DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
|
||||||
|
dispatch_source_set_event_handler (displaySource, ^(){ onDisplaySourceCallback(); });
|
||||||
|
dispatch_resume (displaySource);
|
||||||
|
|
||||||
|
auto cvReturn = CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
|
||||||
|
jassertquiet (cvReturn == kCVReturnSuccess);
|
||||||
|
|
||||||
|
cvReturn = CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
|
||||||
|
jassertquiet (cvReturn == kCVReturnSuccess);
|
||||||
|
|
||||||
|
CVDisplayLinkStart (displayLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
CVDisplayLinkRef displayLink = nullptr;
|
||||||
|
dispatch_source_t displaySource = nullptr;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1848,6 +1898,7 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper<ObjCClass<NSView>>
|
||||||
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse);
|
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse);
|
||||||
addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize);
|
addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize);
|
||||||
addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize);
|
addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize);
|
||||||
|
addMethod (@selector (windowDidChangeScreen:), windowDidChangeScreen);
|
||||||
addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping);
|
addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping);
|
||||||
addMethod (@selector (worksWhenModal), worksWhenModal);
|
addMethod (@selector (worksWhenModal), worksWhenModal);
|
||||||
addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow);
|
addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow);
|
||||||
|
|
@ -2014,6 +2065,12 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void windowDidChangeScreen (id self, SEL, NSNotification*)
|
||||||
|
{
|
||||||
|
if (auto* p = getOwner (self))
|
||||||
|
p->windowDidChangeScreen();
|
||||||
|
}
|
||||||
|
|
||||||
static BOOL isOpaque (id self, SEL)
|
static BOOL isOpaque (id self, SEL)
|
||||||
{
|
{
|
||||||
auto* owner = getOwner (self);
|
auto* owner = getOwner (self);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue