Skip to content

Commit 7fb2663

Browse files
authored
GPU interop features now don't require to skip the first frame and available earlier in general (#14651)
1 parent f3f26eb commit 7fb2663

File tree

13 files changed

+139
-22
lines changed

13 files changed

+139
-22
lines changed

src/Android/Avalonia.Android/AndroidPlatform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static void Initialize()
9090
}
9191

9292
Compositor = new Compositor(graphics);
93+
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
9394
}
9495

9596
private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts)

src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,5 +217,10 @@ public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDi
217217
/// Indicates that the context is no longer usable. This method should be thread-safe
218218
/// </summary>
219219
bool IsLost { get; }
220+
221+
/// <summary>
222+
/// Exposes features that should be available for consumption while context isn't active (e. g. from the UI thread)
223+
/// </summary>
224+
IReadOnlyDictionary<Type, object> PublicFeatures { get; }
220225
}
221226
}

src/Avalonia.Base/Rendering/Composition/Compositor.cs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using Avalonia.Animation;
55
using Avalonia.Animation.Easings;
6+
using Avalonia.Controls;
67
using Avalonia.Media;
78
using Avalonia.Metadata;
89
using Avalonia.Platform;
@@ -256,33 +257,56 @@ internal Task<T> InvokeServerJobAsync<T>(Func<T> job)
256257
return tcs.Task;
257258
}
258259

260+
internal ValueTask<IReadOnlyDictionary<Type, object>> GetRenderInterfacePublicFeatures()
261+
{
262+
if (Server.AT_TryGetCachedRenderInterfaceFeatures() is { } rv)
263+
return new(rv);
264+
if (!Loop.RunsInBackground)
265+
return new(Server.RT_GetRenderInterfaceFeatures());
266+
return new(InvokeServerJobAsync(Server.RT_GetRenderInterfaceFeatures));
267+
}
268+
259269
/// <summary>
260270
/// Attempts to query for a feature from the platform render interface
261271
/// </summary>
262-
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) =>
263-
new(InvokeServerJobAsync(() =>
264-
{
265-
using (Server.RenderInterface.EnsureCurrent())
266-
{
267-
return Server.RenderInterface.Value.TryGetFeature(featureType);
268-
}
269-
}));
272+
public async ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
273+
{
274+
(await GetRenderInterfacePublicFeatures().ConfigureAwait(false)).TryGetValue(featureType, out var rv);
275+
return rv;
276+
}
277+
278+
/// <summary>
279+
/// Attempts to query for GPU interop feature from the platform render interface
280+
/// </summary>
281+
/// <returns></returns>
282+
public async ValueTask<ICompositionGpuInterop?> TryGetCompositionGpuInterop()
283+
{
284+
var externalObjects =
285+
(IExternalObjectsRenderInterfaceContextFeature?)await TryGetRenderInterfaceFeature(
286+
typeof(IExternalObjectsRenderInterfaceContextFeature)).ConfigureAwait(false);
270287

271-
public ValueTask<ICompositionGpuInterop?> TryGetCompositionGpuInterop() =>
272-
new(InvokeServerJobAsync<ICompositionGpuInterop?>(() =>
273-
{
274-
using (Server.RenderInterface.EnsureCurrent())
275-
{
276-
var feature = Server.RenderInterface.Value
277-
.TryGetFeature<IExternalObjectsRenderInterfaceContextFeature>();
278-
if (feature == null)
279-
return null;
280-
return new CompositionInterop(this, feature);
281-
}
282-
}));
288+
if (externalObjects == null)
289+
return null;
290+
return new CompositionInterop(this, externalObjects);
291+
}
283292

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

288312
internal interface ICompositorScheduler
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Avalonia.Platform;
5+
using Avalonia.Utilities;
6+
7+
namespace Avalonia.Rendering.Composition.Server;
8+
9+
internal partial class ServerCompositor
10+
{
11+
private IReadOnlyDictionary<Type, object>? _renderInterfaceFeatureCache;
12+
private readonly object _renderInterfaceFeaturesUserApiLock = new();
13+
14+
void RT_OnContextCreated(IPlatformRenderInterfaceContext context)
15+
{
16+
lock (_renderInterfaceFeaturesUserApiLock)
17+
{
18+
_renderInterfaceFeatureCache = null;
19+
_renderInterfaceFeatureCache = context.PublicFeatures.ToDictionary(x => x.Key, x => x.Value);
20+
}
21+
}
22+
23+
bool RT_OnContextLostExceptionFilterObserver(Exception e)
24+
{
25+
if (e is PlatformGraphicsContextLostException)
26+
{
27+
lock (_renderInterfaceFeaturesUserApiLock)
28+
_renderInterfaceFeatureCache = null;
29+
}
30+
return false;
31+
}
32+
33+
void RT_OnContextDisposed()
34+
{
35+
lock (_renderInterfaceFeaturesUserApiLock)
36+
_renderInterfaceFeatureCache = null;
37+
}
38+
39+
public IReadOnlyDictionary<Type, object>? AT_TryGetCachedRenderInterfaceFeatures()
40+
{
41+
lock (_renderInterfaceFeaturesUserApiLock)
42+
return _renderInterfaceFeatureCache;
43+
}
44+
45+
public IReadOnlyDictionary<Type, object> RT_GetRenderInterfaceFeatures()
46+
{
47+
lock (_renderInterfaceFeaturesUserApiLock)
48+
return _renderInterfaceFeatureCache ??= RenderInterface.Value.PublicFeatures;
49+
}
50+
}

src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public ServerCompositor(IRenderLoop renderLoop, IPlatformGraphics? platformGraph
4444
{
4545
_renderLoop = renderLoop;
4646
RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics);
47+
RenderInterface.ContextDisposed += RT_OnContextDisposed;
48+
RenderInterface.ContextCreated += RT_OnContextCreated;
4749
BatchObjectPool = batchObjectPool;
4850
BatchMemoryPool = batchMemoryPool;
4951
_renderLoop.Add(this);
@@ -187,6 +189,10 @@ private void RenderReentrancySafe()
187189
_safeThread = Thread.CurrentThread;
188190
RenderCore();
189191
}
192+
catch (Exception e) when (RT_OnContextLostExceptionFilterObserver(e) && false)
193+
// Will never get here, only using exception filter side effect
194+
{
195+
}
190196
finally
191197
{
192198
NotifyBatchesRendered();

src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ internal class PlatformRenderInterfaceContextManager
1111
private readonly IPlatformGraphics? _graphics;
1212
private IPlatformRenderInterfaceContext? _backend;
1313
private OwnedDisposable<IPlatformGraphicsContext>? _gpuContext;
14+
public event Action? ContextDisposed;
15+
public event Action<IPlatformRenderInterfaceContext>? ContextCreated;
1416

1517
public PlatformRenderInterfaceContextManager(IPlatformGraphics? graphics)
1618
{
@@ -23,8 +25,12 @@ public void EnsureValidBackendContext()
2325
{
2426
_backend?.Dispose();
2527
_backend = null;
26-
_gpuContext?.Dispose();
27-
_gpuContext = null;
28+
if (_gpuContext != null)
29+
{
30+
_gpuContext?.Dispose();
31+
_gpuContext = null;
32+
ContextDisposed?.Invoke();
33+
}
2834

2935
if (_graphics != null)
3036
{
@@ -36,6 +42,7 @@ public void EnsureValidBackendContext()
3642

3743
_backend = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>()
3844
.CreateBackendContext(_gpuContext?.Value);
45+
ContextCreated?.Invoke(_backend);
3946
}
4047
}
4148

src/Avalonia.Native/AvaloniaNativePlatform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ void DoInitialize(AvaloniaNativePlatformOptions options)
161161

162162

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

165166
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
166167
}

src/Avalonia.X11/X11Platform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public void Initialize(X11PlatformOptions options)
9898
}
9999

100100
Compositor = new Compositor(graphics);
101+
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
101102
}
102103

103104
public IntPtr DeferredDisplay { get; set; }

src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, IGe
6060

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

6566
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)

src/Skia/Avalonia.Skia/SkiaBackendContext.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Generic;
44
using System.Linq;
55
using Avalonia.Controls.Platform.Surfaces;
6+
using Avalonia.OpenGL;
67
using Avalonia.Platform;
78

89
namespace Avalonia.Skia;
@@ -14,6 +15,22 @@ internal class SkiaContext : IPlatformRenderInterfaceContext
1415
public SkiaContext(ISkiaGpu? gpu)
1516
{
1617
_gpu = gpu;
18+
19+
var features = new Dictionary<Type, object>();
20+
21+
if (gpu != null)
22+
{
23+
void TryFeature<T>() where T : class
24+
{
25+
if (gpu!.TryGetFeature<T>() is { } feature)
26+
features!.Add(typeof(T), feature);
27+
}
28+
// TODO12: extend ISkiaGpu with PublicFeatures instead
29+
TryFeature<IOpenGlTextureSharingRenderInterfaceContextFeature>();
30+
TryFeature<IExternalObjectsRenderInterfaceContextFeature>();
31+
}
32+
33+
PublicFeatures = features;
1734
}
1835

1936
public void Dispose()
@@ -44,6 +61,7 @@ public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
4461
}
4562

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

4866
public object? TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType);
4967
}

src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public void Dispose()
181181

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

186187
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) =>

src/Windows/Avalonia.Win32/Win32Platform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public static void Initialize(Win32PlatformOptions options)
127127
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
128128

129129
s_compositor = new Compositor( platformGraphics);
130+
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(s_compositor);
130131
}
131132

132133
public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;

src/iOS/Avalonia.iOS/Platform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public static void Register()
7878
.Bind<IKeyboardDevice>().ToConstant(keyboard);
7979

8080
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
81+
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
8182
}
8283

8384
private static IPlatformGraphics InitializeGraphics(iOSPlatformOptions opts)

0 commit comments

Comments
 (0)