Vulkan中如何更新推送常量(Push Constants)的数据?
解决方案:更新计算命令缓冲区的推送常量并解决同步问题
核心结论
推送常量的数据是录制时嵌入到命令缓冲区的命令流中的,因此要更新相机矩阵这类动态数据,必须重新录制命令缓冲区。你的同步问题源于错误的操作顺序,只要调整同步逻辑即可解决,完全不需要改用描述集(集成GPU上推送常量的性能优势确实更明显)。
一、修复同步问题:正确的重录流程
你之前的错误在于先提交命令缓冲区,再等待围栏——此时GPU可能还在执行该缓冲区,直接重置会导致未定义行为。正确的顺序应该是:
- 等待当前帧的围栏,确保GPU已完成该帧所有使用该命令缓冲区的工作
- 重置命令缓冲区
- 重新录制(此时会获取最新的相机矩阵)
- 提交更新后的命令缓冲区,并绑定新的同步对象
修改后的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 = ¤t_cmd_buf; compute_submit_info.signalSemaphoreCount = 1; compute_submit_info.pSignalSemaphores = ¤t_semaphore; GPU_.compute_queue_.submit(compute_submit_info, current_fence); // ... 绘制代码:等待current_semaphore后执行绘制命令 }
关键说明
- 推送常量的性能优势:集成GPU上,推送常量直接嵌入命令流,无需访问显存,比描述集的内存访问延迟更低,完全适合你的场景。
- 重录开销:计算命令缓冲区的内容非常简短,重录的CPU开销可以忽略,不会影响性能。
- 同步核心原则:永远不要在GPU仍在使用命令缓冲区时修改/重置它,必须通过围栏确认GPU工作完成后再操作。
内容的提问来源于stack exchange,提问作者user20972572
相关产品推荐
相关产品推荐

