我在使用vulkan绘制图像时遇到了一个错误

问题描述:

我想编写一个也支持图像绘制的vulkan引擎。但是,当我尝试将图像渲染到全屏四合一时,它不起作用。我试图调试一个多星期的代码,但我无法得到它的工作。我正在用stb_image加载图片。下面是该代码:我在使用vulkan绘制图像时遇到了一个错误

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的调试配置)

+0

您使用的是验证图层吗? – krOoze

+1

代码看起来很好,但在Vulkan中,很容易错过有问题的地方。验证层说什么?你有你的代码在一个可编译项目的形式吗?调试整个项目比分析这个片段要容易得多。也许问题在其他地方,例如在描述符集中。 – Ekzuzy

+0

你可能是对的。我将编辑我的问题并将我的Visual Studio项目链接为zip下载。 – Max

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

但我也对你的代码的一些意见:

  1. 版本错误福尔康实例创建过程中指定。您指定0.0.1版本而不是1.0.0。补丁版本目前无关紧要,因为所有Vulkan驱动程序和SDK版本都应该与旧版的Vulkan版本(具有较低的补丁版本)兼容。

  2. 错误的假设swapchain创建期间被显示(和警告消息):

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

minImageCount的表面功能元件是指可以与至少该数字图像的创建swapchain (你不能请求更少)。但这并不意味着不支持双缓冲。如果驱动程序指定3,会怎样3张图像对于双重(甚至三重)缓冲是足够的,但这里3张图像也会触发中断。

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

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

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

+0

非常感谢您的反馈!我对vulkan不是很有经验,所以这对我来说真的很有帮助! – Max

+0

很高兴我能帮到你。如果您有任何问题,请不要犹豫与我或其他专家联系;-)。 – Ekzuzy