Browse Source

Implemented interop with externally managed GPU memory

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

1
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
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
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>

15
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>

19
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -79,11 +79,6 @@ namespace ControlCatalog.Pages
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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

Binary file not shown.

36
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

Binary file not shown.

47
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
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
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
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
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
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
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
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
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
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
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
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
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
{
}

9
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
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
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);
}

6
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
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
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
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
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)
{
}
}

4
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);
}

86
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();
@ -79,14 +79,28 @@ namespace Avalonia.Rendering.Composition
}
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
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();
}
}

9
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
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;
}
}

17
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs

@ -23,6 +23,7 @@ namespace Avalonia.Rendering.Composition.Server
private readonly IRenderLoop _renderLoop;
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
src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs

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

89
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();
}

5
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?"/>

1
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)

4
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
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);
}
}

296
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;
private OpenGlControlBaseResources? _resources;
private Compositor? _compositor;
protected GlVersion GlVersion => _resources?.Context.Version ?? default;
protected GlVersion GlVersion { get; private set; }
public sealed override void Render(DrawingContext context)
public OpenGlControlBase()
{
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);
}
void EnsureTextureAttachment()
{
_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
using (_resources.Context.EnsureCurrent())
{
if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true })
{
OnOpenGlDeinit(_context.GlInterface, _fb);
_initialization = null;
}
}
finally
{
_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;
}
using (_context.MakeCurrent())
{
try
{
_depthBufferSize = GetPixelSize();
var gl = _context.GlInterface;
_fb = gl.GenFramebuffer();
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
EnsureDepthBufferAttachment(gl);
EnsureTextureAttachment();
_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;
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
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!;
}
}
}

12
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;
}
}

16
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;

2
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;
}

2
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
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
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; }
}
}

14
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
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();
}

13
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
src/Avalonia.OpenGL/IPlatformGraphicsOpenGlContextFactory.cs

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

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

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
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);

13
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;
}
}
}

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

40
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,7 +109,33 @@ 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()
{
@ -125,6 +157,8 @@ namespace Avalonia.Skia
{
if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature))
return this;
if (featureType == typeof(IExternalObjectsRenderInterfaceContextFeature))
return _externalObjectsFeature;
return null;
}

44
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);
}
}

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
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);

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;

2
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
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
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,

4
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
}

33
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
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
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();
}
}

132
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 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);
var d3dDevice = MicroComRuntime.CreateProxyFor<ID3D11Device>(pD3dDevice, true);
var angleDevice = IntPtr.Zero;
var display = IntPtr.Zero;
void* pAdapter = null;
if (preferDiscreteAdapter)
{
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++;
}
void Cleanup()
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();
}
else
{
if (angleDevice != IntPtr.Zero)
egl.ReleaseDeviceANGLE(angleDevice);
d3dDevice.Dispose();
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);
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);
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();
}
}
}

18
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
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
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
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
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)

Loading…
Cancel
Save