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 a portion of the code:

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



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);
	VkDescriptorSetLayout CreateSamplerDescriptorSetLayout();

private:
	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);
};VkDescriptorSetLayout Renderer::CreateDescriptorSetLayout() {
	VkDescriptorSetLayoutBinding uboLayoutBinding{};
	uboLayoutBinding.binding = 0;
	uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	uboLayoutBinding.descriptorCount = 1;
	uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
	uboLayoutBinding.pImmutableSamplers = nullptr;

	// Add sampler layout binding
	VkDescriptorSetLayoutBinding samplerLayoutBinding{};
	samplerLayoutBinding.binding = 1;
	samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	samplerLayoutBinding.descriptorCount = 1;
	samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
	samplerLayoutBinding.pImmutableSamplers = nullptr;

	std::array<VkDescriptorSetLayoutBinding, 2> bindings = { uboLayoutBinding, samplerLayoutBinding };

	VkDescriptorSetLayoutCreateInfo layoutInfo{};
	layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
	layoutInfo.pBindings = bindings.data();

	VkDescriptorSetLayout descriptorSetLayout;
	if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
		throw std::runtime_error("Failed to create descriptor set layout!");
	}

	return descriptorSetLayout;
}

VkDescriptorSetLayout Renderer::CreateSamplerDescriptorSetLayout() {
	VkDescriptorSetLayoutBinding samplerLayoutBinding{};
	samplerLayoutBinding.binding = 0;
	samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	samplerLayoutBinding.descriptorCount = 1;
	samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
	samplerLayoutBinding.pImmutableSamplers = nullptr;

	VkDescriptorSetLayoutCreateInfo layoutInfo{};
	layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	layoutInfo.bindingCount = 1;
	layoutInfo.pBindings = &samplerLayoutBinding;

	VkDescriptorSetLayout descriptorSetLayout;
	if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
		throw std::runtime_error("Failed to create sampler descriptor set layout!");
	}

	return descriptorSetLayout;
}

VkDescriptorPool Renderer::CreateDescriptorPool(uint32_t maxSets) {
	VkDescriptorPoolSize poolSize{};
	poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
	poolSize.descriptorCount = maxSets;

	// Add an additional descriptor pool size for COMBINED_IMAGE_SAMPLER
	VkDescriptorPoolSize samplerPoolSize{};
	samplerPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
	samplerPoolSize.descriptorCount = maxSets;

	std::array<VkDescriptorPoolSize, 2> poolSizes = { poolSize, samplerPoolSize };

	VkDescriptorPoolCreateInfo poolInfo{};
	poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
	poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
	poolInfo.pPoolSizes = poolSizes.data();
	poolInfo.maxSets = maxSets;

	VkDescriptorPool descriptorPool;
	if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
		throw std::runtime_error("Failed to create descriptor pool!");
	}

	return descriptorPool;
}

How can I alter the code to fix this issue?