Browse Source

[WIP] Vulkan backend

feature/vulkan
Nikita Tsukanov 3 years ago
parent
commit
3eb72c33da
  1. 6
      Avalonia.sln
  2. 1
      build/CoreLibraries.props
  3. 12
      samples/ControlCatalog.NetCore/Program.cs
  4. 3
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  5. 3
      src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs
  6. 4
      src/Avalonia.Controls/Platform/Surfaces/IPlatformNativeSurfaceHandle.cs
  7. 17
      src/Avalonia.Vulkan/Avalonia.Vulkan.csproj
  8. 34
      src/Avalonia.Vulkan/IVulkanDevice.cs
  9. 15
      src/Avalonia.Vulkan/IVulkanPlatformSurface.cs
  10. 29
      src/Avalonia.Vulkan/IVulkanRenderTarget.cs
  11. 108
      src/Avalonia.Vulkan/Interop/VulkanCommandBuffer.cs
  12. 62
      src/Avalonia.Vulkan/Interop/VulkanCommandBufferPool.cs
  13. 37
      src/Avalonia.Vulkan/Interop/VulkanDebugLogger.cs
  14. 163
      src/Avalonia.Vulkan/Interop/VulkanDevice.Create.cs
  15. 66
      src/Avalonia.Vulkan/Interop/VulkanDevice.cs
  16. 328
      src/Avalonia.Vulkan/Interop/VulkanDisplay.cs
  17. 47
      src/Avalonia.Vulkan/Interop/VulkanFence.cs
  18. 152
      src/Avalonia.Vulkan/Interop/VulkanImage.cs
  19. 134
      src/Avalonia.Vulkan/Interop/VulkanInstance.cs
  20. 69
      src/Avalonia.Vulkan/Interop/VulkanKhrSurface.cs
  21. 67
      src/Avalonia.Vulkan/Interop/VulkanMemoryHelper.cs
  22. 53
      src/Avalonia.Vulkan/Interop/VulkanSemaphore.cs
  23. 46
      src/Avalonia.Vulkan/UnmanagedInterop/Utf8BufferArray.cs
  24. 139
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs
  25. 2102
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs
  26. 25
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanGlobalApi.cs
  27. 75
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs
  28. 657
      src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs
  29. 19
      src/Avalonia.Vulkan/VulkanBindings.cs
  30. 53
      src/Avalonia.Vulkan/VulkanContext.cs
  31. 27
      src/Avalonia.Vulkan/VulkanException.cs
  32. 109
      src/Avalonia.Vulkan/VulkanKhrSurfaceRenderTarget.cs
  33. 73
      src/Avalonia.Vulkan/VulkanOptions.cs
  34. 103
      src/Avalonia.Vulkan/VulkanPlatformGraphics.cs
  35. 3
      src/Avalonia.X11/Avalonia.X11.csproj
  36. 29
      src/Avalonia.X11/Vulkan/VulkanNativeInterop.cs
  37. 97
      src/Avalonia.X11/Vulkan/VulkanSupport.cs
  38. 22
      src/Avalonia.X11/X11Platform.cs
  39. 3
      src/Avalonia.X11/X11Window.cs
  40. 76
      src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs
  41. 104
      src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaRenderTarget.cs
  42. 4
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  43. 5
      src/Windows/Avalonia.Win32/WindowImpl.cs

6
Avalonia.sln

@ -232,6 +232,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Vulkan", "src\Avalonia.Vulkan\Avalonia.Vulkan.csproj", "{3E2DE2B6-13BC-4C27-BCB9-A423B86CAF77}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -543,6 +545,10 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
{3E2DE2B6-13BC-4C27-BCB9-A423B86CAF77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E2DE2B6-13BC-4C27-BCB9-A423B86CAF77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E2DE2B6-13BC-4C27-BCB9-A423B86CAF77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E2DE2B6-13BC-4C27-BCB9-A423B86CAF77}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

1
build/CoreLibraries.props

@ -4,6 +4,7 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Controls/Avalonia.Controls.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Vulkan/Avalonia.Vulkan.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />

12
samples/ControlCatalog.NetCore/Program.cs

@ -10,7 +10,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Headless;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using Avalonia.Vulkan;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore
@ -122,7 +122,15 @@ namespace ControlCatalog.NetCore
{
EnableMultiTouch = true,
UseDBusMenu = true,
EnableIme = true
EnableIme = true,
UseVulkan = true
})
.With(new VulkanOptions
{
VulkanInstanceCreationOptions = new ()
{
UseDebug = true
}
})
.UseSkia()
.AfterSetup(builder =>

3
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -7,10 +7,11 @@ using Android.Util;
using Android.Views;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Platform;
using Avalonia.Platform.Surfaces;
namespace Avalonia.Android
{
public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, INativeHandlePlatformSurface
{
bool _invalidateQueued;
readonly object _lock = new object();

3
src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs

@ -54,5 +54,8 @@ namespace Avalonia.Platform.Interop
ArrayPool<byte>.Shared.Return(bytes);
}
}
public static implicit operator IntPtr(Utf8Buffer b) => b.handle;
public static unsafe implicit operator byte*(Utf8Buffer b) => (byte*)b.handle;
}
}

4
src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs → src/Avalonia.Controls/Platform/Surfaces/IPlatformNativeSurfaceHandle.cs

@ -1,10 +1,10 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Platform
namespace Avalonia.Platform.Surfaces
{
[Unstable]
public interface IPlatformNativeSurfaceHandle : IPlatformHandle
public interface INativeHandlePlatformSurface : IPlatformHandle
{
PixelSize Size { get; }
double Scaling { get; }

17
src/Avalonia.Vulkan/Avalonia.Vulkan.csproj

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
</ItemGroup>
<Import Project="..\..\build\DevAnalyzers.props" />
<Import Project="..\..\build\SourceGenerators.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\NullableEnable.props" />
</Project>

34
src/Avalonia.Vulkan/IVulkanDevice.cs

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
public interface IVulkanDevice : IDisposable, IOptionalFeatureProvider
{
public IntPtr Handle { get; }
public IntPtr PhysicalDeviceHandle { get; }
public IntPtr MainQueueHandle { get; }
public uint GraphicsQueueFamilyIndex { get; }
public IVulkanInstance Instance { get; }
bool IsLost { get; }
public IDisposable Lock();
}
[NotClientImplementable]
public interface IVulkanPlatformGraphicsContext : IPlatformGraphicsContext
{
IVulkanDevice Device { get; }
IVulkanInstance Instance { get; }
internal VulkanInstanceApi InstanceApi { get; }
internal VulkanDeviceApi DeviceApi { get; }
IVulkanRenderTarget CreateRenderTarget(IEnumerable<object> surfaces);
}
public interface IVulkanInstance
{
public IntPtr Handle { get; }
public IntPtr GetInstanceProcAddress(IntPtr instance, string name);
public IntPtr GetDeviceProcAddress(IntPtr device, string name);
}

15
src/Avalonia.Vulkan/IVulkanPlatformSurface.cs

@ -0,0 +1,15 @@
using System;
namespace Avalonia.Vulkan;
public interface IVulkanKhrSurfacePlatformSurface : IDisposable
{
double Scaling { get; }
PixelSize Size { get; }
IntPtr CreateSurface(IVulkanPlatformGraphicsContext context);
}
public interface IVulkanKhrSurfacePlatformSurfaceFactory
{
bool CanRenderToSurface(IVulkanPlatformGraphicsContext context, object surface);
IVulkanKhrSurfacePlatformSurface CreateSurface(IVulkanPlatformGraphicsContext context, object surface);
}

29
src/Avalonia.Vulkan/IVulkanRenderTarget.cs

@ -0,0 +1,29 @@
using System;
namespace Avalonia.Vulkan;
public interface IVulkanPlatformSurface
{
public IVulkanRenderTarget CreateRenderTarget(IVulkanPlatformGraphicsContext context);
}
public interface IVulkanRenderTarget : IDisposable
{
IVulkanRenderSession BeginDraw();
}
public interface IVulkanRenderSession : IDisposable
{
double Scaling { get; }
PixelSize Size { get; }
public bool IsYFlipped { get; }
uint ImageFormat { get; }
IntPtr ImageHandle { get; }
uint ImageLayout { get; }
uint ImageTiling { get; }
uint ImageUsageFlags { get; }
uint LevelCount { get; }
IntPtr ImageMemoryHandle { get; }
ulong ImageMemorySize { get; }
bool IsRgba { get; }
}

108
src/Avalonia.Vulkan/Interop/VulkanCommandBuffer.cs

@ -0,0 +1,108 @@
using System;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan.Interop;
internal class VulkanCommandBuffer : IDisposable
{
private readonly VulkanCommandBufferPool _pool;
private IntPtr _handle;
private readonly IVulkanDevice _device;
private readonly VulkanDeviceApi _api;
private VulkanFence _fence;
private bool _hasEnded;
private bool _hasStarted;
public IntPtr Handle => _handle;
public VulkanCommandBuffer(VulkanCommandBufferPool pool, IntPtr handle, IVulkanDevice device, VulkanDeviceApi api)
{
_pool = pool;
_handle = handle;
_device = device;
_api = api;
_fence = new VulkanFence(_device, _api, VkFenceCreateFlags.VK_FENCE_CREATE_SIGNALED_BIT);
}
public unsafe void Dispose()
{
if (_fence.Handle != IntPtr.Zero)
_fence.Wait();
_fence.Dispose();
if (_handle != IntPtr.Zero)
{
IntPtr buf = _handle;
_api.FreeCommandBuffers(_device.Handle, _handle, 1, &buf);
_handle = IntPtr.Zero;
}
_pool.OnCommandBufferDisposed(this);
}
public void BeginRecording()
{
if (_hasStarted)
return;
var beginInfo = new VkCommandBufferBeginInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
flags = VkCommandBufferUsageFlags.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
};
_api.BeginCommandBuffer(_handle, ref beginInfo).ThrowOnError("vkBeginCommandBuffer");
_hasStarted = true;
}
public void EndRecording()
{
if (_hasStarted && !_hasEnded)
{
_api.EndCommandBuffer(_handle).ThrowOnError("vkEndCommandBuffer");
_hasEnded = true;
}
}
public void Submit()
{
Submit(null, null, null);
}
public unsafe void Submit(
ReadOnlySpan<VulkanSemaphore> waitSemaphores,
ReadOnlySpan<VkPipelineStageFlags> waitDstStageMask,
ReadOnlySpan<VulkanSemaphore> signalSemaphores,
VulkanFence? fence = null)
{
EndRecording();
var fenceHandle = (fence ?? _fence).Handle;
_api.ResetFences(_device.Handle, 1, &fenceHandle)
.ThrowOnError("vkResetFences");
var pWaitSempaphores = stackalloc IntPtr[waitSemaphores.Length];
for (var c = 0; c < waitSemaphores.Length; c++)
pWaitSempaphores[c] = waitSemaphores[c].Handle;
var pSignalSemaphores = stackalloc IntPtr[signalSemaphores.Length];
for (var c = 0; c < signalSemaphores.Length; c++)
pSignalSemaphores[c] = signalSemaphores[c].Handle;
IntPtr commandBuffer = _handle;
fixed (VkPipelineStageFlags* flags = waitDstStageMask)
{
var submitInfo = new VkSubmitInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_SUBMIT_INFO,
waitSemaphoreCount = (uint)waitSemaphores.Length,
pWaitSemaphores = pWaitSempaphores,
signalSemaphoreCount = (uint)signalSemaphores.Length,
pSignalSemaphores = pSignalSemaphores,
commandBufferCount = 1,
pCommandBuffers = &commandBuffer,
pWaitDstStageMask = flags
};
_api.QueueSubmit(_device.MainQueueHandle, 1, &submitInfo, fenceHandle)
.ThrowOnError("vkQueueSubmit");
}
}
}

62
src/Avalonia.Vulkan/Interop/VulkanCommandBufferPool.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal class VulkanCommandBufferPool : IDisposable
{
private readonly IVulkanDevice _device;
private readonly VulkanDeviceApi _api;
private readonly List<VulkanCommandBuffer> _commandBuffers = new();
private IntPtr _handle;
public VulkanCommandBufferPool(IVulkanDevice device, VulkanDeviceApi api)
{
_device = device;
_api = api;
var createInfo = new VkCommandPoolCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
flags = VkCommandPoolCreateFlags.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
queueFamilyIndex = device.GraphicsQueueFamilyIndex
};
api.CreateCommandPool(device.Handle, ref createInfo, IntPtr.Zero, out _handle)
.ThrowOnError("vkCreateCommandPool");
}
public void FreeUsedCommandBuffers()
{
foreach (var usedCommandBuffer in _commandBuffers)
usedCommandBuffer.Dispose();
_commandBuffers.Clear();
}
public void Dispose()
{
FreeUsedCommandBuffers();
if (_handle != IntPtr.Zero)
_api.DestroyCommandPool(_device.Handle, _handle, IntPtr.Zero);
_handle = IntPtr.Zero;
}
public unsafe VulkanCommandBuffer CreateCommandBuffer()
{
var commandBufferAllocateInfo = new VkCommandBufferAllocateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
commandPool = _handle,
commandBufferCount = 1,
level = VkCommandBufferLevel.VK_COMMAND_BUFFER_LEVEL_PRIMARY
};
var bufferHandle = IntPtr.Zero;
_api.AllocateCommandBuffers(_device.Handle, ref commandBufferAllocateInfo,
&bufferHandle).ThrowOnError("vkAllocateCommandBuffers");
return new VulkanCommandBuffer(this, bufferHandle, _device, _api);
}
internal void OnCommandBufferDisposed(VulkanCommandBuffer buffer) => _commandBuffers.Remove(buffer);
}

37
src/Avalonia.Vulkan/Interop/VulkanDebugLogger.cs

@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Logging;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan.Interop;
unsafe static class VulkanDebugLogger
{
private static VkDebugUtilsMessengerCallbackEXTDelegate s_Delegate = WriteLogEvent;
public static IntPtr CallbackPtr { get; } = Marshal.GetFunctionPointerForDelegate(s_Delegate);
private static uint WriteLogEvent(VkDebugUtilsMessageSeverityFlagsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messagetypes,
VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* puserdata)
{
var level = messageSeverity switch
{
VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT => LogEventLevel.Error,
VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT =>
LogEventLevel.Warning,
VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT =>
LogEventLevel.Verbose,
VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT => LogEventLevel
.Information,
_ => LogEventLevel.Information
};
if (Logger.TryGet(level, "Vulkan", out var logger))
{
var msg =Marshal.PtrToStringAnsi((nint)pCallbackData->pMessage);
logger.Log("Vulkan", "Vulkan: {0}", msg);
}
return 0;
}
}

163
src/Avalonia.Vulkan/Interop/VulkanDevice.Create.cs

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal unsafe partial class VulkanDevice
{
public static unsafe IVulkanDevice Create(IVulkanInstance instance,
VulkanDeviceCreationOptions options, VulkanPlatformSpecificOptions platformOptions)
{
uint deviceCount = 0;
var api = new VulkanInstanceApi(instance);
api.EnumeratePhysicalDevices(api.Instance.Handle, ref deviceCount, null)
.ThrowOnError("vkEnumeratePhysicalDevices");
if (deviceCount == 0)
throw new VulkanException("No devices found");
var devices = stackalloc IntPtr[(int)deviceCount];
api.EnumeratePhysicalDevices(api.Instance.Handle, ref deviceCount, devices)
.ThrowOnError("vkEnumeratePhysicalDevices");
var surfaceForProbe = platformOptions.DeviceCheckSurfaceFactory?.Invoke(api.Instance);
DeviceInfo? compatibleDevice = null, discreteDevice = null;
for (var c = 0; c < deviceCount; c++)
{
var info = CheckDevice(api, devices[c], options, surfaceForProbe);
if (info != null)
{
compatibleDevice ??= info;
if (info.Value.Type == VkPhysicalDeviceType.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
discreteDevice ??= info;
}
if (compatibleDevice != null && (discreteDevice != null || !options.PreferDiscreteGpu))
break;
}
if (options.PreferDiscreteGpu && discreteDevice != null)
compatibleDevice = discreteDevice;
if (compatibleDevice == null)
throw new VulkanException("No compatible devices found");
var dev = compatibleDevice.Value;
var queuePriorities = stackalloc float[(int)dev.QueueCount];
for (var c = 0; c < dev.QueueCount; c++)
queuePriorities[c] = 1f;
var queueCreateInfo = new VkDeviceQueueCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
queueFamilyIndex = dev.QueueFamilyIndex,
queueCount = dev.QueueCount,
pQueuePriorities = queuePriorities,
};
using var enabledExtensions = new Utf8BufferArray(options.DeviceExtensions
.Intersect(dev.Extensions).Append(VK_KHR_swapchain).Distinct());
var createInfo = new VkDeviceCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
queueCreateInfoCount = 1,
pQueueCreateInfos = &queueCreateInfo,
ppEnabledExtensionNames = enabledExtensions,
enabledExtensionCount = enabledExtensions.UCount,
};
api.CreateDevice(dev.PhysicalDevice, ref createInfo, IntPtr.Zero, out var createdDevice)
.ThrowOnError("vkCreateDevice");
api.GetDeviceQueue(createdDevice, dev.QueueFamilyIndex, 0, out var createdQueue);
return new VulkanDevice(api.Instance, createdDevice, dev.PhysicalDevice, createdQueue,
dev.QueueFamilyIndex, platformOptions.PlatformFeatures);
}
struct DeviceInfo
{
public IntPtr PhysicalDevice;
public uint QueueFamilyIndex;
public VkPhysicalDeviceType Type;
public List<string> Extensions;
public uint QueueCount;
}
static unsafe List<string> GetDeviceExtensions(VulkanInstanceApi instance, IntPtr physicalDevice)
{
uint propertyCount = 0;
instance.EnumerateDeviceExtensionProperties(physicalDevice, null, ref propertyCount, null);
var extensionProps = new VkExtensionProperties[propertyCount];
var extensions = new List<string>((int)propertyCount);
if (propertyCount != 0)
fixed (VkExtensionProperties* ptr = extensionProps)
{
instance.EnumerateDeviceExtensionProperties(physicalDevice, null, ref propertyCount, ptr);
for (var c = 0; c < propertyCount; c++)
extensions.Add(Marshal.PtrToStringAnsi(new IntPtr(ptr[c].extensionName))!);
}
return extensions;
}
private const string VK_KHR_swapchain = "VK_KHR_swapchain";
static unsafe DeviceInfo? CheckDevice(VulkanInstanceApi instance, IntPtr physicalDevice,
VulkanDeviceCreationOptions options, IntPtr? surface)
{
instance.GetPhysicalDeviceProperties(physicalDevice, out var properties);
var supportedExtensions = GetDeviceExtensions(instance, physicalDevice);
var foundSwapchain = false;
foreach (var ext in supportedExtensions)
if (ext == VK_KHR_swapchain)
foundSwapchain = true;
if (!foundSwapchain)
return null;
uint familyCount = 0;
instance.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref familyCount, null);
var familyProperties = stackalloc VkQueueFamilyProperties[(int)familyCount];
instance.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref familyCount, familyProperties);
var requredFlags = VkQueueFlags.VK_QUEUE_GRAPHICS_BIT;
if (options.RequireComputeBit)
requredFlags |= VkQueueFlags.VK_QUEUE_COMPUTE_BIT;
for (var c = 0; c < familyCount; c++)
{
if ((familyProperties[c].queueFlags & requredFlags) != requredFlags)
continue;
if (surface.HasValue)
{
instance.GetPhysicalDeviceSurfaceSupportKHR(physicalDevice, (uint)c, surface.Value, out var supported)
.ThrowOnError("vkGetPhysicalDeviceSurfaceSupportKHR");
if (supported == 0)
continue;
}
return new DeviceInfo
{
PhysicalDevice = physicalDevice,
Extensions = supportedExtensions,
Type = properties.deviceType,
QueueFamilyIndex = (uint)c,
QueueCount = familyProperties[c].queueCount
};
}
return null;
}
}

66
src/Avalonia.Vulkan/Interop/VulkanDevice.cs

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Vulkan.Interop;
namespace Avalonia.Vulkan;
internal partial class VulkanDevice : IVulkanDevice
{
private readonly IntPtr _handle;
private readonly IntPtr _physicalDeviceHandle;
private readonly IntPtr _mainQueue;
private readonly uint _graphicsQueueIndex;
private readonly Dictionary<Type, object> _features;
private readonly object _lock = new();
private Thread? _lockedByThread;
private VulkanDevice(IVulkanInstance instance, IntPtr handle, IntPtr physicalDeviceHandle,
IntPtr mainQueue, uint graphicsQueueIndex, Dictionary<Type, object> features)
{
_handle = handle;
_physicalDeviceHandle = physicalDeviceHandle;
_mainQueue = mainQueue;
_graphicsQueueIndex = graphicsQueueIndex;
_features = features;
Instance = instance;
}
T CheckAccess<T>(T f)
{
if (_lockedByThread != Thread.CurrentThread)
throw new InvalidOperationException("This class is only usable when locked");
return f;
}
public IDisposable Lock()
{
Monitor.Enter(_lock);
var oldLockedBy = _lockedByThread;
_lockedByThread = Thread.CurrentThread;
return Disposable.Create(() =>
{
_lockedByThread = null;
Monitor.Exit(_lock);
});
}
public bool IsLost => false;
public IntPtr Handle => CheckAccess(_handle);
public IntPtr PhysicalDeviceHandle => CheckAccess(_physicalDeviceHandle);
public IntPtr MainQueueHandle => CheckAccess(_mainQueue);
public uint GraphicsQueueFamilyIndex => CheckAccess(_graphicsQueueIndex);
public IVulkanInstance Instance { get; }
public void Dispose()
{
// TODO
}
public object? TryGetFeature(Type featureType) => null;
}

328
src/Avalonia.Vulkan/Interop/VulkanDisplay.cs

@ -0,0 +1,328 @@
using System;
using System.Linq;
using System.Threading;
using Avalonia.Vulkan.UnmanagedInterop;
// ReSharper disable FieldCanBeMadeReadOnly.Local
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
namespace Avalonia.Vulkan.Interop;
internal class VulkanDisplay : IDisposable
{
private IVulkanPlatformGraphicsContext _context;
private VulkanSemaphorePair _semaphorePair;
private uint _nextImage;
private readonly VulkanKhrSurface _surface;
private VkSurfaceFormatKHR _surfaceFormat;
private VkSwapchainKHR _swapchain;
private VkExtent2D _swapchainExtent;
private IntPtr[] _swapchainImages = Array.Empty<IntPtr>();
private IntPtr[] _swapchainImageViews = Array.Empty<IntPtr>();
public VulkanCommandBufferPool CommandBufferPool { get; private set; }
public PixelSize Size { get; private set; }
public uint QueueFamilyIndex => _context.Device.GraphicsQueueFamilyIndex;
private VulkanDisplay(IVulkanPlatformGraphicsContext context, VulkanKhrSurface surface, VkSwapchainKHR swapchain,
VkExtent2D swapchainExtent)
{
_context = context;
_surface = surface;
_swapchain = swapchain;
_swapchainExtent = swapchainExtent;
_semaphorePair = new VulkanSemaphorePair(_context.Device, _context.DeviceApi);
CommandBufferPool = new VulkanCommandBufferPool(_context.Device, _context.DeviceApi);
CreateSwapchainImages();
}
internal VkSurfaceFormatKHR SurfaceFormat
{
get
{
if (_surfaceFormat.format == VkFormat.VK_FORMAT_UNDEFINED)
_surfaceFormat = _surface.GetSurfaceFormat();
return _surfaceFormat;
}
}
private static unsafe IntPtr CreateSwapchain(IVulkanPlatformGraphicsContext context,
VulkanKhrSurface surface, out VkExtent2D swapchainExtent, VulkanDisplay? oldDisplay = null)
{
while (!surface.CanSurfacePresent())
Thread.Sleep(16);
context.InstanceApi.GetPhysicalDeviceSurfaceCapabilitiesKHR(context.Device.PhysicalDeviceHandle,
surface.Handle, out var capabilities)
.ThrowOnError("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
uint presentModesCount = 0;
context.InstanceApi.GetPhysicalDeviceSurfacePresentModesKHR(context.Device.PhysicalDeviceHandle,
surface.Handle, ref presentModesCount, null)
.ThrowOnError("vkGetPhysicalDeviceSurfacePresentModesKHR");
var modes = new VkPresentModeKHR[(int)presentModesCount];
fixed (VkPresentModeKHR* pModes = modes)
context.InstanceApi.GetPhysicalDeviceSurfacePresentModesKHR(context.Device.PhysicalDeviceHandle,
surface.Handle, ref presentModesCount, pModes)
.ThrowOnError("vkGetPhysicalDeviceSurfacePresentModesKHR");
var imageCount = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount)
imageCount = capabilities.maxImageCount;
var surfaceFormat = surface.GetSurfaceFormat();
bool supportsIdentityTransform = capabilities.supportedTransforms.HasAllFlags(
VkSurfaceTransformFlagsKHR.VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR);
bool isRotated =
capabilities.currentTransform.HasAllFlags(VkSurfaceTransformFlagsKHR.VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR)
|| capabilities.currentTransform.HasAllFlags(VkSurfaceTransformFlagsKHR
.VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR);
if (capabilities.currentExtent.width != uint.MaxValue)
swapchainExtent = capabilities.currentExtent;
else
{
var surfaceSize = surface.Size;
var width = Math.Max(capabilities.minImageExtent.width,
Math.Min(capabilities.maxImageExtent.width, (uint)surfaceSize.Width));
var height = Math.Max(capabilities.minImageExtent.height,
Math.Min(capabilities.maxImageExtent.height, (uint)surfaceSize.Height));
swapchainExtent = new VkExtent2D
{
width = width,
height = height
};
}
VkPresentModeKHR presentMode;
if (modes.Contains(VkPresentModeKHR.VK_PRESENT_MODE_MAILBOX_KHR))
presentMode = VkPresentModeKHR.VK_PRESENT_MODE_MAILBOX_KHR;
else if (modes.Contains(VkPresentModeKHR.VK_PRESENT_MODE_FIFO_KHR))
presentMode = VkPresentModeKHR.VK_PRESENT_MODE_FIFO_KHR;
else
presentMode = VkPresentModeKHR.VK_PRESENT_MODE_IMMEDIATE_KHR;
var swapchainCreateInfo = new VkSwapchainCreateInfoKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
surface = surface.Handle,
minImageCount = imageCount,
imageFormat = surfaceFormat.format,
imageColorSpace = surfaceFormat.colorSpace,
imageExtent = swapchainExtent,
imageUsage = VkImageUsageFlags.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VkImageUsageFlags.VK_IMAGE_USAGE_TRANSFER_DST_BIT,
imageSharingMode = VkSharingMode.VK_SHARING_MODE_EXCLUSIVE,
imageArrayLayers = 1,
preTransform = supportsIdentityTransform && isRotated
? VkSurfaceTransformFlagsKHR.VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
: capabilities.currentTransform,
compositeAlpha = VkCompositeAlphaFlagsKHR.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
presentMode = presentMode,
clipped = 1,
oldSwapchain = oldDisplay?._swapchain ?? default
};
context.DeviceApi.CreateSwapchainKHR(context.Device.Handle, ref swapchainCreateInfo, IntPtr.Zero,
out var swapchain).ThrowOnError("vkCreateSwapchainKHR");
oldDisplay?.DestroySwapchain();
return swapchain;
}
private void DestroySwapchain()
{
if(_swapchain != IntPtr.Zero)
_context.DeviceApi.DestroySwapchainKHR(_context.Device.Handle, _swapchain, IntPtr.Zero);
_swapchain = IntPtr.Zero;
}
internal static VulkanDisplay CreateDisplay(IVulkanPlatformGraphicsContext context, VulkanKhrSurface surface)
{
var swapchain = CreateSwapchain(context, surface, out var extent);
return new VulkanDisplay(context, surface, swapchain, extent);
}
private unsafe void DestroyCurrentImageViews()
{
if (_swapchainImageViews.Length <= 0)
return;
for (var i = 0; i < _swapchainImageViews.Length; i++)
_context.DeviceApi.DestroyImageView(_context.Device.Handle, _swapchainImageViews[i], IntPtr.Zero);
_swapchainImageViews = Array.Empty<IntPtr>();
}
private unsafe void CreateSwapchainImages()
{
DestroyCurrentImageViews();
Size = new PixelSize((int)_swapchainExtent.width, (int)_swapchainExtent.height);
uint imageCount = 0;
_context.DeviceApi.GetSwapchainImagesKHR(_context.Device.Handle, _swapchain, ref imageCount, null)
.ThrowOnError("vkGetSwapchainImagesKHR");
_swapchainImages = new IntPtr[imageCount];
fixed (IntPtr* pImages = _swapchainImages)
_context.DeviceApi.GetSwapchainImagesKHR(_context.Device.Handle, _swapchain, ref imageCount,
pImages).ThrowOnError("vkGetSwapchainImagesKHR");
_swapchainImageViews = new IntPtr[imageCount];
for (var c = 0; c < imageCount; c++)
_swapchainImageViews[c] = CreateSwapchainImageView(_swapchainImages[c], SurfaceFormat.format);
}
private unsafe IntPtr CreateSwapchainImageView(IntPtr swapchainImage, VkFormat format)
{
var imageViewCreateInfo = new VkImageViewCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
subresourceRange =
{
aspectMask = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT,
levelCount = 1,
layerCount = 1
},
format = format,
image = swapchainImage,
viewType = VkImageViewType.VK_IMAGE_VIEW_TYPE_2D,
};
_context.DeviceApi.CreateImageView(_context.Device.Handle, ref imageViewCreateInfo,
IntPtr.Zero, out var imageView).ThrowOnError("vkCreateImageView");
return imageView;
}
private void Recreate()
{
_context.DeviceApi.DeviceWaitIdle(_context.Device.Handle);
_swapchain = CreateSwapchain(_context, _surface, out var extent, this);
_swapchainExtent = extent;
CreateSwapchainImages();
}
public bool EnsureSwapchainAvailable()
{
if (Size != _surface.Size)
{
Recreate();
return false;
}
return true;
}
public unsafe VulkanCommandBuffer StartPresentation()
{
_nextImage = 0;
while (true)
{
var acquireResult = _context.DeviceApi.AcquireNextImageKHR(
_context.Device.Handle,
_swapchain,
ulong.MaxValue,
_semaphorePair.ImageAvailableSemaphore.Handle,
default, out _nextImage);
if (acquireResult is VkResult.VK_ERROR_OUT_OF_DATE_KHR or VkResult.VK_SUBOPTIMAL_KHR)
Recreate();
else
{
acquireResult.ThrowOnError("vkAcquireNextImageKHR");
break;
}
}
var commandBuffer = CommandBufferPool.CreateCommandBuffer();
commandBuffer.BeginRecording();
VulkanMemoryHelper.TransitionLayout(_context, commandBuffer,
_swapchainImages[_nextImage], VkImageLayout.VK_IMAGE_LAYOUT_UNDEFINED,
VkAccessFlags.VK_ACCESS_NONE, VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VkAccessFlags.VK_ACCESS_TRANSFER_WRITE_BIT, 1);
return commandBuffer;
}
internal unsafe void BlitImageToCurrentImage(VulkanCommandBuffer commandBuffer, VulkanImage image)
{
VulkanMemoryHelper.TransitionLayout(_context, commandBuffer,
image.Handle, image.CurrentLayout, VkAccessFlags.VK_ACCESS_NONE,
VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VkAccessFlags.VK_ACCESS_TRANSFER_READ_BIT,
image.MipLevels);
var srcBlitRegion = new VkImageBlit
{
srcOffsets2 =
{
x = image.Size.Width,
y = image.Size.Height,
z = 1
},
dstOffsets2 =
{
x = Size.Width,
y = Size.Height
},
srcSubresource =
{
aspectMask = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT,
layerCount = 1
},
dstSubresource =
{
aspectMask = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT,
layerCount = 1
}
};
_context.DeviceApi.CmdBlitImage(commandBuffer.Handle, image.Handle,
VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
_swapchainImages[_nextImage],
VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &srcBlitRegion, VkFilter.VK_FILTER_LINEAR);
VulkanMemoryHelper.TransitionLayout(_context, commandBuffer,
image.Handle, VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VkAccessFlags.VK_ACCESS_TRANSFER_READ_BIT,
image.CurrentLayout, VkAccessFlags.VK_ACCESS_NONE, image.MipLevels);
}
internal unsafe void EndPresentation(VulkanCommandBuffer commandBuffer)
{
VulkanMemoryHelper.TransitionLayout(_context, commandBuffer,
_swapchainImages[_nextImage],
VkImageLayout.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VkAccessFlags.VK_ACCESS_NONE,
VkImageLayout.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VkAccessFlags.VK_ACCESS_NONE,
1);
commandBuffer.Submit(new[] { _semaphorePair.ImageAvailableSemaphore },
new[] { VkPipelineStageFlags.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT },
new[] { _semaphorePair.RenderFinishedSemaphore });
var semaphore = (VkSemaphore)_semaphorePair.RenderFinishedSemaphore.Handle;
var swapchain = _swapchain;
var nextImage = _nextImage;
VkResult result;
var presentInfo = new VkPresentInfoKHR
{
sType = VkStructureType.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
waitSemaphoreCount = 1,
pWaitSemaphores = &semaphore,
swapchainCount = 1,
pSwapchains = &swapchain,
pImageIndices = &nextImage,
pResults = &result
};
_context.DeviceApi.vkQueuePresentKHR(_context.Device.MainQueueHandle, ref presentInfo)
.ThrowOnError("vkQueuePresentKHR");
result.ThrowOnError("vkQueuePresentKHR");
CommandBufferPool.FreeUsedCommandBuffers();
}
public unsafe void Dispose()
{
_context.DeviceApi.DeviceWaitIdle(_context.Device.Handle);
_semaphorePair?.Dispose();
DestroyCurrentImageViews();
DestroySwapchain();
CommandBufferPool?.Dispose();
CommandBufferPool = null!;
}
}

47
src/Avalonia.Vulkan/Interop/VulkanFence.cs

@ -0,0 +1,47 @@
using System;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal struct VulkanFence : IDisposable
{
private readonly VulkanDeviceApi _api;
private readonly IVulkanDevice _device;
private IntPtr _handle;
public VulkanFence(VulkanDeviceApi api, IVulkanDevice device, IntPtr handle)
{
_api = api;
_device = device;
_handle = handle;
}
public VulkanFence(IVulkanDevice device, VulkanDeviceApi api, VkFenceCreateFlags vkFenceCreateSignaledBit)
{
_device = device;
_api = api;
var fenceCreateInfo = new VkFenceCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
flags = VkFenceCreateFlags.VK_FENCE_CREATE_SIGNALED_BIT
};
_api.CreateFence(_device.Handle, ref fenceCreateInfo, IntPtr.Zero, out _handle)
.ThrowOnError("vkCreateFence");
}
public IntPtr Handle => _handle;
public void Dispose()
{
_api.DestroyFence(_device.Handle, _handle, IntPtr.Zero);
_handle = IntPtr.Zero;
}
public unsafe void Wait(ulong timeout = ulong.MaxValue)
{
IntPtr fence = _handle;
_api.WaitForFences(_device.Handle, 1, &fence, 1, timeout).ThrowOnError("vkWaitForFences");
}
}

152
src/Avalonia.Vulkan/Interop/VulkanImage.cs

@ -0,0 +1,152 @@
using System;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal class VulkanImage : IDisposable
{
private IVulkanPlatformGraphicsContext _context;
private VkAccessFlags _currentAccessFlags;
public VkImageUsageFlags UsageFlags { get; }
private IntPtr _imageView;
private IntPtr _imageMemory;
private IntPtr _handle;
private VulkanCommandBufferPool _commandBufferPool;
internal IntPtr Handle => _handle;
internal VkFormat Format { get; }
internal VkImageAspectFlags AspectFlags { get; private set; }
public uint MipLevels { get; private set; }
public PixelSize Size { get; }
public ulong MemorySize { get; private set; }
public VkImageLayout CurrentLayout { get; private set; }
public IntPtr MemoryHandle => _imageMemory;
public VkImageTiling Tiling => VkImageTiling.VK_IMAGE_TILING_OPTIMAL;
public VulkanImage(IVulkanPlatformGraphicsContext context,
VulkanCommandBufferPool commandBufferPool,
VkFormat format, PixelSize size, uint mipLevels = 0)
{
Format = format;
Size = size;
MipLevels = mipLevels;
_context = context;
_commandBufferPool = commandBufferPool;
UsageFlags = VkImageUsageFlags.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VkImageUsageFlags.VK_IMAGE_USAGE_TRANSFER_DST_BIT
| VkImageUsageFlags.VK_IMAGE_USAGE_TRANSFER_SRC_BIT
| VkImageUsageFlags.VK_IMAGE_USAGE_SAMPLED_BIT;
Initialize();
}
public unsafe void Initialize()
{
if (Handle != IntPtr.Zero)
return;
MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
var createInfo = new VkImageCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
imageType = VkImageType.VK_IMAGE_TYPE_2D,
format = Format,
extent = new VkExtent3D
{
depth = 1,
width = (uint)Size.Width,
height = (uint)Size.Height
},
mipLevels = MipLevels,
arrayLayers = 1,
samples = VkSampleCountFlags.VK_SAMPLE_COUNT_1_BIT,
tiling = Tiling,
usage = UsageFlags,
sharingMode = VkSharingMode.VK_SHARING_MODE_EXCLUSIVE,
initialLayout = VkImageLayout.VK_IMAGE_LAYOUT_UNDEFINED,
flags = VkImageCreateFlags.VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT
};
_context.DeviceApi.CreateImage(_context.Device.Handle, ref createInfo, IntPtr.Zero, out _handle)
.ThrowOnError("vkCreateImage");
_context.DeviceApi.GetImageMemoryRequirements(_context.Device.Handle, _handle, out var memoryRequirements);
var memoryAllocateInfo = new VkMemoryAllocateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
allocationSize = memoryRequirements.size,
memoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(_context,
memoryRequirements.memoryTypeBits,
VkMemoryPropertyFlags.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
};
_context.DeviceApi.AllocateMemory(_context.Device.Handle, ref memoryAllocateInfo, IntPtr.Zero,
out _imageMemory).ThrowOnError("vkAllocateMemory");
_context.DeviceApi.BindImageMemory(_context.Device.Handle, _handle, _imageMemory, 0)
.ThrowOnError("vkBindImageMemory");
MemorySize = memoryRequirements.size;
AspectFlags = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT;
var imageViewCreateInfo = new VkImageViewCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
components = new(),
subresourceRange = new()
{
aspectMask = AspectFlags,
levelCount = MipLevels,
layerCount = 1
},
format = Format,
image = _handle,
viewType = VkImageViewType.VK_IMAGE_VIEW_TYPE_2D
};
_context.DeviceApi.CreateImageView(_context.Device.Handle, ref imageViewCreateInfo,
IntPtr.Zero, out _imageView).ThrowOnError("vkCreateImageView");
CurrentLayout = VkImageLayout.VK_IMAGE_LAYOUT_UNDEFINED;
TransitionLayout(VkImageLayout.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VkAccessFlags.VK_ACCESS_NONE_KHR);
}
internal void TransitionLayout(VkImageLayout destinationLayout, VkAccessFlags destinationAccessFlags)
{
var commandBuffer = _commandBufferPool!.CreateCommandBuffer();
commandBuffer.BeginRecording();
VulkanMemoryHelper.TransitionLayout(_context, commandBuffer, Handle,
CurrentLayout, _currentAccessFlags, destinationLayout, destinationAccessFlags,
MipLevels);
commandBuffer.EndRecording();
commandBuffer.Submit();
CurrentLayout = destinationLayout;
_currentAccessFlags = destinationAccessFlags;
}
public void TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
{
TransitionLayout((VkImageLayout)destinationLayout, (VkAccessFlags)destinationAccessFlags);
}
public void Dispose()
{
var api = _context.DeviceApi;
var d = _context.Device.Handle;
if (_imageView != IntPtr.Zero)
{
api.DestroyImageView(d, _imageView, IntPtr.Zero);
_imageView = IntPtr.Zero;
}
if (_handle != IntPtr.Zero)
{
api.DestroyImage(d, _handle, IntPtr.Zero);
_handle = IntPtr.Zero;
}
if (_imageMemory != IntPtr.Zero)
{
api.FreeMemory(d, _imageMemory, IntPtr.Zero);
_imageMemory = IntPtr.Zero;
}
}
}

134
src/Avalonia.Vulkan/Interop/VulkanInstance.cs

@ -0,0 +1,134 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal class VulkanInstance : IVulkanInstance
{
private readonly VkGetInstanceProcAddressDelegate _getProcAddress;
private readonly VulkanInstanceApi _api;
public VulkanInstance(IntPtr handle, VkGetInstanceProcAddressDelegate getProcAddress)
{
Handle = handle;
_getProcAddress = getProcAddress;
_api = new VulkanInstanceApi(this);
}
internal static unsafe IVulkanInstance Create(
VulkanInstanceCreationOptions options,
VulkanPlatformSpecificOptions platformOptions)
{
var getProcAddress = options.CustomGetProcAddressDelegate ??
platformOptions.GetProcAddressDelegate ??
throw new ArgumentException("No VkGetInstanceProcAddr provided");
using var name = new Utf8Buffer(options.ApplicationName ?? "AvaloniaUI");
using var engineName = new Utf8Buffer("AvaloniaUI");
var gapi = new UnmanagedInterop.VulkanGlobalApi(getProcAddress);
var appInfo = new VkApplicationInfo()
{
sType = VkStructureType.VK_STRUCTURE_TYPE_APPLICATION_INFO,
apiVersion = VulkanHelpers.MakeVersion(options.VulkanVersion),
applicationVersion = VulkanHelpers.MakeVersion(1, 0, 0),
engineVersion = VulkanHelpers.MakeVersion(1, 0, 0),
pApplicationName = name,
pEngineName = engineName,
};
var enabledExtensions = options.InstanceExtensions.Concat(platformOptions.RequiredInstanceExtensions)
.Append("VK_KHR_surface").ToList();
var enabledLayers = options.EnabledLayers.ToList();
if (options.UseDebug)
{
enabledExtensions.Add("VK_EXT_debug_utils");
if (IsLayerAvailable(gapi, "VK_LAYER_KHRONOS_validation"))
enabledLayers.Add("VK_LAYER_KHRONOS_validation");
}
using var enabledExtensionBuffers = new Utf8BufferArray(
enabledExtensions
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct());
using var enabledLayersBuffers = new Utf8BufferArray(enabledLayers
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct());
var createInfo = new VkInstanceCreateInfo()
{
sType = VkStructureType.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
pApplicationInfo = &appInfo,
ppEnabledLayerNames = enabledLayersBuffers,
enabledLayerCount = enabledLayersBuffers.UCount,
ppEnabledExtensionNames = enabledExtensionBuffers,
enabledExtensionCount = enabledExtensionBuffers.UCount
};
gapi.vkCreateInstance(ref createInfo, IntPtr.Zero, out var pInstance)
.ThrowOnError(nameof(gapi.vkCreateInstance));
var instance = new VulkanInstance(pInstance, getProcAddress);
var instanceApi = new VulkanInstanceApi(instance);
if (options.UseDebug)
{
var debugCreateInfo = new VkDebugUtilsMessengerCreateInfoEXT
{
sType = VkStructureType.VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
messageSeverity = VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
|VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
|VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
|VkDebugUtilsMessageSeverityFlagsEXT.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
messageType = VkDebugUtilsMessageTypeFlagsEXT.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
|VkDebugUtilsMessageTypeFlagsEXT.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
|VkDebugUtilsMessageTypeFlagsEXT.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
pfnUserCallback = VulkanDebugLogger.CallbackPtr
};
instanceApi.CreateDebugUtilsMessengerEXT(pInstance, ref debugCreateInfo, IntPtr.Zero, out var messenger);
}
return instance;
}
private static unsafe bool IsLayerAvailable(VulkanGlobalApi api, string layerName)
{
uint layerPropertiesCount = 0;
api.EnumerateInstanceLayerProperties(ref layerPropertiesCount, null)
.ThrowOnError("vkEnumerateInstanceLayerProperties");
var layerProperties = new VkLayerProperties[layerPropertiesCount];
fixed (VkLayerProperties* pLayerProperties = layerProperties)
{
api.EnumerateInstanceLayerProperties(ref layerPropertiesCount, pLayerProperties)
.ThrowOnError("vkEnumerateInstanceLayerProperties");
for (var i = 0; i < layerPropertiesCount; i++)
{
var currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].layerName);
if (currentLayerName == layerName) return true;
}
}
return false;
}
public IntPtr Handle { get; }
public IntPtr GetInstanceProcAddress(IntPtr instance, string name) => _getProcAddress(instance, name);
public IntPtr GetDeviceProcAddress(IntPtr device, string name)
{
using var buf = new Utf8Buffer(name);
return _api.GetDeviceProcAddr(device, buf);
}
}

69
src/Avalonia.Vulkan/Interop/VulkanKhrSurface.cs

@ -0,0 +1,69 @@
using System;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan.Interop;
internal class VulkanKhrSurface : IDisposable
{
private readonly IVulkanPlatformGraphicsContext _context;
private readonly IVulkanKhrSurfacePlatformSurface _surfaceInfo;
private IntPtr _handle;
public IntPtr Handle => _handle;
public VulkanKhrSurface(IVulkanPlatformGraphicsContext context, IVulkanKhrSurfacePlatformSurface surfaceInfo)
{
_context = context;
_surfaceInfo = surfaceInfo;
_handle = surfaceInfo.CreateSurface(context);
}
internal bool CanSurfacePresent()
{
_context.InstanceApi.GetPhysicalDeviceSurfaceSupportKHR(_context.Device.PhysicalDeviceHandle,
_context.Device.GraphicsQueueFamilyIndex, _handle, out var isSupported)
.ThrowOnError("vkGetPhysicalDeviceSurfaceSupportKHR");
return isSupported != 0;
}
internal unsafe VkSurfaceFormatKHR GetSurfaceFormat()
{
uint surfaceFormatsCount = 0;
_context.InstanceApi.GetPhysicalDeviceSurfaceFormatsKHR(_context.Device.PhysicalDeviceHandle,
_handle, ref surfaceFormatsCount, null)
.ThrowOnError("vkGetPhysicalDeviceSurfaceFormatsKHR");
if (surfaceFormatsCount == 0)
throw new VulkanException("vkGetPhysicalDeviceSurfaceFormatsKHR returned 0 formats");
var surfaceFormats = stackalloc VkSurfaceFormatKHR[(int)surfaceFormatsCount];
_context.InstanceApi.GetPhysicalDeviceSurfaceFormatsKHR(_context.Device.PhysicalDeviceHandle,
_handle, ref surfaceFormatsCount, surfaceFormats)
.ThrowOnError("vkGetPhysicalDeviceSurfaceFormatsKHR");
if (surfaceFormatsCount == 1 && surfaceFormats[0].format == VkFormat.VK_FORMAT_UNDEFINED)
return new VkSurfaceFormatKHR
{
format = VkFormat.VK_FORMAT_B8G8R8A8_UNORM,
colorSpace = VkColorSpaceKHR.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
};
for (var c = 0; c < surfaceFormatsCount; c++)
{
if (surfaceFormats[c].format == VkFormat.VK_FORMAT_B8G8R8A8_UNORM
&& surfaceFormats[c].colorSpace == VkColorSpaceKHR.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
return surfaceFormats[c];
}
return surfaceFormats[0];
}
public PixelSize Size => _surfaceInfo.Size;
public void Dispose()
{
if (_handle != IntPtr.Zero)
_context.InstanceApi.DestroySurfaceKHR(_context.Instance.Handle, _handle, IntPtr.Zero);
_handle = IntPtr.Zero;
}
}

67
src/Avalonia.Vulkan/Interop/VulkanMemoryHelper.cs

@ -0,0 +1,67 @@
using System;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan.Interop;
internal static class VulkanMemoryHelper
{
internal static int FindSuitableMemoryTypeIndex(IVulkanPlatformGraphicsContext context, uint memoryTypeBits,
VkMemoryPropertyFlags flags)
{
context.InstanceApi.GetPhysicalDeviceMemoryProperties(context.Device.PhysicalDeviceHandle,
out var properties);
for (var i = 0; i < properties.memoryTypeCount; i++)
{
var type = properties.memoryTypes[i];
if ((memoryTypeBits & (1 << i)) != 0 && type.propertyFlags.HasAllFlags(flags))
return i;
}
return -1;
}
internal static unsafe void TransitionLayout(IVulkanPlatformGraphicsContext context,
VulkanCommandBuffer commandBuffer,
VkImage image,
VkImageLayout sourceLayout,
VkAccessFlags sourceAccessMask,
VkImageLayout destinationLayout,
VkAccessFlags destinationAccessMask,
uint mipLevels)
{
var subresourceRange = new VkImageSubresourceRange
{
aspectMask = VkImageAspectFlags.VK_IMAGE_ASPECT_COLOR_BIT,
levelCount = mipLevels,
layerCount = 1
};
var barrier = new VkImageMemoryBarrier
{
sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
srcAccessMask = sourceAccessMask,
dstAccessMask = destinationAccessMask,
oldLayout = sourceLayout,
newLayout = destinationLayout,
srcQueueFamilyIndex = VulkanHelpers.QueueFamilyIgnored,
dstQueueFamilyIndex = VulkanHelpers.QueueFamilyIgnored,
image = image,
subresourceRange = subresourceRange
};
context.DeviceApi.CmdPipelineBarrier(
commandBuffer.Handle,
VkPipelineStageFlags.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VkPipelineStageFlags.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
0,
IntPtr.Zero,
0,
IntPtr.Zero,
1,
&barrier);
}
}

53
src/Avalonia.Vulkan/Interop/VulkanSemaphore.cs

@ -0,0 +1,53 @@
using System;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
class VulkanSemaphore : IDisposable
{
private readonly IVulkanDevice _device;
private readonly VulkanDeviceApi _api;
private IntPtr _handle;
public IntPtr Handle => _handle;
public VulkanSemaphore(IVulkanDevice device, VulkanDeviceApi api)
{
_device = device;
_api = api;
var info = new VkSemaphoreCreateInfo
{
sType = VkStructureType.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
};
_api.CreateSemaphore(device.Handle, ref info, IntPtr.Zero, out _handle)
.ThrowOnError("vkCreateSemaphore");
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
_api.DestroySemaphore(_device.Handle, _handle, IntPtr.Zero);
_handle = IntPtr.Zero;
}
}
}
internal class VulkanSemaphorePair : IDisposable
{
public unsafe VulkanSemaphorePair(IVulkanDevice device, VulkanDeviceApi api)
{
ImageAvailableSemaphore = new VulkanSemaphore(device, api);
RenderFinishedSemaphore = new VulkanSemaphore(device, api);
}
internal VulkanSemaphore ImageAvailableSemaphore { get; }
internal VulkanSemaphore RenderFinishedSemaphore { get; }
public void Dispose()
{
ImageAvailableSemaphore.Dispose();
RenderFinishedSemaphore.Dispose();
}
}

46
src/Avalonia.Vulkan/UnmanagedInterop/Utf8BufferArray.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.Vulkan.Interop;
internal unsafe class Utf8BufferArray : IDisposable
{
private readonly List<Utf8Buffer> _buffers;
private byte** _bufferArray;
public Utf8BufferArray(IEnumerable<string> strings)
{
_buffers = strings.Select(x => new Utf8Buffer(x)).ToList();
_bufferArray = (byte**)Marshal.AllocHGlobal(_buffers.Count * IntPtr.Size);
for (var c = 0; c < _buffers.Count; c++)
_bufferArray[c] = _buffers[c];
}
public static unsafe implicit operator byte**(Utf8BufferArray a) => a._bufferArray;
public int Count => _buffers.Count;
public uint UCount => (uint)Count;
public void Dispose() => Dispose(true);
void Dispose(bool disposing)
{
if (_bufferArray != null)
Marshal.FreeHGlobal(new IntPtr(_bufferArray));
_bufferArray = null;
if (disposing)
{
foreach (var b in _buffers)
b.Dispose();
_buffers.Clear();
GC.SuppressFinalize(this);
}
}
~Utf8BufferArray()
{
Dispose(false);
}
}

139
src/Avalonia.Vulkan/UnmanagedInterop/VulkanDeviceApi.cs

@ -0,0 +1,139 @@
using System;
using Avalonia.SourceGenerator;
using Avalonia.Vulkan.Interop;
using uint32_t = System.UInt32;
using uint64_t = System.UInt64;
using VkBool32 = System.UInt32;
using VkDeviceSize = System.UInt64;
namespace Avalonia.Vulkan.UnmanagedInterop;
internal unsafe partial class VulkanDeviceApi
{
public VulkanDeviceApi(IVulkanDevice device)
{
Initialize(name =>
{
var addr = device.Instance.GetDeviceProcAddress(device.Handle, name);
if (addr != IntPtr.Zero)
return addr;
return device.Instance.GetInstanceProcAddress(device.Instance.Handle, name);
});
}
[GetProcAddress("vkCreateFence")]
public partial VkResult CreateFence(IntPtr device,
ref VkFenceCreateInfo pCreateInfo,
IntPtr pAllocator,
out IntPtr pFence);
[GetProcAddress("vkDestroyFence")]
public partial void DestroyFence(IntPtr device, IntPtr fence, IntPtr pAllocator);
[GetProcAddress("vkCreateCommandPool")]
public partial VkResult CreateCommandPool(IntPtr device, ref VkCommandPoolCreateInfo pCreateInfo,
IntPtr pAllocator, out IntPtr pCommandPool);
[GetProcAddress("vkDestroyCommandPool")]
public partial void DestroyCommandPool(IntPtr device, IntPtr pool, IntPtr pAllocator);
[GetProcAddress("vkAllocateCommandBuffers")]
public partial VkResult AllocateCommandBuffers(IntPtr device,
ref VkCommandBufferAllocateInfo pAllocateInfo, IntPtr* pCommandBuffers);
[GetProcAddress("vkFreeCommandBuffers")]
public partial void FreeCommandBuffers(IntPtr device, IntPtr commandPool, uint32_t commandBufferCount,
IntPtr* pCommandBuffers);
[GetProcAddress("vkWaitForFences")]
public partial VkResult WaitForFences(IntPtr device, uint32_t fenceCount, IntPtr* pFences, VkBool32 waitAll,
uint64_t timeout);
[GetProcAddress("vkBeginCommandBuffer")]
public partial VkResult BeginCommandBuffer(IntPtr commandBuffer, ref VkCommandBufferBeginInfo pBeginInfo);
[GetProcAddress("vkEndCommandBuffer")]
public partial VkResult EndCommandBuffer(IntPtr commandBuffer);
[GetProcAddress("vkCreateSemaphore")]
public partial VkResult CreateSemaphore(IntPtr device, ref VkSemaphoreCreateInfo pCreateInfo,
IntPtr pAllocator, out IntPtr pSemaphore);
[GetProcAddress("vkDestroySemaphore")]
public partial void DestroySemaphore(IntPtr device, IntPtr semaphore, IntPtr pAllocator);
[GetProcAddress("vkResetFences")]
public partial VkResult ResetFences(IntPtr device, uint32_t fenceCount, IntPtr* pFences);
[GetProcAddress("vkQueueSubmit")]
public partial VkResult QueueSubmit(IntPtr queue, uint32_t submitCount, VkSubmitInfo* pSubmits,
IntPtr fence);
[GetProcAddress("vkCreateImage")]
public partial VkResult CreateImage(IntPtr device, ref VkImageCreateInfo pCreateInfo, IntPtr pAllocator,
out IntPtr pImage);
[GetProcAddress("vkDestroyImage")]
public partial void DestroyImage(IntPtr device, IntPtr image, IntPtr pAllocator);
[GetProcAddress("vkGetImageMemoryRequirements")]
public partial void GetImageMemoryRequirements(IntPtr device, IntPtr image,
out VkMemoryRequirements pMemoryRequirements);
[GetProcAddress("vkAllocateMemory")]
public partial VkResult AllocateMemory(IntPtr device, ref VkMemoryAllocateInfo pAllocateInfo, IntPtr pAllocator,
out IntPtr pMemory);
[GetProcAddress("vkFreeMemory")]
public partial void FreeMemory(IntPtr device, IntPtr memory, IntPtr pAllocator);
[GetProcAddress("vkBindImageMemory")]
public partial VkResult BindImageMemory(IntPtr device, IntPtr image, IntPtr memory, VkDeviceSize memoryOffset);
[GetProcAddress("vkCreateImageView")]
public partial VkResult CreateImageView(IntPtr device, ref VkImageViewCreateInfo pCreateInfo, IntPtr pAllocator,
out IntPtr pView);
[GetProcAddress("vkDestroyImageView")]
public partial void DestroyImageView(IntPtr device, IntPtr imageView, IntPtr pAllocator);
[GetProcAddress("vkCmdPipelineBarrier")]
public partial void CmdPipelineBarrier(IntPtr commandBuffer,
VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
VkDependencyFlags dependencyFlags,
uint32_t memoryBarrierCount,
IntPtr pMemoryBarriers,
uint32_t bufferMemoryBarrierCount,
IntPtr pBufferMemoryBarriers,
uint32_t imageMemoryBarrierCount,
VkImageMemoryBarrier* pImageMemoryBarriers);
[GetProcAddress("vkCreateSwapchainKHR")]
public partial VkResult CreateSwapchainKHR(IntPtr device, ref VkSwapchainCreateInfoKHR pCreateInfo,
IntPtr pAllocator, out IntPtr pSwapchain);
[GetProcAddress("vkDestroySwapchainKHR")]
public partial void DestroySwapchainKHR(IntPtr device, IntPtr swapchain, IntPtr pAllocator);
[GetProcAddress("vkGetSwapchainImagesKHR")]
public partial VkResult GetSwapchainImagesKHR(IntPtr device, IntPtr swapchain, ref uint32_t pSwapchainImageCount,
IntPtr* pSwapchainImages);
[GetProcAddress("vkDeviceWaitIdle")]
public partial VkResult DeviceWaitIdle(IntPtr device);
[GetProcAddress("vkAcquireNextImageKHR")]
public partial VkResult AcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout,
VkSemaphore semaphore, VkFence fence, out uint32_t pImageIndex);
[GetProcAddress("vkCmdBlitImage")]
public partial void CmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, VkImageBlit* pRegions, VkFilter filter);
[GetProcAddress("vkQueuePresentKHR")]
public partial VkResult vkQueuePresentKHR(VkQueue queue, ref VkPresentInfoKHR pPresentInfo);
}

2102
src/Avalonia.Vulkan/UnmanagedInterop/VulkanEnums.cs

File diff suppressed because it is too large

25
src/Avalonia.Vulkan/UnmanagedInterop/VulkanGlobalApi.cs

@ -0,0 +1,25 @@
using System;
using Avalonia.SourceGenerator;
using Avalonia.Vulkan.Interop;
namespace Avalonia.Vulkan.UnmanagedInterop;
internal unsafe partial class VulkanGlobalApi
{
private readonly VkGetInstanceProcAddressDelegate _vkGetProcAddress;
public VulkanGlobalApi(VkGetInstanceProcAddressDelegate vkGetProcAddress)
{
_vkGetProcAddress = vkGetProcAddress;
Initialize(name => vkGetProcAddress(IntPtr.Zero, name));
}
public IntPtr GetProcAddress(IntPtr instance, string name) => _vkGetProcAddress(instance, name);
[GetProcAddress("vkEnumerateInstanceLayerProperties")]
public partial VkResult EnumerateInstanceLayerProperties(ref uint pPropertyCount, VkLayerProperties* pProperties);
[GetProcAddress("vkCreateInstance")]
public partial VkResult vkCreateInstance(ref VkInstanceCreateInfo pCreateInfo, IntPtr pAllocator, out IntPtr pInstance);
}

75
src/Avalonia.Vulkan/UnmanagedInterop/VulkanInstanceApi.cs

@ -0,0 +1,75 @@
using System;
using Avalonia.SourceGenerator;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
using uint32_t = System.UInt32;
using VkBool32 = System.UInt32;
namespace Avalonia.Vulkan;
internal unsafe partial class VulkanInstanceApi
{
public IVulkanInstance Instance { get; }
public VulkanInstanceApi(IVulkanInstance instance)
{
Instance = instance;
Initialize(name => instance.GetInstanceProcAddress(instance.Handle, name));
}
[GetProcAddress("vkCreateDebugUtilsMessengerEXT", true)]
public partial VkResult CreateDebugUtilsMessengerEXT(IntPtr instance,
ref VkDebugUtilsMessengerCreateInfoEXT pCreateInfo, IntPtr pAllocator, out IntPtr pMessenger);
[GetProcAddress("vkEnumeratePhysicalDevices")]
public partial VkResult EnumeratePhysicalDevices(IntPtr instance, ref uint32_t pPhysicalDeviceCount,
IntPtr* pPhysicalDevices);
[GetProcAddress("vkGetPhysicalDeviceProperties")]
public partial void GetPhysicalDeviceProperties(IntPtr physicalDevice, out VkPhysicalDeviceProperties pProperties);
[GetProcAddress("vkEnumerateDeviceExtensionProperties")]
public partial VkResult EnumerateDeviceExtensionProperties(IntPtr physicalDevice, byte* pLayerName,
ref uint32_t pPropertyCount, VkExtensionProperties* pProperties);
[GetProcAddress("vkGetPhysicalDeviceSurfaceSupportKHR")]
public partial VkResult GetPhysicalDeviceSurfaceSupportKHR(IntPtr physicalDevice, uint32_t queueFamilyIndex,
IntPtr surface, out VkBool32 pSupported);
[GetProcAddress("vkGetPhysicalDeviceQueueFamilyProperties")]
public partial void GetPhysicalDeviceQueueFamilyProperties(IntPtr physicalDevice,
ref uint32_t pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
[GetProcAddress("vkCreateDevice")]
public partial VkResult CreateDevice(IntPtr physicalDevice, ref VkDeviceCreateInfo pCreateInfo,
IntPtr pAllocator, out IntPtr pDevice);
[GetProcAddress("vkGetDeviceQueue")]
public partial void GetDeviceQueue(IntPtr device, uint32_t queueFamilyIndex, uint32_t queueIndex,
out IntPtr pQueue);
[GetProcAddress("vkGetDeviceProcAddr")]
public partial IntPtr GetDeviceProcAddr(IntPtr device, IntPtr pName);
[GetProcAddress("vkDestroySurfaceKHR")]
public partial void DestroySurfaceKHR(IntPtr instance, IntPtr surface, IntPtr pAllocator);
[GetProcAddress("vkGetPhysicalDeviceSurfaceFormatsKHR")]
public partial VkResult GetPhysicalDeviceSurfaceFormatsKHR(
IntPtr physicalDevice,
IntPtr surface,
ref uint32_t pSurfaceFormatCount,
VkSurfaceFormatKHR* pSurfaceFormats);
[GetProcAddress("vkGetPhysicalDeviceMemoryProperties")]
public partial void GetPhysicalDeviceMemoryProperties(IntPtr physicalDevice,
out VkPhysicalDeviceMemoryProperties pMemoryProperties);
[GetProcAddress("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")]
public partial VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR(IntPtr physicalDevice, IntPtr surface,
out VkSurfaceCapabilitiesKHR pSurfaceCapabilities);
[GetProcAddress("vkGetPhysicalDeviceSurfacePresentModesKHR")]
public partial VkResult GetPhysicalDeviceSurfacePresentModesKHR(IntPtr physicalDevice, IntPtr surface,
ref uint32_t pPresentModeCount, VkPresentModeKHR* pPresentModes);
}

657
src/Avalonia.Vulkan/UnmanagedInterop/VulkanStructs.cs

@ -0,0 +1,657 @@
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable NotAccessedField.Global
#pragma warning disable CS0649
#pragma warning disable CS0169
#pragma warning disable CA1823
using System;
using uint32_t = System.UInt32;
using VkSampleCountFlags = System.UInt32;
using int32_t = System.Int32;
using VkBool32 = System.UInt32;
using uint8_t = System.Byte;
using size_t = System.IntPtr;
using VkDeviceSize = System.UInt64;
// ReSharper disable RedundantUnsafeContext
namespace Avalonia.Vulkan.UnmanagedInterop
{
struct VkDevice
{
public IntPtr Handle;
public static implicit operator IntPtr(VkDevice d) => d.Handle;
public static implicit operator VkDevice(IntPtr d) => new() { Handle = d };
}
struct VkSwapchainKHR
{
public IntPtr Handle;
public static implicit operator IntPtr(VkSwapchainKHR d) => d.Handle;
public static implicit operator VkSwapchainKHR(IntPtr d) => new() { Handle = d };
}
struct VkSemaphore
{
public IntPtr Handle;
public static implicit operator IntPtr(VkSemaphore d) => d.Handle;
public static implicit operator VkSemaphore(IntPtr d) => new() { Handle = d };
}
struct VkFence
{
public IntPtr Handle;
public static implicit operator IntPtr(VkFence d) => d.Handle;
public static implicit operator VkFence(IntPtr d) => new() { Handle = d };
}
struct VkImage
{
public IntPtr Handle;
public static implicit operator IntPtr(VkImage d) => d.Handle;
public static implicit operator VkImage(IntPtr d) => new() { Handle = d };
}
struct VkQueue
{
public IntPtr Handle;
public static implicit operator IntPtr(VkQueue d) => d.Handle;
public static implicit operator VkQueue(IntPtr d) => new() { Handle = d };
}
struct VkCommandBuffer
{
public IntPtr Handle;
public static implicit operator IntPtr(VkCommandBuffer d) => d.Handle;
public static implicit operator VkCommandBuffer(IntPtr d) => new() { Handle = d };
}
unsafe struct VkLayerProperties
{
public fixed byte layerName[256];
public uint32_t specVersion;
public uint32_t implementationVersion;
public fixed byte description[256];
}
unsafe struct VkDebugUtilsLabelEXT
{
public VkStructureType sType;
public IntPtr pNext;
public IntPtr pLabelName;
public fixed float color[4];
}
struct VkDebugUtilsObjectNameInfoEXT
{
public VkStructureType sType;
public IntPtr pNext;
public VkObjectType objectType;
public ulong objectHandle;
public IntPtr pObjectName;
}
unsafe struct VkDebugUtilsMessengerCallbackDataEXT
{
public VkStructureType sType;
public IntPtr pNext;
public uint flags;
public IntPtr pMessageIdName;
public int32_t messageIdNumber;
public IntPtr pMessage;
public uint32_t queueLabelCount;
public VkDebugUtilsLabelEXT* pQueueLabels;
public uint32_t cmdBufLabelCount;
public VkDebugUtilsLabelEXT* pCmdBufLabels;
public uint32_t objectCount;
public VkDebugUtilsObjectNameInfoEXT* pObjects;
}
unsafe delegate VkBool32 VkDebugUtilsMessengerCallbackEXTDelegate(
VkDebugUtilsMessageSeverityFlagsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageTypes,
VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData);
struct VkDebugUtilsMessengerCreateInfoEXT
{
public VkStructureType sType;
public IntPtr pNext;
public uint flags;
public VkDebugUtilsMessageSeverityFlagsEXT messageSeverity;
public VkDebugUtilsMessageTypeFlagsEXT messageType;
public IntPtr pfnUserCallback;
public IntPtr pUserData;
}
unsafe struct VkInstanceCreateInfo
{
public VkStructureType sType;
public IntPtr pNext;
public int flags;
public VkApplicationInfo* pApplicationInfo;
public uint32_t enabledLayerCount;
public byte** ppEnabledLayerNames;
public uint32_t enabledExtensionCount;
public byte** ppEnabledExtensionNames;
}
unsafe struct VkApplicationInfo
{
public VkStructureType sType;
public void* pNext;
public byte* pApplicationName;
public uint32_t applicationVersion;
public byte* pEngineName;
public uint32_t engineVersion;
public uint32_t apiVersion;
}
unsafe struct VkPhysicalDeviceSparseProperties
{
public VkBool32 residencyStandard2DBlockShape;
public VkBool32 residencyStandard2DMultisampleBlockShape;
public VkBool32 residencyStandard3DBlockShape;
public VkBool32 residencyAlignedMipSize;
public VkBool32 residencyNonResidentStrict;
}
unsafe struct VkPhysicalDeviceProperties
{
public uint32_t apiVersion;
public uint32_t driverVersion;
public uint32_t vendorID;
public uint32_t deviceID;
public VkPhysicalDeviceType deviceType;
public fixed byte deviceName[256];
public fixed uint8_t pipelineCacheUUID[16];
public VkPhysicalDeviceLimits limits;
public VkPhysicalDeviceSparseProperties sparseProperties;
}
unsafe struct VkPhysicalDeviceLimits
{
public uint32_t maxImageDimension1D;
public uint32_t maxImageDimension2D;
public uint32_t maxImageDimension3D;
public uint32_t maxImageDimensionCube;
public uint32_t maxImageArrayLayers;
public uint32_t maxTexelBufferElements;
public uint32_t maxUniformBufferRange;
public uint32_t maxStorageBufferRange;
public uint32_t maxPushConstantsSize;
public uint32_t maxMemoryAllocationCount;
public uint32_t maxSamplerAllocationCount;
public VkDeviceSize bufferImageGranularity;
public VkDeviceSize sparseAddressSpaceSize;
public uint32_t maxBoundDescriptorSets;
public uint32_t maxPerStageDescriptorSamplers;
public uint32_t maxPerStageDescriptorUniformBuffers;
public uint32_t maxPerStageDescriptorStorageBuffers;
public uint32_t maxPerStageDescriptorSampledImages;
public uint32_t maxPerStageDescriptorStorageImages;
public uint32_t maxPerStageDescriptorInputAttachments;
public uint32_t maxPerStageResources;
public uint32_t maxDescriptorSetSamplers;
public uint32_t maxDescriptorSetUniformBuffers;
public uint32_t maxDescriptorSetUniformBuffersDynamic;
public uint32_t maxDescriptorSetStorageBuffers;
public uint32_t maxDescriptorSetStorageBuffersDynamic;
public uint32_t maxDescriptorSetSampledImages;
public uint32_t maxDescriptorSetStorageImages;
public uint32_t maxDescriptorSetInputAttachments;
public uint32_t maxVertexInputAttributes;
public uint32_t maxVertexInputBindings;
public uint32_t maxVertexInputAttributeOffset;
public uint32_t maxVertexInputBindingStride;
public uint32_t maxVertexOutputComponents;
public uint32_t maxTessellationGenerationLevel;
public uint32_t maxTessellationPatchSize;
public uint32_t maxTessellationControlPerVertexInputComponents;
public uint32_t maxTessellationControlPerVertexOutputComponents;
public uint32_t maxTessellationControlPerPatchOutputComponents;
public uint32_t maxTessellationControlTotalOutputComponents;
public uint32_t maxTessellationEvaluationInputComponents;
public uint32_t maxTessellationEvaluationOutputComponents;
public uint32_t maxGeometryShaderInvocations;
public uint32_t maxGeometryInputComponents;
public uint32_t maxGeometryOutputComponents;
public uint32_t maxGeometryOutputVertices;
public uint32_t maxGeometryTotalOutputComponents;
public uint32_t maxFragmentInputComponents;
public uint32_t maxFragmentOutputAttachments;
public uint32_t maxFragmentDualSrcAttachments;
public uint32_t maxFragmentCombinedOutputResources;
public uint32_t maxComputeSharedMemorySize;
public fixed uint32_t maxComputeWorkGroupCount[3];
public uint32_t maxComputeWorkGroupInvocations;
public fixed uint32_t maxComputeWorkGroupSize[3];
public uint32_t subPixelPrecisionBits;
public uint32_t subTexelPrecisionBits;
public uint32_t mipmapPrecisionBits;
public uint32_t maxDrawIndexedIndexValue;
public uint32_t maxDrawIndirectCount;
public float maxSamplerLodBias;
public float maxSamplerAnisotropy;
public uint32_t maxViewports;
public fixed uint32_t maxViewportDimensions[2];
public fixed float viewportBoundsRange[2];
public uint32_t viewportSubPixelBits;
public size_t minMemoryMapAlignment;
public VkDeviceSize minTexelBufferOffsetAlignment;
public VkDeviceSize minUniformBufferOffsetAlignment;
public VkDeviceSize minStorageBufferOffsetAlignment;
public int32_t minTexelOffset;
public uint32_t maxTexelOffset;
public int32_t minTexelGatherOffset;
public uint32_t maxTexelGatherOffset;
public float minInterpolationOffset;
public float maxInterpolationOffset;
public uint32_t subPixelInterpolationOffsetBits;
public uint32_t maxFramebufferWidth;
public uint32_t maxFramebufferHeight;
public uint32_t maxFramebufferLayers;
public VkSampleCountFlags framebufferColorSampleCounts;
public VkSampleCountFlags framebufferDepthSampleCounts;
public VkSampleCountFlags framebufferStencilSampleCounts;
public VkSampleCountFlags framebufferNoAttachmentsSampleCounts;
public uint32_t maxColorAttachments;
public VkSampleCountFlags sampledImageColorSampleCounts;
public VkSampleCountFlags sampledImageIntegerSampleCounts;
public VkSampleCountFlags sampledImageDepthSampleCounts;
public VkSampleCountFlags sampledImageStencilSampleCounts;
public VkSampleCountFlags storageImageSampleCounts;
public uint32_t maxSampleMaskWords;
public VkBool32 timestampComputeAndGraphics;
public float timestampPeriod;
public uint32_t maxClipDistances;
public uint32_t maxCullDistances;
public uint32_t maxCombinedClipAndCullDistances;
public uint32_t discreteQueuePriorities;
public fixed float pointSizeRange[2];
public fixed float lineWidthRange[2];
public float pointSizeGranularity;
public float lineWidthGranularity;
public VkBool32 strictLines;
public VkBool32 standardSampleLocations;
public VkDeviceSize optimalBufferCopyOffsetAlignment;
public VkDeviceSize optimalBufferCopyRowPitchAlignment;
public VkDeviceSize nonCoherentAtomSize;
}
internal unsafe struct VkExtensionProperties
{
public fixed byte extensionName[256];
public uint32_t specVersion;
}
struct VkQueueFamilyProperties
{
public VkQueueFlags queueFlags;
public uint32_t queueCount;
public uint32_t timestampValidBits;
public VkExtent3D minImageTransferGranularity;
}
struct VkExtent2D
{
public uint32_t width;
public uint32_t height;
}
struct VkExtent3D
{
public uint32_t width;
public uint32_t height;
public uint32_t depth;
}
unsafe struct VkDeviceQueueCreateInfo
{
public VkStructureType sType;
public IntPtr pNext;
public VkDeviceQueueCreateFlags flags;
public uint32_t queueFamilyIndex;
public uint32_t queueCount;
public float* pQueuePriorities;
}
unsafe struct VkDeviceCreateInfo
{
public VkStructureType sType;
public IntPtr pNext;
public uint flags;
public uint32_t queueCreateInfoCount;
public VkDeviceQueueCreateInfo* pQueueCreateInfos;
public uint32_t enabledLayerCount;
public byte** ppEnabledLayerNames;
public uint32_t enabledExtensionCount;
public byte** ppEnabledExtensionNames;
public IntPtr pEnabledFeatures;
}
struct VkFenceCreateInfo
{
public VkStructureType sType;
IntPtr pNext;
public VkFenceCreateFlags flags;
}
struct VkCommandPoolCreateInfo
{
public VkStructureType sType;
IntPtr pNext;
public VkCommandPoolCreateFlags flags;
public uint32_t queueFamilyIndex;
}
struct VkCommandBufferAllocateInfo
{
public VkStructureType sType;
IntPtr pNext;
public IntPtr commandPool;
public VkCommandBufferLevel level;
public uint32_t commandBufferCount;
}
struct VkCommandBufferBeginInfo
{
public VkStructureType sType;
IntPtr pNext;
public VkCommandBufferUsageFlags flags;
IntPtr pInheritanceInfo;
}
struct VkSemaphoreCreateInfo
{
public VkStructureType sType;
IntPtr pNext;
uint flags;
}
unsafe struct VkSubmitInfo
{
public VkStructureType sType;
public IntPtr pNext;
public uint32_t waitSemaphoreCount;
public IntPtr* pWaitSemaphores;
public VkPipelineStageFlags* pWaitDstStageMask;
public uint32_t commandBufferCount;
public IntPtr* pCommandBuffers;
public uint32_t signalSemaphoreCount;
public IntPtr* pSignalSemaphores;
}
struct VkSurfaceFormatKHR
{
public VkFormat format;
public VkColorSpaceKHR colorSpace;
}
struct VkMemoryType
{
public VkMemoryPropertyFlags propertyFlags;
public uint32_t heapIndex;
}
struct VkMemoryHeap
{
public VkDeviceSize size;
public VkMemoryHeapFlags flags;
}
unsafe struct VkPhysicalDeviceMemoryProperties
{
public uint32_t memoryTypeCount;
public VkMemoryTypesBuffer memoryTypes;
public uint32_t memoryHeapCount;
public VkMemoryHeapsBuffer memoryHeaps;
public struct VkMemoryTypesBuffer
{
public VkMemoryType Element0;
public VkMemoryType Element1;
public VkMemoryType Element2;
public VkMemoryType Element3;
public VkMemoryType Element4;
public VkMemoryType Element5;
public VkMemoryType Element6;
public VkMemoryType Element7;
public VkMemoryType Element8;
public VkMemoryType Element9;
public VkMemoryType Element10;
public VkMemoryType Element11;
public VkMemoryType Element12;
public VkMemoryType Element13;
public VkMemoryType Element14;
public VkMemoryType Element15;
public VkMemoryType Element16;
public VkMemoryType Element17;
public VkMemoryType Element18;
public VkMemoryType Element19;
public VkMemoryType Element20;
public VkMemoryType Element21;
public VkMemoryType Element22;
public VkMemoryType Element23;
public VkMemoryType Element24;
public VkMemoryType Element25;
public VkMemoryType Element26;
public VkMemoryType Element27;
public VkMemoryType Element28;
public VkMemoryType Element29;
public VkMemoryType Element30;
public VkMemoryType Element31;
public ref VkMemoryType this[int index]
{
get
{
if (index > 31 || index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
fixed (VkMemoryType* ptr = &Element0)
{
return ref ptr[index];
}
}
}
}
public struct VkMemoryHeapsBuffer
{
public VkMemoryHeap Element0;
public VkMemoryHeap Element1;
public VkMemoryHeap Element2;
public VkMemoryHeap Element3;
public VkMemoryHeap Element4;
public VkMemoryHeap Element5;
public VkMemoryHeap Element6;
public VkMemoryHeap Element7;
public VkMemoryHeap Element8;
public VkMemoryHeap Element9;
public VkMemoryHeap Element10;
public VkMemoryHeap Element11;
public VkMemoryHeap Element12;
public VkMemoryHeap Element13;
public VkMemoryHeap Element14;
public VkMemoryHeap Element15;
public ref VkMemoryHeap this[int index]
{
get
{
if (index > 15 || index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
fixed (VkMemoryHeap* ptr = &Element0)
{
return ref ptr[index];
}
}
}
}
}
unsafe struct VkImageCreateInfo
{
public VkStructureType sType;
public IntPtr pNext;
public VkImageCreateFlags flags;
public VkImageType imageType;
public VkFormat format;
public VkExtent3D extent;
public uint32_t mipLevels;
public uint32_t arrayLayers;
public VkSampleCountFlags samples;
public VkImageTiling tiling;
public VkImageUsageFlags usage;
public VkSharingMode sharingMode;
public uint32_t queueFamilyIndexCount;
public uint32_t* pQueueFamilyIndices;
public VkImageLayout initialLayout;
}
struct VkMemoryRequirements
{
public VkDeviceSize size;
public VkDeviceSize alignment;
public uint32_t memoryTypeBits;
}
struct VkMemoryAllocateInfo
{
public VkStructureType sType;
IntPtr pNext;
public VkDeviceSize allocationSize;
public uint32_t memoryTypeIndex;
}
struct VkComponentMapping
{
public VkComponentSwizzle r;
public VkComponentSwizzle g;
public VkComponentSwizzle b;
public VkComponentSwizzle a;
}
struct VkImageSubresourceRange
{
public VkImageAspectFlags aspectMask;
public uint32_t baseMipLevel;
public uint32_t levelCount;
public uint32_t baseArrayLayer;
public uint32_t layerCount;
}
struct VkImageViewCreateInfo
{
public VkStructureType sType;
public IntPtr pNext;
public VkImageViewCreateFlags flags;
public IntPtr image;
public VkImageViewType viewType;
public VkFormat format;
public VkComponentMapping components;
public VkImageSubresourceRange subresourceRange;
}
struct VkImageMemoryBarrier
{
public VkStructureType sType;
IntPtr pNext;
public VkAccessFlags srcAccessMask;
public VkAccessFlags dstAccessMask;
public VkImageLayout oldLayout;
public VkImageLayout newLayout;
public uint32_t srcQueueFamilyIndex;
public uint32_t dstQueueFamilyIndex;
public IntPtr image;
public VkImageSubresourceRange subresourceRange;
}
struct VkSurfaceCapabilitiesKHR
{
public uint32_t minImageCount;
public uint32_t maxImageCount;
public VkExtent2D currentExtent;
public VkExtent2D minImageExtent;
public VkExtent2D maxImageExtent;
public uint32_t maxImageArrayLayers;
public VkSurfaceTransformFlagsKHR supportedTransforms;
public VkSurfaceTransformFlagsKHR currentTransform;
public VkCompositeAlphaFlagsKHR supportedCompositeAlpha;
public VkImageUsageFlags supportedUsageFlags;
}
unsafe struct VkSwapchainCreateInfoKHR
{
public VkStructureType sType;
public IntPtr pNext;
public VkSwapchainCreateFlagsKHR flags;
public IntPtr surface;
public uint32_t minImageCount;
public VkFormat imageFormat;
public VkColorSpaceKHR imageColorSpace;
public VkExtent2D imageExtent;
public uint32_t imageArrayLayers;
public VkImageUsageFlags imageUsage;
public VkSharingMode imageSharingMode;
public uint32_t queueFamilyIndexCount;
public uint32_t* pQueueFamilyIndices;
public VkSurfaceTransformFlagsKHR preTransform;
public VkCompositeAlphaFlagsKHR compositeAlpha;
public VkPresentModeKHR presentMode;
public VkBool32 clipped;
public IntPtr oldSwapchain;
}
struct VkOffset3D
{
public int32_t x;
public int32_t y;
public int32_t z;
}
struct VkImageSubresourceLayers
{
public VkImageAspectFlags aspectMask;
public uint32_t mipLevel;
public uint32_t baseArrayLayer;
public uint32_t layerCount;
}
struct VkImageBlit
{
public VkImageSubresourceLayers srcSubresource;
public VkOffset3D srcOffsets1;
public VkOffset3D srcOffsets2;
public VkImageSubresourceLayers dstSubresource;
public VkOffset3D dstOffsets1;
public VkOffset3D dstOffsets2;
}
unsafe struct VkPresentInfoKHR
{
public VkStructureType sType;
public IntPtr pNext;
public uint32_t waitSemaphoreCount;
public VkSemaphore* pWaitSemaphores;
public uint32_t swapchainCount;
public VkSwapchainKHR* pSwapchains;
public uint32_t* pImageIndices;
public VkResult* pResults;
}
}

19
src/Avalonia.Vulkan/VulkanBindings.cs

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Vulkan.Interop;
namespace Avalonia.Vulkan;
static class VulkanHelpers
{
public static uint MakeVersion(Version v) => MakeVersion(v.Major, v.Minor, v.Build);
public static uint MakeVersion(int major, int minor, int patch)
{
return (uint)((major << 22) | (minor << 12) | patch);
}
public const uint QueueFamilyIgnored = 4294967295;
}

53
src/Avalonia.Vulkan/VulkanContext.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal class VulkanContext : IVulkanPlatformGraphicsContext
{
private readonly IVulkanKhrSurfacePlatformSurfaceFactory? _surfaceFactory;
public VulkanContext(IVulkanDevice device, Dictionary<Type, object> platformFeatures)
{
Device = device;
using (device.Lock())
{
InstanceApi = new VulkanInstanceApi(device.Instance);
DeviceApi = new VulkanDeviceApi(device);
if (platformFeatures.TryGetValue(typeof(IVulkanKhrSurfacePlatformSurfaceFactory), out var factory))
_surfaceFactory = (IVulkanKhrSurfacePlatformSurfaceFactory)factory;
}
}
public void Dispose()
{
throw new NotImplementedException();
}
public object? TryGetFeature(Type featureType) => null;
public bool IsLost => Device.IsLost;
public IDisposable EnsureCurrent() => Device.Lock();
public IVulkanDevice Device { get; }
public IVulkanInstance Instance => Device.Instance;
public VulkanInstanceApi InstanceApi { get; }
public VulkanDeviceApi DeviceApi { get; }
public IVulkanRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
foreach (var surf in surfaces)
{
IVulkanKhrSurfacePlatformSurface khrSurface;
if (surf is IVulkanKhrSurfacePlatformSurface khr)
khrSurface = khr;
else if (_surfaceFactory?.CanRenderToSurface(this, surf) == true)
khrSurface = _surfaceFactory.CreateSurface(this, surf);
else
continue;
return new VulkanKhrRenderTarget(khrSurface, this);
}
throw new VulkanException("Unable to find a suitable platform surface");
}
}

27
src/Avalonia.Vulkan/VulkanException.cs

@ -0,0 +1,27 @@
using System;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
public class VulkanException : Exception
{
public VulkanException(string message) : base(message)
{
}
public VulkanException(string funcName, VkResult res) : base($"{funcName} returned {res}")
{
}
}
public static class VulkanExceptionExtensions
{
public static void ThrowOnError(this VkResult res, string funcName)
{
if (res != VkResult.VK_SUCCESS)
throw new VulkanException(funcName, res);
}
}

109
src/Avalonia.Vulkan/VulkanKhrSurfaceRenderTarget.cs

@ -0,0 +1,109 @@
using System;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.Vulkan;
internal class VulkanKhrRenderTarget : IVulkanRenderTarget
{
private IVulkanKhrSurfacePlatformSurface _surface;
private readonly VulkanKhrSurface _khrSurface;
private readonly IVulkanPlatformGraphicsContext _context;
private VulkanDisplay _display;
private PixelSize _size;
private VulkanImage? _image;
public VkFormat Format { get; }
public bool IsRgba { get; }
public VulkanKhrRenderTarget(IVulkanKhrSurfacePlatformSurface surface, IVulkanPlatformGraphicsContext context)
{
_surface = surface;
_khrSurface = new(context, surface);
_display = VulkanDisplay.CreateDisplay(context, _khrSurface);
_context = context;
IsRgba = _display.SurfaceFormat.format >= VkFormat.VK_FORMAT_R8G8B8A8_UNORM &&
_display.SurfaceFormat.format <= VkFormat.VK_FORMAT_R8G8B8A8_SRGB;
// Skia seems to only create surfaces from images with unorm format
Format = IsRgba ? VkFormat.VK_FORMAT_R8G8B8A8_UNORM : VkFormat.VK_FORMAT_B8G8R8A8_UNORM;
}
private void CreateImage()
{
_size = _display.Size;
_image = new VulkanImage(_context, _display.CommandBufferPool, Format, _display.Size);
}
private void DestroyImage()
{
_context.DeviceApi.DeviceWaitIdle(_context.Device.Handle);
_image?.Dispose();
_image = null;
}
public void Dispose()
{
_context.DeviceApi.DeviceWaitIdle(_context.Device.Handle);
DestroyImage();
_display?.Dispose();
_display = null!;
_surface?.Dispose();
_surface = null!;
}
public IVulkanRenderSession BeginDraw()
{
var l = _context.EnsureCurrent();
if (_display.EnsureSwapchainAvailable() || _image == null)
{
DestroyImage();
CreateImage();
}
else
_image.TransitionLayout(VkImageLayout.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VkAccessFlags.VK_ACCESS_NONE);
return new RenderingSession(_display, _image!, IsRgba, _surface.Scaling, l);
}
public class RenderingSession : IVulkanRenderSession
{
private readonly VulkanImage _image;
private readonly IDisposable _dispose;
public RenderingSession(VulkanDisplay display, VulkanImage image, bool isRgba, double scaling,
IDisposable dispose)
{
_image = image;
_dispose = dispose;
Display = display;
IsRgba = isRgba;
Scaling = scaling;
}
public VulkanDisplay Display { get; }
public PixelSize Size => _image.Size;
public double Scaling { get; }
public bool IsYFlipped => true;
public uint ImageFormat => (uint)_image.Format;
public IntPtr ImageHandle => _image.Handle;
public uint ImageLayout => (uint)_image.CurrentLayout;
public uint ImageTiling => (uint)_image.Tiling;
public uint ImageUsageFlags => (uint)_image.UsageFlags;
public uint LevelCount => (uint)_image.MipLevels;
public IntPtr ImageMemoryHandle => _image.MemoryHandle;
public ulong ImageMemorySize => _image.MemorySize;
public bool IsRgba { get; }
public void Dispose()
{
var commandBuffer = Display.StartPresentation();
Display.BlitImageToCurrentImage(commandBuffer, _image);
Display.EndPresentation(commandBuffer);
_dispose.Dispose();
}
}
}

73
src/Avalonia.Vulkan/VulkanOptions.cs

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Vulkan;
public class VulkanOptions
{
public VulkanInstanceCreationOptions VulkanInstanceCreationOptions { get; set; } = new();
public VulkanDeviceCreationOptions VulkanDeviceCreationOptions { get; set; } = new();
public IVulkanDevice? CustomSharedDevice { get; set; }
}
public class VulkanInstanceCreationOptions
{
public VkGetInstanceProcAddressDelegate? CustomGetProcAddressDelegate { get; set; }
/// <summary>
/// Sets the application name of the vulkan instance
/// </summary>
public string? ApplicationName { get; set; }
/// <summary>
/// Specifies the vulkan api version to use
/// </summary>
public Version VulkanVersion{ get; set; } = new Version(1, 1, 0);
/// <summary>
/// Specifies additional extensions to enable if available on the instance
/// </summary>
public IList<string> InstanceExtensions { get; set; } = new List<string>();
/// <summary>
/// Specifies layers to enable if available on the instance
/// </summary>
public IList<string> EnabledLayers { get; set; } = new List<string>();
/// <summary>
/// Enables the debug layer
/// </summary>
public bool UseDebug { get; set; }
/*
/// <summary>
/// Sets the presentation mode the swapchain uses if available.
/// </summary>
//public PresentMode PresentMode { get; set; } = PresentMode.Mailbox;*/
}
public class VulkanDeviceCreationOptions
{
/// <summary>
/// Specifies extensions to enable if available on the logical device
/// </summary>
public IList<string> DeviceExtensions { get; set; } = new List<string>();
/// <summary>
/// Selects the first suitable discrete gpu available
/// </summary>
public bool PreferDiscreteGpu { get; set; }
public bool RequireComputeBit { get; set; }
}
public class VulkanPlatformSpecificOptions
{
public IList<string> RequiredInstanceExtensions { get; set; } = new List<string>();
public VkGetInstanceProcAddressDelegate? GetProcAddressDelegate { get; set; }
public Func<IVulkanInstance, IntPtr>? DeviceCheckSurfaceFactory { get; set; }
public Dictionary<Type, object> PlatformFeatures { get; set; } = new();
}
public delegate IntPtr VkGetInstanceProcAddressDelegate(IntPtr instance, string name);

103
src/Avalonia.Vulkan/VulkanPlatformGraphics.cs

@ -0,0 +1,103 @@
using System;
using Avalonia.Logging;
using Avalonia.Platform;
namespace Avalonia.Vulkan;
public class VulkanPlatformGraphics : IPlatformGraphics
{
private readonly IVulkanDeviceFactory _factory;
private IVulkanPlatformGraphicsContext? _currentSharedContext;
private readonly VulkanPlatformSpecificOptions _platformOptions;
public VulkanPlatformGraphics(IVulkanDeviceFactory factory, VulkanPlatformSpecificOptions platformOptions)
{
_factory = factory;
_platformOptions = platformOptions;
}
public IPlatformGraphicsContext CreateContext() =>
new VulkanContext(_factory.CreateDevice(_platformOptions), _platformOptions.PlatformFeatures);
public IPlatformGraphicsContext GetSharedContext()
{
if (_currentSharedContext?.IsLost == true)
_currentSharedContext = null;
return _currentSharedContext =
new VulkanContext(_factory.GetSharedDevice(_platformOptions), _platformOptions.PlatformFeatures);
}
public bool UsesSharedContext => _factory.UsesShadedDevice;
public interface IVulkanDeviceFactory
{
bool UsesShadedDevice { get; }
IVulkanDevice GetSharedDevice(VulkanPlatformSpecificOptions platformOptions);
IVulkanDevice CreateDevice(VulkanPlatformSpecificOptions platformOptions);
}
class DefaultDeviceFactory : IVulkanDeviceFactory
{
private readonly IVulkanInstance _instance;
private readonly VulkanDeviceCreationOptions _deviceOptions;
public DefaultDeviceFactory(IVulkanInstance instance, VulkanDeviceCreationOptions deviceOptions)
{
_instance = instance;
_deviceOptions = deviceOptions;
}
public bool UsesShadedDevice => false;
public IVulkanDevice GetSharedDevice(VulkanPlatformSpecificOptions platformOptions) => throw new NotSupportedException();
public IVulkanDevice CreateDevice(VulkanPlatformSpecificOptions platformOptions)
{
return VulkanDevice.Create(_instance, _deviceOptions, platformOptions);
}
}
class CustomSharedDeviceFactory : IVulkanDeviceFactory
{
private readonly IVulkanDevice _device;
public CustomSharedDeviceFactory(IVulkanDevice device)
{
_device = device;
}
public bool UsesShadedDevice => true;
public IVulkanDevice GetSharedDevice(VulkanPlatformSpecificOptions platformOptions) => _device;
public IVulkanDevice CreateDevice(VulkanPlatformSpecificOptions platformOptions) =>
throw new NotSupportedException();
}
public static VulkanPlatformGraphics? TryCreate(VulkanOptions options, VulkanPlatformSpecificOptions platformOptions)
{
if (options.CustomSharedDevice != null)
return new(new CustomSharedDeviceFactory(options.CustomSharedDevice), platformOptions);
IVulkanInstance? instance = null;
try
{
instance = VulkanInstance.Create(options.VulkanInstanceCreationOptions ?? new(),
platformOptions);
var devOpts = options.VulkanDeviceCreationOptions ?? new();
VulkanDevice.Create(instance, devOpts, platformOptions)
.Dispose();
return new VulkanPlatformGraphics(new DefaultDeviceFactory(instance, devOpts), platformOptions);
}
catch (Exception e)
{
//instance?.Dispose();
Logger.TryGet(LogEventLevel.Error, "Vulkan")?.Log(null, "Unable to initialize Vulkan rendering: {0}", e);
return null;
}
}
}

3
src/Avalonia.X11/Avalonia.X11.csproj

@ -13,4 +13,7 @@
</ItemGroup>
<Import Project="..\..\build\SourceGenerators.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<ItemGroup>
<Compile Remove="..\Shared\SourceGeneratorAttributes.cs" />
</ItemGroup>
</Project>

29
src/Avalonia.X11/Vulkan/VulkanNativeInterop.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.SourceGenerator;
using Avalonia.Vulkan;
using Avalonia.Vulkan.Interop;
using Avalonia.Vulkan.UnmanagedInterop;
namespace Avalonia.X11.Vulkan;
partial class X11VulkanInterface
{
public X11VulkanInterface(IVulkanInstance instance)
{
Initialize(name => instance.GetInstanceProcAddress(instance.Handle, name));
}
[GetProcAddress("vkCreateXlibSurfaceKHR")]
public partial VkResult vkCreateXlibSurfaceKHR(IntPtr instance, ref VkXlibSurfaceCreateInfoKHR pCreateInfo,
IntPtr pAllocator, out IntPtr pSurface);
}
struct VkXlibSurfaceCreateInfoKHR
{
public const uint VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000;
public uint sType;
public IntPtr pNext;
public uint flags;
public IntPtr dpy;
public IntPtr window;
}

97
src/Avalonia.X11/Vulkan/VulkanSupport.cs

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Platform.Surfaces;
using Avalonia.Vulkan;
using JetBrains.Annotations;
namespace Avalonia.X11.Vulkan;
internal class VulkanSupport
{
private static IntPtr s_offscreenWindow;
[DllImport("libvulkan.so.1")]
private static extern IntPtr vkGetInstanceProcAddr(IntPtr instance, string name);
[CanBeNull]
public static VulkanPlatformGraphics TryInitialize(X11Info info, VulkanOptions options)
{
s_offscreenWindow = XLib.XCreateSimpleWindow(info.DeferredDisplay,
XLib.XDefaultRootWindow(info.DeferredDisplay), 0, 0, 1,
1, 1, IntPtr.Zero, IntPtr.Zero);
XLib.XSync(info.DeferredDisplay, true);
return VulkanPlatformGraphics.TryCreate(options ?? new(), new VulkanPlatformSpecificOptions
{
RequiredInstanceExtensions = { "VK_KHR_xlib_surface" },
GetProcAddressDelegate = vkGetInstanceProcAddr,
DeviceCheckSurfaceFactory = instance => CreateDummySurface(info.DeferredDisplay, instance),
PlatformFeatures = new Dictionary<Type, object>
{
[typeof(IVulkanKhrSurfacePlatformSurfaceFactory)] = new VulkanSurfaceFactory(info.DeferredDisplay)
}
});
}
internal class VulkanSurfaceFactory : IVulkanKhrSurfacePlatformSurfaceFactory
{
private readonly IntPtr _display;
public VulkanSurfaceFactory(IntPtr display)
{
_display = display;
}
public bool CanRenderToSurface(IVulkanPlatformGraphicsContext context, object surface) =>
surface is INativeHandlePlatformSurface handle && handle.HandleDescriptor == "XID";
public IVulkanKhrSurfacePlatformSurface CreateSurface(IVulkanPlatformGraphicsContext context, object handle) =>
new XidSurface(_display, (INativeHandlePlatformSurface)handle);
}
class XidSurface : IVulkanKhrSurfacePlatformSurface
{
private readonly IntPtr _display;
private readonly INativeHandlePlatformSurface _handle;
public XidSurface(IntPtr display, INativeHandlePlatformSurface handle)
{
_display = display;
_handle = handle;
}
public void Dispose()
{
// No-op
}
public double Scaling => _handle.Scaling;
public PixelSize Size => _handle.Size;
public IntPtr CreateSurface(IVulkanPlatformGraphicsContext context) =>
CreateXlibSurface(_display, _handle.Handle, context.Instance);
}
private static IntPtr CreateDummySurface(IntPtr display, IVulkanInstance instance)
{
var surf = CreateXlibSurface(display, s_offscreenWindow, instance);
XLib.XSync(display, true);
return surf;
}
private static IntPtr CreateXlibSurface(IntPtr display, IntPtr window, IVulkanInstance instance)
{
var vulkanXlib = new X11VulkanInterface(instance);
var createInfo = new VkXlibSurfaceCreateInfoKHR
{
sType = VkXlibSurfaceCreateInfoKHR.VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
dpy = display,
window = window
};
vulkanXlib.vkCreateXlibSurfaceKHR(instance.Handle, ref createInfo, IntPtr.Zero, out var surface)
.ThrowOnError("vkCreateXlibSurfaceKHR");
return surface;
}
}

22
src/Avalonia.X11/X11Platform.cs

@ -14,8 +14,10 @@ using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan;
using Avalonia.X11;
using Avalonia.X11.Glx;
using Avalonia.X11.Vulkan;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
@ -94,7 +96,14 @@ namespace Avalonia.X11
XI2 = xi2;
}
if (options.UseGpu)
if (options.UseVulkan)
{
var vulkan = VulkanSupport.TryInitialize(Info, AvaloniaLocator.Current.GetService<VulkanOptions>());
if (vulkan != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(vulkan);
}
if (options.UseGpu && AvaloniaLocator.Current.GetService<IPlatformGraphics>() == null)
{
if (options.UseEGL)
EglPlatformGraphics.TryInitialize();
@ -102,12 +111,12 @@ namespace Avalonia.X11
GlxPlatformGraphics.TryInitialize(Info, Options.GlProfiles);
}
var gl = AvaloniaLocator.Current.GetService<IPlatformGraphics>();
var graphics = AvaloniaLocator.Current.GetService<IPlatformGraphics>();
if (options.UseCompositor)
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IRenderLoop>()!, gl);
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IRenderLoop>()!, graphics);
else
RenderInterface = new(gl);
RenderInterface = new(graphics);
}
@ -289,6 +298,11 @@ namespace Avalonia
/// </remarks>
public bool? EnableMultiTouch { get; set; } = true;
/// <summary>
/// Enables Vulkan rendering backend
/// </summary>
public bool UseVulkan { get; set; }
public X11PlatformOptions()
{
try

3
src/Avalonia.X11/X11Window.cs

@ -16,6 +16,7 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Surfaces;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
@ -1231,7 +1232,7 @@ namespace Avalonia.X11
public IStorageProvider StorageProvider { get; }
public class SurfacePlatformHandle : IPlatformNativeSurfaceHandle
public class SurfacePlatformHandle : INativeHandlePlatformSurface
{
private readonly X11Window _owner;

76
src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaGpu.cs

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using Avalonia.Vulkan;
using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp;
namespace Avalonia.Skia.Vulkan;
internal class VulkanSkiaGpu : ISkiaGpu
{
public IVulkanPlatformGraphicsContext Vulkan { get; private set; }
public GRContext GrContext { get; private set; }
public VulkanSkiaGpu(IVulkanPlatformGraphicsContext vulkan, long? maxResourceBytes)
{
Vulkan = vulkan;
var device = vulkan.Device;
using (Vulkan.EnsureCurrent())
{
IntPtr GetProcAddressWrapper(string name, IntPtr instance, IntPtr device)
{
if (device != IntPtr.Zero)
{
var addr = Vulkan.Instance.GetDeviceProcAddress(device, name);
if (addr != IntPtr.Zero)
return addr;
}
if (instance != IntPtr.Zero)
{
var addr = Vulkan.Instance.GetInstanceProcAddress(instance, name);
if (addr != IntPtr.Zero)
return addr;
}
return Vulkan.Instance.GetInstanceProcAddress(IntPtr.Zero, name);
}
var ctx = new GRVkBackendContext
{
VkInstance = device.Instance.Handle,
VkPhysicalDevice = device.PhysicalDeviceHandle,
VkDevice = device.Handle,
VkQueue = device.MainQueueHandle,
GraphicsQueueIndex = device.GraphicsQueueFamilyIndex,
GetProcedureAddress = GetProcAddressWrapper
};
GrContext = GRContext.CreateVulkan(ctx) ??
throw new VulkanException("Unable to create GrContext from IVulkanDevice");
if (maxResourceBytes.HasValue)
GrContext.SetResourceCacheLimit(maxResourceBytes.Value);
}
}
public void Dispose()
{
Vulkan.Dispose();
}
public object TryGetFeature(Type featureType) => null;
public bool IsLost => Vulkan.IsLost;
public IDisposable EnsureCurrent() => Vulkan.EnsureCurrent();
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
{
var target = Vulkan.CreateRenderTarget(surfaces);
return new VulkanSkiaRenderTarget(this, target);
}
public ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session) => null;
}

104
src/Skia/Avalonia.Skia/Gpu/Vulkan/VulkanSkiaRenderTarget.cs

@ -0,0 +1,104 @@
using System;
using Avalonia.Skia.Helpers;
using Avalonia.Vulkan;
using SkiaSharp;
namespace Avalonia.Skia.Vulkan;
class VulkanSkiaRenderTarget : ISkiaGpuRenderTarget
{
private readonly VulkanSkiaGpu _gpu;
private readonly IVulkanRenderTarget _target;
public VulkanSkiaRenderTarget(VulkanSkiaGpu gpu, IVulkanRenderTarget target)
{
_gpu = gpu;
_target = target;
}
public void Dispose()
{
_target.Dispose();
}
public ISkiaGpuRenderSession BeginRenderingSession()
{
var session = _target.BeginDraw();
bool success = false;
try
{
var size = session.Size;
var scaling = session.Scaling;
if (size.Width <= 0 || size.Height <= 0 || scaling < 0)
{
session.Dispose();
throw new InvalidOperationException(
$"Can't create drawing context for surface with {size} size and {scaling} scaling");
}
_gpu.GrContext.ResetContext();
var imageInfo = new GRVkImageInfo
{
CurrentQueueFamily = _gpu.Vulkan.Device.GraphicsQueueFamilyIndex,
Format = session.ImageFormat,
Image = (ulong)session.ImageHandle,
ImageLayout = session.ImageLayout,
ImageTiling = session.ImageTiling,
ImageUsageFlags = session.ImageUsageFlags,
LevelCount = session.LevelCount,
SampleCount = 1,
Protected = false,
Alloc = new GRVkAlloc
{
Memory = (ulong)session.ImageMemoryHandle,
Size = session.ImageMemorySize
}
};
using var renderTarget = new GRBackendRenderTarget(size.Width, size.Height, 1, imageInfo);
var surface = SKSurface.Create(_gpu.GrContext, renderTarget,
session.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
session.IsRgba ? SKColorType.Rgba8888 : SKColorType.Bgra8888, SKColorSpace.CreateSrgb());
if (surface == null)
throw new InvalidOperationException(
$"Surface can't be created with the provided render target");
success = true;
return new VulkanSkiaRenderSession(_gpu.GrContext, surface, session);
}
finally
{
if(!success)
session.Dispose();
}
}
public bool IsCorrupted => false;
internal class VulkanSkiaRenderSession : ISkiaGpuRenderSession
{
private readonly IVulkanRenderSession _vulkanSession;
public VulkanSkiaRenderSession(GRContext grContext,
SKSurface surface,
IVulkanRenderSession vulkanSession)
{
GrContext = grContext;
SkSurface = surface;
_vulkanSession = vulkanSession;
SurfaceOrigin = vulkanSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft;
}
public void Dispose()
{
SkSurface.Canvas.Flush();
SkSurface.Dispose();
GrContext.Flush();
_vulkanSession.Dispose();
}
public GRContext GrContext { get; }
public SKSurface SkSurface { get; }
public double ScaleFactor => _vulkanSession.Scaling;
public GRSurfaceOrigin SurfaceOrigin { get; }
}
}

4
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -11,6 +11,8 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Skia.Vulkan;
using Avalonia.Vulkan;
using SkiaSharp;
namespace Avalonia.Skia
@ -37,6 +39,8 @@ namespace Avalonia.Skia
return new SkiaContext(skiaGpu);
if (graphicsContext is IGlContext gl)
return new SkiaContext(new GlSkiaGpu(gl, _maxResourceBytes));
if (graphicsContext is IVulkanPlatformGraphicsContext vulkanContext)
return new SkiaContext(new VulkanSkiaGpu(vulkanContext, _maxResourceBytes));
throw new ArgumentException("Graphics context of type is not supported");
}

5
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -26,6 +26,7 @@ using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Collections.Pooled;
using Avalonia.Metadata;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Surfaces;
using Avalonia.Win32.DirectX;
using Avalonia.Win32.OpenGl.Angle;
@ -503,7 +504,7 @@ namespace Avalonia.Win32
}
}
public IEnumerable<object> Surfaces => new object[] { (IPlatformNativeSurfaceHandle)Handle, _gl, _framebuffer };
public IEnumerable<object> Surfaces => new object[] { Handle, _gl, _framebuffer };
public PixelPoint Position
{
@ -1468,7 +1469,7 @@ namespace Avalonia.Win32
public IStorageProvider StorageProvider { get; }
private class WindowImplPlatformHandle : IPlatformNativeSurfaceHandle
private class WindowImplPlatformHandle : INativeHandlePlatformSurface
{
private readonly WindowImpl _owner;
public WindowImplPlatformHandle(WindowImpl owner) => _owner = owner;

Loading…
Cancel
Save