A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

258 lines
8.7 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Logging;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.VisualTree;
using Avalonia.Platform;
namespace Avalonia.OpenGL.Controls
{
public abstract class OpenGlControlBase : Control
{
private CompositionSurfaceVisual? _visual;
private readonly Action _update;
private bool _updateQueued;
private Task<bool>? _initialization;
private OpenGlControlBaseResources? _resources;
private Compositor? _compositor;
protected GlVersion GlVersion => _resources?.Context.Version ?? default;
public OpenGlControlBase()
{
_update = Update;
}
private void DoCleanup()
{
if (_initialization is { Status: TaskStatus.RanToCompletion } && _resources != null)
{
try
{
using (_resources.Context.EnsureCurrent())
{
OnOpenGlDeinit(_resources.Context.GlInterface);
}
}
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to free user OpenGL resources: {exception}", e);
}
}
ElementComposition.SetElementChildVisual(this, null);
_visual = null;
_resources?.DisposeAsync();
_resources = null;
_initialization = null;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DoCleanup();
base.OnDetachedFromVisualTree(e);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_compositor = (this.GetVisualRoot()?.Renderer as IRendererWithCompositor)?.Compositor;
RequestNextFrameRendering();
}
[MemberNotNullWhen(true, nameof(_resources))]
private bool EnsureInitializedCore(
ICompositionGpuInterop interop,
IOpenGlTextureSharingRenderInterfaceContextFeature? contextSharingFeature)
{
var surface = _compositor!.CreateDrawingSurface();
IGlContext? ctx = null;
try
{
if (contextSharingFeature?.CanCreateSharedContext == true)
_resources = OpenGlControlBaseResources.TryCreate(surface, interop, contextSharingFeature);
if(_resources == null)
{
var contextFactory = AvaloniaLocator.Current.GetRequiredService<IPlatformGraphicsOpenGlContextFactory>();
ctx = contextFactory.CreateContext(null);
if (ctx.TryGetFeature<IGlContextExternalObjectsFeature>(out var externalObjects))
_resources = OpenGlControlBaseResources.TryCreate(ctx, surface, interop, externalObjects);
else
ctx.Dispose();
}
if(_resources == null)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL: current platform does not support multithreaded context sharing and shared memory");
return false;
}
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL: {exception}", e);
ctx?.Dispose();
return false;
}
_visual = _compositor.CreateSurfaceVisual();
_visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
_visual.Surface = _resources.Surface;
ElementComposition.SetElementChildVisual(this, _visual);
using (_resources.Context.MakeCurrent())
OnOpenGlInit(_resources.Context.GlInterface);
return true;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (_visual != null && change.Property == BoundsProperty)
{
_visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
RequestNextFrameRendering();
}
base.OnPropertyChanged(change);
}
private void ContextLost()
{
_initialization = null;
_resources?.DisposeAsync();
OnOpenGlLost();
}
[MemberNotNullWhen(true, nameof(_resources))]
private bool EnsureInitialized()
{
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 (_resources!.Context.IsLost)
ContextLost();
else
return true;
}
_initialization = InitializeAsync();
async void ContinueOnInitialization()
{
try
{
await _initialization;
RequestNextFrameRendering();
}
catch
{
//
}
}
ContinueOnInitialization();
return false;
}
private void Update()
{
_updateQueued = false;
if (VisualRoot is not { } visualRoot)
return;
if(!EnsureInitialized())
return;
using (_resources.BeginDraw(GetPixelSize(visualRoot)))
OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo);
}
private async Task<bool> InitializeAsync()
{
if (_compositor == null)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to obtain Compositor instance");
return false;
}
var gpuInteropTask = _compositor.TryGetCompositionGpuInterop();
var contextSharingFeature =
(IOpenGlTextureSharingRenderInterfaceContextFeature?)
await _compositor.TryGetRenderInterfaceFeature(
typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
var interop = await gpuInteropTask;
if (interop == null)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Compositor backend doesn't support GPU interop");
return false;
}
if (!EnsureInitializedCore(interop, contextSharingFeature))
{
DoCleanup();
return false;
}
using (_resources.Context.MakeCurrent())
OnOpenGlInit(_resources.Context.GlInterface);
return true;
}
[Obsolete("Use RequestNextFrameRendering()")]
// ReSharper disable once MemberCanBeProtected.Global
public new void InvalidateVisual() => RequestNextFrameRendering();
public void RequestNextFrameRendering()
{
if ((_initialization == null || _initialization is { Status: TaskStatus.RanToCompletion }) &&
!_updateQueued)
{
_updateQueued = true;
_compositor?.RequestCompositionUpdate(_update);
}
}
private PixelSize GetPixelSize(IRenderRoot visualRoot)
{
var scaling = visualRoot.RenderScaling;
return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
Math.Max(1, (int)(Bounds.Height * scaling)));
}
protected virtual void OnOpenGlInit(GlInterface gl)
{
}
protected virtual void OnOpenGlDeinit(GlInterface gl)
{
}
protected virtual void OnOpenGlLost()
{
}
protected abstract void OnOpenGlRender(GlInterface gl, int fb);
}
}