用vulkan绘制图像时我有一个错误

时间:2017-10-07 15:14:12

标签: image graphics vulkan

我正在尝试编写一个也支持图像绘制的vulkan引擎。但是当我尝试将图像渲染到全屏四边形时,它不起作用。我试着调试代码超过一个星期了,但我无法让它工作。我正在使用stb_image加载图片。这是代码:

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

然后我将m_imageData传递给此方法:

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是一个辅助函数:

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

VulkanCommandBufferVulkanBuffer只是VkBufferVkCommandBuffer的包装。

在所有图像加载之后,我创建了一个imageView并使用图像更新了片段着色器中的描述符。但是当我执行程序时,屏幕上只有白色。但是当我在片段着色器中绘制一种颜色时,我的着色器正在工作。

以下是完整Visual Studio 2017项目的链接:link(使用x64调试配置构建)

1 个答案:

答案 0 :(得分:1)

回答你的问题。问题在于描述符集,更具体地说 - 在绘制之前不要将它绑定到命令缓冲区。为了使用描述符集,除了分配和更新它们之外,还需要绑定它们。更新描述符集只是将特定资源(图像,采样器,缓冲区)与给定的描述符集相关联。这样您可能有多个具有各种资源的描述符集。但是为了使用给定的描述符集,您需要在绘制之前将其绑定。绑定操作通过 vkCmdBindDescriptorSets()函数调用完成,您可以通过该函数调用指定应使用哪些描述符集(因此 - 应该使用哪些资源进行绘制)。在您的代码中, vkCmdBindDescriptorSets()函数永远不会被调用(它没有在任何地方使用,或者至少Visual Studio没有找到它。)

但我也有一些关于你的代码的评论:

  1. 在Vulkan实例创建期间指定了错误的版本。您指定0.0.1版本而不是1.0.0。补丁版本目前并不重要,因为所有Vulkan驱动程序和SDK版本都应与旧的Vulkan版本兼容(具有较低的补丁版本)。

  2. 在交换链创建期间显示错误的假设(和警告消息):

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

    minImageCount 表面功能的成员意味着您可以创建至少具有该数量的图像的交换链(您不能请求更少)。但它并不意味着不支持双缓冲。如果司机指定3怎么办? 3个图像足以进行双重(甚至三重)缓冲,但这里有3个图像也会触发休息。

    1. 您可以创建启用了混合的图形管道。当我将片段着色器设置为仅提供红色输出(1,0,0,0)时,颜色不可见(因为它是透明的)。我不得不把它改成(1,0,0,1)。它当然是有效的,但有时它可能会导致调试问题。

    2. 您的绘图代码以奇怪的方式组织。您可以创建描述符集布局,管道布局(使用描述符集)和图形管道。然后,您立即记录绑定管道并绘制几何体的命令缓冲区。这是无效的,因为管道已经使用了描述符集(在管道布局和片段着色器中指定),但到目前为止还没有更新。几帧之后更新描述符集。仅在此之后您的绘图代码才开始正确。您应该重构它以避免将来出现潜在问题。

    3. 您的纹理坐标错误。但是,一旦修复了描述符集的问题,就很容易发现这个问题。