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 9ec1c3c2d3..433ce8e950 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..30a4c19d35
--- /dev/null
+++ b/samples/GpuInterop/D3DDemo/D3D11Swapchain.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+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;
+
+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..c201d9bf85
--- /dev/null
+++ b/samples/GpuInterop/GpuInterop.csproj
@@ -0,0 +1,50 @@
+
+
+
+ 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..86fd239b4c
--- /dev/null
+++ b/samples/GpuInterop/Program.cs
@@ -0,0 +1,15 @@
+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 1828f24aff..89bf047401 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -210,5 +210,10 @@ namespace Avalonia.Platform
///
/// An .
IRenderTarget CreateRenderTarget(IEnumerable