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;
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue