1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

CGMetalRenderer: Avoid glitching when resizing views

This commit is contained in:
reuk 2023-04-03 17:40:35 +01:00
parent fe09902e83
commit 9d50ab6c59
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
3 changed files with 215 additions and 122 deletions

View file

@ -30,15 +30,14 @@ namespace juce
{
//==============================================================================
template <typename ViewType>
class CoreGraphicsMetalLayerRenderer
{
public:
//==============================================================================
static auto create (ViewType* view, bool isOpaque)
static auto create()
{
ObjCObjectHandle<id<MTLDevice>> 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 <typename Callback>
bool drawRectangleList (ViewType* view,
float scaleFactor,
Callback&& drawRectWithContext,
const RectangleList<float>& dirtyRegions)
[[nodiscard]] RectangleList<float> drawRectangleList (CAMetalLayer* layer,
float scaleFactor,
Callback&& drawRectWithContext,
RectangleList<float> 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<Resources> (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<CAMetalDrawable> 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<id<MTLDevice>> mtlDevice,
ViewType* view,
bool isOpaque)
explicit CoreGraphicsMetalLayerRenderer (ObjCObjectHandle<id<MTLDevice>> 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> resources;
ObjCObjectHandle<id<MTLDevice>> device;

View file

@ -122,7 +122,54 @@ static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept
constexpr int extendedKeyModifier = 0x30000;
//==============================================================================
class NSViewComponentPeer : public ComponentPeer
class JuceCALayerDelegate : public ObjCClass<NSObject<CALayerDelegate>>
{
public:
struct Callback
{
virtual ~Callback() = default;
virtual void displayLayer (CALayer*) = 0;
};
static NSObject<CALayerDelegate>* construct (Callback* owner)
{
static JuceCALayerDelegate cls;
auto* result = cls.createInstance();
setOwner (result, owner);
return result;
}
private:
JuceCALayerDelegate()
: ObjCClass ("JuceCALayerDelegate_")
{
addIvar<Callback*> ("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<Callback*> (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<NSView>::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<float> lastSizeBeforeZoom;
Rectangle<float> boundsWhenRepaintsDeferred;
RectangleList<float> 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<CoreGraphicsMetalLayerRenderer> metalRenderer;
NSUniquePtr<NSObject<CALayerDelegate>> layerDelegate;
#endif
private:
JUCE_DECLARE_WEAK_REFERENCEABLE (NSViewComponentPeer)
@ -1935,9 +1973,29 @@ private:
}
}
//==============================================================================
std::unique_ptr<CoreGraphicsMetalLayerRenderer<NSView>> 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<CAMetalLayer*> (layer),
scale,
[this] (auto&&... args) { drawRectWithContext (args...); },
std::move (deferredRepaints),
[view inLiveResize]);
#endif
}
//==============================================================================
std::vector<ScopedNotificationCenterObserver> scopedObservers;
std::vector<ScopedNotificationCenterObserver> windowObservers;
@ -1969,6 +2027,7 @@ struct NSViewComponentPeerWrapper : public Base
}
};
//==============================================================================
struct JuceNSViewClass : public NSViewComponentPeerWrapper<ObjCClass<NSView>>
{
JuceNSViewClass() : NSViewComponentPeerWrapper ("JUCEView_")
@ -2042,6 +2101,35 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper<ObjCClass<NSView>>
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<CALayer*> (self, @selector (makeBackingLayer));
});
#endif
addMethod (@selector (windowWillMiniaturize:), [] (id self, SEL, NSNotification*)
{
if (auto* p = getOwner (self))

View file

@ -295,7 +295,7 @@ struct CADisplayLinkDeleter
@end
@interface JuceUIView : UIView
@interface JuceUIView : UIView<CALayerDelegate>
{
@public
UIViewComponentPeer* owner;
@ -520,6 +520,12 @@ public:
return UIKeyboardTypeDefault;
}
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
std::unique_ptr<CoreGraphicsMetalLayerRenderer> metalRenderer;
#endif
RectangleList<float> deferredRepaints;
private:
void appStyleChanged() override
{
@ -545,9 +551,6 @@ private:
}
};
std::unique_ptr<CoreGraphicsMetalLayerRenderer<UIView>> metalRenderer;
RectangleList<float> deferredRepaints;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)
};
@ -698,6 +701,26 @@ MultiTouchMapper<UITouch*> 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<UITouch*> 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<CAMetalLayer*> (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<UIView>::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)];
}
//==============================================================================