Browse Source

Context management

pull/9639/head
Nikita Tsukanov 4 years ago
parent
commit
eaf2ce38a4
  1. 9
      src/Android/Avalonia.Android/AndroidPlatform.cs
  2. 32
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  3. 30
      src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
  4. 18
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  5. 2
      src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs
  6. 18
      src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
  7. 14
      src/Avalonia.Base/Platform/IPlatformGpu.cs
  8. 29
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  9. 10
      src/Avalonia.Base/Platform/IRenderTarget.cs
  10. 9
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  11. 7
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
  12. 33
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  13. 16
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  14. 54
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  15. 53
      src/Avalonia.Base/Rendering/DeferredRenderer.cs
  16. 6
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  17. 6
      src/Avalonia.Base/Rendering/IRenderer.cs
  18. 26
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  19. 24
      src/Avalonia.Base/Rendering/OwnedDisposable.cs
  20. 66
      src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs
  21. 27
      src/Avalonia.Base/Rendering/RenderLoop.cs
  22. 2
      src/Avalonia.Base/Rendering/SceneGraph/Scene.cs
  23. 2
      src/Avalonia.Controls/AppBuilderBase.cs
  24. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  25. 12
      src/Avalonia.Controls/TopLevel.cs
  26. 5
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  27. 14
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  28. 5
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  29. 12
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  30. 60
      src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs
  31. 7
      src/Avalonia.Native/PopupImpl.cs
  32. 7
      src/Avalonia.Native/WindowImpl.cs
  33. 16
      src/Avalonia.Native/WindowImplBase.cs
  34. 95
      src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs
  35. 118
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  36. 5
      src/Avalonia.OpenGL/Egl/EglConsts.cs
  37. 69
      src/Avalonia.OpenGL/Egl/EglContext.cs
  38. 273
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  39. 30
      src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
  40. 135
      src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs
  41. 39
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs
  42. 49
      src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs
  43. 3
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  44. 48
      src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs
  45. 77
      src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs
  46. 6
      src/Avalonia.OpenGL/Egl/EglSurface.cs
  47. 12
      src/Avalonia.OpenGL/IGlContext.cs
  48. 9
      src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs
  49. 13
      src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
  50. 15
      src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs
  51. 16
      src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
  52. 11
      src/Avalonia.OpenGL/OpenGlException.cs
  53. 2
      src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs
  54. 10
      src/Avalonia.X11/Glx/GlxContext.cs
  55. 4
      src/Avalonia.X11/Glx/GlxDisplay.cs
  56. 10
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  57. 20
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  58. 3
      src/Avalonia.X11/X11CursorFactory.cs
  59. 3
      src/Avalonia.X11/X11IconLoader.cs
  60. 11
      src/Avalonia.X11/X11ImmediateRendererProxy.cs
  61. 11
      src/Avalonia.X11/X11Platform.cs
  62. 20
      src/Avalonia.X11/X11Window.cs
  63. 3
      src/Browser/Avalonia.Browser/AvaloniaView.cs
  64. 3
      src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
  65. 3
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  66. 26
      src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs
  67. 2
      src/Browser/Avalonia.Browser/WindowingPlatform.cs
  68. 4
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  69. 4
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  70. 50
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  71. 3
      src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs
  72. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  73. 4
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  74. 5
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs
  75. 55
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  76. 6
      src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
  77. 2
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  78. 57
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  79. 47
      src/Skia/Avalonia.Skia/SkiaBackendContext.cs
  80. 5
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  81. 3
      src/Skia/Avalonia.Skia/SkiaPlatform.cs
  82. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  83. 3
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  84. 22
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  85. 2
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  86. 2
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  87. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  88. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  89. 2
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  90. 2
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  91. 4
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  92. 3
      src/Windows/Avalonia.Win32/AngleOptions.cs
  93. 4
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  94. 51
      src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
  95. 1108
      src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
  96. 15
      src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs
  97. 34
      src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs
  98. 19
      src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs
  99. 16
      src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs
  100. 26
      src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs

9
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -32,6 +32,7 @@ namespace Avalonia.Android
public static AndroidPlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@ -51,15 +52,19 @@ namespace Avalonia.Android
if (Options.UseGpu)
{
EglPlatformOpenGlInterface.TryInitialize();
EglPlatformGraphics.TryInitialize();
}
if (Options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
}
else
RenderInterface =
new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current
.GetService<IPlatformGraphics>());
}
}

32
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@ -1,32 +0,0 @@
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase
{
private readonly EglPlatformOpenGlInterface _egl;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info)
{
_egl = egl;
_info = info;
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle);
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{
var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (feature is EglPlatformOpenGlInterface egl)
{
return new GlPlatformSurface(egl, info);
}
return null;
}
}
}

30
src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs

@ -1,30 +0,0 @@
using System;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglSurface _surface;
private readonly IntPtr _handle;
public GlRenderTarget(
EglPlatformOpenGlInterface egl,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
EglSurface surface,
IntPtr handle)
: base(egl)
{
_info = info;
_surface = surface;
_handle = handle;
}
public bool IsCorrupted => _handle != _info.Handle;
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
}
}

18
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -8,7 +8,6 @@ using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
@ -30,7 +29,7 @@ using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo,
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private readonly IGlPlatformSurface _gl;
@ -47,7 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_pointerHelper = new AndroidMotionEventsHelper(this);
_gl = GlPlatformSurface.TryCreate(this);
_gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this);
RenderScaling = _view.Scaling;
@ -106,10 +105,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseCompositor
? new CompositingRenderer(root, AndroidPlatform.Compositor)
? new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces)
: AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer((Visual)root);
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
() => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
AndroidPlatform.RenderInterface)
{ RenderOnlyOnRenderThread = true }
: new ImmediateRenderer((Visual)root,
() => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
AndroidPlatform.RenderInterface);
public virtual void Hide()
{
@ -283,7 +287,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
public PixelSize Size => _view.Size;

2
src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs

@ -60,5 +60,7 @@ namespace Avalonia.Media.Imaging
/// <inheritdoc/>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? vbr) => PlatformImpl.Item.CreateDrawingContext(vbr);
bool IRenderTarget.IsCorrupted => false;
}
}

18
src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs

@ -0,0 +1,18 @@
using System;
namespace Avalonia.Platform;
public interface IOptionalFeatureProvider
{
/// <summary>
/// Queries for an optional feature
/// </summary>
/// <param name="featureType">Feature type</param>
public object? TryGetFeature(Type featureType);
}
public static class OptionalFeatureProviderExtensions
{
public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class =>
(T?)provider.TryGetFeature(typeof(T));
}

14
src/Avalonia.Base/Platform/IPlatformGpu.cs

@ -4,13 +4,21 @@ using Avalonia.Metadata;
namespace Avalonia.Platform;
[Unstable]
public interface IPlatformGpu
public interface IPlatformGraphics
{
IPlatformGpuContext PrimaryContext { get; }
bool UsesSharedContext { get; }
IPlatformGraphicsContext CreateContext();
IPlatformGraphicsContext GetSharedContext();
}
[Unstable]
public interface IPlatformGpuContext : IDisposable
public interface IPlatformGraphicsContext : IDisposable, IOptionalFeatureProvider
{
bool IsLost { get; }
IDisposable EnsureCurrent();
}
public class PlatformGraphicsContextLostException : Exception
{
}

29
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -65,15 +65,6 @@ namespace Avalonia.Platform
/// <returns>The geometry returned contains the combined geometry of all glyphs in the glyph run.</returns>
IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun);
/// <summary>
/// Creates a renderer.
/// </summary>
/// <param name="surfaces">
/// The list of native platform surfaces that can be used for output.
/// </param>
/// <returns>An <see cref="IRenderTarget"/>.</returns>
IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces);
/// <summary>
/// Creates a render target bitmap implementation.
/// </summary>
@ -181,6 +172,13 @@ namespace Avalonia.Platform
/// <returns></returns>
IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices, IReadOnlyList<double>? glyphAdvances, IReadOnlyList<Vector>? glyphOffsets);
/// <summary>
/// Creates a backend-specific object using a low-level API graphics context
/// </summary>
/// <param name="graphicsApiContext">An underlying low-level graphics context (e. g. wrapped OpenGL context, Vulkan device, D3DDevice, etc)</param>
/// <returns></returns>
IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext? graphicsApiContext);
/// <summary>
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners.
/// </summary>
@ -200,4 +198,17 @@ namespace Avalonia.Platform
/// </summary>
public PixelFormat DefaultPixelFormat { get; }
}
[Unstable]
public interface IPlatformRenderInterfaceContext : IOptionalFeatureProvider, IDisposable
{
/// <summary>
/// Creates a renderer.
/// </summary>
/// <param name="surfaces">
/// The list of native platform surfaces that can be used for output.
/// </param>
/// <returns>An <see cref="IRenderTarget"/>.</returns>
IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces);
}
}

10
src/Avalonia.Base/Platform/IRenderTarget.cs

@ -19,10 +19,10 @@ namespace Avalonia.Platform
/// to be drawn.
/// </param>
IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? visualBrushRenderer);
}
public interface IRenderTargetWithCorruptionInfo : IRenderTarget
{
bool IsCorrupted { get; }
/// <summary>
/// Indicates if the render target is no longer usable and needs to be recreated
/// </summary>
public bool IsCorrupted { get; }
}
}

9
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -38,13 +38,12 @@ public class CompositingRenderer : IRendererWithCompositor
/// </summary>
public bool RenderOnlyOnRenderThread { get; set; } = true;
public CompositingRenderer(IRenderRoot root,
Compositor compositor)
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
{
_root = root;
_compositor = compositor;
_recordingContext = new DrawingContext(_recorder);
CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget);
CompositionTarget = compositor.CreateCompositionTarget(surfaces);
CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
_update = Update;
}
@ -301,7 +300,9 @@ public class CompositingRenderer : IRendererWithCompositor
{
CompositionTarget.IsEnabled = false;
}
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType);
public void Dispose()
{
Stop();

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
@ -10,11 +11,11 @@ public partial class Compositor
/// <summary>
/// Creates a new CompositionTarget
/// </summary>
/// <param name="renderTargetFactory">A factory method to create IRenderTarget to be called from the render thread</param>
/// <param name="surfaces">A factory method to create IRenderTarget to be called from the render thread</param>
/// <returns></returns>
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory)
public CompositionTarget CreateCompositionTarget(Func<IEnumerable<object>> surfaces)
{
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory));
return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces));
}
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));

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

@ -34,6 +34,7 @@ namespace Avalonia.Rendering.Composition
internal ServerCompositor Server => _server;
private Task? _pendingBatch;
private readonly object _pendingBatchLock = new();
private List<Action> _pendingServerCompositorJobs = new();
internal IEasing DefaultEasing { get; }
@ -43,7 +44,7 @@ namespace Avalonia.Rendering.Composition
/// </summary>
/// <param name="loop"></param>
/// <param name="gpu"></param>
public Compositor(IRenderLoop loop, IPlatformGpu? gpu)
public Compositor(IRenderLoop loop, IPlatformGraphics? gpu)
{
Loop = loop;
_server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool);
@ -101,6 +102,13 @@ namespace Avalonia.Rendering.Composition
#endif
}
_objectsForSerialization.Clear();
if (_pendingServerCompositorJobs.Count > 0)
{
writer.WriteObject(ServerCompositor.RenderThreadJobsStartMarker);
foreach (var job in _pendingServerCompositorJobs)
writer.WriteObject(job);
writer.WriteObject(ServerCompositor.RenderThreadJobsEndMarker);
}
}
batch.CommitedAt = Server.Clock.Elapsed;
@ -136,5 +144,28 @@ namespace Avalonia.Rendering.Composition
_invokeBeforeCommit.Enqueue(action);
RequestCommitAsync();
}
/// <summary>
/// Attempts to query for a feature from the platform render interface
/// </summary>
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
{
var tcs = new TaskCompletionSource<object?>();
_pendingServerCompositorJobs.Add(() =>
{
try
{
using (Server.RenderInterface.EnsureCurrent())
{
tcs.TrySetResult(Server.RenderInterface.Value.TryGetFeature(featureType));
}
}
catch (Exception e)
{
tcs.SetResult(e);
}
});
return new ValueTask<object?>(tcs.Task);
}
}
}

16
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -20,7 +20,7 @@ namespace Avalonia.Rendering.Composition.Server
internal partial class ServerCompositionTarget : IDisposable
{
private readonly ServerCompositor _compositor;
private readonly Func<IRenderTarget> _renderTargetFactory;
private readonly Func<IEnumerable<object>> _surfaces;
private static long s_nextId = 1;
public long Id { get; }
public ulong Revision { get; private set; }
@ -39,11 +39,11 @@ namespace Avalonia.Rendering.Composition.Server
public ReadbackIndices Readback { get; } = new();
public int RenderedVisuals { get; set; }
public ServerCompositionTarget(ServerCompositor compositor, Func<IRenderTarget> renderTargetFactory) :
public ServerCompositionTarget(ServerCompositor compositor, Func<IEnumerable<object>> surfaces) :
base(compositor)
{
_compositor = compositor;
_renderTargetFactory = renderTargetFactory;
_surfaces = surfaces;
Id = Interlocked.Increment(ref s_nextId);
}
@ -79,13 +79,14 @@ namespace Avalonia.Rendering.Composition.Server
if (Root == null)
return;
if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
if (_renderTarget?.IsCorrupted == true)
{
_renderTarget!.Dispose();
_renderTarget = null;
_redrawRequested = true;
}
_renderTarget ??= _renderTargetFactory();
_renderTarget ??= _compositor.CreateRenderTarget(_surfaces());
Compositor.UpdateServerTime();
@ -109,12 +110,13 @@ namespace Avalonia.Rendering.Composition.Server
using (var targetContext = _renderTarget.CreateDrawingContext(null))
{
var layerSize = Size * Scaling;
if (layerSize != _layerSize || _layer == null)
if (layerSize != _layerSize || _layer == null || _layer.IsCorrupted)
{
_layer?.Dispose();
_layer = null;
_layer = targetContext.CreateLayer(Size);
_layerSize = layerSize;
_dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height);
}
if (!_dirtyRect.IsEmpty)
@ -197,7 +199,7 @@ namespace Avalonia.Rendering.Composition.Server
if(_disposed)
return;
_disposed = true;
using (_compositor.GpuContext?.EnsureCurrent())
using (_compositor.RenderInterface.EnsureCurrent())
{
if (_layer != null)
{

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Logging;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Expressions;
@ -19,6 +20,7 @@ namespace Avalonia.Rendering.Composition.Server
internal class ServerCompositor : IRenderLoopTask
{
private readonly IRenderLoop _renderLoop;
private readonly Queue<Batch> _batches = new Queue<Batch>();
public long LastBatchId { get; private set; }
public Stopwatch Clock { get; } = Stopwatch.StartNew();
@ -29,13 +31,15 @@ namespace Avalonia.Rendering.Composition.Server
internal BatchStreamObjectPool<object?> BatchObjectPool;
internal BatchStreamMemoryPool BatchMemoryPool;
private object _lock = new object();
public IPlatformGpuContext? GpuContext { get; }
public PlatformRenderInterfaceContextManager RenderInterface { get; }
internal static readonly object RenderThreadJobsStartMarker = new();
internal static readonly object RenderThreadJobsEndMarker = new();
public ServerCompositor(IRenderLoop renderLoop, IPlatformGpu? platformGpu,
public ServerCompositor(IRenderLoop renderLoop, IPlatformGraphics? platformGraphics,
BatchStreamObjectPool<object?> batchObjectPool, BatchStreamMemoryPool batchMemoryPool)
{
GpuContext = platformGpu?.PrimaryContext;
_renderLoop = renderLoop;
RenderInterface = new PlatformRenderInterfaceContextManager(platformGraphics);
BatchObjectPool = batchObjectPool;
BatchMemoryPool = batchMemoryPool;
_renderLoop.Add(this);
@ -66,7 +70,14 @@ namespace Avalonia.Rendering.Composition.Server
{
while (!stream.IsObjectEof)
{
var target = (ServerObject)stream.ReadObject()!;
var readObject = stream.ReadObject();
if (readObject == RenderThreadJobsStartMarker)
{
ReadAndExecuteJobs(stream);
continue;
}
var target = (ServerObject)readObject!;
target.DeserializeChanges(stream, batch);
#if DEBUG_COMPOSITOR_SERIALIZATION
if (stream.ReadObject() != BatchStreamDebugMarkers.ObjectEndMarker)
@ -84,6 +95,23 @@ namespace Avalonia.Rendering.Composition.Server
}
}
void ReadAndExecuteJobs(BatchStreamReader reader)
{
object? readObject;
while ((readObject = reader.ReadObject()) != RenderThreadJobsEndMarker)
{
var job = (Action)readObject!;
try
{
job();
}
catch
{
// Ignore
}
}
}
void CompletePendingBatches()
{
foreach(var batch in _reusableToCompleteList)
@ -118,8 +146,16 @@ namespace Avalonia.Rendering.Composition.Server
_animationsToUpdate.Clear();
foreach (var t in _activeTargets)
t.Render();
try
{
RenderInterface.EnsureValidBackendContext();
foreach (var t in _activeTargets)
t.Render();
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, LogArea.Visual)?.Log(this, "Exception when rendering: {Error}", e);
}
}
public void AddCompositionTarget(ServerCompositionTarget target)
@ -137,5 +173,11 @@ namespace Avalonia.Rendering.Composition.Server
public void RemoveFromClock(IAnimationInstance animationInstance) =>
_activeAnimations.Remove(animationInstance);
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
using (RenderInterface.EnsureCurrent())
return RenderInterface.CreateRenderTarget(surfaces);
}
}
}

53
src/Avalonia.Base/Rendering/DeferredRenderer.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Immutable;
@ -22,6 +23,8 @@ namespace Avalonia.Rendering
{
private readonly IDispatcher? _dispatcher;
private readonly IRenderLoop? _renderLoop;
private readonly Func<IRenderTarget>? _renderTargetFactory;
private readonly PlatformRenderInterfaceContextManager? _renderInterface;
private readonly Visual _root;
private readonly ISceneBuilder _sceneBuilder;
@ -39,6 +42,7 @@ namespace Avalonia.Rendering
private readonly object _startStopLock = new object();
private readonly object _renderLoopIsRenderingLock = new object();
private readonly Action _updateSceneIfNeededDelegate;
private List<Action>? _pendingRenderThreadJobs;
/// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@ -51,6 +55,8 @@ namespace Avalonia.Rendering
public DeferredRenderer(
IRenderRoot root,
IRenderLoop renderLoop,
Func<IRenderTarget> renderTargetFactory,
PlatformRenderInterfaceContextManager? renderInterface = null,
ISceneBuilder? sceneBuilder = null,
IDispatcher? dispatcher = null,
IDeferredRendererLock? rendererLock = null) : base(true)
@ -60,6 +66,8 @@ namespace Avalonia.Rendering
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers();
_renderLoop = renderLoop;
_renderTargetFactory = renderTargetFactory;
_renderInterface = renderInterface;
_lock = rendererLock ?? new ManagedDeferredRendererLock();
_updateSceneIfNeededDelegate = UpdateSceneIfNeeded;
}
@ -256,6 +264,30 @@ namespace Avalonia.Rendering
}
}
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
{
if (_renderInterface == null)
return new((object?)null);
var tcs = new TaskCompletionSource<object?>();
_pendingRenderThreadJobs ??= new();
_pendingRenderThreadJobs.Add(() =>
{
try
{
using (_renderInterface.EnsureCurrent())
{
tcs.TrySetResult(_renderInterface.Value.TryGetFeature(featureType));
}
}
catch (Exception e)
{
tcs.SetResult(e);
}
});
return new ValueTask<object?>(tcs.Task);
}
bool NeedsUpdate => _dirty == null || _dirty.Count > 0;
bool IRenderLoopTask.NeedsUpdate => NeedsUpdate;
@ -337,7 +369,16 @@ namespace Avalonia.Rendering
}
finally
{
scene.Item.MarkAsRendered();
try
{
if(scene.Item.RenderThreadJobs!=null)
foreach (var job in scene.Item.RenderThreadJobs)
job();
}
finally
{
scene.Item.MarkAsRendered();
}
}
}
}
@ -604,7 +645,7 @@ namespace Avalonia.Rendering
return;
}
if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
if (RenderTarget?.IsCorrupted == true)
{
RenderTarget!.Dispose();
RenderTarget = null;
@ -612,7 +653,7 @@ namespace Avalonia.Rendering
if (RenderTarget == null)
{
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
RenderTarget = _renderTargetFactory!();
}
context = RenderTarget.CreateDrawingContext(this);
@ -637,7 +678,11 @@ namespace Avalonia.Rendering
}
if (_root.IsVisible)
{
var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root));
var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root)
{
RenderThreadJobs = _pendingRenderThreadJobs
});
_pendingRenderThreadJobs = null;
var scene = sceneRef.Item;
if (_dirty == null)

6
src/Avalonia.Base/Rendering/IRenderRoot.cs

@ -25,12 +25,6 @@ namespace Avalonia.Rendering
/// </summary>
double RenderScaling { get; }
/// <summary>
/// Creates a render target for the window.
/// </summary>
/// <returns>An <see cref="IRenderTarget"/>.</returns>
IRenderTarget CreateRenderTarget();
/// <summary>
/// Adds a rectangle to the window's dirty region.
/// </summary>

6
src/Avalonia.Base/Rendering/IRenderer.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.VisualTree;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Rendering.Composition;
namespace Avalonia.Rendering
@ -87,6 +88,11 @@ namespace Avalonia.Rendering
/// Stops the renderer.
/// </summary>
void Stop();
/// <summary>
/// Attempts to query for a feature from the platform render interface
/// </summary>
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType);
}
public interface IRendererWithCompositor : IRenderer

26
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Platform;
@ -19,6 +20,8 @@ namespace Avalonia.Rendering
public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer
{
private readonly Visual _root;
private readonly Func<IRenderTarget> _renderTargetFactory;
private readonly PlatformRenderInterfaceContextManager? _renderContext;
private readonly IRenderRoot? _renderRoot;
private bool _updateTransformedBounds = true;
private IRenderTarget? _renderTarget;
@ -27,15 +30,19 @@ namespace Avalonia.Rendering
/// Initializes a new instance of the <see cref="ImmediateRenderer"/> class.
/// </summary>
/// <param name="root">The control to render.</param>
public ImmediateRenderer(Visual root)
public ImmediateRenderer(Visual root, Func<IRenderTarget> renderTargetFactory,
PlatformRenderInterfaceContextManager? renderContext = null)
{
_root = root ?? throw new ArgumentNullException(nameof(root));
_renderTargetFactory = renderTargetFactory;
_renderContext = renderContext;
_renderRoot = root as IRenderRoot;
}
private ImmediateRenderer(Visual root, bool updateTransformedBounds)
private ImmediateRenderer(Visual root, Func<IRenderTarget> renderTargetFactory, bool updateTransformedBounds)
{
_root = root ?? throw new ArgumentNullException(nameof(root));
_renderTargetFactory = renderTargetFactory;
_renderRoot = root as IRenderRoot;
_updateTransformedBounds = updateTransformedBounds;
}
@ -54,7 +61,7 @@ namespace Avalonia.Rendering
{
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
_renderTarget = _renderTargetFactory();
}
try
@ -104,7 +111,7 @@ namespace Avalonia.Rendering
/// <param name="target">The render target.</param>
public static void Render(Visual visual, IRenderTarget target)
{
using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false))
using (var renderer = new ImmediateRenderer(visual, () => target, updateTransformedBounds: false))
using (var context = new DrawingContext(target.CreateDrawingContext(renderer)))
{
renderer.Render(context, visual, visual.Bounds);
@ -118,7 +125,9 @@ namespace Avalonia.Rendering
/// <param name="context">The drawing context.</param>
public static void Render(Visual visual, DrawingContext context)
{
using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false))
using (var renderer = new ImmediateRenderer(visual,
() => throw new InvalidOperationException("This is not supposed to be called"),
updateTransformedBounds: false))
{
renderer.Render(context, visual, visual.Bounds);
}
@ -185,6 +194,9 @@ namespace Avalonia.Rendering
{
}
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) =>
new(_renderContext?.Value?.TryGetFeature(featureType));
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
@ -201,7 +213,9 @@ namespace Avalonia.Rendering
internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)
{
using var renderer = new ImmediateRenderer(visual, updateTransformedBounds);
using var renderer = new ImmediateRenderer(visual,
() => throw new InvalidOperationException("This is not supposed to be called"),
updateTransformedBounds);
renderer.Render(context, visual, visual.Bounds);
}

24
src/Avalonia.Base/Rendering/OwnedDisposable.cs

@ -0,0 +1,24 @@
using System;
namespace Avalonia.Rendering;
struct OwnedDisposable<T> :IDisposable where T : class, IDisposable
{
private readonly bool _owns;
private T? _value;
public T Value => _value ?? throw new ObjectDisposedException("OwnedDisposable");
public OwnedDisposable(T value, bool owns)
{
_owns = owns;
_value = value;
}
public void Dispose()
{
if(_owns)
_value?.Dispose();
_value = null;
}
}

66
src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Rendering;
[Unstable]
// TODO: Make it internal once legacy renderers are removed
public class PlatformRenderInterfaceContextManager
{
private readonly IPlatformGraphics? _graphics;
private IPlatformRenderInterfaceContext? _backend;
private OwnedDisposable<IPlatformGraphicsContext>? _gpuContext;
public PlatformRenderInterfaceContextManager(IPlatformGraphics? graphics)
{
_graphics = graphics;
}
public void EnsureValidBackendContext()
{
if (_backend == null || _gpuContext?.Value.IsLost == true)
{
_backend?.Dispose();
_backend = null;
_gpuContext?.Dispose();
_gpuContext = null;
if (_graphics != null)
{
if (_graphics.UsesSharedContext)
_gpuContext = new OwnedDisposable<IPlatformGraphicsContext>(_graphics.GetSharedContext(), false);
else
_gpuContext = new OwnedDisposable<IPlatformGraphicsContext>(_graphics.CreateContext(), true);
}
_backend = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>()
.CreateBackendContext(_gpuContext?.Value);
}
}
public IPlatformRenderInterfaceContext Value
{
get
{
EnsureValidBackendContext();
return _backend!;
}
}
public IDisposable EnsureCurrent()
{
EnsureValidBackendContext();
if (_gpuContext.HasValue)
return _gpuContext.Value.Value.EnsureCurrent();
return Disposable.Empty;
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
EnsureValidBackendContext();
return _backend!.CreateRenderTarget(surfaces);
}
}

27
src/Avalonia.Base/Rendering/RenderLoop.cs

@ -19,6 +19,7 @@ namespace Avalonia.Rendering
private readonly IDispatcher _dispatcher;
private List<IRenderLoopTask> _items = new List<IRenderLoopTask>();
private List<IRenderLoopTask> _itemsCopy = new List<IRenderLoopTask>();
private List<IRenderLoopTask> _updateItemsCopy = new List<IRenderLoopTask>();
private IRenderTimer? _timer;
private int _inTick;
private int _inUpdate;
@ -97,7 +98,14 @@ namespace Avalonia.Rendering
{
bool needsUpdate = false;
foreach (IRenderLoopTask item in _items)
lock (_items)
{
_itemsCopy.Clear();
foreach (var i in _items)
_itemsCopy.Add(i);
}
foreach (IRenderLoopTask item in _itemsCopy)
{
if (item.NeedsUpdate)
{
@ -112,10 +120,13 @@ namespace Avalonia.Rendering
{
_dispatcher.Post(() =>
{
for (var i = 0; i < _items.Count; ++i)
lock (_items)
{
_updateItemsCopy.Clear();
_updateItemsCopy.AddRange(_items);
}
foreach (var item in _updateItemsCopy)
{
var item = _items[i];
if (item.NeedsUpdate)
{
try
@ -128,18 +139,12 @@ namespace Avalonia.Rendering
}
}
}
_updateItemsCopy.Clear();
Interlocked.Exchange(ref _inUpdate, 0);
}, DispatcherPriority.Render);
}
lock (_items)
{
_itemsCopy.Clear();
foreach (var i in _items)
_itemsCopy.Add(i);
}
for (int i = 0; i < _itemsCopy.Count; i++)
{
_itemsCopy[i].Render();

2
src/Avalonia.Base/Rendering/SceneGraph/Scene.cs

@ -346,5 +346,7 @@ namespace Avalonia.Rendering.SceneGraph
}
public void MarkAsRendered() => _rendered.TrySetResult(true);
public List<Action>? RenderThreadJobs { get; set; }
}
}

2
src/Avalonia.Controls/AppBuilderBase.cs

@ -229,8 +229,8 @@ namespace Avalonia.Controls
s_setupWasAlreadyCalled = true;
_optionsInitializers?.Invoke();
RuntimePlatformServicesInitializer();
WindowingSubsystemInitializer();
RenderingSubsystemInitializer();
WindowingSubsystemInitializer();
AfterPlatformServicesSetupCallback(Self);
Instance = _appFactory();
Instance.ApplicationLifetime = _lifetime;

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -13,6 +13,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
private double _scaling = 1;
private Size _clientSize;
private PlatformRenderInterfaceContextManager _renderInterface = new(null);
public IInputRoot? InputRoot { get; private set; }
public bool IsDisposed { get; private set; }
@ -22,7 +23,8 @@ namespace Avalonia.Controls.Embedding.Offscreen
IsDisposed = true;
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root);
public IRenderer CreateRenderer(IRenderRoot root) =>
new ImmediateRenderer((Visual)root, () => _renderInterface.CreateRenderTarget(Surfaces), _renderInterface);
public abstract void Invalidate(Rect rect);
public abstract IEnumerable<object> Surfaces { get; }

12
src/Avalonia.Controls/TopLevel.cs

@ -323,17 +323,7 @@ namespace Avalonia.Controls
??= AvaloniaLocator.Current.GetService<IStorageProviderFactory>()?.CreateProvider(this)
?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider
?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget();
/// <inheritdoc/>
protected virtual IRenderTarget CreateRenderTarget()
{
if(PlatformImpl == null)
throw new InvalidOperationException("Can't create render target, PlatformImpl is null (might be already disposed)");
return _renderInterface!.CreateRenderTarget(PlatformImpl.Surfaces);
}
/// <inheritdoc/>
void IRenderRoot.Invalidate(Rect rect)
{

5
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -61,7 +61,10 @@ namespace Avalonia.DesignerSupport.Remote
}));
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root);
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer((Visual)root, () =>
new PlatformRenderInterfaceContextManager(null)
.CreateRenderTarget(Surfaces));
public void Dispose()
{
}

14
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -12,7 +12,7 @@ using Avalonia.Media.Imaging;
namespace Avalonia.Headless
{
internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface
internal class HeadlessPlatformRenderInterface : IPlatformRenderInterface, IPlatformRenderInterfaceContext
{
public static void Initialize()
{
@ -22,6 +22,8 @@ namespace Avalonia.Headless
public IEnumerable<string> InstalledFontNames { get; } = new[] { "Tahoma" };
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
public bool SupportsIndividualRoundRects => false;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
@ -47,6 +49,7 @@ namespace Avalonia.Headless
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException();
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{
@ -313,6 +316,8 @@ namespace Avalonia.Headless
return new HeadlessDrawingContextStub();
}
public bool IsCorrupted => false;
public void Blit(IDrawingContextImpl context)
{
@ -474,6 +479,13 @@ namespace Avalonia.Headless
{
return new HeadlessDrawingContextStub();
}
public bool IsCorrupted => false;
}
public void Dispose()
{
}
}
}

5
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -56,8 +56,9 @@ namespace Avalonia.Headless
public IRenderer CreateRenderer(IRenderRoot root)
=> AvaloniaHeadlessPlatform.Compositor != null
? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor)
: new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>());
? new CompositingRenderer(root, AvaloniaHeadlessPlatform.Compositor, () => Surfaces)
: new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
() => new PlatformRenderInterfaceContextManager(null).CreateRenderTarget(Surfaces), null);
public void Invalidate(Rect rect)
{

12
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -18,13 +18,14 @@ namespace Avalonia.Native
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options;
private AvaloniaNativePlatformOpenGlInterface _platformGl;
private AvaloniaNativeGlPlatformGraphics _platformGl;
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
[CanBeNull] internal static Compositor Compositor { get; private set; }
[CanBeNull] internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
@ -126,10 +127,9 @@ namespace Avalonia.Native
{
try
{
_platformGl = new AvaloniaNativePlatformOpenGlInterface(_factory.ObtainGlDisplay());
_platformGl = new AvaloniaNativeGlPlatformGraphics(_factory.ObtainGlDisplay());
AvaloniaLocator.CurrentMutable
.Bind<IPlatformOpenGlInterface>().ToConstant(_platformGl)
.Bind<IPlatformGpu>().ToConstant(_platformGl);
.Bind<IPlatformGraphics>().ToConstant(_platformGl);
}
catch (Exception)
@ -137,12 +137,14 @@ namespace Avalonia.Native
// ignored
}
}
if (_options.UseDeferredRendering && _options.UseCompositor)
{
Compositor = new Compositor(renderLoop, _platformGl);
}
else
RenderInterface = new PlatformRenderInterfaceContextManager(_platformGl);
}
public ITrayIconImpl CreateTrayIcon()

60
src/Avalonia.Native/AvaloniaNativePlatformOpenGlInterface.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.Native.Interop;
using System.Drawing;
@ -8,18 +9,18 @@ using Avalonia.Threading;
namespace Avalonia.Native
{
class AvaloniaNativePlatformOpenGlInterface : IPlatformOpenGlInterface
class AvaloniaNativeGlPlatformGraphics : IPlatformGraphics
{
private readonly IAvnGlDisplay _display;
public AvaloniaNativePlatformOpenGlInterface(IAvnGlDisplay display)
public AvaloniaNativeGlPlatformGraphics(IAvnGlDisplay display)
{
_display = display;
var immediate = display.CreateContext(null);
var context = display.CreateContext(null);
int major, minor;
GlInterface glInterface;
using (immediate.MakeCurrent())
using (context.MakeCurrent())
{
var basic = new GlBasicInfoInterface(display.GetProcAddress);
basic.GetIntegerv(GlConsts.GL_MAJOR_VERSION, out major);
@ -32,24 +33,23 @@ namespace Avalonia.Native
});
}
GlDisplay = new GlDisplay(display, glInterface, immediate.SampleCount, immediate.StencilSize);
MainContext = new GlContext(GlDisplay, null, immediate, _version);
GlDisplay = new GlDisplay(display, glInterface, context.SampleCount, context.StencilSize);
SharedContext =(GlContext)CreateContext();
}
internal GlContext MainContext { get; }
public IGlContext PrimaryContext => MainContext;
IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
public bool UsesSharedContext => true;
public IPlatformGraphicsContext CreateContext() => new GlContext(GlDisplay,
null, _display.CreateContext(null), _version);
public IPlatformGraphicsContext GetSharedContext() => SharedContext;
public bool CanShareContexts => true;
public bool CanCreateContexts => true;
internal GlDisplay GlDisplay;
private readonly GlVersion _version;
public IGlContext CreateSharedContext() => new GlContext(GlDisplay,
MainContext, _display.CreateContext(MainContext.Context), _version);
public IGlContext CreateContext() => new GlContext(GlDisplay,
null, _display.CreateContext(null), _version);
internal GlContext SharedContext { get; }
}
class GlDisplay
@ -71,6 +71,10 @@ namespace Avalonia.Native
public int StencilSize { get; }
public void ClearContext() => _display.LegacyClearCurrentContext();
public GlContext CreateSharedContext(GlContext share) =>
new GlContext(this, share, _display.CreateContext(share.Context), share.Version);
}
class GlContext : IGlContext
@ -91,7 +95,14 @@ namespace Avalonia.Native
public GlInterface GlInterface => _display.GlInterface;
public int SampleCount => _display.SampleCount;
public int StencilSize => _display.StencilSize;
public IDisposable MakeCurrent() => Context.MakeCurrent();
public IDisposable MakeCurrent()
{
if (IsLost)
throw new PlatformGraphicsContextLostException();
return Context.MakeCurrent();
}
public bool IsLost => Context == null;
public IDisposable EnsureCurrent() => MakeCurrent();
public bool IsSharedWith(IGlContext context)
@ -103,12 +114,18 @@ namespace Avalonia.Native
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
public bool CanCreateSharedContext => true;
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
_display.CreateSharedContext(_sharedWith ?? this);
public void Dispose()
{
Context.Dispose();
Context = null;
}
public object TryGetFeature(Type featureType) => null;
}
@ -125,7 +142,6 @@ namespace Avalonia.Native
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var feature = (AvaloniaNativePlatformOpenGlInterface)AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
return new GlPlatformSurfaceRenderingSession(_context, _target.BeginDrawing());
}
@ -172,16 +188,14 @@ namespace Avalonia.Native
class GlPlatformSurface : IGlPlatformSurface
{
private readonly IAvnWindowBase _window;
private readonly IGlContext _context;
public GlPlatformSurface(IAvnWindowBase window, IGlContext context)
public GlPlatformSurface(IAvnWindowBase window)
{
_window = window;
_context = context;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), _context);
var avnContext = (GlContext)context;
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget(), avnContext);
}
}

7
src/Avalonia.Native/PopupImpl.cs

@ -8,12 +8,12 @@ namespace Avalonia.Native
class PopupImpl : WindowBaseImpl, IPopupImpl
{
private readonly AvaloniaNativePlatformOptions _opts;
private readonly AvaloniaNativePlatformOpenGlInterface _glFeature;
private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
AvaloniaNativePlatformOpenGlInterface glFeature,
AvaloniaNativeGlPlatformGraphics glFeature,
IWindowBaseImpl parent) : base(factory, opts, glFeature)
{
_opts = opts;
@ -21,8 +21,7 @@ namespace Avalonia.Native
_parent = parent;
using (var e = new PopupEvents(this))
{
var context = _opts.UseGpu ? glFeature?.MainContext : null;
Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context);
Init(factory.CreatePopup(e, _glFeature.SharedContext.Context), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}

7
src/Avalonia.Native/WindowImpl.cs

@ -14,14 +14,14 @@ namespace Avalonia.Native
internal class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter
{
private readonly AvaloniaNativePlatformOptions _opts;
private readonly AvaloniaNativePlatformOpenGlInterface _glFeature;
private readonly AvaloniaNativeGlPlatformGraphics _glFeature;
IAvnWindow _native;
private double _extendTitleBarHeight = -1;
private DoubleClickHelper _doubleClickHelper;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativePlatformOpenGlInterface glFeature) : base(factory, opts, glFeature)
AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
{
_opts = opts;
_glFeature = glFeature;
@ -29,8 +29,7 @@ namespace Avalonia.Native
using (var e = new WindowEvents(this))
{
var context = _opts.UseGpu ? glFeature?.MainContext : null;
Init(_native = factory.CreateWindow(e, context?.Context), factory.CreateScreens(), context);
Init(_native = factory.CreateWindow(e, glFeature.SharedContext.Context), factory.CreateScreens());
}
NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);

16
src/Avalonia.Native/WindowImplBase.cs

@ -66,7 +66,7 @@ namespace Avalonia.Native
private IGlContext _glContext;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativePlatformOpenGlInterface glFeature)
AvaloniaNativeGlPlatformGraphics glFeature)
{
_factory = factory;
_gpu = opts.UseGpu && glFeature != null;
@ -78,14 +78,13 @@ namespace Avalonia.Native
StorageProvider = new SystemDialogs(this, _factory.CreateSystemDialogs());
}
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
protected void Init(IAvnWindowBase window, IAvnScreens screens)
{
_native = window;
_glContext = glContext;
Handle = new MacOSTopLevelWindowHandle(window);
if (_gpu)
_glSurface = new GlPlatformSurface(window, _glContext);
_glSurface = new GlPlatformSurface(window);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
@ -375,14 +374,17 @@ namespace Avalonia.Native
if (_deferredRendering)
{
if (AvaloniaNativePlatform.Compositor != null)
return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor)
return new CompositingRenderer(root, AvaloniaNativePlatform.Compositor, () => Surfaces)
{
RenderOnlyOnRenderThread = false
};
return new DeferredRenderer(root, loop);
return new DeferredRenderer(root, loop,
() => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces),
AvaloniaNativePlatform.RenderInterface);
}
return new ImmediateRenderer((Visual)root);
return new ImmediateRenderer((Visual)root,
() => AvaloniaNativePlatform.RenderInterface!.CreateRenderTarget(Surfaces), AvaloniaNativePlatform.RenderInterface);
}
public virtual void Dispose()

95
src/Avalonia.OpenGL/Angle/AngleWin32EglDisplay.cs

@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.OpenGL.Egl;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Angle
{
public class AngleWin32EglDisplay : EglDisplay
{
struct AngleInfo
{
public IntPtr Display { get; set; }
public AngleOptions.PlatformApi PlatformApi { get; set; }
}
static AngleInfo CreateAngleDisplay(EglInterface _egl)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException();
var display = IntPtr.Zero;
AngleOptions.PlatformApi angleApi = default;
{
if (!_egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
?? new [] { AngleOptions.PlatformApi.DirectX11, AngleOptions.PlatformApi.DirectX9 };
foreach (var platformApi in allowedApis)
{
int dapi;
if (platformApi == AngleOptions.PlatformApi.DirectX9)
dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE;
else if (platformApi == AngleOptions.PlatformApi.DirectX11)
dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
else
continue;
display = _egl.GetPlatformDisplayExt(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
new[] { EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE });
if (display != IntPtr.Zero)
{
angleApi = platformApi;
break;
}
}
if (display == IntPtr.Zero)
throw new OpenGlException("Unable to create ANGLE display");
return new AngleInfo { Display = display, PlatformApi = angleApi };
}
}
private AngleWin32EglDisplay(EglInterface egl, AngleInfo info) : base(egl, false, info.Display)
{
PlatformApi = info.PlatformApi;
}
public AngleWin32EglDisplay(EglInterface egl) : this(egl, CreateAngleDisplay(egl))
{
}
public AngleWin32EglDisplay() : this(new AngleEglInterface())
{
}
public AngleOptions.PlatformApi PlatformApi { get; }
public IntPtr GetDirect3DDevice()
{
if (!EglInterface.QueryDisplayAttribExt(Handle, EglConsts.EGL_DEVICE_EXT, out var eglDevice))
throw new OpenGlException("Unable to get EGL_DEVICE_EXT");
if (!EglInterface.QueryDeviceAttribExt(eglDevice, PlatformApi == AngleOptions.PlatformApi.DirectX9 ? EGL_D3D9_DEVICE_ANGLE : EGL_D3D11_DEVICE_ANGLE, out var d3dDeviceHandle))
throw new OpenGlException("Unable to get EGL_D3D9_DEVICE_ANGLE");
return d3dDeviceHandle;
}
public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle)
{
if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
throw new InvalidOperationException("Current platform API is " + PlatformApi);
return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_NONE, EGL_NONE });
}
public EglSurface WrapDirect3D11Texture(EglPlatformOpenGlInterface egl, IntPtr handle, int offsetX, int offsetY, int width, int height)
{
if (PlatformApi != AngleOptions.PlatformApi.DirectX11)
throw new InvalidOperationException("Current platform API is " + PlatformApi);
return egl.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, handle, new[] { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE, EGL_TEXTURE_OFFSET_X_ANGLE, offsetX, EGL_TEXTURE_OFFSET_Y_ANGLE, offsetY, EGL_NONE });
}
}
}

118
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -1,8 +1,10 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.OpenGL.Imaging;
using Avalonia.VisualTree;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.OpenGL.Controls
@ -14,8 +16,10 @@ namespace Avalonia.OpenGL.Controls
private OpenGlBitmap _bitmap;
private IOpenGlBitmapAttachment _attachment;
private PixelSize _depthBufferSize;
private bool _glFailed;
private bool _initialized;
private Task<bool> _initialization;
private IOpenGlTextureSharingRenderInterfaceContextFeature _feature;
protected GlVersion GlVersion { get; private set; }
public sealed override void Render(DrawingContext context)
{
@ -38,13 +42,6 @@ namespace Avalonia.OpenGL.Controls
base.Render(context);
}
private static void CheckError(GlInterface gl)
{
int err;
while ((err = gl.GetError()) != GL_NO_ERROR)
Console.WriteLine(err);
}
void EnsureTextureAttachment()
{
_context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
@ -54,7 +51,7 @@ namespace Avalonia.OpenGL.Controls
_attachment = null;
_bitmap?.Dispose();
_bitmap = null;
_bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96));
_bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
_attachment = _bitmap.CreateFramebufferAttachment(_context);
}
}
@ -87,9 +84,11 @@ namespace Avalonia.OpenGL.Controls
gl.ActiveTexture(GL_TEXTURE0);
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffer(_fb);
if (_fb != 0)
gl.DeleteFramebuffer(_fb);
_fb = 0;
gl.DeleteRenderbuffer(_depthBuffer);
if (_depthBuffer != 0)
gl.DeleteRenderbuffer(_depthBuffer);
_depthBuffer = 0;
_attachment?.Dispose();
_attachment = null;
@ -98,10 +97,10 @@ namespace Avalonia.OpenGL.Controls
try
{
if (_initialized)
if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true })
{
_initialized = false;
OnOpenGlDeinit(_context.GlInterface, _fb);
_initialization = null;
}
}
finally
@ -111,6 +110,11 @@ namespace Avalonia.OpenGL.Controls
}
}
}
_fb = _depthBuffer = 0;
_attachment = null;
_bitmap = null;
_feature = null;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@ -119,26 +123,12 @@ namespace Avalonia.OpenGL.Controls
base.OnDetachedFromVisualTree(e);
}
private bool EnsureInitializedCore()
private bool EnsureInitializedCore(IOpenGlTextureSharingRenderInterfaceContextFeature feature)
{
if (_context != null)
return true;
if (_glFailed)
return false;
var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (feature == null)
return false;
if (!feature.CanShareContexts)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
return false;
}
try
{
_context = feature.CreateSharedContext();
_feature = feature;
}
catch (Exception e)
{
@ -157,7 +147,7 @@ namespace Avalonia.OpenGL.Controls
GlVersion = _context.Version;
try
{
_bitmap = new OpenGlBitmap(GetPixelSize(), new Vector(96, 96));
_bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
if (!_bitmap.SupportsContext(_context))
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
@ -212,15 +202,68 @@ namespace Avalonia.OpenGL.Controls
return true;
}
void ContextLost()
{
_context = null;
_feature = null;
_initialization = null;
_attachment = null;
_bitmap = null;
_fb = 0;
_depthBuffer = 0;
_depthBufferSize = default;
OnOpenGlLost();
}
private bool EnsureInitialized()
{
if (_initialized)
return true;
_glFailed = !(_initialized = EnsureInitializedCore());
if (_glFailed)
if (_initialization != null)
{
// Check if we've previously failed to initialize OpenGL on this platform
if (_initialization is { IsCompleted: true, Result: false } ||
_initialization?.IsFaulted == true)
return false;
// Check if we are still waiting for init to complete
if (_initialization is { IsCompleted: false })
return false;
if (_context.IsLost)
ContextLost();
else
return true;
}
_initialization = InitializeAsync();
return false;
}
private async Task<bool> InitializeAsync()
{
var contextSharingFeature =
(IOpenGlTextureSharingRenderInterfaceContextFeature)
await this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature(
typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
if (contextSharingFeature == null || !contextSharingFeature.CanCreateSharedContext)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
return false;
}
if (!EnsureInitializedCore(contextSharingFeature))
{
DoCleanup();
return false;
}
using (_context.MakeCurrent())
OnOpenGlInit(_context.GlInterface, _fb);
InvalidateVisual();
return true;
}
@ -242,6 +285,11 @@ namespace Avalonia.OpenGL.Controls
}
protected virtual void OnOpenGlLost()
{
}
protected abstract void OnOpenGlRender(GlInterface gl, int fb);
}
}

5
src/Avalonia.OpenGL/Egl/EglConsts.cs

@ -196,6 +196,11 @@ namespace Avalonia.OpenGL.Egl
// public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_WARP_ANGLE = 0x320B;
// public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_D3D_REFERENCE_ANGLE = 0x320C;
//
// EXT_platform_device
public const int EGL_PLATFORM_DEVICE_EXT = 0x313F;
//EXT_device_query
public const int EGL_DEVICE_EXT = 0x322C;

69
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.Platform;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
@ -10,24 +12,32 @@ namespace Avalonia.OpenGL.Egl
private readonly EglDisplay _disp;
private readonly EglInterface _egl;
private readonly EglContext _sharedWith;
private readonly object _lock = new object();
private bool _isLost;
private IntPtr _context;
private readonly Action _disposeCallback;
private readonly Dictionary<Type, object> _features;
private readonly object _lock;
public EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, Func<EglContext, EglSurface> offscreenSurface,
GlVersion version, int sampleCount, int stencilSize)
internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface,
GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, Dictionary<Type, object> features)
{
_disp = display;
_egl = egl;
_sharedWith = sharedWith;
Context = ctx;
OffscreenSurface = offscreenSurface(this);
_context = ctx;
_disposeCallback = disposeCallback;
_features = features;
OffscreenSurface = offscreenSurface;
Version = version;
SampleCount = sampleCount;
StencilSize = stencilSize;
_lock = display.ContextSharedSyncRoot ?? new object();
using (MakeCurrent())
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
}
public IntPtr Context { get; }
public IntPtr Context =>
_context == IntPtr.Zero ? throw new ObjectDisposedException(nameof(EglContext)) : _context;
public EglSurface OffscreenSurface { get; }
public GlVersion Version { get; }
public GlInterface GlInterface { get; }
@ -40,7 +50,7 @@ namespace Avalonia.OpenGL.Egl
private readonly EglInterface _egl;
private readonly object _l;
private readonly IntPtr _display;
private IntPtr _context, _read, _draw;
public IntPtr _context, _read, _draw;
public RestoreContext(EglInterface egl, IntPtr defDisplay, object l)
{
@ -66,6 +76,9 @@ namespace Avalonia.OpenGL.Egl
public IDisposable MakeCurrent(EglSurface surface)
{
if (IsLost)
throw new PlatformGraphicsContextLostException();
Monitor.Enter(_lock);
var success = false;
try
@ -74,8 +87,18 @@ namespace Avalonia.OpenGL.Egl
var surf = surface ?? OffscreenSurface;
_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (!_egl.MakeCurrent(_disp.Handle, surf?.DangerousGetHandle() ?? IntPtr.Zero,
surf?.DangerousGetHandle() ?? IntPtr.Zero, Context))
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
surf?.DangerousGetHandle() ?? IntPtr.Zero, Context))
{
var error = _egl.GetError();
if (error == EGL_CONTEXT_LOST)
{
NotifyContextLost();
throw new PlatformGraphicsContextLostException();
}
throw OpenGlException.GetFormattedEglException("eglMakeCurrent", error);
}
success = true;
return old;
}
@ -85,7 +108,15 @@ namespace Avalonia.OpenGL.Egl
Monitor.Exit(_lock);
}
}
public void NotifyContextLost()
{
_isLost = true;
_disp.OnContextLost(this);
}
public bool IsLost => _isLost || _disp.IsLost || Context == IntPtr.Zero;
public IDisposable EnsureCurrent()
{
if(IsCurrent)
@ -110,12 +141,32 @@ namespace Avalonia.OpenGL.Egl
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
public bool CanCreateSharedContext => _disp.SupportsSharing;
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
_disp.CreateContext(new EglContextOptions
{
ShareWith = _sharedWith ?? this
});
public bool IsCurrent => _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == Context;
public void Dispose()
{
if(_context == IntPtr.Zero)
return;
_egl.DestroyContext(_disp.Handle, Context);
OffscreenSurface?.Dispose();
_context = IntPtr.Zero;
_disp.OnContextDisposed(this);
_disposeCallback?.Invoke();
}
public object TryGetFeature(Type featureType)
{
if (_features?.TryGetValue(featureType, out var feature) == true)
return feature;
return null;
}
}
}

273
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@ -1,193 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
{
public class EglDisplay
public class EglDisplay : IDisposable
{
private readonly EglInterface _egl;
private IntPtr _display;
private readonly EglDisplayOptions _options;
private EglConfigInfo _config;
private bool _isLost;
private object _lock = new();
public bool SupportsSharing { get; }
private readonly IntPtr _display;
private readonly IntPtr _config;
private readonly int[] _contextAttributes;
private readonly int _surfaceType;
public IntPtr Handle => _display;
public IntPtr Config => _config;
private int _sampleCount;
private int _stencilSize;
private GlVersion _version;
public EglDisplay(EglInterface egl, bool supportsSharing) : this(egl, supportsSharing, -1, IntPtr.Zero, null)
public IntPtr Config => _config.Config;
internal bool SingleContext => !_options.SupportsMultipleContexts;
private List<EglContext> _contexts = new();
public EglDisplay() : this(new EglDisplayCreationOptions
{
}
static IntPtr CreateDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
Egl = new EglInterface()
})
{
var display = IntPtr.Zero;
if (platformType == -1 && platformDisplay == IntPtr.Zero)
{
if (display == IntPtr.Zero)
display = egl.GetDisplay(IntPtr.Zero);
}
else
{
if (!egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
display = egl.GetPlatformDisplayExt(platformType, platformDisplay, attrs);
}
if (display == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglGetDisplay", egl);
return display;
}
public EglDisplay(EglInterface egl, bool supportsSharing, int platformType, IntPtr platformDisplay, int[] attrs)
: this(egl, supportsSharing, CreateDisplay(egl, platformType, platformDisplay, attrs))
public EglDisplay(EglDisplayCreationOptions options) : this(EglDisplayUtils.CreateDisplay(options), options)
{
}
public EglDisplay(EglInterface egl, bool supportsSharing, IntPtr display)
public EglDisplay(IntPtr display, EglDisplayOptions options)
{
_egl = egl;
SupportsSharing = supportsSharing;
_egl = options.Egl;
SupportsSharing = options.SupportsContextSharing;
_display = display;
_options = options;
if(_display == IntPtr.Zero)
throw new ArgumentException();
if (!_egl.Initialize(_display, out var major, out var minor))
throw OpenGlException.GetFormattedException("eglInitialize", _egl);
var glProfiles = AvaloniaLocator.Current.GetService<AngleOptions>()?.GlProfiles
?? new[]
{
new GlVersion(GlProfileType.OpenGLES, 3, 0),
new GlVersion(GlProfileType.OpenGLES, 2, 0)
};
var cfgs = glProfiles.Select(x =>
_config = EglDisplayUtils.InitializeAndGetConfig(_egl, display, options.GlVersions);
}
public EglInterface EglInterface => _egl;
public EglContext CreateContext(EglContextOptions options)
{
options ??= new EglContextOptions();
lock (_lock)
{
var typeBit = EGL_OPENGL_ES3_BIT;
var share = options.ShareWith;
if (share != null && !SupportsSharing)
throw new NotSupportedException("Context sharing is not supported by this display");
switch (x.Major)
{
case 2:
typeBit = EGL_OPENGL_ES2_BIT;
break;
var offscreenSurface = options.OffscreenSurface;
case 1:
typeBit = EGL_OPENGL_ES_BIT;
break;
}
return new
if (offscreenSurface == null)
{
Attributes = new[]
// Check if eglMakeCurrent can work with EGL_NONE as read-write surfaces
var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS);
if (extensions?.Contains("EGL_KHR_surfaceless_context") != true)
{
EGL_CONTEXT_MAJOR_VERSION, x.Major,
EGL_CONTEXT_MINOR_VERSION, x.Minor,
EGL_NONE
},
Api = EGL_OPENGL_ES_API,
RenderableTypeBit = typeBit,
Version = x
};
});
foreach (var cfg in cfgs)
{
if (!_egl.BindApi(cfg.Api))
continue;
foreach(var surfaceType in new[]{EGL_PBUFFER_BIT|EGL_WINDOW_BIT, EGL_WINDOW_BIT})
foreach(var stencilSize in new[]{8, 1, 0})
foreach (var depthSize in new []{8, 1, 0})
// Attempt to create a PBuffer as a surface for offscreen rendering
if ((_config.SurfaceType | EGL_PBUFFER_BIT) == 0)
throw new InvalidOperationException(
"Platform doesn't support EGL_KHR_surfaceless_context and PBUFFER surfaces");
var pBufferSurface = _egl.CreatePBufferSurface(_display, Config,
new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
if (pBufferSurface == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
offscreenSurface = new EglSurface(this, pBufferSurface);
}
}
var ctx = _egl.CreateContext(_display, Config, share?.Context ?? IntPtr.Zero, _config.Attributes);
if (ctx == IntPtr.Zero)
{
var attribs = new[]
{
EGL_SURFACE_TYPE, surfaceType,
EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_STENCIL_SIZE, stencilSize,
EGL_DEPTH_SIZE, depthSize,
EGL_NONE
};
if (!_egl.ChooseConfig(_display, attribs, out _config, 1, out int numConfigs))
continue;
if (numConfigs == 0)
continue;
_contextAttributes = cfg.Attributes;
_surfaceType = surfaceType;
_version = cfg.Version;
_egl.GetConfigAttrib(_display, _config, EGL_SAMPLES, out _sampleCount);
_egl.GetConfigAttrib(_display, _config, EGL_STENCIL_SIZE, out _stencilSize);
goto Found;
var ex = OpenGlException.GetFormattedException("eglCreateContext", _egl);
offscreenSurface?.Dispose();
throw ex;
}
var rv = new EglContext(this, _egl, share, ctx, offscreenSurface,
_config.Version, _config.SampleCount, _config.StencilSize,
options.DisposeCallback, options.ExtraFeatures);
_contexts.Add(rv);
return rv;
}
Found:
if (_contextAttributes == null)
throw new OpenGlException("No suitable EGL config was found");
}
public EglDisplay() : this(false)
public EglSurface CreateWindowSurface(IntPtr window)
{
if (window == IntPtr.Zero)
throw new OpenGlException($"Window {window} is invalid.");
using (Lock())
{
var s = EglInterface.CreateWindowSurface(Handle, Config, window,
new[] { EGL_NONE, EGL_NONE });
if (s == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", EglInterface);
return new EglSurface(this, s);
}
}
public EglDisplay(bool supportsSharing) : this(new EglInterface(), supportsSharing)
public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs)
{
using (Lock())
{
var s = EglInterface.CreatePbufferFromClientBuffer(Handle, bufferType, handle,
Config, attribs);
if (s == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", EglInterface);
return new EglSurface(this, s);
}
}
protected virtual bool DisplayLockIsSharedWithContexts => false;
public EglInterface EglInterface => _egl;
public EglContext CreateContext(IGlContext share)
internal object ContextSharedSyncRoot => DisplayLockIsSharedWithContexts ? _lock : null;
internal void OnContextLost(EglContext context)
{
if (share != null && !SupportsSharing)
throw new NotSupportedException("Context sharing is not supported by this display");
if((_surfaceType|EGL_PBUFFER_BIT) == 0)
throw new InvalidOperationException("Platform doesn't support PBUFFER surfaces");
var shareCtx = (EglContext)share;
var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
var extensions = _egl.QueryString(Handle, EGL_EXTENSIONS);
IntPtr surf = IntPtr.Zero;
if (extensions?.Contains("EGL_KHR_surfaceless_context") != true)
if (_options.ContextLossIsDisplayLoss)
_isLost = true;
}
internal void OnContextDisposed(EglContext context)
{
lock (_lock)
_contexts.Remove(context);
}
public bool IsLost
{
get
{
surf = _egl.CreatePBufferSurface(_display, _config,
new[] { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE });
if (surf == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
if (_isLost || _display == IntPtr.Zero)
return true;
if (_options.DeviceLostCheckCallback?.Invoke() == true)
return _isLost = true;
return false;
}
var rv = new EglContext(this, _egl, shareCtx, ctx,
context =>
surf == IntPtr.Zero ? null : new EglSurface(this, context, surf),
_version, _sampleCount, _stencilSize);
return rv;
}
public IDisposable Lock()
{
Monitor.Enter(_lock);
return Disposable.Create(() => { Monitor.Exit(_lock); });
}
public EglContext CreateContext(EglContext share, EglSurface offscreenSurface)
public void Dispose()
{
if (share != null && !SupportsSharing)
throw new NotSupportedException("Context sharing is not supported by this display");
var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
var rv = new EglContext(this, _egl, share, ctx, _ => offscreenSurface, _version, _sampleCount, _stencilSize);
rv.MakeCurrent(null);
return rv;
lock (_lock)
{
foreach(var ctx in _contexts)
ctx.Dispose();
_contexts.Clear();
if (_display != IntPtr.Zero)
_egl.Terminate(_display);
_display = IntPtr.Zero;
_config = null;
_options.DisposeCallback?.Invoke();
}
}
}
}

30
src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace Avalonia.OpenGL.Egl;
public class EglDisplayOptions
{
public EglInterface Egl { get; set; }
public bool SupportsContextSharing { get; set; }
public bool SupportsMultipleContexts { get; set; }
public bool ContextLossIsDisplayLoss { get; set; }
public Func<bool> DeviceLostCheckCallback { get; set; }
public Action DisposeCallback { get; set; }
public IEnumerable<GlVersion> GlVersions { get; set; }
}
public class EglContextOptions
{
public EglContext ShareWith { get; set; }
public EglSurface OffscreenSurface { get; set; }
public Action DisposeCallback { get; set; }
public Dictionary<Type, object> ExtraFeatures { get; set; }
}
public class EglDisplayCreationOptions : EglDisplayOptions
{
public int? PlatformType { get; set; }
public IntPtr PlatformDisplay { get; set; }
public int[] PlatformDisplayAttrs { get; set; }
}

135
src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs

@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl;
static class EglDisplayUtils
{
public static IntPtr CreateDisplay(EglDisplayCreationOptions options)
{
var egl = options.Egl;
var display = IntPtr.Zero;
if (options.PlatformType == null)
{
if (display == IntPtr.Zero)
display = egl.GetDisplay(IntPtr.Zero);
}
else
{
if (!egl.IsGetPlatformDisplayExtAvailable)
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
display = egl.GetPlatformDisplayExt(options.PlatformType.Value, options.PlatformDisplay,
options.PlatformDisplayAttrs);
}
if (display == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglGetDisplay", egl);
return display;
}
public static EglConfigInfo InitializeAndGetConfig(EglInterface egl, IntPtr display, IEnumerable<GlVersion> versions)
{
if (!egl.Initialize(display, out var major, out var minor))
throw OpenGlException.GetFormattedException("eglInitialize", egl);
// TODO: AvaloniaLocator.Current.GetService<AngleOptions>()?.GlProfiles
versions ??= new[]
{
new GlVersion(GlProfileType.OpenGLES, 3, 0),
new GlVersion(GlProfileType.OpenGLES, 2, 0)
};
var cfgs = versions
.Where(x => x.Type == GlProfileType.OpenGLES)
.Select(x =>
{
var typeBit = EGL_OPENGL_ES3_BIT;
switch (x.Major)
{
case 2:
typeBit = EGL_OPENGL_ES2_BIT;
break;
case 1:
typeBit = EGL_OPENGL_ES_BIT;
break;
}
return new
{
Attributes = new[]
{
EGL_CONTEXT_MAJOR_VERSION, x.Major,
EGL_CONTEXT_MINOR_VERSION, x.Minor,
EGL_NONE
},
Api = EGL_OPENGL_ES_API,
RenderableTypeBit = typeBit,
Version = x
};
});
foreach (var cfg in cfgs)
{
if (!egl.BindApi(cfg.Api))
continue;
foreach (var surfaceType in new[] { EGL_PBUFFER_BIT | EGL_WINDOW_BIT, EGL_WINDOW_BIT })
foreach (var stencilSize in new[] { 8, 1, 0 })
foreach (var depthSize in new[] { 8, 1, 0 })
{
var attribs = new[]
{
EGL_SURFACE_TYPE, surfaceType,
EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_STENCIL_SIZE, stencilSize,
EGL_DEPTH_SIZE, depthSize,
EGL_NONE
};
if (!egl.ChooseConfig(display, attribs, out var config, 1, out int numConfigs))
continue;
if (numConfigs == 0)
continue;
egl.GetConfigAttrib(display, config, EGL_SAMPLES, out var sampleCount);
egl.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, out var returnedStencilSize);
return new EglConfigInfo(config, cfg.Version, surfaceType, cfg.Attributes, sampleCount,
returnedStencilSize);
}
}
throw new OpenGlException("No suitable EGL config was found");
}
}
class EglConfigInfo
{
public IntPtr Config { get; }
public GlVersion Version { get; }
public int SurfaceType { get; }
public int[] Attributes { get; }
public int SampleCount { get; }
public int StencilSize { get; }
public EglConfigInfo(IntPtr config, GlVersion version, int surfaceType, int[] attributes, int sampleCount,
int stencilSize)
{
Config = config;
Version = version;
SurfaceType = surfaceType;
Attributes = attributes;
SampleCount = sampleCount;
StencilSize = stencilSize;
}
}

39
src/Avalonia.OpenGL/Egl/EglGlPlatformSurface.cs

@ -1,52 +1,61 @@
using System;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.OpenGL.Egl
{
public class EglGlPlatformSurface : EglGlPlatformSurfaceBase
{
private readonly EglPlatformOpenGlInterface _egl;
public interface IEglWindowGlPlatformSurfaceInfo
{
IntPtr Handle { get; }
PixelSize Size { get; }
double Scaling { get; }
}
private readonly IEglWindowGlPlatformSurfaceInfo _info;
public EglGlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info) : base()
public EglGlPlatformSurface(IEglWindowGlPlatformSurfaceInfo info)
{
_egl = egl;
_info = info;
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
var glSurface = _egl.CreateWindowSurface(_info.Handle);
return new RenderTarget(_egl, glSurface, _info);
var eglContext = (EglContext)context;
var glSurface = eglContext.Display.CreateWindowSurface(_info.Handle);
return new RenderTarget(glSurface, eglContext, _info);
}
class RenderTarget : EglPlatformSurfaceRenderTargetBase
{
private readonly EglPlatformOpenGlInterface _egl;
private EglSurface _glSurface;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private PixelSize _currentSize;
private readonly IntPtr _handle;
public RenderTarget(EglPlatformOpenGlInterface egl,
EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info) : base(egl)
public RenderTarget(EglSurface glSurface, EglContext context, IEglWindowGlPlatformSurfaceInfo info) : base(context)
{
_egl = egl;
_glSurface = glSurface;
_info = info;
_currentSize = info.Size;
_handle = _info.Handle;
}
public override void Dispose() => _glSurface.Dispose();
public override IGlPlatformSurfaceRenderingSession BeginDraw()
public override IGlPlatformSurfaceRenderingSession BeginDrawCore()
{
if (_info.Size != _currentSize || _glSurface == null)
if (_info.Size != _currentSize
|| _handle != _info.Handle
|| _glSurface == null)
{
_glSurface?.Dispose();
_glSurface = null;
_glSurface = _egl.CreateWindowSurface(_info.Handle);
_glSurface = Context.Display.CreateWindowSurface(_info.Handle);
_currentSize = _info.Size;
}
return base.BeginDraw(_glSurface, _info);
return base.BeginDraw(_glSurface, _info.Size, _info.Scaling);
}
}
}

49
src/Avalonia.OpenGL/Egl/EglGlPlatformSurfaceBase.cs

@ -5,23 +5,16 @@ namespace Avalonia.OpenGL.Egl
{
public abstract class EglGlPlatformSurfaceBase : IGlPlatformSurface
{
public interface IEglWindowGlPlatformSurfaceInfo
{
IntPtr Handle { get; }
PixelSize Size { get; }
double Scaling { get; }
}
public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget();
public abstract IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context);
}
public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTarget
public abstract class EglPlatformSurfaceRenderTargetBase : IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{
private readonly EglPlatformOpenGlInterface _egl;
protected EglContext Context { get; }
protected EglPlatformSurfaceRenderTargetBase(EglPlatformOpenGlInterface egl)
protected EglPlatformSurfaceRenderTargetBase(EglContext context)
{
_egl = egl;
Context = context;
}
public virtual void Dispose()
@ -29,25 +22,33 @@ namespace Avalonia.OpenGL.Egl
}
public abstract IGlPlatformSurfaceRenderingSession BeginDraw();
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
if (Context.IsLost)
throw new RenderTargetCorruptedException();
return BeginDrawCore();
}
public abstract IGlPlatformSurfaceRenderingSession BeginDrawCore();
protected IGlPlatformSurfaceRenderingSession BeginDraw(EglSurface surface,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, Action onFinish = null, bool isYFlipped = false)
PixelSize size, double scaling, Action onFinish = null, bool isYFlipped = false)
{
var restoreContext = _egl.PrimaryEglContext.MakeCurrent(surface);
var restoreContext = Context.MakeCurrent(surface);
var success = false;
try
{
var egli = _egl.Display.EglInterface;
var egli = Context.Display.EglInterface;
egli.WaitClient();
egli.WaitGL();
egli.WaitNative(EglConsts.EGL_CORE_NATIVE_ENGINE);
_egl.PrimaryContext.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0);
Context.GlInterface.BindFramebuffer(GlConsts.GL_FRAMEBUFFER, 0);
success = true;
return new Session(_egl.Display, _egl.PrimaryEglContext, surface, info, restoreContext, onFinish, isYFlipped);
return new Session(Context.Display, Context, surface, size, scaling, restoreContext, onFinish, isYFlipped);
}
finally
{
@ -60,21 +61,21 @@ namespace Avalonia.OpenGL.Egl
{
private readonly EglContext _context;
private readonly EglSurface _glSurface;
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglDisplay _display;
private readonly IDisposable _restoreContext;
private readonly Action _onFinish;
public Session(EglDisplay display, EglContext context,
EglSurface glSurface, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
EglSurface glSurface, PixelSize size, double scaling,
IDisposable restoreContext, Action onFinish, bool isYFlipped)
{
Size = size;
Scaling = scaling;
IsYFlipped = isYFlipped;
_context = context;
_display = display;
_glSurface = glSurface;
_info = info;
_restoreContext = restoreContext;
_onFinish = onFinish;
}
@ -92,9 +93,11 @@ namespace Avalonia.OpenGL.Egl
}
public IGlContext Context => _context;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
public PixelSize Size { get; }
public double Scaling { get; }
public bool IsYFlipped { get; }
}
public virtual bool IsCorrupted => Context.IsLost;
}
}

3
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -53,6 +53,9 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglInitialize")]
public partial bool Initialize(IntPtr display, out int major, out int minor);
[GetProcAddress("eglTerminate")]
public partial void Terminate(IntPtr display);
[GetProcAddress("eglGetProcAddress")]
public partial IntPtr GetProcAddress(IntPtr proc);

48
src/Avalonia.OpenGL/Egl/EglPlatformGraphics.cs

@ -0,0 +1,48 @@
using System;
using Avalonia.Logging;
using Avalonia.Platform;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
{
public sealed class EglPlatformGraphics : IPlatformGraphics
{
private readonly EglDisplay _display;
public bool UsesSharedContext => false;
public IPlatformGraphicsContext CreateContext() => _display.CreateContext(null);
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
public EglPlatformGraphics(EglDisplay display)
{
_display = display;
}
public static void TryInitialize()
{
var feature = TryCreate();
if (feature != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
}
public static EglPlatformGraphics TryCreate() => TryCreate(() => new EglDisplay(new EglDisplayCreationOptions
{
Egl = new EglInterface(),
// Those are expected to be supported by most EGL implementations
SupportsMultipleContexts = true,
SupportsContextSharing = true
}));
public static EglPlatformGraphics TryCreate(Func<EglDisplay> displayFactory)
{
try
{
return new EglPlatformGraphics(displayFactory());
}
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e);
return null;
}
}
}
}

77
src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs

@ -1,77 +0,0 @@
using System;
using Avalonia.Logging;
using Avalonia.Platform;
using static Avalonia.OpenGL.Egl.EglConsts;
namespace Avalonia.OpenGL.Egl
{
public class EglPlatformOpenGlInterface : IPlatformOpenGlInterface
{
public EglDisplay Display { get; private set; }
public bool CanCreateContexts => true;
public bool CanShareContexts => Display.SupportsSharing;
public EglContext PrimaryEglContext { get; }
public IGlContext PrimaryContext => PrimaryEglContext;
IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
public EglPlatformOpenGlInterface(EglDisplay display)
{
Display = display;
PrimaryEglContext = display.CreateContext(null);
}
public static void TryInitialize()
{
var feature = TryCreate();
if (feature != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToConstant(feature);
}
public static EglPlatformOpenGlInterface TryCreate() => TryCreate(() => new EglDisplay());
public static EglPlatformOpenGlInterface TryCreate(Func<EglDisplay> displayFactory)
{
try
{
return new EglPlatformOpenGlInterface(displayFactory());
}
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(null, "Unable to initialize EGL-based rendering: {0}", e);
return null;
}
}
public IGlContext CreateContext() => Display.CreateContext(null);
public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryEglContext);
public EglSurface CreateWindowSurface(IntPtr window)
{
if (window == IntPtr.Zero)
throw new OpenGlException($"Window {window} is invalid.");
using (PrimaryContext.MakeCurrent())
{
var s = Display.EglInterface.CreateWindowSurface(Display.Handle, Display.Config, window,
new[] { EGL_NONE, EGL_NONE });
if (s == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", Display.EglInterface);
return new EglSurface(Display, PrimaryEglContext, s);
}
}
public EglSurface CreatePBufferFromClientBuffer (int bufferType, IntPtr handle, int[] attribs)
{
using (PrimaryContext.MakeCurrent())
{
var s = Display.EglInterface.CreatePbufferFromClientBuffer(Display.Handle, bufferType, handle,
Display.Config, attribs);
if (s == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglCreatePbufferFromClientBuffer", Display.EglInterface);
return new EglSurface(Display, PrimaryEglContext, s);
}
}
}
}

6
src/Avalonia.OpenGL/Egl/EglSurface.cs

@ -6,19 +6,17 @@ namespace Avalonia.OpenGL.Egl
public class EglSurface : SafeHandle
{
private readonly EglDisplay _display;
private readonly EglContext _context;
private readonly EglInterface _egl;
public EglSurface(EglDisplay display, EglContext context, IntPtr surface) : base(surface, true)
public EglSurface(EglDisplay display, IntPtr surface) : base(surface, true)
{
_display = display;
_context = context;
_egl = display.EglInterface;
}
protected override bool ReleaseHandle()
{
using (_context.MakeCurrent())
using (_display.Lock())
_egl.DestroySurface(_display.Handle, handle);
return true;
}

12
src/Avalonia.OpenGL/IGlContext.cs

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
namespace Avalonia.OpenGL
{
public interface IGlContext : IPlatformGpuContext
public interface IGlContext : IPlatformGraphicsContext
{
GlVersion Version { get; }
GlInterface GlInterface { get; }
@ -12,5 +14,13 @@ namespace Avalonia.OpenGL
IDisposable MakeCurrent();
IDisposable EnsureCurrent();
bool IsSharedWith(IGlContext context);
bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null);
}
public interface IGlPlatformSurfaceRenderTargetFactory
{
bool CanRenderToSurface(IGlContext context, object surface);
IGlPlatformSurfaceRenderTarget CreateRenderTarget(IGlContext context, object surface);
}
}

9
src/Avalonia.OpenGL/IOpenGlAwarePlatformRenderInterface.cs

@ -1,9 +0,0 @@
using Avalonia.OpenGL.Imaging;
namespace Avalonia.OpenGL
{
public interface IOpenGlAwarePlatformRenderInterface
{
IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
}
}

13
src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs

@ -0,0 +1,13 @@
using System.Collections.Generic;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
namespace Avalonia.OpenGL
{
public interface IOpenGlTextureSharingRenderInterfaceContextFeature
{
bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null);
IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
}
}

15
src/Avalonia.OpenGL/IPlatformOpenGlInterface.cs

@ -1,15 +0,0 @@
using Avalonia.Platform;
namespace Avalonia.OpenGL
{
public interface IPlatformOpenGlInterface : IPlatformGpu
{
new IGlContext PrimaryContext { get; }
IGlContext CreateSharedContext();
bool CanShareContexts { get; }
bool CanCreateContexts { get; }
IGlContext CreateContext();
/*IGlContext TryCreateContext(GlVersion version);
*/
}
}

16
src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs

@ -10,19 +10,15 @@ namespace Avalonia.OpenGL.Imaging
{
private IOpenGlBitmapImpl _impl;
public OpenGlBitmap(PixelSize size, Vector dpi)
: base(CreateOrThrow(size, dpi))
public OpenGlBitmap(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
PixelSize size, Vector dpi)
: base(CreateOrThrow(feature, size, dpi))
{
_impl = (IOpenGlBitmapImpl)PlatformImpl.Item;
}
static IOpenGlBitmapImpl CreateOrThrow(PixelSize size, Vector dpi)
{
if (!(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() is IOpenGlAwarePlatformRenderInterface
glAware))
throw new PlatformNotSupportedException("Rendering platform does not support OpenGL integration");
return glAware.CreateOpenGlBitmap(size, dpi);
}
static IOpenGlBitmapImpl CreateOrThrow(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
PixelSize size, Vector dpi) => feature.CreateOpenGlBitmap(size, dpi);
public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) =>
_impl.CreateFramebufferAttachment(context, SetIsDirty);

11
src/Avalonia.OpenGL/OpenGlException.cs

@ -18,19 +18,22 @@ namespace Avalonia.OpenGL
public static OpenGlException GetFormattedException(string funcName, EglInterface egl)
{
return GetFormattedException(typeof(EglErrors), funcName, egl.GetError());
return GetFormattedException<EglErrors>(funcName, egl.GetError());
}
public static OpenGlException GetFormattedException(string funcName, GlInterface gl)
{
return GetFormattedException(typeof(GlErrors), funcName, gl.GetError());
return GetFormattedException<GlErrors>(funcName, gl.GetError());
}
private static OpenGlException GetFormattedException(Type consts, string funcName, int errorCode)
public static OpenGlException GetFormattedEglException(string funcName, int errorCode) =>
GetFormattedException<EglErrors>(funcName, errorCode);
private static OpenGlException GetFormattedException<T>(string funcName, int errorCode)
{
try
{
string errorName = Enum.GetName(consts, errorCode);
string errorName = Enum.GetName(typeof(T), errorCode);
return new OpenGlException(
$"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", errorCode);
}

2
src/Avalonia.OpenGL/Surfaces/IGlPlatformSurface.cs

@ -2,6 +2,6 @@ namespace Avalonia.OpenGL.Surfaces
{
public interface IGlPlatformSurface
{
IGlPlatformSurfaceRenderTarget CreateGlRenderTarget();
IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context);
}
}

10
src/Avalonia.X11/Glx/GlxContext.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.OpenGL;
@ -70,6 +71,8 @@ namespace Avalonia.X11.Glx
}
public IDisposable MakeCurrent() => MakeCurrent(_defaultXid);
public bool IsLost => false;
public IDisposable EnsureCurrent()
{
if(IsCurrent)
@ -86,6 +89,11 @@ namespace Avalonia.X11.Glx
|| _sharedWith != null && _sharedWith == c._sharedWith;
}
public bool CanCreateSharedContext => true;
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
Display.CreateContext(_sharedWith ?? this);
public IDisposable MakeCurrent(IntPtr xid)
{
Monitor.Enter(_lock);
@ -114,5 +122,7 @@ namespace Avalonia.X11.Glx
if (_ownsPBuffer)
Glx.DestroyPbuffer(_x11.Display, _defaultXid);
}
public object TryGetFeature(Type featureType) => null;
}
}

4
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -111,8 +111,8 @@ namespace Avalonia.X11.Glx
return Glx.CreatePbuffer(_x11.Display, _fbconfig, new[] { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 });
}
public GlxContext CreateContext() => CreateContext();
public GlxContext CreateContext() => CreateContext(CreatePBuffer(), null, DeferredContext.SampleCount,
DeferredContext.StencilSize, true);
public GlxContext CreateContext(IGlContext share) => CreateContext(CreatePBuffer(), share,
share.SampleCount, share.StencilSize, true);

10
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -9,20 +9,16 @@ namespace Avalonia.X11.Glx
class GlxGlPlatformSurface: IGlPlatformSurface
{
private readonly GlxDisplay _display;
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
public GlxGlPlatformSurface(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_display = display;
_context = context;
_info = info;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
return new RenderTarget(_context, _info);
return new RenderTarget((GlxContext)context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget

20
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -6,38 +6,36 @@ using Avalonia.Platform;
namespace Avalonia.X11.Glx
{
class GlxPlatformOpenGlInterface : IPlatformOpenGlInterface
class GlxPlatformGraphics : IPlatformGraphics
{
public GlxDisplay Display { get; private set; }
public bool CanCreateContexts => true;
public bool CanShareContexts => true;
public IGlContext CreateContext() => Display.CreateContext();
public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryContext);
public GlxContext DeferredContext { get; private set; }
public IGlContext PrimaryContext => DeferredContext;
IPlatformGpuContext IPlatformGpu.PrimaryContext => PrimaryContext;
public bool UsesSharedContext => false;
IPlatformGraphicsContext IPlatformGraphics.CreateContext() => Display.CreateContext();
public IPlatformGraphicsContext GetSharedContext() => throw new NotSupportedException();
public static bool TryInitialize(X11Info x11, IList<GlVersion> glProfiles)
{
var feature = TryCreate(x11, glProfiles);
if (feature != null)
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToConstant(feature);
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(feature);
return true;
}
return false;
}
public static GlxPlatformOpenGlInterface TryCreate(X11Info x11, IList<GlVersion> glProfiles)
public static GlxPlatformGraphics TryCreate(X11Info x11, IList<GlVersion> glProfiles)
{
try
{
var disp = new GlxDisplay(x11, glProfiles);
return new GlxPlatformOpenGlInterface
return new GlxPlatformGraphics
{
Display = disp,
DeferredContext = disp.DeferredContext
Display = disp
};
}
catch(Exception e)

3
src/Avalonia.X11/X11CursorFactory.cs

@ -113,7 +113,8 @@ namespace Avalonia.X11
image->yhot = hotSpot.Y;
image->pixels = (IntPtr)(image + 1);
using (var renderTarget = platformRenderInterface.CreateRenderTarget(new[] { this }))
using (var cpuContext = platformRenderInterface.CreateBackendContext(null))
using (var renderTarget = cpuContext.CreateRenderTarget(new[] { this }))
using (var ctx = renderTarget.CreateDrawingContext(null))
{
var r = new Rect(_pixelSize.ToSize(1));

3
src/Avalonia.X11/X11IconLoader.cs

@ -41,7 +41,8 @@ namespace Avalonia.X11
_width = Math.Min(bitmap.PixelSize.Width, 128);
_height = Math.Min(bitmap.PixelSize.Height, 128);
_bdata = new uint[_width * _height];
using(var rt = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[]{this}))
using(var cpuContext = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>().CreateBackendContext(null))
using(var rt = cpuContext.CreateRenderTarget(new[]{this}))
using (var ctx = rt.CreateDrawingContext(null))
ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
new Rect(0, 0, _width, _height));

11
src/Avalonia.X11/X11ImmediateRendererProxy.cs

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
@ -14,11 +16,11 @@ namespace Avalonia.X11
private bool _running;
private object _lock = new object();
public X11ImmediateRendererProxy(Visual root, IRenderLoop loop)
public X11ImmediateRendererProxy(Visual root, IRenderLoop loop, Func<IRenderTarget> renderTargetFactory,
PlatformRenderInterfaceContextManager renderContext)
{
_loop = loop;
_renderer = new ImmediateRenderer(root);
_renderer = new ImmediateRenderer(root, renderTargetFactory, renderContext);
}
public void Dispose()
@ -92,6 +94,9 @@ namespace Avalonia.X11
_renderer.Stop();
}
public ValueTask<object> TryGetRenderInterfaceFeature(Type featureType) =>
_renderer.TryGetRenderInterfaceFeature(featureType);
public bool NeedsUpdate => false;
public void Update(TimeSpan time)
{

11
src/Avalonia.X11/X11Platform.cs

@ -30,6 +30,7 @@ namespace Avalonia.X11
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public Compositor Compositor { get; private set; }
public PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public IntPtr OrphanedWindow { get; private set; }
@ -96,17 +97,19 @@ namespace Avalonia.X11
if (options.UseGpu)
{
if (options.UseEGL)
EglPlatformOpenGlInterface.TryInitialize();
EglPlatformGraphics.TryInitialize();
else
GlxPlatformOpenGlInterface.TryInitialize(Info, Options.GlProfiles);
GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles);
}
var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
var gl = AvaloniaLocator.Current.GetService<IPlatformGraphics>();
if (gl != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformGpu>().ToConstant(gl);
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl);
if (options.UseCompositor)
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IRenderLoop>()!, gl);
else
RenderInterface = new(gl);
}

20
src/Avalonia.X11/X11Window.cs

@ -74,7 +74,7 @@ namespace Avalonia.X11
_touch = new TouchDevice();
_keyboard = platform.KeyboardDevice;
var glfeature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
var glfeature = AvaloniaLocator.Current.GetService<IPlatformGraphics>();
XSetWindowAttributes attr = new XSetWindowAttributes();
var valueMask = default(SetWindowValuemask);
@ -96,13 +96,13 @@ namespace Avalonia.X11
// OpenGL seems to be do weird things to it's current window which breaks resize sometimes
_useRenderWindow = glfeature != null;
var glx = glfeature as GlxPlatformOpenGlInterface;
var glx = glfeature as GlxPlatformGraphics;
if (glx != null)
visualInfo = *glx.Display.VisualInfo;
else if (glfeature == null)
visualInfo = _x11.TransparentVisualInfo;
var egl = glfeature as EglPlatformOpenGlInterface;
var egl = glfeature as EglPlatformGraphics;
var visual = IntPtr.Zero;
var depth = 24;
@ -176,11 +176,9 @@ namespace Avalonia.X11
if (egl != null)
surfaces.Insert(0,
new EglGlPlatformSurface(egl,
new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
new EglGlPlatformSurface(new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
if (glx != null)
surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,
new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
surfaces.Insert(0, new GlxGlPlatformSurface(new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
surfaces.Add(Handle);
@ -391,12 +389,14 @@ namespace Avalonia.X11
return _platform.Options.UseDeferredRendering
? _platform.Options.UseCompositor
? new CompositingRenderer(root, this._platform.Compositor)
: new DeferredRenderer(root, loop)
? new CompositingRenderer(root, this._platform.Compositor, () => Surfaces)
: new DeferredRenderer(root, loop, () => _platform.RenderInterface.CreateRenderTarget(Surfaces), _platform.RenderInterface)
{
RenderOnlyOnRenderThread = true
}
: (IRenderer)new X11ImmediateRendererProxy((Visual)root, loop);
: new X11ImmediateRendererProxy((Visual)root, loop,
() => _platform.RenderInterface.CreateRenderTarget(Surfaces),
_platform.RenderInterface);
}
void OnEvent(ref XEvent ev)

3
src/Browser/Avalonia.Browser/AvaloniaView.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using SkiaSharp;
@ -106,7 +107,7 @@ namespace Avalonia.Browser
_dpi = DomHelper.ObserveDpi(OnDpiChanged);
_useGL = skiaOptions?.CustomGpuFactory != null;
_useGL = AvaloniaLocator.Current.GetRequiredService<IPlatformGraphics>() != null;
if (_useGL)
{

3
src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs

@ -48,7 +48,6 @@ public static class WebAppBuilder
{
return builder
.UseWindowingSubsystem(BrowserWindowingPlatform.Register)
.UseSkia()
.With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() });
.UseSkia();
}
}

3
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -166,7 +166,8 @@ namespace Avalonia.Browser
public IRenderer CreateRenderer(IRenderRoot root)
{
var loop = AvaloniaLocator.Current.GetRequiredService<IRenderLoop>();
return new CompositingRenderer(root, new Compositor(loop, null));
return new CompositingRenderer(root,
new Compositor(loop, AvaloniaLocator.Current.GetRequiredService<IPlatformGraphics>()), () => Surfaces);
}
public void Invalidate(Rect rect)

26
src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Platform;
using Avalonia.Skia;
namespace Avalonia.Browser.Skia
@ -22,5 +25,28 @@ namespace Avalonia.Browser.Skia
{
return null;
}
public void Dispose()
{
}
public object? TryGetFeature(Type t) => null;
public bool IsLost => false;
public IDisposable EnsureCurrent()
{
return Disposable.Empty;
}
}
class BrowserSkiaGraphics : IPlatformGraphics
{
private BrowserSkiaGpu _skia = new();
public bool UsesSharedContext => true;
public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException();
public IPlatformGraphicsContext GetSharedContext() => _skia;
}
}

2
src/Browser/Avalonia.Browser/WindowingPlatform.cs

@ -1,5 +1,6 @@
using System;
using System.Threading;
using Avalonia.Browser.Skia;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
@ -43,6 +44,7 @@ namespace Avalonia.Browser
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(ManualTriggerRenderTimer.Instance)
.Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformGraphics>().ToConstant(new BrowserSkiaGraphics())
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}

4
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -1,4 +1,4 @@
using System;
 using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
@ -33,7 +33,7 @@ namespace Avalonia.LinuxFramebuffer
{
var factory = AvaloniaLocator.Current.GetService<IRendererFactory>();
var renderLoop = AvaloniaLocator.Current.GetService<IRenderLoop>();
return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor);
return factory?.Create(root, renderLoop) ?? new CompositingRenderer(root, LinuxFramebufferPlatform.Compositor, () => Surfaces);
}
public void Dispose()

4
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -41,7 +41,7 @@ namespace Avalonia.LinuxFramebuffer
{
Threading = new InternalPlatformThreadingInterface();
if (_fb is IGlOutputBackend gl)
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToConstant(gl.PlatformOpenGlInterface);
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl.PlatformGraphics);
var opts = AvaloniaLocator.Current.GetService<LinuxFramebufferPlatformOptions>() ?? new LinuxFramebufferPlatformOptions();
@ -56,7 +56,7 @@ namespace Avalonia.LinuxFramebuffer
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
}

50
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using JetBrains.Annotations;
using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
@ -25,10 +26,22 @@ namespace Avalonia.LinuxFramebuffer.Output
get => _outputOptions.Scaling;
set => _outputOptions.Scaling = value;
}
public IGlContext PrimaryContext => _deferredContext;
private EglPlatformOpenGlInterface _platformGl;
public IPlatformOpenGlInterface PlatformOpenGlInterface => _platformGl;
class SharedContextGraphics : IPlatformGraphics
{
private readonly IPlatformGraphicsContext _context;
public SharedContextGraphics(IPlatformGraphicsContext context)
{
_context = context;
}
public bool UsesSharedContext => true;
public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException();
public IPlatformGraphicsContext GetSharedContext() => _context;
}
public IPlatformGraphics PlatformGraphics { get; private set; }
public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo,
DrmOutputOptions? options = null)
@ -161,11 +174,22 @@ namespace Avalonia.LinuxFramebuffer.Output
if(_gbmTargetSurface == IntPtr.Zero)
throw new InvalidOperationException("Unable to create GBM surface");
_eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), false, 0x31D7, device, null);
_platformGl = new EglPlatformOpenGlInterface(_eglDisplay);
_eglSurface = _platformGl.CreateWindowSurface(_gbmTargetSurface);
_eglDisplay = new EglDisplay(
new EglDisplayCreationOptions
{
Egl = new EglInterface(eglGetProcAddress),
PlatformType = 0x31D7,
PlatformDisplay = device,
SupportsMultipleContexts = true,
SupportsContextSharing = true
});
var surface = _eglDisplay.EglInterface.CreateWindowSurface(_eglDisplay.Handle, _eglDisplay.Config, _gbmTargetSurface, new[] { EglConsts.EGL_NONE, EglConsts.EGL_NONE });
_eglSurface = new EglSurface(_eglDisplay, surface);
_deferredContext = _platformGl.PrimaryEglContext;
_deferredContext = _eglDisplay.CreateContext(null);
PlatformGraphics = new SharedContextGraphics(_deferredContext);
var initialBufferSwappingColorR = _outputOptions.InitialBufferSwappingColor.R / 255.0f;
var initialBufferSwappingColorG = _outputOptions.InitialBufferSwappingColor.G / 255.0f;
@ -206,11 +230,17 @@ namespace Avalonia.LinuxFramebuffer.Output
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => new RenderTarget(this);
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
return new RenderTarget(this);
if (context != _deferredContext)
throw new InvalidOperationException(
"This platform backend can only create render targets for its primary context");
return CreateGlRenderTarget();
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly DrmOutput _parent;

3
src/Linux/Avalonia.LinuxFramebuffer/Output/IGlOutputBackend.cs

@ -1,9 +1,10 @@
using Avalonia.OpenGL;
using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer.Output
{
public interface IGlOutputBackend : IOutputBackend
{
public IPlatformOpenGlInterface PlatformOpenGlInterface { get; }
public IPlatformGraphics PlatformGraphics { get; }
}
}

2
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -61,6 +61,8 @@ namespace Avalonia.Skia
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);
}
public bool IsCorrupted => false;
/// <summary>
/// Check if two images info are compatible.
/// </summary>

4
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
@ -8,7 +10,7 @@ namespace Avalonia.Skia
/// <summary>
/// Custom Skia gpu instance.
/// </summary>
public interface ISkiaGpu
public interface ISkiaGpu : IPlatformGraphicsContext
{
/// <summary>
/// Attempts to create custom render target from given surfaces.

5
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs

@ -14,10 +14,11 @@ namespace Avalonia.Skia
private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface;
public GlRenderTarget(GRContext grContext, IGlPlatformSurface glSurface)
public GlRenderTarget(GRContext grContext, IGlContext glContext, IGlPlatformSurface glSurface)
{
_grContext = grContext;
_surface = glSurface.CreateGlRenderTarget();
using (glContext.EnsureCurrent())
_surface = glSurface.CreateGlRenderTarget(glContext);
}
public void Dispose() => _surface.Dispose();

55
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -5,21 +5,21 @@ using Avalonia.Logging;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
{
class GlSkiaGpu : IOpenGlAwareSkiaGpu
class GlSkiaGpu : IOpenGlAwareSkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
{
private GRContext _grContext;
private IGlContext _glContext;
private bool? _canCreateSurfaces;
public GlSkiaGpu(IPlatformOpenGlInterface openGl, long? maxResourceBytes)
public GlSkiaGpu(IGlContext context, long? maxResourceBytes)
{
var context = openGl.PrimaryContext;
_glContext = context;
using (context.MakeCurrent())
using (_glContext.EnsureCurrent())
{
using (var iface = context.Version.Type == GlProfileType.OpenGL ?
GRGlInterface.CreateOpenGl(proc => context.GlInterface.GetProcAddress(proc)) :
@ -34,13 +34,34 @@ namespace Avalonia.Skia
}
}
class SurfaceWrapper : IGlPlatformSurface
{
private readonly object _surface;
public SurfaceWrapper( object surface)
{
_surface = surface;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
var feature = context.TryGetFeature<IGlPlatformSurfaceRenderTargetFactory>()!;
return feature.CreateRenderTarget(context, _surface);
}
}
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
{
var customRenderTargetFactory = _glContext.TryGetFeature<IGlPlatformSurfaceRenderTargetFactory>();
foreach (var surface in surfaces)
{
if (customRenderTargetFactory?.CanRenderToSurface(_glContext, surface) == true)
{
return new GlRenderTarget(_grContext, _glContext, new SurfaceWrapper(surface));
}
if (surface is IGlPlatformSurface glSurface)
{
return new GlRenderTarget(_grContext, glSurface);
return new GlRenderTarget(_grContext, _glContext, glSurface);
}
}
@ -75,6 +96,30 @@ namespace Avalonia.Skia
}
}
public bool CanCreateSharedContext => _glContext.CanCreateSharedContext;
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
_glContext.CreateSharedContext(preferredVersions);
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi);
public void Dispose()
{
if (_glContext.IsLost)
_grContext.AbandonContext();
else
_grContext.AbandonContext(true);
_grContext.Dispose();
}
public bool IsLost => _glContext.IsLost;
public IDisposable EnsureCurrent() => _glContext.EnsureCurrent();
public object TryGetFeature(Type featureType)
{
if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature))
return this;
return null;
}
}
}

6
src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs

@ -109,7 +109,9 @@ namespace Avalonia.Skia
using (_context.EnsureCurrent())
{
var glVersion = _context.Version;
InternalFormat = glVersion.Type == GlProfileType.OpenGLES ? GL_RGBA : GL_RGBA8;
InternalFormat = glVersion.Type == GlProfileType.OpenGLES && glVersion.Major == 2
? GL_RGBA
: GL_RGBA8;
_context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo);
if (_fbo == 0)
@ -145,7 +147,7 @@ namespace Avalonia.Skia
public void Present()
{
using (_context.MakeCurrent())
using (_context.EnsureCurrent())
{
if (_disposed)
throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment));

2
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -6,7 +6,7 @@ namespace Avalonia.Skia
/// <summary>
/// Adapts <see cref="ISkiaGpuRenderTarget"/> to be used within our rendering pipeline.
/// </summary>
internal class SkiaGpuRenderTarget : IRenderTargetWithCorruptionInfo
internal class SkiaGpuRenderTarget : IRenderTarget
{
private readonly ISkiaGpu _skiaGpu;
private readonly ISkiaGpuRenderTarget _renderTarget;

57
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -18,26 +18,28 @@ namespace Avalonia.Skia
/// <summary>
/// Skia platform render interface.
/// </summary>
internal class PlatformRenderInterface : IPlatformRenderInterface, IOpenGlAwarePlatformRenderInterface
internal class PlatformRenderInterface : IPlatformRenderInterface
{
private readonly ISkiaGpu _skiaGpu;
private readonly long? _maxResourceBytes;
public PlatformRenderInterface(ISkiaGpu skiaGpu, long? maxResourceBytes = null)
public PlatformRenderInterface(long? maxResourceBytes = null)
{
_maxResourceBytes = maxResourceBytes;
DefaultPixelFormat = SKImageInfo.PlatformColorType.ToPixelFormat();
}
if (skiaGpu != null)
{
_skiaGpu = skiaGpu;
return;
}
var gl = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (gl != null)
_skiaGpu = new GlSkiaGpu(gl, maxResourceBytes);
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)
{
if (graphicsContext == null)
return new SkiaContext(null);
if (graphicsContext is ISkiaGpu skiaGpu)
return new SkiaContext(skiaGpu);
if (graphicsContext is IGlContext gl)
return new SkiaContext(new GlSkiaGpu(gl, _maxResourceBytes));
throw new ArgumentException("Graphics context of type is not supported");
}
public bool SupportsIndividualRoundRects => true;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
@ -205,43 +207,12 @@ namespace Avalonia.Skia
return new SurfaceRenderTarget(createInfo);
}
/// <inheritdoc />
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
if (!(surfaces is IList))
surfaces = surfaces.ToList();
var gpuRenderTarget = _skiaGpu?.TryCreateRenderTarget(surfaces);
if (gpuRenderTarget != null)
{
return new SkiaGpuRenderTarget(_skiaGpu, gpuRenderTarget);
}
foreach (var surface in surfaces)
{
if (surface is IFramebufferPlatformSurface framebufferSurface)
return new FramebufferRenderTarget(framebufferSurface);
}
throw new NotSupportedException(
"Don't know how to create a Skia render target from any of provided surfaces");
}
/// <inheritdoc />
public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat)
{
return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
}
public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi)
{
if (_skiaGpu is IOpenGlAwareSkiaGpu glAware)
return glAware.CreateOpenGlBitmap(size, dpi);
if (_skiaGpu == null)
throw new PlatformNotSupportedException("GPU acceleration is not available");
throw new PlatformNotSupportedException(
"Current GPU acceleration backend does not support OpenGL integration");
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices,
IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{

47
src/Skia/Avalonia.Skia/SkiaBackendContext.cs

@ -0,0 +1,47 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Skia;
internal class SkiaContext : IPlatformRenderInterfaceContext
{
private ISkiaGpu _gpu;
public SkiaContext(ISkiaGpu gpu)
{
_gpu = gpu;
}
public void Dispose()
{
_gpu?.Dispose();
_gpu = null;
}
/// <inheritdoc />
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
if (!(surfaces is IList))
surfaces = surfaces.ToList();
var gpuRenderTarget = _gpu?.TryCreateRenderTarget(surfaces);
if (gpuRenderTarget != null)
{
return new SkiaGpuRenderTarget(_gpu, gpuRenderTarget);
}
foreach (var surface in surfaces)
{
if (surface is IFramebufferPlatformSurface framebufferSurface)
return new FramebufferRenderTarget(framebufferSurface);
}
throw new NotSupportedException(
"Don't know how to create a Skia render target from any of provided surfaces");
}
public object TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType);
}

5
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -8,11 +8,6 @@ namespace Avalonia
/// </summary>
public class SkiaOptions
{
/// <summary>
/// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
/// </summary>
public Func<ISkiaGpu> CustomGpuFactory { get; set; }
/// <summary>
/// The maximum number of bytes for video memory to store textures and resources.
/// </summary>

3
src/Skia/Avalonia.Skia/SkiaPlatform.cs

@ -17,8 +17,7 @@ namespace Avalonia.Skia
public static void Initialize(SkiaOptions options)
{
var customGpu = options.CustomGpuFactory?.Invoke();
var renderInterface = new PlatformRenderInterface(customGpu, options.MaxGpuResourceSizeBytes);
var renderInterface = new PlatformRenderInterface(options.MaxGpuResourceSizeBytes);
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface)

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -107,6 +107,8 @@ namespace Avalonia.Skia
return new DrawingContextImpl(createInfo, Disposable.Create(() => Version++));
}
public bool IsCorrupted => _gpu?.IsLost == true;
/// <inheritdoc />
public Vector Dpi { get; }

3
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -217,8 +217,7 @@ namespace Avalonia.Skia
public int RowBytes => _bitmap.RowBytes;
/// <inheritdoc />
public Vector Dpi { get; } = SkiaPlatform.DefaultDpi;
public Vector Dpi => _parent.Dpi;
/// <inheritdoc />
public PixelFormat Format => _bitmap.ColorType.ToPixelFormat();
}

22
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -113,7 +113,7 @@ namespace Avalonia.Direct2D1
SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
private IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
foreach (var s in surfaces)
{
@ -223,6 +223,26 @@ namespace Avalonia.Direct2D1
return new GlyphRunImpl(run);
}
class D2DApi : IPlatformRenderInterfaceContext
{
private readonly Direct2D1Platform _platform;
public D2DApi(Direct2D1Platform platform)
{
_platform = platform;
}
public object TryGetFeature(Type featureType) => null;
public void Dispose()
{
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => _platform.CreateRenderTarget(surfaces);
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) =>
new D2DApi(this);
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)

2
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -38,6 +38,8 @@ namespace Avalonia.Direct2D1
});
}
public bool IsCorrupted => false;
public IDrawingContextLayerImpl CreateLayer(Size size)
{
var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget();

2
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -35,6 +35,8 @@ namespace Avalonia.Direct2D1
.CreateDrawingContext(visualBrushRenderer);
}
public bool IsCorrupted => false;
class FramebufferShim : WicRenderTargetBitmapImpl
{
private readonly ILockedFramebuffer _target;

2
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -37,6 +37,8 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, null, () => Version++);
}
public bool IsCorrupted => false;
public void Blit(IDrawingContextImpl context) => throw new NotSupportedException();
public bool CanBlit => false;

2
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -39,6 +39,8 @@ namespace Avalonia.Direct2D1.Media
public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
=> CreateDrawingContext(visualBrushRenderer, null);
public bool IsCorrupted => false;
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{
return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: () =>

2
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -30,6 +30,8 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget);
}
public bool IsCorrupted => false;
public IDrawingContextLayerImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size);

2
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -35,6 +35,8 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain);
}
public bool IsCorrupted => false;
public IDrawingContextLayerImpl CreateLayer(Size size)
{
if (_deviceContext == null)

4
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -87,9 +87,11 @@ namespace Avalonia.Win32.Interop.Wpf
_ttl.ScalingChanged?.Invoke(_ttl.RenderScaling);
}
public IRenderer CreateRenderer(IRenderRoot root)
{
return new ImmediateRenderer((Visual)root);
var mgr = new PlatformRenderInterfaceContextManager(null);
return new ImmediateRenderer((Visual)root, () => mgr.CreateRenderTarget(_surfaces), mgr);
}
public void Dispose()

3
src/Avalonia.OpenGL/AngleOptions.cs → src/Windows/Avalonia.Win32/AngleOptions.cs

@ -1,6 +1,7 @@
using System.Collections.Generic;
using Avalonia.OpenGL;
namespace Avalonia.OpenGL
namespace Avalonia.Win32
{
public class AngleOptions
{

4
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -16,8 +16,12 @@
<MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" />
<MicroComIdl Include="DirectX\directx.idl" CSharpInteropPath="DirectX\directx.Generated.cs" />
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\SourceGenerators.props" />
<ItemGroup>
<Compile Remove="..\..\Shared\ModuleInitializer.cs" />
<Compile Remove="..\..\Shared\SourceGeneratorAttributes.cs" />
</ItemGroup>
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />

51
src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Win32.DxgiSwapchain
namespace Avalonia.Win32.DirectX
{
internal enum D3D_FEATURE_LEVEL
{
@ -136,4 +136,53 @@ namespace Avalonia.Win32.DxgiSwapchain
DXGI_ALPHA_MODE_FORCE_DWORD = (unchecked((int)0xffffffff)),
}
internal enum D3D_DRIVER_TYPE
{
D3D_DRIVER_TYPE_UNKNOWN = 0,
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_REFERENCE,
D3D_DRIVER_TYPE_NULL,
D3D_DRIVER_TYPE_SOFTWARE,
D3D_DRIVER_TYPE_WARP
}
internal enum DXGI_ERROR : uint
{
DXGI_ERROR_ACCESS_DENIED = 0x887A002B,
DXGI_ERROR_ACCESS_LOST = 0x887A0026,
DXGI_ERROR_ALREADY_EXISTS = 0x887A0036,
DXGI_ERROR_CANNOT_PROTECT_CONTENT = 0x887A002A,
DXGI_ERROR_DEVICE_HUNG = 0x887A0006,
DXGI_ERROR_DEVICE_REMOVED = 0x887A0005,
DXGI_ERROR_DEVICE_RESET = 0x887A0007,
DXGI_ERROR_DRIVER_INTERNAL_ERROR = 0x887A0020,
DXGI_ERROR_FRAME_STATISTICS_DISJOINT = 0x887A000B,
DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE = 0x887A000C,
DXGI_ERROR_INVALID_CALL = 0x887A0001,
DXGI_ERROR_MORE_DATA = 0x887A0003,
DXGI_ERROR_NAME_ALREADY_EXISTS = 0x887A002C,
DXGI_ERROR_NONEXCLUSIVE = 0x887A0021,
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE = 0x887A0022,
DXGI_ERROR_NOT_FOUND = 0x887A0002,
DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED = 0x887A0023,
DXGI_ERROR_REMOTE_OUTOFMEMORY = 0x887A0024,
DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE = 0x887A0029,
DXGI_ERROR_SDK_COMPONENT_MISSING = 0x887A002D,
DXGI_ERROR_SESSION_DISCONNECTED = 0x887A0028,
DXGI_ERROR_UNSUPPORTED = 0x887A0004,
DXGI_ERROR_WAIT_TIMEOUT = 0x887A0027,
DXGI_ERROR_WAS_STILL_DRAWING = 0x887A000A
}
internal static class DxgiErrorExtensions
{
public static bool IsDeviceLostError(this DXGI_ERROR error)
{
return error is DXGI_ERROR.DXGI_ERROR_DEVICE_REMOVED
or DXGI_ERROR.DXGI_ERROR_DEVICE_HUNG
or DXGI_ERROR.DXGI_ERROR_DEVICE_RESET
or DXGI_ERROR.DXGI_ERROR_NOT_CURRENTLY_AVAILABLE;
}
}
}

1108
src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs

File diff suppressed because it is too large

15
src/Windows/Avalonia.Win32/DirectX/DirectXUnmanagedMethods.cs

@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Win32.DxgiSwapchain
namespace Avalonia.Win32.DirectX
{
internal unsafe class DirectXUnmanagedMethods
{
@ -23,7 +23,16 @@ namespace Avalonia.Win32.DxgiSwapchain
[DllImport("user32", ExactSpelling = true)]
internal static extern bool EnumDisplaySettingsW(ushort* lpszDeviceName, uint iModeNum, DEVMODEW* lpDevMode);
[DllImport("user32", ExactSpelling = true, SetLastError = true)]
internal static extern bool GetClientRect(IntPtr hWnd, Interop.UnmanagedMethods.RECT* lpRect);
[DllImport("d3d11", ExactSpelling = true, PreserveSig = false)]
public static extern void D3D11CreateDevice(
IntPtr adapter, D3D_DRIVER_TYPE DriverType,
IntPtr Software,
uint Flags,
D3D_FEATURE_LEVEL[] pFeatureLevels,
uint FeatureLevels,
uint SDKVersion,
out IntPtr ppDevice,
out D3D_FEATURE_LEVEL pFeatureLevel,
IntPtr* ppImmediateContext);
}
}

34
src/Windows/Avalonia.Win32/DirectX/DxgiConnection.cs

@ -10,11 +10,12 @@ using Avalonia.Logging;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.Rendering;
using Avalonia.Win32.OpenGl.Angle;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using static Avalonia.Win32.DxgiSwapchain.DirectXUnmanagedMethods;
using static Avalonia.Win32.DirectX.DirectXUnmanagedMethods;
using MicroCom.Runtime;
namespace Avalonia.Win32.DxgiSwapchain
namespace Avalonia.Win32.DirectX
{
#pragma warning disable CA1416 // This should only be reachable on Windows
#nullable enable
@ -25,34 +26,27 @@ namespace Avalonia.Win32.DxgiSwapchain
public bool RunsInBackground => true;
public event Action<TimeSpan>? Tick;
private AngleWin32EglDisplay _angle;
private EglPlatformOpenGlInterface _gl;
private object _syncLock;
private IDXGIOutput? _output = null;
private Stopwatch? _stopwatch = null;
private const string LogArea = "DXGI";
public DxgiConnection(EglPlatformOpenGlInterface gl, object syncLock)
public DxgiConnection(object syncLock)
{
_syncLock = syncLock;
_angle = (AngleWin32EglDisplay)gl.Display;
_gl = gl;
}
public EglPlatformOpenGlInterface Egl => _gl;
public static void TryCreateAndRegister(EglPlatformOpenGlInterface angle)
public static void TryCreateAndRegister()
{
try
{
TryCreateAndRegisterCore(angle);
TryCreateAndRegisterCore();
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(null, "Unable to establish Dxgi: {0}", ex);
}
}
@ -66,7 +60,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}");
}
@ -84,7 +78,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}");
_output.Dispose();
_output = null;
@ -103,7 +97,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, nameof(DxgiSwapchain))
Logger.TryGet(LogEventLevel.Error, LogArea)
?.Log(this, $"Failed to wait for vblank, Exception: {ex.Message}, HRESULT = {ex.HResult}");
}
}
@ -168,7 +162,7 @@ namespace Avalonia.Win32.DxgiSwapchain
}
// Used the windows composition as a blueprint for this startup/creation
static private bool TryCreateAndRegisterCore(EglPlatformOpenGlInterface gl)
static private bool TryCreateAndRegisterCore()
{
var tcs = new TaskCompletionSource<bool>();
var pumpLock = new object();
@ -178,7 +172,7 @@ namespace Avalonia.Win32.DxgiSwapchain
{
DxgiConnection connection;
connection = new DxgiConnection(gl, pumpLock);
connection = new DxgiConnection(pumpLock);
AvaloniaLocator.CurrentMutable.BindToSelf(connection);
AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(connection);

19
src/Windows/Avalonia.Win32/DirectX/DxgiRenderTarget.cs

@ -8,11 +8,12 @@ using System.Threading.Tasks;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Win32.OpenGl.Angle;
using MicroCom.Runtime;
using static Avalonia.OpenGL.Egl.EglGlPlatformSurfaceBase;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.DxgiSwapchain
namespace Avalonia.Win32.DirectX
{
#pragma warning disable CA1416 // Validate platform compatibility, if you enter this not on windows you have messed up badly
#nullable enable
@ -22,8 +23,7 @@ namespace Avalonia.Win32.DxgiSwapchain
public const uint DXGI_USAGE_RENDER_TARGET_OUTPUT = 0x00000020U;
private IEglWindowGlPlatformSurfaceInfo _window;
private EglPlatformOpenGlInterface _egl;
private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window;
private DxgiConnection _connection;
private IDXGIDevice? _dxgiDevice = null;
private IDXGIFactory2? _dxgiFactory = null;
@ -36,15 +36,14 @@ namespace Avalonia.Win32.DxgiSwapchain
private Guid ID3D11Texture2DGuid = Guid.Parse("6F15AAF2-D208-4E89-9AB4-489535D34F9C");
public DxgiRenderTarget(IEglWindowGlPlatformSurfaceInfo window, EglPlatformOpenGlInterface egl, DxgiConnection connection) : base(egl)
public DxgiRenderTarget(EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window, EglContext context, DxgiConnection connection) : base(context)
{
_window = window;
_egl = egl;
_connection = connection;
// the D3D device is expected to at least be an ID3D11Device
// but how do I wrap an IntPtr as a managed IUnknown now? Like this.
IUnknown pdevice = MicroComRuntime.CreateProxyFor<IUnknown>(((AngleWin32EglDisplay)_egl.Display).GetDirect3DDevice(), false);
IUnknown pdevice = MicroComRuntime.CreateProxyFor<IUnknown>(((AngleWin32EglDisplay)context.Display).GetDirect3DDevice(), false);
_dxgiDevice = pdevice.QueryInterface<IDXGIDevice>();
@ -86,14 +85,14 @@ namespace Avalonia.Win32.DxgiSwapchain
_clientRect = pClientRect;
}
public override IGlPlatformSurfaceRenderingSession BeginDraw()
public override IGlPlatformSurfaceRenderingSession BeginDrawCore()
{
if (_swapChain is null)
{
throw new InvalidOperationException("No chain to draw on");
}
var contextLock = _egl.PrimaryContext.EnsureCurrent();
var contextLock = Context.EnsureCurrent();
EglSurface? surface = null;
IDisposable? transaction = null;
var success = false;
@ -132,10 +131,10 @@ namespace Avalonia.Win32.DxgiSwapchain
_renderTexture = texture;
// I also have to get the pointer to this texture directly
surface = ((AngleWin32EglDisplay)_egl.Display).WrapDirect3D11Texture(_egl, MicroComRuntime.GetNativeIntPtr(_renderTexture),
surface = ((AngleWin32EglDisplay)Context.Display).WrapDirect3D11Texture(MicroComRuntime.GetNativeIntPtr(_renderTexture),
0, 0, size.Width, size.Height);
var res = base.BeginDraw(surface, _window, () =>
var res = base.BeginDraw(surface, _window.Size, _window.Scaling, () =>
{
_swapChain.Present((ushort)0U, (ushort)0U);
surface?.Dispose();

16
src/Windows/Avalonia.Win32/DirectX/DxgiSwapchainWindow.cs

@ -3,29 +3,29 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Win32.DxgiSwapchain
namespace Avalonia.Win32.DirectX
{
public class DxgiSwapchainWindow : EglGlPlatformSurfaceBase
{
private DxgiConnection _connection;
private EglPlatformOpenGlInterface _egl;
private IEglWindowGlPlatformSurfaceInfo _window;
private EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _window;
public DxgiSwapchainWindow(DxgiConnection connection, IEglWindowGlPlatformSurfaceInfo window)
public DxgiSwapchainWindow(DxgiConnection connection, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo window)
{
_connection = connection;
_window = window;
_egl = connection.Egl;
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
{
using (_egl.PrimaryContext.EnsureCurrent())
var eglContext = (EglContext)context;
using (eglContext.EnsureCurrent())
{
return new DxgiRenderTarget(_window, _egl, _connection);
return new DxgiRenderTarget(_window, eglContext, _connection);
}
}
}

26
src/Windows/Avalonia.Win32/DirectX/IDirect3D11TexturePlatformSurface.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.OpenGL;
using Avalonia.Platform;
namespace Avalonia.Win32.DirectX;
public interface IDirect3D11TexturePlatformSurface
{
public IDirect3D11TextureRenderTarget CreateRenderTarget(IPlatformGraphicsContext graphicsContext, IntPtr d3dDevice);
}
public interface IDirect3D11TextureRenderTarget : IDisposable
{
bool IsCorrupted { get; }
IDirect3D11TextureRenderTargetRenderSession BeginDraw();
}
public interface IDirect3D11TextureRenderTargetRenderSession : IDisposable
{
public IntPtr D3D11Texture2D { get; }
public PixelSize Size { get; }
public PixelPoint Offset { get; }
public double Scaling { get; }
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save