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.

Here is some the code:

Engine.h:

#pragma once

#include "Window.h"
#include "Renderer.h"
#include "Scene.h"
#include <chrono>
#include <thread>

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

	void Run();
	void Shutdown();

	int MaxFPS = 60;

private:

	void Initialize();
	void MainLoop();
	void Update(float deltaTime);
	void Render();

	Window window;
	Renderer renderer;
	Scene scene;

	vk::UniqueDescriptorPool descriptorPool;
	bool pauseRender;
};

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::UniqueDescriptorPool 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();

    void RecreateSwapchain(int width, int height);

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

    vk::Image depthImage;
    vk::DeviceMemory depthImageMemory;
    vk::ImageView depthImageView;

    void CreateDepthResources();
    void CleanupDepthResources();

    vk::Format FindDepthFormat(vk::PhysicalDevice physicalDevice);
    vk::Format FindSupportedFormat(vk::PhysicalDevice physicalDevice, const std::vector<vk::Format>& candidates, vk::ImageTiling tiling, vk::FormatFeatureFlags features);
    uint32_t FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties);
    vk::ImageView CreateImageView(vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags);
};

Texture.h:

#pragma once

#include <vulkan/vulkan.hpp>
#include "stb_image.h" // Include the stb_image header
#include "BufferUtils.h"
#include <string>

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

    void LoadFromFile(const std::string& filename, vk::Device device, vk::PhysicalDevice physicalDevice, vk::CommandPool commandPool, vk::Queue graphicsQueue);
    vk::ImageView GetImageView() const;
    vk::Sampler GetSampler() const;
    void Cleanup();

private:
    vk::Device device;
    vk::Image image;
    vk::DeviceMemory imageMemory;
    vk::ImageView imageView;
    vk::Sampler sampler;
    vk::PhysicalDevice physicalDevice;
    vk::CommandPool commandPool;
    vk::Queue graphicsQueue;
    bool initialized = false;

    void CreateImage(uint32_t width, uint32_t height, uint32_t mipLevels, vk::SampleCountFlagBits numSamples,
        vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties);
    void TransitionImageLayout(vk::ImageLayout oldLayout, vk::ImageLayout newLayout,
        uint32_t mipLevels, vk::SampleCountFlagBits numSamples);
    void CopyBufferToImage(vk::Buffer buffer, uint32_t width, uint32_t height);
    void CreateImageView(vk::Format format, vk::ImageAspectFlags aspectFlags, uint32_t mipLevels);
    void CreateSampler(uint32_t mipLevels);
    void GenerateMipmaps(uint32_t width, uint32_t height, uint32_t mipLevels);
    vk::CommandBuffer BeginSingleTimeCommands(vk::CommandPool commandPool, vk::Device device);
    void EndSingleTimeCommands(vk::CommandPool commandPool, vk::Queue graphicsQueue, vk::Device device, vk::CommandBuffer commandBuffer);
};


Pipeline.h:

#pragma once

#include <vulkan/vulkan.hpp>
#include <vector>
#include <array>
#include <stdexcept>
#include "Shader.h"

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

	void CreateGraphicsPipeline(const std::vector<vk::VertexInputBindingDescription>& vertexBindingDescriptions,
		const std::vector<vk::VertexInputAttributeDescription>& vertexAttributeDescriptions,
		vk::Extent2D swapchainExtent,
		const std::vector<Shader*>& shaders,
		vk::RenderPass renderPass,
		vk::PipelineLayout pipelineLayout,
		vk::Device device);

	void RecreateGraphicsPipeline(vk::Extent2D swapchainExtent, vk::RenderPass renderPass);

	void Cleanup();

	vk::Pipeline GetPipeline() const;

	bool IsInitialized() const;

private:
	vk::Device device;
	vk::Pipeline pipeline;
	bool initialized;

	vk::GraphicsPipelineCreateInfo pipelineCreateInfo;
	std::vector<vk::PipelineShaderStageCreateInfo> shaderStages; // Added member variable
	vk::PipelineVertexInputStateCreateInfo vertexInputInfo{};
	vk::PipelineInputAssemblyStateCreateInfo inputAssembly{};
	vk::Viewport viewport{};
	vk::Rect2D scissor{};
	vk::PipelineViewportStateCreateInfo viewportState{};
	vk::PipelineRasterizationStateCreateInfo rasterizer{};
	vk::PipelineMultisampleStateCreateInfo multisampling{};
	vk::PipelineColorBlendAttachmentState colorBlendAttachment{};
	vk::PipelineColorBlendStateCreateInfo colorBlending{};
	vk::PipelineDepthStencilStateCreateInfo depthStencil{};

	vk::PipelineLayout pipelineLayout;

	std::vector<vk::VertexInputBindingDescription> defaultVertexBindingDescriptions;
	std::vector<vk::VertexInputAttributeDescription> defaultVertexAttributeDescriptions;
	std::vector<vk::VertexInputBindingDescription>& vertexBindingDescriptions;
	std::vector<vk::VertexInputAttributeDescription>& vertexAttributeDescriptions;

	void CreateShaderStages(const std::vector<Shader*>& shaders);
};

Shader.h:

#pragma once

#include <vulkan/vulkan.hpp>
#include <string>

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

	void loadFromFile(const std::string& filename, vk::Device device, vk::ShaderStageFlagBits stage);
	vk::PipelineShaderStageCreateInfo getPipelineShaderStageCreateInfo() const;
	static void cleanup(Shader* shader);

private:
	vk::Device m_device;
	vk::UniqueShaderModule m_shaderModule;
	vk::ShaderStageFlagBits m_stage;
};

What parts of the above code would need to be involved to implement multisampling?