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. 21
      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. 11
      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. 6
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
  46. 88
      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. 19
      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. 7
      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. 302
      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. 42
      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. 4
      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", "packages\\Avalonia\\Avalonia.csproj",
"samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj", "samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
"samples\\ControlCatalog\\ControlCatalog.csproj", "samples\\ControlCatalog\\ControlCatalog.csproj",
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj", "samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj", "samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\SampleControls\\ControlSamples.csproj", "samples\\SampleControls\\ControlSamples.csproj",

7
Avalonia.sln

@ -231,6 +231,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -606,6 +612,7 @@ Global
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098} {15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {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} {75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} 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"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(TargetFramework)' != 'net6'"> <ItemGroup Condition="'$(TargetFramework)' != 'net6'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <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" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

15
build/SharpDX.props

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

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

@ -78,12 +78,7 @@ namespace ControlCatalog.Pages
get => _info; get => _info;
private set => SetAndRaise(InfoProperty, ref _info, value); private set => SetAndRaise(InfoProperty, ref _info, value);
} }
static OpenGlPageControl()
{
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty, DiscoProperty);
}
private int _vertexShader; private int _vertexShader;
private int _fragmentShader; private int _fragmentShader;
private int _shaderProgram; private int _shaderProgram;
@ -254,7 +249,7 @@ namespace ControlCatalog.Pages
Console.WriteLine(err); Console.WriteLine(err);
} }
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb) protected override unsafe void OnOpenGlInit(GlInterface GL)
{ {
CheckError(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 // Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0); GL.BindBuffer(GL_ARRAY_BUFFER, 0);
@ -366,7 +361,15 @@ namespace ControlCatalog.Pages
CheckError(GL); CheckError(GL);
if (_disco > 0.01) 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
{
}

11
src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
namespace Avalonia.Platform; namespace Avalonia.Platform;
@ -15,4 +16,12 @@ public static class OptionalFeatureProviderExtensions
{ {
public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class => public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class =>
(T?)provider.TryGetFeature(typeof(T)); (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> /// </param>
/// <returns>An <see cref="IRenderTarget"/>.</returns> /// <returns>An <see cref="IRenderTarget"/>.</returns>
IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces); 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> _dirty = new();
private HashSet<Visual> _recalculateChildren = new(); private HashSet<Visual> _recalculateChildren = new();
private bool _queuedUpdate; private bool _queuedUpdate;
private Action<Task> _update; private Action _update;
private bool _updating; private bool _updating;
internal CompositionTarget CompositionTarget; internal CompositionTarget CompositionTarget;
@ -70,7 +70,7 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate) if(_queuedUpdate)
return; return;
_queuedUpdate = true; _queuedUpdate = true;
_compositor.InvokeBeforeNextCommit(_update); _compositor.RequestCompositionUpdate(_update);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -265,7 +265,7 @@ public class CompositingRenderer : IRendererWithCompositor
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize))); SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
} }
private void Update(Task batchCompletion) private void Update()
{ {
if(_updating) if(_updating)
return; 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)
{
}
}

6
src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs

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

88
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -30,7 +30,7 @@ namespace Avalonia.Rendering.Composition
private BatchStreamObjectPool<object?> _batchObjectPool = new(); private BatchStreamObjectPool<object?> _batchObjectPool = new();
private BatchStreamMemoryPool _batchMemoryPool = new(); private BatchStreamMemoryPool _batchMemoryPool = new();
private List<CompositionObject> _objectsForSerialization = new(); private List<CompositionObject> _objectsForSerialization = new();
private Queue<Action<Task>> _invokeBeforeCommit = new(); private Queue<Action> _invokeBeforeCommitWrite = new(), _invokeBeforeCommitRead = new();
internal ServerCompositor Server => _server; internal ServerCompositor Server => _server;
private Task? _pendingBatch; private Task? _pendingBatch;
private readonly object _pendingBatchLock = new(); private readonly object _pendingBatchLock = new();
@ -77,16 +77,30 @@ namespace Avalonia.Rendering.Composition
return _nextCommit.Task; return _nextCommit.Task;
} }
internal Task Commit() internal Task Commit()
{
try
{
return CommitCore();
}
finally
{
if (_invokeBeforeCommitWrite.Count > 0)
RequestCommitAsync();
}
}
Task CommitCore()
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
using var noPump = NonPumpingLockHelper.Use(); using var noPump = NonPumpingLockHelper.Use();
_nextCommit ??= new TaskCompletionSource<int>(); _nextCommit ??= new TaskCompletionSource<int>();
while (_invokeBeforeCommit.Count > 0) (_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead);
_invokeBeforeCommit.Dequeue()(_nextCommit.Task); while (_invokeBeforeCommitRead.Count > 0)
_invokeBeforeCommitRead.Dequeue()();
var batch = new Batch(_nextCommit); var batch = new Batch(_nextCommit);
@ -109,6 +123,7 @@ namespace Avalonia.Rendering.Composition
writer.WriteObject(job); writer.WriteObject(job);
writer.WriteObject(ServerCompositor.RenderThreadJobsEndMarker); writer.WriteObject(ServerCompositor.RenderThreadJobsEndMarker);
} }
_pendingServerCompositorJobs.Clear();
} }
batch.CommittedAt = Server.Clock.Elapsed; batch.CommittedAt = Server.Clock.Elapsed;
@ -138,34 +153,73 @@ namespace Avalonia.Rendering.Composition
RequestCommitAsync(); 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(); Dispatcher.UIThread.VerifyAccess();
_invokeBeforeCommit.Enqueue(action); _invokeBeforeCommitWrite.Enqueue(action);
RequestCommitAsync(); RequestCommitAsync();
} }
/// <summary> internal void PostServerJob(Action job)
/// Attempts to query for a feature from the platform render interface
/// </summary>
public ValueTask<object?> TryGetRenderInterfaceFeature(Type featureType)
{ {
var tcs = new TaskCompletionSource<object?>(); Dispatcher.UIThread.VerifyAccess();
_pendingServerCompositorJobs.Add(() => _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 try
{ {
using (Server.RenderInterface.EnsureCurrent()) tcs.SetResult(job());
{
tcs.TrySetResult(Server.RenderInterface.Value.TryGetFeature(featureType));
}
} }
catch (Exception e) catch (Exception e)
{ {
tcs.TrySetException(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> // 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 namespace Avalonia.Rendering.Composition.Server
{ {
internal abstract class ServerCompositionSurface : ServerObject internal abstract partial class ServerCompositionSurface : ServerObject
{ {
protected ServerCompositionSurface(ServerCompositor compositor) : base(compositor) 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;
}
}

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

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

2
src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs

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

7
src/Avalonia.Base/composition-schema.xml

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

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 IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException();
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget(); public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => new HeadlessRenderTarget();
public bool IsLost => false;
public object TryGetFeature(Type featureType) => null; public object TryGetFeature(Type featureType) => null;
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi) public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)

4
src/Avalonia.OpenGL/Avalonia.OpenGL.csproj

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

302
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -1,120 +1,53 @@
using System; using System;
using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Rendering;
using Avalonia.OpenGL.Imaging; using Avalonia.Rendering.Composition;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using static Avalonia.OpenGL.GlConsts; using Avalonia.Platform;
namespace Avalonia.OpenGL.Controls namespace Avalonia.OpenGL.Controls
{ {
public abstract class OpenGlControlBase : Control public abstract class OpenGlControlBase : Control
{ {
private IGlContext _context; private CompositionSurfaceVisual _visual;
private int _fb, _depthBuffer; private Action _update;
private OpenGlBitmap _bitmap; private bool _updateQueued;
private IOpenGlBitmapAttachment _attachment;
private PixelSize _depthBufferSize;
private Task<bool> _initialization; private Task<bool> _initialization;
private IOpenGlTextureSharingRenderInterfaceContextFeature _feature; private OpenGlControlBaseResources? _resources;
private Compositor? _compositor;
protected GlVersion GlVersion { get; private set; } protected GlVersion GlVersion => _resources?.Context.Version ?? default;
public sealed override void Render(DrawingContext context)
{
if(!EnsureInitialized())
return;
using (_context.MakeCurrent())
{
_context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb);
EnsureTextureAttachment();
EnsureDepthBufferAttachment(_context.GlInterface);
if(!CheckFramebufferStatus(_context.GlInterface))
return;
OnOpenGlRender(_context.GlInterface, _fb);
_attachment.Present();
}
context.DrawImage(_bitmap, new Rect(_bitmap.Size), new Rect(Bounds.Size));
base.Render(context);
}
void EnsureTextureAttachment() public OpenGlControlBase()
{ {
_context.GlInterface.BindFramebuffer(GL_FRAMEBUFFER, _fb); _update = Update;
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);
} }
void DoCleanup() void DoCleanup()
{ {
if (_context != null) if (_initialization is { Status: TaskStatus.RanToCompletion } && _resources != null)
{ {
using (_context.MakeCurrent()) try
{ {
var gl = _context.GlInterface; using (_resources.Context.EnsureCurrent())
gl.ActiveTexture(GL_TEXTURE0);
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
if (_fb != 0)
gl.DeleteFramebuffer(_fb);
_fb = 0;
if (_depthBuffer != 0)
gl.DeleteRenderbuffer(_depthBuffer);
_depthBuffer = 0;
_attachment?.Dispose();
_attachment = null;
_bitmap?.Dispose();
_bitmap = null;
try
{
if (_initialization is { Status: TaskStatus.RanToCompletion, Result: true })
{
OnOpenGlDeinit(_context.GlInterface, _fb);
_initialization = null;
}
}
finally
{ {
_context.Dispose(); OnOpenGlDeinit(_resources.Context.GlInterface);
_context = null;
} }
} }
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to free user OpenGL resources: {exception}", e);
}
} }
_fb = _depthBuffer = 0; ElementComposition.SetElementChildVisual(this, null);
_attachment = null; _visual = null;
_bitmap = null;
_feature = null; _resources?.DisposeAsync();
_resources = null;
_initialization = null;
} }
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@ -123,95 +56,75 @@ namespace Avalonia.OpenGL.Controls
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
} }
private bool EnsureInitializedCore(IOpenGlTextureSharingRenderInterfaceContextFeature feature) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
try base.OnAttachedToVisualTree(e);
{ _compositor = (this.GetVisualRoot()?.Renderer as IRendererWithCompositor)?.Compositor;
_context = feature.CreateSharedContext(); RequestNextFrameRendering();
_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;
}
if (_context == null) private bool EnsureInitializedCore(
{ ICompositionGpuInterop interop,
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", IOpenGlTextureSharingRenderInterfaceContextFeature contextSharingFeature)
"Unable to initialize OpenGL: unable to create additional OpenGL context."); {
return false; var surface = _compositor.CreateDrawingSurface();
}
GlVersion = _context.Version; IGlContext ctx = null;
var contextFactory = AvaloniaLocator.Current.GetService<IPlatformGraphicsOpenGlContextFactory>();
try try
{ {
_bitmap = new OpenGlBitmap(_feature, GetPixelSize(), new Vector(96, 96)); if (contextSharingFeature?.CanCreateSharedContext == true)
if (!_bitmap.SupportsContext(_context)) _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", 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; return false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
_context.Dispose();
_context = null;
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", 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; return false;
} }
_visual = _compositor.CreateSurfaceVisual();
_visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
_visual.Surface = _resources.Surface;
ElementComposition.SetElementChildVisual(this, _visual);
using (_resources.Context.MakeCurrent())
OnOpenGlInit(_resources.Context.GlInterface);
return true;
using (_context.MakeCurrent())
{
try
{
_depthBufferSize = GetPixelSize();
var gl = _context.GlInterface;
_fb = gl.GenFramebuffer();
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb);
EnsureDepthBufferAttachment(gl);
EnsureTextureAttachment();
return CheckFramebufferStatus(gl);
}
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL FBO: {exception}", e);
return false;
}
}
} }
private static bool CheckFramebufferStatus(GlInterface gl) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); if (_visual != null && change.Property == BoundsProperty)
if (status != GL_FRAMEBUFFER_COMPLETE)
{ {
int code; _visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
while ((code = gl.GetError()) != 0) RequestNextFrameRendering();
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
"Unable to initialize OpenGL FBO: {code}", code);
return false;
} }
return true; base.OnPropertyChanged(change);
} }
void ContextLost() void ContextLost()
{ {
_context = null;
_feature = null;
_initialization = null; _initialization = null;
_attachment = null; _resources?.DisposeAsync();
_bitmap = null;
_fb = 0;
_depthBuffer = 0;
_depthBufferSize = default;
OnOpenGlLost(); OnOpenGlLost();
} }
@ -228,59 +141,106 @@ namespace Avalonia.OpenGL.Controls
if (_initialization is { IsCompleted: false }) if (_initialization is { IsCompleted: false })
return false; return false;
if (_context.IsLost) if (_resources!.Context.IsLost)
ContextLost(); ContextLost();
else else
return true; return true;
} }
_initialization = InitializeAsync(); _initialization = InitializeAsync();
async void ContinueOnInitialization()
{
try
{
await _initialization;
RequestNextFrameRendering();
}
catch
{
//
}
}
ContinueOnInitialization();
return false; 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() 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 = var contextSharingFeature =
(IOpenGlTextureSharingRenderInterfaceContextFeature) (IOpenGlTextureSharingRenderInterfaceContextFeature)
await this.GetVisualRoot()!.Renderer.TryGetRenderInterfaceFeature( await _compositor.TryGetRenderInterfaceFeature(
typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)); typeof(IOpenGlTextureSharingRenderInterfaceContextFeature));
var interop = await gpuInteropTask;
if (contextSharingFeature == null || !contextSharingFeature.CanCreateSharedContext) if (interop == null)
{ {
Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", 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; return false;
} }
if (!EnsureInitializedCore(contextSharingFeature)) if (!EnsureInitializedCore(interop, contextSharingFeature))
{ {
DoCleanup(); DoCleanup();
return false; return false;
} }
using (_context.MakeCurrent()) using (_resources!.Context.MakeCurrent())
OnOpenGlInit(_context.GlInterface, _fb); OnOpenGlInit(_resources.Context.GlInterface);
InvalidateVisual();
return true; 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() private PixelSize GetPixelSize()
{ {
var scaling = VisualRoot.RenderScaling; var scaling = VisualRoot!.RenderScaling;
return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)), return new PixelSize(Math.Max(1, (int)(Bounds.Width * scaling)),
Math.Max(1, (int)(Bounds.Height * scaling))); Math.Max(1, (int)(Bounds.Height * scaling)));
} }
protected virtual void OnOpenGlInit(GlInterface gl)
protected virtual void OnOpenGlInit(GlInterface gl, int fb)
{ {
} }
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_WIDTH = 0x3057;
public const int EGL_WINDOW_BIT = 0x0004; 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_RGB = 0x3039;
// public const int EGL_BIND_TO_TEXTURE_RGBA = 0x303A; // public const int EGL_BIND_TO_TEXTURE_RGBA = 0x303A;
public const int EGL_CONTEXT_LOST = 0x300E; 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_TEXTURE = 0x3082;
// public const int EGL_MIPMAP_LEVEL = 0x3083; // public const int EGL_MIPMAP_LEVEL = 0x3083;
// public const int EGL_NO_TEXTURE = 0x305C; // public const int EGL_NO_TEXTURE = 0x305C;
// public const int EGL_TEXTURE_2D = 0x305F; public const int EGL_TEXTURE_2D = 0x305F;
// public const int EGL_TEXTURE_FORMAT = 0x3080; public const int EGL_TEXTURE_FORMAT = 0x3080;
// public const int EGL_TEXTURE_RGB = 0x305D; // public const int EGL_TEXTURE_RGB = 0x305D;
// public const int EGL_TEXTURE_RGBA = 0x305E; public const int EGL_TEXTURE_RGBA = 0x305E;
// public const int EGL_TEXTURE_TARGET = 0x3081; public const int EGL_TEXTURE_TARGET = 0x3081;
// public const int EGL_ALPHA_FORMAT = 0x3088; // public const int EGL_ALPHA_FORMAT = 0x3088;
// public const int EGL_ALPHA_FORMAT_NONPRE = 0x308B; // 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_TEXTURE_OFFSET_Y_ANGLE = 0x3491;
public const int EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE = 0x33A6; 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Threading; using System.Threading;
using Avalonia.Platform; using Avalonia.Platform;
@ -19,21 +20,24 @@ namespace Avalonia.OpenGL.Egl
private readonly object _lock; private readonly object _lock;
internal EglContext(EglDisplay display, EglInterface egl, EglContext sharedWith, IntPtr ctx, EglSurface offscreenSurface, 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; _disp = display;
_egl = egl; _egl = egl;
_sharedWith = sharedWith; _sharedWith = sharedWith;
_context = ctx; _context = ctx;
_disposeCallback = disposeCallback; _disposeCallback = disposeCallback;
_features = features;
OffscreenSurface = offscreenSurface; OffscreenSurface = offscreenSurface;
Version = version; Version = version;
SampleCount = sampleCount; SampleCount = sampleCount;
StencilSize = stencilSize; StencilSize = stencilSize;
_lock = display.ContextSharedSyncRoot ?? new object(); _lock = display.ContextSharedSyncRoot ?? new object();
using (MakeCurrent()) using (MakeCurrent())
{
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress); GlInterface = GlInterface.FromNativeUtf8GetProcAddress(version, _egl.GetProcAddress);
_features = features.ToDictionary(x => x.Key, x => x.Value(this));
}
} }
public IntPtr Context => public IntPtr Context =>
@ -155,6 +159,14 @@ namespace Avalonia.OpenGL.Egl
{ {
if(_context == IntPtr.Zero) if(_context == IntPtr.Zero)
return; return;
foreach(var f in _features.ToList())
if (f.Value is IDisposable d)
{
d.Dispose();
_features.Remove(f.Key);
}
_egl.DestroyContext(_disp.Handle, Context); _egl.DestroyContext(_disp.Handle, Context);
OffscreenSurface?.Dispose(); OffscreenSurface?.Dispose();
_context = IntPtr.Zero; _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, var rv = new EglContext(this, _egl, share, ctx, offscreenSurface,
_config.Version, _config.SampleCount, _config.StencilSize, _config.Version, _config.SampleCount, _config.StencilSize,
options.DisposeCallback, options.ExtraFeatures); options.DisposeCallback, options.ExtraFeatures ?? new());
_contexts.Add(rv); _contexts.Add(rv);
return rv; return rv;
} }

2
src/Avalonia.OpenGL/Egl/EglDisplayOptions.cs

@ -19,7 +19,7 @@ public class EglContextOptions
public EglContext ShareWith { get; set; } public EglContext ShareWith { get; set; }
public EglSurface OffscreenSurface { get; set; } public EglSurface OffscreenSurface { get; set; }
public Action DisposeCallback { 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 public class EglDisplayCreationOptions : EglDisplayOptions

3
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -98,6 +98,9 @@ namespace Avalonia.OpenGL.Egl
[GetProcAddress("eglCreateWindowSurface")] [GetProcAddress("eglCreateWindowSurface")]
public partial IntPtr CreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs); 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")] [GetProcAddress("eglGetConfigAttrib")]
public partial bool GetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv); 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_VERTICES = 0x80E8;
// public const int GL_MAX_ELEMENTS_INDICES = 0x80E9; // public const int GL_MAX_ELEMENTS_INDICES = 0x80E9;
// public const int GL_BGR = 0x80E0; // 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_3_3_2 = 0x8032;
// public const int GL_UNSIGNED_BYTE_2_3_3_REV = 0x8362; // public const int GL_UNSIGNED_BYTE_2_3_3_REV = 0x8362;
// public const int GL_UNSIGNED_SHORT_5_6_5 = 0x8363; // 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_TILING_TYPES_EXT = 0x9583;
// public const int GL_OPTIMAL_TILING_EXT = 0x9584; // public const int GL_OPTIMAL_TILING_EXT = 0x9584;
// public const int GL_LINEAR_TILING_EXT = 0x9585; // public const int GL_LINEAR_TILING_EXT = 0x9585;
// public const int GL_NUM_DEVICE_UUIDS_EXT = 0x9596; internal const int GL_NUM_DEVICE_UUIDS_EXT = 0x9596;
// public const int GL_DEVICE_UUID_EXT = 0x9597; internal const int GL_DEVICE_UUID_EXT = 0x9597;
// public const int GL_DRIVER_UUID_EXT = 0x9598; // public const int GL_DRIVER_UUID_EXT = 0x9598;
// public const int GL_UUID_SIZE_EXT = 16; // public const int GL_UUID_SIZE_EXT = 16;
// public const int GL_EXT_memory_object_fd = 1; // 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_EXT_memory_object_win32 = 1;
// public const int GL_HANDLE_TYPE_OPAQUE_WIN32_EXT = 0x9587; // 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_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_DEVICE_NODE_MASK_EXT = 0x959A;
// public const int GL_LUID_SIZE_EXT = 8; // public const int GL_LUID_SIZE_EXT = 8;
// public const int GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT = 0x9589; // 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_SECONDARY_COLOR_ARRAY_EXT = 0x845E;
// public const int GL_EXT_semaphore = 1; // public const int GL_EXT_semaphore = 1;
// public const int GL_LAYOUT_GENERAL_EXT = 0x958D; // 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_ATTACHMENT_EXT = 0x958F;
// public const int GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT = 0x9590; // 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_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_TRANSFER_DST_EXT = 0x9593;
// public const int GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT = 0x9530; // public const int GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT = 0x9530;
// public const int GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT = 0x9531; // 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 System.Collections.Generic;
using Avalonia.OpenGL.Imaging; using Avalonia.Rendering.Composition;
using Avalonia.Platform;
namespace Avalonia.OpenGL namespace Avalonia.OpenGL
{ {
@ -8,6 +8,13 @@ namespace Avalonia.OpenGL
{ {
bool CanCreateSharedContext { get; } bool CanCreateSharedContext { get; }
IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null); 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); 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) => public static OpenGlException GetFormattedEglException(string funcName, int errorCode) =>
GetFormattedException(funcName, (EglErrors)errorCode,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.Reactive.Disposables;
using System.Threading; using System.Threading;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Features;
namespace Avalonia.X11.Glx namespace Avalonia.X11.Glx
{ {
class GlxContext : IGlContext class GlxContext : IGlContext
@ -14,6 +16,7 @@ namespace Avalonia.X11.Glx
private readonly IntPtr _defaultXid; private readonly IntPtr _defaultXid;
private readonly bool _ownsPBuffer; private readonly bool _ownsPBuffer;
private readonly object _lock = new object(); private readonly object _lock = new object();
private ExternalObjectsOpenGlExtensionFeature? _externalObjects;
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display,
GlxContext sharedWith, GlxContext sharedWith,
@ -32,7 +35,10 @@ namespace Avalonia.X11.Glx
SampleCount = sampleCount; SampleCount = sampleCount;
StencilSize = stencilSize; StencilSize = stencilSize;
using (MakeCurrent()) using (MakeCurrent())
{
GlInterface = new GlInterface(version, GlxInterface.SafeGetProcAddress); GlInterface = new GlInterface(version, GlxInterface.SafeGetProcAddress);
_externalObjects = ExternalObjectsOpenGlExtensionFeature.TryCreate(this);
}
} }
public GlxDisplay Display { get; } public GlxDisplay Display { get; }
@ -123,6 +129,11 @@ namespace Avalonia.X11.Glx
Glx.DestroyPbuffer(_x11.Display, _defaultXid); 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;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
@ -33,9 +31,4 @@ namespace Avalonia.Skia
bool CanBlit { get; } bool CanBlit { get; }
void Blit(SKCanvas canvas); 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();
}
}

42
src/Skia/Avalonia.Skia/Gpu/OpenGl/GlSkiaGpu.cs

@ -3,19 +3,22 @@ using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.OpenGL.Surfaces; using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
class GlSkiaGpu : IOpenGlAwareSkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature class GlSkiaGpu : ISkiaGpu, IOpenGlTextureSharingRenderInterfaceContextFeature
{ {
private GRContext _grContext; private GRContext _grContext;
private IGlContext _glContext; private IGlContext _glContext;
public GRContext GrContext => _grContext;
public IGlContext GlContext => _glContext;
private List<Action> _postDisposeCallbacks = new(); private List<Action> _postDisposeCallbacks = new();
private bool? _canCreateSurfaces; private bool? _canCreateSurfaces;
private IExternalObjectsRenderInterfaceContextFeature? _externalObjectsFeature;
public GlSkiaGpu(IGlContext context, long? maxResourceBytes) public GlSkiaGpu(IGlContext context, long? maxResourceBytes)
{ {
@ -32,6 +35,9 @@ namespace Avalonia.Skia
_grContext.SetResourceCacheLimit(maxResourceBytes.Value); _grContext.SetResourceCacheLimit(maxResourceBytes.Value);
} }
} }
context.TryGetFeature<IGlContextExternalObjectsFeature>(out var externalObjects);
_externalObjectsFeature = new GlSkiaExternalObjectsFeature(this, externalObjects);
} }
} }
@ -103,8 +109,34 @@ namespace Avalonia.Skia
public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) => public IGlContext CreateSharedContext(IEnumerable<GlVersion> preferredVersions = null) =>
_glContext.CreateSharedContext(preferredVersions); _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() public void Dispose()
{ {
if (_glContext.IsLost) if (_glContext.IsLost)
@ -125,6 +157,8 @@ namespace Avalonia.Skia
{ {
if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature)) if (featureType == typeof(IOpenGlTextureSharingRenderInterfaceContextFeature))
return this; return this;
if (featureType == typeof(IExternalObjectsRenderInterfaceContextFeature))
return _externalObjectsFeature;
return null; 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) public ImmutableBitmap(ImmutableBitmap src, PixelSize destinationSize, BitmapInterpolationMode interpolationMode)
{ {
SKImageInfo info = new SKImageInfo(destinationSize.Width, destinationSize.Height, SKColorType.Bgra8888); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using SkiaSharp; using SkiaSharp;

4
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"); "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); 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 IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) => _platform.CreateRenderTarget(surfaces);
public bool IsLost => false;
} }
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => 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, 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 internal enum DXGI_SWAP_EFFECT
{ {
DXGI_SWAP_EFFECT_DISCARD = 0, 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 D3D11_USAGE Usage;
public uint BindFlags; public D3D11_BIND_FLAG BindFlags;
public uint CPUAccessFlags; public uint CPUAccessFlags;
public uint MiscFlags; public D3D11_RESOURCE_MISC_FLAG MiscFlags;
} }
#nullable restore #nullable restore
} }

33
src/Windows/Avalonia.Win32/DirectX/directx.idl

@ -10,6 +10,7 @@
@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE @clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE
@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT @clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT
@clr-map HWND IntPtr @clr-map HWND IntPtr
@clr-map HANDLE IntPtr
@clr-map BOOL int @clr-map BOOL int
@clr-map DWORD int @clr-map DWORD int
@clr-map SIZE_T IntPtr @clr-map SIZE_T IntPtr
@ -259,10 +260,28 @@ interface IDXGISurface : IDXGIDeviceSubObject
HRESULT Unmap(); 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)] [uuid(770aae78-f26f-4dba-a829-253c83d1b387)]
interface IDXGIFactory1 : IDXGIFactory interface IDXGIFactory1 : IDXGIFactory
{ {
HRESULT EnumAdapters1([in] UINT Adapter, [out, annotation("_COM_Outptr_")] IDXGIAdapter1** ppAdapter); int EnumAdapters1([in] UINT Adapter, [out] void** ppAdapter);
BOOL IsCurrent(); BOOL IsCurrent();
} }
@ -339,9 +358,9 @@ interface ID3D11Device : IUnknown
IntPtr pInitialData, IntPtr pInitialData,
[out, retval] IUnknown** ppTexture1D ); [out, retval] IUnknown** ppTexture1D );
HRESULT CreateTexture2D( HRESULT CreateTexture2D(
IntPtr pDesc, D3D11_TEXTURE2D_DESC* pDesc,
IntPtr pInitialData, IntPtr pInitialData,
[out, retval] IUnknown** ppTexture2D ); [out, retval] ID3D11Texture2D** ppTexture2D );
HRESULT CreateTexture3D( HRESULT CreateTexture3D(
IntPtr pDesc, IntPtr pDesc,
IntPtr pInitialData, IntPtr pInitialData,
@ -481,3 +500,11 @@ interface ID3D11Device : IUnknown
HRESULT SetExceptionMode( UINT RaiseFlags ); HRESULT SetExceptionMode( UINT RaiseFlags );
UINT GetExceptionMode(); 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;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Angle;
@ -7,6 +10,7 @@ using Avalonia.OpenGL.Egl;
using Avalonia.Win32.DirectX; using Avalonia.Win32.DirectX;
using MicroCom.Runtime; using MicroCom.Runtime;
using static Avalonia.OpenGL.Egl.EglConsts; using static Avalonia.OpenGL.Egl.EglConsts;
// ReSharper disable SimplifyLinqExpressionUseMinByAndMaxBy
namespace Avalonia.Win32.OpenGl.Angle namespace Avalonia.Win32.OpenGl.Angle
{ {
@ -40,51 +44,90 @@ namespace Avalonia.Win32.OpenGl.Angle
}, AngleOptions.PlatformApi.DirectX11); }, 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_11_1, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_9_1
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");
var d3dDevice = MicroComRuntime.CreateProxyFor<ID3D11Device>(pD3dDevice, true); var dxgiFactoryGuid = MicroComRuntime.GetGuidFor(typeof(IDXGIFactory1));
var angleDevice = IntPtr.Zero; DirectXUnmanagedMethods.CreateDXGIFactory1(ref dxgiFactoryGuid, out var pDxgiFactory);
var display = IntPtr.Zero; IDXGIAdapter1? chosenAdapter = null;
if (pDxgiFactory != null)
{
using var factory = MicroComRuntime.CreateProxyFor<IDXGIFactory1>(pDxgiFactory, true);
void Cleanup() void* pAdapter = null;
if (preferDiscreteAdapter)
{ {
if (angleDevice != IntPtr.Zero) ushort adapterIndex = 0;
egl.ReleaseDeviceANGLE(angleDevice); var adapters = new List<(IDXGIAdapter1 adapter, string name)>();
d3dDevice.Dispose(); while (factory.EnumAdapters1(adapterIndex, &pAdapter) == 0)
{
var adapter = MicroComRuntime.CreateProxyFor<IDXGIAdapter1>(pAdapter, true);
var desc = adapter.Desc1;
var name = Marshal.PtrToStringUni(new IntPtr(desc.Description))!.ToLowerInvariant();
adapters.Add((adapter, name));
adapterIndex++;
}
if (adapters.Count == 0)
throw new OpenGlException("No adapters found");
chosenAdapter = adapters
.OrderByDescending(x => x.name.Contains("nvidia") ? 2 : x.name.Contains("amd") ? 1 : 0)
.First().adapter.CloneReference();
foreach (var a in adapters)
a.adapter.Dispose();
} }
else
bool success = false;
try
{ {
angleDevice = egl.CreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, pD3dDevice, null); if (factory.EnumAdapters1(0, &pAdapter) != 0)
if (angleDevice == IntPtr.Zero) throw new OpenGlException("No adapters found");
throw OpenGlException.GetFormattedException("eglCreateDeviceANGLE", egl); chosenAdapter = MicroComRuntime.CreateProxyFor<IDXGIAdapter1>(pAdapter, true);
}
}
IntPtr pD3dDevice;
using (chosenAdapter)
DirectXUnmanagedMethods.D3D11CreateDevice(chosenAdapter?.GetNativeIntPtr() ?? IntPtr.Zero,
D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_UNKNOWN,
IntPtr.Zero, 0, featureLevels, (uint)featureLevels.Length,
7, out pD3dDevice, out var featureLevel, null);
display = egl.GetPlatformDisplayExt(EGL_PLATFORM_DEVICE_EXT, angleDevice, null); if (pD3dDevice == IntPtr.Zero)
if (display == IntPtr.Zero) throw new Win32Exception("Unable to create D3D11 Device");
throw OpenGlException.GetFormattedException("eglGetPlatformDisplayEXT", egl);
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, DisposeCallback = Cleanup,
Egl = egl, Egl = egl,
@ -92,17 +135,16 @@ namespace Avalonia.Win32.OpenGl.Angle
DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0, DeviceLostCheckCallback = () => d3dDevice.DeviceRemovedReason != 0,
GlVersions = AvaloniaLocator.Current.GetService<AngleOptions>()?.GlProfiles GlVersions = AvaloniaLocator.Current.GetService<AngleOptions>()?.GlProfiles
}, AngleOptions.PlatformApi.DirectX11); }, AngleOptions.PlatformApi.DirectX11);
success = true; success = true;
return rv; return rv;
} }
finally finally
{
if (!success)
{ {
if (!success) if (display != IntPtr.Zero)
{ egl.Terminate(display);
if (display != IntPtr.Zero) Cleanup();
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; namespace Avalonia.Win32.OpenGl.Angle;
internal class AngleWin32PlatformGraphics : IPlatformGraphics internal class AngleWin32PlatformGraphics : IPlatformGraphics, IPlatformGraphicsOpenGlContextFactory
{ {
private readonly Win32AngleEglInterface _egl; private readonly Win32AngleEglInterface _egl;
private AngleWin32EglDisplay _sharedDisplay; private AngleWin32EglDisplay _sharedDisplay;
@ -29,9 +29,10 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
var rv = display.CreateContext(new EglContextOptions var rv = display.CreateContext(new EglContextOptions
{ {
DisposeCallback = display.Dispose, 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; success = true;
@ -73,8 +74,6 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
public static AngleWin32PlatformGraphics TryCreate(AngleOptions options) public static AngleWin32PlatformGraphics TryCreate(AngleOptions options)
{ {
Win32AngleEglInterface egl; Win32AngleEglInterface egl;
try try
{ {
@ -128,4 +127,13 @@ internal class AngleWin32PlatformGraphics : IPlatformGraphics
} }
return null; 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) if (egl != null && egl.PlatformApi == AngleOptions.PlatformApi.DirectX11)
{ {
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphicsOpenGlContextFactory>()
.ToConstant(egl);
if (opts.UseWindowsUIComposition) if (opts.UseWindowsUIComposition)
{ {
WinUiCompositorConnection.TryCreateAndRegister(); WinUiCompositorConnection.TryCreateAndRegister();

2
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

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

2
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

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

2
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

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

Loading…
Cancel
Save