Browse Source

macOS/iOS: Sync repaint request rate to screen FPS and remove repaint throttling in plug-ins

pull/22/head
Tom Poole 3 years ago
parent
commit
04e7014d0f
2 changed files with 126 additions and 27 deletions
  1. +44
    -2
      modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm
  2. +82
    -25
      modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm

+ 44
- 2
modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm View File

@@ -105,16 +105,28 @@ enum class MouseEventFlags
using namespace juce;
struct CADisplayLinkDeleter
{
void operator() (CADisplayLink* displayLink) const noexcept
{
[displayLink invalidate];
[displayLink release];
}
};
@interface JuceUIView : UIView <UITextViewDelegate>
{
@public
UIViewComponentPeer* owner;
UITextView* hiddenTextView;
std::unique_ptr<CADisplayLink, CADisplayLinkDeleter> displayLink;
}
- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame;
- (void) dealloc;
- (void) displayLinkCallback: (CADisplayLink*) dl;
- (void) drawRect: (CGRect) r;
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event;
@@ -224,6 +236,8 @@ public:
void setIcon (const Image& newIcon) override;
StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); }
void displayLinkCallback();
void drawRect (CGRect);
bool canBecomeKeyWindow();
@@ -301,6 +315,8 @@ private:
}
};
RectangleList<float> deferredRepaints;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
};
@@ -453,6 +469,11 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
[super initWithFrame: frame];
owner = peer;
displayLink.reset ([CADisplayLink displayLinkWithTarget: self
selector: @selector (displayLinkCallback:)]);
[displayLink.get() addToRunLoop: [NSRunLoop mainRunLoop]
forMode: NSDefaultRunLoopMode];
hiddenTextView = [[UITextView alloc] initWithFrame: CGRectZero];
[self addSubview: hiddenTextView];
hiddenTextView.delegate = self;
@@ -487,9 +508,18 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
[hiddenTextView removeFromSuperview];
[hiddenTextView release];
displayLink = nullptr;
[super dealloc];
}
//==============================================================================
- (void) displayLinkCallback: (CADisplayLink*) dl
{
if (owner != nullptr)
owner->displayLinkCallback();
}
//==============================================================================
- (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)
{
@@ -1201,9 +1240,12 @@ void Desktop::allowedOrientationsChanged()
void UIViewComponentPeer::repaint (const Rectangle<int>& area)
{
if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread())
{
(new AsyncRepaintMessage (this, area))->post();
else
[view setNeedsDisplayInRect: convertToCGRect (area)];
return;
}
deferredRepaints.add (area.toFloat());
}
void UIViewComponentPeer::performAnyPendingRepaintsNow()


+ 82
- 25
modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm View File

@@ -94,8 +94,7 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept
constexpr int extendedKeyModifier = 0x30000;
//==============================================================================
class NSViewComponentPeer : public ComponentPeer,
private Timer
class NSViewComponentPeer : public ComponentPeer
{
public:
NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo)
@@ -145,6 +144,8 @@ public:
}
#endif
createCVDisplayLink();
if (isSharedWindow)
{
window = [viewToAttachTo window];
@@ -239,6 +240,9 @@ public:
~NSViewComponentPeer() override
{
CVDisplayLinkStop (displayLink);
dispatch_source_cancel (displaySource);
[notificationCenter removeObserver: view];
setOwner (view, nullptr);
@@ -779,6 +783,10 @@ public:
[notificationCenter removeObserver: view
name: NSWindowDidBecomeKeyNotification
object: currentWindow];
[notificationCenter removeObserver: view
name: NSWindowDidChangeScreenNotification
object: currentWindow];
}
if (isSharedWindow && [view window] == window && newWindow == nullptr)
@@ -1016,43 +1024,31 @@ public:
// 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
// asynchronously asking the OS to repaint them.
deferredRepaints.add ((float) area.getX(), (float) area.getY(),
(float) area.getWidth(), (float) area.getHeight());
deferredRepaints.add (area.toFloat());
}
if (isTimerRunning())
static bool shouldThrottleRepaint()
{
return areAnyWindowsInLiveResize();
}
void setNeedsDisplayRectangles()
{
if (deferredRepaints.isEmpty())
return;
auto now = Time::getMillisecondCounter();
auto msSinceLastRepaint = (lastRepaintTime >= now) ? now - lastRepaintTime
: (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
// 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.
if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
{
startTimer (static_cast<int> (minimumRepaintInterval - msSinceLastRepaint));
return;
}
setNeedsDisplayRectangles();
}
static bool shouldThrottleRepaint()
{
return areAnyWindowsInLiveResize() || ! JUCEApplication::isStandaloneApp();
}
void timerCallback() override
{
setNeedsDisplayRectangles();
stopTimer();
}
void setNeedsDisplayRectangles()
{
for (auto& i : deferredRepaints)
[view setNeedsDisplayInRect: makeNSRect (i)];
@@ -1163,6 +1159,11 @@ public:
handleMovedOrResized();
}
void windowDidChangeScreen()
{
updateCVDisplayLinkScreen();
}
void viewMovedToWindow()
{
if (isSharedWindow)
@@ -1197,6 +1198,13 @@ public:
selector: resignKeySelector
name: NSWindowDidResignKeyNotification
object: currentWindow];
[notificationCenter addObserver: view
selector: @selector (windowDidChangeScreen:)
name: NSWindowDidChangeScreenNotification
object: currentWindow];
updateCVDisplayLinkScreen();
}
}
@@ -1786,6 +1794,48 @@ private:
[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)
};
@@ -1848,6 +1898,7 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper<ObjCClass<NSView>>
addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse);
addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize);
addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize);
addMethod (@selector (windowDidChangeScreen:), windowDidChangeScreen);
addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping);
addMethod (@selector (worksWhenModal), worksWhenModal);
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)
{
auto* owner = getOwner (self);


Loading…
Cancel
Save