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