MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VKComputePipeline.cpp
Go to the documentation of this file.
2
4
5namespace MayaFlux::Core {
6
7// ============================================================================
8// Lifecycle
9// ============================================================================
10
12{
13 if (m_pipeline || m_layout) {
15 "VKComputePipeline destroyed without cleanup() - potential leak");
16 }
17}
18
20 : m_pipeline(other.m_pipeline)
21 , m_layout(other.m_layout)
22 , m_workgroup_size(other.m_workgroup_size)
23{
24 other.m_pipeline = nullptr;
25 other.m_layout = nullptr;
26}
27
29{
30 if (this != &other) {
31 if (m_pipeline || m_layout) {
33 "VKComputePipeline move-assigned without cleanup() - potential leak");
34 }
35
36 m_pipeline = other.m_pipeline;
37 m_layout = other.m_layout;
38 m_workgroup_size = other.m_workgroup_size;
39
40 other.m_pipeline = nullptr;
41 other.m_layout = nullptr;
42 }
43 return *this;
44}
45
46void VKComputePipeline::cleanup(vk::Device device)
47{
48 if (m_pipeline) {
49 device.destroyPipeline(m_pipeline);
50 m_pipeline = nullptr;
52 "Compute pipeline destroyed");
53 }
54
55 if (m_layout) {
56 device.destroyPipelineLayout(m_layout);
57 m_layout = nullptr;
59 "Pipeline layout destroyed");
60 }
61
62 m_workgroup_size.reset();
63}
64
65// ============================================================================
66// Pipeline Creation
67// ============================================================================
68
69bool VKComputePipeline::create(vk::Device device, const ComputePipelineConfig& config)
70{
71 if (!config.shader) {
73 "Cannot create compute pipeline without shader");
74 return false;
75 }
76
77 if (!config.shader->is_valid()) {
79 "Cannot create compute pipeline with invalid shader module");
80 return false;
81 }
82
83 if (config.shader->get_stage() != vk::ShaderStageFlagBits::eCompute) {
85 "Shader is not a compute shader (stage: {})",
86 vk::to_string(config.shader->get_stage()));
87 return false;
88 }
89
90 m_layout = create_pipeline_layout(device, config);
91 if (!m_layout) {
93 "Failed to create pipeline layout");
94 return false;
95 }
96
97 auto shader_stage = config.shader->get_stage_create_info();
98
99 vk::ComputePipelineCreateInfo pipeline_info;
100 pipeline_info.stage = shader_stage;
101 pipeline_info.layout = m_layout;
102 pipeline_info.basePipelineHandle = nullptr;
103 pipeline_info.basePipelineIndex = -1;
104
105 try {
106 auto result = device.createComputePipeline(config.cache, pipeline_info);
107 if (result.result != vk::Result::eSuccess) {
109 "Failed to create compute pipeline: {}",
110 vk::to_string(result.result));
111 device.destroyPipelineLayout(m_layout);
112 m_layout = nullptr;
113 return false;
114 }
115 m_pipeline = result.value;
116 } catch (const vk::SystemError& e) {
118 "Failed to create compute pipeline: {}", e.what());
119 device.destroyPipelineLayout(m_layout);
120 m_layout = nullptr;
121 return false;
122 }
123
124 const auto& reflection = config.shader->get_reflection();
125 m_workgroup_size = reflection.workgroup_size;
126
127 if (m_workgroup_size) {
129 "Compute pipeline created (workgroup: {}x{}x{}, {} descriptor sets, {} push constants)",
131 config.set_layouts.size(), config.push_constants.size());
132 } else {
134 "Compute pipeline created ({} descriptor sets, {} push constants)",
135 config.set_layouts.size(), config.push_constants.size());
136 }
137
138 return true;
139}
140
142 vk::Device device,
143 const ComputePipelineConfig& config)
144{
145 std::vector<vk::PushConstantRange> vk_push_constants;
146 vk_push_constants.reserve(config.push_constants.size());
147
148 for (const auto& pc : config.push_constants) {
149 vk::PushConstantRange range;
150 range.stageFlags = pc.stage_flags;
151 range.offset = pc.offset;
152 range.size = pc.size;
153 vk_push_constants.push_back(range);
154 }
155
156 vk::PipelineLayoutCreateInfo layout_info;
157 layout_info.setLayoutCount = static_cast<uint32_t>(config.set_layouts.size());
158 layout_info.pSetLayouts = config.set_layouts.empty() ? nullptr : config.set_layouts.data();
159 layout_info.pushConstantRangeCount = static_cast<uint32_t>(vk_push_constants.size());
160 layout_info.pPushConstantRanges = vk_push_constants.empty() ? nullptr : vk_push_constants.data();
161
162 vk::PipelineLayout layout;
163 try {
164 layout = device.createPipelineLayout(layout_info);
165 } catch (const vk::SystemError& e) {
167 "Failed to create pipeline layout: {}", e.what());
168 return nullptr;
169 }
170
172 "Pipeline layout created ({} sets, {} push constant ranges)",
173 config.set_layouts.size(), config.push_constants.size());
174
175 return layout;
176}
177
178// ============================================================================
179// Pipeline Binding
180// ============================================================================
181
182void VKComputePipeline::bind(vk::CommandBuffer cmd) const
183{
184 if (!m_pipeline) {
186 "Cannot bind invalid compute pipeline");
187 return;
188 }
189
190 cmd.bindPipeline(vk::PipelineBindPoint::eCompute, m_pipeline);
191}
192
194 vk::CommandBuffer cmd,
195 const std::vector<vk::DescriptorSet>& descriptor_sets,
196 uint32_t first_set,
197 const std::vector<uint32_t>& dynamic_offsets) const
198{
199 if (!m_layout) {
201 "Cannot bind descriptor sets without pipeline layout");
202 return;
203 }
204
205 if (descriptor_sets.empty()) {
207 "Binding empty descriptor sets");
208 return;
209 }
210
211 for (size_t i = 0; i < descriptor_sets.size(); ++i) {
212 if (!descriptor_sets[i]) {
214 "Descriptor set at index {} is null", first_set + i);
215 return;
216 }
217 }
218
219 cmd.bindDescriptorSets(
220 vk::PipelineBindPoint::eCompute,
221 m_layout,
222 first_set,
223 static_cast<uint32_t>(descriptor_sets.size()),
224 descriptor_sets.data(),
225 static_cast<uint32_t>(dynamic_offsets.size()),
226 dynamic_offsets.empty() ? nullptr : dynamic_offsets.data());
227}
228
230 vk::CommandBuffer cmd,
231 vk::ShaderStageFlags stage_flags,
232 uint32_t offset,
233 uint32_t size,
234 const void* data) const
235{
236 if (!m_layout) {
238 "Cannot push constants without pipeline layout");
239 return;
240 }
241
242 if (!data) {
244 "Cannot push null data");
245 return;
246 }
247
248 if (size == 0) {
250 "Pushing zero-sized constant data");
251 return;
252 }
253
254 cmd.pushConstants(m_layout, stage_flags, offset, size, data);
255}
256
257// ============================================================================
258// Dispatch
259// ============================================================================
260
262 vk::CommandBuffer cmd,
263 uint32_t group_count_x,
264 uint32_t group_count_y,
265 uint32_t group_count_z) const
266{
267 if (!m_pipeline) {
269 "Cannot dispatch invalid compute pipeline");
270 return;
271 }
272
273 if (group_count_x == 0 || group_count_y == 0 || group_count_z == 0) {
275 "Dispatching with zero workgroups ({}x{}x{})",
276 group_count_x, group_count_y, group_count_z);
277 return;
278 }
279
280 cmd.dispatch(group_count_x, group_count_y, group_count_z);
281}
282
284 vk::CommandBuffer cmd,
285 uint32_t element_count,
286 uint32_t local_size_x) const
287{
288 if (local_size_x == 0) {
290 "Invalid workgroup size: {}", local_size_x);
291 return;
292 }
293
294 uint32_t workgroups = calculate_workgroups(element_count, local_size_x);
295 dispatch(cmd, workgroups, 1, 1);
296}
297
299 vk::CommandBuffer cmd,
300 uint32_t width_elements,
301 uint32_t height_elements,
302 uint32_t local_size_x,
303 uint32_t local_size_y) const
304{
305 if (local_size_x == 0 || local_size_y == 0) {
307 "Invalid workgroup size: {}x{}", local_size_x, local_size_y);
308 return;
309 }
310
311 uint32_t workgroups_x = calculate_workgroups(width_elements, local_size_x);
312 uint32_t workgroups_y = calculate_workgroups(height_elements, local_size_y);
313 dispatch(cmd, workgroups_x, workgroups_y, 1);
314}
315
317 vk::CommandBuffer cmd,
318 uint32_t width_elements,
319 uint32_t height_elements,
320 uint32_t depth_elements,
321 uint32_t local_size_x,
322 uint32_t local_size_y,
323 uint32_t local_size_z) const
324{
325 if (local_size_x == 0 || local_size_y == 0 || local_size_z == 0) {
327 "Invalid workgroup size: {}x{}x{}", local_size_x, local_size_y, local_size_z);
328 return;
329 }
330
331 uint32_t workgroups_x = calculate_workgroups(width_elements, local_size_x);
332 uint32_t workgroups_y = calculate_workgroups(height_elements, local_size_y);
333 uint32_t workgroups_z = calculate_workgroups(depth_elements, local_size_z);
334 dispatch(cmd, workgroups_x, workgroups_y, workgroups_z);
335}
336
337// ============================================================================
338// Utility
339// ============================================================================
340
341std::optional<std::array<uint32_t, 3>> VKComputePipeline::get_workgroup_size() const
342{
343 return m_workgroup_size;
344}
345
346uint32_t VKComputePipeline::calculate_workgroups(uint32_t element_count, uint32_t workgroup_size)
347{
348 if (workgroup_size == 0) {
349 return 0;
350 }
351
352 return (element_count + workgroup_size - 1) / workgroup_size;
353}
354
356 vk::Device device,
357 const ComputePipelineConfig& config,
358 const std::unordered_map<uint32_t, uint32_t>& specialization_data)
359{
360 if (!config.shader) {
362 "Cannot create compute pipeline without shader");
363 return false;
364 }
365
366 config.shader->set_specialization_constants(specialization_data);
367 return create(device, config);
368}
369
371 uint32_t element_count,
372 uint32_t workgroup_size)
373{
374 return { calculate_workgroups(element_count, workgroup_size), 1, 1 };
375}
376
378 uint32_t width, uint32_t height,
379 uint32_t workgroup_x, uint32_t workgroup_y)
380{
381 return {
382 calculate_workgroups(width, workgroup_x),
383 calculate_workgroups(height, workgroup_y),
384 1
385 };
386}
387
389 uint32_t width, uint32_t height, uint32_t depth,
390 uint32_t workgroup_x, uint32_t workgroup_y, uint32_t workgroup_z)
391{
392 return {
393 calculate_workgroups(width, workgroup_x),
394 calculate_workgroups(height, workgroup_y),
395 calculate_workgroups(depth, workgroup_z)
396 };
397}
398
400 vk::CommandBuffer cmd,
401 vk::Buffer buffer,
402 vk::DeviceSize offset)
403{
404 if (!m_pipeline) {
406 "Cannot dispatch invalid compute pipeline");
407 return;
408 }
409
410 if (!buffer) {
412 "Cannot dispatch with null indirect buffer");
413 return;
414 }
415
416 cmd.dispatchIndirect(buffer, offset);
417}
418
420{
421 if (!m_shader) {
423 "Cannot get shader reflection - no shader attached");
424 static ShaderReflection empty;
425 return empty;
426 }
427 return m_shader->get_reflection();
428}
429
430} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
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.
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.
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
void push_constants(vk::CommandBuffer cmd, vk::ShaderStageFlags stage_flags, uint32_t offset, uint32_t size, const void *data) const
Update push constants.
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.
vk::PipelineLayout create_pipeline_layout(vk::Device device, const ComputePipelineConfig &config)
Create pipeline layout from configuration.
Wrapper for Vulkan compute pipeline with simplified interface.
@ GraphicsBackend
Graphics/visual rendering backend (Vulkan, OpenGL)
@ Core
Core engine, backend, subsystems.
std::vector< vk::DescriptorSetLayout > set_layouts
Descriptor layouts.
std::shared_ptr< VKShaderModule > shader
Compute shader.
std::vector< PushConstantRange > push_constants
Push constant ranges.
Configuration for creating a compute pipeline.
Metadata extracted from shader module.