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:
parent
2d7c65589d
commit
c0350c54ab
3 changed files with 151 additions and 127 deletions
|
|
@ -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)];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue