Max
Max

Reputation: 327

I have a bug when drawing an image with vulkan

I am trying to program a vulkan engine which also supports image drawing. But when I try to render an image to a fullscreen quad it does not work. I tried to debug the code for over a week now, but i cannot get it working. I am loading the image with stb_image. Here is the code for that:

errno = 0;

m_imageData = stbi_load(filename.c_str(), &m_width, &m_height, &m_channels, STBI_rgb_alpha);
if (m_imageData == nullptr) {
    String error = stbi_failure_reason();
    console::printErr("Image loading failed!\nImage: "_d + filename + "\nFailure reason: " + error + "\n" + strerror(errno));
}

Then I pass m_imageData to this method:

m_device = vulkanManager.getDevice().getDevice();
m_imageSize = width * height * 4;

VkImageCreateInfo imageCreateInfo;
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.pNext = nullptr;
imageCreateInfo.flags = 0;
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
imageCreateInfo.extent.width = width;
imageCreateInfo.extent.height = height;
imageCreateInfo.extent.depth = 1;
imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageCreateInfo.queueFamilyIndexCount = 0;
imageCreateInfo.pQueueFamilyIndices = nullptr;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

VkResult result = vkCreateImage(m_device, &imageCreateInfo, nullptr, &m_image);
debug::Assert_Vulkan(result);

void* stagingBufferMemory;

VulkanBuffer stagingBuffer((uint)m_imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
stagingBuffer.mapMemory(&stagingBufferMemory, 0);

memcpy(stagingBufferMemory, pixels, m_imageSize);

stagingBuffer.unmapMemory();

VkMemoryRequirements imageMemRequirements;
vkGetImageMemoryRequirements(m_device, m_image, &imageMemRequirements);

VkMemoryAllocateInfo imageMemAllocInfo;
imageMemAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
imageMemAllocInfo.pNext = nullptr;
imageMemAllocInfo.allocationSize = imageMemRequirements.size;
imageMemAllocInfo.memoryTypeIndex = vulkanManager.findMemoryTypeIndex(imageMemRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

result = vkAllocateMemory(m_device, &imageMemAllocInfo, nullptr, &m_imageMemory);
debug::Assert_Vulkan(result);

vkBindImageMemory(m_device, m_image, m_imageMemory, 0);

VulkanCommandBuffer copyBufferToImage(vulkanManager.getDevice().getGraphicsQueueIndex());
copyBufferToImage.startCmdBuffer(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);

VkBufferImageCopy region;
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = { 0, 0, 0 };
region.imageExtent = { width, height, 1 };

vkCmdCopyBufferToImage(copyBufferToImage.getCommandBuffer(), stagingBuffer.getBuffer(), m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

copyBufferToImage.endCmdBuffer();

transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);

copyBufferToImage.execute();

transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
stagingBuffer.destroy();

transitionImageLayout is a helper function:

void VulkanImage::transitionImageLayout(VkImageLayout oldLayout, VkImageLayout newLayout)
{
    VulkanCommandBuffer cmdBuffer(vulkanManager.getDevice().getGraphicsQueueIndex());
    cmdBuffer.startCmdBuffer(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);

    VkPipelineStageFlags srcStage, dstStage;

    VkImageMemoryBarrier memoryBarrier;

    if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
        memoryBarrier.srcAccessMask = 0;
        memoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;

        srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
        dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    }
    else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
        memoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

        srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    }

    memoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    memoryBarrier.pNext = nullptr;
    memoryBarrier.oldLayout = oldLayout;
    memoryBarrier.newLayout = newLayout;
    memoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    memoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    memoryBarrier.image = m_image;
    memoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    memoryBarrier.subresourceRange.baseArrayLayer = 0;
    memoryBarrier.subresourceRange.baseMipLevel = 0;
    memoryBarrier.subresourceRange.layerCount = 1;
    memoryBarrier.subresourceRange.levelCount = 1;

    vkCmdPipelineBarrier(
        cmdBuffer.getCommandBuffer(),
        srcStage, dstStage,
        0,
        0, nullptr,
        0, nullptr,
        1, &memoryBarrier
    );

    cmdBuffer.endCmdBuffer();
    cmdBuffer.execute();
}

VulkanCommandBuffer and VulkanBuffer are just a wrapper for VkBuffer and VkCommandBuffer.

After all the image loading I create an imageView and update a descriptor in my fragment shader with the image. But when I execute the program there is just white on the screen. But my shaders are working when I just draw one color in the fragment shader.

Here is a link to the complete Visual Studio 2017 project: link (build with x64 debug configuration)

Upvotes: 2

Views: 1030

Answers (1)

Ekzuzy
Ekzuzy

Reputation: 3437

To answer Your question. Problem is with descriptor set, more specifically - You don't bind it to a command buffer before drawing. In order to use descriptor sets, apart from allocating and updating them, You also need to bind them. Updating descriptor set just associates specific resources (images, samplers, buffer) with a given descriptor set. This way You may have multiple descriptor sets with various resources. But in order to use a given descriptor set, You need to bind it before drawing. Binding operation is done via the vkCmdBindDescriptorSets() function call through which You specify which descriptor sets should be used (and thus - which resources should be used for drawing). In Your code vkCmdBindDescriptorSets() function is never called (it wasn't used anywhere, or at least Visual Studio didn't find it).

But I have also some comments about Your code:

  1. Wrong version is specified during Vulkan Instance creation. You specify 0.0.1 version instead of 1.0.0. Patch version doesn't matter at the moment, as all Vulkan drivers and SDK versions should be compatible with older Vulkan versions (having lower patch version).

  2. Wrong assumption (and warning message) is displayed during swapchain creation:

if (surface.getSurfaceCapabilities().minImageCount != 2) debug::Break("Your graphics device does not support double buffering!");

minImageCount member of surface capabilities means that You can create swapchain with at least that number of images (You cannot request less). But it doesn't mean double buffering is not supported. What if driver specifies 3? 3 images are enough for double- (or even triple-) buffering, but here 3 images will also trigger a break.

  1. You create a graphics pipeline with blending enabled. When I set fragment shader to just provide a red output (1, 0, 0, 0), the color wasn't visible (because it was transparent). I had to change it to (1, 0, 0, 1). It is, of course, valid, but sometimes it may cause debugging problems.

  2. Your drawing code is organized in a strange way. You create a descriptor set layout, a pipeline layout (that uses a descriptor set) and a graphics pipeline. Then You immediately record a command buffer that binds the pipeline and draws geometry. This is invalid, because the pipeline already uses a descriptor set (as specified in the pipeline layout and a fragment shader), but it hasn't been updated so far. After several frames You update the descriptor set. Only after this point Your drawing code starts being correct. You should refactor it to avoid potential problems in the future.

  3. Your texture coordinates are wrong. But it's easy to spot this issue once You fix problems with descriptor sets.

Upvotes: 2

Related Questions