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.

I am in the process of converting the codebase from using vulkan.h to vulkan.hpp, as well as utilising RAII. Here is the new header for the Renderer class:

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);
};

Can you update the following code of the BufferUtils class to utilise vulkan.hpp, RAII and align with the Renderer class?

BufferUtils.h:

#pragma once

#include <vulkan/vulkan.h>
#include <stdint.h>

namespace BufferUtils
{
    void CreateBuffer(
        VkDevice device, VkPhysicalDevice physicalDevice,
        VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
        VkBuffer& buffer, VkDeviceMemory& bufferMemory);

    uint32_t FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties);

    void CopyBuffer(
        VkDevice device, VkCommandPool commandPool, VkQueue graphicsQueue,
        VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);
}


BufferUtils.cpp:

#include "BufferUtils.h"

#include <stdexcept>

namespace BufferUtils
{
    void CreateBuffer(
        VkDevice device, VkPhysicalDevice physicalDevice,
        VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
        VkBuffer& buffer, VkDeviceMemory& bufferMemory)
    {
        VkBufferCreateInfo bufferInfo{};
        bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
        bufferInfo.size = size;
        bufferInfo.usage = usage;
        bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

        if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
        {
            throw std::runtime_error("Failed to create buffer!");
        }

        VkMemoryRequirements memRequirements;
        vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

        VkMemoryAllocateInfo allocInfo{};
        allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        allocInfo.allocationSize = memRequirements.size;
        allocInfo.memoryTypeIndex = FindMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties);

        if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS)
        {
            throw std::runtime_error("Failed to allocate buffer memory!");
        }

        vkBindBufferMemory(device, buffer, bufferMemory, 0);
    }

    uint32_t FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties)
    {
        VkPhysicalDeviceMemoryProperties memProperties;
        vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);

        for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
        {
            if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
            {
                return i;
            }
        }

        throw std::runtime_error("Failed to find suitable memory type!");
    }

    void CopyBuffer(
        VkDevice device, VkCommandPool commandPool, VkQueue graphicsQueue,
        VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
    {
        VkCommandBufferAllocateInfo allocInfo{};
        allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        allocInfo.commandPool = commandPool;
        allocInfo.commandBufferCount = 1;

        VkCommandBuffer commandBuffer;
        vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);

        VkCommandBufferBeginInfo beginInfo{};
        beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

        vkBeginCommandBuffer(commandBuffer, &beginInfo);

        VkBufferCopy copyRegion{};
        copyRegion.srcOffset = 0; // Optional
        copyRegion.dstOffset = 0; // Optional
        copyRegion.size = size;
        vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

        vkEndCommandBuffer(commandBuffer);

        VkSubmitInfo submitInfo{};
        submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submitInfo.commandBufferCount = 1;
        submitInfo.pCommandBuffers = &commandBuffer;

        vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
        vkQueueWaitIdle(graphicsQueue);

        vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
    }
}