diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 3fa8e969c8..2f034bd083 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -5,6 +5,7 @@
"packages\\Avalonia\\Avalonia.csproj",
"samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
"samples\\ControlCatalog\\ControlCatalog.csproj",
+ "samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
diff --git a/Avalonia.sln b/Avalonia.sln
index fc42a5d63b..ce9a37a3ce 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -231,6 +231,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -542,6 +544,10 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -606,6 +612,7 @@ Global
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/build/Base.props b/build/Base.props
index 26f19e3abc..c2fc11c94b 100644
--- a/build/Base.props
+++ b/build/Base.props
@@ -1,6 +1,7 @@
+
diff --git a/build/SharpDX.props b/build/SharpDX.props
index 69aa817a01..ff521977fd 100644
--- a/build/SharpDX.props
+++ b/build/SharpDX.props
@@ -1,9 +1,14 @@
+
+ 4.0.1
+
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
index ded02330d5..c77d65ddf1 100644
--- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
@@ -78,12 +78,7 @@ namespace ControlCatalog.Pages
get => _info;
private set => SetAndRaise(InfoProperty, ref _info, value);
}
-
- static OpenGlPageControl()
- {
- AffectsRender(YawProperty, PitchProperty, RollProperty, DiscoProperty);
- }
-
+
private int _vertexShader;
private int _fragmentShader;
private int _shaderProgram;
@@ -254,7 +249,7 @@ namespace ControlCatalog.Pages
Console.WriteLine(err);
}
- protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
+ protected override unsafe void OnOpenGlInit(GlInterface GL)
{
CheckError(GL);
@@ -309,7 +304,7 @@ namespace ControlCatalog.Pages
}
- protected override void OnOpenGlDeinit(GlInterface GL, int fb)
+ protected override void OnOpenGlDeinit(GlInterface GL)
{
// Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0);
@@ -366,7 +361,15 @@ namespace ControlCatalog.Pages
CheckError(GL);
if (_disco > 0.01)
- Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
+ RequestNextFrameRendering();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if (change.Property == YawProperty || change.Property == RollProperty || change.Property == PitchProperty ||
+ change.Property == DiscoProperty)
+ RequestNextFrameRendering();
+ base.OnPropertyChanged(change);
}
}
}
diff --git a/samples/GpuInterop/App.axaml b/samples/GpuInterop/App.axaml
new file mode 100644
index 0000000000..6b18dbe520
--- /dev/null
+++ b/samples/GpuInterop/App.axaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/samples/GpuInterop/App.axaml.cs b/samples/GpuInterop/App.axaml.cs
new file mode 100644
index 0000000000..85973d59bb
--- /dev/null
+++ b/samples/GpuInterop/App.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace GpuInterop
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+ {
+ desktopLifetime.MainWindow = new MainWindow();
+ }
+ }
+ }
+}
diff --git a/samples/GpuInterop/D3DDemo/D3D11DemoControl.cs b/samples/GpuInterop/D3DDemo/D3D11DemoControl.cs
new file mode 100644
index 0000000000..c7ee2bb9a4
--- /dev/null
+++ b/samples/GpuInterop/D3DDemo/D3D11DemoControl.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Numerics;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using SharpDX;
+using SharpDX.Direct2D1;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+using SharpDX.Mathematics.Interop;
+using Buffer = SharpDX.Direct3D11.Buffer;
+using DeviceContext = SharpDX.Direct2D1.DeviceContext;
+using DxgiFactory1 = SharpDX.DXGI.Factory1;
+using Matrix = SharpDX.Matrix;
+using D3DDevice = SharpDX.Direct3D11.Device;
+using DxgiResource = SharpDX.DXGI.Resource;
+using FeatureLevel = SharpDX.Direct3D.FeatureLevel;
+using Vector3 = SharpDX.Vector3;
+
+namespace GpuInterop.D3DDemo;
+
+public class D3D11DemoControl : DrawingSurfaceDemoBase
+{
+ private D3DDevice _device;
+ private D3D11Swapchain _swapchain;
+ private SharpDX.Direct3D11.DeviceContext _context;
+ private Matrix _view;
+ private PixelSize _lastSize;
+ private Texture2D _depthBuffer;
+ private DepthStencilView _depthView;
+ private Matrix _proj;
+ private Buffer _constantBuffer;
+ private Stopwatch _st = Stopwatch.StartNew();
+
+ protected override (bool success, string info) InitializeGraphicsResources(Compositor compositor,
+ CompositionDrawingSurface surface, ICompositionGpuInterop interop)
+ {
+ if (interop?.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
+ .D3D11TextureGlobalSharedHandle) != true)
+ return (false, "DXGI shared handle import is not supported by the current graphics backend");
+
+ var factory = new DxgiFactory1();
+ using var adapter = factory.GetAdapter1(0);
+ _device = new D3DDevice(adapter, DeviceCreationFlags.None, new[]
+ {
+ FeatureLevel.Level_12_1,
+ FeatureLevel.Level_12_0,
+ FeatureLevel.Level_11_1,
+ FeatureLevel.Level_11_0,
+ FeatureLevel.Level_10_0,
+ FeatureLevel.Level_9_3,
+ FeatureLevel.Level_9_2,
+ FeatureLevel.Level_9_1,
+ });
+ _swapchain = new D3D11Swapchain(_device, interop, surface);
+ _context = _device.ImmediateContext;
+ _constantBuffer = D3DContent.CreateMesh(_device);
+ _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY);
+ return (true, $"D3D11 ({_device.FeatureLevel}) {adapter.Description1.Description}");
+ }
+
+ protected override void FreeGraphicsResources()
+ {
+ _swapchain.DisposeAsync();
+ _swapchain = null!;
+ Utilities.Dispose(ref _depthView);
+ Utilities.Dispose(ref _depthBuffer);
+ Utilities.Dispose(ref _constantBuffer);
+ Utilities.Dispose(ref _context);
+ Utilities.Dispose(ref _device);
+ }
+
+ protected override bool SupportsDisco => true;
+
+ protected override void RenderFrame(PixelSize pixelSize)
+ {
+ if (pixelSize == default)
+ return;
+ if (pixelSize != _lastSize)
+ Resize(pixelSize);
+ using (_swapchain.BeginDraw(pixelSize, out var renderView))
+ {
+
+ _device.ImmediateContext.OutputMerger.SetTargets(_depthView, renderView);
+ var viewProj = Matrix.Multiply(_view, _proj);
+ var context = _device.ImmediateContext;
+
+ var now = _st.Elapsed.TotalSeconds * 5;
+ var scaleX = (float)(1f + Disco * (Math.Sin(now) + 1) / 6);
+ var scaleY = (float)(1f + Disco * (Math.Cos(now) + 1) / 8);
+ var colorOff =(float) (Math.Sin(now) + 1) / 2 * Disco;
+
+
+ // Clear views
+ context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
+ context.ClearRenderTargetView(renderView,
+ new RawColor4(1 - colorOff, colorOff, (float)0.5 + colorOff / 2, 1));
+
+
+ var ypr = Matrix4x4.CreateFromYawPitchRoll(Yaw, Pitch, Roll);
+ // Update WorldViewProj Matrix
+ var worldViewProj = Matrix.RotationX((float)Yaw) * Matrix.RotationY((float)Pitch)
+ * Matrix.RotationZ((float)Roll)
+ * Matrix.Scaling(new Vector3(scaleX, scaleY, 1))
+ * viewProj;
+ worldViewProj.Transpose();
+ context.UpdateSubresource(ref worldViewProj, _constantBuffer);
+
+ // Draw the cube
+ context.Draw(36, 0);
+
+
+ _context.Flush();
+ }
+ }
+
+ private void Resize(PixelSize size)
+ {
+ Utilities.Dispose(ref _depthBuffer);
+ _depthBuffer = new Texture2D(_device,
+ new Texture2DDescription()
+ {
+ Format = Format.D32_Float_S8X24_UInt,
+ ArraySize = 1,
+ MipLevels = 1,
+ Width = (int)size.Width,
+ Height = (int)size.Height,
+ SampleDescription = new SampleDescription(1, 0),
+ Usage = ResourceUsage.Default,
+ BindFlags = BindFlags.DepthStencil,
+ CpuAccessFlags = CpuAccessFlags.None,
+ OptionFlags = ResourceOptionFlags.None
+ });
+
+ Utilities.Dispose(ref _depthView);
+ _depthView = new DepthStencilView(_device, _depthBuffer);
+
+ // Setup targets and viewport for rendering
+ _device.ImmediateContext.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f));
+
+ // Setup new projection matrix with correct aspect ratio
+ _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f);
+ }
+}
diff --git a/samples/GpuInterop/D3DDemo/D3D11Swapchain.cs b/samples/GpuInterop/D3DDemo/D3D11Swapchain.cs
new file mode 100644
index 0000000000..d2cf43ac74
--- /dev/null
+++ b/samples/GpuInterop/D3DDemo/D3D11Swapchain.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+using DxgiFactory1 = SharpDX.DXGI.Factory1;
+using D3DDevice = SharpDX.Direct3D11.Device;
+using DxgiResource = SharpDX.DXGI.Resource;
+
+namespace GpuInterop.D3DDemo;
+
+class D3D11Swapchain : SwapchainBase
+{
+ private readonly D3DDevice _device;
+
+ public D3D11Swapchain(D3DDevice device, ICompositionGpuInterop interop, CompositionDrawingSurface target)
+ : base(interop, target)
+ {
+ _device = device;
+ }
+
+ protected override D3D11SwapchainImage CreateImage(PixelSize size) => new(_device, size, Interop, Target);
+
+ public IDisposable BeginDraw(PixelSize size, out RenderTargetView view)
+ {
+ var rv = BeginDrawCore(size, out var image);
+ view = image.RenderTargetView;
+ return rv;
+ }
+}
+
+public class D3D11SwapchainImage : ISwapchainImage
+{
+ public PixelSize Size { get; }
+ private readonly ICompositionGpuInterop _interop;
+ private readonly CompositionDrawingSurface _target;
+ private readonly Texture2D _texture;
+ private readonly KeyedMutex _mutex;
+ private readonly IntPtr _handle;
+ private PlatformGraphicsExternalImageProperties _properties;
+ private ICompositionImportedGpuImage? _imported;
+ public Task? LastPresent { get; private set; }
+ public RenderTargetView RenderTargetView { get; }
+
+ public D3D11SwapchainImage(D3DDevice device, PixelSize size,
+ ICompositionGpuInterop interop,
+ CompositionDrawingSurface target)
+ {
+ Size = size;
+ _interop = interop;
+ _target = target;
+ _texture = new Texture2D(device,
+ new Texture2DDescription
+ {
+ Format = Format.R8G8B8A8_UNorm,
+ Width = size.Width,
+ Height = size.Height,
+ ArraySize = 1,
+ MipLevels = 1,
+ SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
+ CpuAccessFlags = default,
+ OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
+ BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
+ });
+ _mutex = _texture.QueryInterface();
+ using (var res = _texture.QueryInterface())
+ _handle = res.SharedHandle;
+ _properties = new PlatformGraphicsExternalImageProperties
+ {
+ Width = size.Width, Height = size.Height, Format = PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm
+ };
+
+ RenderTargetView = new RenderTargetView(device, _texture);
+ }
+
+ public void BeginDraw()
+ {
+ _mutex.Acquire(0, int.MaxValue);
+ }
+
+ public void Present()
+ {
+ _mutex.Release(1);
+ _imported ??= _interop.ImportImage(
+ new PlatformHandle(_handle, KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle),
+ _properties);
+ LastPresent = _target.UpdateWithKeyedMutexAsync(_imported, 1, 0);
+ }
+
+
+ public async ValueTask DisposeAsync()
+ {
+ if (LastPresent != null)
+ try
+ {
+ await LastPresent;
+ }
+ catch
+ {
+ // Ignore
+ }
+
+ RenderTargetView.Dispose();
+ _mutex.Dispose();
+ _texture.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/samples/GpuInterop/D3DDemo/D3DContent.cs b/samples/GpuInterop/D3DDemo/D3DContent.cs
new file mode 100644
index 0000000000..f670a9a8c9
--- /dev/null
+++ b/samples/GpuInterop/D3DDemo/D3DContent.cs
@@ -0,0 +1,110 @@
+using SharpDX;
+using SharpDX.D3DCompiler;
+using SharpDX.Direct3D;
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using SharpDX.Direct2D1;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+using SharpDX.Mathematics.Interop;
+using Buffer = SharpDX.Direct3D11.Buffer;
+using DeviceContext = SharpDX.Direct2D1.DeviceContext;
+using DxgiFactory1 = SharpDX.DXGI.Factory1;
+using Matrix = SharpDX.Matrix;
+using D3DDevice = SharpDX.Direct3D11.Device;
+using DxgiResource = SharpDX.DXGI.Resource;
+using FeatureLevel = SharpDX.Direct3D.FeatureLevel;
+using InputElement = SharpDX.Direct3D11.InputElement;
+
+
+namespace GpuInterop.D3DDemo;
+
+public class D3DContent
+{
+
+ public static Buffer CreateMesh(D3DDevice device)
+ {
+ // Compile Vertex and Pixel shaders
+ var vertexShaderByteCode = ShaderBytecode.CompileFromFile("D3DDemo\\MiniCube.fx", "VS", "vs_4_0");
+ var vertexShader = new VertexShader(device, vertexShaderByteCode);
+
+ var pixelShaderByteCode = ShaderBytecode.CompileFromFile("D3DDemo\\MiniCube.fx", "PS", "ps_4_0");
+ var pixelShader = new PixelShader(device, pixelShaderByteCode);
+
+ var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode);
+
+ var inputElements = new[]
+ {
+ new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
+ new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0)
+ };
+
+ // Layout from VertexShader input signature
+ var layout = new InputLayout(
+ device,
+ signature,
+ inputElements);
+
+ // Instantiate Vertex buffer from vertex data
+ using var vertices = Buffer.Create(
+ device,
+ BindFlags.VertexBuffer,
+ new[]
+ {
+ new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front
+ new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK
+ new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top
+ new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom
+ new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left
+ new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right
+ new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
+ new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
+ });
+
+ // Create Constant Buffer
+ var constantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default,
+ BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
+
+ var context = device.ImmediateContext;
+
+ // Prepare All the stages
+ context.InputAssembler.InputLayout = layout;
+ context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
+ context.InputAssembler.SetVertexBuffers(0,
+ new VertexBufferBinding(vertices, Utilities.SizeOf() * 2, 0));
+ context.VertexShader.SetConstantBuffer(0, constantBuffer);
+ context.VertexShader.Set(vertexShader);
+ context.PixelShader.Set(pixelShader);
+ return constantBuffer;
+ }
+}
diff --git a/samples/GpuInterop/D3DDemo/MiniCube.fx b/samples/GpuInterop/D3DDemo/MiniCube.fx
new file mode 100644
index 0000000000..c6064ea56c
--- /dev/null
+++ b/samples/GpuInterop/D3DDemo/MiniCube.fx
@@ -0,0 +1,47 @@
+// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+struct VS_IN
+{
+ float4 pos : POSITION;
+ float4 col : COLOR;
+};
+
+struct PS_IN
+{
+ float4 pos : SV_POSITION;
+ float4 col : COLOR;
+};
+
+float4x4 worldViewProj;
+
+PS_IN VS( VS_IN input )
+{
+ PS_IN output = (PS_IN)0;
+
+ output.pos = mul(input.pos, worldViewProj);
+ output.col = input.col;
+
+ return output;
+}
+
+float4 PS( PS_IN input ) : SV_Target
+{
+ return input.col;
+}
diff --git a/samples/GpuInterop/DrawingSurfaceDemoBase.cs b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
new file mode 100644
index 0000000000..aad813ea82
--- /dev/null
+++ b/samples/GpuInterop/DrawingSurfaceDemoBase.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Numerics;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Rendering.Composition;
+using Avalonia.VisualTree;
+
+namespace GpuInterop;
+
+public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
+{
+ private CompositionSurfaceVisual? _visual;
+ private Compositor? _compositor;
+ private Action _update;
+ private string _info;
+ private bool _updateQueued;
+ private bool _initialized;
+
+ protected CompositionDrawingSurface Surface { get; private set; }
+
+ public DrawingSurfaceDemoBase()
+ {
+ _update = UpdateFrame;
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ Initialize();
+ }
+
+ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ if (_initialized)
+ FreeGraphicsResources();
+ _initialized = false;
+ base.OnDetachedFromLogicalTree(e);
+ }
+
+ async void Initialize()
+ {
+ try
+ {
+ var selfVisual = ElementComposition.GetElementVisual(this)!;
+ _compositor = selfVisual.Compositor;
+
+ Surface = _compositor.CreateDrawingSurface();
+ _visual = _compositor.CreateSurfaceVisual();
+ _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+ _visual.Surface = Surface;
+ ElementComposition.SetElementChildVisual(this, _visual);
+ var (res, info) = await DoInitialize(_compositor, Surface);
+ _info = info;
+ if (ParentControl != null)
+ ParentControl.Info = info;
+ _initialized = res;
+ QueueNextFrame();
+ }
+ catch (Exception e)
+ {
+ if (ParentControl != null)
+ ParentControl.Info = e.ToString();
+ }
+ }
+
+ void UpdateFrame()
+ {
+ _updateQueued = false;
+ var root = this.GetVisualRoot();
+ if (root == null)
+ return;
+
+ _visual!.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+ var size = PixelSize.FromSize(Bounds.Size, root.RenderScaling);
+ RenderFrame(size);
+ if (SupportsDisco && Disco > 0)
+ QueueNextFrame();
+ }
+
+ void QueueNextFrame()
+ {
+ if (_initialized && !_updateQueued && _compositor != null)
+ {
+ _updateQueued = true;
+ _compositor?.RequestCompositionUpdate(_update);
+ }
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if(change.Property == BoundsProperty)
+ QueueNextFrame();
+ base.OnPropertyChanged(change);
+ }
+
+ async Task<(bool success, string info)> DoInitialize(Compositor compositor,
+ CompositionDrawingSurface compositionDrawingSurface)
+ {
+ var interop = await compositor.TryGetCompositionGpuInterop();
+ if (interop == null)
+ return (false, "Compositor doesn't support interop for the current backend");
+ return InitializeGraphicsResources(compositor, compositionDrawingSurface, interop);
+ }
+
+ protected abstract (bool success, string info) InitializeGraphicsResources(Compositor compositor,
+ CompositionDrawingSurface compositionDrawingSurface, ICompositionGpuInterop gpuInterop);
+
+ protected abstract void FreeGraphicsResources();
+
+
+ protected abstract void RenderFrame(PixelSize pixelSize);
+ protected virtual bool SupportsDisco => false;
+
+ public void Update(GpuDemo parent, float yaw, float pitch, float roll, float disco)
+ {
+ ParentControl = parent;
+ if (ParentControl != null)
+ {
+ ParentControl.Info = _info;
+ ParentControl.DiscoVisible = true;
+ }
+
+ Yaw = yaw;
+ Pitch = pitch;
+ Roll = roll;
+ Disco = disco;
+ QueueNextFrame();
+ }
+
+ public GpuDemo? ParentControl { get; private set; }
+
+ public float Disco { get; private set; }
+
+ public float Roll { get; private set; }
+
+ public float Pitch { get; private set; }
+
+ public float Yaw { get; private set; }
+}
diff --git a/samples/GpuInterop/GpuDemo.axaml b/samples/GpuInterop/GpuDemo.axaml
new file mode 100644
index 0000000000..6ca51abadb
--- /dev/null
+++ b/samples/GpuInterop/GpuDemo.axaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+ Yaw
+
+ Pitch
+
+ Roll
+
+
+
+ D
+ I
+ S
+ C
+ O
+
+
+
+
+
+
+
diff --git a/samples/GpuInterop/GpuDemo.axaml.cs b/samples/GpuInterop/GpuDemo.axaml.cs
new file mode 100644
index 0000000000..f7acac09e1
--- /dev/null
+++ b/samples/GpuInterop/GpuDemo.axaml.cs
@@ -0,0 +1,116 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace GpuInterop;
+
+public class GpuDemo : UserControl
+{
+ public GpuDemo()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private float _yaw = 5;
+
+ public static readonly DirectProperty YawProperty =
+ AvaloniaProperty.RegisterDirect("Yaw", o => o.Yaw, (o, v) => o.Yaw = v);
+
+ public float Yaw
+ {
+ get => _yaw;
+ set => SetAndRaise(YawProperty, ref _yaw, value);
+ }
+
+ private float _pitch = 5;
+
+ public static readonly DirectProperty PitchProperty =
+ AvaloniaProperty.RegisterDirect("Pitch", o => o.Pitch, (o, v) => o.Pitch = v);
+
+ public float Pitch
+ {
+ get => _pitch;
+ set => SetAndRaise(PitchProperty, ref _pitch, value);
+ }
+
+
+ private float _roll = 5;
+
+ public static readonly DirectProperty RollProperty =
+ AvaloniaProperty.RegisterDirect("Roll", o => o.Roll, (o, v) => o.Roll = v);
+
+ public float Roll
+ {
+ get => _roll;
+ set => SetAndRaise(RollProperty, ref _roll, value);
+ }
+
+
+ private float _disco;
+
+ public static readonly DirectProperty DiscoProperty =
+ AvaloniaProperty.RegisterDirect("Disco", o => o.Disco, (o, v) => o.Disco = v);
+
+ public float Disco
+ {
+ get => _disco;
+ set => SetAndRaise(DiscoProperty, ref _disco, value);
+ }
+
+ private string _info = string.Empty;
+
+ public static readonly DirectProperty InfoProperty =
+ AvaloniaProperty.RegisterDirect("Info", o => o.Info, (o, v) => o.Info = v);
+
+ public string Info
+ {
+ get => _info;
+ set => SetAndRaise(InfoProperty, ref _info, value);
+ }
+
+ private bool _discoVisible;
+
+ public static readonly DirectProperty DiscoVisibleProperty =
+ AvaloniaProperty.RegisterDirect("DiscoVisible", o => o.DiscoVisible,
+ (o, v) => o._discoVisible = v);
+
+ public bool DiscoVisible
+ {
+ get => _discoVisible;
+ set => SetAndRaise(DiscoVisibleProperty, ref _discoVisible, value);
+ }
+
+ private IGpuDemo _demo;
+
+ public static readonly DirectProperty DemoProperty =
+ AvaloniaProperty.RegisterDirect("Demo", o => o.Demo,
+ (o, v) => o._demo = v);
+
+ public IGpuDemo Demo
+ {
+ get => _demo;
+ set => SetAndRaise(DemoProperty, ref _demo, value);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if (change.Property == YawProperty
+ || change.Property == PitchProperty
+ || change.Property == RollProperty
+ || change.Property == DiscoProperty
+ || change.Property == DemoProperty
+ )
+ {
+ if (change.Property == DemoProperty)
+ ((IGpuDemo)change.OldValue)?.Update(null, 0, 0, 0, 0);
+ _demo?.Update(this, Yaw, Pitch, Roll, Disco);
+ }
+
+ base.OnPropertyChanged(change);
+ }
+}
+
+public interface IGpuDemo
+{
+ void Update(GpuDemo parent, float yaw, float pitch, float roll, float disco);
+}
diff --git a/samples/GpuInterop/GpuInterop.csproj b/samples/GpuInterop/GpuInterop.csproj
new file mode 100644
index 0000000000..88e6d3d283
--- /dev/null
+++ b/samples/GpuInterop/GpuInterop.csproj
@@ -0,0 +1,51 @@
+
+
+
+ Exe
+ net7.0
+ true
+ enable
+ false
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/GpuInterop/MainWindow.axaml b/samples/GpuInterop/MainWindow.axaml
new file mode 100644
index 0000000000..afb025ec27
--- /dev/null
+++ b/samples/GpuInterop/MainWindow.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/samples/GpuInterop/MainWindow.axaml.cs b/samples/GpuInterop/MainWindow.axaml.cs
new file mode 100644
index 0000000000..8fc8926783
--- /dev/null
+++ b/samples/GpuInterop/MainWindow.axaml.cs
@@ -0,0 +1,21 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace GpuInterop
+{
+ public class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ this.AttachDevTools();
+ this.Renderer.DrawFps = true;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/GpuInterop/Program.cs b/samples/GpuInterop/Program.cs
new file mode 100644
index 0000000000..8d7ccf4866
--- /dev/null
+++ b/samples/GpuInterop/Program.cs
@@ -0,0 +1,15 @@
+global using System.Reactive.Disposables;
+using Avalonia;
+namespace GpuInterop
+{
+ public class Program
+ {
+ static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ public static AppBuilder BuildAvaloniaApp() =>
+ AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace();
+ }
+}
diff --git a/samples/GpuInterop/VulkanDemo/Assets/Shaders/Makefile b/samples/GpuInterop/VulkanDemo/Assets/Shaders/Makefile
new file mode 100644
index 0000000000..900e35a93e
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/Assets/Shaders/Makefile
@@ -0,0 +1,12 @@
+#!/usr/bin/make -f
+
+all: vert.spirv frag.spirv
+.PHONY: all
+
+vert.spirv: vert.glsl
+ glslc -fshader-stage=vert vert.glsl -o vert.spirv
+
+frag.spirv: frag.glsl
+ glslc -fshader-stage=frag frag.glsl -o frag.spirv
+
+
diff --git a/samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.glsl b/samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.glsl
new file mode 100644
index 0000000000..31e9f00390
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.glsl
@@ -0,0 +1,42 @@
+#version 450
+ layout(location = 0) in vec3 FragPos;
+ layout(location = 1) in vec3 VecPos;
+ layout(location = 2) in vec3 Normal;
+ layout(push_constant) uniform constants{
+ layout(offset = 0) float maxY;
+ layout(offset = 4) float minY;
+ layout(offset = 8) float time;
+ layout(offset = 12) float disco;
+ };
+ layout(location = 0) out vec4 outFragColor;
+
+ void main()
+ {
+ float y = (VecPos.y - minY) / (maxY - minY);
+ float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + time * 40.0 + y * 50.0);
+ float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - time * 20.0 - y * 30.0);
+
+ vec3 discoColor = vec3(
+ 0.5 + abs(0.5 - y) * cos(time * 10.0),
+ 0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)),
+ 0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0))));
+
+ vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25);
+ objectColor = objectColor * (1.0 - disco) + discoColor * disco;
+
+ float ambientStrength = 0.3;
+ vec3 lightColor = vec3(1.0, 1.0, 1.0);
+ vec3 lightPos = vec3(maxY * 2.0, maxY * 2.0, maxY * 2.0);
+ vec3 ambient = ambientStrength * lightColor;
+
+
+ vec3 norm = normalize(Normal);
+ vec3 lightDir = normalize(lightPos - FragPos);
+
+ float diff = max(dot(norm, lightDir), 0.0);
+ vec3 diffuse = diff * lightColor;
+
+ vec3 result = (ambient + diffuse) * objectColor;
+ outFragColor = vec4(result, 1.0);
+
+ }
\ No newline at end of file
diff --git a/samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv b/samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv
new file mode 100644
index 0000000000..dbac67c368
Binary files /dev/null and b/samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv differ
diff --git a/samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.glsl b/samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.glsl
new file mode 100644
index 0000000000..c700d78d25
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.glsl
@@ -0,0 +1,36 @@
+#version 450
+ layout(location = 0) in vec3 aPos;
+ layout(location = 1) in vec3 aNormal;
+
+ layout(location = 0) out vec3 FragPos;
+ layout(location = 1) out vec3 VecPos;
+ layout(location = 2) out vec3 Normal;
+
+ layout(push_constant) uniform constants{
+ float maxY;
+ float minY;
+ float time;
+ float disco;
+ mat4 model;
+ };
+
+ layout(binding = 0) uniform UniformBufferObject {
+ mat4 projection;
+ } ubo;
+
+ void main()
+ {
+ float discoScale = sin(time * 10.0) / 10.0;
+ float distortionX = 1.0 + disco * cos(time * 20.0) / 10.0;
+
+ float scale = 1.0 + disco * discoScale;
+
+ vec3 scaledPos = aPos;
+ scaledPos.x = scaledPos.x * distortionX;
+
+ scaledPos *= scale;
+ gl_Position = ubo.projection * model * vec4(scaledPos, 1.0);
+ FragPos = vec3(model * vec4(aPos, 1.0));
+ VecPos = aPos;
+ Normal = normalize(vec3(model * vec4(aNormal, 1.0)));
+ }
\ No newline at end of file
diff --git a/samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv b/samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv
new file mode 100644
index 0000000000..3bb2ba9383
Binary files /dev/null and b/samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv differ
diff --git a/samples/GpuInterop/VulkanDemo/ByteString.cs b/samples/GpuInterop/VulkanDemo/ByteString.cs
new file mode 100644
index 0000000000..bce66e017d
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/ByteString.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace GpuInterop.VulkanDemo;
+
+unsafe class ByteString : IDisposable
+{
+ public IntPtr Pointer { get; }
+
+ public ByteString(string s)
+ {
+ Pointer = Marshal.StringToHGlobalAnsi(s);
+ }
+
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal(Pointer);
+ }
+
+ public static implicit operator byte*(ByteString h) => (byte*)h.Pointer;
+}
+
+unsafe class ByteStringList : IDisposable
+{
+ private List _inner;
+ private byte** _ptr;
+
+ public ByteStringList(IEnumerable items)
+ {
+ _inner = items.Select(x => new ByteString(x)).ToList();
+ _ptr = (byte**)Marshal.AllocHGlobal(IntPtr.Size * _inner.Count + 1);
+ for (var c = 0; c < _inner.Count; c++)
+ _ptr[c] = (byte*)_inner[c].Pointer;
+ }
+
+ public int Count => _inner.Count;
+ public uint UCount => (uint)_inner.Count;
+
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal(new IntPtr(_ptr));
+ }
+
+ public static implicit operator byte**(ByteStringList h) => h._ptr;
+}
diff --git a/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs b/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
new file mode 100644
index 0000000000..a0b7d32d3b
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Avalonia;
+using SharpDX.Direct3D;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+using D3DDevice = SharpDX.Direct3D11.Device;
+using DxgiFactory1 = SharpDX.DXGI.Factory1;
+namespace GpuInterop.VulkanDemo;
+
+public class D3DMemoryHelper
+{
+ public static D3DDevice CreateDeviceByLuid(Span luid)
+ {
+ var factory = new DxgiFactory1();
+ var longLuid = MemoryMarshal.Cast(luid)[0];
+ for (var c = 0; c < factory.GetAdapterCount1(); c++)
+ {
+ using var adapter = factory.GetAdapter1(0);
+ if (adapter.Description1.Luid != longLuid)
+ continue;
+
+ return new D3DDevice(adapter, DeviceCreationFlags.None,
+ new[]
+ {
+ FeatureLevel.Level_12_1, FeatureLevel.Level_12_0, FeatureLevel.Level_11_1,
+ FeatureLevel.Level_11_0, FeatureLevel.Level_10_0, FeatureLevel.Level_9_3,
+ FeatureLevel.Level_9_2, FeatureLevel.Level_9_1,
+ });
+ }
+
+ throw new ArgumentException("Device with the corresponding LUID not found");
+ }
+
+ public static Texture2D CreateMemoryHandle(D3DDevice device, PixelSize size, Silk.NET.Vulkan.Format format)
+ {
+ if (format != Silk.NET.Vulkan.Format.R8G8B8A8Unorm)
+ throw new ArgumentException("Not supported format");
+ return new Texture2D(device,
+ new Texture2DDescription
+ {
+ Format = Format.R8G8B8A8_UNorm,
+ Width = size.Width,
+ Height = size.Height,
+ ArraySize = 1,
+ MipLevels = 1,
+ SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
+ CpuAccessFlags = default,
+ OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
+ BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
+ });
+ }
+}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs b/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs
new file mode 100644
index 0000000000..949a951a36
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Runtime.CompilerServices;
+using Silk.NET.Vulkan;
+using SilkNetDemo;
+using Buffer = Silk.NET.Vulkan.Buffer;
+using SystemBuffer = System.Buffer;
+
+namespace GpuInterop.VulkanDemo;
+
+static class VulkanBufferHelper
+{
+ public unsafe static void AllocateBuffer(VulkanContext vk,
+ BufferUsageFlags bufferUsageFlags,
+ out Buffer buffer, out DeviceMemory memory,
+ Span initialData) where T:unmanaged
+ {
+ var api = vk.Api;
+ var device = vk.Device;
+
+ var size = Unsafe.SizeOf() * initialData.Length;
+ var bufferInfo = new BufferCreateInfo()
+ {
+ SType = StructureType.BufferCreateInfo,
+ Size = (ulong)size,
+ Usage = bufferUsageFlags,
+ SharingMode = SharingMode.Exclusive
+ };
+ api.CreateBuffer(device, bufferInfo, null, out buffer).ThrowOnError();
+
+ api.GetBufferMemoryRequirements(device, buffer, out var memoryRequirements);
+
+ var physicalDevice = vk.PhysicalDevice;
+
+ var memoryAllocateInfo = new MemoryAllocateInfo
+ {
+ SType = StructureType.MemoryAllocateInfo,
+ AllocationSize = memoryRequirements.Size,
+ MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
+ physicalDevice,
+ memoryRequirements.MemoryTypeBits,
+ MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
+ MemoryPropertyFlags.MemoryPropertyHostVisibleBit)
+ };
+
+ api.AllocateMemory(device, memoryAllocateInfo, null, out memory).ThrowOnError();
+ api.BindBufferMemory(device, buffer, memory, 0);
+ UpdateBufferMemory(vk, memory, initialData);
+ }
+
+ public static unsafe void UpdateBufferMemory(VulkanContext vk, DeviceMemory memory,
+ Span data) where T : unmanaged
+ {
+ var api = vk.Api;
+ var device = vk.Device;
+
+ var size = data.Length * Unsafe.SizeOf();
+ void* pointer = null;
+ api.MapMemory(device, memory, 0, (ulong)size, 0, ref pointer);
+
+ data.CopyTo(new Span(pointer, size));
+
+ api.UnmapMemory(device, memory);
+
+ }
+
+ private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits,
+ MemoryPropertyFlags flags)
+ {
+ api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
+
+ for (var i = 0; i < properties.MemoryTypeCount; i++)
+ {
+ var type = properties.MemoryTypes[i];
+
+ if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
+ }
+
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs b/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
new file mode 100644
index 0000000000..65a05c5226
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Input;
+using Silk.NET.Vulkan;
+using SilkNetDemo;
+
+namespace Avalonia.Vulkan
+{
+ public class VulkanCommandBufferPool : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly Queue _queue;
+ private readonly CommandPool _commandPool;
+
+ private readonly List _usedCommandBuffers = new();
+ private object _lock = new object();
+
+ public unsafe VulkanCommandBufferPool(Vk api, Device device, Queue queue, uint queueFamilyIndex)
+ {
+ _api = api;
+ _device = device;
+ _queue = queue;
+
+ var commandPoolCreateInfo = new CommandPoolCreateInfo
+ {
+ SType = StructureType.CommandPoolCreateInfo,
+ Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit,
+ QueueFamilyIndex = queueFamilyIndex
+ };
+
+ _api.CreateCommandPool(_device, commandPoolCreateInfo, null, out _commandPool)
+ .ThrowOnError();
+ }
+
+ public unsafe void Dispose()
+ {
+ lock (_lock)
+ {
+ FreeUsedCommandBuffers();
+ _api.DestroyCommandPool(_device, _commandPool, null);
+ }
+ }
+
+ private CommandBuffer AllocateCommandBuffer()
+ {
+ var commandBufferAllocateInfo = new CommandBufferAllocateInfo
+ {
+ SType = StructureType.CommandBufferAllocateInfo,
+ CommandPool = _commandPool,
+ CommandBufferCount = 1,
+ Level = CommandBufferLevel.Primary
+ };
+
+ lock (_lock)
+ {
+ _api.AllocateCommandBuffers(_device, commandBufferAllocateInfo, out var commandBuffer);
+
+ return commandBuffer;
+ }
+ }
+
+ public VulkanCommandBuffer CreateCommandBuffer()
+ {
+ return new(_api, _device, _queue, this);
+ }
+
+ public void FreeUsedCommandBuffers()
+ {
+ lock (_lock)
+ {
+ foreach (var usedCommandBuffer in _usedCommandBuffers) usedCommandBuffer.Dispose();
+
+ _usedCommandBuffers.Clear();
+ }
+ }
+
+ private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
+ {
+ lock (_lock)
+ {
+ _usedCommandBuffers.Add(commandBuffer);
+ }
+ }
+
+ public class VulkanCommandBuffer : IDisposable
+ {
+ private readonly VulkanCommandBufferPool _commandBufferPool;
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly Queue _queue;
+ private readonly Fence _fence;
+ private bool _hasEnded;
+ private bool _hasStarted;
+
+ public IntPtr Handle => InternalHandle.Handle;
+
+ internal CommandBuffer InternalHandle { get; }
+
+ internal unsafe VulkanCommandBuffer(Vk api, Device device, Queue queue, VulkanCommandBufferPool commandBufferPool)
+ {
+ _api = api;
+ _device = device;
+ _queue = queue;
+ _commandBufferPool = commandBufferPool;
+
+ InternalHandle = _commandBufferPool.AllocateCommandBuffer();
+
+ var fenceCreateInfo = new FenceCreateInfo()
+ {
+ SType = StructureType.FenceCreateInfo,
+ Flags = FenceCreateFlags.FenceCreateSignaledBit
+ };
+
+ api.CreateFence(device, fenceCreateInfo, null, out _fence);
+ }
+
+ public unsafe void Dispose()
+ {
+ _api.WaitForFences(_device, 1, _fence, true, ulong.MaxValue);
+ lock (_commandBufferPool._lock)
+ {
+ _api.FreeCommandBuffers(_device, _commandBufferPool._commandPool, 1, InternalHandle);
+ }
+ _api.DestroyFence(_device, _fence, null);
+ }
+
+ public void BeginRecording()
+ {
+ if (!_hasStarted)
+ {
+ _hasStarted = true;
+
+ var beginInfo = new CommandBufferBeginInfo
+ {
+ SType = StructureType.CommandBufferBeginInfo,
+ Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit
+ };
+
+ _api.BeginCommandBuffer(InternalHandle, beginInfo);
+ }
+ }
+
+ public void EndRecording()
+ {
+ if (_hasStarted && !_hasEnded)
+ {
+ _hasEnded = true;
+
+ _api.EndCommandBuffer(InternalHandle);
+ }
+ }
+
+ public void Submit()
+ {
+ Submit(null, null, null, _fence);
+ }
+
+ public class KeyedMutexSubmitInfo
+ {
+ public ulong? AcquireKey { get; set; }
+ public ulong? ReleaseKey { get; set; }
+ public DeviceMemory DeviceMemory { get; set; }
+ }
+
+ public unsafe void Submit(
+ ReadOnlySpan waitSemaphores,
+ ReadOnlySpan waitDstStageMask = default,
+ ReadOnlySpan signalSemaphores = default,
+ Fence? fence = null,
+ KeyedMutexSubmitInfo keyedMutex = null)
+ {
+ EndRecording();
+
+ if (!fence.HasValue)
+ fence = _fence;
+
+
+ ulong acquireKey = keyedMutex?.AcquireKey ?? 0, releaseKey = keyedMutex?.ReleaseKey ?? 0;
+ DeviceMemory devMem = keyedMutex?.DeviceMemory ?? default;
+ uint timeout = uint.MaxValue;
+ Win32KeyedMutexAcquireReleaseInfoKHR mutex = default;
+ if (keyedMutex != null)
+ mutex = new Win32KeyedMutexAcquireReleaseInfoKHR
+ {
+ SType = StructureType.Win32KeyedMutexAcquireReleaseInfoKhr,
+ AcquireCount = keyedMutex.AcquireKey.HasValue ? 1u : 0u,
+ ReleaseCount = keyedMutex.ReleaseKey.HasValue ? 1u : 0u,
+ PAcquireKeys = &acquireKey,
+ PReleaseKeys = &releaseKey,
+ PAcquireSyncs = &devMem,
+ PReleaseSyncs = &devMem,
+ PAcquireTimeouts = &timeout
+ };
+
+ fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
+ {
+ fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
+ {
+ var commandBuffer = InternalHandle;
+ var submitInfo = new SubmitInfo
+ {
+ PNext = keyedMutex != null ? &mutex : null,
+ SType = StructureType.SubmitInfo,
+ WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
+ PWaitSemaphores = pWaitSemaphores,
+ PWaitDstStageMask = pWaitDstStageMask,
+ CommandBufferCount = 1,
+ PCommandBuffers = &commandBuffer,
+ SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
+ PSignalSemaphores = pSignalSemaphores,
+ };
+
+ _api.ResetFences(_device, 1, fence.Value);
+
+ _api.QueueSubmit(_queue, 1, submitInfo, fence.Value);
+ }
+ }
+
+ _commandBufferPool.DisposeCommandBuffer(this);
+ }
+ }
+ }
+}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanContent.cs b/samples/GpuInterop/VulkanDemo/VulkanContent.cs
new file mode 100644
index 0000000000..b16343190a
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanContent.cs
@@ -0,0 +1,829 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Threading;
+using Silk.NET.Vulkan;
+using SilkNetDemo;
+using Buffer = System.Buffer;
+using Image = Silk.NET.Vulkan.Image;
+
+namespace GpuInterop.VulkanDemo;
+
+unsafe class VulkanContent : IDisposable
+{
+ private readonly VulkanContext _context;
+ private ShaderModule _vertShader;
+ private ShaderModule _fragShader;
+ private PipelineLayout _pipelineLayout;
+ private RenderPass _renderPass;
+ private Pipeline _pipeline;
+ private DescriptorSetLayout _descriptorSetLayout;
+ private Silk.NET.Vulkan.Buffer _vertexBuffer;
+ private DeviceMemory _vertexBufferMemory;
+ private Silk.NET.Vulkan.Buffer _indexBuffer;
+ private DeviceMemory _indexBufferMemory;
+ private Silk.NET.Vulkan.Buffer _uniformBuffer;
+ private DeviceMemory _uniformBufferMemory;
+ private Framebuffer _framebuffer;
+
+ private Image _depthImage;
+ private DeviceMemory _depthImageMemory;
+ private ImageView _depthImageView;
+
+ public VulkanContent(VulkanContext context)
+ {
+ _context = context;
+ var name = typeof(VulkanContent).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
+ using (var sr = new BinaryReader(typeof(VulkanContent).Assembly.GetManifestResourceStream(name)))
+ {
+ var buf = new byte[sr.ReadInt32()];
+ sr.Read(buf, 0, buf.Length);
+ var points = new float[buf.Length / 4];
+ Buffer.BlockCopy(buf, 0, points, 0, buf.Length);
+ buf = new byte[sr.ReadInt32()];
+ sr.Read(buf, 0, buf.Length);
+ _indices = new ushort[buf.Length / 2];
+ Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length);
+ _points = new Vertex[points.Length / 3];
+ for (var primitive = 0; primitive < points.Length / 3; primitive++)
+ {
+ var srci = primitive * 3;
+ _points[primitive] = new Vertex
+ {
+ Position = new Vector3(points[srci], points[srci + 1], points[srci + 2])
+ };
+ }
+
+ for (int i = 0; i < _indices.Length; i += 3)
+ {
+ Vector3 a = _points[_indices[i]].Position;
+ Vector3 b = _points[_indices[i + 1]].Position;
+ Vector3 c = _points[_indices[i + 2]].Position;
+ var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b));
+
+ _points[_indices[i]].Normal += normal;
+ _points[_indices[i + 1]].Normal += normal;
+ _points[_indices[i + 2]].Normal += normal;
+ }
+
+ for (int i = 0; i < _points.Length; i++)
+ {
+ _points[i].Normal = Vector3.Normalize(_points[i].Normal);
+ _maxY = Math.Max(_maxY, _points[i].Position.Y);
+ _minY = Math.Min(_minY, _points[i].Position.Y);
+ }
+ }
+
+ var api = _context.Api;
+ var device = _context.Device;
+ var vertShaderData = GetShader(false);
+ var fragShaderData = GetShader(true);
+
+ fixed (byte* ptr = vertShaderData)
+ {
+ var shaderCreateInfo = new ShaderModuleCreateInfo()
+ {
+ SType = StructureType.ShaderModuleCreateInfo,
+ CodeSize = (nuint)vertShaderData.Length,
+ PCode = (uint*)ptr,
+ };
+
+ api.CreateShaderModule(device, shaderCreateInfo, null, out _vertShader);
+ }
+
+ fixed (byte* ptr = fragShaderData)
+ {
+ var shaderCreateInfo = new ShaderModuleCreateInfo()
+ {
+ SType = StructureType.ShaderModuleCreateInfo,
+ CodeSize = (nuint)fragShaderData.Length,
+ PCode = (uint*)ptr,
+ };
+
+ api.CreateShaderModule(device, shaderCreateInfo, null, out _fragShader);
+ }
+
+ CreateBuffers();
+ }
+
+ private byte[] GetShader(bool fragment)
+ {
+ var name = typeof(VulkanContent).Assembly.GetManifestResourceNames()
+ .First(x => x.Contains((fragment ? "frag" : "vert") + ".spirv"));
+ using (var sr = typeof(VulkanContent).Assembly.GetManifestResourceStream(name))
+ {
+ using (var mem = new MemoryStream())
+ {
+ sr.CopyTo(mem);
+ return mem.ToArray();
+ }
+ }
+ }
+
+ private PixelSize? _previousImageSize = PixelSize.Empty;
+
+
+ public void Render(VulkanImage image,
+ double yaw, double pitch, double roll, double disco)
+ {
+
+ var api = _context.Api;
+
+ if (image.Size != _previousImageSize)
+ CreateTemporalObjects(image.Size);
+
+ _previousImageSize = image.Size;
+
+
+ var model = Matrix4x4.CreateFromYawPitchRoll((float)yaw, (float)pitch, (float)roll);
+ var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
+ var projection =
+ Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)((float)image.Size.Width / image.Size.Height),
+ 0.01f, 1000);
+
+ var vertexConstant = new VertextPushConstant()
+ {
+ Disco = (float)disco,
+ MinY = _minY,
+ MaxY = _maxY,
+ Model = model,
+ Time = (float)St.Elapsed.TotalSeconds
+ };
+
+ var commandBuffer = _context.Pool.CreateCommandBuffer();
+ commandBuffer.BeginRecording();
+
+ _colorAttachment.TransitionLayout(commandBuffer.InternalHandle,
+ ImageLayout.Undefined, AccessFlags.None,
+ ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentWriteBit);
+
+ var commandBufferHandle = new CommandBuffer(commandBuffer.Handle);
+
+ api.CmdSetViewport(commandBufferHandle, 0, 1,
+ new Viewport()
+ {
+ Width = (float)image.Size.Width,
+ Height = (float)image.Size.Height,
+ MaxDepth = 1,
+ MinDepth = 0,
+ X = 0,
+ Y = 0
+ });
+
+ var scissor = new Rect2D
+ {
+ Extent = new Extent2D((uint?)image.Size.Width, (uint?)image.Size.Height)
+ };
+
+ api.CmdSetScissor(commandBufferHandle, 0, 1, &scissor);
+
+ var clearColor = new ClearValue(new ClearColorValue(1, 0, 0, 0.1f), new ClearDepthStencilValue(1, 0));
+
+ var clearValues = new[] { clearColor, clearColor };
+
+
+ fixed (ClearValue* clearValue = clearValues)
+ {
+ var beginInfo = new RenderPassBeginInfo()
+ {
+ SType = StructureType.RenderPassBeginInfo,
+ RenderPass = _renderPass,
+ Framebuffer = _framebuffer,
+ RenderArea = new Rect2D(new Offset2D(0, 0), new Extent2D((uint?)image.Size.Width, (uint?)image.Size.Height)),
+ ClearValueCount = 2,
+ PClearValues = clearValue
+ };
+
+ api.CmdBeginRenderPass(commandBufferHandle, beginInfo, SubpassContents.Inline);
+ }
+
+ api.CmdBindPipeline(commandBufferHandle, PipelineBindPoint.Graphics, _pipeline);
+
+ var dset = _descriptorSet;
+ api.CmdBindDescriptorSets(commandBufferHandle, PipelineBindPoint.Graphics,
+ _pipelineLayout,0,1, &dset, null);
+
+ api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.FragmentBit, 0,
+ (uint)Marshal.SizeOf(), &vertexConstant);
+ api.CmdBindVertexBuffers(commandBufferHandle, 0, 1, _vertexBuffer, 0);
+ api.CmdBindIndexBuffer(commandBufferHandle, _indexBuffer, 0, IndexType.Uint16);
+
+ api.CmdDrawIndexed(commandBufferHandle, (uint)_indices.Length, 1, 0, 0, 0);
+
+
+ api.CmdEndRenderPass(commandBufferHandle);
+
+ _colorAttachment.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferSrcOptimal, AccessFlags.TransferReadBit);
+ image.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferDstOptimal, AccessFlags.TransferWriteBit);
+
+
+ var srcBlitRegion = new ImageBlit
+ {
+ SrcOffsets = new ImageBlit.SrcOffsetsBuffer
+ {
+ Element0 = new Offset3D(0, 0, 0),
+ Element1 = new Offset3D(image.Size.Width, image.Size.Height, 1),
+ },
+ DstOffsets = new ImageBlit.DstOffsetsBuffer
+ {
+ Element0 = new Offset3D(0, 0, 0),
+ Element1 = new Offset3D(image.Size.Width, image.Size.Height, 1),
+ },
+ SrcSubresource =
+ new ImageSubresourceLayers
+ {
+ AspectMask = ImageAspectFlags.ImageAspectColorBit,
+ BaseArrayLayer = 0,
+ LayerCount = 1,
+ MipLevel = 0
+ },
+ DstSubresource = new ImageSubresourceLayers
+ {
+ AspectMask = ImageAspectFlags.ImageAspectColorBit,
+ BaseArrayLayer = 0,
+ LayerCount = 1,
+ MipLevel = 0
+ }
+ };
+
+ api.CmdBlitImage(commandBuffer.InternalHandle, _colorAttachment.InternalHandle.Value,
+ ImageLayout.TransferSrcOptimal,
+ image.InternalHandle.Value, ImageLayout.TransferDstOptimal, 1, srcBlitRegion, Filter.Linear);
+
+ commandBuffer.Submit();
+ }
+
+ public unsafe void Dispose()
+ {
+ if (_isInit)
+ {
+ var api = _context.Api;
+ var device = _context.Device;
+
+ DestroyTemporalObjects();
+
+ api.DestroyShaderModule(device, _vertShader, null);
+ api.DestroyShaderModule(device, _fragShader, null);
+
+ api.DestroyBuffer(device, _vertexBuffer, null);
+ api.FreeMemory(device, _vertexBufferMemory, null);
+
+ api.DestroyBuffer(device, _indexBuffer, null);
+ api.FreeMemory(device, _indexBufferMemory, null);
+
+
+ }
+
+ _isInit = false;
+ }
+
+ public unsafe void DestroyTemporalObjects()
+ {
+ if (_isInit)
+ {
+ if (_renderPass.Handle != 0)
+ {
+ var api = _context.Api;
+ var device = _context.Device;
+ api.FreeDescriptorSets(_context.Device, _context.DescriptorPool, new[] { _descriptorSet });
+
+ api.DestroyImageView(device, _depthImageView, null);
+ api.DestroyImage(device, _depthImage, null);
+ api.FreeMemory(device, _depthImageMemory, null);
+
+ api.DestroyFramebuffer(device, _framebuffer, null);
+ api.DestroyPipeline(device, _pipeline, null);
+ api.DestroyPipelineLayout(device, _pipelineLayout, null);
+ api.DestroyRenderPass(device, _renderPass, null);
+ api.DestroyDescriptorSetLayout(device, _descriptorSetLayout, null);
+
+ api.DestroyBuffer(device, _uniformBuffer, null);
+ api.FreeMemory(device, _uniformBufferMemory, null);
+ _colorAttachment?.Dispose();
+
+ _colorAttachment = null;
+ _depthImage = default;
+ _depthImageView = default;
+ _depthImageView = default;
+ _framebuffer = default;
+ _pipeline = default;
+ _renderPass = default;
+ _pipelineLayout = default;
+ _descriptorSetLayout = default;
+ _uniformBuffer = default;
+ _uniformBufferMemory = default;
+ }
+ }
+ }
+
+ private unsafe void CreateDepthAttachment(PixelSize size)
+ {
+ var imageCreateInfo = new ImageCreateInfo
+ {
+ SType = StructureType.ImageCreateInfo,
+ ImageType = ImageType.ImageType2D,
+ Format = Format.D32Sfloat,
+ Extent =
+ new Extent3D((uint?)size.Width,
+ (uint?)size.Height, 1),
+ MipLevels = 1,
+ ArrayLayers = 1,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ Tiling = ImageTiling.Optimal,
+ Usage = ImageUsageFlags.ImageUsageDepthStencilAttachmentBit,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = ImageCreateFlags.ImageCreateMutableFormatBit
+ };
+
+ var api = _context.Api;
+ var device = _context.Device;
+ api
+ .CreateImage(device, imageCreateInfo, null, out _depthImage).ThrowOnError();
+
+ api.GetImageMemoryRequirements(device, _depthImage,
+ out var memoryRequirements);
+
+ var memoryAllocateInfo = new MemoryAllocateInfo
+ {
+ SType = StructureType.MemoryAllocateInfo,
+ AllocationSize = memoryRequirements.Size,
+ MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
+ _context.PhysicalDevice,
+ memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
+ };
+
+ api.AllocateMemory(device, memoryAllocateInfo, null,
+ out _depthImageMemory).ThrowOnError();
+
+ api.BindImageMemory(device, _depthImage, _depthImageMemory, 0);
+
+ var componentMapping = new ComponentMapping(
+ ComponentSwizzle.R,
+ ComponentSwizzle.G,
+ ComponentSwizzle.B,
+ ComponentSwizzle.A);
+
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectDepthBit,
+ 0, 1, 0, 1);
+
+ var imageViewCreateInfo = new ImageViewCreateInfo
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = _depthImage,
+ ViewType = ImageViewType.ImageViewType2D,
+ Format = Format.D32Sfloat,
+ Components = componentMapping,
+ SubresourceRange = subresourceRange
+ };
+
+ api
+ .CreateImageView(device, imageViewCreateInfo, null, out _depthImageView)
+ .ThrowOnError();
+ }
+
+ private unsafe void CreateTemporalObjects(PixelSize size)
+ {
+ DestroyTemporalObjects();
+
+ var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
+ var projection =
+ Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)((float)size.Width / size.Height),
+ 0.01f, 1000);
+
+ _colorAttachment = new VulkanImage(_context, (uint)Format.R8G8B8A8Unorm, size, false);
+ CreateDepthAttachment(size);
+
+ var api = _context.Api;
+ var device = _context.Device;
+
+ // create renderpasses
+ var colorAttachment = new AttachmentDescription()
+ {
+ Format = Format.R8G8B8A8Unorm,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ LoadOp = AttachmentLoadOp.Clear,
+ StoreOp = AttachmentStoreOp.Store,
+ InitialLayout = ImageLayout.Undefined,
+ FinalLayout = ImageLayout.ColorAttachmentOptimal,
+ StencilLoadOp = AttachmentLoadOp.DontCare,
+ StencilStoreOp = AttachmentStoreOp.DontCare
+ };
+
+ var depthAttachment = new AttachmentDescription()
+ {
+ Format = Format.D32Sfloat,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ LoadOp = AttachmentLoadOp.Clear,
+ StoreOp = AttachmentStoreOp.DontCare,
+ InitialLayout = ImageLayout.Undefined,
+ FinalLayout = ImageLayout.DepthStencilAttachmentOptimal,
+ StencilLoadOp = AttachmentLoadOp.DontCare,
+ StencilStoreOp = AttachmentStoreOp.DontCare
+ };
+
+ var subpassDependency = new SubpassDependency()
+ {
+ SrcSubpass = Vk.SubpassExternal,
+ DstSubpass = 0,
+ SrcStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
+ SrcAccessMask = 0,
+ DstStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
+ DstAccessMask = AccessFlags.AccessColorAttachmentWriteBit
+ };
+
+ var colorAttachmentReference = new AttachmentReference()
+ {
+ Attachment = 0, Layout = ImageLayout.ColorAttachmentOptimal
+ };
+
+ var depthAttachmentReference = new AttachmentReference()
+ {
+ Attachment = 1, Layout = ImageLayout.DepthStencilAttachmentOptimal
+ };
+
+ var subpassDescription = new SubpassDescription()
+ {
+ PipelineBindPoint = PipelineBindPoint.Graphics,
+ ColorAttachmentCount = 1,
+ PColorAttachments = &colorAttachmentReference,
+ PDepthStencilAttachment = &depthAttachmentReference
+ };
+
+ var attachments = new[] { colorAttachment, depthAttachment };
+
+ fixed (AttachmentDescription* atPtr = attachments)
+ {
+ var renderPassCreateInfo = new RenderPassCreateInfo()
+ {
+ SType = StructureType.RenderPassCreateInfo,
+ AttachmentCount = (uint)attachments.Length,
+ PAttachments = atPtr,
+ SubpassCount = 1,
+ PSubpasses = &subpassDescription,
+ DependencyCount = 1,
+ PDependencies = &subpassDependency
+ };
+
+ api.CreateRenderPass(device, renderPassCreateInfo, null, out _renderPass).ThrowOnError();
+
+
+ // create framebuffer
+ var frameBufferAttachments = new[] { new ImageView(_colorAttachment.ViewHandle), _depthImageView };
+
+ fixed (ImageView* frAtPtr = frameBufferAttachments)
+ {
+ var framebufferCreateInfo = new FramebufferCreateInfo()
+ {
+ SType = StructureType.FramebufferCreateInfo,
+ RenderPass = _renderPass,
+ AttachmentCount = (uint)frameBufferAttachments.Length,
+ PAttachments = frAtPtr,
+ Width = (uint)size.Width,
+ Height = (uint)size.Height,
+ Layers = 1
+ };
+
+ api.CreateFramebuffer(device, framebufferCreateInfo, null, out _framebuffer).ThrowOnError();
+ }
+ }
+
+ // Create pipeline
+ var pname = Marshal.StringToHGlobalAnsi("main");
+ var vertShaderStageInfo = new PipelineShaderStageCreateInfo()
+ {
+ SType = StructureType.PipelineShaderStageCreateInfo,
+ Stage = ShaderStageFlags.ShaderStageVertexBit,
+ Module = _vertShader,
+ PName = (byte*)pname,
+ };
+ var fragShaderStageInfo = new PipelineShaderStageCreateInfo()
+ {
+ SType = StructureType.PipelineShaderStageCreateInfo,
+ Stage = ShaderStageFlags.ShaderStageFragmentBit,
+ Module = _fragShader,
+ PName = (byte*)pname,
+ };
+
+ var stages = new[] { vertShaderStageInfo, fragShaderStageInfo };
+
+ var bindingDescription = Vertex.VertexInputBindingDescription;
+ var attributeDescription = Vertex.VertexInputAttributeDescription;
+
+ fixed (VertexInputAttributeDescription* attrPtr = attributeDescription)
+ {
+ var vertextInputInfo = new PipelineVertexInputStateCreateInfo()
+ {
+ SType = StructureType.PipelineVertexInputStateCreateInfo,
+ VertexAttributeDescriptionCount = (uint)attributeDescription.Length,
+ VertexBindingDescriptionCount = 1,
+ PVertexAttributeDescriptions = attrPtr,
+ PVertexBindingDescriptions = &bindingDescription
+ };
+
+ var inputAssembly = new PipelineInputAssemblyStateCreateInfo()
+ {
+ SType = StructureType.PipelineInputAssemblyStateCreateInfo,
+ Topology = PrimitiveTopology.TriangleList,
+ PrimitiveRestartEnable = false
+ };
+
+ var viewport = new Viewport()
+ {
+ X = 0,
+ Y = 0,
+ Width = (float)size.Width,
+ Height = (float)size.Height,
+ MinDepth = 0,
+ MaxDepth = 1
+ };
+
+ var scissor = new Rect2D()
+ {
+ Offset = new Offset2D(0, 0), Extent = new Extent2D((uint)viewport.Width, (uint)viewport.Height)
+ };
+
+ var pipelineViewPortCreateInfo = new PipelineViewportStateCreateInfo()
+ {
+ SType = StructureType.PipelineViewportStateCreateInfo,
+ ViewportCount = 1,
+ PViewports = &viewport,
+ ScissorCount = 1,
+ PScissors = &scissor
+ };
+
+ var rasterizerStateCreateInfo = new PipelineRasterizationStateCreateInfo()
+ {
+ SType = StructureType.PipelineRasterizationStateCreateInfo,
+ DepthClampEnable = false,
+ RasterizerDiscardEnable = false,
+ PolygonMode = PolygonMode.Fill,
+ LineWidth = 1,
+ CullMode = CullModeFlags.CullModeNone,
+ DepthBiasEnable = false
+ };
+
+ var multisampleStateCreateInfo = new PipelineMultisampleStateCreateInfo()
+ {
+ SType = StructureType.PipelineMultisampleStateCreateInfo,
+ SampleShadingEnable = false,
+ RasterizationSamples = SampleCountFlags.SampleCount1Bit
+ };
+
+ var depthStencilCreateInfo = new PipelineDepthStencilStateCreateInfo()
+ {
+ SType = StructureType.PipelineDepthStencilStateCreateInfo,
+ StencilTestEnable = false,
+ DepthCompareOp = CompareOp.Less,
+ DepthTestEnable = true,
+ DepthWriteEnable = true,
+ DepthBoundsTestEnable = false,
+ };
+
+ var colorBlendAttachmentState = new PipelineColorBlendAttachmentState()
+ {
+ ColorWriteMask = ColorComponentFlags.ColorComponentABit |
+ ColorComponentFlags.ColorComponentRBit |
+ ColorComponentFlags.ColorComponentGBit |
+ ColorComponentFlags.ColorComponentBBit,
+ BlendEnable = false
+ };
+
+ var colorBlendState = new PipelineColorBlendStateCreateInfo()
+ {
+ SType = StructureType.PipelineColorBlendStateCreateInfo,
+ LogicOpEnable = false,
+ AttachmentCount = 1,
+ PAttachments = &colorBlendAttachmentState
+ };
+
+ var dynamicStates = new DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };
+
+ fixed (DynamicState* states = dynamicStates)
+ {
+ var dynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()
+ {
+ SType = StructureType.PipelineDynamicStateCreateInfo,
+ DynamicStateCount = (uint)dynamicStates.Length,
+ PDynamicStates = states
+ };
+
+ var vertexPushConstantRange = new PushConstantRange()
+ {
+ Offset = 0,
+ Size = (uint)Marshal.SizeOf(),
+ StageFlags = ShaderStageFlags.ShaderStageVertexBit
+ };
+
+ var fragPushConstantRange = new PushConstantRange()
+ {
+ //Offset = vertexPushConstantRange.Size,
+ Size = (uint)Marshal.SizeOf(),
+ StageFlags = ShaderStageFlags.ShaderStageFragmentBit
+ };
+
+ var layoutBindingInfo = new DescriptorSetLayoutBinding
+ {
+ Binding = 0,
+ StageFlags = ShaderStageFlags.VertexBit,
+ DescriptorCount = 1,
+ DescriptorType = DescriptorType.UniformBuffer,
+ };
+
+ var layoutInfo = new DescriptorSetLayoutCreateInfo
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ BindingCount = 1,
+ PBindings = &layoutBindingInfo
+ };
+
+ api.CreateDescriptorSetLayout(device, &layoutInfo, null, out _descriptorSetLayout).ThrowOnError();
+
+ var projView = view * projection;
+ VulkanBufferHelper.AllocateBuffer(_context, BufferUsageFlags.UniformBufferBit,
+ out _uniformBuffer,
+ out _uniformBufferMemory, new[]
+ {
+ new UniformBuffer
+ {
+ Projection = projView
+ }
+ });
+
+ var descriptorSetLayout = _descriptorSetLayout;
+ var descriptorCreateInfo = new DescriptorSetAllocateInfo
+ {
+ SType = StructureType.DescriptorSetAllocateInfo,
+ DescriptorPool = _context.DescriptorPool,
+ DescriptorSetCount = 1,
+ PSetLayouts = &descriptorSetLayout
+ };
+ api.AllocateDescriptorSets(device, &descriptorCreateInfo, out _descriptorSet).ThrowOnError();
+
+ var descriptorBufferInfo = new DescriptorBufferInfo
+ {
+ Buffer = _uniformBuffer,
+ Range = (ulong)Unsafe.SizeOf(),
+ };
+ var descriptorWrite = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSet,
+ DescriptorType = DescriptorType.UniformBuffer,
+ DescriptorCount = 1,
+ PBufferInfo = &descriptorBufferInfo,
+ };
+ api.UpdateDescriptorSets(device, 1, &descriptorWrite, 0, null);
+
+ var constants = new[] { vertexPushConstantRange, fragPushConstantRange };
+
+ fixed (PushConstantRange* constant = constants)
+ {
+ var setLayout = _descriptorSetLayout;
+ var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
+ {
+ SType = StructureType.PipelineLayoutCreateInfo,
+ PushConstantRangeCount = (uint)constants.Length,
+ PPushConstantRanges = constant,
+ SetLayoutCount = 1,
+ PSetLayouts = &setLayout
+ };
+
+ api.CreatePipelineLayout(device, pipelineLayoutCreateInfo, null, out _pipelineLayout)
+ .ThrowOnError();
+ }
+
+
+ fixed (PipelineShaderStageCreateInfo* stPtr = stages)
+ {
+ var pipelineCreateInfo = new GraphicsPipelineCreateInfo()
+ {
+ SType = StructureType.GraphicsPipelineCreateInfo,
+ StageCount = 2,
+ PStages = stPtr,
+ PVertexInputState = &vertextInputInfo,
+ PInputAssemblyState = &inputAssembly,
+ PViewportState = &pipelineViewPortCreateInfo,
+ PRasterizationState = &rasterizerStateCreateInfo,
+ PMultisampleState = &multisampleStateCreateInfo,
+ PDepthStencilState = &depthStencilCreateInfo,
+ PColorBlendState = &colorBlendState,
+ PDynamicState = &dynamicStateCreateInfo,
+ Layout = _pipelineLayout,
+ RenderPass = _renderPass,
+ Subpass = 0,
+ BasePipelineHandle = _pipeline.Handle != 0 ? _pipeline : new Pipeline(),
+ BasePipelineIndex = _pipeline.Handle != 0 ? 0 : -1
+ };
+
+ api.CreateGraphicsPipelines(device, new PipelineCache(), 1, &pipelineCreateInfo, null,
+ out _pipeline).ThrowOnError();
+ }
+ }
+ }
+
+ Marshal.FreeHGlobal(pname);
+ _isInit = true;
+ }
+
+ private unsafe void CreateBuffers()
+ {
+ VulkanBufferHelper.AllocateBuffer(_context, BufferUsageFlags.VertexBufferBit, out _vertexBuffer,
+ out _vertexBufferMemory, _points);
+ VulkanBufferHelper.AllocateBuffer(_context, BufferUsageFlags.IndexBufferBit, out _indexBuffer,
+ out _indexBufferMemory, _indices);
+ }
+
+ private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits,
+ MemoryPropertyFlags flags)
+ {
+ api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
+
+ for (var i = 0; i < properties.MemoryTypeCount; i++)
+ {
+ var type = properties.MemoryTypes[i];
+
+ if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
+ }
+
+ return -1;
+ }
+
+
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ private struct Vertex
+ {
+ public Vector3 Position;
+ public Vector3 Normal;
+
+ public static unsafe VertexInputBindingDescription VertexInputBindingDescription
+ {
+ get
+ {
+ return new VertexInputBindingDescription()
+ {
+ Binding = 0,
+ Stride = (uint)Marshal.SizeOf(),
+ InputRate = VertexInputRate.Vertex
+ };
+ }
+ }
+
+ public static unsafe VertexInputAttributeDescription[] VertexInputAttributeDescription
+ {
+ get
+ {
+ return new VertexInputAttributeDescription[]
+ {
+ new VertexInputAttributeDescription
+ {
+ Binding = 0,
+ Location = 0,
+ Format = Format.R32G32B32Sfloat,
+ Offset = (uint)Marshal.OffsetOf("Position")
+ },
+ new VertexInputAttributeDescription
+ {
+ Binding = 0,
+ Location = 1,
+ Format = Format.R32G32B32Sfloat,
+ Offset = (uint)Marshal.OffsetOf("Normal")
+ }
+ };
+ }
+ }
+ }
+
+ private readonly Vertex[] _points;
+ private readonly ushort[] _indices;
+ private readonly float _minY;
+ private readonly float _maxY;
+
+
+ static Stopwatch St = Stopwatch.StartNew();
+ private bool _isInit;
+ private VulkanImage _colorAttachment;
+ private DescriptorSet _descriptorSet;
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ private struct VertextPushConstant
+ {
+ public float MaxY;
+ public float MinY;
+ public float Time;
+ public float Disco;
+ public Matrix4x4 Model;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ private struct UniformBuffer
+ {
+ public Matrix4x4 Projection;
+ }
+}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanContext.cs b/samples/GpuInterop/VulkanDemo/VulkanContext.cs
new file mode 100644
index 0000000000..56041d6965
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanContext.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using Avalonia.Vulkan;
+using Silk.NET.Core;
+using Silk.NET.Core.Native;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.EXT;
+using Silk.NET.Vulkan.Extensions.KHR;
+using SilkNetDemo;
+using SkiaSharp;
+using D3DDevice = SharpDX.Direct3D11.Device;
+using DxgiDevice = SharpDX.DXGI.Device;
+
+namespace GpuInterop.VulkanDemo;
+
+public unsafe class VulkanContext : IDisposable
+{
+ public Vk Api { get; init; }
+ public Instance Instance { get; init; }
+ public PhysicalDevice PhysicalDevice { get; init; }
+ public Device Device { get; init; }
+ public Queue Queue { get; init; }
+ public uint QueueFamilyIndex { get; init; }
+ public VulkanCommandBufferPool Pool { get; init; }
+ public GRContext GrContext { get; init; }
+ public DescriptorPool DescriptorPool { get; init; }
+ public D3DDevice? D3DDevice { get; init; }
+
+ public static (VulkanContext? result, string info) TryCreate(ICompositionGpuInterop gpuInterop)
+ {
+ using var appName = new ByteString("GpuInterop");
+ using var engineName = new ByteString("Test");
+ var applicationInfo = new ApplicationInfo
+ {
+ SType = StructureType.ApplicationInfo,
+ PApplicationName = appName,
+ ApiVersion = new Version32(1, 1, 0),
+ PEngineName = appName,
+ EngineVersion = new Version32(1, 0, 0),
+ ApplicationVersion = new Version32(1, 0, 0)
+ };
+
+ var enabledExtensions = new List()
+ {
+ "VK_KHR_get_physical_device_properties2",
+ "VK_KHR_external_memory_capabilities",
+ "VK_KHR_external_semaphore_capabilities"
+ };
+
+ var enabledLayers = new List();
+
+ Vk api = Vk.GetApi();
+ enabledExtensions.Add("VK_EXT_debug_utils");
+ if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation"))
+ enabledLayers.Add("VK_LAYER_KHRONOS_validation");
+
+
+ Instance vkInstance = default;
+ Silk.NET.Vulkan.PhysicalDevice physicalDevice = default;
+ Device device = default;
+ DescriptorPool descriptorPool = default;
+ VulkanCommandBufferPool? pool = null;
+ GRContext? grContext = null;
+ try
+ {
+ using var pRequiredExtensions = new ByteStringList(enabledExtensions);
+ using var pEnabledLayers = new ByteStringList(enabledLayers);
+ api.CreateInstance(new InstanceCreateInfo
+ {
+ SType = StructureType.InstanceCreateInfo,
+ PApplicationInfo = &applicationInfo,
+ PpEnabledExtensionNames = pRequiredExtensions,
+ EnabledExtensionCount = pRequiredExtensions.UCount,
+ PpEnabledLayerNames = pEnabledLayers,
+ EnabledLayerCount = pEnabledLayers.UCount
+ }, null, out vkInstance).ThrowOnError();
+
+
+ if (api.TryGetInstanceExtension(vkInstance, out ExtDebugUtils debugUtils))
+ {
+ var debugCreateInfo = new DebugUtilsMessengerCreateInfoEXT
+ {
+ SType = StructureType.DebugUtilsMessengerCreateInfoExt,
+ MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
+ DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
+ DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
+ MessageType = DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
+ DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
+ DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
+ PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback),
+ };
+
+ debugUtils.CreateDebugUtilsMessenger(vkInstance, debugCreateInfo, null, out var messenger);
+ }
+
+ var requireDeviceExtensions = new List
+ {
+ "VK_KHR_external_memory",
+ "VK_KHR_external_semaphore"
+ };
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
+ .D3D11TextureGlobalSharedHandle)
+ )
+ return (null, "Image sharing is not supported by the current backend");
+ requireDeviceExtensions.Add(KhrExternalMemoryWin32.ExtensionName);
+ requireDeviceExtensions.Add(KhrExternalSemaphoreWin32.ExtensionName);
+ requireDeviceExtensions.Add("VK_KHR_dedicated_allocation");
+ requireDeviceExtensions.Add("VK_KHR_get_memory_requirements2");
+ }
+ else
+ {
+ if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
+ .VulkanOpaquePosixFileDescriptor)
+ || !gpuInterop.SupportedSemaphoreTypes.Contains(KnownPlatformGraphicsExternalSemaphoreHandleTypes
+ .VulkanOpaquePosixFileDescriptor)
+ )
+ return (null, "Image sharing is not supported by the current backend");
+ requireDeviceExtensions.Add(KhrExternalMemoryFd.ExtensionName);
+ requireDeviceExtensions.Add(KhrExternalSemaphoreFd.ExtensionName);
+ }
+
+ uint count = 0;
+ api.EnumeratePhysicalDevices(vkInstance, ref count, null).ThrowOnError();
+ var physicalDevices = stackalloc PhysicalDevice[(int)count];
+ api.EnumeratePhysicalDevices(vkInstance, ref count, physicalDevices)
+ .ThrowOnError();
+
+ for (uint c = 0; c < count; c++)
+ {
+ if (requireDeviceExtensions.Any(ext => !api.IsDeviceExtensionPresent(physicalDevices[c], ext)))
+ continue;
+
+ var physicalDeviceIDProperties = new PhysicalDeviceIDProperties()
+ {
+ SType = StructureType.PhysicalDeviceIDProperties
+ };
+ var physicalDeviceProperties2 = new PhysicalDeviceProperties2()
+ {
+ SType = StructureType.PhysicalDeviceProperties2,
+ PNext = &physicalDeviceIDProperties
+ };
+ api.GetPhysicalDeviceProperties2(physicalDevices[c], &physicalDeviceProperties2);
+
+ if (gpuInterop.DeviceLuid != null && physicalDeviceIDProperties.DeviceLuidvalid)
+ {
+ if (!new Span(physicalDeviceIDProperties.DeviceLuid, 8)
+ .SequenceEqual(gpuInterop.DeviceLuid))
+ continue;
+ }
+ else if (gpuInterop.DeviceUuid != null)
+ {
+ if (!new Span(physicalDeviceIDProperties.DeviceUuid, 16)
+ .SequenceEqual(gpuInterop?.DeviceUuid))
+ continue;
+ }
+
+ physicalDevice = physicalDevices[c];
+
+ var name = Marshal.PtrToStringAnsi(new IntPtr(physicalDeviceProperties2.Properties.DeviceName))!;
+
+
+ uint queueFamilyCount = 0;
+ api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, null);
+ var familyProperties = stackalloc QueueFamilyProperties[(int)queueFamilyCount];
+ api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
+ for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
+ {
+ var family = familyProperties[c];
+ if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit))
+ continue;
+
+
+ var queuePriorities = stackalloc float[(int)family.QueueCount];
+
+ for (var i = 0; i < family.QueueCount; i++)
+ queuePriorities[i] = 1f;
+
+ var features = new PhysicalDeviceFeatures();
+
+ var queueCreateInfo = new DeviceQueueCreateInfo
+ {
+ SType = StructureType.DeviceQueueCreateInfo,
+ QueueFamilyIndex = queueFamilyIndex,
+ QueueCount = family.QueueCount,
+ PQueuePriorities = queuePriorities
+ };
+
+ using var pEnabledDeviceExtensions = new ByteStringList(requireDeviceExtensions);
+ var deviceCreateInfo = new DeviceCreateInfo
+ {
+ SType = StructureType.DeviceCreateInfo,
+ QueueCreateInfoCount = 1,
+ PQueueCreateInfos = &queueCreateInfo,
+ PpEnabledExtensionNames = pEnabledDeviceExtensions,
+ EnabledExtensionCount = pEnabledDeviceExtensions.UCount,
+ PEnabledFeatures = &features
+ };
+
+ api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out device)
+ .ThrowOnError();
+
+ api.GetDeviceQueue(device, queueFamilyIndex, 0, out var queue);
+
+ var descriptorPoolSize = new DescriptorPoolSize
+ {
+ Type = DescriptorType.UniformBuffer, DescriptorCount = 16
+ };
+ var descriptorPoolInfo = new DescriptorPoolCreateInfo
+ {
+ SType = StructureType.DescriptorPoolCreateInfo,
+ PoolSizeCount = 1,
+ PPoolSizes = &descriptorPoolSize,
+ MaxSets = 16,
+ Flags = DescriptorPoolCreateFlags.FreeDescriptorSetBit
+ };
+
+ api.CreateDescriptorPool(device, &descriptorPoolInfo, null, out descriptorPool)
+ .ThrowOnError();
+
+ pool = new VulkanCommandBufferPool(api, device, queue, queueFamilyIndex);
+ grContext = GRContext.CreateVulkan(new GRVkBackendContext
+ {
+ VkInstance = vkInstance.Handle,
+ VkDevice = device.Handle,
+ VkQueue = queue.Handle,
+ GraphicsQueueIndex = queueFamilyIndex,
+ VkPhysicalDevice = physicalDevice.Handle,
+ GetProcedureAddress = (proc, _, _) =>
+ {
+ var rv = api.GetDeviceProcAddr(device, proc);
+ if (rv != IntPtr.Zero)
+ return rv;
+ rv = api.GetInstanceProcAddr(vkInstance, proc);
+ if (rv != IntPtr.Zero)
+ return rv;
+ return api.GetInstanceProcAddr(default, proc);
+ }
+ });
+
+
+ D3DDevice? d3dDevice = null;
+ if (physicalDeviceIDProperties.DeviceLuidvalid &&
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ d3dDevice = D3DMemoryHelper.CreateDeviceByLuid(
+ new Span(physicalDeviceIDProperties.DeviceLuid, 8));
+
+ var dxgiDevice = d3dDevice?.QueryInterface();
+ return (new VulkanContext
+ {
+ Api = api,
+ Device = device,
+ Instance = vkInstance,
+ PhysicalDevice = physicalDevice,
+ Queue = queue,
+ QueueFamilyIndex = queueFamilyIndex,
+ Pool = pool,
+ DescriptorPool = descriptorPool,
+ GrContext = grContext,
+ D3DDevice = d3dDevice
+ }, name);
+ }
+ return (null, "No suitable device queue found");
+ }
+
+ return (null, "Suitable device not found");
+
+ }
+ catch (Exception e)
+ {
+ return (null, e.ToString());
+ }
+ finally
+ {
+ if (grContext == null && api != null)
+ {
+ pool?.Dispose();
+ if (descriptorPool.Handle != default)
+ api.DestroyDescriptorPool(device, descriptorPool, null);
+ if (device.Handle != default)
+ api.DestroyDevice(device, null);
+ }
+ }
+ }
+
+ private static unsafe bool IsLayerAvailable(Vk api, string layerName)
+ {
+ uint layerPropertiesCount;
+
+ api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
+
+ var layerProperties = new LayerProperties[layerPropertiesCount];
+
+ fixed (LayerProperties* pLayerProperties = layerProperties)
+ {
+ api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
+
+ for (var i = 0; i < layerPropertiesCount; i++)
+ {
+ var currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
+
+ if (currentLayerName == layerName) return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static unsafe uint LogCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, DebugUtilsMessageTypeFlagsEXT messageTypes, DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
+ {
+ if (messageSeverity != DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt)
+ {
+ var message = Marshal.PtrToStringAnsi((nint)pCallbackData->PMessage);
+ Console.WriteLine(message);
+ }
+
+ return Vk.False;
+ }
+
+ public void Dispose()
+ {
+ D3DDevice?.Dispose();
+ GrContext.Dispose();
+ Pool.Dispose();
+ Api.DestroyDescriptorPool(Device, DescriptorPool, null);
+ Api.DestroyDevice(Device, null);
+ }
+}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs b/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs
new file mode 100644
index 0000000000..6a1cd641b3
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using Silk.NET.Core;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.KHR;
+using SilkNetDemo;
+
+namespace GpuInterop.VulkanDemo;
+
+public class VulkanDemoControl : DrawingSurfaceDemoBase
+{
+ private Instance _vkInstance;
+ private Vk _api;
+
+ class VulkanResources : IAsyncDisposable
+ {
+ public VulkanContext Context { get; }
+ public VulkanSwapchain Swapchain { get; }
+ public VulkanContent Content { get; }
+
+ public VulkanResources(VulkanContext context, VulkanSwapchain swapchain, VulkanContent content)
+ {
+ Context = context;
+ Swapchain = swapchain;
+ Content = content;
+ }
+ public async ValueTask DisposeAsync()
+ {
+ Context.Pool.FreeUsedCommandBuffers();
+ Content.Dispose();
+ await Swapchain.DisposeAsync();
+ Context.Dispose();
+ }
+ }
+
+ protected override bool SupportsDisco => true;
+
+ private VulkanResources? _resources;
+
+ protected override (bool success, string info) InitializeGraphicsResources(Compositor compositor,
+ CompositionDrawingSurface compositionDrawingSurface, ICompositionGpuInterop gpuInterop)
+ {
+ var (context, info) = VulkanContext.TryCreate(gpuInterop);
+ if (context == null)
+ return (false, info);
+ try
+ {
+ var content = new VulkanContent(context);
+ _resources = new VulkanResources(context,
+ new VulkanSwapchain(context, gpuInterop, compositionDrawingSurface), content);
+ return (true, info);
+ }
+ catch(Exception e)
+ {
+ return (false, e.ToString());
+ }
+ }
+
+ protected override void FreeGraphicsResources()
+ {
+ _resources?.DisposeAsync();
+ _resources = null;
+ }
+
+ protected override unsafe void RenderFrame(PixelSize pixelSize)
+ {
+ if (_resources == null)
+ return;
+ using (_resources.Swapchain.BeginDraw(pixelSize, out var image))
+ {
+ /*
+ var commandBuffer = _resources.Context.Pool.CreateCommandBuffer();
+ commandBuffer.BeginRecording();
+ image.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferDstOptimal, AccessFlags.None);
+
+ var range = new ImageSubresourceRange
+ {
+ AspectMask = ImageAspectFlags.ColorBit,
+ LayerCount = 1,
+ LevelCount = 1,
+ BaseArrayLayer = 0,
+ BaseMipLevel = 0
+ };
+ var color = new ClearColorValue
+ {
+ Float32_0 = 1, Float32_1 = 0, Float32_2 = 0, Float32_3 = 1
+ };
+ _resources.Context.Api.CmdClearColorImage(commandBuffer.InternalHandle, image.InternalHandle.Value, ImageLayout.TransferDstOptimal,
+ &color, 1, &range);
+ commandBuffer.Submit();*/
+ _resources.Content.Render(image, Yaw, Pitch, Roll, Disco);
+ }
+ }
+}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs b/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
new file mode 100644
index 0000000000..1a13ebd4c5
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
@@ -0,0 +1,12 @@
+using System;
+using Silk.NET.Vulkan;
+
+namespace SilkNetDemo;
+
+public static class VulkanExtensions
+{
+ public static void ThrowOnError(this Result result)
+ {
+ if (result != Result.Success) throw new Exception($"Unexpected API error \"{result}\".");
+ }
+}
\ No newline at end of file
diff --git a/samples/GpuInterop/VulkanDemo/VulkanImage.cs b/samples/GpuInterop/VulkanDemo/VulkanImage.cs
new file mode 100644
index 0000000000..e8854bfeb2
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanImage.cs
@@ -0,0 +1,276 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Vulkan;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.KHR;
+using SilkNetDemo;
+using SkiaSharp;
+
+namespace GpuInterop.VulkanDemo;
+
+public unsafe class VulkanImage : IDisposable
+ {
+ private readonly VulkanContext _vk;
+ private readonly Instance _instance;
+ private readonly Device _device;
+ private readonly PhysicalDevice _physicalDevice;
+ private readonly VulkanCommandBufferPool _commandBufferPool;
+ private ImageLayout _currentLayout;
+ private AccessFlags _currentAccessFlags;
+ private ImageUsageFlags _imageUsageFlags { get; }
+ private ImageView? _imageView { get; set; }
+ private DeviceMemory _imageMemory { get; set; }
+ private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
+ private IntPtr _win32ShareHandle;
+
+ internal Image? InternalHandle { get; private set; }
+ internal Format Format { get; }
+ internal ImageAspectFlags AspectFlags { get; private set; }
+
+ public ulong Handle => InternalHandle?.Handle ?? 0;
+ public ulong ViewHandle => _imageView?.Handle ?? 0;
+ public uint UsageFlags => (uint) _imageUsageFlags;
+ public ulong MemoryHandle => _imageMemory.Handle;
+ public DeviceMemory DeviceMemory => _imageMemory;
+ public uint MipLevels { get; private set; }
+ public Vk Api { get; }
+ public PixelSize Size { get; }
+ public ulong MemorySize { get; private set; }
+ public uint CurrentLayout => (uint) _currentLayout;
+
+ public VulkanImage(VulkanContext vk, uint format, PixelSize size,
+ bool exportable, uint mipLevels = 0)
+ {
+ _vk = vk;
+ _instance = vk.Instance;
+ _device = vk.Device;
+ _physicalDevice = vk.PhysicalDevice;
+ _commandBufferPool = vk.Pool;
+ Format = (Format)format;
+ Api = vk.Api;
+ Size = size;
+ MipLevels = 1;//mipLevels;
+ _imageUsageFlags =
+ ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit |
+ ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit;
+
+ //MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
+
+ var handleType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+ ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit :
+ ExternalMemoryHandleTypeFlags.OpaqueFDBit;
+ var externalMemoryCreateInfo = new ExternalMemoryImageCreateInfo
+ {
+ SType = StructureType.ExternalMemoryImageCreateInfo,
+ HandleTypes = handleType
+ };
+
+ var imageCreateInfo = new ImageCreateInfo
+ {
+ PNext = exportable ? &externalMemoryCreateInfo : null,
+ SType = StructureType.ImageCreateInfo,
+ ImageType = ImageType.ImageType2D,
+ Format = Format,
+ Extent =
+ new Extent3D((uint?)Size.Width,
+ (uint?)Size.Height, 1),
+ MipLevels = MipLevels,
+ ArrayLayers = 1,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ Tiling = Tiling,
+ Usage = _imageUsageFlags,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = ImageCreateFlags.ImageCreateMutableFormatBit
+ };
+
+ Api
+ .CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
+ InternalHandle = image;
+
+ Api.GetImageMemoryRequirements(_device, InternalHandle.Value,
+ out var memoryRequirements);
+
+
+ var fdExport = new ExportMemoryAllocateInfo
+ {
+ HandleTypes = handleType, SType = StructureType.ExportMemoryAllocateInfo
+ };
+ var dedicatedAllocation = new MemoryDedicatedAllocateInfoKHR
+ {
+ SType = StructureType.MemoryDedicatedAllocateInfoKhr,
+ Image = image
+ };
+ ImportMemoryWin32HandleInfoKHR handleImport = default;
+ if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ _d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
+ using var dxgi = _d3dTexture2D.QueryInterface();
+ _win32ShareHandle = dxgi.SharedHandle;
+ handleImport = new ImportMemoryWin32HandleInfoKHR
+ {
+ PNext = &dedicatedAllocation,
+ SType = StructureType.ImportMemoryWin32HandleInfoKhr,
+ HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
+ Handle = _win32ShareHandle,
+ };
+ }
+
+ var memoryAllocateInfo = new MemoryAllocateInfo
+ {
+ PNext =
+ exportable ? RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? &handleImport : &fdExport : null,
+ SType = StructureType.MemoryAllocateInfo,
+ AllocationSize = memoryRequirements.Size,
+ MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
+ Api,
+ _physicalDevice,
+ memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
+ };
+
+ Api.AllocateMemory(_device, memoryAllocateInfo, null,
+ out var imageMemory).ThrowOnError();
+
+ _imageMemory = imageMemory;
+
+
+ MemorySize = memoryRequirements.Size;
+
+ Api.BindImageMemory(_device, InternalHandle.Value, _imageMemory, 0).ThrowOnError();
+ var componentMapping = new ComponentMapping(
+ ComponentSwizzle.Identity,
+ ComponentSwizzle.Identity,
+ ComponentSwizzle.Identity,
+ ComponentSwizzle.Identity);
+
+ AspectFlags = ImageAspectFlags.ImageAspectColorBit;
+
+ var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1);
+
+ var imageViewCreateInfo = new ImageViewCreateInfo
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = InternalHandle.Value,
+ ViewType = ImageViewType.ImageViewType2D,
+ Format = Format,
+ Components = componentMapping,
+ SubresourceRange = subresourceRange
+ };
+
+ Api
+ .CreateImageView(_device, imageViewCreateInfo, null, out var imageView)
+ .ThrowOnError();
+
+ _imageView = imageView;
+
+ _currentLayout = ImageLayout.Undefined;
+
+ TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
+ }
+
+ public int ExportFd()
+ {
+ if (!Api.TryGetDeviceExtension(_instance, _device, out var ext))
+ throw new InvalidOperationException();
+ var info = new MemoryGetFdInfoKHR
+ {
+ Memory = _imageMemory,
+ SType = StructureType.MemoryGetFDInfoKhr,
+ HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit
+ };
+ ext.GetMemoryF(_device, info, out var fd).ThrowOnError();
+ return fd;
+ }
+
+ public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+ new PlatformHandle(_win32ShareHandle,
+ KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) :
+ new PlatformHandle(new IntPtr(ExportFd()),
+ KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+
+ public ImageTiling Tiling => ImageTiling.Optimal;
+
+
+
+ internal void TransitionLayout(CommandBuffer commandBuffer,
+ ImageLayout fromLayout, AccessFlags fromAccessFlags,
+ ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
+ {
+ VulkanMemoryHelper.TransitionLayout(Api, commandBuffer, InternalHandle.Value,
+ fromLayout,
+ fromAccessFlags,
+ destinationLayout, destinationAccessFlags,
+ MipLevels);
+
+ _currentLayout = destinationLayout;
+ _currentAccessFlags = destinationAccessFlags;
+ }
+
+ internal void TransitionLayout(CommandBuffer commandBuffer,
+ ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
+ => TransitionLayout(commandBuffer, _currentLayout, _currentAccessFlags, destinationLayout,
+ destinationAccessFlags);
+
+
+ internal void TransitionLayout(ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
+ {
+ var commandBuffer = _commandBufferPool.CreateCommandBuffer();
+ commandBuffer.BeginRecording();
+ TransitionLayout(commandBuffer.InternalHandle, destinationLayout, destinationAccessFlags);
+ commandBuffer.EndRecording();
+ commandBuffer.Submit();
+ }
+
+ public void TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
+ {
+ TransitionLayout((ImageLayout)destinationLayout, (AccessFlags)destinationAccessFlags);
+ }
+
+ public unsafe void Dispose()
+ {
+ Api.DestroyImageView(_device, _imageView.Value, null);
+ Api.DestroyImage(_device, InternalHandle.Value, null);
+ Api.FreeMemory(_device, _imageMemory, null);
+
+ _imageView = default;
+ InternalHandle = default;
+ _imageMemory = default;
+ }
+
+ public void SaveTexture(string path)
+ {
+ _vk.GrContext.ResetContext();
+ var _image = this;
+ var imageInfo = new GRVkImageInfo()
+ {
+ CurrentQueueFamily = _vk.QueueFamilyIndex,
+ Format = (uint)_image.Format,
+ Image = _image.Handle,
+ ImageLayout = (uint)_image.CurrentLayout,
+ ImageTiling = (uint)_image.Tiling,
+ ImageUsageFlags = (uint)_image.UsageFlags,
+ LevelCount = _image.MipLevels,
+ SampleCount = 1,
+ Protected = false,
+ Alloc = new GRVkAlloc()
+ {
+ Memory = _image.MemoryHandle, Flags = 0, Offset = 0, Size = _image.MemorySize
+ }
+ };
+
+ using (var backendTexture = new GRBackendRenderTarget(_image.Size.Width, _image.Size.Height, 1,
+ imageInfo))
+ using (var surface = SKSurface.Create(_vk.GrContext, backendTexture,
+ GRSurfaceOrigin.TopLeft,
+ SKColorType.Rgba8888, SKColorSpace.CreateSrgb()))
+ {
+ using var snap = surface.Snapshot();
+ using var encoded = snap.Encode();
+ using (var s = File.Create(path))
+ encoded.SaveTo(s);
+ }
+ }
+ }
diff --git a/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs b/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs
new file mode 100644
index 0000000000..f6778610dc
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs
@@ -0,0 +1,59 @@
+using Silk.NET.Vulkan;
+
+namespace GpuInterop.VulkanDemo;
+
+internal static class VulkanMemoryHelper
+{
+ internal static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits,
+ MemoryPropertyFlags flags)
+ {
+ api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
+
+ for (var i = 0; i < properties.MemoryTypeCount; i++)
+ {
+ var type = properties.MemoryTypes[i];
+
+ if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
+ }
+
+ return -1;
+ }
+
+ internal static unsafe void TransitionLayout(
+ Vk api,
+ CommandBuffer commandBuffer,
+ Image image,
+ ImageLayout sourceLayout,
+ AccessFlags sourceAccessMask,
+ ImageLayout destinationLayout,
+ AccessFlags destinationAccessMask,
+ uint mipLevels)
+ {
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1);
+
+ var barrier = new ImageMemoryBarrier
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = sourceAccessMask,
+ DstAccessMask = destinationAccessMask,
+ OldLayout = sourceLayout,
+ NewLayout = destinationLayout,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = image,
+ SubresourceRange = subresourceRange
+ };
+
+ api.CmdPipelineBarrier(
+ commandBuffer,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ barrier);
+ }
+}
\ No newline at end of file
diff --git a/samples/GpuInterop/VulkanDemo/VulkanSemaphorePair.cs b/samples/GpuInterop/VulkanDemo/VulkanSemaphorePair.cs
new file mode 100644
index 0000000000..279c313a27
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanSemaphorePair.cs
@@ -0,0 +1,58 @@
+using System;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.KHR;
+using SilkNetDemo;
+
+namespace GpuInterop.VulkanDemo;
+
+class VulkanSemaphorePair : IDisposable
+{
+ private readonly VulkanContext _resources;
+
+ public unsafe VulkanSemaphorePair(VulkanContext resources, bool exportable)
+ {
+ _resources = resources;
+
+ var semaphoreExportInfo = new ExportSemaphoreCreateInfo
+ {
+ SType = StructureType.ExportSemaphoreCreateInfo,
+ HandleTypes = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
+ };
+
+ var semaphoreCreateInfo = new SemaphoreCreateInfo
+ {
+ SType = StructureType.SemaphoreCreateInfo,
+ PNext = exportable ? &semaphoreExportInfo : null
+ };
+
+ resources.Api.CreateSemaphore(resources.Device, semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
+ ImageAvailableSemaphore = semaphore;
+
+ resources.Api.CreateSemaphore(resources.Device, semaphoreCreateInfo, null, out semaphore).ThrowOnError();
+ RenderFinishedSemaphore = semaphore;
+ }
+
+ public int ExportFd(bool renderFinished)
+ {
+ if (!_resources.Api.TryGetDeviceExtension(_resources.Instance, _resources.Device,
+ out var ext))
+ throw new InvalidOperationException();
+ var info = new SemaphoreGetFdInfoKHR()
+ {
+ SType = StructureType.SemaphoreGetFDInfoKhr,
+ Semaphore = renderFinished ? RenderFinishedSemaphore : ImageAvailableSemaphore,
+ HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
+ };
+ ext.GetSemaphoreF(_resources.Device, info, out var fd).ThrowOnError();
+ return fd;
+ }
+
+ internal Semaphore ImageAvailableSemaphore { get; }
+ internal Semaphore RenderFinishedSemaphore { get; }
+
+ public unsafe void Dispose()
+ {
+ _resources.Api.DestroySemaphore(_resources.Device, ImageAvailableSemaphore, null);
+ _resources.Api.DestroySemaphore(_resources.Device, RenderFinishedSemaphore, null);
+ }
+}
\ No newline at end of file
diff --git a/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs b/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
new file mode 100644
index 0000000000..325c815ccb
--- /dev/null
+++ b/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
@@ -0,0 +1,154 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+using Avalonia.Vulkan;
+using Metsys.Bson;
+using Silk.NET.Vulkan;
+using SkiaSharp;
+
+namespace GpuInterop.VulkanDemo;
+
+class VulkanSwapchain : SwapchainBase
+{
+ private readonly VulkanContext _vk;
+
+ public VulkanSwapchain(VulkanContext vk, ICompositionGpuInterop interop, CompositionDrawingSurface target) : base(interop, target)
+ {
+ _vk = vk;
+ }
+
+ protected override VulkanSwapchainImage CreateImage(PixelSize size)
+ {
+ return new VulkanSwapchainImage(_vk, size, Interop, Target);
+ }
+
+ public IDisposable BeginDraw(PixelSize size, out VulkanImage image)
+ {
+ _vk.Pool.FreeUsedCommandBuffers();
+ var rv = BeginDrawCore(size, out var swapchainImage);
+ image = swapchainImage.Image;
+ return rv;
+ }
+}
+
+class VulkanSwapchainImage : ISwapchainImage
+{
+ private readonly VulkanContext _vk;
+ private readonly ICompositionGpuInterop _interop;
+ private readonly CompositionDrawingSurface _target;
+ private readonly VulkanImage _image;
+ private readonly VulkanSemaphorePair _semaphorePair;
+ private ICompositionImportedGpuSemaphore? _availableSemaphore, _renderCompletedSemaphore;
+ private ICompositionImportedGpuImage? _importedImage;
+ private Task? _lastPresent;
+ public VulkanImage Image => _image;
+ private bool _initial = true;
+
+ public VulkanSwapchainImage(VulkanContext vk, PixelSize size, ICompositionGpuInterop interop, CompositionDrawingSurface target)
+ {
+ _vk = vk;
+ _interop = interop;
+ _target = target;
+ Size = size;
+ _image = new VulkanImage(vk, (uint)Format.R8G8B8A8Unorm, size, true);
+ _semaphorePair = new VulkanSemaphorePair(vk, true);
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (LastPresent != null)
+ await LastPresent;
+ if (_importedImage != null)
+ await _importedImage.DisposeAsync();
+ if (_availableSemaphore != null)
+ await _availableSemaphore.DisposeAsync();
+ if (_renderCompletedSemaphore != null)
+ await _renderCompletedSemaphore.DisposeAsync();
+ _semaphorePair.Dispose();
+ _image.Dispose();
+ }
+
+ public PixelSize Size { get; }
+
+ public Task? LastPresent => _lastPresent;
+
+ public void BeginDraw()
+ {
+ var buffer = _vk.Pool.CreateCommandBuffer();
+ buffer.BeginRecording();
+
+ _image.TransitionLayout(buffer.InternalHandle,
+ ImageLayout.Undefined, AccessFlags.None,
+ ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessColorAttachmentReadBit);
+
+ if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ buffer.Submit(null,null,null, null, new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo
+ {
+ AcquireKey = 0,
+ DeviceMemory = _image.DeviceMemory
+ });
+ else if (_initial)
+ {
+ _initial = false;
+ buffer.Submit();
+ }
+ else
+ buffer.Submit(new[] { _semaphorePair.ImageAvailableSemaphore },
+ new[]
+ {
+ PipelineStageFlags.AllGraphicsBit
+ });
+ }
+
+
+
+ public void Present()
+ {
+ var buffer = _vk.Pool.CreateCommandBuffer();
+ buffer.BeginRecording();
+ _image.TransitionLayout(buffer.InternalHandle, ImageLayout.TransferSrcOptimal, AccessFlags.TransferWriteBit);
+
+
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ buffer.Submit(null, null, null, null,
+ new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo
+ {
+ DeviceMemory = _image.DeviceMemory, ReleaseKey = 1
+ });
+ }
+ else
+ buffer.Submit(null, null, new[] { _semaphorePair.RenderFinishedSemaphore });
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ _availableSemaphore ??= _interop.ImportSemaphore(new PlatformHandle(
+ new IntPtr(_semaphorePair.ExportFd(false)),
+ KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor));
+
+ _renderCompletedSemaphore ??= _interop.ImportSemaphore(new PlatformHandle(
+ new IntPtr(_semaphorePair.ExportFd(true)),
+ KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor));
+ }
+
+ _importedImage ??= _interop.ImportImage(_image.Export(),
+ new PlatformGraphicsExternalImageProperties
+ {
+ Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
+ Width = Size.Width,
+ Height = Size.Height,
+ MemorySize = _image.MemorySize
+ });
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ _lastPresent = _target.UpdateWithKeyedMutexAsync(_importedImage, 1, 0);
+ else
+ _lastPresent = _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore, _availableSemaphore);
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs b/src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs
new file mode 100644
index 0000000000..5b06651b5f
--- /dev/null
+++ b/src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Metadata;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Platform;
+
+[Unstable]
+public interface IExternalObjectsRenderInterfaceContextFeature
+{
+ ///
+ /// Returns the list of image handle types supported by the current GPU backend, see
+ ///
+ IReadOnlyList SupportedImageHandleTypes { get; }
+
+ ///
+ /// Returns the list of semaphore types supported by the current GPU backend, see
+ ///
+ IReadOnlyList SupportedSemaphoreTypes { get; }
+
+ IPlatformRenderInterfaceImportedImage ImportImage(IPlatformHandle handle,
+ PlatformGraphicsExternalImageProperties properties);
+
+ IPlatformRenderInterfaceImportedImage ImportImage(ICompositionImportableSharedGpuContextImage image);
+
+ IPlatformRenderInterfaceImportedSemaphore ImportSemaphore(IPlatformHandle handle);
+ CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType);
+ public byte[]? DeviceUuid { get; }
+ public byte[]? DeviceLuid { get; }
+}
+
+[Unstable]
+public interface IPlatformRenderInterfaceImportedObject : IDisposable
+{
+
+}
+
+[Unstable]
+public interface IPlatformRenderInterfaceImportedImage : IPlatformRenderInterfaceImportedObject
+{
+ IBitmapImpl SnapshotWithKeyedMutex(uint acquireIndex, uint releaseIndex);
+
+ IBitmapImpl SnapshotWithSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
+ IPlatformRenderInterfaceImportedSemaphore signalSemaphore);
+
+ IBitmapImpl SnapshotWithAutomaticSync();
+}
+
+[Unstable]
+public interface IPlatformRenderInterfaceImportedSemaphore : IPlatformRenderInterfaceImportedObject
+{
+
+}
diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
index b6464dea58..66d4f083c2 100644
--- a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
+++ b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Platform;
@@ -15,4 +16,12 @@ public static class OptionalFeatureProviderExtensions
{
public static T? TryGetFeature(this IOptionalFeatureProvider provider) where T : class =>
(T?)provider.TryGetFeature(typeof(T));
-}
\ No newline at end of file
+
+ public static bool TryGetFeature(this IOptionalFeatureProvider provider, [MaybeNullWhen(false)] out T rv)
+ where T : class
+ {
+ rv = provider.TryGetFeature();
+ return rv != null;
+ }
+
+}
diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
index 9f4e96da25..e2160f21d2 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -209,5 +209,10 @@ namespace Avalonia.Platform
///
/// An .
IRenderTarget CreateRenderTarget(IEnumerable
diff --git a/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs b/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs
new file mode 100644
index 0000000000..3db0aafd7b
--- /dev/null
+++ b/src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.OpenGL.Controls;
+
+internal class CompositionOpenGlSwapchain : SwapchainBase
+{
+ private readonly IGlContext _context;
+ private readonly IGlContextExternalObjectsFeature? _externalObjectsFeature;
+ private readonly IOpenGlTextureSharingRenderInterfaceContextFeature? _sharingFeature;
+
+ public CompositionOpenGlSwapchain(IGlContext context, ICompositionGpuInterop interop, CompositionDrawingSurface target,
+ IOpenGlTextureSharingRenderInterfaceContextFeature sharingFeature
+ ) : base(interop, target)
+ {
+ _context = context;
+ _sharingFeature = sharingFeature;
+ }
+
+ public CompositionOpenGlSwapchain(IGlContext context, ICompositionGpuInterop interop, CompositionDrawingSurface target,
+ IGlContextExternalObjectsFeature externalObjectsFeature) : base(interop, target)
+ {
+ _context = context;
+ _externalObjectsFeature = externalObjectsFeature;
+ }
+
+
+
+ protected override IGlSwapchainImage CreateImage(PixelSize size)
+ {
+ if (_sharingFeature != null)
+ return new CompositionOpenGlSwapChainImage(_context, _sharingFeature, size, Interop, Target);
+ return new DxgiMutexOpenGlSwapChainImage(Interop, Target, _externalObjectsFeature!, size);
+ }
+
+ public IDisposable BeginDraw(PixelSize size, out IGlTexture texture)
+ {
+ var rv = BeginDrawCore(size, out var tex);
+ texture = tex;
+ return rv;
+ }
+}
+
+internal interface IGlTexture
+{
+ int TextureId { get; }
+ int InternalFormat { get; }
+ PixelSize Size { get; }
+}
+
+
+interface IGlSwapchainImage : ISwapchainImage, IGlTexture
+{
+
+}
+internal class DxgiMutexOpenGlSwapChainImage : IGlSwapchainImage
+{
+ private readonly ICompositionGpuInterop _interop;
+ private readonly CompositionDrawingSurface _surface;
+ private readonly IGlExportableExternalImageTexture _texture;
+ private Task? _lastPresent;
+ private ICompositionImportedGpuImage? _imported;
+
+ public DxgiMutexOpenGlSwapChainImage(ICompositionGpuInterop interop, CompositionDrawingSurface surface,
+ IGlContextExternalObjectsFeature externalObjects, PixelSize size)
+ {
+ _interop = interop;
+ _surface = surface;
+ _texture = externalObjects.CreateImage(KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle,
+ size, PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm);
+ }
+ public async ValueTask DisposeAsync()
+ {
+ // The texture is already sent to the compositor, so we need to wait for its attempts to use the texture
+ // before destroying it
+ if (_imported != null)
+ {
+ // No need to wait for import / LastPresent since calls are serialized on the compositor side anyway
+ try
+ {
+ await _imported.DisposeAsync();
+ }
+ catch
+ {
+ // Ignore
+ }
+ }
+ _texture.Dispose();
+ }
+
+ public int TextureId => _texture.TextureId;
+ public int InternalFormat => _texture.InternalFormat;
+ public PixelSize Size => new(_texture.Properties.Width, _texture.Properties.Height);
+ public Task LastPresent => _lastPresent;
+ public void BeginDraw() => _texture.AcquireKeyedMutex(0);
+
+ public void Present()
+ {
+ _texture.ReleaseKeyedMutex(1);
+ _imported ??= _interop.ImportImage(_texture.GetHandle(), _texture.Properties);
+ _lastPresent = _surface.UpdateWithKeyedMutexAsync(_imported, 1, 0);
+ }
+}
+
+internal class CompositionOpenGlSwapChainImage : IGlSwapchainImage
+{
+ private readonly ICompositionGpuInterop _interop;
+ private readonly CompositionDrawingSurface _target;
+ private readonly ICompositionImportableOpenGlSharedTexture _texture;
+ private ICompositionImportedGpuImage? _imported;
+
+ public CompositionOpenGlSwapChainImage(
+ IGlContext context,
+ IOpenGlTextureSharingRenderInterfaceContextFeature sharingFeature,
+ PixelSize size,
+ ICompositionGpuInterop interop,
+ CompositionDrawingSurface target)
+ {
+ _interop = interop;
+ _target = target;
+ _texture = sharingFeature.CreateSharedTextureForComposition(context, size);
+ }
+
+
+ public async ValueTask DisposeAsync()
+ {
+ // The texture is already sent to the compositor, so we need to wait for its attempts to use the texture
+ // before destroying it
+ if (_imported != null)
+ {
+ // No need to wait for import / LastPresent since calls are serialized on the compositor side anyway
+ try
+ {
+ await _imported.DisposeAsync();
+ }
+ catch
+ {
+ // Ignore
+ }
+ }
+
+ _texture.Dispose();
+ }
+
+ public int TextureId => _texture.TextureId;
+ public int InternalFormat => _texture.InternalFormat;
+ public PixelSize Size => _texture.Size;
+ public Task? LastPresent { get; private set; }
+ public void BeginDraw()
+ {
+ // No-op for texture sharing
+ }
+
+ public void Present()
+ {
+ _imported ??= _interop.ImportImage(_texture);
+ LastPresent = _target.UpdateAsync(_imported);
+ }
+}
diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
index c62a3d8d2f..4d0663a12b 100644
--- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
+++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
@@ -1,120 +1,53 @@
using System;
+using System.Numerics;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Logging;
-using Avalonia.Media;
-using Avalonia.OpenGL.Imaging;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
using Avalonia.VisualTree;
-using static Avalonia.OpenGL.GlConsts;
-
+using Avalonia.Platform;
namespace Avalonia.OpenGL.Controls
{
public abstract class OpenGlControlBase : Control
{
- private IGlContext _context;
- private int _fb, _depthBuffer;
- private OpenGlBitmap _bitmap;
- private IOpenGlBitmapAttachment _attachment;
- private PixelSize _depthBufferSize;
-
+ private CompositionSurfaceVisual _visual;
+ private Action _update;
+ private bool _updateQueued;
private Task _initialization;
- private IOpenGlTextureSharingRenderInterfaceContextFeature _feature;
-
- protected GlVersion GlVersion { get; private set; }
- public sealed override void Render(DrawingContext context)
- {
- if(!EnsureInitialized())
- return;
-
- using (_context.MakeCurrent())
- {
- _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
- EnsureTextureAttachment();
- EnsureDepthBufferAttachment(_context.GlInterface);
- if(!CheckFramebufferStatus(_context.GlInterface))
- return;
-
- OnOpenGlRender(_context.GlInterface, _fb);
- _attachment.Present();
- }
-
- context.DrawImage(_bitmap, new Rect(_bitmap.Size), new Rect(Bounds.Size));
- base.Render(context);
- }
+ private OpenGlControlBaseResources? _resources;
+ private Compositor? _compositor;
+ protected GlVersion GlVersion => _resources?.Context.Version ?? default;
- void EnsureTextureAttachment()
+ public OpenGlControlBase()
{
- _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
- if (_bitmap == null || _attachment == null || _bitmap.PixelSize != GetPixelSize())
- {
- _attachment?.Dispose();
- _attachment = null;
- _bitmap?.Dispose();
- _bitmap = null;
- _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
- _attachment = _bitmap.CreateFramebufferAttachment(_context);
- }
- }
-
- void EnsureDepthBufferAttachment(GlInterface gl)
- {
- var size = GetPixelSize();
- if (size == _depthBufferSize && _depthBuffer != 0)
- return;
-
- gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
- if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
-
- _depthBuffer = gl.GenRenderbuffer();
- gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
- gl.RenderbufferStorage(GL_RENDERBUFFER,
- GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
- size.Width, size.Height);
- gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
- gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
+ _update = Update;
}
void DoCleanup()
{
- if (_context != null)
+ if (_initialization is { Status: TaskStatus.RanToCompletion } && _resources != null)
{
- using (_context.MakeCurrent())
+ try
{
- var gl = _context.GlInterface;
- gl.ActiveTexture(GL_TEXTURE0);
- gl.BindTexture(GL_TEXTURE_2D, 0);
- gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
- if (_fb != 0)
- gl.DeleteFramebuffer(_fb);
- _fb = 0;
- if (_depthBuffer != 0)
- gl.DeleteRenderbuffer(_depthBuffer);
- _depthBuffer = 0;
- _attachment?.Dispose();
- _attachment = null;
- _bitmap?.Dispose();
- _bitmap = null;
-
- try
- {
- if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true })
- {
- OnOpenGlDeinit(_context.GlInterface, _fb);
- _initialization = null;
- }
- }
- finally
+ using (_resources.Context.EnsureCurrent())
{
- _context.Dispose();
- _context = null;
+ OnOpenGlDeinit(_resources.Context.GlInterface);
}
}
+ catch(Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+ "Unable to free user OpenGL resources: {exception}", e);
+ }
}
- _fb = _depthBuffer = 0;
- _attachment = null;
- _bitmap = null;
- _feature = null;
+ ElementComposition.SetElementChildVisual(this, null);
+ _visual = null;
+
+ _resources?.DisposeAsync();
+ _resources = null;
+ _initialization = null;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@@ -123,95 +56,75 @@ namespace Avalonia.OpenGL.Controls
base.OnDetachedFromVisualTree(e);
}
- private bool EnsureInitializedCore(IOpenGlTextureSharingRenderInterfaceContextFeature feature)
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
- try
- {
- _context = feature.CreateSharedContext();
- _feature = feature;
- }
- catch (Exception e)
- {
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
- return false;
- }
+ base.OnAttachedToVisualTree(e);
+ _compositor = (this.GetVisualRoot()?.Renderer as IRendererWithCompositor)?.Compositor;
+ RequestNextFrameRendering();
+ }
- if (_context == null)
- {
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL: unable to create additional OpenGL context.");
- return false;
- }
+ private bool EnsureInitializedCore(
+ ICompositionGpuInterop interop,
+ IOpenGlTextureSharingRenderInterfaceContextFeature contextSharingFeature)
+ {
+ var surface = _compositor.CreateDrawingSurface();
- GlVersion = _context.Version;
+ IGlContext ctx = null;
+ var contextFactory = AvaloniaLocator.Current.GetService();
try
{
- _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
- if (!_bitmap.SupportsContext(_context))
+ if (contextSharingFeature?.CanCreateSharedContext == true)
+ _resources = OpenGlControlBaseResources.TryCreate(surface, interop, contextSharingFeature);
+
+ if(_resources == null)
+ {
+ ctx = contextFactory.CreateContext(null);
+ if (ctx.TryGetFeature(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: unable to create OpenGlBitmap: OpenGL context is not compatible");
+ "Unable to initialize OpenGL: current platform does not support multithreaded context sharing and shared memory");
return false;
}
}
catch (Exception e)
{
- _context.Dispose();
- _context = null;
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL: unable to create OpenGlBitmap: {exception}", e);
+ "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;
- using (_context.MakeCurrent())
- {
- try
- {
- _depthBufferSize = GetPixelSize();
- var gl = _context.GlInterface;
- _fb = gl.GenFramebuffer();
- gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
-
- EnsureDepthBufferAttachment(gl);
- EnsureTextureAttachment();
-
- return CheckFramebufferStatus(gl);
- }
- catch(Exception e)
- {
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL FBO: {exception}", e);
- return false;
- }
- }
}
-
- private static bool CheckFramebufferStatus(GlInterface gl)
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
- var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
- if (status != GL_FRAMEBUFFER_COMPLETE)
+ if (_visual != null && change.Property == BoundsProperty)
{
- int code;
- while ((code = gl.GetError()) != 0)
- Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL FBO: {code}", code);
- return false;
+ _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+ RequestNextFrameRendering();
}
- return true;
+ base.OnPropertyChanged(change);
}
void ContextLost()
{
- _context = null;
- _feature = null;
_initialization = null;
- _attachment = null;
- _bitmap = null;
- _fb = 0;
- _depthBuffer = 0;
- _depthBufferSize = default;
+ _resources?.DisposeAsync();
OnOpenGlLost();
}
@@ -228,59 +141,106 @@ namespace Avalonia.OpenGL.Controls
if (_initialization is { IsCompleted: false })
return false;
- if (_context.IsLost)
+ 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 == null)
+ return;
+ if(!EnsureInitialized())
+ return;
+ using (_resources.BeginDraw(GetPixelSize()))
+ OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo);
+ }
+
private async Task 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 this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature(
+ await _compositor.TryGetRenderInterfaceFeature(
typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
+ var interop = await gpuInteropTask;
- if (contextSharingFeature == null || !contextSharingFeature.CanCreateSharedContext)
+ if (interop == null)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
- "Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
+ "Compositor backend doesn't support GPU interop");
return false;
}
- if (!EnsureInitializedCore(contextSharingFeature))
+ if (!EnsureInitializedCore(interop, contextSharingFeature))
{
DoCleanup();
return false;
}
- using (_context.MakeCurrent())
- OnOpenGlInit(_context.GlInterface, _fb);
-
- InvalidateVisual();
+ 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()
{
- var scaling = VisualRoot.RenderScaling;
+ 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, int fb)
+
+ protected virtual void OnOpenGlInit(GlInterface gl)
{
}
- protected virtual void OnOpenGlDeinit(GlInterface gl, int fb)
+ protected virtual void OnOpenGlDeinit(GlInterface gl)
{
}
diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs
new file mode 100644
index 0000000000..bda2bd1568
--- /dev/null
+++ b/src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Reactive;
+using Avalonia.Rendering.Composition;
+using static Avalonia.OpenGL.GlConsts;
+namespace Avalonia.OpenGL.Controls;
+
+internal class OpenGlControlBaseResources : IAsyncDisposable
+{
+ private int _depthBuffer;
+ public int Fbo { get; private set; }
+ private PixelSize _depthBufferSize;
+ public CompositionDrawingSurface Surface { get; }
+ public CompositionOpenGlSwapchain _swapchain;
+ public IGlContext Context { get; private set; }
+
+ public static OpenGlControlBaseResources? TryCreate(CompositionDrawingSurface surface,
+ ICompositionGpuInterop interop,
+ IOpenGlTextureSharingRenderInterfaceContextFeature feature)
+ {
+ IGlContext context;
+ try
+ {
+ context = feature.CreateSharedContext();
+ }
+ catch (Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+ "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
+ return null;
+ }
+
+ if (context == null)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+ "Unable to initialize OpenGL: unable to create additional OpenGL context.");
+ return null;
+ }
+
+ return new OpenGlControlBaseResources(context, surface, interop, feature, null);
+ }
+
+ public static OpenGlControlBaseResources? TryCreate(IGlContext context, CompositionDrawingSurface surface,
+ ICompositionGpuInterop interop, IGlContextExternalObjectsFeature externalObjects)
+ {
+ if (!interop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
+ .D3D11TextureGlobalSharedHandle)
+ || !externalObjects.SupportedExportableExternalImageTypes.Contains(
+ KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle))
+ return null;
+ return new OpenGlControlBaseResources(context, surface, interop, null, externalObjects);
+ }
+
+ public OpenGlControlBaseResources(IGlContext context,
+ CompositionDrawingSurface surface,
+ ICompositionGpuInterop interop,
+ IOpenGlTextureSharingRenderInterfaceContextFeature? feature,
+ IGlContextExternalObjectsFeature? externalObjects
+ )
+ {
+ Context = context;
+ Surface = surface;
+ using (context.MakeCurrent())
+ Fbo = context.GlInterface.GenFramebuffer();
+ _swapchain =
+ feature != null ?
+ new CompositionOpenGlSwapchain(context, interop, Surface, feature) :
+ new CompositionOpenGlSwapchain(context, interop, Surface, externalObjects);
+ }
+
+ void UpdateDepthRenderbuffer(PixelSize size)
+ {
+ if (size == _depthBufferSize && _depthBuffer != 0)
+ return;
+
+ var gl = Context.GlInterface;
+ gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
+ if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
+
+ _depthBuffer = gl.GenRenderbuffer();
+ gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
+ gl.RenderbufferStorage(GL_RENDERBUFFER,
+ Context.Version.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
+ size.Width, size.Height);
+ gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
+ gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
+ _depthBufferSize = size;
+ }
+
+ public IDisposable BeginDraw(PixelSize size)
+ {
+ var restoreContext = Context.EnsureCurrent();
+ IDisposable? imagePresent = null;
+ var success = false;
+ try
+ {
+ var gl = Context.GlInterface;
+ Context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, Fbo);
+ UpdateDepthRenderbuffer(size);
+
+ imagePresent = _swapchain.BeginDraw(size, out var texture);
+ gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.TextureId, 0);
+
+ var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ int code = gl.GetError();
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+ "Unable to configure OpenGL FBO: {code}", code);
+ throw OpenGlException.GetFormattedException("Unable to configure OpenGL FBO", code);
+ }
+
+ success = true;
+ return Disposable.Create(() =>
+ {
+ try
+ {
+ Context.GlInterface.Flush();
+ imagePresent.Dispose();
+ }
+ finally
+ {
+ restoreContext.Dispose();
+ }
+ });
+ }
+ finally
+ {
+ if (!success)
+ {
+ imagePresent?.Dispose();
+ restoreContext.Dispose();
+ }
+ }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (Context is { IsLost: false })
+ {
+ try
+ {
+ using (Context.MakeCurrent())
+ {
+ var gl = Context.GlInterface;
+ if (Fbo != 0)
+ gl.DeleteFramebuffer(Fbo);
+ Fbo = 0;
+ if (_depthBuffer != 0)
+ gl.DeleteRenderbuffer(_depthBuffer);
+ _depthBuffer = 0;
+ }
+
+ }
+ catch
+ {
+ //
+ }
+
+ Surface.Dispose();
+ await _swapchain.DisposeAsync();
+
+ Context = null!;
+ }
+ }
+}
diff --git a/src/Avalonia.OpenGL/Egl/EglConsts.cs b/src/Avalonia.OpenGL/Egl/EglConsts.cs
index 428d11857f..aff56ecfe0 100644
--- a/src/Avalonia.OpenGL/Egl/EglConsts.cs
+++ b/src/Avalonia.OpenGL/Egl/EglConsts.cs
@@ -64,7 +64,7 @@ namespace Avalonia.OpenGL.Egl
public const int EGL_WIDTH = 0x3057;
public const int EGL_WINDOW_BIT = 0x0004;
-// public const int EGL_BACK_BUFFER = 0x3084;
+ public const int EGL_BACK_BUFFER = 0x3084;
// public const int EGL_BIND_TO_TEXTURE_RGB = 0x3039;
// public const int EGL_BIND_TO_TEXTURE_RGBA = 0x303A;
public const int EGL_CONTEXT_LOST = 0x300E;
@@ -73,11 +73,11 @@ namespace Avalonia.OpenGL.Egl
// public const int EGL_MIPMAP_TEXTURE = 0x3082;
// public const int EGL_MIPMAP_LEVEL = 0x3083;
// public const int EGL_NO_TEXTURE = 0x305C;
-// public const int EGL_TEXTURE_2D = 0x305F;
-// public const int EGL_TEXTURE_FORMAT = 0x3080;
+ public const int EGL_TEXTURE_2D = 0x305F;
+ public const int EGL_TEXTURE_FORMAT = 0x3080;
// public const int EGL_TEXTURE_RGB = 0x305D;
-// public const int EGL_TEXTURE_RGBA = 0x305E;
-// public const int EGL_TEXTURE_TARGET = 0x3081;
+ public const int EGL_TEXTURE_RGBA = 0x305E;
+ public const int EGL_TEXTURE_TARGET = 0x3081;
// public const int EGL_ALPHA_FORMAT = 0x3088;
// public const int EGL_ALPHA_FORMAT_NONPRE = 0x308B;
@@ -216,5 +216,7 @@ namespace Avalonia.OpenGL.Egl
public const int EGL_TEXTURE_OFFSET_Y_ANGLE = 0x3491;
public const int EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6;
+
+ public const int EGL_TEXTURE_INTERNAL_FORMAT_ANGLE = 0x345D;
}
}
diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs
index 0b4b7d26a1..48dfc6c4c0 100644
--- a/src/Avalonia.OpenGL/Egl/EglContext.cs
+++ b/src/Avalonia.OpenGL/Egl/EglContext.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Reactive;
@@ -19,21 +20,24 @@ namespace Avalonia.OpenGL.Egl
private readonly object _lock;
internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface,
- GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, Dictionary features)
+ GlVersion version, int sampleCount, int stencilSize, Action disposeCallback,
+ Dictionary> features)
{
_disp = display;
_egl = egl;
_sharedWith = sharedWith;
_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);
+ _features = features.ToDictionary(x => x.Key, x => x.Value(this));
+ }
}
public IntPtr Context =>
@@ -155,6 +159,14 @@ namespace Avalonia.OpenGL.Egl
{
if(_context == IntPtr.Zero)
return;
+
+ foreach(var f in _features.ToList())
+ if (f.Value is IDisposable d)
+ {
+ d.Dispose();
+ _features.Remove(f.Key);
+ }
+
_egl.DestroyContext(_disp.Handle, Context);
OffscreenSurface?.Dispose();
_context = IntPtr.Zero;
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
index c67d7674a8..60cad2fa1e 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs
@@ -93,7 +93,7 @@ namespace Avalonia.OpenGL.Egl
var rv = new EglContext(this, _egl, share, ctx, offscreenSurface,
_config.Version, _config.SampleCount, _config.StencilSize,
- options.DisposeCallback, options.ExtraFeatures);
+ options.DisposeCallback, options.ExtraFeatures ?? new());
_contexts.Add(rv);
return rv;
}
diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
index 5648645c54..906a533e2d 100644
--- a/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
+++ b/src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
@@ -19,7 +19,7 @@ public class EglContextOptions
public EglContext ShareWith { get; set; }
public EglSurface OffscreenSurface { get; set; }
public Action DisposeCallback { get; set; }
- public Dictionary ExtraFeatures { get; set; }
+ public Dictionary> ExtraFeatures { get; set; }
}
public class EglDisplayCreationOptions : EglDisplayOptions
diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs
index 06aafb4f57..d2d41f4def 100644
--- a/src/Avalonia.OpenGL/Egl/EglInterface.cs
+++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs
@@ -98,6 +98,9 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglCreateWindowSurface")]
public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
+ [GetProcAddress("eglBindTexImage")]
+ public partial int BindTexImage(IntPtr display, IntPtr surface, int buffer);
+
[GetProcAddress("eglGetConfigAttrib")]
public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
diff --git a/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs b/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
new file mode 100644
index 0000000000..02d152ff61
--- /dev/null
+++ b/src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using Avalonia.SourceGenerator;
+using static Avalonia.OpenGL.GlConsts;
+
+namespace Avalonia.OpenGL.Features;
+
+unsafe partial class ExternalObjectsInterface
+{
+ public ExternalObjectsInterface(Func getProcAddress)
+ {
+ Initialize(getProcAddress);
+ }
+
+ [GetProcAddress("glImportMemoryFdEXT", true)]
+ public partial void ImportMemoryFdEXT(uint memory, ulong size, int handleType, int fd);
+
+ [GetProcAddress("glImportSemaphoreFdEXT", true)]
+ public partial void ImportSemaphoreFdEXT(uint semaphore,
+ int handleType,
+ int fd);
+
+ [GetProcAddress("glCreateMemoryObjectsEXT")]
+ public partial void CreateMemoryObjectsEXT(int n, out uint memoryObjects);
+
+ [GetProcAddress("glDeleteMemoryObjectsEXT")]
+ public partial void DeleteMemoryObjectsEXT(int n, ref uint objects);
+
+ [GetProcAddress("glTexStorageMem2DEXT")]
+ public partial void TexStorageMem2DEXT(int target, int levels, int internalFormat, int width, int height,
+ uint memory, ulong offset);
+
+ [GetProcAddress("glGenSemaphoresEXT")]
+ public partial void GenSemaphoresEXT(int n, out uint semaphores);
+
+ [GetProcAddress("glDeleteSemaphoresEXT")]
+ public partial void DeleteSemaphoresEXT(int n, ref uint semaphores);
+
+ [GetProcAddress("glWaitSemaphoreEXT")]
+ public partial void WaitSemaphoreEXT(uint semaphore,
+ uint numBufferBarriers, uint* buffers,
+ uint numTextureBarriers, int* textures,
+ int* srcLayouts);
+
+ [GetProcAddress("glSignalSemaphoreEXT")]
+ public partial void SignalSemaphoreEXT(uint semaphore,
+ uint numBufferBarriers, uint* buffers,
+ uint numTextureBarriers, int* textures,
+ int* dstLayouts);
+
+
+ [GetProcAddress("glGetUnsignedBytei_vEXT", true)]
+ public partial void GetUnsignedBytei_vEXT(int target, uint index, byte* data);
+
+ [GetProcAddress("glGetUnsignedBytevEXT", true)]
+ public partial void GetUnsignedBytevEXT(int target, byte* data);
+}
+
+public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFeature
+{
+ private readonly IGlContext _context;
+ private readonly ExternalObjectsInterface _ext;
+ private List _imageTypes = new();
+ private List _semaphoreTypes = new();
+
+ public static ExternalObjectsOpenGlExtensionFeature TryCreate(IGlContext context)
+ {
+ var extensions = context.GlInterface.GetExtensions();
+ if (extensions.Contains("GL_EXT_memory_object") && extensions.Contains("GL_EXT_semaphore"))
+ {
+ try
+ {
+ return new ExternalObjectsOpenGlExtensionFeature(context, extensions);
+ }
+ catch (Exception e)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(nameof(ExternalObjectsOpenGlExtensionFeature),
+ "Unable to initialize EXT_external_objects extension: " + e);
+ }
+ }
+
+ return null;
+ }
+
+ private unsafe ExternalObjectsOpenGlExtensionFeature(IGlContext context, List extensions)
+ {
+ _context = context;
+ _ext = new ExternalObjectsInterface(_context.GlInterface.GetProcAddress);
+
+ if (_ext.IsGetUnsignedBytei_vEXTAvailable)
+ {
+ _context.GlInterface.GetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, out var numUiids);
+ if (numUiids > 0)
+ {
+ DeviceUuid = new byte[16];
+ fixed (byte* pUuid = DeviceUuid)
+ _ext.GetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, 0, pUuid);
+ }
+ }
+
+ if (_ext.IsGetUnsignedBytevEXTAvailable)
+ {
+ if (extensions.Contains("GL_EXT_memory_object_win32") || extensions.Contains("GL_EXT_semaphore_win32"))
+ {
+ DeviceLuid = new byte[8];
+ fixed (byte* pLuid = DeviceLuid)
+ _ext.GetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, pLuid);
+ }
+ }
+
+ if (extensions.Contains("GL_EXT_memory_object_fd")
+ && extensions.Contains("GL_EXT_semaphore_fd"))
+ {
+ _imageTypes.Add(KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+ _semaphoreTypes.Add(KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor);
+ }
+
+
+
+ }
+
+ public IReadOnlyList SupportedImportableExternalImageTypes => _imageTypes;
+ public IReadOnlyList SupportedExportableExternalImageTypes { get; } = Array.Empty();
+ public IReadOnlyList SupportedImportableExternalSemaphoreTypes => _semaphoreTypes;
+ public IReadOnlyList SupportedExportableExternalSemaphoreTypes { get; } = Array.Empty();
+ public IReadOnlyList GetSupportedFormatsForExternalMemoryType(string type)
+ {
+ return new[]
+ {
+ PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm
+ };
+ }
+
+ public IGlExportableExternalImageTexture CreateImage(string type, PixelSize size,
+ PlatformGraphicsExternalImageFormat format) =>
+ throw new NotSupportedException();
+
+ public IGlExportableExternalImageTexture CreateSemaphore(string type) => throw new NotSupportedException();
+
+ public IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
+ {
+ if(!_imageTypes.Contains(handle.HandleDescriptor))
+ throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+
+ if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor)
+ {
+ while (_context.GlInterface.GetError() != 0)
+ {
+ //Skip existing errors
+ }
+ _ext.CreateMemoryObjectsEXT(1, out var memoryObject);
+ _ext.ImportMemoryFdEXT(memoryObject, properties.MemorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT,
+ handle.Handle.ToInt32());
+
+ var err = _context.GlInterface.GetError();
+ if (err != 0)
+ throw OpenGlException.GetFormattedException("glImportMemoryFdEXT", err);
+
+ _context.GlInterface.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
+
+ var texture = _context.GlInterface.GenTexture();
+ _context.GlInterface.BindTexture(GL_TEXTURE_2D, texture);
+ _ext.TexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, properties.Width, properties.Height,
+ memoryObject, properties.MemoryOffset);
+ err = _context.GlInterface.GetError();
+
+ _context.GlInterface.BindTexture(GL_TEXTURE_2D, oldTexture);
+ if (err != 0)
+ throw OpenGlException.GetFormattedException("glTexStorageMem2DEXT", err);
+
+ return new ExternalImageTexture(_context, properties, _ext, memoryObject, texture);
+ }
+
+ throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+ }
+
+ public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle)
+ {
+ if(!_semaphoreTypes.Contains(handle.HandleDescriptor))
+ throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+
+ if (handle.HandleDescriptor ==
+ KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
+ {
+ _ext.GenSemaphoresEXT(1, out var semaphore);
+ _ext.ImportSemaphoreFdEXT(semaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, handle.Handle.ToInt32());
+ return new ExternalSemaphore(_context, _ext, semaphore);
+ }
+
+ throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+ }
+
+ public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+ {
+ if (imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor)
+ return CompositionGpuImportedImageSynchronizationCapabilities.Semaphores;
+ return default;
+ }
+
+ public byte[] DeviceLuid { get; }
+ public byte[] DeviceUuid { get; }
+
+ unsafe class ExternalSemaphore : IGlExternalSemaphore
+ {
+ private readonly IGlContext _context;
+ private readonly ExternalObjectsInterface _ext;
+ private uint _semaphore;
+
+ public ExternalSemaphore(IGlContext context, ExternalObjectsInterface ext, uint semaphore)
+ {
+ _context = context;
+ _ext = ext;
+ _semaphore = semaphore;
+ }
+
+ public void Dispose()
+ {
+ if(_context.IsLost)
+ return;
+ using (_context.EnsureCurrent())
+ _ext.DeleteSemaphoresEXT(1, ref _semaphore);
+ _semaphore = 0;
+ }
+
+ public void WaitSemaphore(IGlExternalImageTexture texture)
+ {
+ var tex = (ExternalImageTexture)texture;
+ var texId = tex.TextureId;
+ var srcLayout = GL_LAYOUT_TRANSFER_SRC_EXT;
+ _ext.WaitSemaphoreEXT(_semaphore, 0, null, 1, &texId, &srcLayout);
+ }
+
+ public void SignalSemaphore(IGlExternalImageTexture texture)
+ {
+ var tex = (ExternalImageTexture)texture;
+ var texId = tex.TextureId;
+ var dstLayout = 0;
+ _ext.SignalSemaphoreEXT(_semaphore, 0, null, 1, &texId, &dstLayout);
+ }
+ }
+
+ class ExternalImageTexture : IGlExternalImageTexture
+ {
+ private readonly IGlContext _context;
+ private readonly ExternalObjectsInterface _ext;
+ private uint _objectId;
+
+ public ExternalImageTexture(IGlContext context,
+ PlatformGraphicsExternalImageProperties properties,
+ ExternalObjectsInterface ext, uint objectId, int textureId)
+ {
+ Properties = properties;
+ TextureId = textureId;
+ _context = context;
+ _ext = ext;
+ _objectId = objectId;
+ }
+
+ public void Dispose()
+ {
+ if(_context.IsLost)
+ return;
+ using (_context.EnsureCurrent())
+ {
+ _context.GlInterface.DeleteTexture(TextureId);
+ _ext.DeleteMemoryObjectsEXT(1, ref _objectId);
+ _objectId = 0;
+ }
+ }
+
+ public void AcquireKeyedMutex(uint key) => throw new NotSupportedException();
+
+ public void ReleaseKeyedMutex(uint key) => throw new NotSupportedException();
+
+ public int TextureId { get; }
+ public int InternalFormat => GL_RGBA8;
+ public PlatformGraphicsExternalImageProperties Properties { get; }
+ }
+}
diff --git a/src/Avalonia.OpenGL/GlConsts.cs b/src/Avalonia.OpenGL/GlConsts.cs
index 3fdc147701..4fbe738ca9 100644
--- a/src/Avalonia.OpenGL/GlConsts.cs
+++ b/src/Avalonia.OpenGL/GlConsts.cs
@@ -534,7 +534,7 @@ namespace Avalonia.OpenGL
// public const int GL_MAX_ELEMENTS_VERTICES = 0x80E8;
// public const int GL_MAX_ELEMENTS_INDICES = 0x80E9;
// public const int GL_BGR = 0x80E0;
-// public const int GL_BGRA = 0x80E1;
+ public const int GL_BGRA = 0x80E1;
// public const int GL_UNSIGNED_BYTE_3_3_2 = 0x8032;
// public const int GL_UNSIGNED_BYTE_2_3_3_REV = 0x8362;
// public const int GL_UNSIGNED_SHORT_5_6_5 = 0x8363;
@@ -3372,16 +3372,16 @@ namespace Avalonia.OpenGL
// public const int GL_TILING_TYPES_EXT = 0x9583;
// public const int GL_OPTIMAL_TILING_EXT = 0x9584;
// public const int GL_LINEAR_TILING_EXT = 0x9585;
-// public const int GL_NUM_DEVICE_UUIDS_EXT = 0x9596;
-// public const int GL_DEVICE_UUID_EXT = 0x9597;
+ internal const int GL_NUM_DEVICE_UUIDS_EXT = 0x9596;
+ internal const int GL_DEVICE_UUID_EXT = 0x9597;
// public const int GL_DRIVER_UUID_EXT = 0x9598;
// public const int GL_UUID_SIZE_EXT = 16;
// public const int GL_EXT_memory_object_fd = 1;
-// public const int GL_HANDLE_TYPE_OPAQUE_FD_EXT = 0x9586;
+ internal const int GL_HANDLE_TYPE_OPAQUE_FD_EXT = 0x9586;
// public const int GL_EXT_memory_object_win32 = 1;
// public const int GL_HANDLE_TYPE_OPAQUE_WIN32_EXT = 0x9587;
// public const int GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT = 0x9588;
-// public const int GL_DEVICE_LUID_EXT = 0x9599;
+ public const int GL_DEVICE_LUID_EXT = 0x9599;
// public const int GL_DEVICE_NODE_MASK_EXT = 0x959A;
// public const int GL_LUID_SIZE_EXT = 8;
// public const int GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT = 0x9589;
@@ -3483,11 +3483,11 @@ namespace Avalonia.OpenGL
// public const int GL_SECONDARY_COLOR_ARRAY_EXT = 0x845E;
// public const int GL_EXT_semaphore = 1;
// public const int GL_LAYOUT_GENERAL_EXT = 0x958D;
-// public const int GL_LAYOUT_COLOR_ATTACHMENT_EXT = 0x958E;
+ internal const int GL_LAYOUT_COLOR_ATTACHMENT_EXT = 0x958E;
// public const int GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT = 0x958F;
// public const int GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT = 0x9590;
// public const int GL_LAYOUT_SHADER_READ_ONLY_EXT = 0x9591;
-// public const int GL_LAYOUT_TRANSFER_SRC_EXT = 0x9592;
+ internal const int GL_LAYOUT_TRANSFER_SRC_EXT = 0x9592;
// public const int GL_LAYOUT_TRANSFER_DST_EXT = 0x9593;
// public const int GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT = 0x9530;
// public const int GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT = 0x9531;
diff --git a/src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs b/src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs
new file mode 100644
index 0000000000..676f8eaf86
--- /dev/null
+++ b/src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.OpenGL;
+
+public interface IGlContextExternalObjectsFeature
+{
+ IReadOnlyList SupportedImportableExternalImageTypes { get; }
+ IReadOnlyList SupportedExportableExternalImageTypes { get; }
+ IReadOnlyList SupportedImportableExternalSemaphoreTypes { get; }
+ IReadOnlyList SupportedExportableExternalSemaphoreTypes { get; }
+ IReadOnlyList GetSupportedFormatsForExternalMemoryType(string type);
+
+ IGlExportableExternalImageTexture CreateImage(string type,PixelSize size, PlatformGraphicsExternalImageFormat format);
+
+ IGlExportableExternalImageTexture CreateSemaphore(string type);
+ IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties);
+ IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle);
+ CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType);
+ public byte[]? DeviceLuid { get; }
+ public byte[]? DeviceUuid { get; }
+}
+
+public interface IGlExternalSemaphore : IDisposable
+{
+ void WaitSemaphore(IGlExternalImageTexture texture);
+ void SignalSemaphore(IGlExternalImageTexture texture);
+}
+
+public interface IGlExportableExternalSemaphore : IGlExternalSemaphore
+{
+ IPlatformHandle GetHandle();
+}
+
+public interface IGlExternalImageTexture : IDisposable
+{
+ void AcquireKeyedMutex(uint key);
+ void ReleaseKeyedMutex(uint key);
+ int TextureId { get; }
+ int InternalFormat { get; }
+
+ PlatformGraphicsExternalImageProperties Properties { get; }
+}
+
+public interface IGlExportableExternalImageTexture : IGlExternalImageTexture
+{
+ IPlatformHandle GetHandle();
+}
diff --git a/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
index 9c22d446ef..2043e944d1 100644
--- a/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
+++ b/src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
@@ -1,6 +1,6 @@
+using System;
using System.Collections.Generic;
-using Avalonia.OpenGL.Imaging;
-using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
namespace Avalonia.OpenGL
{
@@ -8,6 +8,13 @@ namespace Avalonia.OpenGL
{
bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable preferredVersions = null);
- IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
+ ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size);
+ }
+
+ public interface ICompositionImportableOpenGlSharedTexture : ICompositionImportableSharedGpuContextImage
+ {
+ int TextureId { get; }
+ int InternalFormat { get; }
+ PixelSize Size { get; }
}
}
diff --git a/src/Avalonia.OpenGL/IPlatformGraphicsOpenGlContextFactory.cs b/src/Avalonia.OpenGL/IPlatformGraphicsOpenGlContextFactory.cs
new file mode 100644
index 0000000000..e6c83415e6
--- /dev/null
+++ b/src/Avalonia.OpenGL/IPlatformGraphicsOpenGlContextFactory.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace Avalonia.OpenGL;
+
+public interface IPlatformGraphicsOpenGlContextFactory
+{
+ IGlContext CreateContext(IEnumerable? versions);
+}
diff --git a/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs
deleted file mode 100644
index 22f0cebf57..0000000000
--- a/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-using Avalonia.Metadata;
-using Avalonia.Platform;
-
-namespace Avalonia.OpenGL.Imaging
-{
- [Unstable]
- public interface IOpenGlBitmapImpl : IBitmapImpl
- {
- IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback);
- bool SupportsContext(IGlContext context);
- }
-
- [Unstable]
- public interface IOpenGlBitmapAttachment : IDisposable
- {
- void Present();
- }
-}
diff --git a/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs b/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
deleted file mode 100644
index 53013ae5a3..0000000000
--- a/src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using Avalonia.Media;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform;
-using Avalonia.Threading;
-
-namespace Avalonia.OpenGL.Imaging
-{
- public class OpenGlBitmap : Bitmap, IAffectsRender
- {
- private IOpenGlBitmapImpl _impl;
-
- public OpenGlBitmap(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
- PixelSize size, Vector dpi)
- : base(CreateOrThrow(feature, size, dpi))
- {
- _impl = (IOpenGlBitmapImpl)PlatformImpl.Item;
- }
-
- static IOpenGlBitmapImpl CreateOrThrow(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
- PixelSize size, Vector dpi) => feature.CreateOpenGlBitmap(size, dpi);
-
- public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) =>
- _impl.CreateFramebufferAttachment(context, SetIsDirty);
-
- public bool SupportsContext(IGlContext context) => _impl.SupportsContext(context);
-
- void SetIsDirty()
- {
- if (Dispatcher.UIThread.CheckAccess())
- CallInvalidated();
- else
- Dispatcher.UIThread.Post(CallInvalidated);
- }
-
- private void CallInvalidated() => Invalidated?.Invoke(this, EventArgs.Empty);
-
- public event EventHandler Invalidated;
- }
-}
diff --git a/src/Avalonia.OpenGL/OpenGlException.cs b/src/Avalonia.OpenGL/OpenGlException.cs
index efe305dba5..c498ed7833 100644
--- a/src/Avalonia.OpenGL/OpenGlException.cs
+++ b/src/Avalonia.OpenGL/OpenGlException.cs
@@ -27,6 +27,9 @@ namespace Avalonia.OpenGL
return GetFormattedException(funcName, (GlErrors)err, err);
}
+ public static OpenGlException GetFormattedException(string funcName, int errorCode) =>
+ GetFormattedException(funcName, (GlErrors)errorCode, errorCode);
+
public static OpenGlException GetFormattedEglException(string funcName, int errorCode) =>
GetFormattedException(funcName, (EglErrors)errorCode,errorCode);
diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs
index 88006f0b47..572e4a6077 100644
--- a/src/Avalonia.X11/Glx/GlxContext.cs
+++ b/src/Avalonia.X11/Glx/GlxContext.cs
@@ -2,7 +2,9 @@ using System;
using System.Collections.Generic;
using System.Threading;
using Avalonia.OpenGL;
+using Avalonia.OpenGL.Features;
using Avalonia.Reactive;
+
namespace Avalonia.X11.Glx
{
class GlxContext : IGlContext
@@ -14,6 +16,7 @@ namespace Avalonia.X11.Glx
private readonly IntPtr _defaultXid;
private readonly bool _ownsPBuffer;
private readonly object _lock = new object();
+ private ExternalObjectsOpenGlExtensionFeature? _externalObjects;
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display,
GlxContext sharedWith,
@@ -32,7 +35,10 @@ namespace Avalonia.X11.Glx
SampleCount = sampleCount;
StencilSize = stencilSize;
using (MakeCurrent())
+ {
GlInterface = new GlInterface(version, GlxInterface.SafeGetProcAddress);
+ _externalObjects = ExternalObjectsOpenGlExtensionFeature.TryCreate(this);
+ }
}
public GlxDisplay Display { get; }
@@ -123,6 +129,11 @@ namespace Avalonia.X11.Glx
Glx.DestroyPbuffer(_x11.Display, _defaultXid);
}
- public object TryGetFeature(Type featureType) => null;
+ public object TryGetFeature(Type featureType)
+ {
+ if (featureType == typeof(IGlContextExternalObjectsFeature))
+ return _externalObjects;
+ return null;
+ }
}
}
diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
index b064445a0b..a5782037f3 100644
--- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
using SkiaSharp;
@@ -33,9 +31,4 @@ namespace Avalonia.Skia
bool CanBlit { get; }
void Blit(SKCanvas canvas);
}
-
- public interface IOpenGlAwareSkiaGpu : ISkiaGpu
- {
- IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
- }
}
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs
new file mode 100644
index 0000000000..4bf43634ef
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs
@@ -0,0 +1,191 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using Avalonia.OpenGL;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using SkiaSharp;
+
+namespace Avalonia.Skia;
+
+internal class GlSkiaExternalObjectsFeature : IExternalObjectsRenderInterfaceContextFeature
+{
+ private readonly GlSkiaGpu _gpu;
+ private readonly IGlContextExternalObjectsFeature? _feature;
+
+ public GlSkiaExternalObjectsFeature(GlSkiaGpu gpu, IGlContextExternalObjectsFeature? feature)
+ {
+ _gpu = gpu;
+ _feature = feature;
+ }
+
+ public IReadOnlyList SupportedImageHandleTypes => _feature?.SupportedImportableExternalImageTypes
+ ?? Array.Empty();
+ public IReadOnlyList SupportedSemaphoreTypes => _feature?.SupportedImportableExternalSemaphoreTypes
+ ?? Array.Empty();
+
+ public IPlatformRenderInterfaceImportedImage ImportImage(IPlatformHandle handle,
+ PlatformGraphicsExternalImageProperties properties)
+ {
+ if (_feature == null)
+ throw new NotSupportedException("Importing this platform handle is not supported");
+ using (_gpu.EnsureCurrent())
+ {
+ var image = _feature.ImportImage(handle, properties);
+ return new GlSkiaImportedImage(_gpu, image);
+ }
+ }
+
+ public IPlatformRenderInterfaceImportedImage ImportImage(ICompositionImportableSharedGpuContextImage image)
+ {
+ var img = (GlSkiaSharedTextureForComposition)image;
+ if (!img.Context.IsSharedWith(_gpu.GlContext))
+ throw new InvalidOperationException("Contexts do not belong to the same share group");
+
+ return new GlSkiaImportedImage(_gpu, img);
+ }
+
+ public IPlatformRenderInterfaceImportedSemaphore ImportSemaphore(IPlatformHandle handle)
+ {
+ if (_feature == null)
+ throw new NotSupportedException("Importing this platform handle is not supported");
+ using (_gpu.EnsureCurrent())
+ {
+ var semaphore = _feature.ImportSemaphore(handle);
+ return new GlSkiaImportedSemaphore(_gpu, semaphore);
+ }
+ }
+
+ public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+ => _feature?.GetSynchronizationCapabilities(imageHandleType) ?? default;
+
+ public byte[]? DeviceUuid => _feature?.DeviceUuid;
+ public byte[]? DeviceLuid => _feature?.DeviceLuid;
+}
+
+internal class GlSkiaImportedSemaphore : IPlatformRenderInterfaceImportedSemaphore
+{
+ private readonly GlSkiaGpu _gpu;
+ public IGlExternalSemaphore Semaphore { get; }
+
+ public GlSkiaImportedSemaphore(GlSkiaGpu gpu, IGlExternalSemaphore semaphore)
+ {
+ _gpu = gpu;
+ Semaphore = semaphore;
+ }
+
+ public void Dispose() => Semaphore.Dispose();
+}
+
+internal class GlSkiaImportedImage : IPlatformRenderInterfaceImportedImage
+{
+ private readonly GlSkiaSharedTextureForComposition? _sharedTexture;
+ private readonly GlSkiaGpu _gpu;
+ private readonly IGlExternalImageTexture? _image;
+
+ public GlSkiaImportedImage(GlSkiaGpu gpu, IGlExternalImageTexture image)
+ {
+ _gpu = gpu;
+ _image = image;
+ }
+
+ public GlSkiaImportedImage(GlSkiaGpu gpu, GlSkiaSharedTextureForComposition sharedTexture)
+ {
+ _gpu = gpu;
+ _sharedTexture = sharedTexture;
+ }
+
+ public void Dispose()
+ {
+ _image?.Dispose();
+ _sharedTexture?.Dispose(_gpu.GlContext);
+ }
+
+ SKColorType ConvertColorType(PlatformGraphicsExternalImageFormat format) =>
+ format switch
+ {
+ PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm => SKColorType.Bgra8888,
+ PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm => SKColorType.Rgba8888,
+ _ => SKColorType.Rgba8888
+ };
+
+ SKSurface? TryCreateSurface(int textureId, int format, int width, int height, bool topLeft)
+ {
+ var origin = topLeft ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft;
+ using var texture = new GRBackendTexture(width, height, false,
+ new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId, (uint)format));
+ var surf = SKSurface.Create(_gpu.GrContext, texture, origin, SKColorType.Rgba8888);
+ if (surf != null)
+ return surf;
+
+ using var unformatted = new GRBackendTexture(width, height, false,
+ new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId));
+
+ return SKSurface.Create(_gpu.GrContext, unformatted, origin, SKColorType.Rgba8888);
+ }
+
+ IBitmapImpl TakeSnapshot()
+ {
+ var width = _image?.Properties.Width ?? _sharedTexture!.Size.Width;
+ var height = _image?.Properties.Height ?? _sharedTexture!.Size.Height;
+ var internalFormat = _image?.InternalFormat ?? _sharedTexture!.InternalFormat;
+ var textureId = _image?.TextureId ?? _sharedTexture!.TextureId;
+ var topLeft = _image?.Properties.TopLeftOrigin ?? false;
+
+ using var texture = new GRBackendTexture(width, height, false,
+ new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId, (uint)internalFormat));
+
+ IBitmapImpl rv;
+ using (var surf = TryCreateSurface(textureId, internalFormat, width, height, topLeft))
+ {
+ if (surf == null)
+ throw new OpenGlException("Unable to consume provided texture");
+ rv = new ImmutableBitmap(surf.Snapshot());
+ }
+
+ _gpu.GrContext.Flush();
+ _gpu.GlContext.GlInterface.Flush();
+ return rv;
+ }
+
+ public IBitmapImpl SnapshotWithKeyedMutex(uint acquireIndex, uint releaseIndex)
+ {
+ using (_gpu.EnsureCurrent())
+ {
+ _image.AcquireKeyedMutex(acquireIndex);
+ try
+ {
+ return TakeSnapshot();
+ }
+ finally
+ {
+ _image.ReleaseKeyedMutex(releaseIndex);
+ }
+ }
+ }
+
+ public IBitmapImpl SnapshotWithSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
+ IPlatformRenderInterfaceImportedSemaphore signalSemaphore)
+ {
+ var wait = (GlSkiaImportedSemaphore)waitForSemaphore;
+ var signal = (GlSkiaImportedSemaphore)signalSemaphore;
+ using (_gpu.EnsureCurrent())
+ {
+ wait.Semaphore.WaitSemaphore(_image);
+ try
+ {
+ return TakeSnapshot();
+ }
+ finally
+ {
+ signal.Semaphore.SignalSemaphore(_image);
+ }
+ }
+ }
+
+ public IBitmapImpl SnapshotWithAutomaticSync()
+ {
+ using (_gpu.EnsureCurrent())
+ return TakeSnapshot();
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
index cdba3b9ea2..bf3e950e81 100644
--- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
@@ -3,19 +3,22 @@ using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Logging;
using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using SkiaSharp;
+using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
- class GlSkiaGpu : IOpenGlAwareSkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
+ class GlSkiaGpu : ISkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
{
private GRContext _grContext;
private IGlContext _glContext;
+ public GRContext GrContext => _grContext;
+ public IGlContext GlContext => _glContext;
private List _postDisposeCallbacks = new();
private bool? _canCreateSurfaces;
+ private IExternalObjectsRenderInterfaceContextFeature? _externalObjectsFeature;
public GlSkiaGpu(IGlContext context, long? maxResourceBytes)
{
@@ -32,6 +35,9 @@ namespace Avalonia.Skia
_grContext.SetResourceCacheLimit(maxResourceBytes.Value);
}
}
+
+ context.TryGetFeature(out var externalObjects);
+ _externalObjectsFeature = new GlSkiaExternalObjectsFeature(this, externalObjects);
}
}
@@ -103,8 +109,34 @@ namespace Avalonia.Skia
public IGlContext CreateSharedContext(IEnumerable preferredVersions = null) =>
_glContext.CreateSharedContext(preferredVersions);
- public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi);
-
+ public ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size)
+ {
+ if (!context.IsSharedWith(_glContext))
+ throw new InvalidOperationException("Contexts do not belong to the same share group");
+
+ using (context.EnsureCurrent())
+ {
+ var gl = context.GlInterface;
+ gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out int oldTexture);
+ var tex = gl.GenTexture();
+
+ var format = context.Version.Type == GlProfileType.OpenGLES && context.Version.Major == 2
+ ? GL_RGBA
+ : GL_RGBA8;
+
+ gl.BindTexture(GL_TEXTURE_2D, tex);
+ gl.TexImage2D(GL_TEXTURE_2D, 0,
+ format, size.Width, size.Height,
+ 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
+
+ gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl.BindTexture(GL_TEXTURE_2D, oldTexture);
+
+ return new GlSkiaSharedTextureForComposition(context, tex, format, size);
+ }
+ }
+
public void Dispose()
{
if (_glContext.IsLost)
@@ -125,6 +157,8 @@ namespace Avalonia.Skia
{
if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature))
return this;
+ if (featureType == typeof(IExternalObjectsRenderInterfaceContextFeature))
+ return _externalObjectsFeature;
return null;
}
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaSharedTextureForComposition.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaSharedTextureForComposition.cs
new file mode 100644
index 0000000000..2d53254500
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaSharedTextureForComposition.cs
@@ -0,0 +1,44 @@
+using Avalonia.OpenGL;
+
+namespace Avalonia.Skia;
+
+internal class GlSkiaSharedTextureForComposition : ICompositionImportableOpenGlSharedTexture
+{
+ public IGlContext Context { get; }
+ private readonly object _lock = new();
+
+ public GlSkiaSharedTextureForComposition(IGlContext context, int textureId, int internalFormat, PixelSize size)
+ {
+ Context = context;
+ TextureId = textureId;
+ InternalFormat = internalFormat;
+ Size = size;
+ }
+ public void Dispose(IGlContext context)
+ {
+ lock (_lock)
+ {
+ if(TextureId == 0)
+ return;
+ try
+ {
+ using (context.EnsureCurrent())
+ context.GlInterface.DeleteTexture(TextureId);
+ }
+ catch
+ {
+ // Ignore
+ }
+
+ TextureId = 0;
+ }
+ }
+
+ public int TextureId { get; private set; }
+ public int InternalFormat { get; }
+ public PixelSize Size { get; }
+ public void Dispose()
+ {
+ Dispose(Context);
+ }
+}
\ No newline at end of file
diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
deleted file mode 100644
index d8bff7cfc8..0000000000
--- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
-using Avalonia.Utilities;
-using SkiaSharp;
-using static Avalonia.OpenGL.GlConsts;
-
-namespace Avalonia.Skia
-{
- class GlOpenGlBitmapImpl : IOpenGlBitmapImpl, IDrawableBitmapImpl
- {
- private readonly IGlContext _context;
- private readonly object _lock = new object();
- private IGlPresentableOpenGlSurface _surface;
-
- public GlOpenGlBitmapImpl(IGlContext context, PixelSize pixelSize, Vector dpi)
- {
- _context = context;
- PixelSize = pixelSize;
- Dpi = dpi;
- }
-
- public Vector Dpi { get; }
- public PixelSize PixelSize { get; }
- public int Version { get; private set; }
- public void Save(string fileName, int? quality = null) => throw new NotSupportedException();
-
- public void Save(Stream stream, int? quality = null) => throw new NotSupportedException();
-
- public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
- {
- lock (_lock)
- {
- if (_surface == null)
- return;
- using (_surface.Lock())
- {
- using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false,
- new GRGlTextureInfo(
- GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(),
- (uint)_surface.InternalFormat)))
- using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft,
- SKColorType.Rgba8888, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)))
- {
- // Again, silently ignore, if something went wrong it's not our fault
- if (surface == null)
- return;
-
- using (var snapshot = surface.Snapshot())
- context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint);
- }
-
- }
- }
- }
-
- public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback)
- {
- if (!SupportsContext(context))
- throw new OpenGlException("Context is not supported for texture sharing");
- return new SharedOpenGlBitmapAttachment(this, context, presentCallback);
- }
-
- public bool SupportsContext(IGlContext context)
- {
- // TODO: negotiated platform surface sharing
- return _context.IsSharedWith(context);
- }
-
- public void Dispose()
- {
-
- }
-
- internal void Present(IGlPresentableOpenGlSurface surface)
- {
- lock (_lock)
- {
- _surface = surface;
- }
- }
- }
-
- interface IGlPresentableOpenGlSurface : IDisposable
- {
- int GetTextureId();
- int InternalFormat { get; }
- IDisposable Lock();
- }
-
- class SharedOpenGlBitmapAttachment : IOpenGlBitmapAttachment, IGlPresentableOpenGlSurface
- {
- private readonly GlOpenGlBitmapImpl _bitmap;
- private readonly IGlContext _context;
- private readonly Action _presentCallback;
- private readonly int _fbo;
- private readonly int _texture;
- private readonly int _frontBuffer;
- private bool _disposed;
- private readonly DisposableLock _lock = new DisposableLock();
-
- public unsafe SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback)
- {
- _bitmap = bitmap;
- _context = context;
- _presentCallback = presentCallback;
- using (_context.EnsureCurrent())
- {
- var glVersion = _context.Version;
- InternalFormat = glVersion.Type == GlProfileType.OpenGLES && glVersion.Major == 2
- ? GL_RGBA
- : GL_RGBA8;
-
- _context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo);
- if (_fbo == 0)
- throw new OpenGlException("Current FBO is 0");
-
- {
- var gl = _context.GlInterface;
-
- Span textures = stackalloc int[2];
- fixed (int* ptex = textures)
- gl.GenTextures(2, ptex);
- _texture = textures[0];
- _frontBuffer = textures[1];
-
- gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
- foreach (var t in textures)
- {
- gl.BindTexture(GL_TEXTURE_2D, t);
- gl.TexImage2D(GL_TEXTURE_2D, 0,
- InternalFormat,
- _bitmap.PixelSize.Width, _bitmap.PixelSize.Height,
- 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
-
- gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- }
-
- gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
- gl.BindTexture(GL_TEXTURE_2D, oldTexture);
- }
- }
- }
-
- public void Present()
- {
- using (_context.EnsureCurrent())
- {
- if (_disposed)
- throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment));
-
- var gl = _context.GlInterface;
-
- gl.Finish();
- using (Lock())
- {
- gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo);
- gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
- gl.GetIntegerv(GL_ACTIVE_TEXTURE, out var oldActive);
-
- gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo);
- gl.ActiveTexture(GL_TEXTURE0);
- gl.BindTexture(GL_TEXTURE_2D, _frontBuffer);
-
- gl.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _bitmap.PixelSize.Width,
- _bitmap.PixelSize.Height);
-
- gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo);
- gl.ActiveTexture(oldActive);
- gl.BindTexture(GL_TEXTURE_2D, oldTexture);
-
- gl.Finish();
- }
- }
-
- _bitmap.Present(this);
- _presentCallback();
- }
-
- public unsafe void Dispose()
- {
- var gl = _context.GlInterface;
- _bitmap.Present(null);
-
- if(_disposed)
- return;
- using (_context.MakeCurrent())
- using (Lock())
- {
- if(_disposed)
- return;
- _disposed = true;
- var ptex = stackalloc[] { _texture, _frontBuffer };
- gl.DeleteTextures(2, ptex);
- }
- }
-
- int IGlPresentableOpenGlSurface.GetTextureId()
- {
- return _frontBuffer;
- }
-
- public int InternalFormat { get; }
-
- public IDisposable Lock() => _lock.Lock();
- }
-}
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index e24d805050..802736119f 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -37,6 +37,13 @@ namespace Avalonia.Skia
}
}
+ public ImmutableBitmap(SKImage image)
+ {
+ _image = image;
+ PixelSize = new PixelSize(image.Width, image.Height);
+ Dpi = new Vector(96, 96);
+ }
+
public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
{
SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);
diff --git a/src/Skia/Avalonia.Skia/SkiaBackendContext.cs b/src/Skia/Avalonia.Skia/SkiaBackendContext.cs
index 4949f4a50d..0cf66767cb 100644
--- a/src/Skia/Avalonia.Skia/SkiaBackendContext.cs
+++ b/src/Skia/Avalonia.Skia/SkiaBackendContext.cs
@@ -43,5 +43,7 @@ internal class SkiaContext : IPlatformRenderInterfaceContext
"Don't know how to create a Skia render target from any of provided surfaces");
}
+ public bool IsLost => _gpu.IsLost;
+
public object TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType);
-}
\ No newline at end of file
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index a2f99d9d71..461950b728 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -227,6 +227,7 @@ namespace Avalonia.Direct2D1
}
public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => _platform.CreateRenderTarget(surfaces);
+ public bool IsLost => false;
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) =>
diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
index a749ed1b45..1357e8aa63 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
@@ -52,6 +52,44 @@ namespace Avalonia.Win32.DirectX
D3D11_USAGE_STAGING = 3,
}
+
+ [Flags]
+ internal enum D3D11_RESOURCE_MISC_FLAG
+ {
+ D3D11_RESOURCE_MISC_GENERATE_MIPS = 0x00000001,
+ D3D11_RESOURCE_MISC_SHARED = 0x00000002,
+ D3D11_RESOURCE_MISC_TEXTURECUBE = 0x00000004,
+ D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS = 0x00000010,
+ D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS = 0x00000020,
+ D3D11_RESOURCE_MISC_BUFFER_STRUCTURED = 0x00000040,
+ D3D11_RESOURCE_MISC_RESOURCE_CLAMP = 0x00000080,
+ D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX = 0x00000100,
+ D3D11_RESOURCE_MISC_GDI_COMPATIBLE = 0x00000200,
+ D3D11_RESOURCE_MISC_SHARED_NTHANDLE = 0x00000800,
+ D3D11_RESOURCE_MISC_RESTRICTED_CONTENT = 0x00001000,
+ D3D11_RESOURCE_MISC_RESTRICT_SHARED_RESOURCE = 0x00002000,
+ D3D11_RESOURCE_MISC_RESTRICT_SHARED_RESOURCE_DRIVER = 0x00004000,
+ D3D11_RESOURCE_MISC_GUARDED = 0x00008000,
+ D3D11_RESOURCE_MISC_TILE_POOL = 0x00020000,
+ D3D11_RESOURCE_MISC_TILED = 0x00040000,
+ D3D11_RESOURCE_MISC_HW_PROTECTED = 0x00080000,
+ }
+
+ [Flags]
+ internal enum D3D11_BIND_FLAG
+ {
+ D3D11_BIND_VERTEX_BUFFER = 0x00000001,
+ D3D11_BIND_INDEX_BUFFER = 0x00000002,
+ D3D11_BIND_CONSTANT_BUFFER = 0x00000004,
+ D3D11_BIND_SHADER_RESOURCE = 0x00000008,
+ D3D11_BIND_STREAM_OUTPUT = 0x00000010,
+ D3D11_BIND_RENDER_TARGET = 0x00000020,
+ D3D11_BIND_DEPTH_STENCIL = 0x00000040,
+ D3D11_BIND_UNORDERED_ACCESS = 0x00000080,
+ D3D11_BIND_DECODER = 0x00000200,
+ D3D11_BIND_VIDEO_ENCODER = 0x00000400,
+ }
+
internal enum DXGI_SWAP_EFFECT
{
DXGI_SWAP_EFFECT_DISCARD = 0,
diff --git a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
index 47451831a6..c11a6026e7 100644
--- a/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
+++ b/src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
@@ -282,11 +282,11 @@ namespace Avalonia.Win32.DirectX
public D3D11_USAGE Usage;
- public uint BindFlags;
+ public D3D11_BIND_FLAG BindFlags;
public uint CPUAccessFlags;
- public uint MiscFlags;
+ public D3D11_RESOURCE_MISC_FLAG MiscFlags;
}
#nullable restore
}
diff --git a/src/Windows/Avalonia.Win32/DirectX/directx.idl b/src/Windows/Avalonia.Win32/DirectX/directx.idl
index a4552eedea..1d66c898db 100644
--- a/src/Windows/Avalonia.Win32/DirectX/directx.idl
+++ b/src/Windows/Avalonia.Win32/DirectX/directx.idl
@@ -10,6 +10,7 @@
@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE
@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT
@clr-map HWND IntPtr
+@clr-map HANDLE IntPtr
@clr-map BOOL int
@clr-map DWORD int
@clr-map SIZE_T IntPtr
@@ -259,10 +260,28 @@ interface IDXGISurface : IDXGIDeviceSubObject
HRESULT Unmap();
}
+[uuid( 035f3ab4-482e-4e50-b41f-8a7f8bd8960b)]
+interface IDXGIResource : IDXGIDeviceSubObject
+{
+ HRESULT GetSharedHandle( [out, annotation("_Out_")] HANDLE * pSharedHandle );
+ HRESULT GetUsage( [out] DXGI_USAGE * pUsage );
+ HRESULT SetEvictionPriority( [in] UINT EvictionPriority );
+ HRESULT GetEvictionPriority( [out, retval, annotation("_Out_")] UINT* pEvictionPriority );
+};
+
+
+[ uuid( 9d8e1289-d7b3-465f-8126-250e349af85d)]
+interface IDXGIKeyedMutex :
+ IDXGIDeviceSubObject
+{
+ HRESULT AcquireSync( [in] UINT64 Key, [in] uint dwMilliseconds);
+ HRESULT ReleaseSync( [in] UINT64 Key);
+};
+
[uuid(770aae78-f26f-4dba-a829-253c83d1b387)]
interface IDXGIFactory1 : IDXGIFactory
{
- HRESULT EnumAdapters1([in] UINT Adapter, [out, annotation("_COM_Outptr_")] IDXGIAdapter1** ppAdapter);
+ int EnumAdapters1([in] UINT Adapter, [out] void** ppAdapter);
BOOL IsCurrent();
}
@@ -339,9 +358,9 @@ interface ID3D11Device : IUnknown
IntPtr pInitialData,
[out, retval] IUnknown** ppTexture1D );
HRESULT CreateTexture2D(
- IntPtr pDesc,
+ D3D11_TEXTURE2D_DESC* pDesc,
IntPtr pInitialData,
- [out, retval] IUnknown** ppTexture2D );
+ [out, retval] ID3D11Texture2D** ppTexture2D );
HRESULT CreateTexture3D(
IntPtr pDesc,
IntPtr pInitialData,
@@ -481,3 +500,11 @@ interface ID3D11Device : IUnknown
HRESULT SetExceptionMode( UINT RaiseFlags );
UINT GetExceptionMode();
}
+
+
+[uuid( 6f15aaf2-d208-4e89-9ab4-489535d34f9c)]
+interface ID3D11Texture2D : IUnknown
+{
+ // Just a marker interface for now
+};
+
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs
new file mode 100644
index 0000000000..b0fa76fda2
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs
@@ -0,0 +1,107 @@
+#nullable enable
+using System;
+using System.Threading;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Angle;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Platform;
+using Avalonia.Win32.DirectX;
+using MicroCom.Runtime;
+using static Avalonia.OpenGL.Egl.EglConsts;
+using static Avalonia.OpenGL.GlConsts;
+
+namespace Avalonia.Win32.OpenGl.Angle;
+
+internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture
+{
+ private readonly EglContext _context;
+ private ID3D11Texture2D _texture2D;
+ private EglSurface _eglSurface;
+ private IDXGIKeyedMutex _mutex;
+
+ public unsafe AngleExternalMemoryD3D11Texture2D(EglContext context, ID3D11Texture2D texture2D, PlatformGraphicsExternalImageProperties props)
+ {
+ _context = context;
+ _texture2D = texture2D.CloneReference();
+ _mutex = _texture2D.QueryInterface();
+ Properties = props;
+
+ InternalFormat = GL_RGBA8;
+
+ _eglSurface = _context.Display.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, texture2D.GetNativeIntPtr(),
+ new[]
+ {
+ EGL_WIDTH, props.Width, EGL_HEIGHT, props.Height, EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
+ EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_RGBA, EGL_NONE, EGL_NONE,
+ EGL_NONE
+ });
+
+ var gl = _context.GlInterface;
+ int temp = 0;
+ gl.GenTextures(1, &temp);
+ TextureId = temp;
+ gl.BindTexture(GlConsts.GL_TEXTURE_2D, TextureId);
+
+ if (_context.Display.EglInterface.BindTexImage(_context.Display.Handle, _eglSurface.DangerousGetHandle(),
+ EGL_BACK_BUFFER) == 0)
+
+ throw OpenGlException.GetFormattedException("eglBindTexImage", _context.Display.EglInterface);
+ }
+
+ public void Dispose()
+ {
+
+ if (!_context.IsLost && TextureId != 0)
+ using (_context.EnsureCurrent())
+ _context.GlInterface.DeleteTexture(TextureId);
+ TextureId = 0;
+ _eglSurface?.Dispose();
+ _eglSurface = null!;
+ _texture2D?.Dispose();
+ _texture2D = null!;
+ _mutex?.Dispose();
+ _mutex = null!;
+ }
+
+
+ public void AcquireKeyedMutex(uint key) => _mutex.AcquireSync(key, int.MaxValue);
+
+ public void ReleaseKeyedMutex(uint key) => _mutex.ReleaseSync(key);
+
+ public int TextureId { get; private set; }
+ public int InternalFormat { get; }
+ public PlatformGraphicsExternalImageProperties Properties { get; }
+}
+
+internal class AngleExternalMemoryD3D11ExportedTexture2D : AngleExternalMemoryD3D11Texture2D, IGlExportableExternalImageTexture
+{
+ static IPlatformHandle GetHandle(ID3D11Texture2D texture2D)
+ {
+ using var resource = texture2D.QueryInterface();
+ return new PlatformHandle(resource.SharedHandle,
+ KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle);
+ }
+
+ public AngleExternalMemoryD3D11ExportedTexture2D(EglContext context, ID3D11Texture2D texture2D,
+ D3D11_TEXTURE2D_DESC desc,
+ PlatformGraphicsExternalImageFormat format)
+ : this(context, texture2D, GetHandle(texture2D),
+ new PlatformGraphicsExternalImageProperties
+ {
+ Width = (int)desc.Width, Height = (int)desc.Height, Format = format
+ })
+ {
+
+ }
+
+ private AngleExternalMemoryD3D11ExportedTexture2D(EglContext context, ID3D11Texture2D texture2D,
+ IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
+ : base(context, texture2D, properties)
+ {
+ Handle = handle;
+ }
+
+ public IPlatformHandle Handle { get; }
+ public IPlatformHandle GetHandle() => Handle;
+
+}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs
new file mode 100644
index 0000000000..a7b52e953e
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls.Documents;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using Avalonia.Win32.DirectX;
+using MicroCom.Runtime;
+
+namespace Avalonia.Win32.OpenGl.Angle;
+
+internal class AngleExternalObjectsFeature : IGlContextExternalObjectsFeature, IDisposable
+{
+ private readonly EglContext _context;
+ private readonly ID3D11Device _device;
+
+ public AngleExternalObjectsFeature(EglContext context)
+ {
+ _context = context;
+ var angle = (AngleWin32EglDisplay)context.Display;
+ _device = MicroComRuntime.CreateProxyFor(angle.GetDirect3DDevice(), false).CloneReference();
+ using var dxgiDevice = _device.QueryInterface();
+ using var adapter = dxgiDevice.Adapter;
+ DeviceLuid = BitConverter.GetBytes(adapter.Desc.AdapterLuid);
+ }
+
+ public IReadOnlyList SupportedImportableExternalImageTypes { get; } = new[]
+ {
+ KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle
+ };
+
+ public IReadOnlyList SupportedExportableExternalImageTypes => SupportedImportableExternalImageTypes;
+ public IReadOnlyList SupportedImportableExternalSemaphoreTypes => Array.Empty();
+ public IReadOnlyList SupportedExportableExternalSemaphoreTypes => Array.Empty();
+
+ public IReadOnlyList GetSupportedFormatsForExternalMemoryType(string type) =>
+ new[]
+ {
+ PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm
+ };
+
+ public unsafe IGlExportableExternalImageTexture CreateImage(string type, PixelSize size,
+ PlatformGraphicsExternalImageFormat format)
+ {
+ if (format != PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm)
+ throw new NotSupportedException("Unsupported external memory format");
+ using (_context.EnsureCurrent())
+ {
+ var fmt = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ var desc = new D3D11_TEXTURE2D_DESC
+ {
+ Format = fmt,
+ Width = (uint)size.Width,
+ Height = (uint)size.Height,
+ ArraySize = 1,
+ MipLevels = 1,
+ SampleDesc = new DXGI_SAMPLE_DESC { Count = 1, Quality = 0 },
+ Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
+ CPUAccessFlags = 0,
+ MiscFlags = D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX,
+ BindFlags = D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET | D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE,
+ };
+ using var texture = _device.CreateTexture2D(&desc, IntPtr.Zero);
+ return new AngleExternalMemoryD3D11ExportedTexture2D(_context, texture, desc, format);
+ }
+ }
+
+ public IGlExportableExternalImageTexture CreateSemaphore(string type) => throw new NotSupportedException();
+
+ public unsafe IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
+ {
+ if (handle.HandleDescriptor != KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle)
+ throw new NotSupportedException("Unsupported external memory type");
+
+ using (_context.EnsureCurrent())
+ {
+ var guid = MicroComRuntime.GetGuidFor(typeof(ID3D11Texture2D));
+ using var opened = _device.OpenSharedResource(handle.Handle, &guid);
+ using var texture = opened.QueryInterface();
+ return new AngleExternalMemoryD3D11Texture2D(_context, texture, properties);
+ }
+ }
+
+ public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle) => throw new NotSupportedException();
+ public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+ {
+ if (imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle
+ || imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle)
+ return CompositionGpuImportedImageSynchronizationCapabilities.KeyedMutex;
+ return default;
+ }
+
+ public byte[] DeviceLuid { get; }
+ public byte[] DeviceUuid { get; }
+
+ public void Dispose()
+ {
+ _device.Dispose();
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
index b2d7b2014b..53bf2fb8b1 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
@@ -1,5 +1,8 @@
+#nullable enable annotations
using System;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
@@ -7,6 +10,7 @@ using Avalonia.OpenGL.Egl;
using Avalonia.Win32.DirectX;
using MicroCom.Runtime;
using static Avalonia.OpenGL.Egl.EglConsts;
+// ReSharper disable SimplifyLinqExpressionUseMinByAndMaxBy
namespace Avalonia.Win32.OpenGl.Angle
{
@@ -40,51 +44,90 @@ namespace Avalonia.Win32.OpenGl.Angle
}, AngleOptions.PlatformApi.DirectX11);
}
- public static AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl)
+ public static unsafe AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl,
+ bool preferDiscreteAdapter = false)
{
- unsafe
+ var featureLevels = new[]
{
- var featureLevels = new[]
- {
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1,
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0,
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_1,
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_0,
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3,
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2,
- D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1
- };
-
- DirectXUnmanagedMethods.D3D11CreateDevice(IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE,
- IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length,
- 7, out var pD3dDevice, out var featureLevel, null);
- if (pD3dDevice == IntPtr.Zero)
- throw new Win32Exception("Unable to create D3D11 Device");
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_0,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2,
+ D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1
+ };
- var d3dDevice = MicroComRuntime.CreateProxyFor(pD3dDevice, true);
- var angleDevice = IntPtr.Zero;
- var display = IntPtr.Zero;
+ var dxgiFactoryGuid = MicroComRuntime.GetGuidFor(typeof(IDXGIFactory1));
+ DirectXUnmanagedMethods.CreateDXGIFactory1(ref dxgiFactoryGuid, out var pDxgiFactory);
+ IDXGIAdapter1? chosenAdapter = null;
+ if (pDxgiFactory != null)
+ {
+ using var factory = MicroComRuntime.CreateProxyFor(pDxgiFactory, true);
- void Cleanup()
+ void* pAdapter = null;
+ if (preferDiscreteAdapter)
{
- if (angleDevice != IntPtr.Zero)
- egl.ReleaseDeviceANGLE(angleDevice);
- d3dDevice.Dispose();
+ ushort adapterIndex = 0;
+ var adapters = new List<(IDXGIAdapter1 adapter, string name)>();
+ while (factory.EnumAdapters1(adapterIndex, &pAdapter) == 0)
+ {
+ var adapter = MicroComRuntime.CreateProxyFor(pAdapter, true);
+ var desc = adapter.Desc1;
+ var name = Marshal.PtrToStringUni(new IntPtr(desc.Description))!.ToLowerInvariant();
+ adapters.Add((adapter, name));
+ adapterIndex++;
+ }
+
+ if (adapters.Count == 0)
+ throw new OpenGlException("No adapters found");
+ chosenAdapter = adapters
+ .OrderByDescending(x => x.name.Contains("nvidia") ? 2 : x.name.Contains("amd") ? 1 : 0)
+ .First().adapter.CloneReference();
+ foreach (var a in adapters)
+ a.adapter.Dispose();
}
-
- bool success = false;
- try
+ else
{
- angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null);
- if (angleDevice == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl);
+ if (factory.EnumAdapters1(0, &pAdapter) != 0)
+ throw new OpenGlException("No adapters found");
+ chosenAdapter = MicroComRuntime.CreateProxyFor(pAdapter, true);
+ }
+ }
+
+ IntPtr pD3dDevice;
+ using (chosenAdapter)
+ DirectXUnmanagedMethods.D3D11CreateDevice(chosenAdapter?.GetNativeIntPtr() ?? IntPtr.Zero,
+ D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_UNKNOWN,
+ IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length,
+ 7, out pD3dDevice, out var featureLevel, null);
+
- display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null);
- if (display == IntPtr.Zero)
- throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl);
+ if (pD3dDevice == IntPtr.Zero)
+ throw new Win32Exception("Unable to create D3D11 Device");
+ var d3dDevice = MicroComRuntime.CreateProxyFor(pD3dDevice, true);
+ var angleDevice = IntPtr.Zero;
+ var display = IntPtr.Zero;
- var rv = new AngleWin32EglDisplay(display, new EglDisplayOptions
+ void Cleanup()
+ {
+ if (angleDevice != IntPtr.Zero)
+ egl.ReleaseDeviceANGLE(angleDevice);
+ d3dDevice.Dispose();
+ }
+
+ bool success = false;
+ try
+ {
+ angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null);
+ if (angleDevice == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl);
+
+ display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null);
+ if (display == IntPtr.Zero)
+ throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl);
+
+
+ var rv = new AngleWin32EglDisplay(display,
+ new EglDisplayOptions
{
DisposeCallback = Cleanup,
Egl = egl,
@@ -92,17 +135,16 @@ namespace Avalonia.Win32.OpenGl.Angle
DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0,
GlVersions = AvaloniaLocator.Current.GetService()?.GlProfiles
}, AngleOptions.PlatformApi.DirectX11);
- success = true;
- return rv;
- }
- finally
+ success = true;
+ return rv;
+ }
+ finally
+ {
+ if (!success)
{
- if (!success)
- {
- if (display != IntPtr.Zero)
- egl.Terminate(display);
- Cleanup();
- }
+ if (display != IntPtr.Zero)
+ egl.Terminate(display);
+ Cleanup();
}
}
}
diff --git a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
index 9a829aff92..a4d1ea457a 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
@@ -9,7 +9,7 @@ using Avalonia.Platform;
namespace Avalonia.Win32.OpenGl.Angle;
-internal class AngleWin32PlatformGraphics : IPlatformGraphics
+internal class AngleWin32PlatformGraphics : IPlatformGraphics, IPlatformGraphicsOpenGlContextFactory
{
private readonly Win32AngleEglInterface _egl;
private AngleWin32EglDisplay _sharedDisplay;
@@ -29,9 +29,10 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
var rv = display.CreateContext(new EglContextOptions
{
DisposeCallback = display.Dispose,
- ExtraFeatures = new Dictionary
+ ExtraFeatures = new Dictionary>
{
- [typeof(IGlPlatformSurfaceRenderTargetFactory)] = new AngleD3DTextureFeature()
+ [typeof(IGlPlatformSurfaceRenderTargetFactory)] = _ => new AngleD3DTextureFeature(),
+ [typeof(IGlContextExternalObjectsFeature)] = context => new AngleExternalObjectsFeature(context)
}
});
success = true;
@@ -73,8 +74,6 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
public static AngleWin32PlatformGraphics TryCreate(AngleOptions options)
{
-
-
Win32AngleEglInterface egl;
try
{
@@ -128,4 +127,13 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
}
return null;
}
+
+ public IGlContext CreateContext(IEnumerable? versions)
+ {
+ if (UsesSharedContext)
+ throw new InvalidOperationException();
+ if (versions != null && versions.All(v => v.Type != GlProfileType.OpenGLES || v.Major != 3))
+ throw new OpenGlException("Unable to create context with requested version");
+ return (IGlContext)CreateContext();
+ }
}
diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs
index 25ea060576..ab6de1a027 100644
--- a/src/Windows/Avalonia.Win32/Win32GlManager.cs
+++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs
@@ -37,6 +37,9 @@ namespace Avalonia.Win32
if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11)
{
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(egl);
+
if (opts.UseWindowsUIComposition)
{
WinUiCompositorConnection.TryCreateAndRegister();
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 93d97e6cfb..ee501a86c1 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -16,6 +16,8 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public bool IsLost => false;
+
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 7a17efa59b..9b148c798b 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -47,6 +47,8 @@ namespace Avalonia.Benchmarks
throw new NotImplementedException();
}
+ public bool IsLost => false;
+
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 2a00ef8e40..30f949ccb8 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -58,6 +58,8 @@ namespace Avalonia.UnitTests
return new MockRenderTarget();
}
+ public bool IsLost => false;
+
public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)