You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:24:40