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

macOS: Fix CGMetalLayerRenderer assertions and resizing

This commit is contained in:
Tom Poole 2022-06-29 16:41:12 +01:00
parent 2d7c65589d
commit c0350c54ab
3 changed files with 151 additions and 127 deletions

View file

@ -331,7 +331,7 @@ private:
}
};
std::unique_ptr<CoreGraphicsMetalLayerRenderer> metalRenderer;
std::unique_ptr<CoreGraphicsMetalLayerRenderer<UIView>> metalRenderer;
RectangleList<float> deferredRepaints;
//==============================================================================
@ -534,7 +534,7 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;
+ (Class) layerClass
{
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
if (@available (iOS 12, *))
if (@available (iOS 13, *))
return [CAMetalLayer class];
#endif
@ -717,8 +717,8 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags,
view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
if (@available (iOS 12, *))
metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer> ((CAMetalLayer*) view.layer, comp);
if (@available (iOS 13, *))
metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer<UIView>> (view, comp);
#endif
if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0)
@ -1195,26 +1195,13 @@ void UIViewComponentPeer::displayLinkCallback()
auto dispatchRectangles = [this] ()
{
// We shouldn't need this preprocessor guard, but when running in the simulator
// CAMetalLayer is flagged as requiring iOS 13
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
if (metalRenderer != nullptr)
{
if (@available (iOS 12, *))
{
return metalRenderer->drawRectangleList ((CAMetalLayer*) view.layer,
(float) view.contentScaleFactor,
view.frame,
component,
[this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
deferredRepaints);
}
// The creation of metalRenderer should already be guarded with @available (iOS 12, *).
jassertfalse;
return false;
}
#endif
return metalRenderer->drawRectangleList (view,
(float) view.contentScaleFactor,
view.frame,
component,
[this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
deferredRepaints);
for (const auto& r : deferredRepaints)
[view setNeedsDisplayInRect: convertToCGRect (r)];

View file

@ -30,13 +30,37 @@ namespace juce
{
//==============================================================================
template <typename ViewType>
class CoreGraphicsMetalLayerRenderer
{
public:
//==============================================================================
CoreGraphicsMetalLayerRenderer (CAMetalLayer* layer, const Component& comp)
CoreGraphicsMetalLayerRenderer (ViewType* view, const Component& comp)
{
device.reset (MTLCreateSystemDefaultDevice());
commandQueue.reset ([device.get() newCommandQueue]);
attach (view, comp);
}
~CoreGraphicsMetalLayerRenderer()
{
if (memoryBlitCommandBuffer != nullptr)
{
stopGpuCommandSubmission = true;
[memoryBlitCommandBuffer.get() waitUntilCompleted];
}
}
void attach (ViewType* view, const Component& comp)
{
#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;
@ -44,23 +68,35 @@ public:
layer.opaque = comp.isOpaque();
layer.allowsNextDrawableTimeout = NO;
commandQueue.reset ([device.get() newCommandQueue]);
attachedView = view;
doSynchronousRender = true;
}
~CoreGraphicsMetalLayerRenderer()
void detach()
{
stopGpuCommandSubmission = true;
[memoryBlitCommandBuffer.get() waitUntilCompleted];
#if JUCE_MAC
attachedView.wantsLayer = NO;
attachedView.layer = nil;
#endif
attachedView = nullptr;
}
bool isAttachedToView (ViewType* view) const
{
return view == attachedView && attachedView != nullptr;
}
template <typename Callback>
bool drawRectangleList (CAMetalLayer* layer,
bool drawRectangleList (ViewType* view,
float scaleFactor,
CGRect viewFrame,
const Component& comp,
Callback&& drawRectWithContext,
const RectangleList<float>& dirtyRegions)
{
auto layer = (CAMetalLayer*) view.layer;
if (memoryBlitCommandBuffer != nullptr)
{
switch ([memoryBlitCommandBuffer.get() status])
@ -85,7 +121,7 @@ public:
const auto componentHeight = comp.getHeight();
if (! CGSizeEqualToSize (layer.drawableSize, transformedFrameSize))
if (resources == nullptr || ! CGSizeEqualToSize (layer.drawableSize, transformedFrameSize))
{
layer.drawableSize = transformedFrameSize;
resources = std::make_unique<Resources> (device.get(), layer, componentHeight);
@ -117,62 +153,75 @@ public:
auto sharedTexture = resources->getSharedTexture();
memoryBlitCommandBuffer.reset ([commandQueue.get() commandBuffer]);
// Command buffers are usually considered temporary, and are automatically released by
// the operating system when the rendering pipeline is finsihed. However, we want to keep
// this one alive so that we can wait for pipeline completion in the destructor.
[memoryBlitCommandBuffer.get() retain];
auto blitCommandEncoder = [memoryBlitCommandBuffer.get() blitCommandEncoder];
[blitCommandEncoder copyFromTexture: sharedTexture
sourceSlice: 0
sourceLevel: 0
sourceOrigin: MTLOrigin{}
sourceSize: MTLSize { sharedTexture.width, sharedTexture.height, 1 }
toTexture: gpuTexture
destinationSlice: 0
destinationLevel: 0
destinationOrigin: MTLOrigin{}];
[blitCommandEncoder endEncoding];
[memoryBlitCommandBuffer.get() addScheduledHandler: ^(id<MTLCommandBuffer>)
auto encodeBlit = [] (id<MTLCommandBuffer> commandBuffer,
id<MTLTexture> source,
id<MTLTexture> destination)
{
// We're on a Metal thread, so we can make a blocking nextDrawable call
// without stalling the message thread.
// Check if we can do an early exit.
if (stopGpuCommandSubmission)
return;
auto blitCommandEncoder = [commandBuffer blitCommandEncoder];
[blitCommandEncoder copyFromTexture: source
sourceSlice: 0
sourceLevel: 0
sourceOrigin: MTLOrigin{}
sourceSize: MTLSize { source.width, source.height, 1 }
toTexture: destination
destinationSlice: 0
destinationLevel: 0
destinationOrigin: MTLOrigin{}];
[blitCommandEncoder endEncoding];
};
if (doSynchronousRender)
{
@autoreleasepool
{
id<MTLCommandBuffer> commandBuffer = [commandQueue.get() commandBuffer];
id<CAMetalDrawable> drawable = [layer nextDrawable];
encodeBlit (commandBuffer, sharedTexture, drawable.texture);
id<MTLCommandBuffer> presentationCommandBuffer = [commandQueue.get() commandBuffer];
auto presentationBlitCommandEncoder = [presentationCommandBuffer blitCommandEncoder];
[presentationBlitCommandEncoder copyFromTexture: gpuTexture
sourceSlice: 0
sourceLevel: 0
sourceOrigin: MTLOrigin{}
sourceSize: MTLSize { gpuTexture.width, gpuTexture.height, 1 }
toTexture: drawable.texture
destinationSlice: 0
destinationLevel: 0
destinationOrigin: MTLOrigin{}];
[presentationBlitCommandEncoder endEncoding];
[presentationCommandBuffer addScheduledHandler: ^(id<MTLCommandBuffer>)
{
[drawable present];
}];
[presentationCommandBuffer commit];
[commandBuffer presentDrawable: drawable];
[commandBuffer commit];
}
}];
[memoryBlitCommandBuffer.get() commit];
doSynchronousRender = false;
}
else
{
// Command buffers are usually considered temporary, and are automatically released by
// the operating system when the rendering pipeline is finsihed. However, we want to keep
// this one alive so that we can wait for pipeline completion in the destructor.
memoryBlitCommandBuffer.reset ([[commandQueue.get() commandBuffer] retain]);
encodeBlit (memoryBlitCommandBuffer.get(), sharedTexture, gpuTexture);
[memoryBlitCommandBuffer.get() addScheduledHandler: ^(id<MTLCommandBuffer>)
{
// We're on a Metal thread, so we can make a blocking nextDrawable call
// without stalling the message thread.
// Check if we can do an early exit.
if (stopGpuCommandSubmission)
return;
@autoreleasepool
{
id<CAMetalDrawable> drawable = [layer nextDrawable];
id<MTLCommandBuffer> presentationCommandBuffer = [commandQueue.get() commandBuffer];
encodeBlit (presentationCommandBuffer, gpuTexture, drawable.texture);
[presentationCommandBuffer addScheduledHandler: ^(id<MTLCommandBuffer>)
{
[drawable present];
}];
[presentationCommandBuffer commit];
}
}];
[memoryBlitCommandBuffer.get() commit];
}
return true;
}
@ -184,18 +233,6 @@ private:
return ((n + alignment - 1) / alignment) * alignment;
}
//==============================================================================
struct TextureDeleter
{
void operator() (id<MTLTexture> texture) const noexcept
{
[texture setPurgeableState: MTLPurgeableStateEmpty];
[texture release];
}
};
using TextureUniquePtr = std::unique_ptr<std::remove_pointer_t<id<MTLTexture>>, TextureDeleter>;
//==============================================================================
class GpuTexturePool
{
@ -209,12 +246,12 @@ private:
id<MTLTexture> take() const
{
auto iter = std::find_if (textureCache.begin(), textureCache.end(),
[] (const TextureUniquePtr& t) { return [t.get() retainCount] == 1; });
[] (const ObjCObjectHandle<id<MTLTexture>>& t) { return [t.get() retainCount] == 1; });
return iter == textureCache.end() ? nullptr : (*iter).get();
}
private:
std::array<TextureUniquePtr, 3> textureCache;
std::array<ObjCObjectHandle<id<MTLTexture>>, 3> textureCache;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GpuTexturePool)
JUCE_DECLARE_NON_MOVEABLE (GpuTexturePool)
@ -339,7 +376,7 @@ private:
detail::ContextPtr cgContext;
ObjCObjectHandle<id<MTLBuffer>> buffer;
TextureUniquePtr sharedTexture;
ObjCObjectHandle<id<MTLTexture>> sharedTexture;
std::unique_ptr<GpuTexturePool> gpuTexturePool;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Resources)
@ -347,6 +384,9 @@ private:
};
//==============================================================================
ViewType* attachedView = nullptr;
bool doSynchronousRender = false;
std::unique_ptr<Resources> resources;
ObjCObjectHandle<id<MTLDevice>> device;

View file

@ -153,7 +153,11 @@ public:
[view setPostsFrameChangedNotifications: YES];
#if USE_COREGRAPHICS_RENDERING
#if USE_COREGRAPHICS_RENDERING
#if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
if (@available (macOS 10.14, *))
metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer<NSView>> (view, getComponent());
#endif
if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0)
{
if (@available (macOS 10.8, *))
@ -162,7 +166,7 @@ public:
[[view layer] setDrawsAsynchronously: YES];
}
}
#endif
#endif
createCVDisplayLink();
@ -362,7 +366,10 @@ public:
}
if (oldViewSize.width != r.size.width || oldViewSize.height != r.size.height)
{
numFramesToSkipMetalRenderer = 5;
[view setNeedsDisplay: true];
}
}
Rectangle<int> getBounds (const bool global) const
@ -1076,52 +1083,41 @@ public:
if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint())
return;
#if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS
// We require macOS 10.14 to use the Metal layer renderer
if (@available (macOS 10.14, *))
if (metalRenderer != nullptr)
{
const auto& comp = getComponent();
const auto compBounds = getComponent().getLocalBounds().toFloat();
// If we are resizing we need to fall back to synchronous drawing to avoid artefacts
if (areAnyWindowsInLiveResize())
if ([window inLiveResize] || numFramesToSkipMetalRenderer > 0)
{
if (metalRenderer != nullptr)
if (metalRenderer->isAttachedToView (view))
{
metalRenderer.reset();
view.wantsLayer = NO;
view.layer = nil;
deferredRepaints = comp.getLocalBounds().toFloat();
metalRenderer->detach();
deferredRepaints = compBounds;
}
if (numFramesToSkipMetalRenderer > 0)
--numFramesToSkipMetalRenderer;
}
else
{
if (metalRenderer == nullptr)
if (! metalRenderer->isAttachedToView (view))
{
view.wantsLayer = YES;
view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft;
view.layer = [CAMetalLayer layer];
metalRenderer = std::make_unique<CoreGraphicsMetalLayerRenderer> ((CAMetalLayer*) view.layer, getComponent());
deferredRepaints = comp.getLocalBounds().toFloat();
metalRenderer->attach (view, getComponent());
deferredRepaints = compBounds;
}
}
}
#endif
auto dispatchRectangles = [this] ()
auto dispatchRectangles = [this]
{
if (@available (macOS 10.14, *))
{
if (metalRenderer != nullptr)
{
return metalRenderer->drawRectangleList ((CAMetalLayer*) view.layer,
(float) [[view window] backingScaleFactor],
view.frame,
getComponent(),
[this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
deferredRepaints);
}
}
if (metalRenderer != nullptr && metalRenderer->isAttachedToView (view))
return metalRenderer->drawRectangleList (view,
(float) [[view window] backingScaleFactor],
view.frame,
getComponent(),
[this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); },
deferredRepaints);
for (auto& i : deferredRepaints)
[view setNeedsDisplayInRect: makeNSRect (i)];
@ -1893,7 +1889,8 @@ private:
CVDisplayLinkRef displayLink = nullptr;
dispatch_source_t displaySource = nullptr;
std::unique_ptr<CoreGraphicsMetalLayerRenderer> metalRenderer;
int numFramesToSkipMetalRenderer = 0;
std::unique_ptr<CoreGraphicsMetalLayerRenderer<NSView>> metalRenderer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer)
};