diff --git a/modules/juce_gui_basics/native/juce_CGMetalLayerRenderer_mac.h b/modules/juce_gui_basics/native/juce_CGMetalLayerRenderer_mac.h index 392d3475b6..c0187d3e67 100644 --- a/modules/juce_gui_basics/native/juce_CGMetalLayerRenderer_mac.h +++ b/modules/juce_gui_basics/native/juce_CGMetalLayerRenderer_mac.h @@ -30,15 +30,14 @@ namespace juce { //============================================================================== -template class CoreGraphicsMetalLayerRenderer { public: //============================================================================== - static auto create (ViewType* view, bool isOpaque) + static auto create() { ObjCObjectHandle> device { MTLCreateSystemDefaultDevice() }; - return rawToUniquePtr (device != nullptr ? new CoreGraphicsMetalLayerRenderer (device, view, isOpaque) + return rawToUniquePtr (device != nullptr ? new CoreGraphicsMetalLayerRenderer (device) : nullptr); } @@ -51,48 +50,15 @@ public: } } - void attach (ViewType* view, bool isOpaque) - { - #if JUCE_MAC - view.wantsLayer = YES; - view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; - view.layer = [CAMetalLayer layer]; - #endif - - auto layer = (CAMetalLayer*) view.layer; - - layer.device = device.get(); - layer.framebufferOnly = NO; - layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; - layer.opaque = isOpaque; - layer.allowsNextDrawableTimeout = NO; - - attachedView = view; - doSynchronousRender = true; - } - - void detach() - { - #if JUCE_MAC - attachedView.wantsLayer = NO; - attachedView.layer = nil; - #endif - - attachedView = nullptr; - } - - bool isAttachedToView (ViewType* view) const - { - return view == attachedView && attachedView != nullptr; - } - + /* Returns any regions that weren't redrawn, and which should be retried next frame. */ template - bool drawRectangleList (ViewType* view, - float scaleFactor, - Callback&& drawRectWithContext, - const RectangleList& dirtyRegions) + [[nodiscard]] RectangleList drawRectangleList (CAMetalLayer* layer, + float scaleFactor, + Callback&& drawRectWithContext, + RectangleList dirtyRegions, + const bool renderSync) { - auto layer = (CAMetalLayer*) view.layer; + layer.presentsWithTransaction = renderSync; if (memoryBlitCommandBuffer != nullptr) { @@ -104,7 +70,7 @@ public: case MTLCommandBufferStatusScheduled: // If we haven't finished blitting the CPU texture to the GPU then // report that we have been unable to draw anything. - return false; + return dirtyRegions; case MTLCommandBufferStatusCompleted: case MTLCommandBufferStatusError: break; @@ -112,14 +78,15 @@ public: } layer.contentsScale = scaleFactor; - const auto drawableSizeTansform = CGAffineTransformMakeScale (layer.contentsScale, - layer.contentsScale); - const auto transformedFrameSize = CGSizeApplyAffineTransform (view.frame.size, drawableSizeTansform); + + const auto drawableSizeTransform = CGAffineTransformMakeScale (layer.contentsScale, layer.contentsScale); + const auto transformedFrameSize = CGSizeApplyAffineTransform (layer.bounds.size, drawableSizeTransform); if (resources == nullptr || ! CGSizeEqualToSize (layer.drawableSize, transformedFrameSize)) { layer.drawableSize = transformedFrameSize; resources = std::make_unique (device.get(), layer); + dirtyRegions = convertToRectFloat (layer.bounds); } auto gpuTexture = resources->getGpuTexture(); @@ -127,7 +94,7 @@ public: if (gpuTexture == nullptr) { jassertfalse; - return false; + return dirtyRegions; } auto cgContext = resources->getCGContext(); @@ -165,7 +132,7 @@ public: [blitCommandEncoder endEncoding]; }; - if (doSynchronousRender) + if (renderSync) { @autoreleasepool { @@ -174,11 +141,10 @@ public: id drawable = [layer nextDrawable]; encodeBlit (commandBuffer, sharedTexture, drawable.texture); - [commandBuffer presentDrawable: drawable]; [commandBuffer commit]; + [commandBuffer waitUntilScheduled]; + [drawable present]; } - - doSynchronousRender = false; } else { @@ -218,18 +184,16 @@ public: [memoryBlitCommandBuffer.get() commit]; } - return true; + dirtyRegions.clear(); + return dirtyRegions; } private: //============================================================================== - CoreGraphicsMetalLayerRenderer (ObjCObjectHandle> mtlDevice, - ViewType* view, - bool isOpaque) + explicit CoreGraphicsMetalLayerRenderer (ObjCObjectHandle> mtlDevice) : device (mtlDevice), commandQueue ([device.get() newCommandQueue]) { - attach (view, isOpaque); } //============================================================================== @@ -389,9 +353,6 @@ private: }; //============================================================================== - ViewType* attachedView = nullptr; - bool doSynchronousRender = false; - std::unique_ptr resources; ObjCObjectHandle> device; diff --git a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm index d90272f6c9..b68018ca05 100644 --- a/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm +++ b/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm @@ -122,7 +122,54 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept constexpr int extendedKeyModifier = 0x30000; //============================================================================== -class NSViewComponentPeer : public ComponentPeer +class JuceCALayerDelegate : public ObjCClass> +{ +public: + struct Callback + { + virtual ~Callback() = default; + virtual void displayLayer (CALayer*) = 0; + }; + + static NSObject* construct (Callback* owner) + { + static JuceCALayerDelegate cls; + auto* result = cls.createInstance(); + setOwner (result, owner); + return result; + } + +private: + JuceCALayerDelegate() + : ObjCClass ("JuceCALayerDelegate_") + { + addIvar ("owner"); + + addMethod (@selector (displayLayer:), [] (id self, SEL, CALayer* layer) + { + if (auto* owner = getOwner (self)) + owner->displayLayer (layer); + }); + + addProtocol (@protocol (CALayerDelegate)); + + registerClass(); + } + + static Callback* getOwner (id self) + { + return getIvar (self, "owner"); + } + + static void setOwner (id self, Callback* newOwner) + { + object_setInstanceVariable (self, "owner", newOwner); + } +}; + +//============================================================================== +class NSViewComponentPeer : public ComponentPeer, + private JuceCALayerDelegate::Callback { public: NSViewComponentPeer (Component& comp, const int windowStyleFlags, NSView* viewToAttachTo) @@ -148,18 +195,37 @@ public: [view setPostsFrameChangedNotifications: YES]; #if USE_COREGRAPHICS_RENDERING + // Creating a metal renderer may fail on some systems. + // We need to try creating the renderer before first creating a backing layer + // so that we know whether to use a metal layer or the system default layer + // (setWantsLayer: YES will call through to makeBackingLayer, where we check + // whether metalRenderer is non-null). + // The system overwrites the layer delegate set during makeBackingLayer, + // so that must be set separately, after the layer has been created and + // configured. #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS if (@available (macOS 10.14, *)) - metalRenderer = CoreGraphicsMetalLayerRenderer::create (view, getComponent().isOpaque()); + { + metalRenderer = CoreGraphicsMetalLayerRenderer::create(); + layerDelegate.reset (JuceCALayerDelegate::construct (this)); + } #endif + if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0) { if (@available (macOS 10.8, *)) { [view setWantsLayer: YES]; - [[view layer] setDrawsAsynchronously: YES]; + [view setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize]; + [view layer].drawsAsynchronously = YES; } } + + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + if (@available (macOS 10.14, *)) + if (metalRenderer != nullptr) + view.layer.delegate = layerDelegate.get(); + #endif #endif if (isSharedWindow) @@ -1009,8 +1075,6 @@ public: // As a workaround for this, we use a RectangleList to do our own coalescing of regions before // asynchronously asking the OS to repaint them. deferredRepaints.add (area.toFloat()); - const auto frameSize = view.frame.size; - boundsWhenRepaintsDeferred = { (float) frameSize.width, (float) frameSize.height }; } static bool shouldThrottleRepaint() @@ -1044,40 +1108,10 @@ public: const auto frameSize = view.frame.size; const Rectangle currentBounds { (float) frameSize.width, (float) frameSize.height }; - if (boundsWhenRepaintsDeferred != currentBounds) - { - deferredRepaints = currentBounds; + for (auto& i : deferredRepaints) + [view setNeedsDisplayInRect: makeNSRect (i)]; - if (metalRenderer != nullptr && metalRenderer->isAttachedToView (view)) - metalRenderer->detach(); - } - else - { - if (metalRenderer != nullptr && ! metalRenderer->isAttachedToView (view)) - metalRenderer->attach (view, getComponent().isOpaque()); - } - - auto dispatchRectangles = [this] - { - if (metalRenderer != nullptr && metalRenderer->isAttachedToView (view)) - { - return metalRenderer->drawRectangleList (view, - (float) [[view window] backingScaleFactor], - [this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); }, - deferredRepaints); - } - - for (auto& i : deferredRepaints) - [view setNeedsDisplayInRect: makeNSRect (i)]; - - return true; - }; - - if (dispatchRectangles()) - { - lastRepaintTime = Time::getMillisecondCounter(); - deferredRepaints.clear(); - } + lastRepaintTime = Time::getMillisecondCounter(); } void performAnyPendingRepaintsNow() override @@ -1690,7 +1724,6 @@ public: int startOfMarkedTextInTextInputTarget = 0; Rectangle lastSizeBeforeZoom; - Rectangle boundsWhenRepaintsDeferred; RectangleList deferredRepaints; uint32 lastRepaintTime; @@ -1709,6 +1742,11 @@ public: static inline const auto resignKeySelector = @selector (resignKey:); JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + std::unique_ptr metalRenderer; + NSUniquePtr> layerDelegate; + #endif + private: JUCE_DECLARE_WEAK_REFERENCEABLE (NSViewComponentPeer) @@ -1935,9 +1973,29 @@ private: } } - //============================================================================== - std::unique_ptr> metalRenderer; + void displayLayer ([[maybe_unused]] CALayer* layer) override + { + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + if (metalRenderer == nullptr) + return; + const auto scale = [this] + { + if (auto* viewWindow = [view window]) + return (float) viewWindow.backingScaleFactor; + + return 1.0f; + }(); + + deferredRepaints = metalRenderer->drawRectangleList (static_cast (layer), + scale, + [this] (auto&&... args) { drawRectWithContext (args...); }, + std::move (deferredRepaints), + [view inLiveResize]); + #endif + } + + //============================================================================== std::vector scopedObservers; std::vector windowObservers; @@ -1969,6 +2027,7 @@ struct NSViewComponentPeerWrapper : public Base } }; +//============================================================================== struct JuceNSViewClass : public NSViewComponentPeerWrapper> { JuceNSViewClass() : NSViewComponentPeerWrapper ("JUCEView_") @@ -2042,6 +2101,35 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper> addMethod (@selector (acceptsFirstMouse:), [] (id, SEL, NSEvent*) { return YES; }); + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + addMethod (@selector (makeBackingLayer), [] (id self, SEL) -> CALayer* + { + if (auto* owner = getOwner (self)) + { + if (owner->metalRenderer != nullptr) + { + auto* layer = [CAMetalLayer layer]; + + layer.device = MTLCreateSystemDefaultDevice(); + layer.framebufferOnly = NO; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; + layer.opaque = getOwner (self)->getComponent().isOpaque(); + layer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable; + layer.needsDisplayOnBoundsChange = YES; + layer.drawsAsynchronously = YES; + layer.delegate = owner->layerDelegate.get(); + + if (@available (macOS 10.13, *)) + layer.allowsNextDrawableTimeout = NO; + + return layer; + } + } + + return sendSuperclassMessage (self, @selector (makeBackingLayer)); + }); + #endif + addMethod (@selector (windowWillMiniaturize:), [] (id self, SEL, NSNotification*) { if (auto* p = getOwner (self)) diff --git a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm index 2d1168b618..c5db8159d4 100644 --- a/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm +++ b/modules/juce_gui_basics/native/juce_UIViewComponentPeer_ios.mm @@ -295,7 +295,7 @@ struct CADisplayLinkDeleter @end -@interface JuceUIView : UIView +@interface JuceUIView : UIView { @public UIViewComponentPeer* owner; @@ -520,6 +520,12 @@ public: return UIKeyboardTypeDefault; } + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + std::unique_ptr metalRenderer; + #endif + + RectangleList deferredRepaints; + private: void appStyleChanged() override { @@ -545,9 +551,6 @@ private: } }; - std::unique_ptr> metalRenderer; - RectangleList deferredRepaints; - //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) }; @@ -698,6 +701,26 @@ MultiTouchMapper UIViewComponentPeer::currentTouches; [super initWithFrame: frame]; owner = peer; + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + if (@available (iOS 13.0, *)) + { + auto* layer = (CAMetalLayer*) [self layer]; + layer.device = MTLCreateSystemDefaultDevice(); + layer.framebufferOnly = NO; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; + + if (owner != nullptr) + layer.opaque = owner->getComponent().isOpaque(); + + layer.presentsWithTransaction = YES; + layer.needsDisplayOnBoundsChange = true; + layer.presentsWithTransaction = true; + layer.delegate = self; + + layer.allowsNextDrawableTimeout = NO; + } + #endif + displayLink.reset ([CADisplayLink displayLinkWithTarget: self selector: @selector (displayLinkCallback:)]); [displayLink.get() addToRunLoop: [NSRunLoop mainRunLoop] @@ -750,6 +773,41 @@ MultiTouchMapper UIViewComponentPeer::currentTouches; owner->displayLinkCallback(); } +#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS +- (CALayer*) makeBackingLayer +{ + auto* layer = [CAMetalLayer layer]; + + layer.device = MTLCreateSystemDefaultDevice(); + layer.framebufferOnly = NO; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; + + if (owner != nullptr) + layer.opaque = owner->getComponent().isOpaque(); + + layer.presentsWithTransaction = YES; + layer.needsDisplayOnBoundsChange = true; + layer.presentsWithTransaction = true; + layer.delegate = self; + + layer.allowsNextDrawableTimeout = NO; + + return layer; +} + +- (void) displayLayer: (CALayer*) layer +{ + if (owner != nullptr) + { + owner->deferredRepaints = owner->metalRenderer->drawRectangleList (static_cast (layer), + (float) [self contentScaleFactor], + [self] (auto&&... args) { owner->drawRectWithContext (args...); }, + std::move (owner->deferredRepaints), + false); + } +} +#endif + //============================================================================== - (void) drawRect: (CGRect) r { @@ -1680,7 +1738,7 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS if (@available (iOS 13, *)) { - metalRenderer = CoreGraphicsMetalLayerRenderer::create (view, comp.isOpaque()); + metalRenderer = CoreGraphicsMetalLayerRenderer::create(); jassert (metalRenderer != nullptr); } #endif @@ -2138,22 +2196,8 @@ void UIViewComponentPeer::displayLinkCallback() if (deferredRepaints.isEmpty()) return; - auto dispatchRectangles = [this] () - { - if (metalRenderer != nullptr) - return metalRenderer->drawRectangleList (view, - (float) view.contentScaleFactor, - [this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); }, - deferredRepaints); - - for (const auto& r : deferredRepaints) - [view setNeedsDisplayInRect: convertToCGRect (r)]; - - return true; - }; - - if (dispatchRectangles()) - deferredRepaints.clear(); + for (const auto& r : deferredRepaints) + [view setNeedsDisplayInRect: convertToCGRect (r)]; } //==============================================================================