committed by
GitHub
419 changed files with 16733 additions and 10960 deletions
@ -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,113 @@ |
|||||
|
#include "common.h" |
||||
|
|
||||
|
@interface CocoaThemeObserver : NSObject |
||||
|
-(id)initWithCallback:(IAvnActionCallback *)callback; |
||||
|
@end |
||||
|
|
||||
|
class PlatformSettings : public ComSingleObject<IAvnPlatformSettings, &IID_IAvnPlatformSettings> |
||||
|
{ |
||||
|
CocoaThemeObserver* observer; |
||||
|
|
||||
|
public: |
||||
|
FORWARD_IUNKNOWN() |
||||
|
virtual AvnPlatformThemeVariant GetPlatformTheme() override |
||||
|
{ |
||||
|
@autoreleasepool |
||||
|
{ |
||||
|
if (@available(macOS 10.14, *)) |
||||
|
{ |
||||
|
if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua |
||||
|
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) { |
||||
|
return AvnPlatformThemeVariant::Light; |
||||
|
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua |
||||
|
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark) { |
||||
|
return AvnPlatformThemeVariant::Dark; |
||||
|
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua |
||||
|
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { |
||||
|
return AvnPlatformThemeVariant::HighContrastLight; |
||||
|
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua |
||||
|
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { |
||||
|
return AvnPlatformThemeVariant::HighContrastDark; |
||||
|
} |
||||
|
} |
||||
|
return AvnPlatformThemeVariant::Light; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
virtual unsigned int GetAccentColor() override |
||||
|
{ |
||||
|
@autoreleasepool |
||||
|
{ |
||||
|
if (@available(macOS 10.14, *)) |
||||
|
{ |
||||
|
auto color = [NSColor controlAccentColor]; |
||||
|
return to_argb(color); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
virtual void RegisterColorsChange(IAvnActionCallback *callback) override |
||||
|
{ |
||||
|
if (@available(macOS 10.14, *)) |
||||
|
{ |
||||
|
observer = [[CocoaThemeObserver alloc] initWithCallback: callback]; |
||||
|
[[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
unsigned int to_argb(NSColor* color) |
||||
|
{ |
||||
|
const CGFloat* components = CGColorGetComponents(color.CGColor); |
||||
|
unsigned int alpha = static_cast<unsigned int>(CGColorGetAlpha(color.CGColor) * 0xFF); |
||||
|
unsigned int red = static_cast<unsigned int>(components[0] * 0xFF); |
||||
|
unsigned int green = static_cast<unsigned int>(components[1] * 0xFF); |
||||
|
unsigned int blue = static_cast<unsigned int>(components[2] * 0xFF); |
||||
|
return (alpha << 24) + (red << 16) + (green << 8) + blue; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
@implementation CocoaThemeObserver |
||||
|
{ |
||||
|
ComPtr<IAvnActionCallback> _callback; |
||||
|
} |
||||
|
- (id) initWithCallback:(IAvnActionCallback *)callback{ |
||||
|
self = [super init]; |
||||
|
if (self) { |
||||
|
_callback = callback; |
||||
|
} |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
/*- (void)didChangeValueForKey:(NSString *)key { |
||||
|
if([key isEqualToString:@"effectiveAppearance"]) { |
||||
|
_callback->Run(); |
||||
|
} |
||||
|
else { |
||||
|
[super didChangeValueForKey:key]; |
||||
|
} |
||||
|
}*/ |
||||
|
|
||||
|
- (void)observeValueForKeyPath:(NSString *)keyPath |
||||
|
ofObject:(id)object |
||||
|
change:(NSDictionary *)change |
||||
|
context:(void *)context { |
||||
|
if([keyPath isEqualToString:@"effectiveAppearance"]) { |
||||
|
_callback->Run(); |
||||
|
} else { |
||||
|
[super observeValueForKeyPath:keyPath |
||||
|
ofObject:object |
||||
|
change:change |
||||
|
context:context]; |
||||
|
} |
||||
|
} |
||||
|
@end |
||||
|
|
||||
|
extern IAvnPlatformSettings* CreatePlatformSettings() |
||||
|
{ |
||||
|
return new PlatformSettings(); |
||||
|
} |
||||
@ -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,110 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Rendering.Composition; |
||||
|
using SharpDX.Direct3D11; |
||||
|
using SharpDX.DXGI; |
||||
|
using DxgiFactory1 = SharpDX.DXGI.Factory1; |
||||
|
using D3DDevice = SharpDX.Direct3D11.Device; |
||||
|
using DxgiResource = SharpDX.DXGI.Resource; |
||||
|
|
||||
|
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,51 @@ |
|||||
|
<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" /> |
||||
|
<PackageReference Include="System.Reactive" Version="5.0.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 @@ |
|||||
|
global using System.Reactive.Disposables; |
||||
|
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,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Android |
||||
|
{ |
||||
|
public interface IActivityNavigationService |
||||
|
{ |
||||
|
event EventHandler<AndroidBackRequestedEventArgs> BackRequested; |
||||
|
} |
||||
|
|
||||
|
public class AndroidBackRequestedEventArgs : EventArgs |
||||
|
{ |
||||
|
public bool Handled { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
using System; |
||||
|
using Android; |
||||
|
using Android.Content; |
||||
|
using Android.Content.Res; |
||||
|
using Android.Graphics; |
||||
|
using Android.Provider; |
||||
|
using Android.Views.Accessibility; |
||||
|
using AndroidX.Core.Content.Resources; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using Color = Avalonia.Media.Color; |
||||
|
|
||||
|
namespace Avalonia.Android.Platform; |
||||
|
|
||||
|
// TODO: ideally should be created per view/activity.
|
||||
|
internal class AndroidPlatformSettings : DefaultPlatformSettings |
||||
|
{ |
||||
|
private PlatformColorValues _latestValues; |
||||
|
|
||||
|
public AndroidPlatformSettings() |
||||
|
{ |
||||
|
_latestValues = base.GetColorValues(); |
||||
|
} |
||||
|
|
||||
|
public override PlatformColorValues GetColorValues() |
||||
|
{ |
||||
|
return _latestValues; |
||||
|
} |
||||
|
|
||||
|
internal void OnViewConfigurationChanged(Context context) |
||||
|
{ |
||||
|
if (context.Resources?.Configuration is null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch |
||||
|
{ |
||||
|
UiMode.NightYes => PlatformThemeVariant.Dark, |
||||
|
UiMode.NightNo => PlatformThemeVariant.Light, |
||||
|
_ => throw new ArgumentOutOfRangeException() |
||||
|
}; |
||||
|
|
||||
|
if (OperatingSystem.IsAndroidVersionAtLeast(31)) |
||||
|
{ |
||||
|
// See https://developer.android.com/reference/android/R.color
|
||||
|
var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500
|
||||
|
var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500
|
||||
|
var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500
|
||||
|
|
||||
|
_latestValues = new PlatformColorValues |
||||
|
{ |
||||
|
ThemeVariant = systemTheme, |
||||
|
ContrastPreference = IsHighContrast(context), |
||||
|
AccentColor1 = new Color(accent1.A, accent1.R, accent1.G, accent1.B), |
||||
|
AccentColor2 = new Color(accent2.A, accent2.R, accent2.G, accent2.B), |
||||
|
AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B), |
||||
|
}; |
||||
|
} |
||||
|
else if (OperatingSystem.IsAndroidVersionAtLeast(23)) |
||||
|
{ |
||||
|
// See https://developer.android.com/reference/android/R.attr
|
||||
|
var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
|
||||
|
var accent = array.GetColor(0, 0); |
||||
|
|
||||
|
_latestValues = new PlatformColorValues |
||||
|
{ |
||||
|
ThemeVariant = systemTheme, |
||||
|
ContrastPreference = IsHighContrast(context), |
||||
|
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B) |
||||
|
}; |
||||
|
array.Recycle(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_latestValues = _latestValues with { ThemeVariant = systemTheme }; |
||||
|
} |
||||
|
|
||||
|
OnColorValuesChanged(_latestValues); |
||||
|
} |
||||
|
|
||||
|
private static ColorContrastPreference IsHighContrast(Context context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
return Settings.Secure.GetInt(context.ContentResolver, "high_text_contrast_enabled", 0) == 1 |
||||
|
? ColorContrastPreference.High : ColorContrastPreference.NoPreference; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return ColorContrastPreference.NoPreference; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Android.Platform |
||||
|
{ |
||||
|
internal class AndroidSystemNavigationManager : ISystemNavigationManager |
||||
|
{ |
||||
|
public event EventHandler<RoutedEventArgs> BackRequested; |
||||
|
|
||||
|
public AndroidSystemNavigationManager(IActivityNavigationService? navigationService) |
||||
|
{ |
||||
|
if(navigationService != null) |
||||
|
{ |
||||
|
navigationService.BackRequested += OnBackRequested; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e) |
||||
|
{ |
||||
|
var routedEventArgs = new RoutedEventArgs(); |
||||
|
|
||||
|
BackRequested?.Invoke(this, routedEventArgs); |
||||
|
|
||||
|
e.Handled = routedEventArgs.Handled; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Data.Core.Plugins |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Holds a registry of plugins used for bindings.
|
||||
|
/// </summary>
|
||||
|
public static class BindingPlugins |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An ordered collection of property accessor plugins that can be used to customize
|
||||
|
/// the reading and subscription of property values on a type.
|
||||
|
/// </summary>
|
||||
|
public static IList<IPropertyAccessorPlugin> PropertyAccessors => ExpressionObserver.PropertyAccessors; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An ordered collection of validation checker plugins that can be used to customize
|
||||
|
/// the validation of view model and model data.
|
||||
|
/// </summary>
|
||||
|
public static IList<IDataValidationPlugin> DataValidators => ExpressionObserver.DataValidators; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An ordered collection of stream plugins that can be used to customize the behavior
|
||||
|
/// of the '^' stream binding operator.
|
||||
|
/// </summary>
|
||||
|
public static IList<IStreamPlugin> StreamHandlers => ExpressionObserver.StreamHandlers; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,268 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Media.TextFormatting |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Reorders text runs according to their bidi level.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>To avoid allocations, this class is designed to be reused.</remarks>
|
||||
|
internal sealed class BidiReorderer |
||||
|
{ |
||||
|
[ThreadStatic] private static BidiReorderer? t_instance; |
||||
|
|
||||
|
private ArrayBuilder<OrderedBidiRun> _runs; |
||||
|
private ArrayBuilder<BidiRange> _ranges; |
||||
|
|
||||
|
public static BidiReorderer Instance |
||||
|
=> t_instance ??= new(); |
||||
|
|
||||
|
public void BidiReorder(Span<TextRun> textRuns, FlowDirection flowDirection) |
||||
|
{ |
||||
|
Debug.Assert(_runs.Length == 0); |
||||
|
Debug.Assert(_ranges.Length == 0); |
||||
|
|
||||
|
if (textRuns.IsEmpty) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
_runs.Add(textRuns.Length); |
||||
|
|
||||
|
// Build up the collection of ordered runs.
|
||||
|
for (var i = 0; i < textRuns.Length; i++) |
||||
|
{ |
||||
|
var textRun = textRuns[i]; |
||||
|
_runs[i] = new OrderedBidiRun(i, textRun, GetRunBidiLevel(textRun, flowDirection)); |
||||
|
|
||||
|
if (i > 0) |
||||
|
{ |
||||
|
_runs[i - 1].NextRunIndex = i; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Reorder them into visual order.
|
||||
|
var firstIndex = LinearReorder(); |
||||
|
|
||||
|
// Now perform a recursive reversal of each run.
|
||||
|
// From the highest level found in the text to the lowest odd level on each line, including intermediate levels
|
||||
|
// not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
|
||||
|
// https://unicode.org/reports/tr9/#L2
|
||||
|
sbyte max = 0; |
||||
|
var min = sbyte.MaxValue; |
||||
|
|
||||
|
for (var i = 0; i < textRuns.Length; i++) |
||||
|
{ |
||||
|
var level = GetRunBidiLevel(textRuns[i], flowDirection); |
||||
|
if (level > max) |
||||
|
{ |
||||
|
max = level; |
||||
|
} |
||||
|
|
||||
|
if ((level & 1) != 0 && level < min) |
||||
|
{ |
||||
|
min = level; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (min > max) |
||||
|
{ |
||||
|
min = max; |
||||
|
} |
||||
|
|
||||
|
if (max == 0 || (min == max && (max & 1) == 0)) |
||||
|
{ |
||||
|
// Nothing to reverse.
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Now apply the reversal and replace the original contents.
|
||||
|
var minLevelToReverse = max; |
||||
|
int currentIndex; |
||||
|
|
||||
|
while (minLevelToReverse >= min) |
||||
|
{ |
||||
|
currentIndex = firstIndex; |
||||
|
|
||||
|
while (currentIndex >= 0) |
||||
|
{ |
||||
|
ref var current = ref _runs[currentIndex]; |
||||
|
if (current.Level >= minLevelToReverse && current.Level % 2 != 0) |
||||
|
{ |
||||
|
if (current.Run is ShapedTextRun { IsReversed: false } shapedTextCharacters) |
||||
|
{ |
||||
|
shapedTextCharacters.Reverse(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
currentIndex = current.NextRunIndex; |
||||
|
} |
||||
|
|
||||
|
minLevelToReverse--; |
||||
|
} |
||||
|
|
||||
|
var index = 0; |
||||
|
|
||||
|
currentIndex = firstIndex; |
||||
|
while (currentIndex >= 0) |
||||
|
{ |
||||
|
ref var current = ref _runs[currentIndex]; |
||||
|
textRuns[index++] = current.Run; |
||||
|
|
||||
|
currentIndex = current.NextRunIndex; |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
FormattingBufferHelper.ClearThenResetIfTooLarge(ref _runs); |
||||
|
FormattingBufferHelper.ClearThenResetIfTooLarge(ref _ranges); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static sbyte GetRunBidiLevel(TextRun run, FlowDirection flowDirection) |
||||
|
{ |
||||
|
if (run is ShapedTextRun shapedTextRun) |
||||
|
{ |
||||
|
return shapedTextRun.BidiLevel; |
||||
|
} |
||||
|
|
||||
|
var defaultLevel = flowDirection == FlowDirection.LeftToRight ? 0 : 1; |
||||
|
return (sbyte)defaultLevel; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reorders the runs from logical to visual order.
|
||||
|
/// <see href="https://github.com/fribidi/linear-reorder/blob/f2f872257d4d8b8e137fcf831f254d6d4db79d3c/linear-reorder.c"/>
|
||||
|
/// </summary>
|
||||
|
/// <returns>The first run index in visual order.</returns>
|
||||
|
private int LinearReorder() |
||||
|
{ |
||||
|
var runIndex = 0; |
||||
|
var rangeIndex = -1; |
||||
|
|
||||
|
while (runIndex >= 0) |
||||
|
{ |
||||
|
ref var run = ref _runs[runIndex]; |
||||
|
var nextRunIndex = run.NextRunIndex; |
||||
|
|
||||
|
while (rangeIndex >= 0 |
||||
|
&& _ranges[rangeIndex].Level > run.Level |
||||
|
&& _ranges[rangeIndex].PreviousRangeIndex >= 0 |
||||
|
&& _ranges[_ranges[rangeIndex].PreviousRangeIndex].Level >= run.Level) |
||||
|
{ |
||||
|
|
||||
|
rangeIndex = MergeRangeWithPrevious(rangeIndex); |
||||
|
} |
||||
|
|
||||
|
if (rangeIndex >= 0 && _ranges[rangeIndex].Level >= run.Level) |
||||
|
{ |
||||
|
// Attach run to the range.
|
||||
|
if ((run.Level & 1) != 0) |
||||
|
{ |
||||
|
// Odd, range goes to the right of run.
|
||||
|
run.NextRunIndex = _ranges[rangeIndex].LeftRunIndex; |
||||
|
_ranges[rangeIndex].LeftRunIndex = runIndex; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Even, range goes to the left of run.
|
||||
|
_runs[_ranges[rangeIndex].RightRunIndex].NextRunIndex = runIndex; |
||||
|
_ranges[rangeIndex].RightRunIndex = runIndex; |
||||
|
} |
||||
|
|
||||
|
_ranges[rangeIndex].Level = run.Level; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var r = new BidiRange(run.Level, runIndex, runIndex, previousRangeIndex: rangeIndex); |
||||
|
_ranges.AddItem(r); |
||||
|
rangeIndex = _ranges.Length - 1; |
||||
|
} |
||||
|
|
||||
|
runIndex = nextRunIndex; |
||||
|
} |
||||
|
|
||||
|
while (rangeIndex >= 0 && _ranges[rangeIndex].PreviousRangeIndex >= 0) |
||||
|
{ |
||||
|
rangeIndex = MergeRangeWithPrevious(rangeIndex); |
||||
|
} |
||||
|
|
||||
|
// Terminate.
|
||||
|
_runs[_ranges[rangeIndex].RightRunIndex].NextRunIndex = -1; |
||||
|
|
||||
|
return _runs[_ranges[rangeIndex].LeftRunIndex].RunIndex; |
||||
|
} |
||||
|
|
||||
|
private int MergeRangeWithPrevious(int index) |
||||
|
{ |
||||
|
var previousIndex = _ranges[index].PreviousRangeIndex; |
||||
|
ref var previous = ref _ranges[previousIndex]; |
||||
|
|
||||
|
int leftIndex; |
||||
|
int rightIndex; |
||||
|
|
||||
|
if ((previous.Level & 1) != 0) |
||||
|
{ |
||||
|
// Odd, previous goes to the right of range.
|
||||
|
leftIndex = index; |
||||
|
rightIndex = previousIndex; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Even, previous goes to the left of range.
|
||||
|
leftIndex = previousIndex; |
||||
|
rightIndex = index; |
||||
|
} |
||||
|
|
||||
|
// Stitch them
|
||||
|
ref var left = ref _ranges[leftIndex]; |
||||
|
ref var right = ref _ranges[rightIndex]; |
||||
|
_runs[left.RightRunIndex].NextRunIndex = _runs[right.LeftRunIndex].RunIndex; |
||||
|
previous.LeftRunIndex = left.LeftRunIndex; |
||||
|
previous.RightRunIndex = right.RightRunIndex; |
||||
|
|
||||
|
return previousIndex; |
||||
|
} |
||||
|
|
||||
|
private struct OrderedBidiRun |
||||
|
{ |
||||
|
public OrderedBidiRun(int runIndex, TextRun run, sbyte level) |
||||
|
{ |
||||
|
RunIndex = runIndex; |
||||
|
Run = run; |
||||
|
Level = level; |
||||
|
NextRunIndex = -1; |
||||
|
} |
||||
|
|
||||
|
public int RunIndex { get; } |
||||
|
|
||||
|
public sbyte Level { get; } |
||||
|
|
||||
|
public TextRun Run { get; } |
||||
|
|
||||
|
public int NextRunIndex { get; set; } // -1 if none
|
||||
|
} |
||||
|
|
||||
|
private struct BidiRange |
||||
|
{ |
||||
|
public BidiRange(sbyte level, int leftRunIndex, int rightRunIndex, int previousRangeIndex) |
||||
|
{ |
||||
|
Level = level; |
||||
|
LeftRunIndex = leftRunIndex; |
||||
|
RightRunIndex = rightRunIndex; |
||||
|
PreviousRangeIndex = previousRangeIndex; |
||||
|
} |
||||
|
|
||||
|
public sbyte Level { get; set; } |
||||
|
|
||||
|
public int LeftRunIndex { get; set; } |
||||
|
|
||||
|
public int RightRunIndex { get; set; } |
||||
|
|
||||
|
public int PreviousRangeIndex { get; } // -1 if none
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue