-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathGodotVkSkiaGpu.cs
216 lines (173 loc) · 7.97 KB
/
GodotVkSkiaGpu.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Unicode;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Skia;
using Godot;
using SkiaSharp;
using static JLeb.Estragonia.VkInterop;
using Environment = System.Environment;
namespace JLeb.Estragonia;
/// <summary>Bridges the Godot Vulkan renderer with a Skia context used by Avalonia.</summary>
internal sealed class GodotVkSkiaGpu : ISkiaGpu {
private readonly RenderingDevice _renderingDevice;
private readonly GRContext _grContext;
private readonly uint _queueFamilyIndex;
private readonly VkBarrierHelper _barrierHelper;
public bool IsLost
=> _grContext.IsAbandoned;
public unsafe GodotVkSkiaGpu() {
_renderingDevice = RenderingServer.GetRenderingDevice();
if (_renderingDevice is null)
throw new NotSupportedException("Estragonia is only supported on Vulkan renderers (Forward+ or Mobile)");
IntPtr GetIntPtrDriverResource(RenderingDevice.DriverResource resource) {
var result = (IntPtr) _renderingDevice.GetDriverResource(resource, default, 0UL);
if (result == IntPtr.Zero)
throw new InvalidOperationException($"Godot returned null for driver resource {resource}");
return result;
}
var vkInstance = new VkInstance(GetIntPtrDriverResource(RenderingDevice.DriverResource.TopmostObject));
var vkPhysicalDevice = new VkPhysicalDevice(GetIntPtrDriverResource(RenderingDevice.DriverResource.PhysicalDevice));
var vkDevice = new VkDevice(GetIntPtrDriverResource(RenderingDevice.DriverResource.LogicalDevice));
var vkQueue = new VkQueue(GetIntPtrDriverResource(RenderingDevice.DriverResource.CommandQueue));
var vkQueueFamilyIndex = (uint) _renderingDevice.GetDriverResource(RenderingDevice.DriverResource.QueueFamily, default, 0UL);
if (!TryLoadVulkanLibrary(out var vkLibrary))
throw new DllNotFoundException("Couldn't find Vulkan loader library");
var vkGetInstanceProcAddr =
(delegate* unmanaged[Stdcall]<VkInstance, byte*, IntPtr>) NativeLibrary.GetExport(vkLibrary, "vkGetInstanceProcAddr");
var vkGetDeviceProcAddr =
(delegate* unmanaged[Stdcall]<VkDevice, byte*, IntPtr>) NativeLibrary.GetExport(vkLibrary, "vkGetDeviceProcAddr");
IntPtr GetVkProcAddress(string name, IntPtr instance, IntPtr device) {
Span<byte> utf8Name = stackalloc byte[128];
// The stackalloc buffer should always be sufficient for proc names
if (Utf8.FromUtf16(name, utf8Name[..^1], out _, out var bytesWritten) != OperationStatus.Done)
throw new InvalidOperationException($"Invalid proc name {name}");
utf8Name[bytesWritten] = 0;
fixed (byte* utf8NamePtr = utf8Name) {
return device != IntPtr.Zero
? vkGetDeviceProcAddr(new VkDevice(device), utf8NamePtr)
: vkGetInstanceProcAddr(new VkInstance(instance), utf8NamePtr);
}
}
var deviceApi = new VkDeviceApi(vkDevice, vkGetDeviceProcAddr);
var vkContext = new GRVkBackendContext {
VkInstance = vkInstance.Handle,
VkPhysicalDevice = vkPhysicalDevice.Handle,
VkDevice = vkDevice.Handle,
VkQueue = vkQueue.Handle,
GraphicsQueueIndex = vkQueueFamilyIndex,
GetProcedureAddress = GetVkProcAddress
};
if (GRContext.CreateVulkan(vkContext) is not { } grContext)
throw new InvalidOperationException("Couldn't create Vulkan context");
_grContext = grContext;
_queueFamilyIndex = vkQueueFamilyIndex;
_barrierHelper = new VkBarrierHelper(vkDevice, vkQueue, deviceApi, vkQueueFamilyIndex);
}
// Logic should match volk:
// https://github.com/godotengine/godot/blob/e4e024ab88efe74677769395886bc1b09eccbac7/thirdparty/volk/volk.c#L71-L115
private static bool TryLoadVulkanLibrary(out IntPtr handle) {
if (OperatingSystem.IsWindows())
return TryLoadByName("vulkan-1.dll", out handle);
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) {
return TryLoadByName("libvulkan.dylib", out handle)
|| TryLoadByName("libvulkan.1.dylib", out handle)
|| TryLoadByName("libMoltenVK.dylib", out handle)
|| TryLoadByPath("vulkan.framework/vulkan", out handle)
|| TryLoadByPath("MoltenVK.framework/MoltenVK", out handle)
|| (Environment.GetEnvironmentVariable("DYLD_FALLBACK_LIBRARY_PATH") is null
&& TryLoadByPath("/usr/local/lib/libvulkan.dylib", out handle)
);
}
return TryLoadByName("libvulkan.so.1", out handle)
|| TryLoadByName("libvulkan.so", out handle);
static bool TryLoadByName(string libraryName, out IntPtr handle)
=> NativeLibrary.TryLoad(libraryName, typeof(GodotVkSkiaGpu).Assembly, null, out handle);
static bool TryLoadByPath(string libraryPath, out IntPtr handle)
=> NativeLibrary.TryLoad(libraryPath, out handle);
}
object? IOptionalFeatureProvider.TryGetFeature(Type featureType)
=> null;
IDisposable IPlatformGraphicsContext.EnsureCurrent()
=> EmptyDisposable.Instance;
ISkiaGpuRenderTarget? ISkiaGpu.TryCreateRenderTarget(IEnumerable<object> surfaces)
=> surfaces.OfType<GodotSkiaSurface>().FirstOrDefault() is { } surface
? new GodotSkiaRenderTarget(surface, _grContext, _barrierHelper)
: null;
public GodotSkiaSurface CreateSurface(PixelSize size, double renderScaling) {
size = new PixelSize(Math.Max(size.Width, 1), Math.Max(size.Height, 1));
var gdRdTextureFormat = new RDTextureFormat {
Format = RenderingDevice.DataFormat.R8G8B8A8Unorm,
TextureType = RenderingDevice.TextureType.Type2D,
Width = (uint)size.Width,
Height = (uint)size.Height,
Depth = 1,
ArrayLayers = 1,
Mipmaps = 1,
Samples = RenderingDevice.TextureSamples.Samples1,
UsageBits = RenderingDevice.TextureUsageBits.SamplingBit
| RenderingDevice.TextureUsageBits.CanCopyFromBit
| RenderingDevice.TextureUsageBits.CanCopyToBit
| RenderingDevice.TextureUsageBits.ColorAttachmentBit
};
var gdRdTexture = _renderingDevice.TextureCreate(gdRdTextureFormat, new RDTextureView());
var vkImage = new VkImage(_renderingDevice.GetDriverResource(RenderingDevice.DriverResource.Texture, gdRdTexture, 0UL));
if (vkImage.Handle == 0UL)
throw new InvalidOperationException("Couldn't get Vulkan image from Godot texture");
var vkFormat = (uint) _renderingDevice.GetDriverResource(RenderingDevice.DriverResource.TextureDataFormat, gdRdTexture, 0UL);
if (vkFormat == 0U)
throw new InvalidOperationException("Couldn't get Vulkan format from Godot texture");
var grVkImageInfo = new GRVkImageInfo {
CurrentQueueFamily = _queueFamilyIndex,
Format = vkFormat,
Image = vkImage.Handle,
ImageLayout = (uint) VkImageLayout.COLOR_ATTACHMENT_OPTIMAL,
ImageTiling = (uint) VkImageTiling.OPTIMAL,
ImageUsageFlags = (uint) (
VkImageUsageFlags.SAMPLED_BIT |
VkImageUsageFlags.TRANSFER_SRC_BIT |
VkImageUsageFlags.TRANSFER_DST_BIT |
VkImageUsageFlags.COLOR_ATTACHMENT_BIT
),
LevelCount = 1,
SampleCount = 1,
Protected = false,
SharingMode = (uint) VkSharingMode.EXCLUSIVE
};
var skSurface = SKSurface.Create(
_grContext,
new GRBackendRenderTarget(size.Width, size.Height, 1, grVkImageInfo),
GRSurfaceOrigin.TopLeft,
SKColorType.Rgba8888,
new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal)
);
if (skSurface is null)
throw new InvalidOperationException("Couldn't create Skia surface from Vulkan image");
var gdTexture = new Texture2Drd {
TextureRdRid = gdRdTexture
};
var surface = new GodotSkiaSurface(
skSurface,
gdTexture,
vkImage,
VkImageLayout.UNDEFINED,
_renderingDevice,
renderScaling,
_barrierHelper
);
surface.TransitionLayoutTo(VkImageLayout.COLOR_ATTACHMENT_OPTIMAL);
return surface;
}
ISkiaSurface? ISkiaGpu.TryCreateSurface(PixelSize size, ISkiaGpuRenderSession? session)
=> session is GodotSkiaGpuRenderSession godotSession
? CreateSurface(size, godotSession.Surface.RenderScaling)
: null;
public void Dispose() {
_grContext.Dispose();
_barrierHelper.Dispose();
}
}