Compute#

The compute path combines VulkanCompute, buffers, a command buffer, and a fence. The ComputeSum test is the smallest complete example in the repository.

Define a workload#

A derived compute class provides:

  • GetBindingInfo() for uniform, storage buffer, and image bindings.

  • LoadShaderCode() for the embedded SPIR-V compute shader.

  • An application method that binds the pipeline and descriptors, then calls vkCmdDispatch().

Override Init() when the workload needs to populate descriptor sets after VulkanCompute::Init() creates the pipeline infrastructure.

class AddValues : public core::vulkan::VulkanCompute {
 public:
  AddValues(core::vulkan::VulkanContext* context,
            core::vulkan::VulkanBuffer& input,
            core::vulkan::VulkanBuffer& output)
      : VulkanCompute(context), input_(input), output_(output) {}

  void Init() override {
    VulkanCompute::Init();
    CreateStorageBufferDescriptorSet(0, input_);
    CreateStorageBufferDescriptorSet(1, output_);
    vkUpdateDescriptorSets(context_->logical_device,
                           static_cast<uint32_t>(writes_.size()),
                           writes_.data(), 0, nullptr);
  }

  void Run(VkCommandBuffer command_buffer, uint32_t element_count) {
    vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE,
                      pipeline);
    vkCmdBindDescriptorSets(command_buffer,
                            VK_PIPELINE_BIND_POINT_COMPUTE,
                            pipeline_layout, 0, 1, &descriptor_set_,
                            0, nullptr);
    vkCmdDispatch(command_buffer, (element_count + 255) / 256, 1, 1);
  }

 protected:
  std::vector<core::vulkan::BindingInfo> GetBindingInfo() const override {
    return {
        {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
         VK_SHADER_STAGE_COMPUTE_BIT},
        {1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
         VK_SHADER_STAGE_COMPUTE_BIT},
    };
  }

 private:
  const std::vector<uint32_t>& LoadShaderCode() const override;
  core::vulkan::VulkanBuffer& input_;
  core::vulkan::VulkanBuffer& output_;
};

LoadShaderCode() must return storage whose lifetime extends through pipeline creation. Existing workloads use a function-local static std::vector<uint32_t> containing generated SPIR-V data.

Dispatch#

Choose workgroup counts from the data dimensions and the local size declared in the shader. ComputeSum uses 16 × 16 groups:

const uint32_t group_x = (width + 15) / 16;
const uint32_t group_y = (height + 15) / 16;
vkCmdDispatch(command_buffer, group_x, group_y, 1);

The division rounds up so partial edge workgroups are included. The shader must still bounds-check global invocation coordinates before reading or writing data.

Execute and read back#

pipeline.Run(command_buffer.buffer(), element_count);
fence.Reset();
command_buffer.Submit(fence.fence);
VK_CHECK(vkWaitForFences(
    context.logical_device, 1, &fence.fence, VK_TRUE, UINT64_MAX));

output_buffer.MapData([&result](void* data) {
  memcpy(result.data(), data, result.size() * sizeof(result[0]));
});

Important

Waiting on the fence establishes completion of the GPU work, but shader memory dependencies inside a longer command buffer still require appropriate Vulkan barriers. The current simple tests dispatch once and wait before reading.

Existing compute coverage#

  • ComputeSum demonstrates uniform and storage buffers.

  • ComputeGaussianBlur demonstrates image processing.

  • Roofline measures copy and fused multiply-add workloads.

  • ComputeBarycentricRasterizer uses compute shaders for rasterization.