DevLog 00 - Building The Base Renderer
- Bobbie Kindt

- Aug 28
- 2 min read
Updated: Sep 14
Introduction
When I started this project, my goal was to build a Vulkan-based renderer from scratch. To kick things off, I used the Vulkan Tutorial as a reference point. The tutorial proved to be a great starting place, but it keeps everything inside a single large VulkanProject class. Since I want my renderer to be modular and maintainable, I decided on to break things into smaller, dedicated pieces of code and refactor my code into clear classes after I completed the tutorial.
What I Did
After going through the tutorial, the first step was restructuring the tutorial code into a cleaner architecture. Instead of keeping everything in one massive file, I created separate classes to handle specific responsibilities. Here are a few examples:
CommandPool & CommandBuffer → Managing command recording and submission
Swapchain → Handling presentation and image buffers
GraphicsPipeline → Setting up shaders, pipeline state, and fixed-function stages
RenderPass & Framebuffers → Organizing how attachments are used in rendering
This approach makes the renderer easier to read, test, and expand later. Each class has a clear role, which fits with my goal of applying the Single-Responsibility Principle to the engine's design.
#pragma once
#define GLFW_INCLUDE_VULKAN
#include "GLFW/glfw3.h"
class VulkanDevice;
class VulkanCommandPool final
{
public:
VulkanCommandPool(VulkanDevice* pDevice);
~VulkanCommandPool();
void Create();
void Cleanup();
VkCommandPool GetCommandPool() const;
private:
VulkanDevice* m_pVulkanDevice;
VkCommandPool m_CommandPool;
};#pragma once
#include <vector>
#define GLFW_INCLUDE_VULKAN
#include "GLFW/glfw3.h"
#include "Window.h"
#include "VulkanDevice.h"
class VulkanSwapChain final
{
public:
VulkanSwapChain(Window* pWindow, VulkanDevice* pDevice);
~VulkanSwapChain();
void Create();
void Cleanup();
void CreateImageViews();
VkSwapchainKHR GetSwapChain() const;
std::vector<VkImage> GetSwapChainImages() const;
VkFormat GetSwapChainImageFormat() const;
VkExtent2D GetSwapChainExtent() const;
std::vector<VkImageView> GetSwapChainImageViews() const;
private:
Window* m_pWindow;
VulkanDevice* m_pVulkanDevice;
VkSwapchainKHR m_SwapChain;
std::vector<VkImage> m_SwapChainImages;
VkFormat m_SwapChainImageFormat;
VkExtent2D m_SwapChainExtent;
std::vector<VkImageView> m_SwapChainImageViews;
VkSurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
VkPresentModeKHR ChooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags);
};Challenges & Fixes
Beyond learing the Vulkan API itself, one of the hardest part was figuring out how to structure my project. Vulkan already comes with a lot of boilerplate, and splitting it into multiple classes made me think hard about dependencies and ownership. Some of the questions I struggeled with were:
Should the swapchain create its own image views, or should that be handled by a separate helper?
How should command buffers be tied to the pipeline and render pass without creating circular dependencies?
How do I keep things flexible for later features without overengineering?
I went through several iterations where classes felt either too tightly coupled or too fragmented. After some trial and error, I landed on a structure that felt balanced and modular, but not overly complicated.
What's Next
With the base renderer in place, the next step is implementing a basic camera. This is first on my list so I can actually move around and explore the scene, instead of staring at a static mesh. Next step is the integration of an ImGui window for debugging and development tools purposes. These features will give me the tools I need to experiment more freely and will lay the foundation for advanced rendering techniques later on.



Comments