i was working on adding 1 or more per object texutes in my Vulkan Renderer. I followed a guide on bindless resources which worked well for others and tried to upload the Textures with the help of my LoadImage class,I upload an Image and a Normalmap:
class LoadImage
{
private:
int m_width;
int m_height;
int m_channel;
stbi_uc* m_ppixels;
bool m_loaded = false;
bool m_uploaded = false;
VkImage m_image;
VkDeviceMemory m_imageMemory;
VkImageView m_imageView;
VkImageLayout m_imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkDevice m_device;
VkSampler m_sampler;
uint32_t mipLevels;
public:
LoadImage() {
m_loaded = false;
}
LoadImage(const char* path, VkPhysicalDevice physicalDevice) {
load(path, physicalDevice);
}
~LoadImage() {
//destroy();
}
void load(const char* path, VkPhysicalDevice physicalDevice) {
if (m_loaded) {
throw std::logic_error("Image was already loaded!");
}
m_ppixels = stbi_load(path, &m_width, &m_height, &m_channel, STBI_rgb_alpha);
m_loaded = true;
mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(m_width, m_height)))) + 1;
if (m_ppixels == nullptr) {
throw std::invalid_argument("Failed to load Image!");
}
}
void createColorResources(VkDevice device, VkPhysicalDevice physicalDevice, VkFormat colorFormat, VkSampleCountFlagBits msaaSamples, uint32_t width, uint32_t height) {
createImage(device, physicalDevice, width, height, 1, msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_image, m_imageMemory);
m_imageView = createImageView(device, m_image, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
m_loaded = true;
}
void upload(const VkDevice& device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue queue) {
if (m_uploaded) {
throw std::logic_error("Image was already uploaded!");
}
this->m_device = device;
VkDeviceSize imageSize = getSizeInBytes();
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
createBuffer(device, physicalDevice, imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, stagingBuffer, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBufferMemory);
void* data;
vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
memcpy(data, getRaw(), imageSize);
vkUnmapMemory(device, stagingBufferMemory);
createImage(device, physicalDevice, getWidth(), getHeight(), mipLevels, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_image, m_imageMemory);
changeLayout(device, commandPool, queue, m_image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
writeBufferToImage(device, commandPool, queue, stagingBuffer);
generateMipmaps(device, physicalDevice, commandPool, queue, m_image, VK_FORMAT_R8G8B8A8_SRGB, m_width, m_height, mipLevels);
vkDestroyBuffer(device, stagingBuffer, nullptr);
vkFreeMemory(device, stagingBufferMemory, nullptr);
m_imageView = createImageView(m_device, m_image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels);
VkSamplerCreateInfo samplerCreateInfo;
samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerCreateInfo.pNext = nullptr;
samplerCreateInfo.flags = 0;
samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerCreateInfo.mipLodBias = 0.0f;
samplerCreateInfo.anisotropyEnable = VK_TRUE; //THIS RIGHT HERE
samplerCreateInfo.maxAnisotropy = 16; //MAX 16
samplerCreateInfo.compareEnable = VK_FALSE;
samplerCreateInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerCreateInfo.minLod = 0.0f;
samplerCreateInfo.maxLod = static_cast<float>(mipLevels);
samplerCreateInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerCreateInfo.unnormalizedCoordinates = VK_FALSE;
VkResult result = vkCreateSampler(device, &samplerCreateInfo, nullptr, &m_sampler);
ASSERT_VULKAN(result);
stbi_image_free(m_ppixels);
m_uploaded = true;
}
void changeLayout(VkDevice device, VkCommandPool commandPool, VkQueue queue, VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) {
changeImageLayout(device, commandPool, queue, m_image, VK_FORMAT_R8G8B8A8_UNORM, this->m_imageLayout, newLayout, mipLevels);
this->m_imageLayout = newLayout;
}
void writeBufferToImage(VkDevice device, VkCommandPool commandPool, VkQueue queue, VkBuffer buffer) {
VkCommandBuffer commandBuffer = startSingleTimeCommandBuffer(device, commandPool);
VkBufferImageCopy bufferImageCopy;
bufferImageCopy.bufferOffset = 0;
bufferImageCopy.bufferRowLength = 0;
bufferImageCopy.bufferImageHeight = 0;
bufferImageCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
bufferImageCopy.imageSubresource.mipLevel = 0;
bufferImageCopy.imageSubresource.baseArrayLayer = 0;
bufferImageCopy.imageSubresource.layerCount = 1;
bufferImageCopy.imageOffset = { 0, 0, 0 };
bufferImageCopy.imageExtent = { (uint32_t)getWidth(), (uint32_t)getHeight(), 1 };
vkCmdCopyBufferToImage(commandBuffer, buffer, m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferImageCopy);
endSingleTimeCommandBuffer(device, queue, commandPool, commandBuffer);
}
void generateMipmaps(VkDevice device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue queue, VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels) {
VkFormatProperties formatProperties;
vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties);
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
throw std::runtime_error("texture image format does not support linear blitting!");
}
VkCommandBuffer commandBuffer = startSingleTimeCommandBuffer(device , commandPool);
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.image = image;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.subresourceRange.levelCount = 1;
int32_t mipWidth = texWidth;
int32_t mipHeight = texHeight;
for (uint32_t i = 1; i < mipLevels; i++) {
barrier.subresourceRange.baseMipLevel = i - 1;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
vkCmdPipelineBarrier(commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
VkImageBlit blit{};
blit.srcOffsets[0] = { 0, 0, 0 };
blit.srcOffsets[1] = { mipWidth, mipHeight, 1 };
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.mipLevel = i - 1;
blit.srcSubresource.baseArrayLayer = 0;
blit.srcSubresource.layerCount = 1;
blit.dstOffsets[0] = { 0, 0, 0 };
blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.mipLevel = i;
blit.dstSubresource.baseArrayLayer = 0;
blit.dstSubresource.layerCount = 1;
vkCmdBlitImage(commandBuffer,
image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit,
VK_FILTER_LINEAR);
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
if (mipWidth > 1) mipWidth /= 2;
if (mipHeight > 1) mipHeight /= 2;
}
barrier.subresourceRange.baseMipLevel = mipLevels - 1;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
endSingleTimeCommandBuffer(device, queue, commandPool, commandBuffer);
}
void clearImage() {
if (m_loaded) {
stbi_image_free(m_ppixels);
m_loaded = false;
}
}
LoadImage(const LoadImage&) = delete;
LoadImage(LoadImage&&) = delete;
LoadImage& operator =(const LoadImage&) = delete;
LoadImage& operator =(LoadImage&&) = delete;
void destroy() {
if (m_loaded) {
stbi_image_free(m_ppixels);
m_loaded = false;
}
if (m_uploaded) {
vkDestroySampler(m_device, m_sampler, nullptr);
vkDestroyImageView(m_device, m_imageView, nullptr);
vkDestroyImage(m_device, m_image, nullptr);
vkFreeMemory(m_device, m_imageMemory, nullptr);
m_uploaded = false;
}
}
int getHeight() {
if (!m_loaded) {
throw std::logic_error("LoadImage wasnt loaded!");
}
return m_height;
}
int getWidth() {
if (!m_loaded) {
throw std::logic_error("LoadImage wasnt loaded!");
}
return m_width;
}
int getChannels() {
if (!m_loaded) {
throw std::logic_error("LoadImage wasnt loaded!");
}
return 4;
}
int getSizeInBytes() {
if (!m_loaded) {
throw std::logic_error("LoadImage wasnt loaded!");
}
return getWidth() * getHeight() * getChannels();
}
stbi_uc* getRaw() {
if (!m_loaded) {
throw std::logic_error("LoadImage wasnt loaded!");
}
return m_ppixels;
}
VkSampler getSampler() {
if (!m_loaded) {
throw std::logic_error("LoadImage wasnt loaded!");
}
return m_sampler;
}
VkImageView getImageView() {
return m_imageView;
}
};
I know its not the best written out there. For better understanding:
LoadImage image;
LoadImage normal;
std::vector<LoadImage> v_image;
std::vector<LoadImage> v_normal;
int TextureElement = 0;
const uint32_t TextureArraySize = 2;
This is how i use my Loadimage class to load/upload an image:
void loadTexture(const std::string& Tex, const std::string& Norm) {
image.load(Tex.c_str(), physicalDevices[0]);
image.upload(device, physicalDevices[0], commandPool, queue);
normal.load(Norm.c_str(), physicalDevices[0]);
normal.upload(device, physicalDevices[0], commandPool, queue);
v_image.push_back(image);
v_normal.push_back(normal);
image.clearImage();
normal.clearImage();
}
void createDescriptorSet() {
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{};
descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptorSetAllocateInfo.pNext = nullptr;
descriptorSetAllocateInfo.descriptorPool = descriptorPool;
descriptorSetAllocateInfo.descriptorSetCount = 1;
descriptorSetAllocateInfo.pSetLayouts = &descriptorSetLayout;
VkResult result = vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet);
ASSERT_VULKAN(result);
VkDescriptorBufferInfo descriptorBufferInfo{};
descriptorBufferInfo.buffer = ShaderStorageBuffer;
descriptorBufferInfo.offset = 0;
descriptorBufferInfo.range =sizeof(ssbo);
VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.pNext = nullptr;
descriptorWrite.dstSet = descriptorSet;
descriptorWrite.dstBinding = StorageBinding;
descriptorWrite.dstArrayElement = TextureElement * 2;
descriptorWrite.descriptorCount = 1;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptorWrite.pImageInfo = nullptr;
descriptorWrite.pBufferInfo = &descriptorBufferInfo;
descriptorWrite.pTexelBufferView = nullptr;
VkDescriptorImageInfo descriptorImageInfo[TextureArraySize];
VkDescriptorImageInfo descriptorImageNormalInfo[TextureArraySize];
for (uint32_t i = 0; i < TextureArraySize; i++) {
descriptorImageInfo[i] = {};
descriptorImageInfo[i].sampler = v_image[i].getSampler();
descriptorImageInfo[i].imageView = v_image[i].getImageView();
descriptorImageInfo[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
descriptorImageNormalInfo[i] = {};
descriptorImageNormalInfo[i].sampler = v_normal[i].getSampler();
descriptorImageNormalInfo[i].imageView = v_normal[i].getImageView();
descriptorImageNormalInfo[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
std::vector<VkDescriptorImageInfo> descriptorImageInfos = { descriptorImageInfo, descriptorImageNormalInfo };
VkWriteDescriptorSet descriptorSampler{};
descriptorSampler.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorSampler.pNext = nullptr;
descriptorSampler.dstSet = descriptorSet;
descriptorSampler.dstBinding = 1;
descriptorSampler.dstArrayElement = TextureElement * 2;
descriptorSampler.descriptorCount = descriptorImageInfos.size();
descriptorSampler.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorSampler.pImageInfo = descriptorImageInfos.data();
descriptorSampler.pBufferInfo = nullptr;
descriptorSampler.pTexelBufferView = nullptr;
std::vector<VkWriteDescriptorSet> writeDescriptorSets;
writeDescriptorSets.push_back(descriptorWrite);
writeDescriptorSets.push_back(descriptorSampler);
vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
}
I know its a long one right here, also some advise for Improvement would be nice, scince i started coding with c++ like a year ago as a side Hobby and use the Vulkan API for about half a year.
I already tried:
EDIT: There a MUCH more error Messages
VUID-vkCmdPipelineBarrier-srcStageMask-04091(ERROR / SPEC): msgNum: 2143709137 - Validation Error: [ VUID-vkCmdPipelineBarrier-srcStageMask-04091 ] Object 0: handle = 0x26c9336abf0, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x7fc667d1 | vkCmdPipelineBarrier(): .srcStageMask includes VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT when the device does not have tessellationShader feature enabled. The Vulkan spec states: If the tessellationShader feature is not enabled, pname:srcStageMask must not contain VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT or VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT (https://vulkan.lunarg.com/doc/view/1.3.250.1/windows/1.3-extensions/vkspec.html#VUID-vkCmdPipelineBarrier-srcStageMask-04091)
Objects: 1
[0] 0x26c9336abf0, type: 6, name: NULL
VUID-vkCmdPipelineBarrier-dstStageMask-04091(ERROR / SPEC): msgNum: -28584298 - Validation Error: [ VUID-vkCmdPipelineBarrier-dstStageMask-04091 ] Object 0: handle = 0x26c9336abf0, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0xfe4bd696 | vkCmdPipelineBarrier(): .dstStageMask includes VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT when the device does not have tessellationShader feature enabled. The Vulkan spec states: If the tessellationShader feature is not enabled, pname:dstStageMask must not contain VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT or VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT (https://vulkan.lunarg.com/doc/view/1.3.250.1/windows/1.3-extensions/vkspec.html#VUID-vkCmdPipelineBarrier-dstStageMask-04091)
Objects: 1
[0] 0x26c9336abf0, type: 6, name: NULL
The barriers in code look correct. But in your comments you mentioned that you use MSI Afterburner. Tools like this install implicit Vulkan layers that are always enabled if you run a Vulkan application. This means that they can inject their own Vulkan code, e.g. for displaying an overlay. MSI Afterburner's Vulkan layer is known to cause problems that also may result in validation layer message errors that then look like they're caused by your applications.
If you develop with Vulkan it's best to disable those implicit layers. You can do this by removing MSI Afterburner, changing it's layer value in the registry to 1 (on Windows, see Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\ImplicitLayers), using vkconfig from the LunarG SDK or by setting an environment variable