This is the second part of my dev diary about implementing Vulkan support in Crimild. Check out the first part for a brief introduction if you haven’t read it yet.
Changes, changes, changes
I’m still struggling with the class hierarchy and responsibilities. I would like to use RAII as much as possible, but I’m not sure about the API design and who’s responsible for creating new objects yet.
For example, it feels natural that the Instance (basically a wrapper for VkInstance) creates the render devices and swapchain. But, since the surface is platform dependent, it must be created somewhere else which doesn’t feel right.
On the other hand, a render device should create new resources (like images or buffers) but that also means that such resources are coupled with that particular device. What if we have more than one device?
I know, I’m overthinking it as usual but, to be honest, defining the class hierarchy has proven to be the most challenging task so far.
As a side note, I decided to use exceptions for error reporting. Like when attempting to create a Vulkan objects and the process fails for some reason. This simplifies the code a lot and, although there’s an overhead in using exceptions, they’re only used in error paths so it’s not a big issue.
The process of initializing Vulkan in Crimild can be described as follows:
- The VulkanSystem creates a Vulkan Instance and keeps a strong reference to it that lives for the rest of the simulation
- The VulkanSystem creates a surface where we’re going to render into. This is platform dependent mostly.
- The Instance creates a Render Device (see below)
- The Instance creates a Swapchain (see below)
- The Render Device creates resources (images, buffers, etc)
- The Swapchain request the Render Device to create Image Views for available Images (usually 2 in order to work with double buffering)
- Dark magic goes here (not implemented yet)
Please keep in mind that this is still work in progress.
I’ve been talking about render devices but I didn’t say what they are yet. RenderDevice is a new class that handles both Vulkan’s physical and logical devices. I know that we may have more than one logical device per physical one, but I’m not seeing that as a requirement for the moment. If the time comes where I need to make that distinction, it won’t be hard to split the class in two.
The goals is for RenderDevice to replace the Renderer interface which has become too big over the years.
I don’t have much code for the RenderDevice class at the moment. Well, there’s a lot of code, but it’s mostly for initialization. I’m expecting this class to get bigger and bigger as the time passes.
The Swapchain is kind of a new concept that I borrowed directly from Vulkan. It’s main responsibility is to handle images that need to be presented to the screen/surface.
For such reason, there are only two main functions for a Swapchain object: 1) acquire a new image for us to render to and 2) present that image to the screen once is ready.
The Swapchain class is pretty much completed and I don’t think it might get much bigger than what it is today.
Thinking out loud: Headless Vulkan
This is something that I would like to try out in future iterations. Unlike OpenGL, we can use Vulkan without having to create a visible window. This could prove useful in several scenarios, like when doing complex compute operations, image generation using procedural algorithms, computer vision… even unit tests. I do like the idea of having automatic tests for everything rendering-related that actually mean something as I do for other systems in the engine.
Again, this is not a priority right now, but I’ll definitely give it a try in the future.
Now that we have a window, a render device and a swapchain, I believe the next logical step is to actually render something. Therefore, I’ll be focusing on pipelines and commands next.