From 57cfb7045f06cc8052f55c0394d215385e23195a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 2 Nov 2024 14:10:48 +0500 Subject: [PATCH] Fix window resize jitter with Metal --- native/Avalonia.Native/src/OSX/metal.mm | 35 ++++++++++++------- .../Server/ServerCompositionTarget.cs | 13 ++++++- src/Avalonia.Native/Metal.cs | 12 +++++-- src/Avalonia.Native/avn.idl | 6 ++++ 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/metal.mm b/native/Avalonia.Native/src/OSX/metal.mm index 5622f2040ec..7ef7cd10ada 100644 --- a/native/Avalonia.Native/src/OSX/metal.mm +++ b/native/Avalonia.Native/src/OSX/metal.mm @@ -57,14 +57,19 @@ HRESULT GetPixelSize(AvnPixelSize *ret) override { ~AvnMetalRenderSession() { + START_ARP_CALL; auto buffer = [_queue commandBuffer]; - [buffer presentDrawable: _drawable]; [buffer commit]; + [buffer waitUntilCompleted]; + [CATransaction begin]; + [_drawable present]; + [CATransaction commit]; } }; class AvnMetalRenderTarget : public ComSingleObject { + CALayer* _hostLayer; CAMetalLayer* _layer; double _scaling = 1; AvnPixelSize _size = {1,1}; @@ -73,30 +78,38 @@ HRESULT GetPixelSize(AvnPixelSize *ret) override { double PendingScaling = 1; AvnPixelSize PendingSize = {1,1}; FORWARD_IUNKNOWN() - AvnMetalRenderTarget(CAMetalLayer* layer, ComPtr device) + AvnMetalRenderTarget(CALayer* hostLayer, ComPtr device) { - _layer = layer; _device = device; + _hostLayer = hostLayer; + _layer = [CAMetalLayer new]; + _layer.opaque = false; + _layer.device = _device->device; + _layer.presentsWithTransaction = YES; + _layer.framebufferOnly = YES; + [_hostLayer addSublayer: _layer]; } HRESULT BeginDrawing(IAvnMetalRenderingSession **ret) override { + START_ARP_CALL; if([NSThread isMainThread]) { - // Flush all existing rendering - auto buffer = [_device->queue commandBuffer]; - [buffer commit]; - [buffer waitUntilCompleted]; _size = PendingSize; _scaling= PendingScaling; CGSize layerSize = {(CGFloat)_size.Width, (CGFloat)_size.Height}; - + [CATransaction begin]; + [CATransaction setDisableActions: YES]; [_layer setDrawableSize: layerSize]; + [_layer setFrame: [_hostLayer frame]]; + [CATransaction commit]; } + else if(PendingSize.Width != _size.Width || PendingSize.Height != _size.Height) + return AvnResultCodes::E_AVN_RENDER_TARGET_NOT_READY; auto drawable = [_layer nextDrawable]; if(drawable == nil) { ret = nil; - return E_FAIL; + return AvnResultCodes::E_AVN_RENDER_TARGET_NOT_READY; } *ret = new AvnMetalRenderSession(_device, _layer, drawable, _size, _scaling); return 0; @@ -111,9 +124,7 @@ @implementation MetalRenderTarget } - (MetalRenderTarget *)initWithDevice:(IAvnMetalDevice *)device { _device = dynamic_cast(device); - _layer = [CAMetalLayer new]; - _layer.opaque = false; - _layer.device = _device->device; + _layer = [CALayer new]; _target.setNoAddRef(new AvnMetalRenderTarget(_layer, _device)); return self; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 8afdb6a2cc1..9c2a7933740 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -148,8 +148,19 @@ public void Render() // Check if render target can be rendered to directly and preserves the previous frame || !(renderTargetWithProperties?.Properties.RetainsPreviousFrameContents == true && renderTargetWithProperties?.Properties.IsSuitableForDirectRendering == true); + + IDrawingContextImpl renderTargetContext; + RenderTargetDrawingContextProperties properties; + try + { + renderTargetContext = _renderTarget.CreateDrawingContextWithProperties(false, out properties); + } + catch (RenderTargetNotReadyException) + { + return; + } - using (var renderTargetContext = _renderTarget.CreateDrawingContextWithProperties(false, out var properties)) + using (renderTargetContext) { if(needLayer && (PixelSize != _layerSize || _layer == null || _layer.IsCorrupted)) { diff --git a/src/Avalonia.Native/Metal.cs b/src/Avalonia.Native/Metal.cs index ef7a1f7d851..3bbe2e0b6e9 100644 --- a/src/Avalonia.Native/Metal.cs +++ b/src/Avalonia.Native/Metal.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using Avalonia.Metal; using Avalonia.Native.Interop; using Avalonia.Platform; @@ -84,8 +85,15 @@ public void Dispose() public IMetalPlatformSurfaceRenderingSession BeginRendering() { - var session = _native.BeginDrawing(); - return new MetalDrawingSession(session); + try + { + var session = _native.BeginDrawing(); + return new MetalDrawingSession(session); + } + catch (COMException com) when (com.HResult == (int)AvnResultCodes.E_AVN_RENDER_TARGET_NOT_READY) + { + throw new RenderTargetNotReadyException(); + } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index dfbd8c2516a..c33681775cc 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -1261,3 +1261,9 @@ interface IAvnPlatformRenderTimer : IUnknown void Stop(); bool RunsInBackground(); } + +enum AvnResultCodes +{ + // Codes are 0x80040200-based because vbObjectError is 0x80040000 but codes <0x200 still somehow got reserved + E_AVN_RENDER_TARGET_NOT_READY = unchecked((int)0x80040201) +}