Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix window resize jitter with Metal #17415

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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];
kekekeks marked this conversation as resolved.
Show resolved Hide resolved
_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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor. But would it be possible to return HResult from BeginDrawing, instead of throwing COMException?
Just to avoid unfilterable exceptions noise.
Throwing RenderTargetNotReadyException exception is fine, as it can be easily filtered-out by the debugger by type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't have non-HRESULT returns for methods with out object arguments. Not supported by MicroCOM and would require some extensive refactoring of the way argument marshaling works.

{
throw new RenderTargetNotReadyException();
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/Avalonia.Native/avn.idl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma once
#include "com.h"
#include "stddef.h"
#define unchecked(x) x
@@

enum AvnKey
Expand Down Expand Up @@ -1261,3 +1262,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)
}
Loading