Simple and efficient Vulkan loading with flextGL Play­ing with Vulkan but don’t want to in­clude thou­sands lines of var­i­ous head­ers just to call a few func­tions? FlextGL just learned Vulkan sup­port and it’s here to speed up your turn­around times.

Update: June 07, 2018 Since the time this ar­ti­cle was orig­i­nal­ly writ­ten, I agreed with @gink­go to take over flextGL main­tain­er­ship. The mosra/flextGL repos­i­to­ry is now the main place. You’ll get au­to­mat­i­cal­ly redi­rect­ed if you have a link to the orig­i­nal lo­ca­tion.

If you don’t know what flextGL is, it’s a func­tion load­er gen­er­a­tor for OpenGL, OpenGL ES and now al­so Vulkan. In com­par­i­son to GLEW, GL3W, GLAD, glLoad­Gen and all oth­er func­tion point­er load­ers it al­lows you to pro­vide a tem­plate and a whitelist of ver­sions, ex­ten­sions and func­tions to load, so you can load what you want, how­ev­er you want.

Chances are you’re us­ing flextGL for func­tion point­er load­ing in your GL / GLES code, so now you can use the same tool for your Vulkan back­end as well.

How? FlextGL con­tains a builtin Vulkan tem­plate that you can use to gen­er­ate a ba­sic load­er. In ad­di­tion you need Python 3 and Wheezy Tem­plate: pip3 install wheezy.template git clone git://github.com/mosra/flextGL --branch vulkan De­sired Vulkan ver­sion, ex­ten­sions and an op­tion­al func­tion white- or black­list is spec­i­fied us­ing a pro­file file: version 1.0 vulkan # Extensions to include on top of the core functionality. The VK_ prefix # is omitted. extension KHR_swapchain optional extension KHR_maintenance1 optional # ... # Function whitelist. If you omit this section, all functions from the # above version and extensions will be pulled in. The vk prefix is omitted. begin functions CreateInstance EnumeratePhysicalDevices GetPhysicalDeviceProperties GetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceMemoryProperties # ... end functions # You can also choose to have a function blacklist instead, delimited by # begin functions blacklist and end functions blacklist. With a pro­file file you can then gen­er­ate the Vulkan load­er head­er + source file like this. The generated/ di­rec­to­ry will then con­tain a pair of flextVk.h and flextVk.cpp files: ./flextGLgen.py -D generated -t vulkan profile.txt Now you can just in­clude it and use. The ac­tu­al func­tion point­er load­ing is done by call­ing flextVkInit() and af­ter that all Vulkan func­tions are avail­able glob­al­ly: #include "flextVk.h" int main () { /* Create an instance, load function pointers */ VkInstance instance ; { VkInstanceCreateInfo info {}; info . sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO ; vkCreateInstance ( & info , nullptr , & instance ); } flextVkInit ( instance ); VkPhysicalDevice physicalDevices [ 5 ]; std :: uint32_t count = 5 ; vkEnumeratePhysicalDevices ( instance , & count , & physicalDevice ); ... }

Why both­er? Com­pared to OpenGL, Vulkan is still do­ing ba­by steps, how­ev­er the amount of avail­able ex­ten­sions is grow­ing at an alarm­ing rate and soon the size of stock “all you can eat” head­ers will have a sig­nif­i­cant im­pact on your build times. Be­cause Vulkan API is more about var­i­ous types than just func­tion point­ers, flextGL en­sures that on­ly the struc­tures, enums and de­fines that are ac­tu­al­ly ref­er­enced by func­tions are pulled in, to shrink head­er sizes even fur­ther. So, let’s have some mea­sure­ments! Head­er sizes The fol­low­ing ta­ble com­pares raw line count and line count of pre­pro­cessed out­put when us­ing var­i­ous Vulkan load­ers, gen­er­at­ed by the fol­low­ing two com­mands us­ing GCC 7.3.1 for Vulkan 1.1.74: wc -l /path/to/header echo "#include <header>" | g++ -std = c++11 -E -x c++ - | wc -l Head­er Line count Af­ter pre­pro­cess­ing #include "flextVk.h" 1 710 1 929 #include <MagnumExternal/Vulkan/flextVk.h> 3 577 3 592 #include "volk.h" 837 6 352 #include <vulkan/vulkan.h> 7 470 7 363 #include <vulkan/vulkan.hpp> 42 544 ! 83 530 !! #include <GL/glew.h> (for com­par­i­son) 23 686 ?! 7 464 Com­pile times I abused Cor­rade::Test­Suite and std::system() a bit to bench­mark how long it takes GCC to com­pile each case from the above ta­ble in­to an ex­e­cutable that cre­ates the Vulkan in­stance and pop­u­lates func­tion point­ers us­ing giv­en load­er. On­ly com­pi­la­tion of the ac­tu­al main file is mea­sured, ex­clud­ing time need­ed to com­pile ex­tra *.cpp , *.c or *.so files, be­cause their cost is usu­al­ly amor­tized in the project. Here are the re­sults (hov­er over the bars to get the con­crete val­ues): 62.69 ± 0.84 ms 69.98 ± 2.04 ms 74.78 ± 3.65 ms 76.76 ± 3.34 ms 719.71 ± 6.95 ms 0 100 200 300 400 500 600 700 ms flextVk minimal flextVk Magnum Volk vulkan.h vulkan.hpp 1929 lines 3592 lines 6352 lines 7363 lines 83530 lines Compile time As ex­pect­ed, vulkan.hpp takes an in­sane amount of time to com­pile — ten times as much as the oth­ers, al­most a sec­ond — and this is for ev­ery file that (tran­si­tive­ly) in­cludes it! The com­pile time rough­ly cor­re­sponds to pre­pro­cessed line count from the above ta­ble, with flextGL-gen­er­at­ed head­ers be­ing the small­est and fastest to com­pile. As is usu­al, the head­ers usu­al­ly get tran­si­tive­ly in­clud­ed in­to ma­jor­i­ty of a project, so sav­ing 15 mil­lisec­onds per file when go­ing from stock head­ers to flextGL-gen­er­at­ed ones can save you 15 sec­onds in mod­er­ate­ly sized project hav­ing 1000 tar­gets. And this gap will be in­creas­ing as more ex­ten­sions get added to the stock head­ers. Run­time cost Be­cause flextGL loads on­ly the func­tions you ac­tu­al­ly re­quest­ed in­stead of ev­ery­thing that any­body could ev­er need, it has al­so some im­pact on start­up time. The fol­low­ing bench­mark mea­sures the time it takes to call load­er-spe­cif­ic ini­tial­iza­tion func­tions. The vulkan.h and vulkan.hpp head­ers aren’t in­clud­ed, be­cause these re­ly on ex­ter­nal func­tion point­er load­ing and don’t do any on their own. 15.09 ± 0.74 µs 84.98 ± 3.65 µs 197.13 ± 9.45 µs 934.27 ± 25.66 µs 0 200 400 600 800 1000 µs flextVk minimal flextVk Magnum Volk vkCreateInstance() 49 ptrs 192 ptrs 302 ptrs (for comparison) Runtime cost Again, the mea­sured time cor­re­sponds to ac­tu­al amount of load­ed func­tion point­ers. The Vulkan Tri­an­gle needs just 49 func­tion point­ers, Mag­num loads ev­ery­thing from Vulkan 1.1 to­geth­er with com­mand alias­es from pro­mot­ed ex­ten­sions, while Volk adds al­so all known ex­ten­sions. How­ev­er, note that these are mi­crosec­onds — and com­pared to time that’s need­ed to cre­ate a Vulkan in­stance (last mea­sure­ment), the sav­ings are on­ly very mi­nor.

Vulkan load­ing in Mag­num As of mosra/mag­num@b137703, Mag­num ships flextGL-gen­er­at­ed Vulkan head­ers. To save on del­e­ga­tion over­head, the de­ci­sion was to load per-de­vice func­tion point­ers in­stead of go­ing through per-in­stance func­tion point­ers for ev­ery­thing — that’s al­so what Volk does with great suc­cess, sav­ing as much as 5% to 10% of driv­er over­head, de­pend­ing on the work­flow. Be­sides that, load­ed Vulkan func­tions are not glob­al by de­fault in or­der to sup­port mul­ti­ple co­ex­ist­ing Vulkan in­stances: #include <MagnumExternal/Vulkan/flextVk.h> int main () { /* Create an instance */ VkInstance instance ; { VkInstanceCreateInfo info {}; info . sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO ; // ... vkCreateInstance ( & info , nullptr , & instance ); } /* Load per-instance function pointers */ FlextVkInstance i ; flextVkInitInstance ( instance , & i ); /* Create a device */ VkPhysicalDevice physicalDevice ; { uint32_t count = 1 ; i . EnumeratePhysicalDevices ( instance , & count , & physicalDevice ); } VkDevice device ; { VkDeviceCreateInfo info {}; info . sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO ; // ... i . CreateDevice ( physicalDevice , & info , nullptr , & device ); } /* Load per-device function pointers */ FlextVkDevice d ; flextVkInitDevice ( device , & d , i . GetDeviceProcAddr ); // ... } In the above snip­pet, the i and d struc­tures con­tain all load­ed func­tion point­ers. So in­stead of vkCreateBuffer(device, ...) you’d write d.Createbuffer(device, ) , for ex­am­ple. While this is prop­er­ly de­cou­pled, it might get in the way when just play­ing around or adapt­ing sam­ple code. For that rea­son, Mag­num pro­vides opt-in glob­al func­tion point­ers as well — just in­clude flextVkGlobal.h in­stead of flextVk.h and load your point­ers glob­al­ly: #include <MagnumExternal/Vulkan/flextVkGlobal.h> int main () { /* Create an instance */ VkInstance instance ; { VkInstanceCreateInfo info {}; info . sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO ; // ... vkCreateInstance ( & info , nullptr , & instance ); } /* Load per-instance function pointers globally */ flextVkInitInstance ( instance , & flextVkInstance ); /* Create a device */ VkPhysicalDevice physicalDevice ; { uint32_t count = 1 ; vkEnumeratePhysicalDevices ( instance , & count , & physicalDevice ); } VkDevice device ; { VkDeviceCreateInfo info {}; info . sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO ; // ... vkCreateDevice ( physicalDevice , & info , nullptr , & device ); } /* Load per-device function pointers globally */ flextVkInitDevice ( device , & flextVkDevice , vkGetDeviceProcAddr ); // ... } In this case flextVkInitInstance() and flextVkInitDevice() will load the point­ers in­to glob­al flextVkInstance and flextVkDevice struc­tures, which then are alias­es to glob­al vk*() func­tions. Both ap­proach­es can co­ex­ist, just be sure that you call in­stance-/de­vice-spe­cif­ic func­tions on the in­stance/de­vice that they were queried from and ev­ery­thing will work well. ~ ~ ~ And that’s it! Check Vulkan sup­port in flextGL out and please re­port bugs, if you find any. Thanks for read­ing, I’ll be back soon! Dis­cus­sion: Twit­ter, Red­dit r/vulkan and r/gamedev