1 /// Vulkan implementation of GrAAL
2 module gfx.vulkan;
3 
4 import gfx.bindings.vulkan;
5 import gfx.core.log : LogTag;
6 import gfx.graal;
7 
8 enum gfxVkLogMask = 0x2000_0000;
9 package(gfx) immutable gfxVkLog = LogTag("GFX-VK", gfxVkLogMask);
10 
11 // some standard layers
12 
13 immutable lunarGValidationLayers = [
14     "VK_LAYER_LUNARG_core_validation",
15     "VK_LAYER_LUNARG_standard_validation",
16     "VK_LAYER_LUNARG_parameter_validation",
17 ];
18 
19 immutable debugReportInstanceExtensions = [
20     "VK_KHR_debug_report", "VK_EXT_debug_report"
21 ];
22 
23 @property ApiProps vulkanApiProps()
24 {
25     import gfx.math.proj : ndc, XYClip, ZClip;
26 
27     return ApiProps(
28         "vulkan", ndc(XYClip.rightHand, ZClip.zeroToOne)
29     );
30 }
31 
32 /// Load global level vulkan functions, and instance level layers and extensions
33 /// This function must be called before any other in this module
34 void vulkanInit()
35 {
36     _globCmds = loadVulkanGlobalCmds();
37     _instanceLayers = loadInstanceLayers();
38     _instanceExtensions = loadInstanceExtensions();
39 }
40 
41 struct VulkanVersion
42 {
43     import std.bitmanip : bitfields;
44     mixin(bitfields!(
45         uint, "patch", 12,
46         uint, "minor", 10,
47         uint, "major", 10,
48     ));
49 
50     this (in uint major, in uint minor, in uint patch) {
51         this.major = major; this.minor = minor; this.patch = patch;
52     }
53 
54     this (in uint vkVer) {
55         this(VK_VERSION_MAJOR(vkVer), VK_VERSION_MINOR(vkVer), VK_VERSION_PATCH(vkVer));
56     }
57 
58     static VulkanVersion fromUint(in uint vkVer) {
59         return *cast(VulkanVersion*)(cast(void*)&vkVer);
60     }
61 
62     uint toUint() const {
63         return *cast(uint*)(cast(void*)&this);
64     }
65 
66     string toString() {
67         import std.format : format;
68         return format("VulkanVersion(%s, %s, %s)", this.major, this.minor, this.patch);
69     }
70 }
71 
72 unittest {
73     const vkVer = VK_MAKE_VERSION(12, 7, 38);
74     auto vv = VulkanVersion.fromUint(vkVer);
75     assert(vv.major == 12);
76     assert(vv.minor == 7);
77     assert(vv.patch == 38);
78     assert(vv.toUint() == vkVer);
79 }
80 
81 struct VulkanLayerProperties
82 {
83     string layerName;
84     VulkanVersion specVer;
85     VulkanVersion implVer;
86     string description;
87 
88     @property VulkanExtensionProperties[] instanceExtensions()
89     {
90         return loadInstanceExtensions(layerName);
91     }
92 }
93 
94 struct VulkanExtensionProperties
95 {
96     string extensionName;
97     VulkanVersion specVer;
98 }
99 
100 /// Retrieve available instance level layer properties
101 @property VulkanLayerProperties[] vulkanInstanceLayers() {
102     return _instanceLayers;
103 }
104 /// Retrieve available instance level extensions properties
105 @property VulkanExtensionProperties[] vulkanInstanceExtensions()
106 {
107     return _instanceExtensions;
108 }
109 
110 /// Options to create a Vulkan instance.
111 struct VulkanCreateInfo
112 {
113     /// Application name and version.
114     string appName;
115     /// ditto
116     VulkanVersion appVersion = VulkanVersion(0, 0, 0);
117 
118     /// Mandatory layers that are needed by the application.
119     /// Instance creation will fail if one is not present.
120     const(string)[] mandatoryLayers;
121     /// Optional layers that will be enabled if present.
122     const(string)[] optionalLayers;
123 
124     /// Mandatory extensions that are needed by the application.
125     /// Instance creation will fail if one is not present.
126     const(string)[] mandatoryExtensions;
127     /// Optional extensions that will be enabled if present.
128     const(string)[] optionalExtensions;
129 
130     /// Build VulkanCreateInfo with default extensions, suitable for
131     /// 3D graphics on a surface.
132     /// Debug builds have by default Lunar-G validation layers and debug
133     /// extensions.
134     static VulkanCreateInfo defaultExts(string appName = "",
135             VulkanVersion appVersion = VulkanVersion(0, 0, 0))
136     {
137         VulkanCreateInfo info;
138         info.appName = appName;
139         info.appVersion = appVersion;
140         debug
141         {
142             info.optionalLayers = lunarGValidationLayers;
143             info.optionalExtensions = debugReportInstanceExtensions;
144         }
145         info.mandatoryExtensions = surfaceInstanceExtensions;
146         return info;
147     }
148 }
149 
150 /// Creates an Instance object with Vulkan backend with options
151 VulkanInstance createVulkanInstance(VulkanCreateInfo createInfo = VulkanCreateInfo.defaultExts())
152 {
153     import gfx : gfxVersionMaj, gfxVersionMin, gfxVersionMic;
154     import std.algorithm : all, canFind, map;
155     import std.array : array;
156     import std.exception : enforce;
157     import std.range : chain;
158     import std.string : toStringz;
159 
160     // throw if some requested layers or extensions are not available
161     // TODO: specific exception
162     foreach (l; createInfo.mandatoryLayers) {
163         enforce(
164             _instanceLayers.map!(il => il.layerName).canFind(l),
165             "Could not find layer " ~ l ~ " when creating Vulkan instance"
166         );
167     }
168     foreach (e; createInfo.mandatoryExtensions) {
169         enforce(
170             _instanceExtensions.map!(ie => ie.extensionName).canFind(e),
171             "Could not find extension " ~ e ~ " when creating Vulkan instance"
172         );
173     }
174 
175     const(string)[] layers = createInfo.mandatoryLayers;
176     foreach (l; createInfo.optionalLayers) {
177         if (_instanceLayers.map!(il => il.layerName).canFind(l)) {
178             layers ~= l;
179         }
180         else {
181             gfxVkLog.warningf("Optional layer %s is not present and won't be enabled", l);
182         }
183     }
184 
185     const(string)[] extensions = createInfo.mandatoryExtensions;
186     foreach (e; createInfo.optionalExtensions) {
187         if (_instanceExtensions.map!(ie => ie.extensionName).canFind(e)) {
188             extensions ~= e;
189         }
190         else {
191             gfxVkLog.warningf("Optional extension %s is not present and won't be enabled", e);
192         }
193     }
194 
195     VkApplicationInfo ai;
196     ai.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
197     if (createInfo.appName.length) {
198         ai.pApplicationName = toStringz(createInfo.appName);
199     }
200     ai.applicationVersion = createInfo.appVersion.toUint();
201     ai.pEngineName = "gfx-d\0".ptr;
202     ai.engineVersion = VK_MAKE_VERSION(gfxVersionMaj, gfxVersionMin, gfxVersionMic);
203     ai.apiVersion = VK_API_VERSION_1_0;
204 
205     const vkLayers = layers.map!toStringz.array;
206     const vkExts = extensions.map!toStringz.array;
207 
208     gfxVkLog.info("Opening Vulkan instance.");
209     gfxVkLog.infof("Vulkan layers:%s", layers.length?"":" none");
210     foreach (l; layers) {
211         gfxVkLog.infof("    %s", l);
212     }
213     gfxVkLog.infof("Vulkan extensions:%s", extensions.length?"":" none");
214     import std.algorithm : canFind, filter, map;
215     import std.array : array;
216     import std.range : chain;
217     foreach (e; extensions) {
218         gfxVkLog.infof("    %s", e);
219     }
220 
221     VkInstanceCreateInfo ici;
222     ici.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
223     ici.pApplicationInfo = &ai;
224     ici.enabledLayerCount = cast(uint)vkLayers.length;
225     ici.ppEnabledLayerNames = vkLayers.ptr;
226     ici.enabledExtensionCount = cast(uint)vkExts.length;
227     ici.ppEnabledExtensionNames = vkExts.ptr;
228 
229     auto vk = _globCmds;
230     VkInstance vkInst;
231     vulkanEnforce(vk.CreateInstance(&ici, null, &vkInst), "Could not create Vulkan instance");
232 
233     return new VulkanInstance(vkInst, layers, extensions);
234 }
235 
236 /// Retrieve available device level layers
237 @property VulkanLayerProperties[] vulkanDeviceLayers(PhysicalDevice device) {
238     auto pd = cast(VulkanPhysicalDevice)device;
239     if (!pd) return [];
240 
241     return pd._availableLayers;
242 }
243 /// Retrieve available instance level extensions properties
244 VulkanExtensionProperties[] vulkanDeviceExtensions(PhysicalDevice device, in string layerName=null)
245 {
246     auto pd = cast(VulkanPhysicalDevice)device;
247     if (!pd) return [];
248 
249     if (!layerName) {
250         return pd._availableExtensions;
251     }
252     else {
253         return pd.loadDeviceExtensions(layerName);
254     }
255 }
256 
257 void overrideDeviceOpenVulkanLayers(PhysicalDevice device, string[] layers)
258 {
259     auto pd = cast(VulkanPhysicalDevice)device;
260     if (!pd) return;
261 
262     pd._openLayers = layers;
263 }
264 
265 void overrideDeviceOpenVulkanExtensions(PhysicalDevice device, string[] extensions)
266 {
267     auto pd = cast(VulkanPhysicalDevice)device;
268     if (!pd) return;
269 
270     pd._openExtensions = extensions;
271 }
272 
273 
274 package:
275 
276 import gfx.core.rc;
277 import gfx.graal.device;
278 import gfx.graal.format;
279 import gfx.graal.memory;
280 import gfx.graal.presentation;
281 import gfx.graal.queue;
282 import gfx.vulkan.conv;
283 import gfx.vulkan.device;
284 import gfx.vulkan.error;
285 import gfx.vulkan.wsi : VulkanSurface;
286 
287 import std.exception : enforce;
288 
289 __gshared VkGlobalCmds _globCmds;
290 __gshared VulkanLayerProperties[] _instanceLayers;
291 __gshared VulkanExtensionProperties[] _instanceExtensions;
292 
293 VulkanLayerProperties[] loadInstanceLayers()
294 {
295     auto vk = _globCmds;
296     uint count;
297     vulkanEnforce(
298         vk.EnumerateInstanceLayerProperties(&count, null),
299         "Could not retrieve Vulkan instance layers"
300     );
301     if (!count) return[];
302 
303     auto vkLayers = new VkLayerProperties[count];
304     vulkanEnforce(
305         vk.EnumerateInstanceLayerProperties(&count, &vkLayers[0]),
306         "Could not retrieve Vulkan instance layers"
307     );
308 
309     import std.algorithm : map;
310     import std.array : array;
311     import std.string : fromStringz;
312 
313     return vkLayers
314             .map!((ref VkLayerProperties vkLp) {
315                 return VulkanLayerProperties(
316                     fromStringz(&vkLp.layerName[0]).idup,
317                     VulkanVersion.fromUint(vkLp.specVersion),
318                     VulkanVersion.fromUint(vkLp.implementationVersion),
319                     fromStringz(&vkLp.description[0]).idup,
320                 );
321             })
322             .array;
323 }
324 
325 VulkanExtensionProperties[] loadInstanceExtensions(in string layerName=null)
326 {
327     import std.string : toStringz;
328 
329     const(char)* layer;
330     if (layerName.length) {
331         layer = toStringz(layerName);
332     }
333     auto vk = _globCmds;
334     uint count;
335     vulkanEnforce(
336         vk.EnumerateInstanceExtensionProperties(layer, &count, null),
337         "Could not retrieve Vulkan instance extensions"
338     );
339     if (!count) return[];
340 
341     auto vkExts = new VkExtensionProperties[count];
342     vulkanEnforce(
343         vk.EnumerateInstanceExtensionProperties(layer, &count, &vkExts[0]),
344         "Could not retrieve Vulkan instance extensions"
345     );
346 
347     import std.algorithm : map;
348     import std.array : array;
349     import std.string : fromStringz;
350 
351     return vkExts
352             .map!((ref VkExtensionProperties vkExt) {
353                 return VulkanExtensionProperties(
354                     fromStringz(&vkExt.extensionName[0]).idup,
355                     VulkanVersion.fromUint(vkExt.specVersion)
356                 );
357             })
358             .array;
359 }
360 
361 VulkanLayerProperties[] loadDeviceLayers(VulkanPhysicalDevice pd)
362 {
363     auto vk = pd.vk;
364     uint count;
365     vulkanEnforce(
366         vk.EnumerateDeviceLayerProperties(pd.vkObj, &count, null),
367         "Could not retrieve Vulkan device layers"
368     );
369     if (!count) return[];
370 
371     auto vkLayers = new VkLayerProperties[count];
372     vulkanEnforce(
373         vk.EnumerateDeviceLayerProperties(pd.vkObj, &count, &vkLayers[0]),
374         "Could not retrieve Vulkan device layers"
375     );
376 
377     import std.algorithm : map;
378     import std.array : array;
379     import std.string : fromStringz;
380 
381     return vkLayers
382             .map!((ref VkLayerProperties vkLp) {
383                 return VulkanLayerProperties(
384                     fromStringz(&vkLp.layerName[0]).idup,
385                     VulkanVersion.fromUint(vkLp.specVersion),
386                     VulkanVersion.fromUint(vkLp.implementationVersion),
387                     fromStringz(&vkLp.description[0]).idup,
388                 );
389             })
390             .array;
391 }
392 
393 VulkanExtensionProperties[] loadDeviceExtensions(VulkanPhysicalDevice pd, in string layerName=null)
394 {
395     import std.string : toStringz;
396 
397     const(char)* layer;
398     if (layerName.length) {
399         layer = toStringz(layerName);
400     }
401 
402     auto vk = pd.vk;
403     uint count;
404     vulkanEnforce(
405         vk.EnumerateDeviceExtensionProperties(pd.vkObj, layer, &count, null),
406         "Could not retrieve Vulkan device extensions"
407     );
408     if (!count) return[];
409 
410     auto vkExts = new VkExtensionProperties[count];
411     vulkanEnforce(
412         vk.EnumerateDeviceExtensionProperties(pd.vkObj, layer, &count, &vkExts[0]),
413         "Could not retrieve Vulkan device extensions"
414     );
415 
416     import std.algorithm : map;
417     import std.array : array;
418     import std.string : fromStringz;
419 
420     return vkExts
421             .map!((ref VkExtensionProperties vkExt) {
422                 return VulkanExtensionProperties(
423                     fromStringz(&vkExt.extensionName[0]).idup,
424                     VulkanVersion.fromUint(vkExt.specVersion)
425                 );
426             })
427             .array;
428 }
429 
430 class VulkanObj(VkType)
431 {
432     this (VkType vkObj) {
433         _vkObj = vkObj;
434     }
435 
436     final @property VkType vkObj() {
437         return _vkObj;
438     }
439 
440     private VkType _vkObj;
441 }
442 
443 class VulkanInstObj(VkType) : Disposable
444 {
445     this (VkType vkObj, VulkanInstance inst)
446     {
447         _vkObj = vkObj;
448         _inst = inst;
449         _inst.retain();
450     }
451 
452     override void dispose() {
453         _inst.release();
454         _inst = null;
455     }
456 
457     final @property VkType vkObj() {
458         return _vkObj;
459     }
460 
461     final @property VulkanInstance inst() {
462         return _inst;
463     }
464 
465     final @property VkInstance vkInst() {
466         return _inst.vkObj;
467     }
468 
469     private VkType _vkObj;
470     private VulkanInstance _inst;
471 }
472 
473 final class VulkanInstance : VulkanObj!(VkInstance), Instance
474 {
475     mixin(atomicRcCode);
476 
477     this(VkInstance vkObj, in string[] layers, in string[] extensions) {
478         super(vkObj);
479         _vk = new VkInstanceCmds(vkObj, _globCmds);
480         this.layers = layers;
481         this.extensions = extensions;
482     }
483 
484     override void dispose() {
485         if (_vkCb) {
486             vk.DestroyDebugReportCallbackEXT(vkObj, _vkCb, null);
487             _vkCb = VK_NULL_ND_HANDLE;
488             _callback = null;
489         }
490         vk.DestroyInstance(vkObj, null);
491     }
492 
493     override @property Backend backend() {
494         return Backend.vulkan;
495     }
496 
497     override @property ApiProps apiProps() {
498         return vulkanApiProps;
499     }
500 
501     @property VkInstanceCmds vk() {
502         return _vk;
503     }
504 
505     override PhysicalDevice[] devices()
506     {
507         import std.algorithm : map;
508         import std.array : array, uninitializedArray;
509 
510         if (!_phDs.length) {
511             uint count;
512             vulkanEnforce(vk.EnumeratePhysicalDevices(vkObj, &count, null),
513                     "Could not enumerate Vulkan devices");
514             auto devices = uninitializedArray!(VkPhysicalDevice[])(count);
515             vulkanEnforce(vk.EnumeratePhysicalDevices(vkObj, &count, devices.ptr),
516                     "Could not enumerate Vulkan devices");
517 
518             _phDs = devices
519                 .map!(vkD => cast(PhysicalDevice)new VulkanPhysicalDevice(vkD, this))
520                 .array();
521         }
522         return _phDs;
523     }
524 
525     override void setDebugCallback(DebugCallback callback) {
526         VkDebugReportCallbackCreateInfoEXT ci;
527         ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
528         ci.flags = 0x1f;
529         ci.pfnCallback = &gfxd_vk_DebugReportCallback;
530         ci.pUserData = cast(void*)this;
531 
532         vk.CreateDebugReportCallbackEXT(vkObj, &ci, null, &_vkCb);
533         _callback = callback;
534     }
535 
536     /// The layers enabled with this instance
537     public const(string[]) layers;
538     /// The extensions enabled with this instance
539     public const(string[]) extensions;
540 
541     private VkInstanceCmds _vk;
542     private PhysicalDevice[] _phDs;
543     private VkDebugReportCallbackEXT _vkCb;
544     private DebugCallback _callback;
545 }
546 
547 extern(C) nothrow {
548     VkBool32 gfxd_vk_DebugReportCallback(VkDebugReportFlagsEXT flags,
549                                          VkDebugReportObjectTypeEXT /+objectType+/,
550                                          ulong /+object+/,
551                                          size_t /+location+/,
552                                          int /+messageCode+/,
553                                          const(char)* pLayerPrefix,
554                                          const(char)* pMessage,
555                                          void* pUserData)
556     {
557         auto vkInst = cast(VulkanInstance)pUserData;
558         if (vkInst && vkInst._callback) {
559             import gfx.vulkan.conv : debugReportFlagsToGfx;
560             import std.string : fromStringz;
561             try {
562                 vkInst._callback(debugReportFlagsToGfx(flags), fromStringz(pMessage).idup);
563             }
564             catch(Exception ex) {
565                 import std.exception : collectException;
566                 import std.stdio : stderr;
567                 collectException(
568                     stderr.writefln("Exception thrown in debug callback: %s", ex.msg)
569                 );
570             }
571         }
572 
573         return VK_FALSE;
574     }
575 }
576 
577 version(glfw) {
578     extern(C) @nogc nothrow int glfwGetPhysicalDevicePresentationSupport(VkInstance, VkPhysicalDevice, uint);
579 }
580 
581 final class VulkanPhysicalDevice : PhysicalDevice
582 {
583     this(VkPhysicalDevice vkObj, VulkanInstance inst) {
584         _vkObj = vkObj;
585         _inst = inst;
586         _vk = _inst.vk;
587 
588         vk.GetPhysicalDeviceProperties(_vkObj, &_vkProps);
589 
590         _availableLayers = loadDeviceLayers(this);
591         _availableExtensions = loadDeviceExtensions(this);
592 
593         import std.algorithm : canFind, map;
594         import std.exception : enforce;
595         debug {
596             foreach (l; lunarGValidationLayers) {
597                 if (_availableLayers.map!"a.layerName".canFind(l)) {
598                     _openLayers ~= l;
599                 }
600             }
601         }
602         version(GfxOffscreen) {}
603         else {
604             import gfx.vulkan.wsi : swapChainDeviceExtension;
605             enforce(_availableExtensions.map!"a.extensionName".canFind(swapChainDeviceExtension));
606             _openExtensions ~= swapChainDeviceExtension;
607         }
608     }
609 
610     @property VkPhysicalDevice vkObj() {
611         return _vkObj;
612     }
613 
614     @property VkInstanceCmds vk() {
615         return _vk;
616     }
617 
618     override @property Instance instance()
619     {
620         auto inst = lockObj(_inst);
621         if (!inst) return null;
622         return giveAwayObj(inst);
623     }
624 
625     override @property string name() {
626         import std.string : fromStringz;
627         return fromStringz(_vkProps.deviceName.ptr).idup;
628     }
629     override @property DeviceType type() {
630         return devTypeToGfx(_vkProps.deviceType);
631     }
632     override @property DeviceFeatures features() {
633         import std.algorithm : canFind, map;
634         import gfx.vulkan.wsi : swapChainDeviceExtension;
635 
636         VkPhysicalDeviceFeatures vkFeats;
637         vk.GetPhysicalDeviceFeatures(vkObj, &vkFeats);
638 
639         DeviceFeatures features;
640         features.anisotropy = vkFeats.samplerAnisotropy == VK_TRUE;
641         features.presentation = vulkanDeviceExtensions(this)
642                 .map!(e => e.extensionName)
643                 .canFind(swapChainDeviceExtension);
644         return features;
645     }
646     /// See_Also: <a href="https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPhysicalDeviceLimits.html">VkPhysicalDeviceLimits</a>
647     override @property DeviceLimits limits()
648     {
649         DeviceLimits limits;
650         limits.linearOptimalGranularity =
651                 cast(size_t)_vkProps.limits.bufferImageGranularity;
652         limits.maxStorageBufferSize =
653                 cast(size_t)_vkProps.limits.maxStorageBufferRange;
654         limits.maxDescriptorSetStorageBuffers =
655                 cast(size_t)_vkProps.limits.maxDescriptorSetStorageBuffers;
656         limits.maxDescriptorSetStorageBuffersDynamic =
657                 cast(size_t)_vkProps.limits.maxDescriptorSetStorageBuffersDynamic;
658         limits.minStorageBufferOffsetAlignment =
659                 cast(size_t)_vkProps.limits.minStorageBufferOffsetAlignment;
660         limits.maxPushConstantsSize =
661                 cast(size_t)_vkProps.limits.maxPushConstantsSize;
662         limits.maxUniformBufferSize =
663                 cast(size_t)_vkProps.limits.maxUniformBufferRange;
664         limits.maxDescriptorSetUniformBuffers =
665                 cast(size_t)_vkProps.limits.maxDescriptorSetUniformBuffers;
666         limits.maxDescriptorSetUniformBuffersDynamic =
667                 cast(size_t)_vkProps.limits.maxDescriptorSetUniformBuffersDynamic;
668         limits.minUniformBufferOffsetAlignment =
669                 cast(size_t)_vkProps.limits.minUniformBufferOffsetAlignment;
670         return limits;
671     }
672 
673     override @property MemoryProperties memoryProperties()
674     {
675         VkPhysicalDeviceMemoryProperties vkProps=void;
676         vk.GetPhysicalDeviceMemoryProperties(_vkObj, &vkProps);
677 
678         MemoryProperties props;
679 
680         foreach(i; 0 .. vkProps.memoryHeapCount) {
681             const vkHeap = vkProps.memoryHeaps[i];
682             props.heaps ~= MemoryHeap(
683                 cast(size_t)vkHeap.size, (vkHeap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0
684             );
685         }
686         foreach(i; 0 .. vkProps.memoryTypeCount) {
687             const vkMemType = vkProps.memoryTypes[i];
688             props.types ~= MemoryType(
689                 memPropsToGfx(vkMemType.propertyFlags),
690                 vkMemType.heapIndex,
691             );
692         }
693 
694         return props;
695     }
696 
697     override @property QueueFamily[] queueFamilies()
698     {
699         import std.array : array, uninitializedArray;
700         uint count;
701         vk.GetPhysicalDeviceQueueFamilyProperties(_vkObj, &count, null);
702 
703         auto vkQueueFams = uninitializedArray!(VkQueueFamilyProperties[])(count);
704         vk.GetPhysicalDeviceQueueFamilyProperties(_vkObj, &count, vkQueueFams.ptr);
705 
706         import std.algorithm : map;
707         return vkQueueFams.map!(vkObj => QueueFamily(
708             queueCapToGfx(vkObj.queueFlags), vkObj.queueCount
709         )).array;
710     }
711 
712     override FormatProperties formatProperties(in Format format)
713     {
714         VkFormatProperties vkFp;
715         vk.GetPhysicalDeviceFormatProperties(_vkObj, format.toVk(), &vkFp);
716 
717         return FormatProperties(
718             vkFp.linearTilingFeatures.toGfx(),
719             vkFp.optimalTilingFeatures.toGfx(),
720             vkFp.bufferFeatures.toGfx(),
721         );
722     }
723 
724     override bool supportsSurface(uint queueFamilyIndex, Surface graalSurface) {
725         auto surf = enforce(
726             cast(VulkanSurface)graalSurface,
727             "Did not pass a Vulkan surface"
728         );
729         VkBool32 supported;
730         vulkanEnforce(
731             vk.GetPhysicalDeviceSurfaceSupportKHR(vkObj, queueFamilyIndex, surf.vkObj, &supported),
732             "Could not query vulkan surface support"
733         );
734 
735         version(glfw) {
736             import bindbc.glfw : GLFW_FALSE;
737 
738             const supportsGlfw = glfwGetPhysicalDevicePresentationSupport(_inst.vkObj, _vkObj, queueFamilyIndex);
739             return supported != VK_FALSE && supportsGlfw != GLFW_FALSE;
740         } else {
741             return supported != VK_FALSE;
742         }
743     }
744 
745     override SurfaceCaps surfaceCaps(Surface graalSurface) {
746         auto surf = enforce(
747             cast(VulkanSurface)graalSurface,
748             "Did not pass a Vulkan surface"
749         );
750         VkSurfaceCapabilitiesKHR vkSc;
751         vulkanEnforce(
752             vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(vkObj, surf.vkObj, &vkSc),
753             "Could not query vulkan surface capabilities"
754         );
755         return vkSc.toGfx();
756     }
757 
758     override Format[] surfaceFormats(Surface graalSurface) {
759         auto surf = enforce(
760             cast(VulkanSurface)graalSurface,
761             "Did not pass a Vulkan surface"
762         );
763 
764         uint count;
765         vulkanEnforce(
766             vk.GetPhysicalDeviceSurfaceFormatsKHR(vkObj, surf.vkObj, &count, null),
767             "Could not query vulkan surface formats"
768         );
769         auto vkSf = new VkSurfaceFormatKHR[count];
770         vulkanEnforce(
771             vk.GetPhysicalDeviceSurfaceFormatsKHR(vkObj, surf.vkObj, &count, &vkSf[0]),
772             "Could not query vulkan surface formats"
773         );
774 
775         import std.algorithm : filter, map;
776         import std.array : array;
777         return vkSf
778                 .filter!(sf => sf.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
779                 .map!(sf => sf.format.toGfx())
780                 .array;
781     }
782 
783     override PresentMode[] surfacePresentModes(Surface graalSurface) {
784         auto surf = enforce(
785             cast(VulkanSurface)graalSurface,
786             "Did not pass a Vulkan surface"
787         );
788 
789         uint count;
790         vulkanEnforce(
791             vk.GetPhysicalDeviceSurfacePresentModesKHR(vkObj, surf.vkObj, &count, null),
792             "Could not query vulkan surface present modes"
793         );
794         auto vkPms = new VkPresentModeKHR[count];
795         vulkanEnforce(
796             vk.GetPhysicalDeviceSurfacePresentModesKHR(vkObj, surf.vkObj, &count, &vkPms[0]),
797             "Could not query vulkan surface present modes"
798         );
799 
800         import std.algorithm : filter, map;
801         import std.array : array;
802         return vkPms
803                 .filter!(pm => pm.hasGfxSupport)
804                 .map!(pm => pm.toGfx())
805                 .array;
806     }
807 
808     override Device open(in QueueRequest[] queues, in DeviceFeatures features=DeviceFeatures.all)
809     {
810         import std.algorithm : filter, map, sort;
811         import std.array : array;
812         import std.exception : enforce;
813         import std.string : toStringz;
814         import gfx.vulkan.wsi : swapChainDeviceExtension;
815 
816         if (!queues.length) {
817             return null;
818         }
819 
820         const qcis = queues.map!((const(QueueRequest) r) {
821             VkDeviceQueueCreateInfo qci;
822             qci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
823             qci.queueFamilyIndex = r.familyIndex;
824             qci.queueCount = cast(uint)r.priorities.length;
825             qci.pQueuePriorities = r.priorities.ptr;
826             return qci;
827         }).array;
828 
829         const layers = _openLayers.map!toStringz.array;
830         const extensions = _openExtensions
831                 .filter!(e => e != swapChainDeviceExtension || features.presentation)
832                 .map!toStringz.array;
833         VkPhysicalDeviceFeatures vkFeats;
834         vkFeats.samplerAnisotropy = features.anisotropy ? VK_TRUE : VK_FALSE;
835 
836         VkDeviceCreateInfo ci;
837         ci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
838         ci.queueCreateInfoCount = cast(uint)qcis.length;
839         ci.pQueueCreateInfos = qcis.ptr;
840         ci.enabledLayerCount = cast(uint)layers.length;
841         ci.ppEnabledLayerNames = layers.ptr;
842         ci.enabledExtensionCount = cast(uint)extensions.length;
843         ci.ppEnabledExtensionNames = extensions.ptr;
844         ci.pEnabledFeatures = &vkFeats;
845 
846         VkDevice vkDev;
847         vulkanEnforce(vk.CreateDevice(_vkObj, &ci, null, &vkDev),
848                 "Vulkan device creation failed");
849 
850         return new VulkanDevice(vkDev, this, _inst);
851     }
852 
853     private VkPhysicalDevice _vkObj;
854     private VkPhysicalDeviceProperties _vkProps;
855     private VulkanInstance _inst;
856 
857     private VkInstanceCmds _vk;
858 
859     private VulkanLayerProperties[] _availableLayers;
860     private VulkanExtensionProperties[] _availableExtensions;
861 
862     private string[] _openLayers;
863     private string[] _openExtensions;
864 }
865 
866 DeviceType devTypeToGfx(in VkPhysicalDeviceType vkType)
867 {
868     switch (vkType) {
869     case VK_PHYSICAL_DEVICE_TYPE_OTHER:
870         return DeviceType.other;
871     case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
872         return DeviceType.integratedGpu;
873     case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
874         return DeviceType.discreteGpu;
875     case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
876         return DeviceType.virtualGpu;
877     case VK_PHYSICAL_DEVICE_TYPE_CPU:
878         return DeviceType.cpu;
879     default:
880         assert(false, "unexpected vulkan device type constant");
881     }
882 }