为什么此Vulkan代码有写后写危险?

问题描述

我正在尝试新的Vulkan synchronization validation tool,它在我的纹理加载代码中发现了写/写危险:


    mipLevelCount = mipmaps == Mipmaps::Generate ? MathUtil::GetMipmapCount( width,height ) : 1;

    VkImageSubresourceRange range = {};
    range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    range.baseMipLevel = 0;
    range.levelCount = mipLevelCount;
    range.baseArrayLayer = 0;
    range.layerCount = 1;

    imageMemoryBarrier = {};
    imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGnorED;
    imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGnorED;
    imageMemoryBarrier.srcAccessMask = 0;
    imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
    imageMemoryBarrier.image = image;
    imageMemoryBarrier.subresourceRange = range;

    vkCmdPipelineBarrier(
        GfxDeviceGlobal::texCmdBuffer,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,nullptr,1,&imageMemoryBarrier );

    for (int i = 1; i < mipLevelCount; ++i)
    {
        const std::int32_t mipwidth = MathUtil::Max( width >> i,1 );
        const std::int32_t mipHeight = MathUtil::Max( height >> i,1 );

        VkImageBlit imageBlit = {};
        imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        imageBlit.srcSubresource.baseArrayLayer = 0;
        imageBlit.srcSubresource.layerCount = 1;
        imageBlit.srcSubresource.mipLevel = 0;
        imageBlit.srcOffsets[ 0 ] = { 0,0 };
        imageBlit.srcOffsets[ 1 ] = { width,height,1 };

        imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        imageBlit.dstSubresource.baseArrayLayer = 0;
        imageBlit.dstSubresource.layerCount = 1;
        imageBlit.dstSubresource.mipLevel = i;
        imageBlit.dstOffsets[ 0 ] = { 0,0 };
        imageBlit.dstOffsets[ 1 ] = { mipwidth,mipHeight,1 };

        vkCmdBlitimage( GfxDeviceGlobal::texCmdBuffer,image,VK_IMAGE_LAYOUT_GENERAL,&imageBlit,VK_FILTER_LINEAR );
    }

    imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
    imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageMemoryBarrier.image = image;
    imageMemoryBarrier.subresourceRange = range;
    imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
    imageMemoryBarrier.subresourceRange.levelCount = mipLevelCount;

    vkCmdPipelineBarrier(                   // <-- The error happens at this call.
            GfxDeviceGlobal::texCmdBuffer,&imageMemoryBarrier );

错误是:

ERROR: Validation Error: [ SYNC-HAZARD-WRITE_AFTER_WRITE ] Object 0: handle = 0x559307e35610,type = VK_OBJECT_TYPE_IMAGE; | MessageID = 0xfdf9f5e1 | vkCmdPipelineBarrier: Hazard WRITE_AFTER_WRITE for image barrier 0 VkImage 0x559307e35610[]. Access info (usage: SYNC_IMAGE_LAYOUT_TRANSITION,prior_usage: SYNC_IMAGE_LAYOUT_TRANSITION,write_barriers: 0,command: vkCmdPipelineBarrier,seq_no: 1,reset_no: 3).

如何解决

解决方法

请记住,布局转换是发生在srcStagedstStage之间的读写操作。

首先,您提交布局转换。它发生在VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT之前。

然后您执行vkCmdBlitImage,由于它是STAGE_TRANSFER,而不是STAGE_FRAGMENT_SHADER,因此会被错误同步。虽然是您的mipLevelCount == 1,所以整个循环都是无效代码。

然后您将进行另一个布局转换。它发生在VK_PIPELINE_STAGE_TRANSFER_BIT之后。

因此,您可能同时发生两个布局转换。

我认为设置dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT应该可以解决此问题。