14 changed files with 853 additions and 229 deletions
@ -0,0 +1,195 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Reactive; |
|||
using Avalonia.Rendering.Composition; |
|||
|
|||
namespace Avalonia.Rendering; |
|||
|
|||
/// <summary>
|
|||
/// A helper class for composition-backed swapchains, should not be a public API
|
|||
/// </summary>
|
|||
abstract class AsyncSwapchainBase<TImage> : IAsyncDisposable where TImage : class |
|||
{ |
|||
private readonly int _queueLen; |
|||
private readonly string _logArea; |
|||
protected ICompositionGpuInterop Interop { get; } |
|||
protected CompositionDrawingSurface Target { get; } |
|||
private readonly Queue<QueueElement> _imageQueue = new(); |
|||
private readonly Queue<ValueTask> _pendingClearOperations = new(); |
|||
private PixelSize _size; |
|||
private bool _disposed; |
|||
|
|||
struct QueueElement |
|||
{ |
|||
public Task Present; |
|||
public TImage Image; |
|||
} |
|||
|
|||
public AsyncSwapchainBase(ICompositionGpuInterop interop, CompositionDrawingSurface target, |
|||
PixelSize size, int queueLen, string logArea) |
|||
{ |
|||
if (queueLen < 2) |
|||
throw new ArgumentOutOfRangeException(); |
|||
_queueLen = queueLen; |
|||
_logArea = logArea; |
|||
Interop = interop; |
|||
Target = target; |
|||
Resize(size); |
|||
} |
|||
|
|||
static bool IsBroken(QueueElement image) => image.Present.IsFaulted; |
|||
static bool IsReady(QueueElement image) => image.Present.Status == TaskStatus.RanToCompletion; |
|||
|
|||
TImage? CleanupAndFindNextImage() |
|||
{ |
|||
while (_imageQueue.Count > 0 && IsBroken(_imageQueue.Peek())) |
|||
DisposeQueueItem(_imageQueue.Dequeue()); |
|||
|
|||
if (_imageQueue.Count < _queueLen) |
|||
return null; |
|||
|
|||
if (IsReady(_imageQueue.Peek())) |
|||
return _imageQueue.Dequeue().Image; |
|||
|
|||
return null; |
|||
} |
|||
|
|||
protected abstract TImage CreateImage(PixelSize size); |
|||
protected abstract ValueTask DisposeImage(TImage image); |
|||
protected abstract Task PresentImage(TImage image); |
|||
protected abstract void BeginDraw(TImage image); |
|||
|
|||
private (IDisposable session, TImage image, Task presented)? BeginDraw(bool forceCreateImage) |
|||
{ |
|||
if (_disposed) |
|||
throw new ObjectDisposedException(null); |
|||
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); |
|||
var img = CleanupAndFindNextImage(); |
|||
if (img == null) |
|||
{ |
|||
if (_imageQueue.Count < _queueLen || forceCreateImage) |
|||
img = CreateImage(_size); |
|||
else |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
BeginDraw(img); |
|||
return (Disposable.Create(() => |
|||
{ |
|||
var presentTask = PresentImage(img); |
|||
// Synchronize the user-visible task
|
|||
presentTask.ContinueWith(_ => |
|||
{ |
|||
if (presentTask.Status == TaskStatus.Canceled) |
|||
tcs.SetCanceled(); |
|||
else if (presentTask.Status == TaskStatus.Faulted) |
|||
tcs.SetException(presentTask.Exception!); |
|||
else |
|||
tcs.SetResult(0); |
|||
}); |
|||
_imageQueue.Enqueue(new() |
|||
{ |
|||
Present = presentTask, |
|||
Image = img |
|||
}); |
|||
}), img, tcs.Task); |
|||
} |
|||
|
|||
protected (IDisposable session, TImage image, Task presented) BeginDraw() => BeginDraw(true)!.Value; |
|||
|
|||
protected (IDisposable session, TImage image, Task presented)? TryBeginDraw() => BeginDraw(false); |
|||
|
|||
protected async ValueTask<(IDisposable session, TImage image, Task presented)> BeginDrawAsync() |
|||
{ |
|||
while (true) |
|||
{ |
|||
var session = BeginDraw(false); |
|||
if (session != null) |
|||
return session.Value; |
|||
try |
|||
{ |
|||
await _imageQueue.Peek().Present!; |
|||
} |
|||
catch |
|||
{ |
|||
// Ignore result, we just need to wait for it
|
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Resize(PixelSize size) |
|||
{ |
|||
if (size.Width < 1 || size.Height < 1) |
|||
throw new ArgumentOutOfRangeException(); |
|||
if (size == _size) |
|||
return; |
|||
DisposeQueueItems(); |
|||
_size = size; |
|||
} |
|||
|
|||
|
|||
async ValueTask DisposeQueueItemCore(QueueElement img) |
|||
{ |
|||
if (img.Present != null) |
|||
try |
|||
{ |
|||
await img.Present; |
|||
} |
|||
catch |
|||
{ |
|||
// Ignore
|
|||
} |
|||
|
|||
try |
|||
{ |
|||
await DisposeImage(img.Image); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.TryGet(LogEventLevel.Error, _logArea) |
|||
?.Log(this, "Unable to dispose for swapchain image: {Exception}", e); |
|||
} |
|||
} |
|||
|
|||
async ValueTask DisposeQueueItemsCore(List<QueueElement> images) |
|||
{ |
|||
foreach (var img in images) |
|||
await DisposeQueueItemCore(img); |
|||
} |
|||
|
|||
void DisposeQueueItem(QueueElement image) |
|||
{ |
|||
_pendingClearOperations.Enqueue(DisposeQueueItemCore(image)); |
|||
DrainPendingClearOperations(); |
|||
} |
|||
|
|||
void DrainPendingClearOperations() |
|||
{ |
|||
while (_pendingClearOperations.Count > 0 && _pendingClearOperations.Peek().IsCompleted) |
|||
_pendingClearOperations.Dequeue().GetAwaiter().GetResult(); |
|||
} |
|||
|
|||
void DisposeQueueItems() |
|||
{ |
|||
if (_imageQueue.Count == 0) |
|||
return; |
|||
|
|||
var images = _imageQueue.ToList(); |
|||
_imageQueue.Clear(); |
|||
_pendingClearOperations.Enqueue(DisposeQueueItemsCore(images)); |
|||
DrainPendingClearOperations(); |
|||
} |
|||
|
|||
public virtual async ValueTask DisposeAsync() |
|||
{ |
|||
_disposed = true; |
|||
DisposeQueueItems(); |
|||
while (_pendingClearOperations.Count > 0) |
|||
await _pendingClearOperations.Dequeue(); |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.OpenGL.Controls; |
|||
using Avalonia.Rendering.Composition; |
|||
|
|||
namespace Avalonia.OpenGL.Composition; |
|||
|
|||
abstract class CompositionGlContextBase : ICompositionGlContext |
|||
{ |
|||
protected readonly ICompositionGpuInterop Interop; |
|||
public Compositor Compositor { get; } |
|||
public IGlContext Context { get; } |
|||
private List<CompositionGlSwapchain> _swapchains = new(); |
|||
|
|||
public abstract ICompositionGlSwapchain CreateSwapchain(CompositionDrawingSurface surface, PixelSize size, |
|||
Action? onDispose = null); |
|||
|
|||
internal CompositionGlContextBase( |
|||
Compositor compositor, |
|||
IGlContext context, |
|||
ICompositionGpuInterop interop) |
|||
{ |
|||
Compositor = compositor; |
|||
Interop = interop; |
|||
Context = context; |
|||
} |
|||
|
|||
public ValueTask DisposeAsync() |
|||
{ |
|||
if (Compositor.Dispatcher.CheckAccess()) |
|||
return DisposeAsyncCore(); |
|||
return new ValueTask(Compositor.Dispatcher.InvokeAsync(() => DisposeAsyncCore().AsTask())); |
|||
} |
|||
|
|||
private async ValueTask DisposeAsyncCore() |
|||
{ |
|||
while (_swapchains.Count > 0) |
|||
{ |
|||
var chain = _swapchains[_swapchains.Count - 1]; |
|||
// The swapchain will remove itself
|
|||
await chain.DisposeAsync(); |
|||
} |
|||
Context.Dispose(); |
|||
} |
|||
|
|||
internal void RemoveSwapchain(CompositionGlSwapchain chain) |
|||
{ |
|||
Compositor.Dispatcher.VerifyAccess(); |
|||
_swapchains.Remove(chain); |
|||
} |
|||
|
|||
internal void AddSwapchain(CompositionGlSwapchain chain) |
|||
{ |
|||
Compositor.Dispatcher.VerifyAccess(); |
|||
_swapchains.Add(chain); |
|||
} |
|||
} |
|||
|
|||
|
|||
class CompositionGlContextViaContextSharing : CompositionGlContextBase |
|||
{ |
|||
private readonly IOpenGlTextureSharingRenderInterfaceContextFeature _sharing; |
|||
|
|||
public CompositionGlContextViaContextSharing( |
|||
Compositor compositor, |
|||
IGlContext context, |
|||
ICompositionGpuInterop interop, |
|||
IOpenGlTextureSharingRenderInterfaceContextFeature sharing) : base(compositor, context, interop) |
|||
{ |
|||
_sharing = sharing; |
|||
} |
|||
|
|||
public override ICompositionGlSwapchain CreateSwapchain(CompositionDrawingSurface surface, PixelSize size, |
|||
Action? onDispose) |
|||
{ |
|||
Compositor.Dispatcher.VerifyAccess(); |
|||
return new CompositionGlSwapchain(this, surface, Interop, |
|||
(imageSize, surface) => new CompositionOpenGlSwapChainImage(Context, _sharing, imageSize, Interop, surface), |
|||
size, 2, onDispose); |
|||
} |
|||
} |
|||
|
|||
class CompositionGlContextViaExternalObjects : CompositionGlContextBase |
|||
{ |
|||
private readonly IGlContextExternalObjectsFeature _externalObjectsFeature; |
|||
|
|||
// ReSharper disable once NotAccessedField.Local
|
|||
// TODO: Implement
|
|||
private readonly string _externalImageType; |
|||
// ReSharper disable once NotAccessedField.Local
|
|||
// TODO: Implement
|
|||
private readonly string? _externalSemaphoreType; |
|||
|
|||
private readonly CompositionGpuImportedImageSynchronizationCapabilities _syncMode; |
|||
|
|||
|
|||
public CompositionGlContextViaExternalObjects(Compositor compositor, IGlContext context, |
|||
ICompositionGpuInterop interop, IGlContextExternalObjectsFeature externalObjectsFeature, |
|||
string externalImageType, CompositionGpuImportedImageSynchronizationCapabilities syncMode, |
|||
string? externalSemaphoreType) : base(compositor, context, interop) |
|||
{ |
|||
_externalObjectsFeature = externalObjectsFeature; |
|||
_externalImageType = externalImageType; |
|||
_syncMode = syncMode; |
|||
_externalSemaphoreType = externalSemaphoreType; |
|||
if (_syncMode != CompositionGpuImportedImageSynchronizationCapabilities.KeyedMutex) |
|||
throw new NotSupportedException("Only IDXGIKeyedMutex sync is supported for non-shared contexts"); |
|||
} |
|||
|
|||
public override ICompositionGlSwapchain CreateSwapchain(CompositionDrawingSurface surface, PixelSize size, Action? onDispose) |
|||
{ |
|||
Compositor.Dispatcher.VerifyAccess(); |
|||
if (_syncMode == CompositionGpuImportedImageSynchronizationCapabilities.KeyedMutex) |
|||
|
|||
return new CompositionGlSwapchain(this, surface, Interop, |
|||
(imageSize, surface) => |
|||
new DxgiMutexOpenGlSwapChainImage(Interop, surface, _externalObjectsFeature, imageSize), |
|||
size, 2, onDispose); |
|||
|
|||
throw new System.NotSupportedException(); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using Avalonia.Rendering.Composition; |
|||
|
|||
namespace Avalonia.OpenGL.Composition; |
|||
|
|||
public static class CompositionGlContextExtensions |
|||
{ |
|||
public static ICompositionGlSwapchain CreateSwapchain(this ICompositionGlContext context, Visual visual, PixelSize size) |
|||
{ |
|||
if (visual.CompositionVisual == null) |
|||
throw new InvalidOperationException("Visual isn't attached to composition tree"); |
|||
if (visual.CompositionVisual.Compositor != context.Compositor) |
|||
throw new InvalidOperationException("Visual is attached to a different compositor"); |
|||
|
|||
var surface = context.Compositor.CreateDrawingSurface(); |
|||
var surfaceVisual = context.Compositor.CreateSurfaceVisual(); |
|||
surfaceVisual.Surface = surface; |
|||
|
|||
void Resize() => surfaceVisual!.Size = new Vector(visual.Bounds.Width, visual.Bounds.Height); |
|||
ElementComposition.SetElementChildVisual(visual, surfaceVisual); |
|||
|
|||
void OnVisualOnPropertyChanged(object? s, AvaloniaPropertyChangedEventArgs e) => Resize(); |
|||
visual.PropertyChanged += OnVisualOnPropertyChanged; |
|||
|
|||
void Dispose() |
|||
{ |
|||
visual.PropertyChanged -= OnVisualOnPropertyChanged; |
|||
ElementComposition.SetElementChildVisual(visual, null); |
|||
} |
|||
|
|||
Resize(); |
|||
bool success = false; |
|||
try |
|||
{ |
|||
var res = context.CreateSwapchain(surface, size, Dispose); |
|||
success = true; |
|||
return res; |
|||
} |
|||
finally |
|||
{ |
|||
if(!success) |
|||
Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.OpenGL.Controls; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.OpenGL.Composition; |
|||
|
|||
class CompositionGlSwapchain : AsyncSwapchainBase<IGlSwapchainImage>, ICompositionGlSwapchain |
|||
{ |
|||
protected readonly IGlContext Context; |
|||
private readonly Func<PixelSize, CompositionDrawingSurface, IGlSwapchainImage> _imageFactory; |
|||
private readonly Action? _onDispose; |
|||
private readonly CompositionDrawingSurface _surface; |
|||
private readonly Dispatcher _dispatcher; |
|||
private readonly CompositionGlContextBase _parent; |
|||
|
|||
public CompositionGlSwapchain( |
|||
CompositionGlContextBase parentContext, |
|||
CompositionDrawingSurface surface, |
|||
ICompositionGpuInterop interop, |
|||
Func<PixelSize, CompositionDrawingSurface, IGlSwapchainImage> imageFactory, |
|||
PixelSize size, int queueLength, Action? onDispose) : base(interop, surface, size, queueLength, "OpenGL") |
|||
{ |
|||
_parent = parentContext; |
|||
Context = parentContext.Context; |
|||
_imageFactory = imageFactory; |
|||
_onDispose = onDispose; |
|||
_surface = surface; |
|||
_dispatcher = _surface.Compositor.Dispatcher; |
|||
_parent.AddSwapchain(this); |
|||
} |
|||
|
|||
|
|||
public CompositionSurface Surface => _surface; |
|||
|
|||
class LockedTexture : ICompositionGlSwapchainLockedTexture |
|||
{ |
|||
private IDisposable? _disposable; |
|||
|
|||
public Task Presented { get; } |
|||
public int TextureId { get; private set; } |
|||
|
|||
public LockedTexture((IDisposable disposable, IGlSwapchainImage texture, Task presented) res) |
|||
{ |
|||
TextureId = res.texture.TextureId; |
|||
_disposable = res.disposable; |
|||
Presented = res.presented; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_disposable?.Dispose(); |
|||
_disposable = null; |
|||
TextureId = 0; |
|||
} |
|||
|
|||
} |
|||
|
|||
ICompositionGlSwapchainLockedTexture? TryGetNextTextureCore() |
|||
{ |
|||
var res = TryBeginDraw(); |
|||
if (res != null) |
|||
return new LockedTexture(res.Value); |
|||
return null; |
|||
} |
|||
|
|||
public ICompositionGlSwapchainLockedTexture? TryGetNextTexture() |
|||
{ |
|||
_dispatcher.VerifyAccess(); |
|||
using (Context.EnsureCurrent()) |
|||
return TryGetNextTextureCore(); |
|||
} |
|||
|
|||
public async ValueTask<ICompositionGlSwapchainLockedTexture> GetNextTextureAsync() |
|||
{ |
|||
_dispatcher.VerifyAccess(); |
|||
if (Context is IGlContextWithIsCurrentCheck currentCheck && currentCheck.IsCurrent) |
|||
throw new InvalidOperationException( |
|||
"You should not be calling _any_ asynchronous methods inside of MakeCurrent/EnsureCurrent blocks. Awaiting such method will result in a broken opengl state"); |
|||
|
|||
var res = await BeginDrawAsync(); |
|||
return new LockedTexture(res); |
|||
} |
|||
|
|||
public ICompositionGlSwapchainLockedTexture GetNextTextureIgnoringQueueLimits() |
|||
{ |
|||
_dispatcher.VerifyAccess(); |
|||
using (Context.EnsureCurrent()) |
|||
{ |
|||
return new LockedTexture(base.BeginDraw()); |
|||
} |
|||
} |
|||
|
|||
|
|||
protected override IGlSwapchainImage CreateImage(PixelSize size) |
|||
{ |
|||
using (Context.EnsureCurrent()) |
|||
return _imageFactory(size, _surface); |
|||
} |
|||
|
|||
protected override async ValueTask DisposeImage(IGlSwapchainImage image) |
|||
{ |
|||
await image.DisposeImportedAsync(); |
|||
using (Context.EnsureCurrent()) |
|||
image.DisposeTexture(); |
|||
} |
|||
|
|||
protected override Task PresentImage(IGlSwapchainImage image) |
|||
{ |
|||
_dispatcher.VerifyAccess(); |
|||
using (Context.EnsureCurrent()) |
|||
{ |
|||
Context.GlInterface.Flush(); |
|||
return image.Present(); |
|||
} |
|||
} |
|||
|
|||
protected override void BeginDraw(IGlSwapchainImage image) |
|||
{ |
|||
using (Context.EnsureCurrent()) |
|||
image.BeginDraw(); |
|||
} |
|||
|
|||
public override ValueTask DisposeAsync() |
|||
{ |
|||
if (!_dispatcher.CheckAccess()) |
|||
return new ValueTask(_dispatcher.InvokeAsync(() => DisposeAsyncCore().AsTask())); |
|||
return DisposeAsyncCore(); |
|||
} |
|||
|
|||
private async ValueTask DisposeAsyncCore() |
|||
{ |
|||
_onDispose?.Invoke(); |
|||
await base.DisposeAsync(); |
|||
_parent.RemoveSwapchain(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Metadata; |
|||
using Avalonia.Rendering.Composition; |
|||
|
|||
namespace Avalonia.OpenGL.Composition; |
|||
|
|||
[NotClientImplementable, Unstable] |
|||
public interface ICompositionGlContext : IAsyncDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The associated compositor
|
|||
/// </summary>
|
|||
Compositor Compositor { get; } |
|||
/// <summary>
|
|||
/// The OpenGL context
|
|||
/// </summary>
|
|||
IGlContext Context { get; } |
|||
/// <summary>
|
|||
/// Creates a swapchain that can draw into provided CompositionDrawingSurface instance
|
|||
/// </summary>
|
|||
/// <param name="surface">The surface to draw into</param>
|
|||
/// <param name="size">The pixel size for the textures generated by the swapchain</param>
|
|||
/// <param name="onDispose">The callback to be called when the swapchain is about to be disposed</param>
|
|||
ICompositionGlSwapchain CreateSwapchain(CompositionDrawingSurface surface, PixelSize size, Action? onDispose = null); |
|||
} |
|||
|
|||
[NotClientImplementable, Unstable] |
|||
public interface ICompositionGlSwapchain : IAsyncDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Attempts to get the next texture in the swapchain. If all textures in the swapchain are currently queued for
|
|||
/// presentation, returns null
|
|||
/// </summary>
|
|||
ICompositionGlSwapchainLockedTexture? TryGetNextTexture(); |
|||
/// <summary>
|
|||
/// Gets the the next texture in the swapchain or extends the swapchain.
|
|||
/// Note that calling this method without waiting for previous textures to be presented can introduce
|
|||
/// high GPU resource usage
|
|||
/// </summary>
|
|||
ICompositionGlSwapchainLockedTexture GetNextTextureIgnoringQueueLimits(); |
|||
/// <summary>
|
|||
/// Asynchronously gets the next texture from the swapchain once one becomes available
|
|||
/// You should not be calling this method while your IGlContext is current
|
|||
/// </summary>
|
|||
ValueTask<ICompositionGlSwapchainLockedTexture> GetNextTextureAsync(); |
|||
/// <summary>
|
|||
/// Resizes the swapchain to a new pixel size
|
|||
/// </summary>
|
|||
void Resize(PixelSize size); |
|||
} |
|||
|
|||
[NotClientImplementable, Unstable] |
|||
public interface ICompositionGlSwapchainLockedTexture : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The task you can use to wait for presentation to complete on the render thread
|
|||
/// </summary>
|
|||
public Task Presented { get; } |
|||
|
|||
/// <summary>
|
|||
/// The texture you are expected to render into. You can bind it to GL_COLOR_ATTACHMENT0 of your framebuffer.
|
|||
/// Note that the texture must be unbound before this object is disposed
|
|||
/// </summary>
|
|||
public int TextureId { get; } |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering.Composition; |
|||
|
|||
namespace Avalonia.OpenGL.Composition; |
|||
|
|||
public static class OpenGLCompositionInterop |
|||
{ |
|||
/// <summary>
|
|||
/// Attempts to create an OpenGL context that is usable with the provided compositor
|
|||
/// </summary>
|
|||
public static async ValueTask<ICompositionGlContext?> TryCreateCompatibleGlContextAsync(this Compositor compositor, |
|||
OpenGLCompositionInteropContextCreationOptions? options = null) |
|||
{ |
|||
compositor.Dispatcher.VerifyAccess(); |
|||
options ??= new(); |
|||
var gpuInteropTask = compositor.TryGetCompositionGpuInterop(); |
|||
|
|||
var contextSharing = |
|||
(IOpenGlTextureSharingRenderInterfaceContextFeature?) |
|||
await compositor.TryGetRenderInterfaceFeature( |
|||
typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)); |
|||
var interop = await gpuInteropTask; |
|||
|
|||
if (interop == null) |
|||
return null; |
|||
|
|||
if (contextSharing != null) |
|||
{ |
|||
// If context sharing is advertised, we should always go for it
|
|||
var context = contextSharing.CreateSharedContext(options.VersionPreferences); |
|||
if (context == null) |
|||
return null; |
|||
return new CompositionGlContextViaContextSharing(compositor, context, interop, contextSharing); |
|||
} |
|||
|
|||
if (interop.DeviceLuid == null && interop.DeviceUuid == null) |
|||
return null; |
|||
|
|||
if (AvaloniaLocator.Current.GetService<IPlatformGraphicsOpenGlContextFactory>() is {} factory) |
|||
{ |
|||
IGlContext context; |
|||
try |
|||
{ |
|||
context = factory.CreateContext(options.VersionPreferences); |
|||
} |
|||
catch |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
bool success = false; |
|||
try |
|||
{ |
|||
var externalObjects = context.TryGetFeature<IGlContextExternalObjectsFeature>(); |
|||
if (externalObjects == null) |
|||
return null; |
|||
|
|||
var luidMatch = interop.DeviceLuid != null |
|||
&& externalObjects.DeviceLuid != null && |
|||
interop.DeviceLuid.SequenceEqual(externalObjects.DeviceLuid); |
|||
var uuidMatch = interop.DeviceLuid != null |
|||
&& externalObjects.DeviceLuid != null && |
|||
interop.DeviceLuid.SequenceEqual(externalObjects.DeviceLuid); |
|||
|
|||
if (!uuidMatch && !luidMatch) |
|||
return null; |
|||
|
|||
foreach (var imageType in externalObjects.SupportedExportableExternalImageTypes) |
|||
{ |
|||
if(!interop.SupportedImageHandleTypes.Contains(imageType)) |
|||
continue; |
|||
|
|||
var clientCaps = externalObjects.GetSynchronizationCapabilities(imageType); |
|||
var serverCaps = interop.GetSynchronizationCapabilities(imageType); |
|||
var matchingCaps = clientCaps & serverCaps; |
|||
|
|||
var syncMode = |
|||
matchingCaps.HasFlag(CompositionGpuImportedImageSynchronizationCapabilities.Automatic) |
|||
? CompositionGpuImportedImageSynchronizationCapabilities.Automatic |
|||
: matchingCaps.HasFlag(CompositionGpuImportedImageSynchronizationCapabilities.KeyedMutex) |
|||
? CompositionGpuImportedImageSynchronizationCapabilities.KeyedMutex |
|||
: matchingCaps.HasFlag(CompositionGpuImportedImageSynchronizationCapabilities |
|||
.Semaphores) |
|||
? CompositionGpuImportedImageSynchronizationCapabilities.Semaphores |
|||
: default; |
|||
if (syncMode == default) |
|||
continue; |
|||
|
|||
if (syncMode == CompositionGpuImportedImageSynchronizationCapabilities.Semaphores) |
|||
{ |
|||
var semaphoreType = externalObjects.SupportedExportableExternalSemaphoreTypes |
|||
.FirstOrDefault(interop.SupportedSemaphoreTypes.Contains); |
|||
if(semaphoreType == null) |
|||
continue; |
|||
success = true; |
|||
return new CompositionGlContextViaExternalObjects(compositor, context, interop, |
|||
externalObjects, imageType, syncMode, semaphoreType); |
|||
} |
|||
success = true; |
|||
return new CompositionGlContextViaExternalObjects(compositor, context, interop, |
|||
externalObjects, |
|||
imageType, syncMode, null); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
if(!success) |
|||
context.Dispose(); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public class OpenGLCompositionInteropContextCreationOptions |
|||
{ |
|||
public List<GlVersion>? VersionPreferences { get; set; } |
|||
} |
|||
Loading…
Reference in new issue