Vulkan存储图像作为描述符传递的语法与报错解决
问题概述
- 已创建附带对应图像视图的交换链,目标是将交换链图像传入计算着色器逐像素修改后完成呈现,创建对应描述符集后触发运行时错误。
现有实现代码
主机端计算管线创建代码(基于vulkan.hpp)
auto computeShaderCode = readFile("Square.spv"); vk::ShaderModuleCreateInfo shaderModuleCreateInfo { {}, computeShaderCode.size(), reinterpret_cast<const uint32_t*>(computeShaderCode.data()) }; vk::ShaderModule shaderModule = device.createShaderModule(shaderModuleCreateInfo); // Create descriptor set layout std::vector<vk::DescriptorSetLayoutBinding> descriptorSetLayoutBindings; uint32_t i = 0; for (auto image : swapchainImages) { vk::DescriptorSetLayoutBinding swapchainImageDescriptorLayoutBinding { i, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute }; i++; descriptorSetLayoutBindings.push_back(swapchainImageDescriptorLayoutBinding); }; vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo( {}, descriptorSetLayoutBindings ); vk::DescriptorSetLayout descriptorSetLayout = device.createDescriptorSetLayout(descriptorSetLayoutCreateInfo); // Create pipeline vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = { {}, descriptorSetLayout }; vk::PipelineLayout pipelineLayout = device.createPipelineLayout(pipelineLayoutCreateInfo); vk::PipelineCache pipelineCache = device.createPipelineCache({}); vk::PipelineShaderStageCreateInfo pipelineShaderStageCreateInfo { {}, vk::ShaderStageFlagBits::eCompute, shaderModule, "main" }; vk::ComputePipelineCreateInfo computePipelineCreateInfo { {}, pipelineShaderStageCreateInfo, pipelineLayout }; computePipeline = device.createComputePipeline(pipelineCache, computePipelineCreateInfo).value; // Create descriptor pool vk::DescriptorPoolSize descriptorPoolSize { vk::DescriptorType::eStorageImage, 2 }; vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo { {}, 1, descriptorPoolSize }; vk::DescriptorPool descriptorPool = device.createDescriptorPool(descriptorPoolCreateInfo); // Allocate descriptor set vk::DescriptorSetAllocateInfo descriptorSetAllocateInfo { descriptorPool, 1, &descriptorSetLayout }; const std::vector<vk::DescriptorSet> descriptorSets = device.allocateDescriptorSets(descriptorSetAllocateInfo); vk::DescriptorSet descriptorSet = descriptorSets.front(); std::vector<vk::DescriptorImageInfo> imageInfos; std::vector<vk::WriteDescriptorSet> writeDescriptorSets; i = 0; for (auto image : swapchainImages) { vk::DescriptorImageInfo imageInfo { VK_NULL_HANDLE, swapchainImageViews[i] }; writeDescriptorSets.push_back({ descriptorSet, i, (uint32_t) 0, (uint32_t) 1, vk::DescriptorType::eStorageImage, &imageInfo }); i++; } device.updateDescriptorSets(writeDescriptorSets, {});
现有计算着色器代码
#version 450 layout (local_size_x = 1) in; layout(set = 0, binding = 0) buffer InputBuffer{ int stuff[]; } inputData; layout(set = 0, binding = 1) buffer OutputBuffer{ int stuff[]; } outputData; void main () { uint gID = gl_GlobalInvocationID.x; outputData.stuff[gID] = inputData.stuff[gID] / inputData.stuff[gID] + 3153; }
运行时报错
VUID-VkComputePipelineCreateInfo-layout-00703(ERROR / SPEC): msgNum: -432263797 - Validation Error: [ VUID-VkComputePipelineCreateInfo-layout-00703 ] Object 0: handle = 0x1ede0385278, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xe63c2d8b | Type mismatch on descriptor slot 0.0 (expected `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC`) but descriptor of type VK_DESCRIPTOR_TYPE_STORAGE_IMAGE The Vulkan spec states: layout must be consistent with the layout of the compute shader specified in stage Objects: 1 [0] 0x1ede0385278, type: 3, name: NULL VUID-VkComputePipelineCreateInfo-layout-00703(ERROR / SPEC): msgNum: -432263797 - Validation Error: [ VUID-VkComputePipelineCreateInfo-layout-00703 ] Object 0: handle = 0x1ede0385278, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xe63c2d8b | Type mismatch on descriptor slot 0.1 (expected `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC`) but descriptor of type VK_DESCRIPTOR_TYPE_STORAGE_IMAGE The Vulkan spec states: layout must be consistent with the layout of the compute shader specified in stage Objects: 1 [0] 0x1ede0385278, type: 3, name: NULL vk::Device::allocateDescriptorSets: ErrorOutOfPoolMemory
错误根因
- 描述符类型不匹配:主机端描述符布局绑定的是
eStorageImage存储图像类型,但着色器中定义的是buffer存储缓冲区类型,二者资源类型完全不对应,触发管线布局校验错误。 - 描述符设计逻辑错误:将所有交换链图像绑定到同一个描述符集的不同binding点,不符合交换链逐帧获取单张图像的使用逻辑,且无意义占用多倍描述符配额。
- 描述符池配额不足:代码写死描述符池仅支持2个存储图像描述符、1个描述符集,当交换链为三缓冲及以上配置时,单个描述符集需要的存储图像数量超过池配额,触发内存不足错误。
- 描述符信息缺失:写入图像描述符时未指定正确的图像布局,存储图像必须使用
VK_IMAGE_LAYOUT_GENERAL布局才能被着色器访问。 - 前置配置缺失:默认创建的交换链图像不支持作为存储图像写入,需要手动添加对应使用标志。
正确实现方案
前置配置修正
创建交换链时,必须在图像usage字段添加vk::ImageUsageFlagBits::eStorage,否则交换链图像不支持被计算着色器写入。
计算着色器修正
操作存储图像需要使用GLSL内置的image2D类型,而非存储缓冲区,参考代码如下:
#version 450 // 工作组大小设为16x16,远高于1x1的执行效率 layout (local_size_x = 16, local_size_y = 16) in; // 绑定单个可写存储图像,格式与交换链格式匹配,此处以RGBA8为例 layout(set = 0, binding = 0, rgba8) uniform writeonly image2D swapchainImage; void main () { ivec2 pixelPos = ivec2(gl_GlobalInvocationID.xy); // 自定义像素写入逻辑,示例为输出纯红色 vec4 fillColor = vec4(1.0f, 0.0f, 0.0f, 1.0f); imageStore(swapchainImage, pixelPos, fillColor); }
主机端描述符配置修正
1. 描述符集布局修正
不需要为每个交换链图像创建单独的binding,仅需1个存储图像绑定即可:
vk::DescriptorSetLayoutBinding imageBinding{ 0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute }; vk::DescriptorSetLayoutCreateInfo layoutCreateInfo{{}, 1, &imageBinding}; vk::DescriptorSetLayout descriptorSetLayout = device.createDescriptorSetLayout(layoutCreateInfo);
2. 描述符池修正
为每个交换链图像分配独立的描述符集,池配额与交换链图像数量对齐:
uint32_t swapchainImgCount = static_cast<uint32_t>(swapchainImages.size()); vk::DescriptorPoolSize poolSize{vk::DescriptorType::eStorageImage, swapchainImgCount}; vk::DescriptorPoolCreateInfo poolCreateInfo{ vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, swapchainImgCount, // 最大分配的描述符集数等于交换链图像数 1, &poolSize }; vk::DescriptorPool descriptorPool = device.createDescriptorPool(poolCreateInfo);
3. 描述符集分配与写入修正
逐图像分配描述符集,写入时指定正确的图像布局:
std::vector<vk::DescriptorSetLayout> layoutList(swapchainImgCount, descriptorSetLayout); vk::DescriptorSetAllocateInfo allocInfo{descriptorPool, swapchainImgCount, layoutList.data()}; std::vector<vk::DescriptorSet> computeDescSets = device.allocateDescriptorSets(allocInfo); std::vector<vk::WriteDescriptorSet> writeOps; std::vector<vk::DescriptorImageInfo> imgInfoList(swapchainImgCount); for(uint32_t idx = 0; idx < swapchainImgCount; idx++){ imgInfoList[idx] = vk::DescriptorImageInfo{ VK_NULL_HANDLE, // 存储图像不需要采样器,传空句柄 swapchainImageViews[idx], vk::ImageLayout::eGeneral // 必须指定通用布局 }; writeOps.emplace_back( computeDescSets[idx], 0, 0, 1, vk::DescriptorType::eStorageImage, &imgInfoList[idx] ); } device.updateDescriptorSets(writeOps, {});
录制命令时的补充操作
- 绑定计算管线后,根据当前获取的交换链图像索引,绑定对应的描述符集。
- 执行计算着色器前,插入图像内存屏障,将当前交换链图像从呈现布局转换为
eGeneral布局;计算完成后再插入屏障,将图像转换回ePresentSrcKHR布局用于呈现。 - 调度计算工作组时,按交换链分辨率计算工作组数量,避免边缘像素遗漏:
cmdBuffer.dispatch( (swapchainExtent.width + 15) / 16, // 与着色器local_size_x对齐 (swapchainExtent.height + 15) / 16, // 与着色器local_size_y对齐 1 );
内容的提问来源于stack exchange,提问作者Logos King
相关产品推荐
相关产品推荐

