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