深度图像金字塔过渡同步异常:Vulkan布局验证错误排查
Vulkan深度金字塔布局验证错误排查
我在遵循Vulkan指南(extra-engine分支)实现计算剔除时,碰到了深度金字塔图像布局过渡的问题。Vulkan验证层抛出错误:
深度金字塔基础图像(句柄0xd897d90000000016)的mip0子资源预期为VK_IMAGE_LAYOUT_GENERAL布局,但当前为VK_IMAGE_LAYOUT_UNDEFINED。
我怀疑问题出在reduce_depth函数或者前向/阴影渲染通道的创建环节,目前临时修改渲染通道依赖能正常运行,想确认指南源码是否存在问题。
问题代码
void VulkanEngine::reduce_depth(VkCommandBuffer cmd) { vkutil::VulkanScopeTimer timer(cmd, _profiler, "Depth Reduce"); VkImageMemoryBarrier depthReadBarriers[] = { vkinit::image_barrier(_depthImage._image, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_ASPECT_DEPTH_BIT), }; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, 0, 0, 0, 1, depthReadBarriers); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _depthReducePipeline); for (int32_t i = 0; i < depthPyramidLevels; ++i) { VkDescriptorImageInfo destTarget; destTarget.sampler = _depthSampler; destTarget.imageView = depthPyramidMips[i]; destTarget.imageLayout = VK_IMAGE_LAYOUT_GENERAL; VkDescriptorImageInfo sourceTarget; sourceTarget.sampler = _depthSampler; if (i == 0) { sourceTarget.imageView = _depthImage._defaultView; sourceTarget.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } else { // DEPTH pyramid current layout is VK_IMAGE_LAYOUT_UNDEFINED by vulkan validation layer sourceTarget.imageView = depthPyramidMips[i - 1]; sourceTarget.imageLayout = VK_IMAGE_LAYOUT_GENERAL; } VkDescriptorSet depthSet; vkutil::DescriptorBuilder::begin(_descriptorLayoutCache, get_current_frame().dynamicDescriptorAllocator) .bind_image(0, &destTarget, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT) .bind_image(1, &sourceTarget, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_COMPUTE_BIT) .build(depthSet); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _depthReduceLayout, 0, 1, &depthSet, 0, nullptr); uint32_t levelWidth = depthPyramidWidth >> i; uint32_t levelHeight = depthPyramidHeight >> i; if (levelHeight < 1) levelHeight = 1; if (levelWidth < 1) levelWidth = 1; DepthReduceData reduceData = { glm::vec2(levelWidth, levelHeight) }; vkCmdPushConstants(cmd, _depthReduceLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(reduceData), &reduceData); vkCmdDispatch(cmd, getGroupCount(levelWidth, 32), getGroupCount(levelHeight, 32), 1); VkImageMemoryBarrier reduceBarrier = vkinit::image_barrier(_depthPyramid._image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_ASPECT_COLOR_BIT); vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, 0, 0, 0, 1, &reduceBarrier); } VkImageMemoryBarrier depthWriteBarrier = vkinit::image_barrier(_depthImage._image, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_ASPECT_DEPTH_BIT); vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, 0, 0, 0, 1, &depthWriteBarrier); }
问题分析
- 初始布局未转换:深度金字塔图像创建后,mip0的初始布局是
VK_IMAGE_LAYOUT_UNDEFINED,但第一次循环中要把它作为VK_DESCRIPTOR_TYPE_STORAGE_IMAGE写入,要求布局为GENERAL,代码里没有做这次初始布局转换。 - 屏障未指定mip层级:循环内的
reduceBarrier没有设置subresourceRange的baseMipLevel和levelCount,默认会作用于图像所有mip层级,导致同步逻辑不准确,也无法正确追踪单个mip的布局状态。
修复方案
1. 添加深度金字塔初始布局转换
在绑定计算管线后、进入循环前,添加屏障将所有mip层级从UNDEFINED转为GENERAL:
// 初始化深度金字塔所有mip布局为GENERAL VkImageMemoryBarrier pyramidInitBarrier = vkinit::image_barrier(_depthPyramid._image, VK_ACCESS_NONE_KHR, VK_ACCESS_SHADER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_ASPECT_COLOR_BIT); pyramidInitBarrier.subresourceRange.baseMipLevel = 0; pyramidInitBarrier.subresourceRange.levelCount = depthPyramidLevels; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &pyramidInitBarrier);
2. 修改循环内的屏障,指定mip层级
把循环内的reduceBarrier修改为仅作用于当前处理的mip层级:
VkImageMemoryBarrier reduceBarrier = vkinit::image_barrier(_depthPyramid._image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_ASPECT_COLOR_BIT); // 指定当前mip层级,确保屏障只影响当前处理的子资源 reduceBarrier.subresourceRange.baseMipLevel = i; reduceBarrier.subresourceRange.levelCount = 1; vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, 0, 0, 0, 1, &reduceBarrier);
这两处修改后,验证层的布局错误应该会消失,同时同步逻辑也更准确。
内容的提问来源于stack exchange,提问作者Ciborg
相关产品推荐
相关产品推荐

