Back to the main page of this topic.
When I started this project, I had had some experience with OpenGL, but I chose to use Vulkan, because I had read that it’s a lower-level API than OpenGL, so I thought I could learn more about GPU-s if I used Vulkan. Well, I’ve definitely learned things using Vulkan. At first I learned that it requires a lot of typing. I don’t like to write small utility functions, I like to keep my code base flexible (especially in a project only for learning). But in this project I ended up typing more or less the same thing even more, than I usually feel comfortable with. But I really have no right to complain about that, it wasn’t a major issue, perhaps I didn’t use Vulkan in a way it was intended, I just wanted to convince myself that my ideas weren’t completely stupid and the implementations produced the images I expected. Although there are plenty of functions in Vulkan, they seemed quiet standardized, they made more sense to me than most of the functions in OpenGL. For example, I was fine with the convention that almost every function has a corresponding struct for storing the parameters of the function. I know it’s more typing, but it’s more readable since I can see easily which value corresponds to which parameter of the function, which I think can help to avoid/detect bugs, since we don’t have named arguments in C++. For example
MyStruct s = {};
s.someParam = "I am a string";
s.someOtherParam = 3.14f;
s.someOtherOtherParam = 42;
someFunction(&s);
seems better to me than
someFunction("I am a string", 3.14f, 42);
if we have lot’s of arguments (and have more meaningful parameter names than in this example).
Also, the multithreading and memory management support in Vulkan seemed much much better than in OpenGL. In Vulkan we can create multiple command queues, which can be used independently from each other, by different threads. (I still don’t know, how these queues map to the hardware, but I guess/hope it happens quiet naturally, and it’s not that the GPU driver alone is responsible for thread-safety in a weird way.) For example, I used different queues for rendering and asset managing, so the asset manager could run on a different thread without any synchronization between the Vulkan API calls. While watching Handmade Hero, seeing that it is basically impossible to use OpenGL on multiple threads (creating a context per thread results in some weird and unpredictable bugs), I started to appreciate more the multithreadedness of Vulkan. Also, if I remember correctly, multithreading can be handled not only on the command queue level, but also on the command buffer level, if we make sure, that the every thread has a separate command pool, from which it allocates its command buffers.
Regarding the memory management, the feature to allocate different types of memory, independently from the actual resources (buffers, textures) seemed pretty useful. It was also nice to have complete control over where the data is copied and when memory is allocated. There are no functions similar to glBufferData or glTexImage2D, which magically make the data in the user’s virtual address space appear in GPU memory; the user has to allocate the host visible memory, map it, fill it, then allocate the GPU memory, record a command buffer which copies the data from the host visible memory to GPU memory, and submit it to a queue. It’s more work of course, than just calling glBufferData, but it’s more customizable, e.g. the memory allocations can be handled entirely by the user. In this project I made some device memory managers for handling memory allocated via Vulkan, e.g. free-list and monotonic buffer memory managers. Their usefulness are definitely questionable in this project, but it was a nice exercise, and again, it was just a project for learning.
I think it was a good choice to use Vulkan in this project. I’ve learned new notions that I probably wouldn’t have learned if I had used OpenGL. For me, the features in Vulkan seems more low-level than in OpenGL, for example command buffers, command queues, proper memory management, graphics pipeline creation, swap chains (and also descriptor sets I guess), but my major problem with these is that just like in OpenGL, I still don’t know how these things map to the hardware, what things are really done by the hardware and what things are done by the driver, I haven’t really found proper docs about that. If anyone found such thing, please share them! But if Vulkan is the best we can have, I can live with that. Or use Direct3D 12… I should try it, in a future project.