Sfoglia il codice sorgente

Implemented interop with externally managed GPU memory

Nikita Tsukanov 2 anni fa
parent
commit
d714f37fce
92 ha cambiato i file con 5541 aggiunte e 579 eliminazioni
  1. 1 0
      Avalonia.Desktop.slnf
  2. 7 0
      Avalonia.sln
  3. 1 0
      build/Base.props
  4. 10 5
      build/SharpDX.props
  5. 12 9
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  6. 8 0
      samples/GpuInterop/App.axaml
  7. 22 0
      samples/GpuInterop/App.axaml.cs
  8. 147 0
      samples/GpuInterop/D3DDemo/D3D11DemoControl.cs
  9. 119 0
      samples/GpuInterop/D3DDemo/D3D11Swapchain.cs
  10. 110 0
      samples/GpuInterop/D3DDemo/D3DContent.cs
  11. 47 0
      samples/GpuInterop/D3DDemo/MiniCube.fx
  12. 141 0
      samples/GpuInterop/DrawingSurfaceDemoBase.cs
  13. 32 0
      samples/GpuInterop/GpuDemo.axaml
  14. 116 0
      samples/GpuInterop/GpuDemo.axaml.cs
  15. 50 0
      samples/GpuInterop/GpuInterop.csproj
  16. 13 0
      samples/GpuInterop/MainWindow.axaml
  17. 21 0
      samples/GpuInterop/MainWindow.axaml.cs
  18. 15 0
      samples/GpuInterop/Program.cs
  19. 12 0
      samples/GpuInterop/VulkanDemo/Assets/Shaders/Makefile
  20. 42 0
      samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.glsl
  21. BIN
      samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv
  22. 36 0
      samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.glsl
  23. BIN
      samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv
  24. 47 0
      samples/GpuInterop/VulkanDemo/ByteString.cs
  25. 54 0
      samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
  26. 80 0
      samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs
  27. 224 0
      samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
  28. 829 0
      samples/GpuInterop/VulkanDemo/VulkanContent.cs
  29. 335 0
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  30. 101 0
      samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs
  31. 12 0
      samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
  32. 276 0
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  33. 59 0
      samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs
  34. 58 0
      samples/GpuInterop/VulkanDemo/VulkanSemaphorePair.cs
  35. 154 0
      samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
  36. 53 0
      src/Avalonia.Base/Platform/IExternalObjectsRenderInterfaceContextFeature.cs
  37. 10 1
      src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
  38. 5 0
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  39. 64 0
      src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs
  40. 3 3
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  41. 60 0
      src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs
  42. 142 0
      src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
  43. 150 0
      src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
  44. 10 0
      src/Avalonia.Base/Rendering/Composition/CompositionSurface.cs
  45. 5 1
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
  46. 71 17
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  47. 74 0
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
  48. 8 1
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs
  49. 48 0
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs
  50. 12 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
  51. 2 0
      src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs
  52. 89 0
      src/Avalonia.Base/Rendering/SwapchainBase.cs
  53. 6 1
      src/Avalonia.Base/composition-schema.xml
  54. 1 0
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  55. 2 2
      src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
  56. 163 0
      src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs
  57. 131 171
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  58. 170 0
      src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs
  59. 7 5
      src/Avalonia.OpenGL/Egl/EglConsts.cs
  60. 14 2
      src/Avalonia.OpenGL/Egl/EglContext.cs
  61. 1 1
      src/Avalonia.OpenGL/Egl/EglDisplay.cs
  62. 1 1
      src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs
  63. 3 0
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  64. 282 0
      src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs
  65. 7 7
      src/Avalonia.OpenGL/GlConsts.cs
  66. 50 0
      src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs
  67. 10 3
      src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs
  68. 8 0
      src/Avalonia.OpenGL/IPlatformGraphicsOpenGlContextFactory.cs
  69. 0 19
      src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs
  70. 0 40
      src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs
  71. 3 0
      src/Avalonia.OpenGL/OpenGlException.cs
  72. 12 1
      src/Avalonia.X11/Glx/GlxContext.cs
  73. 0 7
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  74. 191 0
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs
  75. 38 4
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs
  76. 44 0
      src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaSharedTextureForComposition.cs
  77. 0 210
      src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
  78. 7 0
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  79. 0 5
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  80. 3 1
      src/Skia/Avalonia.Skia/SkiaBackendContext.cs
  81. 1 0
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  82. 38 0
      src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs
  83. 2 2
      src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs
  84. 30 3
      src/Windows/Avalonia.Win32/DirectX/directx.idl
  85. 107 0
      src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs
  86. 103 0
      src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs
  87. 87 45
      src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
  88. 13 5
      src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs
  89. 3 0
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  90. 2 0
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  91. 2 0
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  92. 2 0
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

+ 1 - 0
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",

+ 7 - 0
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}

+ 1 - 0
build/Base.props

@@ -1,6 +1,7 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Condition="'$(TargetFramework)' != 'net6'">
     <PackageReference Include="System.ValueTuple" Version="4.5.0" />
+    <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
   </ItemGroup>
 </Project>

+ 10 - 5
build/SharpDX.props

@@ -1,9 +1,14 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <SharpDXPackageVersion>4.0.1</SharpDXPackageVersion>
+  </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="SharpDX" Version="4.0.1" />
-    <PackageReference Include="SharpDX.Direct2D1" Version="4.0.1" />
-    <PackageReference Include="SharpDX.Direct3D11" Version="4.0.1" />
-    <PackageReference Include="SharpDX.DXGI" Version="4.0.1" />
-    <PackageReference Include="SharpDX.Direct3D9" Version="4.0.1" Condition="'$(UseDirect3D9)' == 'true'" />
+    <PackageReference Include="SharpDX" Version="$(SharpDXPackageVersion)" />
+    <PackageReference Include="SharpDX.Direct2D1" Version="$(SharpDXPackageVersion)" />
+    <PackageReference Include="SharpDX.Direct3D11" Version="$(SharpDXPackageVersion)" />
+    <PackageReference Include="SharpDX.DXGI" Version="$(SharpDXPackageVersion)" />
+    <PackageReference Include="SharpDX.Direct3D9" Version="$(SharpDXPackageVersion)" Condition="'$(UseDirect3D9)' == 'true'" />
+    <PackageReference Include="SharpDX.D3DCompiler" Version="$(SharpDXPackageVersion)" Condition="'$(UseD3DCompiler)' == 'true'" />
+    <PackageReference Include="SharpDX.Mathematics" Version="$(SharpDXPackageVersion)" Condition="'$(UseSharpDXMathematics)' == 'true'" />
   </ItemGroup>
 </Project>

+ 12 - 9
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<OpenGlPageControl>(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);
         }
     }
 }

+ 8 - 0
samples/GpuInterop/App.axaml

@@ -0,0 +1,8 @@
+<Application
+    xmlns="https://github.com/avaloniaui" 
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    x:Class="GpuInterop.App">
+    <Application.Styles>
+        <FluentTheme />
+    </Application.Styles>
+</Application>

+ 22 - 0
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();
+            }
+        }
+    }
+}

+ 147 - 0
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);
+    }
+}

+ 119 - 0
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<D3D11SwapchainImage>
+{
+    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<KeyedMutex>();
+        using (var res = _texture.QueryInterface<DxgiResource>())
+            _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();
+    }
+}

+ 110 - 0
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<Matrix>(), 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<Vector4>() * 2, 0));
+        context.VertexShader.SetConstantBuffer(0, constantBuffer);
+        context.VertexShader.Set(vertexShader);
+        context.PixelShader.Set(pixelShader);
+        return constantBuffer;
+    }
+}

+ 47 - 0
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;
+}

+ 141 - 0
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; }
+}

+ 32 - 0
samples/GpuInterop/GpuDemo.axaml

@@ -0,0 +1,32 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="GpuInterop.GpuDemo"
+             xmlns:local="clr-namespace:GpuInterop;assembly=GpuInterop">
+  <Grid>
+    <ContentControl Content="{Binding $parent[local:GpuDemo].Demo}" />
+
+    <StackPanel>
+      <TextBlock Margin="0 40 0 0" Text="{Binding $parent[local:GpuDemo].Info}"/>
+    </StackPanel>
+    <Grid ColumnDefinitions="*,Auto" Margin="20">
+      <StackPanel Grid.Column="1" MinWidth="300">
+        <TextBlock>Yaw</TextBlock>
+        <Slider Value="{Binding $parent[local:GpuDemo].Yaw, Mode=TwoWay}" Maximum="10"/>
+        <TextBlock>Pitch</TextBlock>
+        <Slider Value="{Binding $parent[local:GpuDemo].Pitch, Mode=TwoWay}" Maximum="10"/>
+        <TextBlock>Roll</TextBlock>
+        <Slider Value="{Binding $parent[local:GpuDemo].Roll, Mode=TwoWay}" Maximum="10"/>
+        <StackPanel IsVisible="{Binding $parent[local:GpuDemo].DiscoVisible}">
+          <StackPanel Orientation="Horizontal">
+            <TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock>
+            <TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock>
+            <TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock>
+            <TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock>
+            <TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock>
+          </StackPanel>
+          <Slider Value="{Binding $parent[local:GpuDemo].Disco, Mode=TwoWay}" Maximum="1"/>
+        </StackPanel>
+      </StackPanel>
+    </Grid>
+  </Grid>
+</UserControl>

+ 116 - 0
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<GpuDemo, float> YawProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, float>("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<GpuDemo, float> PitchProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, float>("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<GpuDemo, float> RollProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, float>("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<GpuDemo, float> DiscoProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, float>("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<GpuDemo, string> InfoProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, string>("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<GpuDemo, bool> DiscoVisibleProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, bool>("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<GpuDemo, IGpuDemo> DemoProperty =
+        AvaloniaProperty.RegisterDirect<GpuDemo, IGpuDemo>("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);
+}

+ 50 - 0
samples/GpuInterop/GpuInterop.csproj

@@ -0,0 +1,50 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
+    <Nullable>enable</Nullable>
+    <GenerateDocumentationFile>false</GenerateDocumentationFile>
+    <UseD3DCompiler>true</UseD3DCompiler>
+    <UseSharpDXMathematics>true</UseSharpDXMathematics>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="D3DDemo\MiniCube.fx">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
+    <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
+    <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="VulkanDemo\Assets\Shaders\Assets" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Include="..\..\src\Avalonia.Base\Rendering\SwapchainBase.cs" />
+    <None Remove="VulkanDemo\Assets\Shaders\frag.spirv" />
+    <EmbeddedResource Include="VulkanDemo\Assets\Shaders\frag.spirv" />
+    <None Remove="VulkanDemo\Assets\Shaders\vert.spirv" />
+    <EmbeddedResource Include="VulkanDemo\Assets\Shaders\vert.spirv" />
+    <EmbeddedResource Include="../ControlCatalog/Pages/teapot.bin" />
+  </ItemGroup>
+  
+  <Import Project="..\..\build\SampleApp.props" />
+  <Import Project="..\..\build\ReferenceCoreLibraries.props" />
+  <Import Project="..\..\build\BuildTargets.targets" />
+  <Import Project="..\..\build\SharpDX.props" />
+</Project>

+ 13 - 0
samples/GpuInterop/MainWindow.axaml

@@ -0,0 +1,13 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:gpuInterop="clr-namespace:GpuInterop"
+        xmlns:d3DDemo="clr-namespace:GpuInterop.D3DDemo"
+        xmlns:vulkanDemo="clr-namespace:GpuInterop.VulkanDemo"
+        x:Class="GpuInterop.MainWindow">
+  <gpuInterop:GpuDemo>
+    <gpuInterop:GpuDemo.Demo>
+      <!--<d3DDemo:D3D11DemoControl/>-->
+      <vulkanDemo:VulkanDemoControl/>
+    </gpuInterop:GpuDemo.Demo>
+  </gpuInterop:GpuDemo>
+</Window>

+ 21 - 0
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);
+        }
+    }
+}

+ 15 - 0
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<App>()
+                .UsePlatformDetect()
+                .LogToTrace();
+    }
+}

+ 12 - 0
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
+
+

+ 42 - 0
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);
+
+	}

BIN
samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv


+ 36 - 0
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)));
+	}

BIN
samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv


+ 47 - 0
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<ByteString> _inner;
+    private byte** _ptr;
+
+    public ByteStringList(IEnumerable<string> 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;
+}

+ 54 - 0
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<byte> luid)
+    {
+        var factory = new DxgiFactory1();
+        var longLuid = MemoryMarshal.Cast<byte, long>(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
+            });
+    }
+}

+ 80 - 0
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<T>(VulkanContext vk,
+        BufferUsageFlags bufferUsageFlags,
+        out Buffer buffer, out DeviceMemory memory,
+        Span<T> initialData) where T:unmanaged
+    {
+        var api = vk.Api;
+        var device = vk.Device;
+
+        var size = Unsafe.SizeOf<T>() * 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<T>(VulkanContext vk, DeviceMemory memory,
+        Span<T> data) where T : unmanaged
+    {
+        var api = vk.Api;
+        var device = vk.Device;
+
+        var size = data.Length * Unsafe.SizeOf<T>();
+        void* pointer = null;
+        api.MapMemory(device, memory, 0, (ulong)size, 0, ref pointer);
+
+        data.CopyTo(new Span<T>(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;
+    }
+}

+ 224 - 0
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<VulkanCommandBuffer> _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<Semaphore> waitSemaphores,
+                ReadOnlySpan<PipelineStageFlags> waitDstStageMask = default,
+                ReadOnlySpan<Semaphore> 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);
+            }
+        }
+    }
+}

+ 829 - 0
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<VertextPushConstant>(), &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<VertextPushConstant>(),
+                    StageFlags = ShaderStageFlags.ShaderStageVertexBit
+                };
+
+                var fragPushConstantRange = new PushConstantRange()
+                {
+                    //Offset = vertexPushConstantRange.Size,
+                    Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
+                    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<UniformBuffer>(_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<UniformBuffer>(),
+                };
+                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<Vertex>(_context, BufferUsageFlags.VertexBufferBit, out _vertexBuffer,
+            out _vertexBufferMemory, _points);
+        VulkanBufferHelper.AllocateBuffer<ushort>(_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<Vertex>(),
+                    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<Vertex>("Position")
+                    },
+                    new VertexInputAttributeDescription
+                    {
+                        Binding = 0,
+                        Location = 1,
+                        Format = Format.R32G32B32Sfloat,
+                        Offset = (uint)Marshal.OffsetOf<Vertex>("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;
+    }
+}

+ 335 - 0
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<string>()
+        {
+            "VK_KHR_get_physical_device_properties2",
+            "VK_KHR_external_memory_capabilities",
+            "VK_KHR_external_semaphore_capabilities"
+        };
+
+        var enabledLayers = new List<string>();
+        
+        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<string>
+            {
+                "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<byte>(physicalDeviceIDProperties.DeviceLuid, 8)
+                            .SequenceEqual(gpuInterop.DeviceLuid))
+                        continue;
+                }
+                else if (gpuInterop.DeviceUuid != null)
+                {
+                    if (!new Span<byte>(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<byte>(physicalDeviceIDProperties.DeviceLuid, 8));
+                    
+                    var dxgiDevice = d3dDevice?.QueryInterface<DxgiDevice>();
+                    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);
+    }
+}

+ 101 - 0
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);
+        }
+    }
+}

+ 12 - 0
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}\".");
+    }
+}

+ 276 - 0
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<SharpDX.DXGI.Resource>();
+                 _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<KhrExternalMemoryFd>(_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);
+            }
+        }
+    }

+ 59 - 0
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);
+    }
+}

+ 58 - 0
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<KhrExternalSemaphoreFd>(_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);
+    }
+}

+ 154 - 0
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<VulkanSwapchainImage>
+{
+    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);
+    }
+}

+ 53 - 0
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
+{
+    /// <summary>
+    /// Returns the list of image handle types supported by the current GPU backend, see <see cref="KnownPlatformGraphicsExternalImageHandleTypes"/>
+    /// </summary>
+    IReadOnlyList<string> SupportedImageHandleTypes { get; }
+    
+    /// <summary>
+    /// Returns the list of semaphore types supported by the current GPU backend, see <see cref="KnownPlatformGraphicsExternalSemaphoreHandleTypes"/>
+    /// </summary>
+    IReadOnlyList<string> 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
+{
+    
+}

+ 10 - 1
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<T>(this IOptionalFeatureProvider provider) where T : class =>
         (T?)provider.TryGetFeature(typeof(T));
-}
+
+    public static bool TryGetFeature<T>(this IOptionalFeatureProvider provider, [MaybeNullWhen(false)] out T rv)
+        where T : class
+    {
+        rv = provider.TryGetFeature<T>();
+        return rv != null;
+    }
+        
+}

+ 5 - 0
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@@ -210,5 +210,10 @@ namespace Avalonia.Platform
         /// </param>
         /// <returns>An <see cref="IRenderTarget"/>.</returns>
         IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces);
+        
+        /// <summary>
+        /// Indicates that the context is no longer usable. This method should be thread-safe
+        /// </summary>
+        bool IsLost { get; }
     }
 }

+ 64 - 0
src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Avalonia.Platform;
+
+public struct PlatformGraphicsExternalImageProperties
+{
+    public int Width { get; set; }
+    public int Height { get; set; }
+    public PlatformGraphicsExternalImageFormat Format { get; set; }
+    public ulong MemorySize { get; set; }
+    public ulong MemoryOffset { get; set; }
+    public bool TopLeftOrigin { get; set; }
+}
+
+public enum PlatformGraphicsExternalImageFormat
+{
+    R8G8B8A8UNorm,
+    B8G8R8A8UNorm
+}
+
+/// <summary>
+/// Describes various GPU memory handle types that are currently supported by Avalonia graphics backends
+/// </summary>
+public static class KnownPlatformGraphicsExternalImageHandleTypes
+{
+    /// <summary>
+    /// An DXGI global shared handle returned by IDXGIResource::GetSharedHandle D3D11_RESOURCE_MISC_SHARED or D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX flag.
+    /// The handle does not own the reference to the underlying video memory, so the provider should make sure that the resource is valid until
+    /// the handle has been successfully imported
+    /// </summary>
+    public const string D3D11TextureGlobalSharedHandle = nameof(D3D11TextureGlobalSharedHandle);
+    /// <summary>
+    /// A DXGI NT handle returned by IDXGIResource1::CreateSharedHandle for a texture created with D3D11_RESOURCE_MISC_SHARED_NTHANDLE or flag
+    /// </summary>
+    public const string D3D11TextureNtHandle = nameof(D3D11TextureNtHandle);
+    /// <summary>
+    /// A POSIX file descriptor that's exported by Vulkan using VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT or in a compatible way
+    /// </summary>
+    public const string VulkanOpaquePosixFileDescriptor = nameof(VulkanOpaquePosixFileDescriptor);
+}
+
+/// <summary>
+/// Describes various GPU semaphore handle types that are currently supported by Avalonia graphics backends
+/// </summary>
+public static class KnownPlatformGraphicsExternalSemaphoreHandleTypes
+{
+    /// <summary>
+    /// A POSIX file descriptor that's been exported by Vulkan using VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT or in a compatible way
+    /// </summary>
+    public const string VulkanOpaquePosixFileDescriptor = nameof(VulkanOpaquePosixFileDescriptor);
+    
+    /// <summary>
+    /// A NT handle that's been exported by Vulkan using VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT or in a compatible way
+    /// </summary>
+    public const string VulkanOpaqueNtHandle = nameof(VulkanOpaqueNtHandle);
+    
+    // A global shared handle that's been exported by Vulkan using VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT or in a compatible way
+    public const string VulkanOpaqueKmtHandle = nameof(VulkanOpaqueKmtHandle);
+    
+    /// A DXGI NT handle returned by ID3D12Device::CreateSharedHandle or ID3D11Fence::CreateSharedHandle
+    public const string Direct3D12FenceNtHandle = nameof(Direct3D12FenceNtHandle);
+}

+ 3 - 3
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@@ -28,7 +28,7 @@ public class CompositingRenderer : IRendererWithCompositor
     private HashSet<Visual> _dirty = new();
     private HashSet<Visual> _recalculateChildren = new();
     private bool _queuedUpdate;
-    private Action<Task> _update;
+    private Action _update;
     private bool _updating;
 
     internal CompositionTarget CompositionTarget;
@@ -70,7 +70,7 @@ public class CompositingRenderer : IRendererWithCompositor
         if(_queuedUpdate)
             return;
         _queuedUpdate = true;
-        _compositor.InvokeBeforeNextCommit(_update);
+        _compositor.RequestCompositionUpdate(_update);
     }
     
     /// <inheritdoc/>
@@ -265,7 +265,7 @@ public class CompositingRenderer : IRendererWithCompositor
         SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
     }
     
-    private void Update(Task batchCompletion)
+    private void Update()
     {
         if(_updating)
             return;

+ 60 - 0
src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Rendering.Composition.Server;
+
+namespace Avalonia.Rendering.Composition;
+
+public class CompositionDrawingSurface : CompositionSurface
+{
+    internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server;
+    internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server))
+    {
+    }
+
+    /// <summary>
+    /// Updates the surface contents using an imported memory image using a keyed mutex as the means of synchronization
+    /// </summary>
+    /// <param name="image">GPU image with new surface contents</param>
+    /// <param name="acquireIndex">The mutex key to wait for before accessing the image</param>
+    /// <param name="releaseIndex">The mutex key to release for after accessing the image </param>
+    /// <returns>A task that completes when update operation is completed and user code is free to destroy or dispose the image</returns>
+    public Task UpdateWithKeyedMutexAsync(ICompositionImportedGpuImage image, uint acquireIndex, uint releaseIndex)
+    {
+        var img = (CompositionImportedGpuImage)image;
+        return Compositor.InvokeServerJobAsync(() => Server.UpdateWithKeyedMutex(img, acquireIndex, releaseIndex));
+    }
+
+    /// <summary>
+    /// Updates the surface contents using an imported memory image using a semaphore pair as the means of synchronization
+    /// </summary>
+    /// <param name="image">GPU image with new surface contents</param>
+    /// <param name="waitForSemaphore">The semaphore to wait for before accessing the image</param>
+    /// <param name="signalSemaphore">The semaphore to signal after accessing the image</param>
+    /// <returns>A task that completes when update operation is completed and user code is free to destroy or dispose the image</returns>
+    public Task UpdateWithSemaphoresAsync(ICompositionImportedGpuImage image,
+        ICompositionImportedGpuSemaphore waitForSemaphore,
+        ICompositionImportedGpuSemaphore signalSemaphore)
+    {
+        var img = (CompositionImportedGpuImage)image;
+        var wait = (CompositionImportedGpuSemaphore)waitForSemaphore;
+        var signal = (CompositionImportedGpuSemaphore)signalSemaphore;
+        return Compositor.InvokeServerJobAsync(() => Server.UpdateWithSemaphores(img, wait, signal));
+    }
+
+    /// <summary>
+    /// Updates the surface contents using an unspecified automatic means of synchronization
+    /// provided by the underlying platform
+    /// </summary>
+    /// <param name="image">GPU image with new surface contents</param>
+    /// <returns>A task that completes when update operation is completed and user code is free to destroy or dispose the image</returns>
+    public Task UpdateAsync(ICompositionImportedGpuImage image)
+    {
+        var img = (CompositionImportedGpuImage)image;
+        return Compositor.InvokeServerJobAsync(() => Server.UpdateWithAutomaticSync(img));
+    }
+
+    ~CompositionDrawingSurface()
+    {
+        Dispose();
+    }
+}

+ 142 - 0
src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition;
+public interface ICompositionGpuInterop
+{
+    /// <summary>
+    /// Returns the list of image handle types supported by the current GPU backend, see <see cref="KnownPlatformGraphicsExternalImageHandleTypes"/>
+    /// </summary>
+    IReadOnlyList<string> SupportedImageHandleTypes { get; }
+    
+    /// <summary>
+    /// Returns the list of semaphore types supported by the current GPU backend, see <see cref="KnownPlatformGraphicsExternalSemaphoreHandleTypes"/>
+    /// </summary>
+    IReadOnlyList<string> SupportedSemaphoreTypes { get; }
+
+    /// <summary>
+    /// Returns the supported ways to synchronize access to the imported GPU image
+    /// </summary>
+    /// <returns></returns>
+    CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType);
+    
+    /// <summary>
+    /// Asynchronously imports a texture. The returned object is immediately usable.
+    /// </summary>
+    ICompositionImportedGpuImage ImportImage(IPlatformHandle handle,
+        PlatformGraphicsExternalImageProperties properties);
+
+    /// <summary>
+    /// Asynchronously imports a texture. The returned object is immediately usable.
+    /// If import operation fails, the caller is responsible for destroying the handle
+    /// </summary>
+    /// <param name="image">An image that belongs to the same GPU context or the same GPU context sharing group as one used by compositor</param>
+    ICompositionImportedGpuImage ImportImage(ICompositionImportableSharedGpuContextImage image);
+
+    /// <summary>
+    /// Asynchronously imports a semaphore object. The returned object is immediately usable.
+    /// If import operation fails, the caller is responsible for destroying the handle
+    /// </summary>
+    ICompositionImportedGpuSemaphore ImportSemaphore(IPlatformHandle handle);
+    
+    /// <summary>
+    /// Asynchronously imports a semaphore object. The returned object is immediately usable.
+    /// </summary>
+    /// <param name="image">A semaphore that belongs to the same GPU context or the same GPU context sharing group as one used by compositor</param>
+    ICompositionImportedGpuImage ImportSemaphore(ICompositionImportableSharedGpuContextSemaphore image);
+    
+    /// <summary>
+    /// Indicates if the device context this instance is associated with is no longer available
+    /// </summary>
+    public bool IsLost { get; }
+    
+    /// <summary>
+    /// The LUID of the graphics adapter used by the compositor
+    /// </summary>
+    public byte[]? DeviceLuid { get; set; }
+    
+    /// <summary>
+    /// The UUID of the graphics adapter used by the compositor
+    /// </summary>
+    public byte[]? DeviceUuid { get; set; }
+}
+
+[Flags]
+public enum CompositionGpuImportedImageSynchronizationCapabilities
+{
+    /// <summary>
+    /// Pre-render and after-render semaphores must be provided alongside with the image
+    /// </summary>
+    Semaphores = 1,
+    /// <summary>
+    /// Image must be created with D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX or in other compatible way
+    /// </summary>
+    KeyedMutex = 2,
+    /// <summary>
+    /// Synchronization and ordering is somehow handled by the underlying platform
+    /// </summary>
+    Automatic = 4
+}
+
+/// <summary>
+/// An imported GPU object that's usable by composition APIs 
+/// </summary>
+public interface ICompositionGpuImportedObject : IAsyncDisposable
+{
+    /// <summary>
+    /// Tracks the import status of the object. Once the task is completed,
+    /// the user code is allowed to free the resource owner in case when a non-owning
+    /// sharing handle was used
+    /// </summary>
+    Task ImportCompeted { get; }
+    /// <summary>
+    /// Indicates if the device context this instance is associated with is no longer available
+    /// </summary>
+    bool IsLost { get; }
+}
+
+/// <summary>
+/// An imported GPU image object that's usable by composition APIs 
+/// </summary>
+[NotClientImplementable]
+public interface ICompositionImportedGpuImage : ICompositionGpuImportedObject
+{
+
+}
+
+/// <summary>
+/// An imported GPU semaphore object that's usable by composition APIs 
+/// </summary>
+[NotClientImplementable]
+public interface ICompositionImportedGpuSemaphore : ICompositionGpuImportedObject
+{
+
+}
+
+/// <summary>
+/// An GPU object descriptor obtained from a context from the same share group as one used by the compositor
+/// </summary>
+[NotClientImplementable]
+public interface ICompositionImportableSharedGpuContextObject : IDisposable
+{
+}
+
+/// <summary>
+/// An GPU image descriptor obtained from a context from the same share group as one used by the compositor
+/// </summary>
+[NotClientImplementable]
+public interface ICompositionImportableSharedGpuContextImage : IDisposable
+{
+}
+
+/// <summary>
+/// An GPU semaphore descriptor obtained from a context from the same share group as one used by the compositor
+/// </summary>
+[NotClientImplementable]
+public interface ICompositionImportableSharedGpuContextSemaphore : IDisposable
+{
+}
+

+ 150 - 0
src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs

@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.Composition;
+
+internal class CompositionInterop : ICompositionGpuInterop
+{
+    private readonly Compositor _compositor;
+    private readonly IPlatformRenderInterfaceContext _context;
+    private readonly IExternalObjectsRenderInterfaceContextFeature _externalObjects;
+    
+
+    public CompositionInterop(
+        Compositor compositor,
+        IExternalObjectsRenderInterfaceContextFeature externalObjects)
+    {
+        _compositor = compositor;
+        _context = compositor.Server.RenderInterface.Value;
+        DeviceLuid = externalObjects.DeviceLuid;
+        DeviceUuid = externalObjects.DeviceUuid;
+        _externalObjects = externalObjects;
+    }
+
+    public IReadOnlyList<string> SupportedImageHandleTypes => _externalObjects.SupportedImageHandleTypes;
+    public IReadOnlyList<string> SupportedSemaphoreTypes => _externalObjects.SupportedSemaphoreTypes;
+
+    public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+        => _externalObjects.GetSynchronizationCapabilities(imageHandleType);
+
+    public ICompositionImportedGpuImage ImportImage(IPlatformHandle handle,
+        PlatformGraphicsExternalImageProperties properties)
+        => new CompositionImportedGpuImage(_compositor, _context, _externalObjects, 
+            () => _externalObjects.ImportImage(handle, properties));
+
+    public ICompositionImportedGpuImage ImportImage(ICompositionImportableSharedGpuContextImage image)
+    {
+        return new CompositionImportedGpuImage(_compositor, _context, _externalObjects,
+            () => _externalObjects.ImportImage(image));
+    }
+
+    public ICompositionImportedGpuSemaphore ImportSemaphore(IPlatformHandle handle)
+        => new CompositionImportedGpuSemaphore(handle, _compositor, _context, _externalObjects);
+
+    public ICompositionImportedGpuImage ImportSemaphore(ICompositionImportableSharedGpuContextSemaphore image)
+    {
+        throw new System.NotSupportedException();
+    }
+
+    public bool IsLost { get; }
+    public byte[]? DeviceLuid { get; set; }
+    public byte[]? DeviceUuid { get; set; }
+}
+
+abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject
+{
+    protected Compositor Compositor { get; }
+    public IPlatformRenderInterfaceContext Context { get; }
+    public IExternalObjectsRenderInterfaceContextFeature Feature { get; }
+
+    public CompositionGpuImportedObjectBase(Compositor compositor,
+        IPlatformRenderInterfaceContext context,
+        IExternalObjectsRenderInterfaceContextFeature feature)
+    {
+        Compositor = compositor;
+        Context = context;
+        Feature = feature;
+        
+        ImportCompeted = Compositor.InvokeServerJobAsync(Import);
+    }
+    
+    protected abstract void Import();
+    public abstract void Dispose();
+
+    public Task ImportCompeted { get; }
+    public bool IsLost => Context.IsLost;
+
+    public ValueTask DisposeAsync() => new(Compositor.InvokeServerJobAsync(() =>
+    {
+        if (ImportCompeted.Status == TaskStatus.RanToCompletion)
+            Dispose();
+    }));
+}
+
+class CompositionImportedGpuImage : CompositionGpuImportedObjectBase, ICompositionImportedGpuImage
+{
+    private readonly Func<IPlatformRenderInterfaceImportedImage> _importer;
+    private IPlatformRenderInterfaceImportedImage? _image;
+
+    public CompositionImportedGpuImage(Compositor compositor,
+        IPlatformRenderInterfaceContext context,
+        IExternalObjectsRenderInterfaceContextFeature feature,
+        Func<IPlatformRenderInterfaceImportedImage> importer): base(compositor, context, feature)
+    {
+        _importer = importer;
+    }
+
+    protected override void Import()
+    {
+        using (Compositor.Server.RenderInterface.EnsureCurrent())
+        {
+            // The original context was lost and the new one might have different capabilities
+            if (Context != Compositor.Server.RenderInterface.Value)
+                throw new PlatformGraphicsContextLostException();
+            _image = _importer();
+        }
+    }
+
+    public IPlatformRenderInterfaceImportedImage Image =>
+        _image ?? throw new ObjectDisposedException(nameof(CompositionImportedGpuImage));
+
+    public bool IsUsable => _image != null && Compositor.Server.RenderInterface.Value == Context;
+
+    public override void Dispose()
+    {
+        _image?.Dispose();
+        _image = null!;
+    }
+}
+
+class CompositionImportedGpuSemaphore : CompositionGpuImportedObjectBase, ICompositionImportedGpuSemaphore
+{
+    private readonly IPlatformHandle _handle;
+    private IPlatformRenderInterfaceImportedSemaphore? _semaphore;
+
+    public CompositionImportedGpuSemaphore(IPlatformHandle handle,
+        Compositor compositor, IPlatformRenderInterfaceContext context,
+        IExternalObjectsRenderInterfaceContextFeature feature) : base(compositor, context, feature)
+    {
+        _handle = handle;
+    }
+
+    public IPlatformRenderInterfaceImportedSemaphore Semaphore =>
+        _semaphore ?? throw new ObjectDisposedException(nameof(CompositionImportedGpuSemaphore));
+
+
+    public bool IsUsable => _semaphore != null && Compositor.Server.RenderInterface.Value == Context;
+
+    protected override void Import()
+    {
+        _semaphore = Feature.ImportSemaphore(_handle);
+    }
+
+    public override void Dispose()
+    {
+        _semaphore?.Dispose();
+        _semaphore = null;
+    }
+}

+ 10 - 0
src/Avalonia.Base/Rendering/Composition/CompositionSurface.cs

@@ -0,0 +1,10 @@
+using Avalonia.Rendering.Composition.Server;
+
+namespace Avalonia.Rendering.Composition;
+
+public class CompositionSurface : CompositionObject
+{
+    internal CompositionSurface(Compositor compositor, ServerObject server) : base(compositor, server)
+    {
+    }
+}

+ 5 - 1
src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs

@@ -35,4 +35,8 @@ public partial class Compositor
         new(this, new ServerCompositionSolidColorVisual(Server));
 
     public CompositionCustomVisual CreateCustomVisual(CompositionCustomVisualHandler handler) => new(this, handler);
-}
+
+    public CompositionSurfaceVisual CreateSurfaceVisual() => new(this, new ServerCompositionSurfaceVisual(_server));
+
+    public CompositionDrawingSurface CreateDrawingSurface() => new(this);
+}

+ 71 - 17
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@@ -30,7 +30,7 @@ namespace Avalonia.Rendering.Composition
         private BatchStreamObjectPool<object?> _batchObjectPool = new();
         private BatchStreamMemoryPool _batchMemoryPool = new();
         private List<CompositionObject> _objectsForSerialization = new();
-        private Queue<Action<Task>> _invokeBeforeCommit = new();
+        private Queue<Action> _invokeBeforeCommitWrite = new(), _invokeBeforeCommitRead = new();
         internal ServerCompositor Server => _server;
         private Task? _pendingBatch;
         private readonly object _pendingBatchLock = new();
@@ -77,16 +77,30 @@ namespace Avalonia.Rendering.Composition
 
             return _nextCommit.Task;
         }
-        
+
         internal Task Commit()
+        {
+            try
+            {
+                return CommitCore();
+            }
+            finally
+            {
+                if (_invokeBeforeCommitWrite.Count > 0)
+                    RequestCommitAsync();
+            }
+        }
+        
+        Task CommitCore()
         {
             Dispatcher.UIThread.VerifyAccess();
             using var noPump = NonPumpingLockHelper.Use();
             
             _nextCommit ??= new TaskCompletionSource<int>();
 
-            while (_invokeBeforeCommit.Count > 0)
-                _invokeBeforeCommit.Dequeue()(_nextCommit.Task);
+            (_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead);
+            while (_invokeBeforeCommitRead.Count > 0)
+                _invokeBeforeCommitRead.Dequeue()();
             
             var batch = new Batch(_nextCommit);
             
@@ -109,6 +123,7 @@ namespace Avalonia.Rendering.Composition
                         writer.WriteObject(job);
                     writer.WriteObject(ServerCompositor.RenderThreadJobsEndMarker);
                 }
+                _pendingServerCompositorJobs.Clear();
             }
             
             batch.CommittedAt = Server.Clock.Elapsed;
@@ -138,34 +153,73 @@ namespace Avalonia.Rendering.Composition
             RequestCommitAsync();
         }
 
-        internal void InvokeBeforeNextCommit(Action<Task> action)
+        /// <summary>
+        /// Enqueues a callback to be called before the next scheduled commit.
+        /// If there is no scheduled commit it automatically schedules one
+        /// This is useful for updating your composition tree objects after binding
+        /// and layout passes have completed
+        /// </summary>
+        public void RequestCompositionUpdate(Action action)
         {
             Dispatcher.UIThread.VerifyAccess();
-            _invokeBeforeCommit.Enqueue(action);
+            _invokeBeforeCommitWrite.Enqueue(action);
             RequestCommitAsync();
         }
 
-        /// <summary>
-        /// Attempts to query for a feature from the platform render interface
-        /// </summary>
-        public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
+        internal void PostServerJob(Action job)
         {
-            var tcs = new TaskCompletionSource<object?>();
-            _pendingServerCompositorJobs.Add(() =>
+            Dispatcher.UIThread.VerifyAccess();
+            _pendingServerCompositorJobs.Add(job);
+            RequestCommitAsync();
+        }
+
+        internal Task InvokeServerJobAsync(Action job) =>
+            InvokeServerJobAsync<object?>(() =>
+            {
+                job();
+                return null;
+            });
+
+        internal Task<T> InvokeServerJobAsync<T>(Func<T> job)
+        {
+            var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
+            PostServerJob(() =>
             {
                 try
                 {
-                    using (Server.RenderInterface.EnsureCurrent())
-                    {
-                        tcs.TrySetResult(Server.RenderInterface.Value.TryGetFeature(featureType));
-                    }
+                    tcs.SetResult(job());
                 }
                 catch (Exception e)
                 {
                     tcs.TrySetException(e);
                 }
             });
-            return new ValueTask<object?>(tcs.Task);
+            return tcs.Task;
         }
+
+        /// <summary>
+        /// Attempts to query for a feature from the platform render interface
+        /// </summary>
+        public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType) =>
+            new(InvokeServerJobAsync(() =>
+            {
+                using (Server.RenderInterface.EnsureCurrent())
+                {
+                    return Server.RenderInterface.Value.TryGetFeature(featureType);
+                }
+            }));
+
+        public ValueTask<ICompositionGpuInterop?> TryGetCompositionGpuInterop() =>
+            new(InvokeServerJobAsync<ICompositionGpuInterop?>(() =>
+            {
+                using (Server.RenderInterface.EnsureCurrent())
+                {
+                    var feature = Server.RenderInterface.Value
+                        .TryGetFeature<IExternalObjectsRenderInterfaceContextFeature>();
+                    if (feature == null)
+                        return null;
+                    return new CompositionInterop(this, feature);
+                }
+            }));
     }
 }

+ 74 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Runtime.ExceptionServices;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisposable
+{
+    private IRef<IBitmapImpl>? _bitmap;
+    private IPlatformRenderInterfaceContext? _createdWithContext;
+    public override IRef<IBitmapImpl>? Bitmap
+    {
+        get
+        {
+            // Failsafe to avoid consuming an image imported with a different context
+            if (Compositor.RenderInterface.Value != _createdWithContext)
+                return null;
+            return _bitmap;
+        }
+    }
+
+    public ServerCompositionDrawingSurface(ServerCompositor compositor) : base(compositor)
+    {
+    }
+
+    void PerformSanityChecks(CompositionImportedGpuImage image)
+    {
+        // Failsafe to avoid consuming an image imported with a different context
+        if (!image.IsUsable)
+            throw new PlatformGraphicsContextLostException();
+
+        // This should never happen, but check for it anyway to avoid a deadlock
+        if (!image.ImportCompeted.IsCompleted)
+            throw new InvalidOperationException("The import operation is not completed yet");
+
+        // Rethrow the import here exception
+        if (image.ImportCompeted.IsFaulted)
+            image.ImportCompeted.GetAwaiter().GetResult();
+    }
+
+    void Update(IBitmapImpl newImage, IPlatformRenderInterfaceContext context)
+    {
+        _bitmap?.Dispose();
+        _bitmap = RefCountable.Create(newImage);
+        _createdWithContext = context;
+        Changed?.Invoke();
+    }
+
+    public void UpdateWithAutomaticSync(CompositionImportedGpuImage image)
+    {
+        PerformSanityChecks(image);
+        Update(image.Image.SnapshotWithAutomaticSync(), image.Context);
+    }
+    
+    public void UpdateWithKeyedMutex(CompositionImportedGpuImage image, uint acquireIndex, uint releaseIndex)
+    {
+        PerformSanityChecks(image);
+        Update(image.Image.SnapshotWithKeyedMutex(acquireIndex, releaseIndex), image.Context);
+    }
+
+    public void UpdateWithSemaphores(CompositionImportedGpuImage image, CompositionImportedGpuSemaphore wait, CompositionImportedGpuSemaphore signal)
+    {
+        PerformSanityChecks(image);
+        if (!wait.IsUsable || !signal.IsUsable)
+            throw new PlatformGraphicsContextLostException();
+        Update(image.Image.SnapshotWithSemaphores(wait.Semaphore, signal.Semaphore), image.Context);
+    }
+
+    public void Dispose()
+    {
+        _bitmap?.Dispose();
+    }
+}

+ 8 - 1
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurface.cs

@@ -1,11 +1,18 @@
 // Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
 
+using System;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
 namespace Avalonia.Rendering.Composition.Server
 {
-    internal abstract class ServerCompositionSurface : ServerObject
+    internal abstract partial class ServerCompositionSurface : ServerObject
     {
         protected ServerCompositionSurface(ServerCompositor compositor) : base(compositor)
         {
         }
+        
+        public abstract IRef<IBitmapImpl>? Bitmap { get; }
+        public Action? Changed { get; set; }
     }
 }

+ 48 - 0
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSurfaceVisual.cs

@@ -0,0 +1,48 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal partial class ServerCompositionSurfaceVisual
+{
+    protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
+    {
+        if (Surface == null)
+            return;
+        if (Surface.Bitmap == null)
+            return;
+        var bmp = Surface.Bitmap.Item;
+
+        //TODO: add a way to always render the whole bitmap instead of just assuming 96 DPI
+        canvas.DrawBitmap(Surface.Bitmap, 1, new Rect(bmp.PixelSize.ToSize(1)), new Rect(
+            new Size(Size.X, Size.Y)));
+    }
+
+
+    private void OnSurfaceInvalidated() => ValuesInvalidated();
+
+    protected override void OnAttachedToRoot(ServerCompositionTarget target)
+    {
+        if (Surface != null)
+            Surface.Changed += OnSurfaceInvalidated;
+        base.OnAttachedToRoot(target);
+    }
+
+    protected override void OnDetachedFromRoot(ServerCompositionTarget target)
+    {
+        if (Surface != null)
+            Surface.Changed -= OnSurfaceInvalidated;
+        base.OnDetachedFromRoot(target);
+    }
+
+    partial void OnSurfaceChanged()
+    {
+        if (Surface != null)
+            Surface.Changed += OnSurfaceInvalidated;
+    }
+
+    partial void OnSurfaceChanging()
+    {
+        if (Surface != null)
+            Surface.Changed -= OnSurfaceInvalidated;
+    }
+}

+ 12 - 7
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@@ -22,7 +22,8 @@ namespace Avalonia.Rendering.Composition.Server
     {
         private readonly IRenderLoop _renderLoop;
 
-        private readonly Queue<Batch> _batches = new Queue<Batch>(); 
+        private readonly Queue<Batch> _batches = new Queue<Batch>();
+        private readonly Queue<Action> _receivedJobQueue = new();
         public long LastBatchId { get; private set; }
         public Stopwatch Clock { get; } = Stopwatch.StartNew();
         public TimeSpan ServerNow { get; private set; }
@@ -75,7 +76,7 @@ namespace Avalonia.Rendering.Composition.Server
                         var readObject = stream.ReadObject();
                         if (readObject == RenderThreadJobsStartMarker)
                         {
-                            ReadAndExecuteJobs(stream);
+                            ReadServerJobs(stream);
                             continue;
                         }
                         
@@ -97,21 +98,24 @@ namespace Avalonia.Rendering.Composition.Server
             }
         }
 
-        void ReadAndExecuteJobs(BatchStreamReader reader)
+        void ReadServerJobs(BatchStreamReader reader)
         {
             object? readObject;
             while ((readObject = reader.ReadObject()) != RenderThreadJobsEndMarker)
-            {
-                var job = (Action)readObject!;
+                _receivedJobQueue.Enqueue((Action)readObject!);
+        }
+
+        void ExecuteServerJobs()
+        {
+            while(_receivedJobQueue.Count > 0)
                 try
                 {
-                    job();
+                    _receivedJobQueue.Dequeue()();
                 }
                 catch
                 {
                     // Ignore
                 }
-            }
         }
 
         void CompletePendingBatches()
@@ -160,6 +164,7 @@ namespace Avalonia.Rendering.Composition.Server
             try
             {
                 RenderInterface.EnsureValidBackendContext();
+                ExecuteServerJobs();
                 foreach (var t in _activeTargets)
                     t.Render();
             }

+ 2 - 0
src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs

@@ -50,6 +50,8 @@ public class PlatformRenderInterfaceContextManager
         }
     }
 
+    internal IPlatformGraphicsContext? GpuContext => _gpuContext?.Value;
+
     public IDisposable EnsureCurrent()
     {
         EnsureValidBackendContext();

+ 89 - 0
src/Avalonia.Base/Rendering/SwapchainBase.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.Rendering;
+
+/// <summary>
+/// A helper class for composition-backed swapchains, should not be a public API yet
+/// </summary>
+abstract class SwapchainBase<TImage> : IAsyncDisposable where TImage : class, ISwapchainImage
+{
+    protected ICompositionGpuInterop Interop { get; }
+    protected CompositionDrawingSurface Target { get; }
+    private List<TImage> _pendingImages = new();
+
+    public SwapchainBase(ICompositionGpuInterop interop, CompositionDrawingSurface target)
+    {
+        Interop = interop;
+        Target = target;
+    }
+
+    static bool IsBroken(TImage image) => image.LastPresent?.IsFaulted == true;
+    static bool IsReady(TImage image) => image.LastPresent == null || image.LastPresent.Status == TaskStatus.RanToCompletion;
+
+    TImage? CleanupAndFindNextImage(PixelSize size)
+    {
+        TImage? firstFound = null;
+        var foundMultiple = false;
+        
+        for (var c = _pendingImages.Count - 1; c > -1; c--)
+        {
+            var image = _pendingImages[c];
+            var ready = IsReady(image);
+            var matches = image.Size == size;
+            if (IsBroken(image) || (!matches && ready))
+            {
+                image.DisposeAsync();
+                _pendingImages.RemoveAt(c);
+            }
+
+            if (matches && ready)
+            {
+                if (firstFound == null)
+                    firstFound = image;
+                else
+                    foundMultiple = true;
+            }
+
+        }
+
+        // We are making sure that there was at least one image of the same size in flight
+        // Otherwise we might encounter UI thread lockups
+        return foundMultiple ? firstFound : null;
+    }
+
+    protected abstract TImage CreateImage(PixelSize size);
+
+    protected IDisposable BeginDrawCore(PixelSize size, out TImage image)
+    {
+        var img = CleanupAndFindNextImage(size) ?? CreateImage(size);
+        
+        img.BeginDraw();
+        _pendingImages.Remove(img);
+        image = img;
+        return Disposable.Create(() =>
+        {
+            img.Present();
+            _pendingImages.Add(img);
+        });
+    }
+    
+    public async ValueTask DisposeAsync()
+    {
+        foreach (var img in _pendingImages)
+            await img.DisposeAsync();
+    }
+}
+
+
+interface ISwapchainImage : IAsyncDisposable
+{
+    PixelSize Size { get; }
+    Task? LastPresent { get; }
+    void BeginDraw();
+    void Present();
+}

+ 6 - 1
src/Avalonia.Base/composition-schema.xml

@@ -7,6 +7,8 @@
     
     <Manual Name="Avalonia.Platform.IGeometryImpl" Passthrough="true"/>
     <Manual Name="Avalonia.Media.IBrush" Passthrough="true"/>
+    <Manual Name="CompositionSurface" />
+    <Manual Name="CompositionDrawingSurface" />
     <Object Name="CompositionVisual" Abstract="true">
         <Property Name="Root" Type="CompositionTarget?" InternalSet="true" />
         <Property Name="Parent" Type="CompositionVisual?" InternalSet="true"  />
@@ -30,6 +32,9 @@
     <Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual">
         <Property Name="Color" Type="Avalonia.Media.Color" Animated="true" />
     </Object>
+    <Object Name="CompositionSurfaceVisual" Inherits="CompositionContainerVisual">
+        <Property Name="Surface" Type="CompositionSurface?" />
+    </Object>
     <List Name="CompositionVisualCollection" ItemType="CompositionVisual" CustomCtor="true"/>
     <Object Name="CompositionTarget" CustomServerCtor="true">
         <Property Name="Root" Type="CompositionVisual?"/>
@@ -46,4 +51,4 @@
     <KeyFrameAnimation Type="Vector3"/>
     <KeyFrameAnimation Type="Vector4"/>
     <KeyFrameAnimation Type="Quaternion"/>
-</NComposition>
+</NComposition>

+ 1 - 0
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@@ -49,6 +49,7 @@ namespace Avalonia.Headless
         public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException();
 
         public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
+        public bool IsLost => false;
         public object TryGetFeature(Type featureType) => null;
 
         public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)

+ 2 - 2
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

@@ -15,7 +15,7 @@
     <Import Project="..\..\build\TrimmingEnable.props" />
 
   <ItemGroup>
-    <Compile Remove="..\Shared\SourceGeneratorAttributes.cs"/>
-    <None Include="..\Shared\SourceGeneratorAttributes.cs" Visible="false"/>
+    <Compile Remove="..\Shared\SourceGeneratorAttributes.cs" />
+    <None Include="..\Shared\SourceGeneratorAttributes.cs" Visible="false" />
   </ItemGroup>
 </Project>

+ 163 - 0
src/Avalonia.OpenGL/Controls/CompositionOpenGlSwapchain.cs

@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.OpenGL.Controls;
+
+internal class CompositionOpenGlSwapchain : SwapchainBase<IGlSwapchainImage>
+{
+    private readonly IGlContext _context;
+    private readonly IGlContextExternalObjectsFeature? _externalObjectsFeature;
+    private readonly IOpenGlTextureSharingRenderInterfaceContextFeature? _sharingFeature;
+
+    public CompositionOpenGlSwapchain(IGlContext context, ICompositionGpuInterop interop, CompositionDrawingSurface target,
+        IOpenGlTextureSharingRenderInterfaceContextFeature sharingFeature
+        ) : base(interop, target)
+    {
+        _context = context;
+        _sharingFeature = sharingFeature;
+    }
+    
+    public CompositionOpenGlSwapchain(IGlContext context, ICompositionGpuInterop interop, CompositionDrawingSurface target,
+        IGlContextExternalObjectsFeature externalObjectsFeature) : base(interop, target)
+    {
+        _context = context;
+        _externalObjectsFeature = externalObjectsFeature;
+    }
+    
+    
+
+    protected override IGlSwapchainImage CreateImage(PixelSize size)
+    {
+        if (_sharingFeature != null)
+            return new CompositionOpenGlSwapChainImage(_context, _sharingFeature, size, Interop, Target);
+        return new DxgiMutexOpenGlSwapChainImage(Interop, Target, _externalObjectsFeature!, size);
+    }
+
+    public IDisposable BeginDraw(PixelSize size, out IGlTexture texture)
+    {
+        var rv = BeginDrawCore(size, out var tex);
+        texture = tex;
+        return rv;
+    }
+}
+
+internal interface IGlTexture
+{
+    int TextureId { get; }
+    int InternalFormat { get; }
+    PixelSize Size { get; }
+}
+
+
+interface IGlSwapchainImage : ISwapchainImage, IGlTexture
+{
+    
+}
+internal class DxgiMutexOpenGlSwapChainImage : IGlSwapchainImage
+{
+    private readonly ICompositionGpuInterop _interop;
+    private readonly CompositionDrawingSurface _surface;
+    private readonly IGlExportableExternalImageTexture _texture;
+    private Task? _lastPresent;
+    private ICompositionImportedGpuImage? _imported;
+
+    public DxgiMutexOpenGlSwapChainImage(ICompositionGpuInterop interop, CompositionDrawingSurface surface,
+        IGlContextExternalObjectsFeature externalObjects, PixelSize size)
+    {
+        _interop = interop;
+        _surface = surface;
+        _texture = externalObjects.CreateImage(KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle,
+            size, PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm);
+    }
+    public async ValueTask DisposeAsync()
+    {
+        // The texture is already sent to the compositor, so we need to wait for its attempts to use the texture
+        // before destroying it
+        if (_imported != null)
+        {
+            // No need to wait for import / LastPresent since calls are serialized on the compositor side anyway
+            try
+            {
+                await _imported.DisposeAsync();
+            }
+            catch
+            {
+                // Ignore
+            }
+        }
+        _texture.Dispose();
+    }
+
+    public int TextureId => _texture.TextureId;
+    public int InternalFormat => _texture.InternalFormat;
+    public PixelSize Size => new(_texture.Properties.Width, _texture.Properties.Height);
+    public Task LastPresent => _lastPresent;
+    public void BeginDraw() => _texture.AcquireKeyedMutex(0);
+
+    public void Present()
+    {
+        _texture.ReleaseKeyedMutex(1);
+        _imported ??= _interop.ImportImage(_texture.GetHandle(), _texture.Properties);
+        _lastPresent = _surface.UpdateWithKeyedMutexAsync(_imported, 1, 0);
+    }
+}
+
+internal class CompositionOpenGlSwapChainImage : IGlSwapchainImage
+{
+    private readonly ICompositionGpuInterop _interop;
+    private readonly CompositionDrawingSurface _target;
+    private readonly ICompositionImportableOpenGlSharedTexture _texture;
+    private ICompositionImportedGpuImage? _imported;
+
+    public CompositionOpenGlSwapChainImage(
+        IGlContext context,
+        IOpenGlTextureSharingRenderInterfaceContextFeature sharingFeature,
+        PixelSize size,
+        ICompositionGpuInterop interop,
+        CompositionDrawingSurface target)
+    {
+        _interop = interop;
+        _target = target;
+        _texture = sharingFeature.CreateSharedTextureForComposition(context, size);
+    }
+
+    
+    public async ValueTask DisposeAsync()
+    {
+        // The texture is already sent to the compositor, so we need to wait for its attempts to use the texture
+        // before destroying it
+        if (_imported != null)
+        {
+            // No need to wait for import / LastPresent since calls are serialized on the compositor side anyway
+            try
+            {
+                await _imported.DisposeAsync();
+            }
+            catch
+            {
+                // Ignore
+            }
+        }
+
+        _texture.Dispose();
+    }
+
+    public int TextureId => _texture.TextureId;
+    public int InternalFormat => _texture.InternalFormat;
+    public PixelSize Size => _texture.Size;
+    public Task? LastPresent { get; private set; }
+    public void BeginDraw()
+    {
+        // No-op for texture sharing
+    }
+
+    public void Present()
+    {
+        _imported ??= _interop.ImportImage(_texture);
+        LastPresent = _target.UpdateAsync(_imported);
+    }
+}

+ 131 - 171
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@@ -1,120 +1,53 @@
 using System;
+using System.Numerics;
 using System.Threading.Tasks;
 using Avalonia.Controls;
 using Avalonia.Logging;
-using Avalonia.Media;
-using Avalonia.OpenGL.Imaging;
+using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
 using Avalonia.VisualTree;
-using static Avalonia.OpenGL.GlConsts;
-
+using Avalonia.Platform;
 namespace Avalonia.OpenGL.Controls
 {
     public abstract class OpenGlControlBase : Control
     {
-        private IGlContext _context;
-        private int _fb, _depthBuffer;
-        private OpenGlBitmap _bitmap;
-        private IOpenGlBitmapAttachment _attachment;
-        private PixelSize _depthBufferSize;
-        
+        private CompositionSurfaceVisual _visual;
+        private Action _update;
+        private bool _updateQueued;
         private Task<bool> _initialization;
-        private IOpenGlTextureSharingRenderInterfaceContextFeature _feature;
-
-        protected GlVersion GlVersion { get; private set; }
-        public sealed override void Render(DrawingContext context)
-        {
-            if(!EnsureInitialized())
-                return;
-            
-            using (_context.MakeCurrent())
-            {
-                _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
-                EnsureTextureAttachment();
-                EnsureDepthBufferAttachment(_context.GlInterface);
-                if(!CheckFramebufferStatus(_context.GlInterface))
-                    return;
-                
-                OnOpenGlRender(_context.GlInterface, _fb);
-                _attachment.Present();
-            }
-
-            context.DrawImage(_bitmap, new Rect(_bitmap.Size), new Rect(Bounds.Size));
-            base.Render(context);
-        }
+        private OpenGlControlBaseResources? _resources;
+        private Compositor? _compositor;
+        protected GlVersion GlVersion => _resources?.Context.Version ?? default;
         
-        void EnsureTextureAttachment()
+        public OpenGlControlBase()
         {
-            _context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
-            if (_bitmap == null || _attachment == null || _bitmap.PixelSize != GetPixelSize())
-            {
-                _attachment?.Dispose();
-                _attachment = null;
-                _bitmap?.Dispose();
-                _bitmap = null;
-                _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
-                _attachment = _bitmap.CreateFramebufferAttachment(_context);
-            }
-        }
-        
-        void EnsureDepthBufferAttachment(GlInterface gl)
-        {
-            var size = GetPixelSize();
-            if (size == _depthBufferSize && _depthBuffer != 0)
-                return;
-                    
-            gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
-            if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
-
-            _depthBuffer = gl.GenRenderbuffer();
-            gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
-            gl.RenderbufferStorage(GL_RENDERBUFFER,
-                GlVersion.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
-                size.Width, size.Height);
-            gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
-            gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
+            _update = Update;
         }
 
         void DoCleanup()
         {
-            if (_context != null)
+            if (_initialization is { Status: TaskStatus.RanToCompletion } && _resources != null)
             {
-                using (_context.MakeCurrent())
+                try
                 {
-                    var gl = _context.GlInterface;
-                    gl.ActiveTexture(GL_TEXTURE0);
-                    gl.BindTexture(GL_TEXTURE_2D, 0);
-                    gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
-                    if (_fb != 0)
-                        gl.DeleteFramebuffer(_fb);
-                    _fb = 0;
-                    if (_depthBuffer != 0)
-                        gl.DeleteRenderbuffer(_depthBuffer);
-                    _depthBuffer = 0;
-                    _attachment?.Dispose();
-                    _attachment = null;
-                    _bitmap?.Dispose();
-                    _bitmap = null;
-                    
-                    try
-                    {
-                        if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true })
-                        {
-                            OnOpenGlDeinit(_context.GlInterface, _fb);
-                            _initialization = null;
-                        }
-                    }
-                    finally
+                    using (_resources.Context.EnsureCurrent())
                     {
-                        _context.Dispose();
-                        _context = null;
+                        OnOpenGlDeinit(_resources.Context.GlInterface);
                     }
                 }
+                catch(Exception e)
+                {
+                    Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+                        "Unable to free user OpenGL resources: {exception}", e);
+                }
             }
 
-            _fb = _depthBuffer = 0;
-            _attachment = null;
-            _bitmap = null;
-            _feature = null;
+            ElementComposition.SetElementChildVisual(this, null);
+            _visual = null;
+            
+            _resources?.DisposeAsync();
+            _resources = null;
+            _initialization = null;
         }
 
         protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@@ -123,95 +56,75 @@ namespace Avalonia.OpenGL.Controls
             base.OnDetachedFromVisualTree(e);
         }
 
-        private bool EnsureInitializedCore(IOpenGlTextureSharingRenderInterfaceContextFeature feature)
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
-            try
-            {
-                _context = feature.CreateSharedContext();
-                _feature = feature;
-            }
-            catch (Exception e)
-            {
-                Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                    "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
-                return false;
-            }
+            base.OnAttachedToVisualTree(e);
+            _compositor = (this.GetVisualRoot()?.Renderer as IRendererWithCompositor)?.Compositor;
+            RequestNextFrameRendering();
+        }
 
-            if (_context == null)
-            {
-                Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                    "Unable to initialize OpenGL: unable to create additional OpenGL context.");
-                return false;
-            }
+        private bool EnsureInitializedCore(
+            ICompositionGpuInterop interop,
+            IOpenGlTextureSharingRenderInterfaceContextFeature contextSharingFeature)
+        {
+            var surface = _compositor.CreateDrawingSurface();
 
-            GlVersion = _context.Version;
+            IGlContext ctx = null;
+            var contextFactory = AvaloniaLocator.Current.GetService<IPlatformGraphicsOpenGlContextFactory>();
             try
             {
-                _bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96));
-                if (!_bitmap.SupportsContext(_context))
+                if (contextSharingFeature?.CanCreateSharedContext == true)
+                    _resources = OpenGlControlBaseResources.TryCreate(surface, interop, contextSharingFeature);
+
+                if(_resources == null)
+                {
+                    ctx = contextFactory.CreateContext(null);
+                    if (ctx.TryGetFeature<IGlContextExternalObjectsFeature>(out var externalObjects))
+                        _resources = OpenGlControlBaseResources.TryCreate(ctx, surface, interop, externalObjects);
+                    else
+                        ctx.Dispose();
+                }
+                
+                if(_resources == null)
                 {
                     Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                        "Unable to initialize OpenGL: unable to create OpenGlBitmap: OpenGL context is not compatible");
+                        "Unable to initialize OpenGL: current platform does not support multithreaded context sharing and shared memory");
                     return false;
                 }
             }
             catch (Exception e)
             {
-                _context.Dispose();
-                _context = null;
                 Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                    "Unable to initialize OpenGL: unable to create OpenGlBitmap: {exception}", e);
+                    "Unable to initialize OpenGL: {exception}", e);
+                ctx?.Dispose();
                 return false;
             }
+            
+            _visual = _compositor.CreateSurfaceVisual();
+            _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+            _visual.Surface = _resources.Surface;
+            ElementComposition.SetElementChildVisual(this, _visual);
+            using (_resources.Context.MakeCurrent())
+                OnOpenGlInit(_resources.Context.GlInterface);
+            return true;
 
-            using (_context.MakeCurrent())
-            {
-                try
-                {
-                    _depthBufferSize = GetPixelSize();
-                    var gl = _context.GlInterface;
-                    _fb = gl.GenFramebuffer();
-                    gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
-                    
-                    EnsureDepthBufferAttachment(gl);
-                    EnsureTextureAttachment();
-
-                    return CheckFramebufferStatus(gl);
-                }
-                catch(Exception e)
-                {
-                    Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                        "Unable to initialize OpenGL FBO: {exception}", e);
-                    return false;
-                }
-            }
         }
-
-        private static bool CheckFramebufferStatus(GlInterface gl)
+        
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
         {
-            var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
-            if (status != GL_FRAMEBUFFER_COMPLETE)
+            if (_visual != null && change.Property == BoundsProperty)
             {
-                int code;
-                while ((code = gl.GetError()) != 0)
-                    Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                        "Unable to initialize OpenGL FBO: {code}", code);
-                return false;
+                _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
+                RequestNextFrameRendering();
             }
 
-            return true;
+            base.OnPropertyChanged(change);
         }
 
         void ContextLost()
         {
-            _context = null;
-            _feature = null;
             _initialization = null;
-            _attachment = null;
-            _bitmap = null;
-            _fb = 0;
-            _depthBuffer = 0;
-            _depthBufferSize = default;
+            _resources?.DisposeAsync();
             OnOpenGlLost();
         }
 
@@ -228,59 +141,106 @@ namespace Avalonia.OpenGL.Controls
                 if (_initialization is { IsCompleted: false })
                     return false;
 
-                if (_context.IsLost)
+                if (_resources!.Context.IsLost)
                     ContextLost();
                 else 
                     return true;
             }
 
             _initialization = InitializeAsync();
+
+            async void ContinueOnInitialization()
+            {
+                try
+                {
+                    await _initialization;
+                    RequestNextFrameRendering();
+                }
+                catch
+                {
+                    //
+                }
+            }
+            ContinueOnInitialization();
             return false;
 
         }
 
+        
+        private void Update()
+        {
+            _updateQueued = false;
+            if (VisualRoot == null)
+                return;
+            if(!EnsureInitialized())
+                return;
+            using (_resources.BeginDraw(GetPixelSize()))
+                OnOpenGlRender(_resources.Context.GlInterface, _resources.Fbo);
+        }
+
         private async Task<bool> InitializeAsync()
         {
+            if (_compositor == null)
+            {
+                Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+                    "Unable to obtain Compositor instance");
+                return false;
+            }
+            
+            var gpuInteropTask = _compositor.TryGetCompositionGpuInterop();
+
             var contextSharingFeature =
                 (IOpenGlTextureSharingRenderInterfaceContextFeature)
-                await this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature(
+                await _compositor.TryGetRenderInterfaceFeature(
                     typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
+            var interop = await gpuInteropTask;
 
-            if (contextSharingFeature == null || !contextSharingFeature.CanCreateSharedContext)
+            if (interop == null)
             {
                 Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
-                    "Unable to initialize OpenGL: current platform does not support multithreaded context sharing");
+                    "Compositor backend doesn't support GPU interop");
                 return false;
             }
 
-            if (!EnsureInitializedCore(contextSharingFeature))
+            if (!EnsureInitializedCore(interop, contextSharingFeature))
             {
                 DoCleanup();
                 return false;
             }
 
-            using (_context.MakeCurrent())
-                OnOpenGlInit(_context.GlInterface, _fb);
-
-            InvalidateVisual();
+            using (_resources!.Context.MakeCurrent())
+                OnOpenGlInit(_resources.Context.GlInterface);
             
             return true;
         }
+
+        [Obsolete("Use RequestNextFrameRendering()")]
+        // ReSharper disable once MemberCanBeProtected.Global
+        public new void InvalidateVisual() => RequestNextFrameRendering(); 
         
+        public void RequestNextFrameRendering()
+        {
+            if ((_initialization == null || _initialization is { Status: TaskStatus.RanToCompletion }) &&
+                !_updateQueued)
+            {
+                _updateQueued = true;
+                _compositor?.RequestCompositionUpdate(_update);
+            }
+        }
+
         private PixelSize GetPixelSize()
         {
-            var scaling = VisualRoot.RenderScaling;
+            var scaling = VisualRoot!.RenderScaling;
             return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
                 Math.Max(1, (int)(Bounds.Height * scaling)));
         }
-
-
-        protected virtual void OnOpenGlInit(GlInterface gl, int fb)
+        
+        protected virtual void OnOpenGlInit(GlInterface gl)
         {
             
         }
 
-        protected virtual void OnOpenGlDeinit(GlInterface gl, int fb)
+        protected virtual void OnOpenGlDeinit(GlInterface gl)
         {
             
         }

+ 170 - 0
src/Avalonia.OpenGL/Controls/OpenGlControlResources.cs

@@ -0,0 +1,170 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Threading.Tasks;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using static Avalonia.OpenGL.GlConsts;
+namespace Avalonia.OpenGL.Controls;
+
+internal class OpenGlControlBaseResources : IAsyncDisposable
+{
+    private int _depthBuffer;
+    public int Fbo { get; private set; }
+    private PixelSize _depthBufferSize;
+    public CompositionDrawingSurface Surface { get; }
+    public CompositionOpenGlSwapchain _swapchain;
+    public IGlContext Context { get; private set; }
+    
+    public static OpenGlControlBaseResources? TryCreate(CompositionDrawingSurface surface,
+        ICompositionGpuInterop interop,
+        IOpenGlTextureSharingRenderInterfaceContextFeature feature)
+    {
+        IGlContext context;
+        try
+        {
+            context = feature.CreateSharedContext();
+        }
+        catch (Exception e)
+        {
+            Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+                "Unable to initialize OpenGL: unable to create additional OpenGL context: {exception}", e);
+            return null;
+        }
+
+        if (context == null)
+        {
+            Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+                "Unable to initialize OpenGL: unable to create additional OpenGL context.");
+            return null;
+        }
+
+        return new OpenGlControlBaseResources(context, surface, interop, feature, null);
+    }
+
+    public static OpenGlControlBaseResources? TryCreate(IGlContext context, CompositionDrawingSurface surface,
+        ICompositionGpuInterop interop, IGlContextExternalObjectsFeature externalObjects)
+    {
+        if (!interop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
+                .D3D11TextureGlobalSharedHandle)
+            || !externalObjects.SupportedExportableExternalImageTypes.Contains(
+                KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle))
+            return null;
+        return new OpenGlControlBaseResources(context, surface, interop, null, externalObjects);
+    }
+    
+    public OpenGlControlBaseResources(IGlContext context,
+        CompositionDrawingSurface surface,
+        ICompositionGpuInterop interop,
+        IOpenGlTextureSharingRenderInterfaceContextFeature? feature,
+        IGlContextExternalObjectsFeature? externalObjects
+        )
+    {
+        Context = context;
+        Surface = surface;
+        using (context.MakeCurrent())
+            Fbo = context.GlInterface.GenFramebuffer();
+        _swapchain =
+            feature != null ?
+                new CompositionOpenGlSwapchain(context, interop, Surface, feature) :
+                new CompositionOpenGlSwapchain(context, interop, Surface, externalObjects);
+    }
+
+    void UpdateDepthRenderbuffer(PixelSize size)
+    {
+        if (size == _depthBufferSize && _depthBuffer != 0)
+            return;
+
+        var gl = Context.GlInterface;
+        gl.GetIntegerv(GL_RENDERBUFFER_BINDING, out var oldRenderBuffer);
+        if (_depthBuffer != 0) gl.DeleteRenderbuffer(_depthBuffer);
+
+        _depthBuffer = gl.GenRenderbuffer();
+        gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
+        gl.RenderbufferStorage(GL_RENDERBUFFER,
+            Context.Version.Type == GlProfileType.OpenGLES ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
+            size.Width, size.Height);
+        gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
+        gl.BindRenderbuffer(GL_RENDERBUFFER, oldRenderBuffer);
+        _depthBufferSize = size;
+    }
+    
+    public IDisposable BeginDraw(PixelSize size)
+    {
+        var restoreContext = Context.EnsureCurrent();
+        IDisposable? imagePresent = null;
+        var success = false;
+        try
+        {
+            var gl = Context.GlInterface;
+            Context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, Fbo);
+            UpdateDepthRenderbuffer(size);
+
+            imagePresent = _swapchain.BeginDraw(size, out var texture);
+            gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.TextureId, 0);
+
+            var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER);
+            if (status != GL_FRAMEBUFFER_COMPLETE)
+            {
+                int code = gl.GetError();
+                Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+                    "Unable to configure OpenGL FBO: {code}", code);
+                throw OpenGlException.GetFormattedException("Unable to configure OpenGL FBO", code);
+            }
+
+            success = true;
+            return Disposable.Create(() =>
+            {
+                try
+                {
+                    Context.GlInterface.Flush();
+                    imagePresent.Dispose();
+                }
+                finally
+                {
+                    restoreContext.Dispose();
+                }
+            });
+        }
+        finally
+        {
+            if (!success)
+            {
+                imagePresent?.Dispose();
+                restoreContext.Dispose();
+            }
+        }
+    }
+
+    public async ValueTask DisposeAsync()
+    {
+        if (Context is { IsLost: false })
+        {
+            try
+            {
+                using (Context.MakeCurrent())
+                {
+                    var gl = Context.GlInterface;
+                    if (Fbo != 0)
+                        gl.DeleteFramebuffer(Fbo);
+                    Fbo = 0;
+                    if (_depthBuffer != 0)
+                        gl.DeleteRenderbuffer(_depthBuffer);
+                    _depthBuffer = 0;
+                }
+
+            }
+            catch
+            {
+                //
+            }
+
+            Surface.Dispose();
+            await _swapchain.DisposeAsync();
+
+            Context = null!;
+        }
+    }
+}

+ 7 - 5
src/Avalonia.OpenGL/Egl/EglConsts.cs

@@ -64,7 +64,7 @@ namespace Avalonia.OpenGL.Egl
         public const int  EGL_WIDTH = 0x3057;
         public const int  EGL_WINDOW_BIT = 0x0004;
 
-//        public const int  EGL_BACK_BUFFER = 0x3084;
+        public const int  EGL_BACK_BUFFER = 0x3084;
 //        public const int  EGL_BIND_TO_TEXTURE_RGB = 0x3039;
 //        public const int  EGL_BIND_TO_TEXTURE_RGBA = 0x303A;
         public const int  EGL_CONTEXT_LOST = 0x300E;
@@ -73,11 +73,11 @@ namespace Avalonia.OpenGL.Egl
 //        public const int  EGL_MIPMAP_TEXTURE = 0x3082;
 //        public const int  EGL_MIPMAP_LEVEL = 0x3083;
 //        public const int  EGL_NO_TEXTURE = 0x305C;
-//        public const int  EGL_TEXTURE_2D = 0x305F;
-//        public const int  EGL_TEXTURE_FORMAT = 0x3080;
+        public const int  EGL_TEXTURE_2D = 0x305F;
+        public const int  EGL_TEXTURE_FORMAT = 0x3080;
 //        public const int  EGL_TEXTURE_RGB = 0x305D;
-//        public const int  EGL_TEXTURE_RGBA = 0x305E;
-//        public const int  EGL_TEXTURE_TARGET = 0x3081;
+        public const int  EGL_TEXTURE_RGBA = 0x305E;
+        public const int  EGL_TEXTURE_TARGET = 0x3081;
 
 //        public const int  EGL_ALPHA_FORMAT = 0x3088;
 //        public const int  EGL_ALPHA_FORMAT_NONPRE = 0x308B;
@@ -216,5 +216,7 @@ namespace Avalonia.OpenGL.Egl
         public const int  EGL_TEXTURE_OFFSET_Y_ANGLE = 0x3491;
 
         public const int  EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6;
+        
+        public const int EGL_TEXTURE_INTERNAL_FORMAT_ANGLE = 0x345D;
     }
 }

+ 14 - 2
src/Avalonia.OpenGL/Egl/EglContext.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Reactive.Disposables;
 using System.Threading;
 using Avalonia.Platform;
@@ -19,21 +20,24 @@ namespace Avalonia.OpenGL.Egl
         private readonly object _lock;
 
         internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface,
-            GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, Dictionary<Type, object> features)
+            GlVersion version, int sampleCount, int stencilSize, Action disposeCallback, 
+            Dictionary<Type, Func<EglContext, object>> features)
         {
             _disp = display;
             _egl = egl;
             _sharedWith = sharedWith;
             _context = ctx;
             _disposeCallback = disposeCallback;
-            _features = features;
             OffscreenSurface = offscreenSurface;
             Version = version;
             SampleCount = sampleCount;
             StencilSize = stencilSize;
             _lock = display.ContextSharedSyncRoot ?? new object();
             using (MakeCurrent())
+            {
                 GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
+                _features = features.ToDictionary(x => x.Key, x => x.Value(this));
+            }
         }
 
         public IntPtr Context =>
@@ -155,6 +159,14 @@ namespace Avalonia.OpenGL.Egl
         {
             if(_context == IntPtr.Zero)
                 return;
+            
+            foreach(var f in _features.ToList())
+                if (f.Value is IDisposable d)
+                {
+                    d.Dispose();
+                    _features.Remove(f.Key);
+                }
+
             _egl.DestroyContext(_disp.Handle, Context);
             OffscreenSurface?.Dispose();
             _context = IntPtr.Zero;

+ 1 - 1
src/Avalonia.OpenGL/Egl/EglDisplay.cs

@@ -93,7 +93,7 @@ namespace Avalonia.OpenGL.Egl
 
                 var rv = new EglContext(this, _egl, share, ctx, offscreenSurface,
                     _config.Version, _config.SampleCount, _config.StencilSize,
-                    options.DisposeCallback, options.ExtraFeatures);
+                    options.DisposeCallback, options.ExtraFeatures ?? new());
                 _contexts.Add(rv);
                 return rv;
             }

+ 1 - 1
src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs

@@ -19,7 +19,7 @@ public class EglContextOptions
     public EglContext ShareWith { get; set; }
     public EglSurface OffscreenSurface { get; set; }
     public Action DisposeCallback { get; set; }
-    public Dictionary<Type, object> ExtraFeatures { get; set; }
+    public Dictionary<Type, Func<EglContext, object>> ExtraFeatures { get; set; }
 }
 
 public class EglDisplayCreationOptions : EglDisplayOptions

+ 3 - 0
src/Avalonia.OpenGL/Egl/EglInterface.cs

@@ -98,6 +98,9 @@ namespace Avalonia.OpenGL.Egl
         [GetProcAddress("eglCreateWindowSurface")]
         public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
 
+        [GetProcAddress("eglBindTexImage")]
+        public partial int BindTexImage(IntPtr display, IntPtr surface, int buffer);
+
         [GetProcAddress("eglGetConfigAttrib")]
         public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
         

+ 282 - 0
src/Avalonia.OpenGL/Features/ExternalObjectsOpenGlExtensionFeature.cs

@@ -0,0 +1,282 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using Avalonia.SourceGenerator;
+using static Avalonia.OpenGL.GlConsts;
+
+namespace Avalonia.OpenGL.Features;
+
+unsafe partial class ExternalObjectsInterface
+{
+    public ExternalObjectsInterface(Func<string, IntPtr> getProcAddress)
+    {
+        Initialize(getProcAddress);
+    }
+    
+    [GetProcAddress("glImportMemoryFdEXT", true)]
+    public partial void ImportMemoryFdEXT(uint memory, ulong size, int handleType, int fd);
+    
+    [GetProcAddress("glImportSemaphoreFdEXT", true)]
+    public partial void ImportSemaphoreFdEXT(uint semaphore,
+                              int handleType,
+                              int fd);
+    
+    [GetProcAddress("glCreateMemoryObjectsEXT")]
+    public partial void CreateMemoryObjectsEXT(int n, out uint memoryObjects);
+    
+    [GetProcAddress("glDeleteMemoryObjectsEXT")]
+    public partial void DeleteMemoryObjectsEXT(int n, ref uint objects);
+
+    [GetProcAddress("glTexStorageMem2DEXT")]
+    public partial void TexStorageMem2DEXT(int target, int levels, int internalFormat, int width, int height,
+        uint memory, ulong offset);
+    
+    [GetProcAddress("glGenSemaphoresEXT")]
+    public partial void GenSemaphoresEXT(int n, out uint semaphores);
+
+    [GetProcAddress("glDeleteSemaphoresEXT")]
+    public partial void DeleteSemaphoresEXT(int n, ref uint semaphores);
+    
+    [GetProcAddress("glWaitSemaphoreEXT")]
+    public partial void WaitSemaphoreEXT(uint semaphore,
+        uint numBufferBarriers, uint* buffers,
+        uint numTextureBarriers, int* textures,
+        int* srcLayouts);
+    
+    [GetProcAddress("glSignalSemaphoreEXT")]
+    public partial void SignalSemaphoreEXT(uint semaphore,
+        uint numBufferBarriers, uint* buffers,
+        uint numTextureBarriers, int* textures,
+        int* dstLayouts);
+    
+    
+    [GetProcAddress("glGetUnsignedBytei_vEXT", true)]
+    public partial void GetUnsignedBytei_vEXT(int target, uint index, byte* data);
+    
+    [GetProcAddress("glGetUnsignedBytevEXT", true)]
+    public partial void GetUnsignedBytevEXT(int target, byte* data);
+}
+
+public class ExternalObjectsOpenGlExtensionFeature : IGlContextExternalObjectsFeature
+{
+    private readonly IGlContext _context;
+    private readonly ExternalObjectsInterface _ext;
+    private List<string> _imageTypes = new();
+    private List<string> _semaphoreTypes = new();
+
+    public static ExternalObjectsOpenGlExtensionFeature TryCreate(IGlContext context)
+    {
+        var extensions = context.GlInterface.GetExtensions();
+        if (extensions.Contains("GL_EXT_memory_object") && extensions.Contains("GL_EXT_semaphore"))
+        {
+            try
+            {
+                return new ExternalObjectsOpenGlExtensionFeature(context, extensions);
+            }
+            catch (Exception e)
+            {
+                Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log(nameof(ExternalObjectsOpenGlExtensionFeature),
+                    "Unable to initialize EXT_external_objects extension: " + e);
+            }
+        }
+
+        return null;
+    }
+
+    private unsafe ExternalObjectsOpenGlExtensionFeature(IGlContext context, List<string> extensions)
+    {
+        _context = context;
+        _ext = new ExternalObjectsInterface(_context.GlInterface.GetProcAddress);
+
+        if (_ext.IsGetUnsignedBytei_vEXTAvailable)
+        {
+            _context.GlInterface.GetIntegerv(GL_NUM_DEVICE_UUIDS_EXT, out var numUiids);
+            if (numUiids > 0)
+            {
+                DeviceUuid = new byte[16];
+                fixed (byte* pUuid = DeviceUuid)
+                    _ext.GetUnsignedBytei_vEXT(GL_DEVICE_UUID_EXT, 0, pUuid);
+            }
+        }
+
+        if (_ext.IsGetUnsignedBytevEXTAvailable)
+        {
+            if (extensions.Contains("GL_EXT_memory_object_win32") || extensions.Contains("GL_EXT_semaphore_win32"))
+            {
+                DeviceLuid = new byte[8];
+                fixed (byte* pLuid = DeviceLuid)
+                    _ext.GetUnsignedBytevEXT(GL_DEVICE_LUID_EXT, pLuid);
+            }
+        }
+
+        if (extensions.Contains("GL_EXT_memory_object_fd")
+            && extensions.Contains("GL_EXT_semaphore_fd"))
+        {
+            _imageTypes.Add(KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+            _semaphoreTypes.Add(KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor);
+        }
+        
+
+
+    }
+
+    public IReadOnlyList<string> SupportedImportableExternalImageTypes => _imageTypes;
+    public IReadOnlyList<string> SupportedExportableExternalImageTypes { get; } = Array.Empty<string>();
+    public IReadOnlyList<string> SupportedImportableExternalSemaphoreTypes => _semaphoreTypes;
+    public IReadOnlyList<string> SupportedExportableExternalSemaphoreTypes { get; } = Array.Empty<string>();
+    public IReadOnlyList<PlatformGraphicsExternalImageFormat> GetSupportedFormatsForExternalMemoryType(string type)
+    {
+        return new[]
+        {
+            PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm
+        };
+    }
+
+    public IGlExportableExternalImageTexture CreateImage(string type, PixelSize size,
+        PlatformGraphicsExternalImageFormat format) =>
+        throw new NotSupportedException();
+
+    public IGlExportableExternalImageTexture CreateSemaphore(string type) => throw new NotSupportedException();
+
+    public IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
+    {
+        if(!_imageTypes.Contains(handle.HandleDescriptor))
+            throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+        
+        if (handle.HandleDescriptor == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor)
+        {
+            while (_context.GlInterface.GetError() != 0)
+            {
+                //Skip existing errors
+            }
+            _ext.CreateMemoryObjectsEXT(1, out var memoryObject);
+            _ext.ImportMemoryFdEXT(memoryObject, properties.MemorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT,
+                handle.Handle.ToInt32());
+            
+            var err = _context.GlInterface.GetError();
+            if (err != 0)
+                throw OpenGlException.GetFormattedException("glImportMemoryFdEXT", err);
+
+            _context.GlInterface.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
+
+            var texture = _context.GlInterface.GenTexture();
+            _context.GlInterface.BindTexture(GL_TEXTURE_2D, texture);
+            _ext.TexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, properties.Width, properties.Height,
+                memoryObject, properties.MemoryOffset);
+            err = _context.GlInterface.GetError();
+
+            _context.GlInterface.BindTexture(GL_TEXTURE_2D, oldTexture);
+            if (err != 0)
+                throw OpenGlException.GetFormattedException("glTexStorageMem2DEXT", err);
+            
+            return new ExternalImageTexture(_context, properties, _ext, memoryObject, texture);
+        }
+
+        throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+    }
+
+    public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle)
+    {
+        if(!_semaphoreTypes.Contains(handle.HandleDescriptor))
+            throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+
+        if (handle.HandleDescriptor ==
+            KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor)
+        {
+            _ext.GenSemaphoresEXT(1, out var semaphore);
+            _ext.ImportSemaphoreFdEXT(semaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, handle.Handle.ToInt32());
+            return new ExternalSemaphore(_context, _ext, semaphore);
+        }
+        
+        throw new ArgumentException(handle.HandleDescriptor + " is not supported");
+    }
+
+    public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+    {
+        if (imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor)
+            return CompositionGpuImportedImageSynchronizationCapabilities.Semaphores;
+        return default;
+    }
+
+    public byte[] DeviceLuid { get; }
+    public byte[] DeviceUuid { get; }
+
+    unsafe class ExternalSemaphore : IGlExternalSemaphore
+    {
+        private readonly IGlContext _context;
+        private readonly ExternalObjectsInterface _ext;
+        private uint _semaphore;
+
+        public ExternalSemaphore(IGlContext context, ExternalObjectsInterface ext, uint semaphore)
+        {
+            _context = context;
+            _ext = ext;
+            _semaphore = semaphore;
+        }
+
+        public void Dispose()
+        {
+            if(_context.IsLost)
+                return;
+            using (_context.EnsureCurrent())
+                _ext.DeleteSemaphoresEXT(1, ref _semaphore);
+            _semaphore = 0;
+        }
+
+        public void WaitSemaphore(IGlExternalImageTexture texture)
+        {
+            var tex = (ExternalImageTexture)texture;
+            var texId = tex.TextureId;
+            var srcLayout = GL_LAYOUT_TRANSFER_SRC_EXT;
+            _ext.WaitSemaphoreEXT(_semaphore, 0, null, 1, &texId, &srcLayout);
+        }
+        
+        public void SignalSemaphore(IGlExternalImageTexture texture)
+        {
+            var tex = (ExternalImageTexture)texture;
+            var texId = tex.TextureId;
+            var dstLayout = 0;
+            _ext.SignalSemaphoreEXT(_semaphore, 0, null, 1, &texId, &dstLayout);
+        }
+    }
+
+    class ExternalImageTexture : IGlExternalImageTexture
+    {
+        private readonly IGlContext _context;
+        private readonly ExternalObjectsInterface _ext;
+        private uint _objectId;
+
+        public ExternalImageTexture(IGlContext context,
+            PlatformGraphicsExternalImageProperties properties,
+            ExternalObjectsInterface ext, uint objectId, int textureId)
+        {
+            Properties = properties;
+            TextureId = textureId;
+            _context = context;
+            _ext = ext;
+            _objectId = objectId;
+        }
+        
+        public void Dispose()
+        {
+            if(_context.IsLost)
+                return;
+            using (_context.EnsureCurrent())
+            {
+                _context.GlInterface.DeleteTexture(TextureId);
+                _ext.DeleteMemoryObjectsEXT(1, ref _objectId);
+                _objectId = 0;
+            }
+        }
+
+        public void AcquireKeyedMutex(uint key) => throw new NotSupportedException();
+
+        public void ReleaseKeyedMutex(uint key) => throw new NotSupportedException();
+
+        public int TextureId { get; }
+        public int InternalFormat => GL_RGBA8;
+        public PlatformGraphicsExternalImageProperties Properties { get; }
+    }
+}

+ 7 - 7
src/Avalonia.OpenGL/GlConsts.cs

@@ -534,7 +534,7 @@ namespace Avalonia.OpenGL
 //        public const int GL_MAX_ELEMENTS_VERTICES = 0x80E8;
 //        public const int GL_MAX_ELEMENTS_INDICES = 0x80E9;
 //        public const int GL_BGR = 0x80E0;
-//        public const int GL_BGRA = 0x80E1;
+        public const int GL_BGRA = 0x80E1;
 //        public const int GL_UNSIGNED_BYTE_3_3_2 = 0x8032;
 //        public const int GL_UNSIGNED_BYTE_2_3_3_REV = 0x8362;
 //        public const int GL_UNSIGNED_SHORT_5_6_5 = 0x8363;
@@ -3372,16 +3372,16 @@ namespace Avalonia.OpenGL
 //        public const int GL_TILING_TYPES_EXT = 0x9583;
 //        public const int GL_OPTIMAL_TILING_EXT = 0x9584;
 //        public const int GL_LINEAR_TILING_EXT = 0x9585;
-//        public const int GL_NUM_DEVICE_UUIDS_EXT = 0x9596;
-//        public const int GL_DEVICE_UUID_EXT = 0x9597;
+        internal const int GL_NUM_DEVICE_UUIDS_EXT = 0x9596;
+        internal const int GL_DEVICE_UUID_EXT = 0x9597;
 //        public const int GL_DRIVER_UUID_EXT = 0x9598;
 //        public const int GL_UUID_SIZE_EXT = 16;
 //        public const int GL_EXT_memory_object_fd = 1;
-//        public const int GL_HANDLE_TYPE_OPAQUE_FD_EXT = 0x9586;
+        internal const int GL_HANDLE_TYPE_OPAQUE_FD_EXT = 0x9586;
 //        public const int GL_EXT_memory_object_win32 = 1;
 //        public const int GL_HANDLE_TYPE_OPAQUE_WIN32_EXT = 0x9587;
 //        public const int GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT = 0x9588;
-//        public const int GL_DEVICE_LUID_EXT = 0x9599;
+        public const int GL_DEVICE_LUID_EXT = 0x9599;
 //        public const int GL_DEVICE_NODE_MASK_EXT = 0x959A;
 //        public const int GL_LUID_SIZE_EXT = 8;
 //        public const int GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT = 0x9589;
@@ -3483,11 +3483,11 @@ namespace Avalonia.OpenGL
 //        public const int GL_SECONDARY_COLOR_ARRAY_EXT = 0x845E;
 //        public const int GL_EXT_semaphore = 1;
 //        public const int GL_LAYOUT_GENERAL_EXT = 0x958D;
-//        public const int GL_LAYOUT_COLOR_ATTACHMENT_EXT = 0x958E;
+        internal const int GL_LAYOUT_COLOR_ATTACHMENT_EXT = 0x958E;
 //        public const int GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT = 0x958F;
 //        public const int GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT = 0x9590;
 //        public const int GL_LAYOUT_SHADER_READ_ONLY_EXT = 0x9591;
-//        public const int GL_LAYOUT_TRANSFER_SRC_EXT = 0x9592;
+        internal const int GL_LAYOUT_TRANSFER_SRC_EXT = 0x9592;
 //        public const int GL_LAYOUT_TRANSFER_DST_EXT = 0x9593;
 //        public const int GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT = 0x9530;
 //        public const int GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT = 0x9531;

+ 50 - 0
src/Avalonia.OpenGL/IGlContextExternalObjectsFeature.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+
+namespace Avalonia.OpenGL;
+
+public interface IGlContextExternalObjectsFeature
+{
+    IReadOnlyList<string> SupportedImportableExternalImageTypes { get; }
+    IReadOnlyList<string> SupportedExportableExternalImageTypes { get; }
+    IReadOnlyList<string> SupportedImportableExternalSemaphoreTypes { get; }
+    IReadOnlyList<string> SupportedExportableExternalSemaphoreTypes { get; }
+    IReadOnlyList<PlatformGraphicsExternalImageFormat> GetSupportedFormatsForExternalMemoryType(string type);
+
+    IGlExportableExternalImageTexture CreateImage(string type,PixelSize size, PlatformGraphicsExternalImageFormat format);
+
+    IGlExportableExternalImageTexture CreateSemaphore(string type);
+    IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties);
+    IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle);
+    CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType);
+    public byte[]? DeviceLuid { get; }
+    public byte[]? DeviceUuid { get; }
+}
+
+public interface IGlExternalSemaphore : IDisposable
+{
+    void WaitSemaphore(IGlExternalImageTexture texture);
+    void SignalSemaphore(IGlExternalImageTexture texture);
+}
+
+public interface IGlExportableExternalSemaphore : IGlExternalSemaphore
+{
+    IPlatformHandle GetHandle();
+}
+
+public interface IGlExternalImageTexture : IDisposable
+{
+    void AcquireKeyedMutex(uint key);
+    void ReleaseKeyedMutex(uint key);
+    int TextureId { get; }
+    int InternalFormat { get; }
+    
+    PlatformGraphicsExternalImageProperties Properties { get; }
+}
+
+public interface IGlExportableExternalImageTexture : IGlExternalImageTexture
+{
+    IPlatformHandle GetHandle();
+}

+ 10 - 3
src/Avalonia.OpenGL/IOpenGlTextureSharingRenderInterfaceContextFeature.cs

@@ -1,6 +1,6 @@
+using System;
 using System.Collections.Generic;
-using Avalonia.OpenGL.Imaging;
-using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
 
 namespace Avalonia.OpenGL
 {
@@ -8,6 +8,13 @@ namespace Avalonia.OpenGL
     {
         bool CanCreateSharedContext { get; }
         IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null);
-        IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
+        ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size);
+    }
+
+    public interface ICompositionImportableOpenGlSharedTexture : ICompositionImportableSharedGpuContextImage
+    {
+        int TextureId { get; }
+        int InternalFormat { get; }
+        PixelSize Size { get; }
     }
 }

+ 8 - 0
src/Avalonia.OpenGL/IPlatformGraphicsOpenGlContextFactory.cs

@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace Avalonia.OpenGL;
+
+public interface IPlatformGraphicsOpenGlContextFactory
+{
+    IGlContext CreateContext(IEnumerable<GlVersion>? versions);
+}

+ 0 - 19
src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs

@@ -1,19 +0,0 @@
-using System;
-using Avalonia.Metadata;
-using Avalonia.Platform;
-
-namespace Avalonia.OpenGL.Imaging
-{
-    [Unstable]
-    public interface IOpenGlBitmapImpl : IBitmapImpl
-    {
-        IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback);
-        bool SupportsContext(IGlContext context);
-    }
-
-    [Unstable]
-    public interface IOpenGlBitmapAttachment : IDisposable
-    {
-        void Present();
-    }
-}

+ 0 - 40
src/Avalonia.OpenGL/Imaging/OpenGlBitmap.cs

@@ -1,40 +0,0 @@
-using System;
-using Avalonia.Media;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform;
-using Avalonia.Threading;
-
-namespace Avalonia.OpenGL.Imaging
-{
-    public class OpenGlBitmap : Bitmap, IAffectsRender
-    {
-        private IOpenGlBitmapImpl _impl;
-
-        public OpenGlBitmap(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
-            PixelSize size, Vector dpi) 
-            : base(CreateOrThrow(feature, size, dpi))
-        {
-            _impl = (IOpenGlBitmapImpl)PlatformImpl.Item;
-        }
-
-        static IOpenGlBitmapImpl CreateOrThrow(IOpenGlTextureSharingRenderInterfaceContextFeature feature,
-            PixelSize size, Vector dpi) => feature.CreateOpenGlBitmap(size, dpi);
-
-        public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context) =>
-            _impl.CreateFramebufferAttachment(context, SetIsDirty);
-
-        public bool SupportsContext(IGlContext context) => _impl.SupportsContext(context);
-        
-        void SetIsDirty()
-        {
-            if (Dispatcher.UIThread.CheckAccess())
-                CallInvalidated();
-            else
-                Dispatcher.UIThread.Post(CallInvalidated);
-        }
-
-        private void CallInvalidated() => Invalidated?.Invoke(this, EventArgs.Empty);
-
-        public event EventHandler Invalidated;
-    }
-}

+ 3 - 0
src/Avalonia.OpenGL/OpenGlException.cs

@@ -27,6 +27,9 @@ namespace Avalonia.OpenGL
             return GetFormattedException(funcName, (GlErrors)err, err);
         }
 
+        public static OpenGlException GetFormattedException(string funcName, int errorCode) =>
+            GetFormattedException(funcName, (GlErrors)errorCode, errorCode);
+
         public static OpenGlException GetFormattedEglException(string funcName, int errorCode) =>
             GetFormattedException(funcName, (EglErrors)errorCode,errorCode);
 

+ 12 - 1
src/Avalonia.X11/Glx/GlxContext.cs

@@ -3,6 +3,8 @@ using System.Collections.Generic;
 using System.Reactive.Disposables;
 using System.Threading;
 using Avalonia.OpenGL;
+using Avalonia.OpenGL.Features;
+
 namespace Avalonia.X11.Glx
 {
     class GlxContext : IGlContext
@@ -14,6 +16,7 @@ namespace Avalonia.X11.Glx
         private readonly IntPtr _defaultXid;
         private readonly bool _ownsPBuffer;
         private readonly object _lock = new object();
+        private ExternalObjectsOpenGlExtensionFeature? _externalObjects;
 
         public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display,
             GlxContext sharedWith,
@@ -32,7 +35,10 @@ namespace Avalonia.X11.Glx
             SampleCount = sampleCount;
             StencilSize = stencilSize;
             using (MakeCurrent())
+            {
                 GlInterface = new GlInterface(version, GlxInterface.SafeGetProcAddress);
+                _externalObjects = ExternalObjectsOpenGlExtensionFeature.TryCreate(this);
+            }
         }
         
         public GlxDisplay Display { get; }
@@ -123,6 +129,11 @@ namespace Avalonia.X11.Glx
                 Glx.DestroyPbuffer(_x11.Display, _defaultXid);
         }
 
-        public object TryGetFeature(Type featureType) => null;
+        public object TryGetFeature(Type featureType)
+        {
+            if (featureType == typeof(IGlContextExternalObjectsFeature))
+                return _externalObjects;
+            return null;
+        }
     }
 }

+ 0 - 7
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@@ -1,7 +1,5 @@
 using System;
 using System.Collections.Generic;
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
 using Avalonia.Platform;
 using SkiaSharp;
 
@@ -33,9 +31,4 @@ namespace Avalonia.Skia
         bool CanBlit { get; }
         void Blit(SKCanvas canvas);
     }
-
-    public interface IOpenGlAwareSkiaGpu : ISkiaGpu
-    {
-        IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi);
-    }
 }

+ 191 - 0
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaExternalObjectsFeature.cs

@@ -0,0 +1,191 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using Avalonia.OpenGL;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using SkiaSharp;
+
+namespace Avalonia.Skia;
+
+internal class GlSkiaExternalObjectsFeature : IExternalObjectsRenderInterfaceContextFeature
+{
+    private readonly GlSkiaGpu _gpu;
+    private readonly IGlContextExternalObjectsFeature? _feature;
+
+    public GlSkiaExternalObjectsFeature(GlSkiaGpu gpu, IGlContextExternalObjectsFeature? feature)
+    {
+        _gpu = gpu;
+        _feature = feature;
+    }
+
+    public IReadOnlyList<string> SupportedImageHandleTypes => _feature?.SupportedImportableExternalImageTypes
+                                                              ?? Array.Empty<string>();
+    public IReadOnlyList<string> SupportedSemaphoreTypes => _feature?.SupportedImportableExternalSemaphoreTypes
+                                                            ?? Array.Empty<string>();
+
+    public IPlatformRenderInterfaceImportedImage ImportImage(IPlatformHandle handle,
+        PlatformGraphicsExternalImageProperties properties)
+    {
+        if (_feature == null)
+            throw new NotSupportedException("Importing this platform handle is not supported");
+        using (_gpu.EnsureCurrent())
+        {
+            var image = _feature.ImportImage(handle, properties);
+            return new GlSkiaImportedImage(_gpu, image);
+        }
+    }
+
+    public IPlatformRenderInterfaceImportedImage ImportImage(ICompositionImportableSharedGpuContextImage image)
+    {
+        var img = (GlSkiaSharedTextureForComposition)image;
+        if (!img.Context.IsSharedWith(_gpu.GlContext))
+            throw new InvalidOperationException("Contexts do not belong to the same share group");
+        
+        return new GlSkiaImportedImage(_gpu, img);
+    }
+
+    public IPlatformRenderInterfaceImportedSemaphore ImportSemaphore(IPlatformHandle handle)
+    {
+        if (_feature == null)
+            throw new NotSupportedException("Importing this platform handle is not supported");
+        using (_gpu.EnsureCurrent())
+        {
+            var semaphore = _feature.ImportSemaphore(handle);
+            return new GlSkiaImportedSemaphore(_gpu, semaphore);
+        }
+    }
+
+    public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+        => _feature?.GetSynchronizationCapabilities(imageHandleType) ?? default;
+
+    public byte[]? DeviceUuid => _feature?.DeviceUuid;
+    public byte[]? DeviceLuid => _feature?.DeviceLuid;
+}
+
+internal class GlSkiaImportedSemaphore : IPlatformRenderInterfaceImportedSemaphore
+{
+    private readonly GlSkiaGpu _gpu;
+    public IGlExternalSemaphore Semaphore { get; }
+
+    public GlSkiaImportedSemaphore(GlSkiaGpu gpu, IGlExternalSemaphore semaphore)
+    {
+        _gpu = gpu;
+        Semaphore = semaphore;
+    }
+
+    public void Dispose() => Semaphore.Dispose();
+}
+
+internal class GlSkiaImportedImage : IPlatformRenderInterfaceImportedImage
+{
+    private readonly GlSkiaSharedTextureForComposition? _sharedTexture;
+    private readonly GlSkiaGpu _gpu;
+    private readonly IGlExternalImageTexture? _image;
+
+    public GlSkiaImportedImage(GlSkiaGpu gpu, IGlExternalImageTexture image)
+    {
+        _gpu = gpu;
+        _image = image;
+    }
+
+    public GlSkiaImportedImage(GlSkiaGpu gpu, GlSkiaSharedTextureForComposition sharedTexture)
+    {
+        _gpu = gpu;
+        _sharedTexture = sharedTexture;
+    }
+
+    public void Dispose()
+    {
+        _image?.Dispose();
+        _sharedTexture?.Dispose(_gpu.GlContext);
+    }
+
+    SKColorType ConvertColorType(PlatformGraphicsExternalImageFormat format) =>
+        format switch
+        {
+            PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm => SKColorType.Bgra8888,
+            PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm => SKColorType.Rgba8888,
+            _ => SKColorType.Rgba8888
+        };
+
+    SKSurface? TryCreateSurface(int textureId, int format, int width, int height, bool topLeft)
+    {
+        var origin = topLeft ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft; 
+        using var texture = new GRBackendTexture(width, height, false,
+            new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId, (uint)format));
+        var surf = SKSurface.Create(_gpu.GrContext, texture, origin, SKColorType.Rgba8888);
+        if (surf != null)
+            return surf;
+        
+        using var unformatted = new GRBackendTexture(width, height, false,
+            new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId));
+        
+        return SKSurface.Create(_gpu.GrContext, unformatted, origin, SKColorType.Rgba8888);
+    }
+    
+    IBitmapImpl TakeSnapshot()
+    {
+        var width = _image?.Properties.Width ?? _sharedTexture!.Size.Width;
+        var height = _image?.Properties.Height ?? _sharedTexture!.Size.Height;
+        var internalFormat = _image?.InternalFormat ?? _sharedTexture!.InternalFormat;
+        var textureId = _image?.TextureId ?? _sharedTexture!.TextureId;
+        var topLeft = _image?.Properties.TopLeftOrigin ?? false;
+        
+        using var texture = new GRBackendTexture(width, height, false,
+            new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)textureId, (uint)internalFormat));
+        
+        IBitmapImpl rv;
+        using (var surf = TryCreateSurface(textureId, internalFormat, width, height, topLeft))
+        {
+            if (surf == null)
+                throw new OpenGlException("Unable to consume provided texture");
+            rv = new ImmutableBitmap(surf.Snapshot());
+        }
+
+        _gpu.GrContext.Flush();
+        _gpu.GlContext.GlInterface.Flush();
+        return rv;
+    }
+    
+    public IBitmapImpl SnapshotWithKeyedMutex(uint acquireIndex, uint releaseIndex)
+    {
+        using (_gpu.EnsureCurrent())
+        {
+            _image.AcquireKeyedMutex(acquireIndex);
+            try
+            {
+                return TakeSnapshot();
+            }
+            finally
+            {
+                _image.ReleaseKeyedMutex(releaseIndex);
+            }
+        }
+    }
+
+    public IBitmapImpl SnapshotWithSemaphores(IPlatformRenderInterfaceImportedSemaphore waitForSemaphore,
+        IPlatformRenderInterfaceImportedSemaphore signalSemaphore)
+    {
+        var wait = (GlSkiaImportedSemaphore)waitForSemaphore;
+        var signal = (GlSkiaImportedSemaphore)signalSemaphore;
+        using (_gpu.EnsureCurrent())
+        {
+            wait.Semaphore.WaitSemaphore(_image);
+            try
+            {
+                return TakeSnapshot();
+            }
+            finally
+            {
+                signal.Semaphore.SignalSemaphore(_image);
+            }
+        }
+    }
+
+    public IBitmapImpl SnapshotWithAutomaticSync()
+    {
+        using (_gpu.EnsureCurrent())
+            return TakeSnapshot();
+    }
+}

+ 38 - 4
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@@ -3,19 +3,22 @@ using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using Avalonia.Logging;
 using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
 using Avalonia.OpenGL.Surfaces;
 using Avalonia.Platform;
 using SkiaSharp;
+using static Avalonia.OpenGL.GlConsts;
 
 namespace Avalonia.Skia
 {
-    class GlSkiaGpu : IOpenGlAwareSkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
+    class GlSkiaGpu : ISkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
     {
         private GRContext _grContext;
         private IGlContext _glContext;
+        public GRContext GrContext => _grContext;
+        public IGlContext GlContext => _glContext;
         private List<Action> _postDisposeCallbacks = new();
         private bool? _canCreateSurfaces;
+        private IExternalObjectsRenderInterfaceContextFeature? _externalObjectsFeature;
 
         public GlSkiaGpu(IGlContext context, long? maxResourceBytes)
         {
@@ -32,6 +35,9 @@ namespace Avalonia.Skia
                         _grContext.SetResourceCacheLimit(maxResourceBytes.Value);
                     }
                 }
+
+                context.TryGetFeature<IGlContextExternalObjectsFeature>(out var externalObjects);
+                _externalObjectsFeature = new GlSkiaExternalObjectsFeature(this, externalObjects);
             }
         }
 
@@ -103,8 +109,34 @@ namespace Avalonia.Skia
         public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
             _glContext.CreateSharedContext(preferredVersions);
 
-        public IOpenGlBitmapImpl CreateOpenGlBitmap(PixelSize size, Vector dpi) => new GlOpenGlBitmapImpl(_glContext, size, dpi);
-        
+        public ICompositionImportableOpenGlSharedTexture CreateSharedTextureForComposition(IGlContext context, PixelSize size)
+        {
+            if (!context.IsSharedWith(_glContext))
+                throw new InvalidOperationException("Contexts do not belong to the same share group");
+            
+            using (context.EnsureCurrent())
+            {
+                var gl = context.GlInterface;
+                gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out int oldTexture);
+                var tex = gl.GenTexture();
+
+                var format = context.Version.Type == GlProfileType.OpenGLES && context.Version.Major == 2
+                    ? GL_RGBA
+                    : GL_RGBA8;
+                
+                gl.BindTexture(GL_TEXTURE_2D, tex);
+                gl.TexImage2D(GL_TEXTURE_2D, 0,
+                    format, size.Width, size.Height,
+                    0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
+
+                gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+                gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+                gl.BindTexture(GL_TEXTURE_2D, oldTexture);
+                
+                return new GlSkiaSharedTextureForComposition(context, tex, format, size);
+            }
+        }
+
         public void Dispose()
         {
             if (_glContext.IsLost)
@@ -125,6 +157,8 @@ namespace Avalonia.Skia
         {
             if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature))
                 return this;
+            if (featureType == typeof(IExternalObjectsRenderInterfaceContextFeature))
+                return _externalObjectsFeature;
             return null;
         }
         

+ 44 - 0
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaSharedTextureForComposition.cs

@@ -0,0 +1,44 @@
+using Avalonia.OpenGL;
+
+namespace Avalonia.Skia;
+
+internal class GlSkiaSharedTextureForComposition : ICompositionImportableOpenGlSharedTexture
+{
+    public IGlContext Context { get; }
+    private readonly object _lock = new();
+
+    public GlSkiaSharedTextureForComposition(IGlContext context, int textureId, int internalFormat, PixelSize size)
+    {
+        Context = context;
+        TextureId = textureId;
+        InternalFormat = internalFormat;
+        Size = size;
+    }
+    public void Dispose(IGlContext context)
+    {
+        lock (_lock)
+        {
+            if(TextureId == 0)
+                return;
+            try
+            {
+                using (context.EnsureCurrent())
+                    context.GlInterface.DeleteTexture(TextureId);
+            }
+            catch
+            {
+                // Ignore
+            }
+            
+            TextureId = 0;
+        }
+    }
+
+    public int TextureId { get; private set; }
+    public int InternalFormat { get; }
+    public PixelSize Size { get; }
+    public void Dispose()
+    {
+        Dispose(Context);
+    }
+}

+ 0 - 210
src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs

@@ -1,210 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
-using Avalonia.Utilities;
-using SkiaSharp;
-using static Avalonia.OpenGL.GlConsts;
-
-namespace Avalonia.Skia
-{
-    class GlOpenGlBitmapImpl : IOpenGlBitmapImpl, IDrawableBitmapImpl
-    {
-        private readonly IGlContext _context;
-        private readonly object _lock = new object();
-        private IGlPresentableOpenGlSurface _surface;
-
-        public GlOpenGlBitmapImpl(IGlContext context, PixelSize pixelSize, Vector dpi)
-        {
-            _context = context;
-            PixelSize = pixelSize;
-            Dpi = dpi;
-        }
-
-        public Vector Dpi { get; }
-        public PixelSize PixelSize { get; }
-        public int Version { get; private set; }
-        public void Save(string fileName, int? quality = null) => throw new NotSupportedException();
-
-        public void Save(Stream stream, int? quality = null) => throw new NotSupportedException();
-
-        public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
-        {
-            lock (_lock)
-            {
-                if (_surface == null)
-                    return;
-                using (_surface.Lock())
-                {
-                    using (var backendTexture = new GRBackendTexture(PixelSize.Width, PixelSize.Height, false,
-                        new GRGlTextureInfo(
-                            GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(),
-                            (uint)_surface.InternalFormat)))
-                    using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft,
-                        SKColorType.Rgba8888, new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)))
-                    {
-                        // Again, silently ignore, if something went wrong it's not our fault
-                        if (surface == null)
-                            return;
-
-                        using (var snapshot = surface.Snapshot())
-                            context.Canvas.DrawImage(snapshot, sourceRect, destRect, paint);
-                    }
-
-                }
-            }
-        }
-
-        public IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback)
-        {
-            if (!SupportsContext(context))
-                throw new OpenGlException("Context is not supported for texture sharing");
-            return new SharedOpenGlBitmapAttachment(this, context, presentCallback);
-        }
-
-        public bool SupportsContext(IGlContext context)
-        {
-            // TODO: negotiated platform surface sharing
-            return _context.IsSharedWith(context);
-        }
-
-        public void Dispose()
-        {
-
-        }
-
-        internal void Present(IGlPresentableOpenGlSurface surface)
-        {
-            lock (_lock)
-            {
-                _surface = surface;
-            }
-        }
-    }
-
-    interface IGlPresentableOpenGlSurface : IDisposable
-    {
-        int GetTextureId();
-        int InternalFormat { get; }
-        IDisposable Lock();
-    }
-
-    class SharedOpenGlBitmapAttachment : IOpenGlBitmapAttachment, IGlPresentableOpenGlSurface
-    {
-        private readonly GlOpenGlBitmapImpl _bitmap;
-        private readonly IGlContext _context;
-        private readonly Action _presentCallback;
-        private readonly int _fbo;
-        private readonly int _texture;
-        private readonly int _frontBuffer;
-        private bool _disposed;
-        private readonly DisposableLock _lock = new DisposableLock();
-
-        public unsafe SharedOpenGlBitmapAttachment(GlOpenGlBitmapImpl bitmap, IGlContext context, Action presentCallback)
-        {
-            _bitmap = bitmap;
-            _context = context;
-            _presentCallback = presentCallback;
-            using (_context.EnsureCurrent())
-            {
-                var glVersion = _context.Version;
-                InternalFormat = glVersion.Type == GlProfileType.OpenGLES && glVersion.Major == 2 
-                    ? GL_RGBA 
-                    : GL_RGBA8;
-                
-                _context.GlInterface.GetIntegerv(GL_FRAMEBUFFER_BINDING, out _fbo);
-                if (_fbo == 0)
-                    throw new OpenGlException("Current FBO is 0");
-
-                {
-                    var gl = _context.GlInterface;
-                    
-                    Span<int> textures = stackalloc int[2];
-                    fixed (int* ptex = textures)
-                        gl.GenTextures(2, ptex);
-                    _texture = textures[0];
-                    _frontBuffer = textures[1];
-
-                    gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
-                    foreach (var t in textures)
-                    {
-                        gl.BindTexture(GL_TEXTURE_2D, t);
-                        gl.TexImage2D(GL_TEXTURE_2D, 0,
-                            InternalFormat,
-                            _bitmap.PixelSize.Width, _bitmap.PixelSize.Height,
-                            0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero);
-
-                        gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-                        gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-                    }
-
-                    gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
-                    gl.BindTexture(GL_TEXTURE_2D, oldTexture);
-                }
-            }
-        }
-
-        public void Present()
-        {
-            using (_context.EnsureCurrent())
-            {
-                if (_disposed)
-                    throw new ObjectDisposedException(nameof(SharedOpenGlBitmapAttachment));
-                
-                var gl = _context.GlInterface;
-               
-                gl.Finish();
-                using (Lock())
-                {
-                    gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFbo);
-                    gl.GetIntegerv(GL_TEXTURE_BINDING_2D, out var oldTexture);
-                    gl.GetIntegerv(GL_ACTIVE_TEXTURE, out var oldActive);
-                    
-                    gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo);
-                    gl.ActiveTexture(GL_TEXTURE0);
-                    gl.BindTexture(GL_TEXTURE_2D, _frontBuffer);
-
-                    gl.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, _bitmap.PixelSize.Width,
-                        _bitmap.PixelSize.Height);
-
-                    gl.BindFramebuffer(GL_FRAMEBUFFER, oldFbo);
-                    gl.ActiveTexture(oldActive);
-                    gl.BindTexture(GL_TEXTURE_2D, oldTexture);
-                    
-                    gl.Finish();
-                }
-            }
-            
-            _bitmap.Present(this);
-            _presentCallback();
-        }
-
-        public unsafe void Dispose()
-        {
-            var gl = _context.GlInterface;
-            _bitmap.Present(null);
-            
-            if(_disposed)
-                return;
-            using (_context.MakeCurrent())
-            using (Lock())
-            {
-                if(_disposed)
-                    return;
-                _disposed = true;
-                var ptex = stackalloc[] { _texture, _frontBuffer };
-                gl.DeleteTextures(2, ptex);
-            }
-        }
-
-        int IGlPresentableOpenGlSurface.GetTextureId()
-        {
-            return _frontBuffer;
-        }
-
-        public int InternalFormat { get; }
-
-        public IDisposable Lock() => _lock.Lock();
-    }
-}

+ 7 - 0
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@@ -37,6 +37,13 @@ namespace Avalonia.Skia
             }
         }
 
+        public ImmutableBitmap(SKImage image)
+        {
+            _image = image;
+            PixelSize = new PixelSize(image.Width, image.Height);
+            Dpi = new Vector(96, 96);
+        }
+
         public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
         {
             SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888);

+ 0 - 5
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -2,13 +2,8 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
-using System.Threading;
-
-using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Media;
 using Avalonia.OpenGL;
-using Avalonia.OpenGL.Imaging;
 using Avalonia.Platform;
 using Avalonia.Media.Imaging;
 using SkiaSharp;

+ 3 - 1
src/Skia/Avalonia.Skia/SkiaBackendContext.cs

@@ -43,5 +43,7 @@ internal class SkiaContext : IPlatformRenderInterfaceContext
             "Don't know how to create a Skia render target from any of provided surfaces");
     }
 
+    public bool IsLost => _gpu.IsLost;
+
     public object TryGetFeature(Type featureType) => _gpu?.TryGetFeature(featureType);
-}
+}

+ 1 - 0
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@@ -238,6 +238,7 @@ namespace Avalonia.Direct2D1
             }
 
             public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => _platform.CreateRenderTarget(surfaces);
+            public bool IsLost => false;
         }
 
         public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) =>

+ 38 - 0
src/Windows/Avalonia.Win32/DirectX/DirectXEnums.cs

@@ -52,6 +52,44 @@ namespace Avalonia.Win32.DirectX
 
         D3D11_USAGE_STAGING = 3,
     }
+    
+    [Flags]
+    internal enum D3D11_RESOURCE_MISC_FLAG
+    {
+        D3D11_RESOURCE_MISC_GENERATE_MIPS                   = 0x00000001,
+        D3D11_RESOURCE_MISC_SHARED                          = 0x00000002,
+        D3D11_RESOURCE_MISC_TEXTURECUBE                     = 0x00000004,
+        D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS               = 0x00000010,
+        D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS          = 0x00000020,
+        D3D11_RESOURCE_MISC_BUFFER_STRUCTURED               = 0x00000040,
+        D3D11_RESOURCE_MISC_RESOURCE_CLAMP                  = 0x00000080,
+        D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX               = 0x00000100,
+        D3D11_RESOURCE_MISC_GDI_COMPATIBLE                  = 0x00000200,
+        D3D11_RESOURCE_MISC_SHARED_NTHANDLE                 = 0x00000800,
+        D3D11_RESOURCE_MISC_RESTRICTED_CONTENT              = 0x00001000,
+        D3D11_RESOURCE_MISC_RESTRICT_SHARED_RESOURCE        = 0x00002000,
+        D3D11_RESOURCE_MISC_RESTRICT_SHARED_RESOURCE_DRIVER = 0x00004000,
+        D3D11_RESOURCE_MISC_GUARDED                         = 0x00008000,
+        D3D11_RESOURCE_MISC_TILE_POOL                       = 0x00020000,
+        D3D11_RESOURCE_MISC_TILED                           = 0x00040000,
+        D3D11_RESOURCE_MISC_HW_PROTECTED                    = 0x00080000,
+    }
+    
+    [Flags]
+    internal enum D3D11_BIND_FLAG
+    {
+        D3D11_BIND_VERTEX_BUFFER = 0x00000001,
+        D3D11_BIND_INDEX_BUFFER = 0x00000002,
+        D3D11_BIND_CONSTANT_BUFFER = 0x00000004,
+        D3D11_BIND_SHADER_RESOURCE = 0x00000008,
+        D3D11_BIND_STREAM_OUTPUT = 0x00000010,
+        D3D11_BIND_RENDER_TARGET = 0x00000020,
+        D3D11_BIND_DEPTH_STENCIL = 0x00000040,
+        D3D11_BIND_UNORDERED_ACCESS = 0x00000080,
+        D3D11_BIND_DECODER = 0x00000200,
+        D3D11_BIND_VIDEO_ENCODER = 0x00000400,
+    }
+    
     internal enum DXGI_SWAP_EFFECT
     {
         DXGI_SWAP_EFFECT_DISCARD = 0,

+ 2 - 2
src/Windows/Avalonia.Win32/DirectX/DirectXStructs.cs

@@ -282,11 +282,11 @@ namespace Avalonia.Win32.DirectX
 
         public D3D11_USAGE Usage;
 
-        public uint BindFlags;
+        public D3D11_BIND_FLAG BindFlags;
 
         public uint CPUAccessFlags;
 
-        public uint MiscFlags;
+        public D3D11_RESOURCE_MISC_FLAG MiscFlags;
     }
 #nullable restore
 }

+ 30 - 3
src/Windows/Avalonia.Win32/DirectX/directx.idl

@@ -10,6 +10,7 @@
 @clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE
 @clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT
 @clr-map HWND IntPtr
+@clr-map HANDLE IntPtr
 @clr-map BOOL int
 @clr-map DWORD int
 @clr-map SIZE_T IntPtr
@@ -259,10 +260,28 @@ interface IDXGISurface : IDXGIDeviceSubObject
     HRESULT Unmap();
 }
 
+[uuid( 035f3ab4-482e-4e50-b41f-8a7f8bd8960b)]
+interface IDXGIResource : IDXGIDeviceSubObject
+{
+    HRESULT GetSharedHandle( [out, annotation("_Out_")] HANDLE * pSharedHandle );
+    HRESULT GetUsage( [out] DXGI_USAGE * pUsage );
+    HRESULT SetEvictionPriority( [in] UINT EvictionPriority );
+    HRESULT GetEvictionPriority( [out, retval, annotation("_Out_")] UINT* pEvictionPriority );
+};
+
+
+[ uuid( 9d8e1289-d7b3-465f-8126-250e349af85d)]
+interface IDXGIKeyedMutex :
+    IDXGIDeviceSubObject
+{
+    HRESULT AcquireSync( [in] UINT64 Key, [in] uint dwMilliseconds);
+    HRESULT ReleaseSync( [in] UINT64 Key);
+};
+
 [uuid(770aae78-f26f-4dba-a829-253c83d1b387)]
 interface IDXGIFactory1 : IDXGIFactory
 {
-    HRESULT EnumAdapters1([in] UINT Adapter, [out, annotation("_COM_Outptr_")] IDXGIAdapter1** ppAdapter);
+    int EnumAdapters1([in] UINT Adapter, [out] void** ppAdapter);
     BOOL IsCurrent();
 }
 
@@ -339,9 +358,9 @@ interface ID3D11Device : IUnknown
         IntPtr pInitialData,
         [out, retval] IUnknown**  ppTexture1D );
     HRESULT CreateTexture2D(
-        IntPtr  pDesc,
+        D3D11_TEXTURE2D_DESC*  pDesc,
         IntPtr pInitialData,
-        [out, retval] IUnknown**  ppTexture2D );
+        [out, retval] ID3D11Texture2D**  ppTexture2D );
     HRESULT CreateTexture3D(
         IntPtr pDesc,
         IntPtr pInitialData,
@@ -481,3 +500,11 @@ interface ID3D11Device : IUnknown
     HRESULT SetExceptionMode( UINT RaiseFlags );
     UINT GetExceptionMode();
 }
+
+
+[uuid( 6f15aaf2-d208-4e89-9ab4-489535d34f9c)]
+interface ID3D11Texture2D : IUnknown
+{
+    // Just a marker interface for now
+};
+

+ 107 - 0
src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalD3D11Texture2D.cs

@@ -0,0 +1,107 @@
+#nullable enable
+using System;
+using System.Threading;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Angle;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Platform;
+using Avalonia.Win32.DirectX;
+using MicroCom.Runtime;
+using static Avalonia.OpenGL.Egl.EglConsts;
+using static Avalonia.OpenGL.GlConsts;
+
+namespace Avalonia.Win32.OpenGl.Angle;
+
+internal class AngleExternalMemoryD3D11Texture2D : IGlExternalImageTexture
+{
+    private readonly EglContext _context;
+    private ID3D11Texture2D _texture2D;
+    private EglSurface _eglSurface;
+    private IDXGIKeyedMutex _mutex;
+
+    public unsafe AngleExternalMemoryD3D11Texture2D(EglContext context, ID3D11Texture2D texture2D, PlatformGraphicsExternalImageProperties props)
+    {
+        _context = context;
+        _texture2D = texture2D.CloneReference();
+        _mutex = _texture2D.QueryInterface<IDXGIKeyedMutex>();
+        Properties = props;
+
+        InternalFormat = GL_RGBA8;
+
+        _eglSurface = _context.Display.CreatePBufferFromClientBuffer(EGL_D3D_TEXTURE_ANGLE, texture2D.GetNativeIntPtr(),
+            new[]
+            {
+                EGL_WIDTH, props.Width, EGL_HEIGHT, props.Height, EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
+                EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_RGBA, EGL_NONE, EGL_NONE,
+                EGL_NONE
+            });
+        
+        var gl = _context.GlInterface;
+        int temp = 0;
+        gl.GenTextures(1, &temp);
+        TextureId = temp;
+        gl.BindTexture(GlConsts.GL_TEXTURE_2D, TextureId);
+
+        if (_context.Display.EglInterface.BindTexImage(_context.Display.Handle, _eglSurface.DangerousGetHandle(),
+                EGL_BACK_BUFFER) == 0)
+            
+            throw OpenGlException.GetFormattedException("eglBindTexImage", _context.Display.EglInterface);
+    }
+    
+    public void Dispose()
+    {
+        
+        if (!_context.IsLost && TextureId != 0)
+            using (_context.EnsureCurrent())
+                _context.GlInterface.DeleteTexture(TextureId);
+        TextureId = 0;
+        _eglSurface?.Dispose();
+        _eglSurface = null!;
+        _texture2D?.Dispose();
+        _texture2D = null!;
+        _mutex?.Dispose();
+        _mutex = null!;
+    }
+
+
+    public void AcquireKeyedMutex(uint key) => _mutex.AcquireSync(key, int.MaxValue);
+
+    public void ReleaseKeyedMutex(uint key) => _mutex.ReleaseSync(key);
+
+    public int TextureId { get; private set; }
+    public int InternalFormat { get; }
+    public PlatformGraphicsExternalImageProperties Properties { get; }
+}
+
+internal class AngleExternalMemoryD3D11ExportedTexture2D : AngleExternalMemoryD3D11Texture2D, IGlExportableExternalImageTexture
+{
+    static IPlatformHandle GetHandle(ID3D11Texture2D texture2D)
+    {
+        using var resource = texture2D.QueryInterface<IDXGIResource>();
+        return new PlatformHandle(resource.SharedHandle,
+            KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle);
+    }
+
+    public AngleExternalMemoryD3D11ExportedTexture2D(EglContext context, ID3D11Texture2D texture2D,
+        D3D11_TEXTURE2D_DESC desc,
+        PlatformGraphicsExternalImageFormat format)
+        : this(context, texture2D, GetHandle(texture2D),
+            new PlatformGraphicsExternalImageProperties
+            {
+                Width = (int)desc.Width, Height = (int)desc.Height, Format = format
+            })
+    {
+
+    }
+
+    private AngleExternalMemoryD3D11ExportedTexture2D(EglContext context, ID3D11Texture2D texture2D,
+        IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties) 
+        : base(context, texture2D, properties)
+    {
+        Handle = handle;
+    }
+
+    public IPlatformHandle Handle { get; }
+    public IPlatformHandle GetHandle() => Handle;
+
+}

+ 103 - 0
src/Windows/Avalonia.Win32/OpenGl/Angle/AngleExternalObjectsFeature.cs

@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls.Documents;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Egl;
+using Avalonia.Platform;
+using Avalonia.Rendering.Composition;
+using Avalonia.Win32.DirectX;
+using MicroCom.Runtime;
+
+namespace Avalonia.Win32.OpenGl.Angle;
+
+internal class AngleExternalObjectsFeature : IGlContextExternalObjectsFeature, IDisposable
+{
+    private readonly EglContext _context;
+    private readonly ID3D11Device _device;
+
+    public AngleExternalObjectsFeature(EglContext context)
+    {
+        _context = context;
+        var angle = (AngleWin32EglDisplay)context.Display;
+        _device = MicroComRuntime.CreateProxyFor<ID3D11Device>(angle.GetDirect3DDevice(), false).CloneReference();
+        using var dxgiDevice = _device.QueryInterface<IDXGIDevice>();
+        using var adapter = dxgiDevice.Adapter;
+        DeviceLuid = BitConverter.GetBytes(adapter.Desc.AdapterLuid);
+    }
+
+    public IReadOnlyList<string> SupportedImportableExternalImageTypes { get; } = new[]
+    {
+        KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle
+    };
+
+    public IReadOnlyList<string> SupportedExportableExternalImageTypes => SupportedImportableExternalImageTypes;
+    public IReadOnlyList<string> SupportedImportableExternalSemaphoreTypes => Array.Empty<string>();
+    public IReadOnlyList<string> SupportedExportableExternalSemaphoreTypes => Array.Empty<string>();
+
+    public IReadOnlyList<PlatformGraphicsExternalImageFormat> GetSupportedFormatsForExternalMemoryType(string type) =>
+        new[]
+        {
+            PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm
+        };
+
+    public unsafe IGlExportableExternalImageTexture CreateImage(string type, PixelSize size, 
+        PlatformGraphicsExternalImageFormat format)
+    {
+        if (format != PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm)
+            throw new NotSupportedException("Unsupported external memory format");
+        using (_context.EnsureCurrent())
+        {
+            var fmt = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM;
+
+            var desc = new D3D11_TEXTURE2D_DESC
+            {
+                Format = fmt,
+                Width = (uint)size.Width,
+                Height = (uint)size.Height,
+                ArraySize = 1,
+                MipLevels = 1,
+                SampleDesc = new DXGI_SAMPLE_DESC { Count = 1, Quality = 0 },
+                Usage = D3D11_USAGE.D3D11_USAGE_DEFAULT,
+                CPUAccessFlags = 0,
+                MiscFlags = D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX,
+                BindFlags = D3D11_BIND_FLAG.D3D11_BIND_RENDER_TARGET | D3D11_BIND_FLAG.D3D11_BIND_SHADER_RESOURCE,
+            };
+            using var texture = _device.CreateTexture2D(&desc, IntPtr.Zero);
+            return new AngleExternalMemoryD3D11ExportedTexture2D(_context, texture, desc, format);
+        }
+    }
+
+    public IGlExportableExternalImageTexture CreateSemaphore(string type) => throw new NotSupportedException();
+
+    public unsafe IGlExternalImageTexture ImportImage(IPlatformHandle handle, PlatformGraphicsExternalImageProperties properties)
+    {
+        if (handle.HandleDescriptor != KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle)
+            throw new NotSupportedException("Unsupported external memory type");
+        
+        using (_context.EnsureCurrent())
+        {
+            var guid = MicroComRuntime.GetGuidFor(typeof(ID3D11Texture2D));
+            using var opened = _device.OpenSharedResource(handle.Handle, &guid);
+            using var texture = opened.QueryInterface<ID3D11Texture2D>();
+            return new AngleExternalMemoryD3D11Texture2D(_context, texture, properties);
+        }
+    }
+
+    public IGlExternalSemaphore ImportSemaphore(IPlatformHandle handle) => throw new NotSupportedException();
+    public CompositionGpuImportedImageSynchronizationCapabilities GetSynchronizationCapabilities(string imageHandleType)
+    {
+        if (imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle
+            || imageHandleType == KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle)
+            return CompositionGpuImportedImageSynchronizationCapabilities.KeyedMutex;
+        return default;
+    }
+
+    public byte[] DeviceLuid { get; }
+    public byte[] DeviceUuid { get; }
+
+    public void Dispose()
+    {
+        _device.Dispose();
+    }
+}

+ 87 - 45
src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs

@@ -1,5 +1,8 @@
+#nullable enable annotations
 using System;
+using System.Collections.Generic;
 using System.ComponentModel;
+using System.Linq;
 using System.Runtime.InteropServices;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL.Angle;
@@ -7,6 +10,7 @@ using Avalonia.OpenGL.Egl;
 using Avalonia.Win32.DirectX;
 using MicroCom.Runtime;
 using static Avalonia.OpenGL.Egl.EglConsts;
+// ReSharper disable SimplifyLinqExpressionUseMinByAndMaxBy
 
 namespace Avalonia.Win32.OpenGl.Angle
 {
@@ -40,51 +44,90 @@ namespace Avalonia.Win32.OpenGl.Angle
             }, AngleOptions.PlatformApi.DirectX11);
         }
 
-        public static AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl)
+        public static unsafe AngleWin32EglDisplay CreateD3D11Display(Win32AngleEglInterface egl,
+            bool preferDiscreteAdapter = false)
         {
-            unsafe
+            var featureLevels = new[]
             {
-                var featureLevels = new[]
-                {
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1,
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0,
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_1,
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_0,
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3,
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2,
-                    D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1
-                };
-
-                DirectXUnmanagedMethods.D3D11CreateDevice(IntPtr.Zero, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE,
-                    IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length,
-                    7, out var pD3dDevice, out var featureLevel, null);
-                if (pD3dDevice == IntPtr.Zero)
-                    throw new Win32Exception("Unable to create D3D11 Device");
+                D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0,
+                D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_10_0,
+                D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2,
+                D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1
+            };
 
-                var d3dDevice = MicroComRuntime.CreateProxyFor<ID3D11Device>(pD3dDevice, true);
-                var angleDevice = IntPtr.Zero;
-                var display = IntPtr.Zero;
+            var dxgiFactoryGuid = MicroComRuntime.GetGuidFor(typeof(IDXGIFactory1));
+            DirectXUnmanagedMethods.CreateDXGIFactory1(ref dxgiFactoryGuid, out var pDxgiFactory);
+            IDXGIAdapter1? chosenAdapter = null;
+            if (pDxgiFactory != null)
+            {
+                using var factory = MicroComRuntime.CreateProxyFor<IDXGIFactory1>(pDxgiFactory, true);
 
-                void Cleanup()
+                void* pAdapter = null;
+                if (preferDiscreteAdapter)
                 {
-                    if (angleDevice != IntPtr.Zero)
-                        egl.ReleaseDeviceANGLE(angleDevice);
-                    d3dDevice.Dispose();
+                    ushort adapterIndex = 0;
+                    var adapters = new List<(IDXGIAdapter1 adapter, string name)>();
+                    while (factory.EnumAdapters1(adapterIndex, &pAdapter) == 0)
+                    {
+                        var adapter = MicroComRuntime.CreateProxyFor<IDXGIAdapter1>(pAdapter, true);
+                        var desc = adapter.Desc1;
+                        var name = Marshal.PtrToStringUni(new IntPtr(desc.Description))!.ToLowerInvariant();
+                        adapters.Add((adapter, name));
+                        adapterIndex++;
+                    }
+
+                    if (adapters.Count == 0)
+                        throw new OpenGlException("No adapters found");
+                    chosenAdapter = adapters
+                        .OrderByDescending(x => x.name.Contains("nvidia") ? 2 : x.name.Contains("amd") ? 1 : 0)
+                        .First().adapter.CloneReference();
+                    foreach (var a in adapters)
+                        a.adapter.Dispose();
                 }
-                
-                bool success = false;
-                try
+                else
                 {
-                    angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null);
-                    if (angleDevice == IntPtr.Zero)
-                        throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl);
+                    if (factory.EnumAdapters1(0, &pAdapter) != 0)
+                        throw new OpenGlException("No adapters found");
+                    chosenAdapter = MicroComRuntime.CreateProxyFor<IDXGIAdapter1>(pAdapter, true);
+                }
+            }
+
+            IntPtr pD3dDevice;
+            using (chosenAdapter)
+                DirectXUnmanagedMethods.D3D11CreateDevice(chosenAdapter?.GetNativeIntPtr() ?? IntPtr.Zero,
+                    D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_UNKNOWN,
+                    IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length,
+                    7, out pD3dDevice, out var featureLevel, null);
+
 
-                    display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null);
-                    if (display == IntPtr.Zero)
-                        throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl);
+            if (pD3dDevice == IntPtr.Zero)
+                throw new Win32Exception("Unable to create D3D11 Device");
 
+            var d3dDevice = MicroComRuntime.CreateProxyFor<ID3D11Device>(pD3dDevice, true);
+            var angleDevice = IntPtr.Zero;
+            var display = IntPtr.Zero;
 
-                    var rv = new AngleWin32EglDisplay(display, new EglDisplayOptions
+            void Cleanup()
+            {
+                if (angleDevice != IntPtr.Zero)
+                    egl.ReleaseDeviceANGLE(angleDevice);
+                d3dDevice.Dispose();
+            }
+
+            bool success = false;
+            try
+            {
+                angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null);
+                if (angleDevice == IntPtr.Zero)
+                    throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl);
+
+                display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null);
+                if (display == IntPtr.Zero)
+                    throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl);
+
+
+                var rv = new AngleWin32EglDisplay(display,
+                    new EglDisplayOptions
                     {
                         DisposeCallback = Cleanup,
                         Egl = egl,
@@ -92,17 +135,16 @@ namespace Avalonia.Win32.OpenGl.Angle
                         DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0,
                         GlVersions = AvaloniaLocator.Current.GetService<AngleOptions>()?.GlProfiles
                     }, AngleOptions.PlatformApi.DirectX11);
-                    success = true;
-                    return rv;
-                }
-                finally
+                success = true;
+                return rv;
+            }
+            finally
+            {
+                if (!success)
                 {
-                    if (!success)
-                    {
-                        if (display != IntPtr.Zero)
-                            egl.Terminate(display);
-                        Cleanup();
-                    }
+                    if (display != IntPtr.Zero)
+                        egl.Terminate(display);
+                    Cleanup();
                 }
             }
         }

+ 13 - 5
src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32PlatformGraphics.cs

@@ -9,7 +9,7 @@ using Avalonia.Platform;
 
 namespace Avalonia.Win32.OpenGl.Angle;
 
-internal class AngleWin32PlatformGraphics : IPlatformGraphics
+internal class AngleWin32PlatformGraphics : IPlatformGraphics, IPlatformGraphicsOpenGlContextFactory
 {
     private readonly Win32AngleEglInterface _egl;
     private AngleWin32EglDisplay _sharedDisplay;
@@ -29,9 +29,10 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
             var rv = display.CreateContext(new EglContextOptions
             {
                 DisposeCallback = display.Dispose,
-                ExtraFeatures = new Dictionary<Type, object>
+                ExtraFeatures = new Dictionary<Type, Func<EglContext, object>>
                 {
-                    [typeof(IGlPlatformSurfaceRenderTargetFactory)] = new AngleD3DTextureFeature()
+                    [typeof(IGlPlatformSurfaceRenderTargetFactory)] = _ => new AngleD3DTextureFeature(),
+                    [typeof(IGlContextExternalObjectsFeature)] = context => new AngleExternalObjectsFeature(context)
                 }
             });
             success = true;
@@ -73,8 +74,6 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
 
     public static AngleWin32PlatformGraphics TryCreate(AngleOptions options)
     {
-         
-        
         Win32AngleEglInterface egl;
         try
         {
@@ -128,4 +127,13 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
             }
         return null;
     }
+
+    public IGlContext CreateContext(IEnumerable<GlVersion>? versions)
+    {
+        if (UsesSharedContext)
+            throw new InvalidOperationException();
+        if (versions != null && versions.All(v => v.Type != GlProfileType.OpenGLES || v.Major != 3))
+            throw new OpenGlException("Unable to create context with requested version");
+        return (IGlContext)CreateContext();
+    }
 }

+ 3 - 0
src/Windows/Avalonia.Win32/Win32GlManager.cs

@@ -37,6 +37,9 @@ namespace Avalonia.Win32
 
                 if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11)
                 {
+                    AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphicsOpenGlContextFactory>()
+                        .ToConstant(egl);
+                    
                     if (opts.UseWindowsUIComposition)
                     {
                         WinUiCompositorConnection.TryCreateAndRegister();

+ 2 - 0
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@@ -15,6 +15,8 @@ namespace Avalonia.Base.UnitTests.VisualTree
             throw new NotImplementedException();
         }
 
+        public bool IsLost => false;
+
         public object TryGetFeature(Type featureType) => null;
 
         public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)

+ 2 - 0
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@@ -46,6 +46,8 @@ namespace Avalonia.Benchmarks
             throw new NotImplementedException();
         }
 
+        public bool IsLost => false;
+
         public object TryGetFeature(Type featureType) => null;
 
         public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)

+ 2 - 0
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@@ -57,6 +57,8 @@ namespace Avalonia.UnitTests
             return new MockRenderTarget();
         }
 
+        public bool IsLost => false;
+
         public object TryGetFeature(Type featureType) => null;
 
         public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)