MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VKComputePipeline.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "VKShaderModule.hpp"
5
6namespace MayaFlux::Core {
7
8/**
9 * @struct PushConstantRange
10 * @brief Defines a push constant range for pipeline creation
11 *
12 * Push constants are small amounts of data (<128 bytes typically) that can be
13 * updated very efficiently without descriptor sets. Useful for per-dispatch
14 * parameters like iteration counts, scaling factors, etc.
15 */
17 vk::ShaderStageFlags stage_flags; ///< Which shader stages access this
18 uint32_t offset; ///< Offset in push constant block (bytes)
19 uint32_t size; ///< Size of push constant data (bytes)
20
21 PushConstantRange(vk::ShaderStageFlags stages, uint32_t offset_, uint32_t size_)
22 : stage_flags(stages)
23 , offset(offset_)
24 , size(size_)
25 {
26 }
27};
28
29/**
30 * @struct ComputePipelineConfig
31 * @brief Configuration for creating a compute pipeline
32 *
33 * Defines all parameters needed to create a compute pipeline:
34 * - Shader module
35 * - Descriptor set layouts (resource bindings)
36 * - Push constants (small uniform data)
37 * - Specialization constants (compile-time shader parameters)
38 */
40 std::shared_ptr<VKShaderModule> shader; ///< Compute shader
41 std::vector<vk::DescriptorSetLayout> set_layouts; ///< Descriptor layouts
42 std::vector<PushConstantRange> push_constants; ///< Push constant ranges
43
44 vk::PipelineCache cache = nullptr;
45
46 void add_descriptor_set_layout(vk::DescriptorSetLayout layout)
47 {
48 set_layouts.push_back(layout);
49 }
50
51 void add_push_constant(vk::ShaderStageFlags stages, uint32_t size, uint32_t offset = 0)
52 {
53 push_constants.emplace_back(stages, offset, size);
54 }
55};
56
57/**
58 * @class VKComputePipeline
59 * @brief Wrapper for Vulkan compute pipeline with simplified interface
60 *
61 * Responsibilities:
62 * - Create compute pipeline from shader and configuration
63 * - Manage pipeline layout (descriptor sets + push constants)
64 * - Bind pipeline to command buffer
65 * - Bind descriptor sets
66 * - Update push constants
67 * - Dispatch compute workgroups
68 * - Handle pipeline recreation (for hot-reload)
69 *
70 * Does NOT handle:
71 * - Shader compilation (that's VKShaderModule)
72 * - Descriptor allocation (that's VKDescriptorManager)
73 * - Command buffer management (that's VKCommandManager)
74 * - Synchronization (that's the caller's responsibility)
75 *
76 * Design:
77 * - Immutable after creation (recreation required for changes)
78 * - Lightweight wrapper around vk::Pipeline
79 * - Can be used directly or via VKComputeProcessor
80 *
81 * Thread Safety:
82 * - NOT thread-safe - caller must synchronize access
83 * - Safe to use same pipeline on different command buffers (sequentially)
84 */
86public:
87 VKComputePipeline() = default;
89
93 VKComputePipeline& operator=(VKComputePipeline&&) noexcept;
94
95 /**
96 * @brief Create compute pipeline from configuration
97 * @param device Logical device
98 * @param config Pipeline configuration (shader, layouts, push constants)
99 * @return true if creation succeeded
100 *
101 * Creates:
102 * 1. Pipeline layout (from descriptor set layouts + push constants)
103 * 2. Compute pipeline (from shader + layout)
104 *
105 * If config.cache is provided, pipeline creation will be faster on
106 * subsequent runs (cache can be saved/loaded between sessions).
107 *
108 * Example:
109 * ComputePipelineConfig config;
110 * config.shader = &my_shader;
111 * config.add_descriptor_set_layout(layout);
112 * config.add_push_constant(vk::ShaderStageFlagBits::eCompute, 16);
113 *
114 * VKComputePipeline pipeline;
115 * pipeline.create(device, config);
116 */
117 bool create(vk::Device device, const ComputePipelineConfig& config);
118
119 /**
120 * @brief Cleanup pipeline resources
121 * @param device Logical device (must match creation device)
122 *
123 * Destroys pipeline and pipeline layout. Safe to call multiple times.
124 */
125 void cleanup(vk::Device device);
126
127 /**
128 * @brief Get raw Vulkan pipeline handle
129 * @return vk::Pipeline handle
130 */
131 [[nodiscard]] vk::Pipeline get() const { return m_pipeline; }
132
133 /**
134 * @brief Get pipeline layout handle
135 * @return vk::PipelineLayout handle
136 */
137 [[nodiscard]] vk::PipelineLayout get_layout() const { return m_layout; }
138
139 /**
140 * @brief Bind pipeline to command buffer
141 * @param cmd Command buffer
142 *
143 * Makes this pipeline the active compute pipeline for subsequent
144 * dispatch commands. Must be called before bind_descriptor_sets()
145 * or dispatch().
146 *
147 * Example:
148 * pipeline.bind(cmd);
149 * pipeline.bind_descriptor_sets(cmd, {desc_set});
150 * pipeline.dispatch(cmd, 256, 1, 1);
151 */
152 void bind(vk::CommandBuffer cmd) const;
153
154 /**
155 * @brief Bind descriptor sets to pipeline
156 * @param cmd Command buffer
157 * @param descriptor_sets Vector of descriptor sets to bind
158 * @param first_set First set index (default 0 for set=0)
159 * @param dynamic_offsets Optional dynamic offsets for dynamic buffers
160 *
161 * Binds descriptor sets for use by the shader. Set indices must match
162 * the layout(set=X) declarations in the shader.
163 *
164 * Example:
165 * // Shader has: layout(set=0, binding=0) buffer Data { ... };
166 * pipeline.bind_descriptor_sets(cmd, {desc_set_0});
167 *
168 * // Multiple sets: layout(set=0, ...) and layout(set=1, ...)
169 * pipeline.bind_descriptor_sets(cmd, {desc_set_0, desc_set_1});
170 */
172 vk::CommandBuffer cmd,
173 const std::vector<vk::DescriptorSet>& descriptor_sets,
174 uint32_t first_set = 0,
175 const std::vector<uint32_t>& dynamic_offsets = {}) const;
176
177 /**
178 * @brief Update push constants
179 * @param cmd Command buffer
180 * @param stage_flags Shader stages that will access this data
181 * @param offset Offset in push constant block (bytes)
182 * @param size Size of data (bytes)
183 * @param data Pointer to data to copy
184 *
185 * Updates push constant data that will be visible to the shader.
186 * More efficient than descriptor sets for small, frequently-changing data.
187 *
188 * Example:
189 * struct Params { float scale; uint32_t iterations; };
190 * Params params = {2.0f, 100};
191 * pipeline.push_constants(cmd, vk::ShaderStageFlagBits::eCompute,
192 * 0, sizeof(params), &params);
193 */
194 void push_constants(
195 vk::CommandBuffer cmd,
196 vk::ShaderStageFlags stage_flags,
197 uint32_t offset,
198 uint32_t size,
199 const void* data) const;
200
201 /**
202 * @brief Dispatch compute workgroups
203 * @param cmd Command buffer
204 * @param group_count_x Number of workgroups in X dimension
205 * @param group_count_y Number of workgroups in Y dimension
206 * @param group_count_z Number of workgroups in Z dimension
207 *
208 * Executes the compute shader with the specified number of workgroups.
209 * Total invocations = group_count * local_size (from shader).
210 *
211 * Example:
212 * // Shader: layout(local_size_x = 256) in;
213 * // Process 1M elements: 1,000,000 / 256 = 3,907 workgroups
214 * pipeline.dispatch(cmd, 3907, 1, 1);
215 *
216 * Must be called after bind() and bind_descriptor_sets().
217 */
218 void dispatch(
219 vk::CommandBuffer cmd,
220 uint32_t group_count_x,
221 uint32_t group_count_y,
222 uint32_t group_count_z) const;
223
224 /**
225 * @brief Dispatch compute workgroups with automatic calculation
226 * @param cmd Command buffer
227 * @param element_count Total number of elements to process
228 * @param local_size_x Workgroup size (from shader local_size_x)
229 *
230 * Convenience function that calculates workgroup count automatically.
231 * Rounds up to ensure all elements are processed.
232 *
233 * Example:
234 * // Shader: layout(local_size_x = 256) in;
235 * pipeline.dispatch_1d(cmd, 1'000'000, 256);
236 * // Internally: dispatch(cmd, ceil(1M / 256), 1, 1)
237 */
238 void dispatch_1d(
239 vk::CommandBuffer cmd,
240 uint32_t element_count,
241 uint32_t local_size_x) const;
242
243 /**
244 * @brief Dispatch compute workgroups in 2D with automatic calculation
245 * @param cmd Command buffer
246 * @param width_elements Width in elements
247 * @param height_elements Height in elements
248 * @param local_size_x Workgroup width (shader local_size_x)
249 * @param local_size_y Workgroup height (shader local_size_y)
250 *
251 * Convenience for 2D operations (image processing, etc.)
252 *
253 * Example:
254 * // Process 1920x1080 image with 16x16 workgroups
255 * pipeline.dispatch_2d(cmd, 1920, 1080, 16, 16);
256 */
257 void dispatch_2d(
258 vk::CommandBuffer cmd,
259 uint32_t width_elements,
260 uint32_t height_elements,
261 uint32_t local_size_x,
262 uint32_t local_size_y) const;
263
264 /**
265 * @brief Dispatch compute workgroups in 3D with automatic calculation
266 * @param cmd Command buffer
267 * @param width_elements Width in elements
268 * @param height_elements Height in elements
269 * @param depth_elements Depth in elements
270 * @param local_size_x Workgroup width
271 * @param local_size_y Workgroup height
272 * @param local_size_z Workgroup depth
273 */
274 void dispatch_3d(
275 vk::CommandBuffer cmd,
276 uint32_t width_elements,
277 uint32_t height_elements,
278 uint32_t depth_elements,
279 uint32_t local_size_x,
280 uint32_t local_size_y,
281 uint32_t local_size_z) const;
282
283 /**
284 * @brief Get shader workgroup size from reflection
285 * @return Optional array of [x, y, z] workgroup size
286 *
287 * Extracts local_size_x/y/z from shader if reflection was enabled.
288 * Useful for automatic dispatch calculation.
289 *
290 * Example:
291 * auto workgroup = pipeline.get_workgroup_size();
292 * if (workgroup) {
293 * pipeline.dispatch_1d(cmd, element_count, (*workgroup)[0]);
294 * }
295 */
296 [[nodiscard]] std::optional<std::array<uint32_t, 3>> get_workgroup_size() const;
297
298 /**
299 * @brief Create pipeline with specialization constants
300 * @param device Logical device
301 * @param config Pipeline configuration
302 * @param specialization_data Map of constant_id -> value
303 * @return true if creation succeeded
304 *
305 * Specialization constants allow compile-time configuration of shaders.
306 * Example: workgroup size, loop unrolling factors, feature toggles.
307 */
309 vk::Device device,
310 const ComputePipelineConfig& config,
311 const std::unordered_map<uint32_t, uint32_t>& specialization_data);
312
313 /**
314 * @brief Calculate dispatch size from element count
315 * @param element_count Total number of elements to process
316 * @param workgroup_size Workgroup size (from shader reflection)
317 * @return {dispatch_x, dispatch_y, dispatch_z}
318 *
319 * Utility: rounds up to cover all elements.
320 * Example: 1000 elements, workgroup_size 256 -> dispatch(4, 1, 1)
321 */
322 static std::array<uint32_t, 3> calculate_dispatch_1d(
323 uint32_t element_count,
324 uint32_t workgroup_size);
325
326 /**
327 * @brief Calculate 2D dispatch size
328 */
329 static std::array<uint32_t, 3> calculate_dispatch_2d(
330 uint32_t width, uint32_t height,
331 uint32_t workgroup_x, uint32_t workgroup_y);
332
333 /**
334 * @brief Calculate 3D dispatch size
335 */
336 static std::array<uint32_t, 3> calculate_dispatch_3d(
337 uint32_t width, uint32_t height, uint32_t depth,
338 uint32_t workgroup_x, uint32_t workgroup_y, uint32_t workgroup_z);
339
340 /**
341 * @brief Push constants with type safety
342 * @tparam T Push constant struct type
343 * @param cmd Command buffer
344 * @param data Push constant data
345 */
346 template <typename T>
347 void push_constants_typed(vk::CommandBuffer cmd, const T& data)
348 {
349 static_assert(sizeof(T) <= 128, "Push constants typically limited to 128 bytes");
350 cmd.pushConstants(m_layout, vk::ShaderStageFlagBits::eCompute,
351 0, sizeof(T), &data);
352 }
353
354 // NEW: Indirect dispatch support
355 /**
356 * @brief Dispatch compute shader indirectly from GPU buffer
357 * @param cmd Command buffer
358 * @param buffer Buffer containing vk::DispatchIndirectCommand
359 * @param offset Offset in buffer
360 *
361 * Allows GPU to determine dispatch size (e.g., after a compute culling pass).
362 */
363 void dispatch_indirect(vk::CommandBuffer cmd,
364 vk::Buffer buffer,
365 vk::DeviceSize offset = 0);
366
367 // NEW: Pipeline statistics query support
368 /**
369 * @brief Get pipeline statistics (if available)
370 * @return Pipeline creation statistics
371 */
372 // [[nodiscard]] const vk::PipelineStatisticsCreateInfo& get_statistics() const
373 // {
374 // return m_statistics;
375 // }
376
377 /**
378 * @brief Check if pipeline was created successfully
379 */
380 [[nodiscard]] bool is_valid() const
381 {
382 return m_pipeline != nullptr && m_layout != nullptr;
383 }
384
385 // NEW: Get shader reflection info
386 /**
387 * @brief Get shader module reflection data
388 * @return Shader reflection (bindings, push constants, workgroup size)
389 */
390 [[nodiscard]] const ShaderReflection& get_shader_reflection() const;
391
392private:
393 vk::Pipeline m_pipeline = nullptr;
394 vk::PipelineLayout m_layout = nullptr;
395
396 // vk::PipelineStatisticsCreateInfo m_statistics;
397 std::shared_ptr<VKShaderModule> m_shader;
398
399 // Cache shader reflection for workgroup size queries
400 std::optional<std::array<uint32_t, 3>> m_workgroup_size;
401
402 /**
403 * @brief Create pipeline layout from configuration
404 * @param device Logical device
405 * @param config Pipeline configuration
406 * @return Pipeline layout handle, or null on failure
407 */
408 vk::PipelineLayout create_pipeline_layout(
409 vk::Device device,
410 const ComputePipelineConfig& config);
411
412 /**
413 * @brief Calculate number of workgroups needed
414 * @param element_count Total elements to process
415 * @param workgroup_size Elements per workgroup
416 * @return Number of workgroups (rounded up)
417 */
418 static uint32_t calculate_workgroups(uint32_t element_count, uint32_t workgroup_size);
419};
420
421} // namespace MayaFlux::Core
bool create_specialized(vk::Device device, const ComputePipelineConfig &config, const std::unordered_map< uint32_t, uint32_t > &specialization_data)
Create pipeline with specialization constants.
void dispatch_1d(vk::CommandBuffer cmd, uint32_t element_count, uint32_t local_size_x) const
Dispatch compute workgroups with automatic calculation.
void bind(vk::CommandBuffer cmd) const
Bind pipeline to command buffer.
std::optional< std::array< uint32_t, 3 > > m_workgroup_size
void cleanup(vk::Device device)
Cleanup pipeline resources.
const ShaderReflection & get_shader_reflection() const
Get shader module reflection data.
void dispatch_indirect(vk::CommandBuffer cmd, vk::Buffer buffer, vk::DeviceSize offset=0)
Dispatch compute shader indirectly from GPU buffer.
VKComputePipeline(const VKComputePipeline &)=delete
static std::array< uint32_t, 3 > calculate_dispatch_2d(uint32_t width, uint32_t height, uint32_t workgroup_x, uint32_t workgroup_y)
Calculate 2D dispatch size.
static uint32_t calculate_workgroups(uint32_t element_count, uint32_t workgroup_size)
Calculate number of workgroups needed.
void dispatch(vk::CommandBuffer cmd, uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) const
Dispatch compute workgroups.
vk::Pipeline get() const
Get raw Vulkan pipeline handle.
std::shared_ptr< VKShaderModule > m_shader
void dispatch_2d(vk::CommandBuffer cmd, uint32_t width_elements, uint32_t height_elements, uint32_t local_size_x, uint32_t local_size_y) const
Dispatch compute workgroups in 2D with automatic calculation.
static std::array< uint32_t, 3 > calculate_dispatch_1d(uint32_t element_count, uint32_t workgroup_size)
Calculate dispatch size from element count.
std::optional< std::array< uint32_t, 3 > > get_workgroup_size() const
Get shader workgroup size from reflection.
void bind_descriptor_sets(vk::CommandBuffer cmd, const std::vector< vk::DescriptorSet > &descriptor_sets, uint32_t first_set=0, const std::vector< uint32_t > &dynamic_offsets={}) const
Bind descriptor sets to pipeline.
bool create(vk::Device device, const ComputePipelineConfig &config)
Create compute pipeline from configuration.
VKComputePipeline & operator=(const VKComputePipeline &)=delete
vk::PipelineLayout get_layout() const
Get pipeline layout handle.
void push_constants(vk::CommandBuffer cmd, vk::ShaderStageFlags stage_flags, uint32_t offset, uint32_t size, const void *data) const
Update push constants.
bool is_valid() const
Get pipeline statistics (if available)
void dispatch_3d(vk::CommandBuffer cmd, uint32_t width_elements, uint32_t height_elements, uint32_t depth_elements, uint32_t local_size_x, uint32_t local_size_y, uint32_t local_size_z) const
Dispatch compute workgroups in 3D with automatic calculation.
static std::array< uint32_t, 3 > calculate_dispatch_3d(uint32_t width, uint32_t height, uint32_t depth, uint32_t workgroup_x, uint32_t workgroup_y, uint32_t workgroup_z)
Calculate 3D dispatch size.
void push_constants_typed(vk::CommandBuffer cmd, const T &data)
Push constants with type safety.
vk::PipelineLayout create_pipeline_layout(vk::Device device, const ComputePipelineConfig &config)
Create pipeline layout from configuration.
Wrapper for Vulkan compute pipeline with simplified interface.
std::vector< vk::DescriptorSetLayout > set_layouts
Descriptor layouts.
void add_descriptor_set_layout(vk::DescriptorSetLayout layout)
std::shared_ptr< VKShaderModule > shader
Compute shader.
void add_push_constant(vk::ShaderStageFlags stages, uint32_t size, uint32_t offset=0)
std::vector< PushConstantRange > push_constants
Push constant ranges.
Configuration for creating a compute pipeline.
vk::ShaderStageFlags stage_flags
Which shader stages access this.
PushConstantRange(vk::ShaderStageFlags stages, uint32_t offset_, uint32_t size_)
uint32_t size
Size of push constant data (bytes)
uint32_t offset
Offset in push constant block (bytes)
Defines a push constant range for pipeline creation.
Metadata extracted from shader module.