Skip to content

Commit cea889d

Browse files
szarvastpoole
authored andcommitted
macOS OpenGL: Use display refresh rate when rate limiting swapBuffers()
1 parent 6d6ecff commit cea889d

File tree

2 files changed

+80
-12
lines changed

2 files changed

+80
-12
lines changed

modules/juce_opengl/native/juce_OpenGL_osx.h

+20-4
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,23 @@ class OpenGLContext::NativeContext
197197

198198
void updateWindowPosition (Rectangle<int>) {}
199199

200-
bool setSwapInterval (int numFramesPerSwap)
200+
bool setSwapInterval (int numFramesPerSwapIn)
201201
{
202+
numFramesPerSwap = numFramesPerSwapIn;
203+
202204
// The macOS OpenGL programming guide says that numFramesPerSwap
203205
// can only be 0 or 1.
204206
jassert (isPositiveAndBelow (numFramesPerSwap, 2));
205207

206-
minSwapTimeMs = (numFramesPerSwap * 1000) / 60;
207-
208208
[renderContext setValues: (const GLint*) &numFramesPerSwap
209209
#if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12
210210
forParameter: NSOpenGLContextParameterSwapInterval];
211211
#else
212212
forParameter: NSOpenGLCPSwapInterval];
213213
#endif
214+
215+
updateMinSwapTime();
216+
214217
return true;
215218
}
216219

@@ -227,11 +230,24 @@ class OpenGLContext::NativeContext
227230
return numFrames;
228231
}
229232

233+
void setNominalVideoRefreshPeriodS (double periodS)
234+
{
235+
jassert (periodS > 0.0);
236+
videoRefreshPeriodS = periodS;
237+
updateMinSwapTime();
238+
}
239+
240+
void updateMinSwapTime()
241+
{
242+
minSwapTimeMs = static_cast<int> (numFramesPerSwap * 1000 * videoRefreshPeriodS);
243+
}
244+
230245
NSOpenGLContext* renderContext = nil;
231246
NSOpenGLView* view = nil;
232247
ReferenceCountedObjectPtr<ReferenceCountedObject> viewAttachment;
233248
double lastSwapTime = 0;
234-
int minSwapTimeMs = 0, underrunCounter = 0;
249+
int minSwapTimeMs = 0, underrunCounter = 0, numFramesPerSwap = 0;
250+
double videoRefreshPeriodS = 1.0 / 60.0;
235251

236252
//==============================================================================
237253
struct MouseForwardingNSOpenGLViewClass : public ObjCClass<NSOpenGLView>

modules/juce_opengl/opengl/juce_OpenGLContext.cpp

+60-8
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,21 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
304304
if (auto* peer = component.getPeer())
305305
{
306306
auto localBounds = component.getLocalBounds();
307-
auto displayScale = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds())->scale;
307+
const auto currentDisplay = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds());
308+
309+
if (currentDisplay != lastDisplay)
310+
{
311+
#if JUCE_MAC
312+
if (cvDisplayLinkWrapper != nullptr)
313+
{
314+
cvDisplayLinkWrapper->updateActiveDisplay (currentDisplay->totalArea.getTopLeft());
315+
nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS());
316+
}
317+
#endif
318+
lastDisplay = currentDisplay;
319+
}
320+
321+
const auto displayScale = currentDisplay->scale;
308322

309323
auto newArea = peer->getComponent().getLocalArea (&component, localBounds).withZeroOrigin() * displayScale;
310324

@@ -345,7 +359,10 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
345359
auto screenBounds = component.getTopLevelComponent()->getScreenBounds();
346360

347361
if (lastScreenBounds != screenBounds)
362+
{
348363
updateViewportSize (true);
364+
lastScreenBounds = screenBounds;
365+
}
349366
}
350367

351368
void paintComponent()
@@ -494,7 +511,7 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
494511
break;
495512

496513
#if JUCE_MAC
497-
if (cvDisplayLinkWrapper != nullptr)
514+
if (context.continuousRepaint)
498515
{
499516
repaintEvent.wait (-1);
500517
renderFrame();
@@ -561,8 +578,8 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
561578
context.renderer->newOpenGLContextCreated();
562579

563580
#if JUCE_MAC
564-
if (context.continuousRepaint)
565-
cvDisplayLinkWrapper = std::make_unique<CVDisplayLinkWrapper> (this);
581+
cvDisplayLinkWrapper = std::make_unique<CVDisplayLinkWrapper> (*this);
582+
nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS());
566583
#endif
567584

568585
return true;
@@ -680,6 +697,7 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
680697
OpenGLFrameBuffer cachedImageFrameBuffer;
681698
RectangleList<int> validArea;
682699
Rectangle<int> viewportArea, lastScreenBounds;
700+
const Displays::Display* lastDisplay = nullptr;
683701
double scale = 1.0;
684702
AffineTransform transform;
685703
GLuint vertexArrayObject = 0;
@@ -700,11 +718,40 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
700718
#if JUCE_MAC
701719
struct CVDisplayLinkWrapper
702720
{
703-
CVDisplayLinkWrapper (CachedImage* im)
721+
CVDisplayLinkWrapper (CachedImage& cachedImageIn) : cachedImage (cachedImageIn),
722+
continuousRepaint (cachedImageIn.context.continuousRepaint.load())
704723
{
705724
CVDisplayLinkCreateWithActiveCGDisplays (&displayLink);
706-
CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, im);
725+
CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this);
707726
CVDisplayLinkStart (displayLink);
727+
728+
const auto topLeftOfCurrentScreen = Desktop::getInstance()
729+
.getDisplays()
730+
.getDisplayForRect (cachedImage.component.getTopLevelComponent()->getScreenBounds())
731+
->totalArea.getTopLeft();
732+
updateActiveDisplay (topLeftOfCurrentScreen);
733+
}
734+
735+
double getNominalVideoRefreshPeriodS() const
736+
{
737+
const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink);
738+
739+
if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
740+
return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
741+
742+
return 0.0;
743+
}
744+
745+
void updateActiveDisplay (Point<int> topLeftOfDisplay)
746+
{
747+
CGPoint point { (CGFloat) topLeftOfDisplay.getX(), (CGFloat) topLeftOfDisplay.getY() };
748+
CGDirectDisplayID displayID;
749+
uint32_t numDisplays = 0;
750+
constexpr uint32_t maxNumDisplays = 1;
751+
CGGetDisplaysWithPoint (point, maxNumDisplays, &displayID, &numDisplays);
752+
753+
if (numDisplays == 1)
754+
CVDisplayLinkSetCurrentCGDisplay (displayLink, displayID);
708755
}
709756

710757
~CVDisplayLinkWrapper()
@@ -716,11 +763,16 @@ class OpenGLContext::CachedImage : public CachedComponentImage,
716763
static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*,
717764
CVOptionFlags, CVOptionFlags*, void* displayLinkContext)
718765
{
719-
auto* self = (CachedImage*) displayLinkContext;
720-
self->repaintEvent.signal();
766+
auto* self = reinterpret_cast<CVDisplayLinkWrapper*> (displayLinkContext);
767+
768+
if (self->continuousRepaint)
769+
self->cachedImage.repaintEvent.signal();
770+
721771
return kCVReturnSuccess;
722772
}
723773

774+
CachedImage& cachedImage;
775+
const bool continuousRepaint;
724776
CVDisplayLinkRef displayLink;
725777
};
726778

0 commit comments

Comments
 (0)