diff --git a/modules/juce_opengl/native/juce_OpenGL_osx.h b/modules/juce_opengl/native/juce_OpenGL_osx.h index c139b7ea93..773ec27966 100644 --- a/modules/juce_opengl/native/juce_OpenGL_osx.h +++ b/modules/juce_opengl/native/juce_OpenGL_osx.h @@ -197,20 +197,23 @@ public: void updateWindowPosition (Rectangle) {} - bool setSwapInterval (int numFramesPerSwap) + bool setSwapInterval (int numFramesPerSwapIn) { + numFramesPerSwap = numFramesPerSwapIn; + // The macOS OpenGL programming guide says that numFramesPerSwap // can only be 0 or 1. jassert (isPositiveAndBelow (numFramesPerSwap, 2)); - minSwapTimeMs = (numFramesPerSwap * 1000) / 60; - [renderContext setValues: (const GLint*) &numFramesPerSwap #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12 forParameter: NSOpenGLContextParameterSwapInterval]; #else forParameter: NSOpenGLCPSwapInterval]; #endif + + updateMinSwapTime(); + return true; } @@ -227,11 +230,24 @@ public: return numFrames; } + void setNominalVideoRefreshPeriodS (double periodS) + { + jassert (periodS > 0.0); + videoRefreshPeriodS = periodS; + updateMinSwapTime(); + } + + void updateMinSwapTime() + { + minSwapTimeMs = static_cast (numFramesPerSwap * 1000 * videoRefreshPeriodS); + } + NSOpenGLContext* renderContext = nil; NSOpenGLView* view = nil; ReferenceCountedObjectPtr viewAttachment; double lastSwapTime = 0; - int minSwapTimeMs = 0, underrunCounter = 0; + int minSwapTimeMs = 0, underrunCounter = 0, numFramesPerSwap = 0; + double videoRefreshPeriodS = 1.0 / 60.0; //============================================================================== struct MouseForwardingNSOpenGLViewClass : public ObjCClass diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index 4af8df087b..7847392e77 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -304,7 +304,21 @@ public: if (auto* peer = component.getPeer()) { auto localBounds = component.getLocalBounds(); - auto displayScale = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds())->scale; + const auto currentDisplay = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds()); + + if (currentDisplay != lastDisplay) + { + #if JUCE_MAC + if (cvDisplayLinkWrapper != nullptr) + { + cvDisplayLinkWrapper->updateActiveDisplay (currentDisplay->totalArea.getTopLeft()); + nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS()); + } + #endif + lastDisplay = currentDisplay; + } + + const auto displayScale = currentDisplay->scale; auto newArea = peer->getComponent().getLocalArea (&component, localBounds).withZeroOrigin() * displayScale; @@ -345,7 +359,10 @@ public: auto screenBounds = component.getTopLevelComponent()->getScreenBounds(); if (lastScreenBounds != screenBounds) + { updateViewportSize (true); + lastScreenBounds = screenBounds; + } } void paintComponent() @@ -494,7 +511,7 @@ public: break; #if JUCE_MAC - if (cvDisplayLinkWrapper != nullptr) + if (context.continuousRepaint) { repaintEvent.wait (-1); renderFrame(); @@ -561,8 +578,8 @@ public: context.renderer->newOpenGLContextCreated(); #if JUCE_MAC - if (context.continuousRepaint) - cvDisplayLinkWrapper = std::make_unique (this); + cvDisplayLinkWrapper = std::make_unique (*this); + nativeContext->setNominalVideoRefreshPeriodS (cvDisplayLinkWrapper->getNominalVideoRefreshPeriodS()); #endif return true; @@ -680,6 +697,7 @@ public: OpenGLFrameBuffer cachedImageFrameBuffer; RectangleList validArea; Rectangle viewportArea, lastScreenBounds; + const Displays::Display* lastDisplay = nullptr; double scale = 1.0; AffineTransform transform; GLuint vertexArrayObject = 0; @@ -700,11 +718,40 @@ public: #if JUCE_MAC struct CVDisplayLinkWrapper { - CVDisplayLinkWrapper (CachedImage* im) + CVDisplayLinkWrapper (CachedImage& cachedImageIn) : cachedImage (cachedImageIn), + continuousRepaint (cachedImageIn.context.continuousRepaint.load()) { CVDisplayLinkCreateWithActiveCGDisplays (&displayLink); - CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, im); + CVDisplayLinkSetOutputCallback (displayLink, &displayLinkCallback, this); CVDisplayLinkStart (displayLink); + + const auto topLeftOfCurrentScreen = Desktop::getInstance() + .getDisplays() + .getDisplayForRect (cachedImage.component.getTopLevelComponent()->getScreenBounds()) + ->totalArea.getTopLeft(); + updateActiveDisplay (topLeftOfCurrentScreen); + } + + double getNominalVideoRefreshPeriodS() const + { + const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (displayLink); + + if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0) + return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale; + + return 0.0; + } + + void updateActiveDisplay (Point topLeftOfDisplay) + { + CGPoint point { (CGFloat) topLeftOfDisplay.getX(), (CGFloat) topLeftOfDisplay.getY() }; + CGDirectDisplayID displayID; + uint32_t numDisplays = 0; + constexpr uint32_t maxNumDisplays = 1; + CGGetDisplaysWithPoint (point, maxNumDisplays, &displayID, &numDisplays); + + if (numDisplays == 1) + CVDisplayLinkSetCurrentCGDisplay (displayLink, displayID); } ~CVDisplayLinkWrapper() @@ -716,11 +763,16 @@ public: static CVReturn displayLinkCallback (CVDisplayLinkRef, const CVTimeStamp*, const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) { - auto* self = (CachedImage*) displayLinkContext; - self->repaintEvent.signal(); + auto* self = reinterpret_cast (displayLinkContext); + + if (self->continuousRepaint) + self->cachedImage.repaintEvent.signal(); + return kCVReturnSuccess; } + CachedImage& cachedImage; + const bool continuousRepaint; CVDisplayLinkRef displayLink; };