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