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.


void Renderer::CreateSurface()
{
	if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS)
	{
		throw std::runtime_error("Failed to create a window surface.");
	}
}

void Renderer::DestroySurface()
{
	vkDestroySurfaceKHR(instance, surface, nullptr);
}

void Renderer::CreateInstance()
{
	// Set up the application info
	VkApplicationInfo appInfo{};
	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
	appInfo.pApplicationName = "Game Engine";
	appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
	appInfo.pEngineName = "Game Engine";
	appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
	appInfo.apiVersion = VK_API_VERSION_1_2;

	// Set up the instance create info
	VkInstanceCreateInfo createInfo{};
	createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
	createInfo.pApplicationInfo = &appInfo;

	// Set up the required extensions
	uint32_t glfwExtensionCount = 0;
	const char** glfwExtensions;
	glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

	createInfo.enabledExtensionCount = glfwExtensionCount;
	createInfo.ppEnabledExtensionNames = glfwExtensions;
	createInfo.enabledLayerCount = 0;

	std::vector<const char*> validationLayers;

#ifdef NDEBUG
	const bool enableValidationLayers = false;
#else
	const bool enableValidationLayers = true;
	validationLayers.push_back("VK_LAYER_KHRONOS_validation");
#endif

	if (enableValidationLayers) {
		// Check if validation layers are supported
		uint32_t layerCount;
		vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
		std::vector<VkLayerProperties> availableLayers(layerCount);
		vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

		for (const char* layerName : validationLayers) {
			bool layerFound = false;

			for (const auto& layerProperties : availableLayers) {
				if (strcmp(layerName, layerProperties.layerName) == 0) {
					layerFound = true;
					break;
				}
			}

			if (!layerFound) {
				throw std::runtime_error("Validation layer requested, but it’s not available.");
			}
		}

		// Enable the validation layers
		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
		createInfo.ppEnabledLayerNames = validationLayers.data();
	}
	else {
		createInfo.enabledLayerCount = 0;
	}

	// Create the Vulkan instance
	if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS)
	{
		throw std::runtime_error("Failed to create the Vulkan instance.");
	}
}

void Renderer::CleanupInstance()
{
	// Destroy the Vulkan instance
	vkDestroyInstance(instance, nullptr);
}

void Renderer::ChoosePhysicalDevice()
{
	// Enumerate the available physical devices and choose one that supports required features
	uint32_t deviceCount = 0;
	vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

	if (deviceCount == 0)
	{
		throw std::runtime_error("Failed to find a GPU with Vulkan support.");
	}

	std::vector<VkPhysicalDevice> allDevices(deviceCount);
	vkEnumeratePhysicalDevices(instance, &deviceCount, allDevices.data());

	for (const auto& testDevice : allDevices)
	{
		if (glfwGetPhysicalDevicePresentationSupport(instance, testDevice, 0) &&
			CheckPhysicalDeviceExtensionSupport(testDevice).empty() &&
			GetQueueFamilyIndices(testDevice).IsComplete())
		{
			VkPhysicalDeviceFeatures supportedFeatures;
			vkGetPhysicalDeviceFeatures(testDevice, &supportedFeatures);

			// Check for anisotropic filtering support
			if (supportedFeatures.samplerAnisotropy) {
				physicalDevice = testDevice;
				break;
			}
		}
	}

	if (physicalDevice == VK_NULL_HANDLE)
	{
		throw std::runtime_error("Failed to find a suitable GPU.");
	}
}

void Renderer::CreateDevice()
{
	// Get the GPU’s queue family indices
	const QueueFamilyIndices indices = GetQueueFamilyIndices(physicalDevice);

	// Set up the device queue create info
	std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
	std::set<uint32_t> uniqueQueueFamilyIndices = { indices.graphicsFamily.value(),indices.presentFamily.value() };

	float queuePriority = 1.0f;
	for (uint32_t queueFamilyIndex : uniqueQueueFamilyIndices)
	{
		VkDeviceQueueCreateInfo queueCreateInfo{};
		queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
		queueCreateInfo.queueFamilyIndex = queueFamilyIndex;
		queueCreateInfo.queueCount = 1;
		queueCreateInfo.pQueuePriorities = &queuePriority;

		queueCreateInfos.push_back(queueCreateInfo);
	}

	// Set up the physical device features
	VkPhysicalDeviceFeatures deviceFeatures{};
	deviceFeatures.samplerAnisotropy = VK_TRUE; // Enable anisotropic filtering

	// Set up the device create info
	VkDeviceCreateInfo createInfo{};
	createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
	createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
	createInfo.pQueueCreateInfos = queueCreateInfos.data();
	createInfo.pEnabledFeatures = &deviceFeatures;
	createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
	createInfo.ppEnabledExtensionNames = deviceExtensions.data();

	// Create the logical device
	if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS)
	{
		throw std::runtime_error("Failed to create a logical device.");
	}

	// Retrieve the graphics queue and the present queue
	vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
	vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
}

void Renderer::CleanupDevice()
{
	// Reset the pipeline first to ensure it’s destroyed before the device
	pipeline.reset();

	// Destroy the logical device
	vkDestroyDevice(device, nullptr);
}