MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ShaderProcessor.cpp
Go to the documentation of this file.
1#include "ShaderProcessor.hpp"
2
4
5namespace MayaFlux::Buffers {
6
7//==============================================================================
8// Construction
9//==============================================================================
10
11ShaderProcessor::ShaderProcessor(const std::string& shader_path, uint32_t workgroup_x)
12 : m_config(shader_path)
13{
14 m_config.dispatch.workgroup_x = workgroup_x;
17}
18
25
30
31//==============================================================================
32// BufferProcessor Interface
33//==============================================================================
34
35void ShaderProcessor::processing_function(std::shared_ptr<Buffer> buffer)
36{
37 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
38 if (!vk_buffer) {
40 "ShaderProcessor can only process VKBuffers");
41 return;
42 }
43
44 if (!m_initialized) {
46 initialize_pipeline(buffer);
48 m_initialized = true;
49 }
50
52 initialize_pipeline(buffer);
54 }
55
59 }
60
62 execute_dispatch(vk_buffer);
63}
64
65void ShaderProcessor::on_attach(std::shared_ptr<Buffer> buffer)
66{
67 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
68 if (!vk_buffer)
69 return;
70
71 if (m_config.bindings.empty()) {
72 auto_bind_buffer(vk_buffer);
73 }
74
76 "ShaderProcessor attached to VKBuffer (size: {} bytes, modality: {})",
77 vk_buffer->get_size_bytes(),
78 static_cast<int>(vk_buffer->get_modality()));
79}
80
81void ShaderProcessor::on_detach(std::shared_ptr<Buffer> buffer)
82{
83 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
84 if (!vk_buffer)
85 return;
86
87 for (auto it = m_bound_buffers.begin(); it != m_bound_buffers.end();) {
88 if (it->second == vk_buffer) {
89 it = m_bound_buffers.erase(it);
90 } else {
91 ++it;
92 }
93 }
95}
96
97bool ShaderProcessor::is_compatible_with(std::shared_ptr<Buffer> buffer) const
98{
99 return std::dynamic_pointer_cast<VKBuffer>(buffer) != nullptr;
100}
101
102//==============================================================================
103// Buffer Binding
104//==============================================================================
105
106void ShaderProcessor::bind_buffer(const std::string& descriptor_name, const std::shared_ptr<VKBuffer>& buffer)
107{
108 if (!buffer) {
110 "Cannot bind null buffer to descriptor '{}'", descriptor_name);
111 return;
112 }
113
114 if (m_config.bindings.find(descriptor_name) == m_config.bindings.end()) {
115 ShaderBinding default_binding;
116 default_binding.set = 0;
117 default_binding.binding = static_cast<uint32_t>(m_config.bindings.size());
118 default_binding.type = vk::DescriptorType::eStorageBuffer;
119 m_config.bindings[descriptor_name] = default_binding;
120
121 // MF_DEBUG(Journal::Component::Buffers, Journal::Context::BufferProcessing,
122 // "Created default binding for '{}': set={}, binding={}",
123 // descriptor_name, default_binding.set, default_binding.binding);
124 }
125
126 m_bound_buffers[descriptor_name] = buffer;
128
129 // MF_DEBUG(Journal::Component::Buffers, Journal::Context::BufferProcessing,
130 // "Bound buffer to descriptor '{}' (size: {} bytes)",
131 // descriptor_name, buffer->get_size_bytes());
132}
133
134void ShaderProcessor::unbind_buffer(const std::string& descriptor_name)
135{
136 auto it = m_bound_buffers.find(descriptor_name);
137 if (it != m_bound_buffers.end()) {
138 m_bound_buffers.erase(it);
140 }
141}
142
143std::shared_ptr<VKBuffer> ShaderProcessor::get_bound_buffer(const std::string& descriptor_name) const
144{
145 auto it = m_bound_buffers.find(descriptor_name);
146 return it != m_bound_buffers.end() ? it->second : nullptr;
147}
148
149void ShaderProcessor::auto_bind_buffer(const std::shared_ptr<VKBuffer>& buffer)
150{
151 std::string descriptor_name;
152 if (m_auto_bind_index == 0) {
153 descriptor_name = "input";
154 } else if (m_auto_bind_index == 1) {
155 descriptor_name = "output";
156 } else {
157 descriptor_name = "buffer_" + std::to_string(m_auto_bind_index);
158 }
159
160 bind_buffer(descriptor_name, buffer);
162}
163
164//==============================================================================
165// Shader Management
166//==============================================================================
167
169{
171 "Hot-reloading shader: {}", m_config.shader_path);
173 auto& foundry = Portal::Graphics::get_shader_foundry();
174 auto new_shader_id = foundry.reload_shader(m_config.shader_path);
175
176 if (new_shader_id == Portal::Graphics::INVALID_SHADER) {
178 "Hot-reload failed for shader: {}", m_config.shader_path);
179 return false;
180 }
181
183 foundry.destroy_shader(m_shader_id);
184 }
185
186 m_shader_id = new_shader_id;
189
191 "Shader hot-reloaded successfully (ID: {})", m_shader_id);
192 return true;
193}
194
195void ShaderProcessor::set_shader(const std::string& shader_path)
196{
197 m_config.shader_path = shader_path;
200}
201
202//==============================================================================
203// Dispatch Configuration
204//==============================================================================
205
206void ShaderProcessor::set_workgroup_size(uint32_t x, uint32_t y, uint32_t z)
207{
211}
212
217
225
227 std::function<std::array<uint32_t, 3>(const std::shared_ptr<VKBuffer>&)> calculator)
228{
230 m_config.dispatch.custom_calculator = std::move(calculator);
231}
232
233//==============================================================================
234// Push Constants
235//==============================================================================
236
243
244void ShaderProcessor::set_push_constant_data_raw(const void* data, size_t size)
245{
246 if (size > m_config.push_constant_size) {
248 "Push constant data size {} exceeds configured size {}",
250 return;
251 }
252
253 m_push_constant_data.resize(size);
254 std::memcpy(m_push_constant_data.data(), data, size);
255}
256
257//==============================================================================
258// Specialization Constants
259//==============================================================================
260
261void ShaderProcessor::set_specialization_constant(uint32_t constant_id, uint32_t value)
262{
263 m_config.specialization_constants[constant_id] = value;
265}
266
272
273//==============================================================================
274// Configuration
275//==============================================================================
276
284
285void ShaderProcessor::add_binding(const std::string& descriptor_name, const ShaderBinding& binding)
286{
287 m_config.bindings[descriptor_name] = binding;
289}
290
291//==========================================================================
292// Data movement Queries
293//==========================================================================
294
295[[nodiscard]] ShaderProcessor::BufferUsageHint ShaderProcessor::get_buffer_usage_hint(const std::string& descriptor_name) const
296{
297 if (descriptor_name == "input")
299 if (descriptor_name == "output")
302}
303
304[[nodiscard]] bool ShaderProcessor::is_in_place_operation(const std::string& descriptor_name) const
305{
306 auto hint = get_buffer_usage_hint(descriptor_name);
307 return hint == BufferUsageHint::BIDIRECTIONAL;
308}
309
310[[nodiscard]] bool ShaderProcessor::has_binding(const std::string& descriptor_name) const
311{
312 return m_config.bindings.find(descriptor_name) != m_config.bindings.end();
313}
314
315[[nodiscard]] std::vector<std::string> ShaderProcessor::get_binding_names() const
316{
317 std::vector<std::string> names;
318 names.reserve(m_config.bindings.size());
319 for (const auto& [name, _] : m_config.bindings) {
320 names.push_back(name);
321 }
322 return names;
323}
324
326{
327 for (const auto& [name, _] : m_config.bindings) {
328 if (m_bound_buffers.find(name) == m_bound_buffers.end()) {
329 return false;
330 }
331 }
332 return true;
333}
334
335//==============================================================================
336// Protected Hooks
337//==============================================================================
338
339void ShaderProcessor::on_before_compile(const std::string&) { }
346
347std::array<uint32_t, 3> ShaderProcessor::calculate_dispatch_size(const std::shared_ptr<VKBuffer>& buffer)
348{
349 using DispatchMode = ShaderDispatchConfig::DispatchMode;
350
351 switch (m_config.dispatch.mode) {
352 case DispatchMode::MANUAL:
354
355 case DispatchMode::ELEMENT_COUNT: {
356 uint64_t element_count = 0;
357 const auto& dimensions = buffer->get_dimensions();
358
359 if (!dimensions.empty()) {
360 element_count = dimensions[0].size;
361 } else {
362 element_count = buffer->get_size_bytes() / sizeof(float);
363 }
364
365 auto groups_x = static_cast<uint32_t>(
366 (element_count + m_config.dispatch.workgroup_x - 1) / m_config.dispatch.workgroup_x);
367 return { groups_x, 1, 1 };
368 }
369
370 case DispatchMode::BUFFER_SIZE: {
371 uint64_t size_bytes = buffer->get_size_bytes();
372 auto groups_x = static_cast<uint32_t>(
374 return { groups_x, 1, 1 };
375 }
376
377 case DispatchMode::CUSTOM:
379 return m_config.dispatch.custom_calculator(buffer);
380 }
381 return { 1, 1, 1 };
382
383 default:
384 return { 1, 1, 1 };
385 }
386}
387
388//==============================================================================
389// Private Implementation
390//==============================================================================
391
410
411void ShaderProcessor::initialize_pipeline(const std::shared_ptr<Buffer>& /*buffer*/)
412{
415 "Cannot create pipeline without shader");
416 return;
417 }
418
419 auto& compute_press = Portal::Graphics::get_compute_press();
420
421 std::map<uint32_t, std::vector<std::pair<std::string, ShaderBinding>>> bindings_by_set;
422 for (const auto& [name, binding] : m_config.bindings) {
423 bindings_by_set[binding.set].emplace_back(name, binding);
424 }
425
426 std::vector<std::vector<Portal::Graphics::DescriptorBindingConfig>> descriptor_sets;
427 for (const auto& [set_index, set_bindings] : bindings_by_set) {
428 std::vector<Portal::Graphics::DescriptorBindingConfig> set_config;
429 for (const auto& [name, binding] : set_bindings) {
430 set_config.emplace_back(binding.set, binding.binding, binding.type);
431 }
432 descriptor_sets.push_back(set_config);
433 }
434
435 m_pipeline_id = compute_press.create_pipeline(
437 descriptor_sets,
439
442 "Failed to create compute pipeline");
443 return;
444 }
445
447
449 "Compute pipeline created (ID: {}, {} descriptor sets, {} bytes push constants)",
450 m_pipeline_id, descriptor_sets.size(), m_config.push_constant_size);
451}
452
454{
457 "Cannot allocate descriptor sets without pipeline");
458 return;
459 }
460
462
463 auto& compute_press = Portal::Graphics::get_compute_press();
464
465 m_descriptor_set_ids = compute_press.allocate_pipeline_descriptors(m_pipeline_id);
466
467 if (m_descriptor_set_ids.empty()) {
469 "Failed to allocate descriptor sets");
470 return;
471 }
472
475
477 "Descriptor sets initialized: {} sets", m_descriptor_set_ids.size());
478}
479
481{
482 if (m_descriptor_set_ids.empty()) {
483 return;
484 }
485
486 auto& foundry = Portal::Graphics::get_shader_foundry();
487
488 for (const auto& [descriptor_name, buffer] : m_bound_buffers) {
489 auto binding_it = m_config.bindings.find(descriptor_name);
490 if (binding_it == m_config.bindings.end()) {
491 continue;
492 }
493
494 const auto& binding = binding_it->second;
495
496 if (binding.set >= m_descriptor_set_ids.size()) {
498 "Invalid descriptor set index {} for binding '{}'",
499 binding.set, descriptor_name);
500 continue;
501 }
502
503 foundry.update_descriptor_buffer(
504 m_descriptor_set_ids[binding.set],
505 binding.binding,
506 binding.type,
507 buffer->get_buffer(),
508 0,
509 buffer->get_size_bytes());
510 }
511}
512
513void ShaderProcessor::execute_dispatch(const std::shared_ptr<VKBuffer>& buffer)
514{
517 "Cannot dispatch without pipeline and descriptors");
518 return;
519 }
520
521 auto& foundry = Portal::Graphics::get_shader_foundry();
522 auto& compute_press = Portal::Graphics::get_compute_press();
523
524 auto cmd_id = foundry.begin_commands(Portal::Graphics::ShaderFoundry::CommandBufferType::COMPUTE);
525
526 m_last_command_buffer = cmd_id;
528
529 compute_press.bind_pipeline(cmd_id, m_pipeline_id);
530
531 compute_press.bind_descriptor_sets(cmd_id, m_pipeline_id, m_descriptor_set_ids);
532
533 if (!m_push_constant_data.empty()) {
534 compute_press.push_constants(
535 cmd_id,
538 m_push_constant_data.size());
539 }
540
541 on_before_dispatch(cmd_id, buffer);
542
543 auto dispatch_size = calculate_dispatch_size(buffer);
544 compute_press.dispatch(cmd_id, dispatch_size[0], dispatch_size[1], dispatch_size[2]);
545
546 on_after_dispatch(cmd_id, buffer);
547
548 foundry.buffer_barrier(
549 cmd_id,
550 buffer->get_buffer(),
551 vk::AccessFlagBits::eShaderWrite,
552 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead,
553 vk::PipelineStageFlagBits::eComputeShader,
554 vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer);
555
556 foundry.submit_and_wait(cmd_id);
557}
558
560{
561 auto& foundry = Portal::Graphics::get_shader_foundry();
562 auto& compute_press = Portal::Graphics::get_compute_press();
563
565 compute_press.destroy_pipeline(m_pipeline_id); // ← ComputePress
567 }
568
570 foundry.destroy_shader(m_shader_id); // ← ShaderFoundry
572 }
573
574 m_descriptor_set_ids.clear();
575 m_bound_buffers.clear();
576 m_initialized = false;
577}
578} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
void execute_dispatch(const std::shared_ptr< VKBuffer > &buffer)
Portal::Graphics::CommandBufferID m_last_command_buffer
bool are_bindings_complete() const
Check if all required bindings are satisfied.
std::unordered_map< std::string, std::shared_ptr< VKBuffer > > m_bound_buffers
virtual void on_before_dispatch(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer)
Called before each dispatch.
void unbind_buffer(const std::string &descriptor_name)
Unbind a buffer from a descriptor.
Portal::Graphics::ShaderID m_shader_id
std::vector< uint8_t > m_push_constant_data
bool is_compatible_with(std::shared_ptr< Buffer > buffer) const override
Checks if this processor can handle the specified buffer type.
virtual void on_descriptors_created()
Called after descriptor sets are created.
std::shared_ptr< VKBuffer > get_bound_buffer(const std::string &descriptor_name) const
Get bound buffer for a descriptor name.
void processing_function(std::shared_ptr< Buffer > buffer) override
The core processing function that must be implemented by derived classes.
void set_workgroup_size(uint32_t x, uint32_t y=1, uint32_t z=1)
Set workgroup size (should match shader local_size)
void set_dispatch_mode(ShaderDispatchConfig::DispatchMode mode)
Set dispatch mode.
virtual void on_before_compile(const std::string &shader_path)
Called before shader compilation.
void auto_bind_buffer(const std::shared_ptr< VKBuffer > &buffer)
Auto-bind buffer based on attachment order.
void set_manual_dispatch(uint32_t x, uint32_t y=1, uint32_t z=1)
Set manual dispatch group counts.
bool has_binding(const std::string &descriptor_name) const
Check if a descriptor binding exists.
void on_detach(std::shared_ptr< Buffer > buffer) override
Called when this processor is detached from a buffer.
void set_push_constant_size()
Set push constant size from type.
Portal::Graphics::ComputePipelineID m_pipeline_id
virtual std::array< uint32_t, 3 > calculate_dispatch_size(const std::shared_ptr< VKBuffer > &buffer)
Calculate dispatch size from buffer.
virtual void on_pipeline_created(Portal::Graphics::ComputePipelineID pipeline_id)
Called after pipeline is created.
virtual bool is_in_place_operation(const std::string &descriptor_name) const
Check if shader modifies a specific buffer in-place.
virtual void on_shader_loaded(Portal::Graphics::ShaderID shader_id)
Called after shader is loaded.
std::shared_ptr< VKBuffer > m_last_processed_buffer
void add_binding(const std::string &descriptor_name, const ShaderBinding &binding)
Add descriptor binding configuration.
bool hot_reload_shader()
Hot-reload shader from ShaderFoundry.
virtual void on_before_descriptors_create()
Called before descriptor sets are created.
void set_shader(const std::string &shader_path)
Update shader path and reload.
virtual void on_after_dispatch(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer)
Called after each dispatch.
void set_config(const ShaderProcessorConfig &config)
Update entire configuration.
virtual BufferUsageHint get_buffer_usage_hint(const std::string &descriptor_name) const
Get buffer usage hint for a descriptor.
BufferUsageHint
Get buffer usage characteristics needed for safe data flow.
@ OUTPUT_WRITE
Shader writes output (modifies)
std::vector< std::string > get_binding_names() const
Get all configured descriptor names.
void set_specialization_constant(uint32_t constant_id, uint32_t value)
Set specialization constant.
void on_attach(std::shared_ptr< Buffer > buffer) override
Called when this processor is attached to a buffer.
void set_custom_dispatch(std::function< std::array< uint32_t, 3 >(const std::shared_ptr< VKBuffer > &)> calculator)
Set custom dispatch calculator.
void bind_buffer(const std::string &descriptor_name, const std::shared_ptr< VKBuffer > &buffer)
Bind a VKBuffer to a named shader descriptor.
virtual void initialize_pipeline(const std::shared_ptr< Buffer > &buffer)
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
void clear_specialization_constants()
Clear all specialization constants.
ShaderProcessor(const std::string &shader_path, uint32_t workgroup_x=256)
Construct processor with shader path.
void set_push_constant_data_raw(const void *data, size_t size)
Update push constant data (raw bytes)
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
constexpr ShaderID INVALID_SHADER
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
constexpr ComputePipelineID INVALID_COMPUTE_PIPELINE
MAYAFLUX_API ComputePress & get_compute_press()
uint32_t binding
Binding point within set.
uint32_t set
Descriptor set index.
Describes how a VKBuffer binds to a shader descriptor.
enum MayaFlux::Buffers::ShaderDispatchConfig::DispatchMode mode
std::function< std::array< uint32_t, 3 >(const std::shared_ptr< VKBuffer > &)> custom_calculator
@ CUSTOM
User-provided calculation function.
uint32_t workgroup_x
Workgroup size X (should match shader)
std::unordered_map< uint32_t, uint32_t > specialization_constants
std::unordered_map< std::string, ShaderBinding > bindings
Portal::Graphics::ShaderStage stage
std::string shader_path
Path to shader file.
Complete configuration for shader processor.