Skip to content

Commit

Permalink
Fix window resize jitter with Metal
Browse files Browse the repository at this point in the history
  • Loading branch information
kekekeks committed Nov 2, 2024
1 parent 680a5e2 commit 57cfb70
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 15 deletions.
35 changes: 23 additions & 12 deletions native/Avalonia.Native/src/OSX/metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<IAvnMetalRenderTarget, &IID_IAvnMetalRenderTarget>
{
CALayer* _hostLayer;
CAMetalLayer* _layer;
double _scaling = 1;
AvnPixelSize _size = {1,1};
Expand All @@ -73,30 +78,38 @@ HRESULT GetPixelSize(AvnPixelSize *ret) override {
double PendingScaling = 1;
AvnPixelSize PendingSize = {1,1};
FORWARD_IUNKNOWN()
AvnMetalRenderTarget(CAMetalLayer* layer, ComPtr<AvnMetalDevice> device)
AvnMetalRenderTarget(CALayer* hostLayer, ComPtr<AvnMetalDevice> 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;
Expand All @@ -111,9 +124,7 @@ @implementation MetalRenderTarget
}
- (MetalRenderTarget *)initWithDevice:(IAvnMetalDevice *)device {
_device = dynamic_cast<AvnMetalDevice*>(device);
_layer = [CAMetalLayer new];
_layer.opaque = false;
_layer.device = _device->device;
_layer = [CALayer new];
_target.setNoAddRef(new AvnMetalRenderTarget(_layer, _device));
return self;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
12 changes: 10 additions & 2 deletions src/Avalonia.Native/Metal.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Metal;
using Avalonia.Native.Interop;
using Avalonia.Platform;
Expand Down Expand Up @@ -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();
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/Avalonia.Native/avn.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 57cfb70

Please sign in to comment.