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 code to make use of vulkan.hpp and RAII. I've been making some chagnes to the Material class. Here is the current code:

Material.h:

#pragma once

#include <vulkan/vulkan.hpp>
#include "Texture.h"
#include "Shader.h"
#include <stdexcept>
#include <memory> // Don’t forget to include <memory>
#include <array>

// Add this struct outside the Material class, possibly at the top of Material.cpp
struct ShaderDeleter {
	void operator()(Shader* shaderPtr) {
		if (shaderPtr != nullptr) {
			Shader::cleanup(shaderPtr);
		}
	}
};

class Material
{
public:
	Material() = default;
	~Material();

	void Initialize(const std::string& vertShaderPath, const std::string& fragShaderPath, const std::string& texturePath, vk::Device device, vk::DescriptorSetLayout descriptorSetLayout, vk::DescriptorPool descriptorPool, vk::PhysicalDevice physicalDevice, vk::CommandPool commandPool, vk::Queue graphicsQueue);
	void Cleanup();

	void LoadTexture(const std::string& filename, vk::Device device, vk::PhysicalDevice physicalDevice, vk::CommandPool commandPool, vk::Queue graphicsQueue);
	void LoadShaders(const std::string& vertFilename, const std::string& fragFilename, vk::Device device);

	void UpdateBufferBinding(VkBuffer newBuffer, vk::Device device, vk::DeviceSize devicesize);
	vk::DescriptorSet GetDescriptorSet() const;
	vk::PipelineLayout GetPipelineLayout() const;

	std::shared_ptr <Shader> GetvertexShader();
	std::shared_ptr <Shader> GetfragmentShader();
	void CreateDescriptorSet(VkBuffer uniformBuffer, vk::DeviceSize bufferSize);

private:
	vk::Device device;
	std::shared_ptr <Shader> vertexShader;
	std::shared_ptr <Shader> fragmentShader;
	std::shared_ptr<Texture> texture;


	void CreatePipelineLayout(vk::DescriptorSetLayout descriptorSetLayout);


	vk::DescriptorSet descriptorSet;
	vk::PipelineLayout pipelineLayout;

	vk::DescriptorSetLayout descriptorSetLayout;// = VK_NULL_HANDLE;

	vk::UniqueDescriptorPool descriptorPool;

	void CleanupDescriptorSetLayout();
};


Material.cpp:

#include "Material.h"

Material::~Material()
{
	Cleanup();
}

void Material::Initialize(const std::string& vertShaderPath, const std::string& fragShaderPath, const std::string& texturePath, vk::Device device, vk::DescriptorSetLayout descriptorSetLayout, vk::DescriptorPool descriptorPool, vk::PhysicalDevice physicalDevice, vk::CommandPool commandPool, vk::Queue graphicsQueue)
{
	this->device = device;

	this->descriptorSetLayout = descriptorSetLayout;

	// Load shaders and texture
	LoadTexture(texturePath, device, physicalDevice, commandPool, graphicsQueue);
	LoadShaders(vertShaderPath, fragShaderPath, device);

	// Create descriptor set and pipeline layout
	CreatePipelineLayout(descriptorSetLayout);
}

void Material::CreateDescriptorSet(VkBuffer uniformBuffer, vk::DeviceSize bufferSize)
{
	vk::DescriptorSetAllocateInfo allocInfo{};
	allocInfo.setDescriptorPool(*descriptorPool);
	allocInfo.setDescriptorSetCount(1);
	allocInfo.setPSetLayouts(&descriptorSetLayout);

	descriptorSet = device.allocateDescriptorSetsUnique(allocInfo)[0].get();

	vk::DescriptorBufferInfo bufferInfo{};
	bufferInfo.buffer = uniformBuffer;
	bufferInfo.offset = 0;
	bufferInfo.range = bufferSize;

	vk::DescriptorImageInfo imageInfo{};
	imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
	imageInfo.imageView = texture->GetImageView();
	imageInfo.sampler = texture->GetSampler();

	std::array<vk::WriteDescriptorSet, 2> descriptorWrites{};

	descriptorWrites[0].setDstSet(descriptorSet);
	descriptorWrites[0].setDstBinding(0);
	descriptorWrites[0].setDstArrayElement(0);
	descriptorWrites[0].setDescriptorType(vk::DescriptorType::eUniformBuffer);
	descriptorWrites[0].setDescriptorCount(1);
	descriptorWrites[0].setPBufferInfo(&bufferInfo);

	descriptorWrites[1].setDstSet(descriptorSet);
	descriptorWrites[1].setDstBinding(1);
	descriptorWrites[1].setDstArrayElement(0);
	descriptorWrites[1].setDescriptorType(vk::DescriptorType::eCombinedImageSampler);
	descriptorWrites[1].setDescriptorCount(1);
	descriptorWrites[1].setPImageInfo(&imageInfo);

	device.updateDescriptorSets(static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}

void Material::CreatePipelineLayout(vk::DescriptorSetLayout descriptorSetLayout)
{
	vk::PipelineLayoutCreateInfo pipelineLayoutInfo{};
	pipelineLayoutInfo.setSetLayouts(descriptorSetLayout);

	pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo);
}

void Material::Cleanup()
{
	if (vertexShader) {
		Shader::cleanup(vertexShader.get());
	}

	if (fragmentShader) {
		Shader::cleanup(fragmentShader.get());
	}

	if (texture) {
		texture->Cleanup();
		texture.reset();
	}

	CleanupDescriptorSetLayout();
	// Be sure to destroy the descriptor set if necessary
	// Note: If the descriptor pool is being destroyed, you don’t need to free individual descriptor sets
}

vk::DescriptorSet Material::GetDescriptorSet() const
{
	return descriptorSet;
}

vk::PipelineLayout Material::GetPipelineLayout() const
{
	return pipelineLayout;
}

std::shared_ptr <Shader> Material::GetvertexShader()
{
	return vertexShader;
}

std::shared_ptr <Shader> Material::GetfragmentShader()
{
	return fragmentShader;
}

void Material::LoadTexture(const std::string& filename, vk::Device device, vk::PhysicalDevice physicalDevice, vk::CommandPool commandPool, vk::Queue graphicsQueue)
{
	texture = std::make_shared<Texture>();
	texture->LoadFromFile(filename, device, physicalDevice, commandPool, graphicsQueue);
}

void Material::LoadShaders(const std::string& vertFilename, const std::string& fragFilename, vk::Device device)
{
	vertexShader = std::make_shared<Shader>();
	fragmentShader = std::make_shared<Shader>();
	vertexShader->loadFromFile(vertFilename, device, vk::ShaderStageFlagBits::eVertex);
	fragmentShader->loadFromFile(fragFilename, device, vk::ShaderStageFlagBits::eFragment);
}

void Material::UpdateBufferBinding(VkBuffer newBuffer, vk::Device device, vk::DeviceSize devicesize)
{
	vk::DescriptorBufferInfo bufferInfo{};
	bufferInfo.buffer = newBuffer;
	bufferInfo.offset = 0;
	bufferInfo.range = devicesize;

	vk::DescriptorImageInfo imageInfo{};
	imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
	imageInfo.imageView = texture->GetImageView();
	imageInfo.sampler = texture->GetSampler();

	std::array<vk::WriteDescriptorSet, 2> descriptorWrites{};
	descriptorWrites[0].setDstSet(descriptorSet);
	descriptorWrites[0].setDstBinding(0);
	descriptorWrites[0].setDstArrayElement(0);
	descriptorWrites[0].setDescriptorType(vk::DescriptorType::eUniformBuffer);
	descriptorWrites[0].setDescriptorCount(1);
	descriptorWrites[0].setPBufferInfo(&bufferInfo);

	descriptorWrites[1].setDstSet(descriptorSet);
	descriptorWrites[1].setDstBinding(1);
	descriptorWrites[1].setDstArrayElement(0);
	descriptorWrites[1].setDescriptorType(vk::DescriptorType::eCombinedImageSampler);
	descriptorWrites[1].setDescriptorCount(1);
	descriptorWrites[1].setPImageInfo(&imageInfo);

	device.updateDescriptorSets(static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
}

void Material::CleanupDescriptorSetLayout()
{
	if (descriptorSetLayout != vk::DescriptorSetLayout{}) {
		device.destroyDescriptorSetLayout(descriptorSetLayout, nullptr);
		descriptorSetLayout = vk::DescriptorSetLayout{};
	}
}

I am now getting the following errors in the log:

UNASSIGNED-GeneralParameterError-RequiredParameter(ERROR / SPEC): msgNum: -1711571459 - Validation Error: [ UNASSIGNED-GeneralParameterError-RequiredParameter ] Object 0: handle = 0x17ec117d1f0, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x99fb7dfd | vkAllocateDescriptorSets: required parameter pAllocateInfo->descriptorPool specified as VK_NULL_HANDLE
    Objects: 1
        [0] 0x17ec117d1f0, type: 3, name: NULL
VUID-VkDescriptorSetAllocateInfo-descriptorPool-parameter(ERROR / SPEC): msgNum: -2127463821 - Validation Error: [ VUID-VkDescriptorSetAllocateInfo-descriptorPool-parameter ] Object 0: handle = 0x17ebdb60a90, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0x81317a73 | Invalid VkDescriptorPool Object 0x0. The Vulkan spec states: descriptorPool must be a valid VkDescriptorPool handle (https://vulkan.lunarg.com/doc/view/1.3.239.0/windows/1.3-extensions/vkspec.html#VUID-VkDescriptorSetAllocateInfo-descriptorPool-parameter)
    Objects: 1
        [0] 0x17ebdb60a90, type: 1, name: NULL

Do you know what the problem is and how to modify the code to fix it?