Vulkan is a new (2016) API from Khronos group (the maintainers of OpenGL and many more OpenWhatevers).
It is low-level than OpenGL and alternatives and feels more like “GPU programming” than “graphics programming”. A deep dive into technicalities may be well worth it, since you’re supposedly writing your own engine.
Vulkan trades some boilerplate for a more refined and thought-out paradigm where you don’t have to track whatever you think your driver is tracking in its deep pockets of mutable state.
While the amount of boilerplate code may appear unruly it is very straight-forward and can be easily stowed away under some combinators and utility packages.
Q: Why not OpenGL?
Q: Do I need to learn OpenGL first?
Q: Do I need new hardware to run Vulkan?
A: Maybe. It’s more of a driver thing.
Create window with SDL or GLFW.
- Pick either, but you’ll have to use its event processing.
Get a list of extensions to create
- Pick physical device.
Select rendering and presentation
- Create synchronization primitives.
- Create memory allocator (optional).
- Create window with SDL or GLFW.
Load static resources:
- Vertex and Index buffers for meshes.
- Textures, texture arrays and cube maps.
Set up rendering:
Create one or more
Swapchainand its resources to render into.
- Should be updated each time window changes size. It’s a major PITA.
Create your shaders (
Pipelines) that will run in
- You can compile their bytecode right in, or load on startup and update.
- Create one or more
- Acquire image index to use its resources (synchronization, framebuffer).
Allocate and fill
Bind one of its
DescriptorSet, if any.
- Bind vertex and index buffers.
- Set Push Constants.
- Bind one of its
- Wait on synchronization.
- Free transient resources (buffers, memory)
While not imposing any particular way to deal with lifetimes, every beginXxx/endXxx pair of functions has a matching withXxx form that accepts bracketing function. This allows to use ordinary
resourcet combinators to control the scope and lifetime.
The main problem is keeping transient resources long enough for the frame that uses them to be rendered and/or presented. When you receive a freshly-acquired image index that means that all resources for the same index could be safely disposed or reused. Beyond that, the usual concurrency practices are to be observed.
It is advised to retain data for that aren’t changing. This is in stark contrast with
[[concept-immediate-mode]]❌ rendering where everything is transient and CPU is hamster-busy telling GPU to draw exactly the same scene in all the details again and again.
For example, camera projection value does not change every frame, so you’d better not recalculate and reupload it. Furthermore, camera view may not change every frame, and that allows to squash projection and view together to free vertex shader from doing extra work for each vertex in the world.
There is supplemental VulkanMemoryAllocator package to manage GPU memory on a higher level than raw Vulkan bindings allow.
Those are usually quite basic and in C (C++ even), but that doesn’t matter too much. It’s the data flow and entity relations that matter.