image - 我在使用 vulkan 绘制图像时遇到错误

标签 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 调试配置构建)

最佳答案

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

但我对您的代码也有一些评论:

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

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

if (surface.getSurfaceCapability().minImageCount != 2) debug::Break("您的图形设备不支持双缓冲!");

minImageCount 表面功能成员意味着您可以使用至少该数量的图像创建交换链(您不能请求更少)。但这并不意味着不支持双缓冲。如果驱动程序指定3怎么办? 3 个图像足以进行双(甚至三)缓冲,但这里 3 个图像也会触发中断。

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

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

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

  • 关于image - 我在使用 vulkan 绘制图像时遇到错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46621763/

    相关文章:

    Vulkan - 从合并的缓存创建多个管道缓存

    html - 如何使用 CSS 将内联文本替换为图像

    image - 如何在delphi中完全删除面板边框?

    c++ - opengl中渲染对象的移动

    android - 关于 Vulkan Initialization/Deinitialization 和 Android App life cycle event 的问题

    Vulkan WaW 危害和内存屏障

    iphone - 在 Core Graphics 中调整图像大小

    image - 如何在 MATLAB 中以 2-D 和 3-D 绘制图像 (.jpg)?

    java - CardLayout 中显示错误的 JPanel。 getGraphics() 的问题

    php - HTML/CSS 可视化 RBAC 图