为什么这个 Vulkan 代码有写后写的危险?
Why does this Vulkan code have a write-after-write hazard?
我正在尝试新的 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,
0,
0, nullptr,
0, 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, 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, 0 };
imageBlit.dstOffsets[ 1 ] = { mipWidth, mipHeight, 1 };
vkCmdBlitImage( GfxDeviceGlobal::texCmdBuffer, image, VK_IMAGE_LAYOUT_GENERAL, image,
VK_IMAGE_LAYOUT_GENERAL, 1, &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,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &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).
如何解决?
请记住,Layout Transition 是 happens-between srcStage
和 dstStage
.
的读写操作
首先提交布局转换。它happens-before VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
.
然后你做一个 vkCmdBlitImage
,这将是 mis-synchronized,因为那是 STAGE_TRANSFER
,而不是 STAGE_FRAGMENT_SHADER
。尽管你的 mipLevelCount == 1
,所以整个循环都是死代码。
然后你有另一个布局转换。它happens-after VK_PIPELINE_STAGE_TRANSFER_BIT
.
因此您有两个可能同时发生的布局转换。
我认为设置 dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT
应该可以解决这个问题。
我正在尝试新的 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,
0,
0, nullptr,
0, 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, 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, 0 };
imageBlit.dstOffsets[ 1 ] = { mipWidth, mipHeight, 1 };
vkCmdBlitImage( GfxDeviceGlobal::texCmdBuffer, image, VK_IMAGE_LAYOUT_GENERAL, image,
VK_IMAGE_LAYOUT_GENERAL, 1, &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,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &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).
如何解决?
请记住,Layout Transition 是 happens-between srcStage
和 dstStage
.
首先提交布局转换。它happens-before VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
.
然后你做一个 vkCmdBlitImage
,这将是 mis-synchronized,因为那是 STAGE_TRANSFER
,而不是 STAGE_FRAGMENT_SHADER
。尽管你的 mipLevelCount == 1
,所以整个循环都是死代码。
然后你有另一个布局转换。它happens-after VK_PIPELINE_STAGE_TRANSFER_BIT
.
因此您有两个可能同时发生的布局转换。
我认为设置 dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT
应该可以解决这个问题。