92 changed files with 5541 additions and 579 deletions
@ -1,6 +1,7 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup Condition="'$(TargetFramework)' != 'net6'"> |
|||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> |
|||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" /> |
|||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,9 +1,14 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup> |
|||
<SharpDXPackageVersion>4.0.1</SharpDXPackageVersion> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="SharpDX" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.Direct2D1" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.Direct3D11" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.DXGI" Version="4.0.1" /> |
|||
<PackageReference Include="SharpDX.Direct3D9" Version="4.0.1" Condition="'$(UseDirect3D9)' == 'true'" /> |
|||
<PackageReference Include="SharpDX" Version="$(SharpDXPackageVersion)" /> |
|||
<PackageReference Include="SharpDX.Direct2D1" Version="$(SharpDXPackageVersion)" /> |
|||
<PackageReference Include="SharpDX.Direct3D11" Version="$(SharpDXPackageVersion)" /> |
|||
<PackageReference Include="SharpDX.DXGI" Version="$(SharpDXPackageVersion)" /> |
|||
<PackageReference Include="SharpDX.Direct3D9" Version="$(SharpDXPackageVersion)" Condition="'$(UseDirect3D9)' == 'true'" /> |
|||
<PackageReference Include="SharpDX.D3DCompiler" Version="$(SharpDXPackageVersion)" Condition="'$(UseD3DCompiler)' == 'true'" /> |
|||
<PackageReference Include="SharpDX.Mathematics" Version="$(SharpDXPackageVersion)" Condition="'$(UseSharpDXMathematics)' == 'true'" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -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> |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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 |
|||
|
|||
|
|||
@ -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); |
|||
|
|||
} |
|||
Binary file not shown.
@ -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))); |
|||
} |
|||
Binary file not shown.
@ -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; |
|||
} |
|||
@ -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 |
|||
}); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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}\"."); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
|
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
} |
|||
|
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -1,11 +1,18 @@ |
|||
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
|
|||
|
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Rendering.Composition.Server |
|||
{ |
|||
internal abstract class ServerCompositionSurface : ServerObject |
|||
internal abstract partial class ServerCompositionSurface : ServerObject |
|||
{ |
|||
protected ServerCompositionSurface(ServerCompositor compositor) : base(compositor) |
|||
{ |
|||
} |
|||
|
|||
public abstract IRef<IBitmapImpl>? Bitmap { get; } |
|||
public Action? Changed { get; set; } |
|||
} |
|||
} |
|||
|
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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!; |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.OpenGL; |
|||
|
|||
public interface IPlatformGraphicsOpenGlContextFactory |
|||
{ |
|||
IGlContext CreateContext(IEnumerable<GlVersion>? versions); |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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; |
|||
|
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue