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