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

Vulkan中如何更新推送常量(Push Constants)的数据?

解决方案:更新计算命令缓冲区的推送常量并解决同步问题

核心结论

推送常量的数据是录制时嵌入到命令缓冲区的命令流中的,因此要更新相机矩阵这类动态数据,必须重新录制命令缓冲区。你的同步问题源于错误的操作顺序,只要调整同步逻辑即可解决,完全不需要改用描述集(集成GPU上推送常量的性能优势确实更明显)。


一、修复同步问题:正确的重录流程

你之前的错误在于先提交命令缓冲区,再等待围栏——此时GPU可能还在执行该缓冲区,直接重置会导致未定义行为。正确的顺序应该是:

  1. 等待当前帧的围栏,确保GPU已完成该帧所有使用该命令缓冲区的工作
  2. 重置命令缓冲区
  3. 重新录制(此时会获取最新的相机矩阵)
  4. 提交更新后的命令缓冲区,并绑定新的同步对象

修改后的draw_frame代码

void draw_frame() {
    // 1. 等待当前帧的围栏,确保之前的GPU工作已完成
    GPU_.logical_device_.waitForFences(GPU_.in_flight_fences[current_frame], true, UINT64_MAX);
    GPU_.logical_device_.resetFences(GPU_.in_flight_fences[current_frame]);

    // 2. 重置计算命令缓冲区(需确保命令池支持重置)
    GPU_.compute_command_buffer_.reset(vk::CommandBufferResetFlagBits::eReleaseResources);

    // 3. 重新录制命令缓冲区,此时会读取最新的相机矩阵
    record_compute_command_buffer();

    // 4. 提交更新后的计算命令缓冲区,添加同步信号量关联后续绘制
    vk::Semaphore compute_finished = GPU_.compute_finished_semaphores[current_frame];
    vk::SubmitInfo compute_submit_info{};
    compute_submit_info.commandBufferCount = 1;
    compute_submit_info.pCommandBuffers = &GPU_.compute_command_buffer_;
    compute_submit_info.signalSemaphoreCount = 1;
    compute_submit_info.pSignalSemaphores = &compute_finished;

    // 将围栏与本次提交绑定,追踪GPU工作状态
    GPU_.compute_queue_.submit(compute_submit_info, GPU_.in_flight_fences[current_frame]);

    // ... 后续绘制代码:等待compute_finished信号量,确保计算完成后再执行绘制
}

确保命令池支持重置

创建计算命令池时,必须添加eResetCommandBuffer标志,否则无法单独重置命令缓冲区:

vk::CommandPoolCreateInfo pool_info{};
pool_info.queueFamilyIndex = compute_queue_family_index;
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
GPU_.compute_command_pool_ = GPU_.logical_device_.createCommandPool(pool_info);

二、进阶优化:多帧缓冲避免冲突

如果使用多帧并行渲染(比如3帧缓冲),建议为每帧分配独立的计算命令缓冲区,避免单缓冲区的重置开销和同步风险:

初始化多计算命令缓冲区

// 初始化时创建3个计算命令缓冲区(与帧缓冲数量一致)
GPU_.compute_command_buffers.resize(3);
vk::CommandBufferAllocateInfo alloc_info{};
alloc_info.commandPool = GPU_.compute_command_pool_;
alloc_info.level = vk::CommandBufferLevel::ePrimary;
alloc_info.commandBufferCount = 3;
GPU_.compute_command_buffers = GPU_.logical_device_.allocateCommandBuffers(alloc_info);

修改录制函数支持指定缓冲区

void record_compute_command_buffer(vk::CommandBuffer cmd_buf) {
    vk::CommandBufferBeginInfo begin_info{};
    // 多帧缓冲下无需eSimultaneousUse,每个缓冲区仅对应一帧的工作
    cmd_buf.begin(begin_info);

    cmd_buf.bindPipeline(vk::PipelineBindPoint::eCompute, GPU_.compute_pipeline_);

    struct push_data {
        alignas(16) glm::mat4 view_matrix = camera_.get_view();
        alignas(16) glm::mat4 projection_matrix = glm::perspective(glm::radians(90.0f), GPU_.aspect_ratio(), 0.1f, 10.0f);
    } push;

    cmd_buf.pushConstants(GPU_.compute_pipeline_layout_, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push), &push);

    cmd_buf.bindDescriptorSets(
        vk::PipelineBindPoint::eCompute, 
        GPU_.compute_pipeline_layout_, 
        0, 
        1, 
        &GPU_.compute_descriptor_sets_[0], 
        0, 
        nullptr
    );
    
    const int workgroup_size = 32;
    const int groupcount = ((models.size()) / workgroup_size) + 1;
    cmd_buf.dispatch(groupcount, 1, 1);

    cmd_buf.end();
}

多帧缓冲版draw_frame

void draw_frame() {
    auto& current_cmd_buf = GPU_.compute_command_buffers[current_frame];
    auto& current_fence = GPU_.in_flight_fences[current_frame];
    auto& current_semaphore = GPU_.compute_finished_semaphores[current_frame];

    GPU_.logical_device_.waitForFences(current_fence, true, UINT64_MAX);
    GPU_.logical_device_.resetFences(current_fence);

    current_cmd_buf.reset();
    record_compute_command_buffer(current_cmd_buf);

    vk::SubmitInfo compute_submit_info{};
    compute_submit_info.commandBufferCount = 1;
    compute_submit_info.pCommandBuffers = &current_cmd_buf;
    compute_submit_info.signalSemaphoreCount = 1;
    compute_submit_info.pSignalSemaphores = &current_semaphore;

    GPU_.compute_queue_.submit(compute_submit_info, current_fence);

    // ... 绘制代码:等待current_semaphore后执行绘制命令
}

关键说明

  1. 推送常量的性能优势:集成GPU上,推送常量直接嵌入命令流,无需访问显存,比描述集的内存访问延迟更低,完全适合你的场景。
  2. 重录开销:计算命令缓冲区的内容非常简短,重录的CPU开销可以忽略,不会影响性能。
  3. 同步核心原则:永远不要在GPU仍在使用命令缓冲区时修改/重置它,必须通过围栏确认GPU工作完成后再操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 09:03:10