Today we won’t be talking about making a game – today is a technical talk about the new OpenGL Replacement, Vulkan! Not to be confused with the planet Vulcan, which is where everyones favorite green blooded, pointy eared alien is from!
We’ve just about finished dropping the new Khronos graphics API – Vulkan – into our Dimension Game Engine.
Now the Dimension Engine is a cross platform cross API engine, supporting OpenGL ES 2.0, OpenGL 3.1, Direct X11 and now Vulkan, and frankly, trying to shoehorn it in has been not the most pleasant of engineering tasks.
So what’s so hard about it?
Well, Vulkan is fast. It’s lightweight – it 20% faster than your average OpenGL or Direct X driver, if not faster in some circumstances. It exposes a lot more of what it’s doing, so you get to manage it, as well as have a better understanding of what’s going on under the hood. And it’s flexible, and offers some features we’ve not seen in an API like this before, certainly not as well thought out and not kludgeful.
But, all that flexibility and lightweightness comes at a cost. And that cost is Your Time and Your Brain, and copious amounts of swearing, most of the time.
It’s lightweight because, basically, half the work a driver used to do is now fostered on you. For example, memory management. It used to be that older drivers handled uploading of textures and vertex array and the like from main memory to the GPU memory for you. Good old openGL would do it for you and keep track of what is where and we all liked that. OpenGL was a good fella for dealing with that all for you. Keep it up, OpenGL. Of course, on occasion, it would sneak up and stab you in the back – run out of memory on the main GPU? No problem. I’ll just keep a copy of all your textures in main memory on your PC and copy them back and forth as you need them. Which, if you are trying to push a quart into a pint pot (*cough cough* Arkham Asylum – 4G of textures expecting an XBox GPu, and you only have a 1G video card? Well hard luck. I’m going to swap things out, in the middle of a frame, because what else am I going to do?) Performance goes out the window and you are feeling very betrayed by OpenGL, but it’s only trying to do what it’s meant to do.
Vulkan is not like that. Vulkan will not stab you in the back. Vulkan will take ten steps in front, turn and put a bullet right in the middle of your forehead, looking you in the eyes the whole time and blowing a raspberry at you while doing it.
With Vulkan, you have to ask for memory to then push graphics into, for buffer space to push up vertex arrays, and space to put render textures into and so on. And if you run out, well boyah, sucks to be you! You now have to work out what textures / buffers to dump and so on and so forth. Effectively, if you burst the card limits, you now have to write your own memory manager. Which sucks a bit if you just want to dump a lot of polygons on the screen.
There are some other gotchas to be aware of – most notably, SPIR-V shader compilation steps. SPIR-V is a Just-In-Time format, where you precompile your shaders from text into pseudo assembly language ahead of time so that on load of the game, the API / then marks up the assembly so the shaders are in the correct machine language format for the card. It’s pretty much the same approach as C# implements. It makes loading shaders – particularly when you have tens of thousands of them, as many games these days are wont to have – much faster, and is a generally good thing. Except when the shaders are loaded, and you need to know what the data you are supposed to feed into them looks like, well, then they suck, because SPIR-V as an API inside of Vulkan doesn’t tell you that. No more “Hey, where’s this uniform? I need to set some data” – no, you just have to already know what the inputs are for that shader, and damn your eyes, sir.
Thankfully, there is help at hand with the SPIR-V Cross compiler – a third party compiler which generates the same asm as the default compiler does, but attaches a bunch of debug labeling information with it, so you can ask what the offset and type is of the third variable in the Uniform Buffer Object definition. It just sucks a lot that you have to do this – that the original decision to go with SPIR-V didn’t take this into account. Eat your own dog food guys.
There are other things that Vulkan allows us to do, and we are barely scratching the surface with these features – like Multi-GPU support, where one GPU can be doing one thing, and another picks up the task as soon as the first has finished. For example, my trusty 2013 MacBook Pro has two GPU’s in it. A less-than-awesome-but-still-capable Intel HD graphics, plus slightly-more-awesome nVidia 450m mobile GPU. With Vulkan, it’s entirely possible to have the nVidia rendering a scene, and doing the heavy lifting, then when it’s done, handing the resulting image to the Intel chip to do some post processing, Depth of field, for example, – while the nVidia chip goes off to render another frame. Vulkan even has the capability to make the Intel command queue wait till the nVidia chip is done with it’s rendering – true cross thread and cross content threading, all as a built in part of the API!
Now, you do have to have more than one GPU available to you use this kind of thing, and it’s pretty much a lost cause on mobile, but still, Vulkan is designed for the future. For Iphone 12, which will have three GPUS on board (You read it here first!).
But the biggest PIA of this language is just that with all the flexibility comes a HUGE amount of extremely niggly overhead. If OpenGL is a V8 Mustang GT, with a 6 speed automatic flappy paddle gearbox, then Vulkan is a kit car, made of carbon fiber, and one that you have to build yourself first. It can go faster than the Mustang but boy, you’d better know what you are doing when you build it, and EVERY bolt had better be the EXACT right bolt, in EXACTLY the right place or nothing is going anywhere.
Vulkan requires an assload of describing everything, and has zero tolerance for anything wrong. And what’s worse, when things _do_ go wrong (and they will, frequently), because the drivers are do lightweight (Yay for driver writers! An easy life at last!), they have precious little range or error checking and they tend to just crash, with out any helpful warnings. Just bang – off to Upside Down world with you.
There are validator libaries available for Vulkan, and I expect them to get better over time, but be aware, you are going to be writing a LOT of validation code to be sure what you are submitting actually works.
And what’s worse is that some of the better profilers out there – RenderDoc, I’m looking at you – also tend to crash when malformed Vulkan data is thrown at the API, for precisely the same reasons – the profilers sit on the driver, and if the driver ain’t happy, then aint NO ONE happy.
Vulkan is fiddly, intensely complicated, extremely fragile, has some incredibly far sighted idea’s behind it’s approaches and is very dense, and the amount of infrastructure code you are going to have to write is going to frighten you, once you understand what that actually means.
But like most things, once it’s written, it’s written.
I suspect there is going to be a LOT of cut-n-paste code going on initially, and when it breaks, no one will understand why, so if you get the opportunity, invest in StackOverflow, because god knows it’s gonna explode soon.
Good luck out there.