加载 HDR 纹理 Vulkan 时出现问题

Problems loading HDR textures Vulkan

我正在尝试制作游戏引擎。目前我正在尝试实现一个 HDR 天空盒,它也有 mip 贴图,但是有一段时间一直出现错误。示例 运行 完全可以很好地加载非 HDR 8 位颜色纹理。但是当我尝试使用 VK_FORMAT_R32G32B32A32_SFLOAT (我很确定这是正确的格式以对应从 stbi 加载的 4 位浮点数)时,命令缓冲区无法完成并且似乎总是处于待定状态状态,我曾尝试为纹理管理器提供它自己的命令缓冲区,但没有成功,并同时使用 vkQueueWaitIdle 和栅栏,但它们都 return VK_SUCCEED。一旦调用 vkResetCommandBuffer,验证层就会抛出错误,因为命令缓冲区处于挂起状态。看起来,如果我单击以专注于控制台,它有时很少起作用,之后会出现更多错误,但情况并非总是如此,HDR 似乎在加载后就可以工作,但仍然有 1/3 的情况主要是抛出相同的错误。

这是用于加载立方体贴图的代码:

Cubemap::Cubemap(CubemapInfo cubemapInfo)
{
    RenderSystem& renderSystem = RenderSystem::instance();
    TextureManager& textureManager = TextureManager::instance();

    VkImageFormatProperties formatProperties;
    assert(("[ERROR] Unsupported texture format", !vkGetPhysicalDeviceImageFormatProperties(renderSystem.mPhysicalDevice, cubemapInfo.format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 0, &formatProperties)));

    FormatInfo formatInfo = getFormatInfo(cubemapInfo.format);

    #pragma region Create cubemap resources
    stbi_set_flip_vertically_on_load(true);

    void* textureData[6];
    // Load images
    int width, height, channels;
    bool hdr = cubemapInfo.format == VK_FORMAT_R16_SFLOAT || cubemapInfo.format == VK_FORMAT_R16G16_SFLOAT || cubemapInfo.format == VK_FORMAT_R16G16B16_SFLOAT || cubemapInfo.format == VK_FORMAT_R16G16B16A16_SFLOAT || cubemapInfo.format == VK_FORMAT_R32_SFLOAT || cubemapInfo.format == VK_FORMAT_R32G32_SFLOAT || cubemapInfo.format == VK_FORMAT_R32G32B32_SFLOAT || cubemapInfo.format == VK_FORMAT_R32G32B32A32_SFLOAT;
    if (hdr)
    {
        if (formatInfo.bytesPerChannel == 4)
        {
            for (unsigned int i = 0; i < 6; i++)
            {
                textureData[i] = stbi_loadf(cubemapInfo.directories[i].c_str(), &width, &height, &channels, formatInfo.nChannels);
            }
        }
        else if (formatInfo.bytesPerChannel == 2)
        {
            for (unsigned int i = 0; i < 6; i++)
            {
                float* data = stbi_loadf(cubemapInfo.directories[i].c_str(), &width, &height, &channels, formatInfo.nChannels);
                unsigned long long dataSize = width * height * formatInfo.nChannels;

                textureData[i] = new float16[dataSize];
                for (unsigned long long j = 0; j < dataSize; j++)
                {
                    ((float16*)textureData[i])[j] = floatToFloat16(data[j]);
                }
                stbi_image_free((void*)data);
            }
        }
    }
    else
    {
        for (unsigned int i = 0; i < 6; i++)
        {
            textureData[i] = stbi_load(cubemapInfo.directories[i].c_str(), &width, &height, &channels, formatInfo.nChannels);
        }
    }

    const VkDeviceSize imageSize = 6 * VkDeviceSize(width) * height * formatInfo.nChannels * formatInfo.bytesPerChannel;
    unsigned int nMips = unsigned int(std::floor(std::log2(width > height ? width : height))) + 1;

    assert(("[ERROR] Unsupported texture format", formatProperties.maxExtent.width >= width && formatProperties.maxExtent.height >= height && formatProperties.maxExtent.depth >= 1 && formatProperties.maxMipLevels >= 1 && formatProperties.maxArrayLayers >= 1 && formatProperties.sampleCounts & VK_SAMPLE_COUNT_1_BIT && formatProperties.maxResourceSize >= imageSize));

    // Create image
    VkImageCreateInfo imageCreateInfo = {};
    imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageCreateInfo.pNext = nullptr;
    imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
    imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
    imageCreateInfo.format = cubemapInfo.format;
    imageCreateInfo.extent = { unsigned int(width), unsigned int(height), 1 };
    imageCreateInfo.mipLevels = nMips;
    imageCreateInfo.arrayLayers = 6;
    imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
    imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | 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(renderSystem.mDevice, &imageCreateInfo, nullptr, &mImage);
    validateResult(result);

    VkMemoryRequirements memoryRequirements;
    vkGetImageMemoryRequirements(renderSystem.mDevice, mImage, &memoryRequirements);

    VkMemoryAllocateInfo memoryAllocateInfo = {};
    memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    memoryAllocateInfo.allocationSize = memoryRequirements.size;
    memoryAllocateInfo.memoryTypeIndex = memoryTypeFromProperties(renderSystem.mPhysicalDeviceMemoryProperties, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    result = vkAllocateMemory(renderSystem.mDevice, &memoryAllocateInfo, nullptr, &mImageMemory);
    validateResult(result);

    result = vkBindImageMemory(renderSystem.mDevice, mImage, mImageMemory, 0);
    validateResult(result);

    // Create staging buffer
    VkBuffer stagingBuffer;
    VkDeviceMemory stagingMemory;

    VkBufferCreateInfo bufferCreateInfo = {};
    bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferCreateInfo.pNext = nullptr;
    bufferCreateInfo.flags = 0;
    bufferCreateInfo.size = imageSize;
    bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
    bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    bufferCreateInfo.queueFamilyIndexCount = 0;
    bufferCreateInfo.pQueueFamilyIndices = nullptr;
    result = vkCreateBuffer(renderSystem.mDevice, &bufferCreateInfo, nullptr, &stagingBuffer);
    validateResult(result);

    vkGetBufferMemoryRequirements(renderSystem.mDevice, stagingBuffer, &memoryRequirements);

    memoryAllocateInfo.allocationSize = memoryRequirements.size;
    memoryAllocateInfo.memoryTypeIndex = memoryTypeFromProperties(renderSystem.mPhysicalDeviceMemoryProperties, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
    result = vkAllocateMemory(renderSystem.mDevice, &memoryAllocateInfo, nullptr, &stagingMemory);
    validateResult(result);

    result = vkBindBufferMemory(renderSystem.mDevice, stagingBuffer, stagingMemory, 0);
    validateResult(result);

    unsigned char* data;
    result = vkMapMemory(renderSystem.mDevice, stagingMemory, 0, imageSize, 0, (void**)&data);
    validateResult(result);

    unsigned long long dataLayer = unsigned long long(width) * height * formatInfo.nChannels * formatInfo.bytesPerChannel;
    for (unsigned int i = 0; i < 6; i++)
    {
        memcpy((void*)(data + i * dataLayer), textureData[i], dataLayer);
        stbi_image_free(textureData[i]);
    }

    vkUnmapMemory(renderSystem.mDevice, stagingMemory);

    result = vkBeginCommandBuffer(textureManager.mCommandBuffer, &renderSystem.mCommandBufferBeginInfo);
    validateResult(result);

    VkImageMemoryBarrier barrier = {};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.pNext = nullptr;
    barrier.srcAccessMask = 0;
    barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT /* Additional >> */ | VK_ACCESS_TRANSFER_READ_BIT;
    barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.image = mImage;
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = nMips;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 6;
    vkCmdPipelineBarrier(textureManager.mCommandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);

    VkBufferImageCopy copyRegion = {};
    copyRegion.bufferOffset = 0;
    copyRegion.bufferRowLength = 0;
    copyRegion.bufferImageHeight = 0;
    copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    copyRegion.imageSubresource.mipLevel = 0;
    copyRegion.imageSubresource.baseArrayLayer = 0;
    copyRegion.imageSubresource.layerCount = 6;
    copyRegion.imageOffset = { 0, 0, 0 };
    copyRegion.imageExtent = { unsigned int(width), unsigned int(height), 1 };
    vkCmdCopyBufferToImage(textureManager.mCommandBuffer, stagingBuffer, mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);

    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
    barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
    barrier.subresourceRange.levelCount = 1;

    VkImageBlit imageBlit = {};
    imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    imageBlit.srcSubresource.baseArrayLayer = 0;
    imageBlit.srcSubresource.layerCount = 6;
    imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    imageBlit.dstSubresource.baseArrayLayer = 0;
    imageBlit.dstSubresource.layerCount = 6;

    unsigned int mipWidth = width, mipHeight = height;
    for (unsigned int i = 1; i < nMips; i++)
    {
        barrier.subresourceRange.baseMipLevel = i - 1;
        vkCmdPipelineBarrier(textureManager.mCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);

        imageBlit.srcSubresource.mipLevel = i - 1;
        imageBlit.srcOffsets[0] = { 0, 0, 0 };
        imageBlit.srcOffsets[1] = { int(mipWidth), int(mipHeight), 1 };

        imageBlit.dstSubresource.mipLevel = i;
        if (mipWidth > 1)
            mipWidth /= 2;
        if (mipHeight > 1)
            mipHeight /= 2;
        imageBlit.dstOffsets[0] = { 0, 0, 0 };
        imageBlit.dstOffsets[1] = { int(mipWidth), int(mipHeight), 1 };

        vkCmdBlitImage(textureManager.mCommandBuffer, mImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, mImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlit, VK_FILTER_LINEAR);
    }

    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    for (unsigned int i = 0; i < nMips; i++)
    {
        barrier.oldLayout = i == nMips - 1 ? VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        barrier.subresourceRange.baseMipLevel = i;
        vkCmdPipelineBarrier(textureManager.mCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
    }

    result = vkEndCommandBuffer(textureManager.mCommandBuffer);
    validateResult(result);

    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.pNext = nullptr;
    submitInfo.waitSemaphoreCount = 0;
    submitInfo.pWaitSemaphores = nullptr;
    submitInfo.pWaitDstStageMask = nullptr;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &textureManager.mCommandBuffer;
    submitInfo.signalSemaphoreCount = 0;
    submitInfo.pSignalSemaphores = nullptr;
    result = vkQueueSubmit(renderSystem.mGraphicsQueue, 1, &submitInfo, NULL);
    validateResult(result);

    // Create image view
    VkImageViewCreateInfo imageViewCreateInfo = {};
    imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    imageViewCreateInfo.pNext = nullptr;
    imageViewCreateInfo.flags = 0;
    imageViewCreateInfo.image = mImage;
    imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
    imageViewCreateInfo.format = cubemapInfo.format;
    imageViewCreateInfo.components = formatInfo.componentMapping;
    imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
    imageViewCreateInfo.subresourceRange.levelCount = nMips;
    imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
    imageViewCreateInfo.subresourceRange.layerCount = 6;
    result = vkCreateImageView(renderSystem.mDevice, &imageViewCreateInfo, nullptr, &mImageView);
    validateResult(result);

    // Create sampler
    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.mipLodBias = 0.0f;
    samplerCreateInfo.minLod = 0.0f;
    samplerCreateInfo.maxLod = float(nMips);
    samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;;
    samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
    samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
    samplerCreateInfo.anisotropyEnable = VK_TRUE;
    samplerCreateInfo.maxAnisotropy = renderSystem.mPhysicalDeviceProperties.limits.maxSamplerAnisotropy;
    samplerCreateInfo.compareEnable = VK_FALSE;
    samplerCreateInfo.compareOp = VK_COMPARE_OP_ALWAYS;
    samplerCreateInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
    samplerCreateInfo.unnormalizedCoordinates = VK_FALSE;
    result = vkCreateSampler(renderSystem.mDevice, &samplerCreateInfo, nullptr, &mSampler);
    validateResult(result);

    result = vkQueueWaitIdle(renderSystem.mGraphicsQueue);
    validateResult(result);
    result = vkResetCommandBuffer(textureManager.mCommandBuffer, 0);
    validateResult(result);

    vkDestroyBuffer(renderSystem.mDevice, stagingBuffer, nullptr);
    vkFreeMemory(renderSystem.mDevice, stagingMemory, nullptr);
    #pragma endregion
}

发生的确切错误:

VUID-vkResetCommandBuffer-commandBuffer-00045(错误/规格):msgNum:511214570 - 验证错误:[VUID-vkResetCommandBuffer-commandBuffer-00045] 对象 0:句柄 = 0x19323492138,类型 = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x1e7883ea |尝试重置正在使用的 VkCommandBuffer 0x19323492138[]。 Vulkan 规范规定:commandBuffer 不得处于挂起状态 (https://vulkan.lunarg.com/doc/view/1.2.162.1/windows/1.2-extensions/vkspec.html#VUID-vkResetCommandBuffer-commandBuffer-00045) 对象:1 [0] 0x19323492138,类型:6,名称:NULL

UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout(错误/规范):msgNum:1303270965 - 验证错误:[UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout] 对象 0:句柄 = 0x19323492138,类型 = VK_OBJECT_TYPE_COMMAND_BUFFER; |消息 ID = 0x4dae5635 |提交的命令缓冲区期望 VkImage 0x5fb0e800000000cd[](子资源:aspectMask 0x1 数组层 0,mip 级别 0)处于布局 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL--instead,当前布局为 VK_IMAGE_LAYOUT_UNDEFINED。 对象:1 [0] 0x19323492138,类型:6,名称:NULL

(^ 对于 mip 级别 0-12 连续抛出此错误 ^)

查看完整来源: https://github.com/finnbuhse/Vulkan-Engine-V1.0

尽管资产和着色器二进制文件不在 github 上,因此将着色器源代码编译成名称与 mesh.cpp 第 1083 行中找到的名称相同的文件,并调整 main.cpp 以包含自定义如果您想尝试编译和 运行 源代码。

任何关于为什么会发生这种情况的线索将不胜感激

所以,在为这个错误苦恼了将近一整年之后...我发现似乎发生的事情是 GPU 内存 运行 用完了,而且我最初选择的天空盒太大了;每个面都是 4K,我发现整个立方体贴图必须分配超过 1 GB 的视频内存,而只有 NVIDIA GTX 1050 Ti,是它的四分之一。但是我确实认为这在早期是可能的,这就是为什么我验证了我能想到的每个 VkResult 如果发生这种情况, VK_ERROR_OUT_OF_DEVICE_MEMORY 将被返回。然而,从 'results' 可以看出,只有成功。也许不是显存太多,而是 GPU 很难对如此大的图像进行 mip-mapping。无论哪种方式,使用不同的 HDR 天空盒 (1k),它都可以很好地处理 16 位浮点图像和 32 位图像。