I’m building a video game engine using C++ as the coding language and Vulkan for graphics. I am trying to set up a generic renderer using Vulkan that is flexible and will render objects based on a vector that is supplied to it. The renderer will also handle the creation of the window using GLFW and use GLM for all relevant math calls. I am using the ASSIMP library to load 3d models and animations.

Can you modify the following header file to properly utilise Vulkan.hpp and RAII? We will then begin updating the class source file to conform to the new header and then expand to the broader codebase.

I am in the process of modifying the Renderer class to properly utilise Vulkan.hpp and RAII. Here are the old and new header files:

Old Renderer.h:

#pragma once

#include <vulkan/vulkan.h>
#include "Window.h"
#include <vector>
#include <stdexcept>
#include <set>
#include <optional>
#include <iostream>
#include "Pipeline.h"
#include "Material.h"
#include "Mesh.h"

struct QueueFamilyIndices
{
	std::optional<uint32_t> graphicsFamily;
	std::optional<uint32_t> presentFamily;

	bool IsComplete()
	{
		return graphicsFamily.has_value() && presentFamily.has_value();
	}
};

struct SwapChainSupportDetails {
	VkSurfaceCapabilitiesKHR capabilities;
	std::vector<VkSurfaceFormatKHR> formats;
	std::vector<VkPresentModeKHR> presentModes;
};

struct MVP {
	glm::mat4 model;
	glm::mat4 view;
	glm::mat4 projection;
};


class Renderer
{
public:
	Renderer();
	~Renderer();

	void Initialize(GLFWwindow* window);
	void Shutdown();

	void BeginFrame();
	void EndFrame();

	VkDescriptorSetLayout CreateDescriptorSetLayout();
	VkDescriptorPool CreateDescriptorPool(uint32_t maxSets);
	VkDevice* GetDevice();
	VkPhysicalDevice* GetPhysicalDevice();
	VkCommandPool* GetCommandPool();
	VkQueue* GetGraphicsQueue();
	VkCommandBuffer* GetCurrentCommandBuffer();
	std::shared_ptr<Pipeline> GetPipeline();
	void CreateGraphicsPipeline(Mesh* mesh, Material* material);
	std::pair<VkBuffer, VkDeviceMemory> RequestMvpBuffer();

private:

	bool isShutDown = false;

	static const uint32_t kMvpBufferCount = 3;
	std::vector<VkBuffer> mvpBuffers;
	std::vector<VkDeviceMemory> mvpBufferMemory;
	uint32_t currentMvpBufferIndex = 0;

	bool shutdownInProgress;
	uint32_t currentCmdBufferIndex = 0;
	std::vector<size_t> currentFramePerImage;

	std::vector<VkImage> swapChainImages;
	std::vector<VkImageView> swapChainImageViews;
	VkExtent2D swapChainExtent;
	VkRenderPass renderPass;
	uint32_t imageIndex;
	std::shared_ptr<Pipeline> pipeline;

	VkFormat swapChainImageFormat;

	std::vector<VkCommandBuffer> commandBuffers;

	void CreateImageViews();
	void CleanupImageViews();
	void CreateRenderPass();
	void CleanupRenderPass();

	void CreateSurface();
	void DestroySurface();

	void CreateInstance();
	void CleanupInstance();
	void ChoosePhysicalDevice();
	void CreateDevice();
	void CleanupDevice();
	void CreateSwapchain();
	void CleanupSwapchain();
	void CreateCommandPool();
	void CleanupCommandPool();
	void CreateFramebuffers();
	void CleanupFramebuffers();

	void CreateCommandBuffers();
	void CleanupCommandBuffers();

	void Present();

	

	

	GLFWwindow* window;
	VkInstance instance = VK_NULL_HANDLE;
	VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
	VkDevice device = VK_NULL_HANDLE;

	VkSurfaceKHR surface;
	VkSwapchainKHR swapchain;
	VkCommandPool commandPool;
	VkCommandBuffer currentCommandBuffer;

	std::vector<VkFramebuffer> framebuffers;

	// Additional Vulkan objects needed for rendering…

	const uint32_t kMaxFramesInFlight = 2;
	std::vector<VkSemaphore> imageAvailableSemaphores;
	std::vector<VkSemaphore> renderFinishedSemaphores;
	std::vector<VkFence> inFlightFences;
	size_t currentFrame;

	VkQueue graphicsQueue;
	VkQueue presentQueue;

	void CreateSyncObjects();
	void CleanupSyncObjects();

	SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface);
	VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
	VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
	VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window);

	std::vector<const char*> deviceExtensions = {
		VK_KHR_SWAPCHAIN_EXTENSION_NAME
	};

	std::vector<const char*> CheckPhysicalDeviceExtensionSupport(VkPhysicalDevice physicalDevice);
	QueueFamilyIndices GetQueueFamilyIndices(VkPhysicalDevice physicalDevice);
};

New Renderer.h:

#pragma once

#include <vulkan/vulkan.hpp>
#include “Window.h”
#include <vector>
#include <stdexcept>
#include <set>
#include <optional>
#include <iostream>
#include “Pipeline.h”
#include “Material.h”
#include “Mesh.h”

struct QueueFamilyIndices
{
    std::optional<uint32_t> graphicsFamily;
    std::optional<uint32_t> presentFamily;

    bool IsComplete()
    {
        return graphicsFamily.has_value() && presentFamily.has_value();
    }
};

struct SwapChainSupportDetails {
    vk::SurfaceCapabilitiesKHR capabilities;
    std::vector<vk::SurfaceFormatKHR> formats;
    std::vector<vk::PresentModeKHR> presentModes;
};

struct MVP {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 projection;
};

class Renderer
{
public:
    Renderer();
    ~Renderer();

    void Initialize(GLFWwindow* window);
    void Shutdown();

    void BeginFrame();
    void EndFrame();

    vk::DescriptorSetLayout CreateDescriptorSetLayout();
    vk::DescriptorPool CreateDescriptorPool(uint32_t maxSets);
    vk::Device* GetDevice();
    vk::PhysicalDevice* GetPhysicalDevice();
    vk::CommandPool* GetCommandPool();
    vk::Queue* GetGraphicsQueue();
    vk::CommandBuffer* GetCurrentCommandBuffer();
    std::shared_ptr<Pipeline> GetPipeline();
    void CreateGraphicsPipeline(Mesh* mesh, Material* material);
    std::pair<vk::Buffer, vk::DeviceMemory> RequestMvpBuffer();

private:

    bool isShutDown = false;

    static const uint32_t kMvpBufferCount = 3;
    std::vector<vk::Buffer> mvpBuffers;
    std::vector<vk::DeviceMemory> mvpBufferMemory;
    uint32_t currentMvpBufferIndex = 0;

    bool shutdownInProgress;
    uint32_t currentCmdBufferIndex = 0;
    std::vector<size_t> currentFramePerImage;

    std::vector<vk::Image> swapChainImages;
    std::vector<vk::ImageView> swapChainImageViews;
    vk::Extent2D swapChainExtent;
    vk::RenderPass renderPass;
    uint32_t imageIndex;
    std::shared_ptr<Pipeline> pipeline;

    vk::Format swapChainImageFormat;

    std::vector<vk::CommandBuffer> commandBuffers;

    void CreateImageViews();
    void CleanupImageViews();
    void CreateRenderPass();
    void CleanupRenderPass();

    void CreateSurface();
    void DestroySurface();

    void CreateInstance();
    void CleanupInstance();
    void ChoosePhysicalDevice();
    void CreateDevice();
    void CleanupDevice();
    void CreateSwapchain();
    void CleanupSwapchain();
    void CreateCommandPool();
    void CleanupCommandPool();
    void CreateFramebuffers();
    void CleanupFramebuffers();

    void CreateCommandBuffers();
    void CleanupCommandBuffers();

    void Present();

    GLFWwindow* window;
    vk::Instance instance;
    vk::PhysicalDevice physicalDevice;
    vk::Device device;

    vk::SurfaceKHR surface;
    vk::SwapchainKHR swapchain;
    vk::CommandPool commandPool;
    vk::CommandBuffer currentCommandBuffer;

    std::vector<vk::Framebuffer> framebuffers;

    // Additional Vulkan objects needed for rendering…

    const uint32_t kMaxFramesInFlight = 2;
    std::vector<vk::Semaphore> imageAvailableSemaphores;
    std::vector<vk::Semaphore> renderFinishedSemaphores;
    std::vector<vk::Fence> inFlightFences;
    size_t currentFrame;

    vk::Queue graphicsQueue;
    vk::Queue presentQueue;

    void CreateSyncObjects();
    void CleanupSyncObjects();

    SwapChainSupportDetails querySwapChainSupport(vk::PhysicalDevice device, vk::SurfaceKHR surface);
    vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& availableFormats);
    vk::PresentModeKHR chooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& availablePresentModes);
    vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, GLFWwindow* window);

    std::vector<const char*> deviceExtensions = {
        VK_KHR_SWAPCHAIN_EXTENSION_NAME
    };

    std::vector<const char*> CheckPhysicalDeviceExtensionSupport(vk::PhysicalDevice physicalDevice);
    QueueFamilyIndices GetQueueFamilyIndices(vk::PhysicalDevice physicalDevice);
};

Please update the following portion of the Renderer.cpp to conform to the new header and to properly utilise both Vulkan.hpp and RAII. Notify me if there are no changes required.

Renderer::Renderer() : currentFrame(0), shutdownInProgress(false)
{
}

Renderer::~Renderer()
{
	Shutdown();
}

void Renderer::Initialize(GLFWwindow* window)
{
	this->window = window;
	CreateInstance();
	CreateSurface();
	ChoosePhysicalDevice();
	CreateDevice();
	// Create the MVP buffers
mvpBuffers.resize(kMvpBufferCount);
mvpBufferMemory.resize(kMvpBufferCount);

for (uint32_t i = 0; i < kMvpBufferCount; ++i)
{
    BufferUtils::CreateBuffer(device, *GetPhysicalDevice(),
        sizeof(MVP), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        mvpBuffers[i], mvpBufferMemory[i]);
}
	CreateSwapchain();
	CreateRenderPass();
	CreateCommandPool();
	CreateFramebuffers();
	CreateSyncObjects();
}

void Renderer::Shutdown()
{
	if (isShutDown) {
		return;
	}

	if (shutdownInProgress) {
		return;
	}

	shutdownInProgress = true;
	if (device != VK_NULL_HANDLE) {
		vkDeviceWaitIdle(device);
	}

	CleanupFramebuffers();
	CleanupRenderPass();
	CleanupSyncObjects();
	CleanupCommandPool();
	CleanupImageViews();
	CleanupSwapchain();
	// Destroy the MVP buffers
	for (uint32_t i = 0; i < kMvpBufferCount; ++i)
	{
		vkDestroyBuffer(device, mvpBuffers[i], nullptr);
		vkFreeMemory(device, mvpBufferMemory[i], nullptr);
	}
	if (device != VK_NULL_HANDLE) {
		CleanupDevice();
	}
	DestroySurface();
	CleanupInstance();

	shutdownInProgress = false;
	isShutDown = true;
}

void Renderer::BeginFrame()
{
	


	// Wait for any previous work on this swapchain image to complete
	vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
	vkResetFences(device, 1, &inFlightFences[currentFrame]);

	// Acquire an image from the swapchain, then begin recording commands for the current frame.
	VkResult acquireResult = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);

	if (acquireResult != VK_SUCCESS && acquireResult != VK_SUBOPTIMAL_KHR) {
		throw std::runtime_error("Failed to acquire next swapchain image.");
	}

	VkCommandBufferBeginInfo beginInfo{};
	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	//currentCommandBuffer = commandBuffers[currentFrame];
	currentCmdBufferIndex = (currentCmdBufferIndex + 1) % 2;
	currentCommandBuffer = commandBuffers[currentFrame * 2 + currentCmdBufferIndex];

	// Add debug message before vkBeginCommandBuffer
	//std::cout << "Current Frame: " << currentFrame << " | Cmd Buffer Index: " << currentCmdBufferIndex << " | Image Index: " << imageIndex << "\n";
	//std::cout << "Calling vkBeginCommandBuffer…\n";
	vkBeginCommandBuffer(currentCommandBuffer, &beginInfo);
	//std::cout << "vkBeginCommandBuffer called…\n";

	VkRenderPassBeginInfo renderPassInfo{};
	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	renderPassInfo.renderPass = renderPass;
	renderPassInfo.framebuffer = framebuffers[imageIndex];
	renderPassInfo.renderArea.offset = { 0, 0 };
	renderPassInfo.renderArea.extent = swapChainExtent;

	// Set the clear color to black
	VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f };
	renderPassInfo.clearValueCount = 1;
	renderPassInfo.pClearValues = &clearColor;

	vkCmdBeginRenderPass(currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
}

void Renderer::EndFrame()
{
	vkCmdEndRenderPass(currentCommandBuffer);

	VkSubmitInfo submitInfo{};
	submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

	VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
	submitInfo.waitSemaphoreCount = 1;
	submitInfo.pWaitSemaphores = &imageAvailableSemaphores[currentFrame];
	submitInfo.pWaitDstStageMask = waitStages;

	submitInfo.commandBufferCount = 1;
	submitInfo.pCommandBuffers = &currentCommandBuffer;

	submitInfo.signalSemaphoreCount = 1;
	submitInfo.pSignalSemaphores = &renderFinishedSemaphores[currentFrame];

	vkEndCommandBuffer(currentCommandBuffer);
	vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]);

	VkPresentInfoKHR presentInfo{};
	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
	presentInfo.waitSemaphoreCount = 1;
	presentInfo.pWaitSemaphores = &renderFinishedSemaphores[currentFrame];

	VkSwapchainKHR swapChains[] = { swapchain };
	presentInfo.swapchainCount = 1;
	presentInfo.pSwapchains = swapChains;
	presentInfo.pImageIndices = &imageIndex;

	VkResult queuePresentResult = vkQueuePresentKHR(presentQueue, &presentInfo);

	//std::cout << "Frame rendered: " << currentFrame << "\n";

	if (queuePresentResult == VK_ERROR_OUT_OF_DATE_KHR || queuePresentResult == VK_SUBOPTIMAL_KHR) {
		// Handle swapchain recreation if needed, e.g., due to resizing the window or other swapchain properties changes
	}
	else if (queuePresentResult != VK_SUCCESS) {
		throw std::runtime_error("Failed to present the swapchain image.");
	}

	currentFrame = (currentFrame + 1) % kMaxFramesInFlight;
}