Skip to content

Commit

Permalink
GPU interop features now don't require to skip the first frame and av…
Browse files Browse the repository at this point in the history
…ailable earlier in general (#14651)

# Conflicts:
#	src/iOS/Avalonia.iOS/Platform.cs
  • Loading branch information
kekekeks authored and maxkatz6 committed Feb 24, 2024
1 parent 760c674 commit b229779
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/Android/Avalonia.Android/AndroidPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public static void Initialize()
}

Compositor = new Compositor(graphics);
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
}

private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts)
Expand Down
5 changes: 5 additions & 0 deletions src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,10 @@ public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDi
/// Indicates that the context is no longer usable. This method should be thread-safe
/// </summary>
bool IsLost { get; }

/// <summary>
/// Exposes features that should be available for consumption while context isn't active (e. g. from the UI thread)
/// </summary>
IReadOnlyDictionary<Type, object> PublicFeatures { get; }
}
}
64 changes: 44 additions & 20 deletions src/Avalonia.Base/Rendering/Composition/Compositor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
Expand Down Expand Up @@ -256,33 +257,56 @@ internal Task<T> InvokeServerJobAsync<T>(Func<T> job)
return tcs.Task;
}

internal ValueTask<IReadOnlyDictionary<Type, object>> GetRenderInterfacePublicFeatures()
{
if (Server.AT_TryGetCachedRenderInterfaceFeatures() is { } rv)
return new(rv);
if (!Loop.RunsInBackground)
return new(Server.RT_GetRenderInterfaceFeatures());
return new(InvokeServerJobAsync(Server.RT_GetRenderInterfaceFeatures));
}

/// <summary>
/// Attempts to query for a feature from the platform render interface
/// </summary>
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) =>
new(InvokeServerJobAsync(() =>
{
using (Server.RenderInterface.EnsureCurrent())
{
return Server.RenderInterface.Value.TryGetFeature(featureType);
}
}));
public async ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
{
(await GetRenderInterfacePublicFeatures().ConfigureAwait(false)).TryGetValue(featureType, out var rv);
return rv;
}

/// <summary>
/// Attempts to query for GPU interop feature from the platform render interface
/// </summary>
/// <returns></returns>
public async ValueTask<ICompositionGpuInterop?> TryGetCompositionGpuInterop()
{
var externalObjects =
(IExternalObjectsRenderInterfaceContextFeature?)await TryGetRenderInterfaceFeature(
typeof(IExternalObjectsRenderInterfaceContextFeature)).ConfigureAwait(false);

public ValueTask<ICompositionGpuInterop?> TryGetCompositionGpuInterop() =>
new(InvokeServerJobAsync<ICompositionGpuInterop?>(() =>
{
using (Server.RenderInterface.EnsureCurrent())
{
var feature = Server.RenderInterface.Value
.TryGetFeature<IExternalObjectsRenderInterfaceContextFeature>();
if (feature == null)
return null;
return new CompositionInterop(this, feature);
}
}));
if (externalObjects == null)
return null;
return new CompositionInterop(this, externalObjects);
}

internal bool UnitTestIsRegisteredForSerialization(ICompositorSerializable serializable) =>
_objectSerializationHashSet.Contains(serializable);

/// <summary>
/// Attempts to get the Compositor instance that will be used by default for new <see cref="Avalonia.Controls.TopLevel"/>s
/// created by the current platform backend.
///
/// This won't work for every single platform backend and backend settings, e. g. with web we'll need to have
/// separate Compositor instances per output HTML canvas since they don't share OpenGL state.
/// Another case where default compositor won't be available is our planned multithreaded rendering mode
/// where each window would get its own Compositor instance
///
/// This method is still useful for obtaining GPU device LUID to speed up initialization, but you should
/// always check if default Compositor matches one used by our control once it gets attached to a TopLevel
/// </summary>
/// <returns></returns>
public static Compositor? TryGetDefaultCompositor() => AvaloniaLocator.Current.GetService<Compositor>();
}

internal interface ICompositorScheduler
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
using Avalonia.Utilities;

namespace Avalonia.Rendering.Composition.Server;

internal partial class ServerCompositor
{
private IReadOnlyDictionary<Type, object>? _renderInterfaceFeatureCache;
private readonly object _renderInterfaceFeaturesUserApiLock = new();

void RT_OnContextCreated(IPlatformRenderInterfaceContext context)
{
lock (_renderInterfaceFeaturesUserApiLock)
{
_renderInterfaceFeatureCache = null;
_renderInterfaceFeatureCache = context.PublicFeatures.ToDictionary(x => x.Key, x => x.Value);
}
}

bool RT_OnContextLostExceptionFilterObserver(Exception e)
{
if (e is PlatformGraphicsContextLostException)
{
lock (_renderInterfaceFeaturesUserApiLock)
_renderInterfaceFeatureCache = null;
}
return false;
}

void RT_OnContextDisposed()
{
lock (_renderInterfaceFeaturesUserApiLock)
_renderInterfaceFeatureCache = null;
}

public IReadOnlyDictionary<Type, object>? AT_TryGetCachedRenderInterfaceFeatures()
{
lock (_renderInterfaceFeaturesUserApiLock)
return _renderInterfaceFeatureCache;
}

public IReadOnlyDictionary<Type, object> RT_GetRenderInterfaceFeatures()
{
lock (_renderInterfaceFeaturesUserApiLock)
return _renderInterfaceFeatureCache ??= RenderInterface.Value.PublicFeatures;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ internal partial class ServerCompositor : IRenderLoopTask
{
_renderLoop = renderLoop;
RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics);
RenderInterface.ContextDisposed += RT_OnContextDisposed;
RenderInterface.ContextCreated += RT_OnContextCreated;
BatchObjectPool = batchObjectPool;
BatchMemoryPool = batchMemoryPool;
_renderLoop.Add(this);
Expand Down Expand Up @@ -187,6 +189,10 @@ private void RenderReentrancySafe()
_safeThread = Thread.CurrentThread;
RenderCore();
}
catch (Exception e) when (RT_OnContextLostExceptionFilterObserver(e) && false)
// Will never get here, only using exception filter side effect
{
}
finally
{
NotifyBatchesRendered();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ internal class PlatformRenderInterfaceContextManager
private readonly IPlatformGraphics? _graphics;
private IPlatformRenderInterfaceContext? _backend;
private OwnedDisposable<IPlatformGraphicsContext>? _gpuContext;
public event Action? ContextDisposed;
public event Action<IPlatformRenderInterfaceContext>? ContextCreated;

public PlatformRenderInterfaceContextManager(IPlatformGraphics? graphics)
{
Expand All @@ -23,8 +25,12 @@ public void EnsureValidBackendContext()
{
_backend?.Dispose();
_backend = null;
_gpuContext?.Dispose();
_gpuContext = null;
if (_gpuContext != null)
{
_gpuContext?.Dispose();
_gpuContext = null;
ContextDisposed?.Invoke();
}

if (_graphics != null)
{
Expand All @@ -36,6 +42,7 @@ public void EnsureValidBackendContext()

_backend = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>()
.CreateBackendContext(_gpuContext?.Value);
ContextCreated?.Invoke(_backend);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Avalonia.Native/AvaloniaNativePlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ void DoInitialize(AvaloniaNativePlatformOptions options)


Compositor = new Compositor(_platformGraphics, true);
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);

AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}
Expand Down
1 change: 1 addition & 0 deletions src/Avalonia.X11/X11Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public void Initialize(X11PlatformOptions options)
}

Compositor = new Compositor(graphics);
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
}

public IntPtr DeferredDisplay { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public IGeometryImpl CreateRectangleGeometry(Rect rect)

public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
public bool IsLost => false;
public IReadOnlyDictionary<Type, object> PublicFeatures { get; } = new Dictionary<Type, object>();
public object? TryGetFeature(Type featureType) => null;

public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
Expand Down
18 changes: 18 additions & 0 deletions src/Skia/Avalonia.Skia/SkiaBackendContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.OpenGL;
using Avalonia.Platform;

namespace Avalonia.Skia;
Expand All @@ -14,6 +15,22 @@ internal class SkiaContext : IPlatformRenderInterfaceContext
public SkiaContext(ISkiaGpu? gpu)
{
_gpu = gpu;

var features = new Dictionary<Type, object>();

if (gpu != null)
{
void TryFeature<T>() where T : class
{
if (gpu!.TryGetFeature<T>() is { } feature)
features!.Add(typeof(T), feature);
}
// TODO12: extend ISkiaGpu with PublicFeatures instead
TryFeature<IOpenGlTextureSharingRenderInterfaceContextFeature>();
TryFeature<IExternalObjectsRenderInterfaceContextFeature>();
}

PublicFeatures = features;
}

public void Dispose()
Expand Down Expand Up @@ -44,6 +61,7 @@ public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
}

public bool IsLost => _gpu?.IsLost ?? false;
public IReadOnlyDictionary<Type, object> PublicFeatures { get; }

public object? TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType);
}
1 change: 1 addition & 0 deletions src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ public void Dispose()

public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => _platform.CreateRenderTarget(surfaces);
public bool IsLost => false;
public IReadOnlyDictionary<Type, object> PublicFeatures { get; } = new Dictionary<Type, object>();
}

public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) =>
Expand Down
1 change: 1 addition & 0 deletions src/Windows/Avalonia.Win32/Win32Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public static void Initialize(Win32PlatformOptions options)
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();

s_compositor = new Compositor( platformGraphics);
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(s_compositor);
}

public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
Expand Down
1 change: 1 addition & 0 deletions src/iOS/Avalonia.iOS/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static void Register()
.Bind<IKeyboardDevice>().ToConstant(keyboard);

Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
}
}
}
Expand Down

0 comments on commit b229779

Please sign in to comment.