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

如何在Vulkan中实现计算着色器与顶点着色器的缓冲区共享?

Vulkan 共享缓冲:同时作为顶点缓冲区与计算着色器存储缓冲区的实现

你猜的方向是对的,但需要补充VK_BUFFER_USAGE_VERTEX_BUFFER_BIT到usage位掩码里——因为这个缓冲要同时被顶点管线用作顶点数据源,以及计算管线用作可读写的存储缓冲区。最终的核心usage组合为:
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
如果需要从CPU侧初始化顶点数据,还得加上VK_BUFFER_USAGE_TRANSFER_DST_BIT(用于CPU到缓冲的数据复制);若后续需要从该缓冲导出数据,再补充VK_BUFFER_USAGE_TRANSFER_SRC_BIT。

关键注意事项

内存分配时,要选能同时被计算队列和图形队列访问的内存类型:

  • 调试阶段优先用VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,可直接从CPU映射读写,无需手动同步缓存;
  • 性能要求高的场景,切换到VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT(设备本地内存),但需要通过传输队列完成CPU到设备内存的数据复制。

完整实现示例

1. 定义顶点数据结构

#include <glm/glm.hpp>
#include <vector>
#include <stdexcept>

// 顶点数据结构,严格匹配std430布局(计算着色器要求)
struct Vertex {
    glm::vec3 position;
    glm::vec3 color;

    // 顶点绑定描述(供顶点管线使用)
    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription bindingDesc{};
        bindingDesc.binding = 0;
        bindingDesc.stride = sizeof(Vertex);
        bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
        return bindingDesc;
    }

    // 顶点属性描述(供顶点管线使用)
    static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
        std::array<VkVertexInputAttributeDescription, 2> attributeDescs{};
        // 位置属性
        attributeDescs[0].binding = 0;
        attributeDescs[0].location = 0;
        attributeDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescs[0].offset = offsetof(Vertex, position);
        // 颜色属性
        attributeDescs[1].binding = 0;
        attributeDescs[1].location = 1;
        attributeDescs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescs[1].offset = offsetof(Vertex, color);
        return attributeDescs;
    }
};

2. 创建共享缓冲

// 辅助函数:查找符合要求的内存类型索引
uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties) {
    VkPhysicalDeviceMemoryProperties memProperties;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);

    for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
        if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
            return i;
        }
    }

    throw std::runtime_error("Failed to find suitable memory type!");
}

// 创建共享缓冲(返回缓冲对象,内存需自行保存管理)
VkBuffer createSharedBuffer(VkDevice device, VkPhysicalDevice physicalDevice, uint32_t bufferSize, VkDeviceMemory& outMemory) {
    VkBufferCreateInfo bufferInfo{};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = bufferSize;
    bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
    // 若计算/图形队列属同一族,用EXCLUSIVE;不同族则用CONCURRENT并指定队列族索引
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    VkBuffer buffer;
    if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
        throw std::runtime_error("Failed to create shared buffer!");
    }

    // 分配内存
    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

    VkMemoryAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(physicalDevice, memRequirements.memoryTypeBits, 
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

    if (vkAllocateMemory(device, &allocInfo, nullptr, &outMemory) != VK_SUCCESS) {
        vkDestroyBuffer(device, buffer, nullptr);
        throw std::runtime_error("Failed to allocate shared buffer memory!");
    }

    vkBindBufferMemory(device, buffer, outMemory, 0);
    return buffer;
}

3. CPU侧初始化顶点数据

void initVertexData(VkDevice device, VkDeviceMemory bufferMemory, uint32_t vertexCount) {
    void* data;
    vkMapMemory(device, bufferMemory, 0, vertexCount * sizeof(Vertex), 0, &data);
    
    std::vector<Vertex> vertices(vertexCount);
    // 初始化10x10网格顶点
    for (uint32_t i = 0; i < vertexCount; i++) {
        float x = (i % 10 - 5.0f) * 0.2f;
        float y = (i / 10 - 5.0f) * 0.2f;
        vertices[i].position = glm::vec3(x, y, 0.0f);
        vertices[i].color = glm::vec3(1.0f, 0.5f, 0.2f);
    }

    memcpy(data, vertices.data(), vertices.size() * sizeof(Vertex));
    vkUnmapMemory(device, bufferMemory);
}

4. 计算着色器代码(GLSL)

#version 450

layout(local_size_x = 16, local_size_y = 1) in;

// 绑定共享缓冲为存储缓冲区
layout(std430, binding = 0) buffer VertexData {
    vec3 position[];
    vec3 color[];
} vertices;

void main() {
    uint index = gl_GlobalInvocationID.x;
    if (index >= vertices.position.length()) return;

    // 简单Verlet积分模拟(示例)
    vec3 currentPos = vertices.position[index];
    // 模拟重力下落
    vertices.position[index].y -= 0.002f;
    // 底部反弹
    if (vertices.position[index].y < -1.0f) {
        vertices.position[index].y = -1.0f;
        vertices.position[index].y *= -0.8f;
    }
}

5. 配置计算管线描述符集

// 假设已提前创建描述符池和描述符集布局
VkDescriptorSet setupComputeDescriptorSet(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorSetLayout layout, VkBuffer sharedBuffer) {
    VkDescriptorSetAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    allocInfo.descriptorPool = descriptorPool;
    allocInfo.descriptorSetCount = 1;
    allocInfo.pSetLayouts = &layout;

    VkDescriptorSet descriptorSet;
    if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) {
        throw std::runtime_error("Failed to allocate compute descriptor set!");
    }

    // 更新描述符集,绑定共享缓冲为存储缓冲区
    VkDescriptorBufferInfo bufferInfo{};
    bufferInfo.buffer = sharedBuffer;
    bufferInfo.offset = 0;
    bufferInfo.range = VK_WHOLE_SIZE;

    VkWriteDescriptorSet descriptorWrite{};
    descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWrite.dstSet = descriptorSet;
    descriptorWrite.dstBinding = 0;
    descriptorWrite.dstArrayElement = 0;
    descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
    descriptorWrite.descriptorCount = 1;
    descriptorWrite.pBufferInfo = &bufferInfo;

    vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
    return descriptorSet;
}

6. 执行计算+渲染流程

void runComputeAndRender(VkCommandBuffer cmdBuffer, VkPipeline computePipeline, VkPipelineLayout computeLayout, VkDescriptorSet computeDescSet, VkBuffer sharedBuffer, uint32_t vertexCount) {
    // 开始记录命令缓冲区
    VkCommandBufferBeginInfo beginInfo{};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    vkBeginCommandBuffer(cmdBuffer, &beginInfo);

    // 执行计算着色器
    vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline);
    vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computeLayout, 0, 1, &computeDescSet, 0, nullptr);
    // 调度计算任务:按16个顶点一组分组
    vkCmdDispatch(cmdBuffer, (vertexCount + 15) / 16, 1, 1);

    // 内存屏障:确保计算写入完成后,顶点管线才能读取
    VkMemoryBarrier barrier{};
    barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
    barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
    vkCmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);

    // 绑定顶点缓冲并渲染
    VkBuffer vertexBuffers[] = {sharedBuffer};
    VkDeviceSize offsets[] = {0};
    vkCmdBindVertexBuffers(cmdBuffer, 0, 1, vertexBuffers, offsets);
    vkCmdDraw(cmdBuffer, vertexCount, 1, 0, 0);

    vkEndCommandBuffer(cmdBuffer);

    // 提交命令到队列(此处省略队列提交逻辑,你应已有相关实现)
}

内容的提问来源于stack exchange,提问作者user14265619

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 05:01:06