MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ShaderProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
5
6namespace MayaFlux::Buffers {
7
8/**
9 * @struct ShaderBinding
10 * @brief Describes how a VKBuffer binds to a shader descriptor
11 */
13 uint32_t set = 0; ///< Descriptor set index
14 uint32_t binding = 0; ///< Binding point within set
15 vk::DescriptorType type = vk::DescriptorType::eStorageBuffer;
16 uint32_t count = 1; ///< Array count for array descriptors (default 1)
17
18 ShaderBinding() = default;
19
20 /**
21 * @brief Construct with semantic role — preferred public API.
22 */
24 : set(s)
25 , binding(b)
26 , type(to_vk_descriptor_type(role))
27 , count(c)
28 {
29 }
30
31 /**
32 * @brief Construct with explicit Vulkan type — internal / advanced use only.
33 */
34 ShaderBinding(uint32_t s, uint32_t b, vk::DescriptorType t, uint32_t c = 1)
35 : set(s)
36 , binding(b)
37 , type(t)
38 , count(c)
39 {
40 }
41};
42
43/**
44 * @struct ShaderProcessorConfig
45 * @brief Complete configuration for shader processor
46 */
48 std::string shader_path; ///< Path to shader file
50 std::string entry_point = "main";
51
52 std::unordered_map<std::string, ShaderBinding> bindings;
53
55
56 std::unordered_map<uint32_t, uint32_t> specialization_constants;
57
58 ShaderConfig() = default;
59 ShaderConfig(std::string path)
60 : shader_path(std::move(path))
61 {
62 }
63};
64
65/**
66 * @class ShaderProcessor
67 * @brief Abstract base class for shader-based buffer processing
68 *
69 * ShaderProcessor provides the foundational infrastructure for managing shader resources,
70 * descriptor sets, and buffer bindings. It is designed to be stage-agnostic, serving as
71 * the common parent for specialized processors like ComputeProcessor and RenderProcessor.
72 *
73 * Core Responsibilities:
74 * - **Shader Management:** Loads and manages shader modules via Portal::Graphics::ShaderFoundry.
75 * - **Descriptor Management:** Handles descriptor set allocation, updates, and binding.
76 * - **Buffer Binding:** Maps logical names (e.g., "input", "output") to physical VKBuffers.
77 * - **Constants:** Manages push constants and specialization constants.
78 * - **Hot-Reload:** Supports runtime shader reloading and pipeline invalidation.
79 *
80 * It does NOT define specific pipeline creation or execution logic (e.g., dispatch vs draw),
81 * leaving those details to derived classes (ComputeProcessor, RenderProcessor).
82 *
83 * Quality-of-life features:
84 * - **Data movement hints:** Query buffer usage (input/output/in-place) for automation.
85 * - **Binding introspection:** Validate if required bindings are satisfied.
86 * - **State queries:** Track processing state for chain management.
87 *
88 * Design Philosophy:
89 * - **Inheritance-focused**: Provides the "plumbing" for shader processors without dictating the pipeline type.
90 * - **Buffer-agnostic**: Works with any VKBuffer modality/usage.
91 * - **Flexible binding**: Decouples logical shader parameters from physical buffers.
92 *
93 * Integration:
94 * - Base class for `ComputeProcessor` (Compute Pipelines)
95 * - Base class for `RenderProcessor` (Graphics Pipelines)
96 * - Base class for `NodeBindingsProcessor` (Node-driven parameters)
97 *
98 * Usage (via derived classes):
99 * // Compute example
100 * auto compute = std::make_shared<ComputeProcessor>("shaders/kernel.comp");
101 * compute->bind_buffer("data", buffer);
102 *
103 * // Graphics example
104 * auto render = std::make_shared<RenderProcessor>(config);
105 * render->bind_buffer("vertices", vertex_buffer);
106 */
107class MAYAFLUX_API ShaderProcessor : public VKBufferProcessor {
108public:
109 /**
110 * @brief Get buffer usage characteristics needed for safe data flow
111 *
112 * Returns flags indicating:
113 * - Does compute read from input? (HOST_TO_DEVICE upload needed?)
114 * - Does compute write to output? (DEVICE_TO_HOST readback needed?)
115 *
116 * This lets ComputeProcessingChain auto-determine staging needs.
117 */
118 enum class BufferUsageHint : uint8_t {
119 NONE = 0,
120 INPUT_READ = 1 << 0, ///< Shader reads input
121 OUTPUT_WRITE = 1 << 1, ///< Shader writes output (modifies)
122 BIDIRECTIONAL = INPUT_READ | OUTPUT_WRITE
123 };
124
125 /**
126 * @brief Construct processor with shader path
127 * @param shader_path Path to shader file (e.g., .comp, .vert, .frag, .spv)
128 */
129 explicit ShaderProcessor(const std::string& shader_path);
130
131 /**
132 * @brief Construct processor with full configuration
133 * @param config Complete shader processor configuration
134 */
135 explicit ShaderProcessor(ShaderConfig config);
136
137 ~ShaderProcessor() override;
138
139 //==========================================================================
140 // BufferProcessor Interface
141 //==========================================================================
142
143 void processing_function(const std::shared_ptr<Buffer>& buffer) override;
144 void on_attach(const std::shared_ptr<Buffer>& buffer) override;
145 void on_detach(const std::shared_ptr<Buffer>& buffer) override;
146
147 [[nodiscard]] bool is_compatible_with(const std::shared_ptr<Buffer>& buffer) const override;
148
149 //==========================================================================
150 // Buffer Binding - Multi-buffer Support
151 //==========================================================================
152
153 /**
154 * @brief Bind a VKBuffer to a named shader descriptor
155 * @param descriptor_name Logical name (e.g., "input", "output")
156 * @param buffer VKBuffer to bind
157 *
158 * Registers the buffer for descriptor set binding.
159 * The descriptor_name must match a key in config.bindings.
160 */
161 void bind_buffer(const std::string& descriptor_name, const std::shared_ptr<VKBuffer>& buffer);
162
163 /**
164 * @brief Unbind a buffer from a descriptor
165 * @param descriptor_name Logical name to unbind
166 */
167 void unbind_buffer(const std::string& descriptor_name);
168
169 /**
170 * @brief Get bound buffer for a descriptor name
171 * @param descriptor_name Logical name
172 * @return Bound buffer, or nullptr if not bound
173 */
174 [[nodiscard]] std::shared_ptr<VKBuffer> get_bound_buffer(const std::string& descriptor_name) const;
175
176 /**
177 * @brief Auto-bind buffer based on attachment order
178 * @param buffer Buffer to auto-bind
179 *
180 * First attachment -> "input" or first binding
181 * Second attachment -> "output" or second binding
182 * Useful for simple single-buffer or input/output patterns.
183 */
184 void auto_bind_buffer(const std::shared_ptr<VKBuffer>& buffer);
185
186 //==========================================================================
187 // Shader Management
188 //==========================================================================
189
190 /**
191 * @brief Hot-reload shader from ShaderFoundry
192 * @return True if reload succeeded
193 *
194 * Invalidates cached shader and rebuilds pipeline.
195 * Existing descriptor sets are preserved if compatible.
196 */
197 bool hot_reload_shader();
198
199 /**
200 * @brief Update shader path and reload
201 * @param shader_path New shader path
202 */
203 void set_shader(const std::string& shader_path);
204
205 /**
206 * @brief Get current shader path
207 */
208 [[nodiscard]] const std::string& get_shader_path() const { return m_config.shader_path; }
209
210 //==========================================================================
211 // Push Constants
212 //==========================================================================
213
214 /**
215 * @brief Set push constant size
216 * @param size Size in bytes
217 */
218 void set_push_constant_size(size_t size);
219
220 /**
221 * @brief Set push constant size from type
222 * @tparam T Push constant struct type
223 */
224 template <typename T>
226 {
227 set_push_constant_size(sizeof(T));
228 }
229
230 /**
231 * @brief Update push constant data (type-safe)
232 * @tparam T Push constant struct type
233 * @param data Push constant data
234 *
235 * Data is copied and uploaded during next process() call.
236 */
237 template <typename T>
238 void set_push_constant_data(const T& data);
239
240 /**
241 * @brief Update push constant data (raw bytes)
242 * @param data Pointer to data
243 * @param size Size in bytes
244 */
245 virtual void set_push_constant_data_raw(const void* data, size_t size);
246
247 /**
248 * @brief Get current push constant data
249 */
250 [[nodiscard]] const std::vector<uint8_t>& get_push_constant_data() const { return m_push_constant_data; }
251 [[nodiscard]] std::vector<uint8_t>& get_push_constant_data() { return m_push_constant_data; }
252
253 //==========================================================================
254 // Specialization Constants
255 //==========================================================================
256
257 /**
258 * @brief Set specialization constant
259 * @param constant_id Specialization constant ID
260 * @param value Value to set
261 *
262 * Requires pipeline recreation to take effect.
263 */
264 void set_specialization_constant(uint32_t constant_id, uint32_t value);
265
266 /**
267 * @brief Clear all specialization constants
268 */
269 void clear_specialization_constants();
270
271 //==========================================================================
272 // Configuration
273 //==========================================================================
274
275 /**
276 * @brief Update entire configuration
277 * @param config New configuration
278 *
279 * Triggers pipeline recreation.
280 */
281 void set_config(const ShaderConfig& config);
282
283 /**
284 * @brief Get current configuration
285 */
286 [[nodiscard]] const ShaderConfig& get_config() const { return m_config; }
287
288 /**
289 * @brief Add descriptor binding configuration
290 * @param descriptor_name Logical name
291 * @param binding Shader binding info
292 */
293 void add_binding(const std::string& descriptor_name, const ShaderBinding& binding);
294
295 //==========================================================================
296 // Data movement hints
297 //==========================================================================
298
299 /**
300 * @brief Get buffer usage hint for a descriptor
301 * @param descriptor_name Binding name
302 * @return BufferUsageHint flags
303 */
304 [[nodiscard]] virtual BufferUsageHint get_buffer_usage_hint(const std::string& descriptor_name) const;
305
306 /**
307 * @brief Check if shader modifies a specific buffer in-place
308 * @param descriptor_name Binding name
309 * @return True if shader both reads and writes this buffer
310 */
311 [[nodiscard]] virtual bool is_in_place_operation(const std::string& descriptor_name) const;
312
313 /**
314 * @brief Check if a descriptor binding exists
315 * @param descriptor_name Name of the binding (e.g., "input", "output")
316 * @return True if binding is configured
317 */
318 [[nodiscard]] bool has_binding(const std::string& descriptor_name) const;
319
320 /**
321 * @brief Get all configured descriptor names
322 * @return Vector of binding names
323 *
324 * Useful for introspection: which buffers does this shader expect?
325 */
326 [[nodiscard]] std::vector<std::string> get_binding_names() const;
327
328 /**
329 * @brief Check if all required bindings are satisfied
330 * @return True if all configured bindings have buffers bound
331 */
332 [[nodiscard]] bool are_bindings_complete() const;
333
334 //==========================================================================
335 // State Queries
336 //==========================================================================
337
338 /**
339 * @brief Check if shader is loaded
340 */
341 [[nodiscard]] bool is_shader_loaded() const { return m_shader_id != Portal::Graphics::INVALID_SHADER; }
342
343 /**
344 * @brief Check if descriptors are initialized
345 */
346 [[nodiscard]] bool are_descriptors_ready() const { return !m_descriptor_set_ids.empty(); }
347
348 /**
349 * @brief Get number of bound buffers
350 */
351 [[nodiscard]] size_t get_bound_buffer_count() const { return m_bound_buffers.size(); }
352
353 /**
354 * @brief Get the output buffer after compute dispatch
355 *
356 * Returns the buffer that was last processed (input/output depends on
357 * shader and binding configuration). Used by ComputeProcessingChain
358 * to determine where compute results ended up.
359 *
360 * Typically the buffer passed to processing_function(), but can be
361 * overridden by subclasses if compute modifies different buffers.
362 */
363 [[nodiscard]] virtual std::shared_ptr<VKBuffer> get_output_buffer() const { return m_last_processed_buffer; }
364
365 /**
366 * @brief Check if compute has been executed at least once
367 * @return True if processing_function() has been called
368 */
369 [[nodiscard]] virtual inline bool has_executed() const
370 {
371 return m_last_command_buffer != Portal::Graphics::INVALID_COMMAND_BUFFER;
372 }
373
374protected:
375 //==========================================================================
376 // Overridable Hooks for Specialized Processors
377 //==========================================================================
378
379 /**
380 * @brief Called before shader compilation
381 * @param shader_path Path to shader
382 *
383 * Override to modify shader compilation (e.g., add defines, includes).
384 */
385 virtual void on_before_compile(const std::string& shader_path);
386
387 /**
388 * @brief Called after shader is loaded
389 * @param shader Loaded shader module
390 *
391 * Override to extract reflection data or validate shader.
392 */
393 virtual void on_shader_loaded(Portal::Graphics::ShaderID shader_id);
394
395 /**
396 * @brief Called before pipeline creation
397 * @param config Pipeline configuration
398 *
399 * Override to modify pipeline configuration.
400 */
402
403 /**
404 * @brief Called after pipeline is created
405 * @param pipeline Created pipeline
406 *
407 * Override for post-pipeline setup.
408 */
409 virtual void on_pipeline_created(Portal::Graphics::ComputePipelineID pipeline_id);
410
411 /**
412 * @brief Called before descriptor sets are created
413 *
414 * Override to add custom descriptor bindings.
415 */
416 virtual void on_before_descriptors_create();
417
418 /**
419 * @brief Called after descriptor sets are created
420 *
421 * Override for custom descriptor updates.
422 */
423 virtual void on_descriptors_created();
424
425 /**
426 * @brief Called before each process callback
427 * @param cmd Command buffer
428 * @param buffer Currently processing buffer
429 * @return True to proceed with execution, false to skip
430 *
431 * Override to update push constants or dynamic descriptors.
432 */
433 virtual bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr<VKBuffer>& buffer);
434
435 /**
436 * @brief Called after each process callback
437 * @param cmd Command buffer
438 * @param buffer Currently processed buffer
439 *
440 * Override for post-dispatch synchronization or state updates.
441 */
442 virtual void on_after_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr<VKBuffer>& buffer);
443
444 /**
445 * @brief Resolve logical descriptor set index to actual index
446 * @param set Logical set index from ShaderBinding
447 * @return Resolved set index, or std::nullopt if invalid
448 *
449 * Handles cases where the engine reserves set 0 for global resources.
450 * If m_engine_owns_set_zero is true, logical set 0 maps to no descriptor,
451 * and logical sets are offset by +1 in the actual descriptor sets.
452 */
453 [[nodiscard]] std::optional<uint32_t> resolve_ds_index(uint32_t set) const;
454
455 //==========================================================================
456 // Protected State - Available to Subclasses
457 //==========================================================================
458
460
461 Portal::Graphics::ShaderID m_shader_id = Portal::Graphics::INVALID_SHADER;
462 std::vector<Portal::Graphics::DescriptorSetID> m_descriptor_set_ids;
463 Portal::Graphics::CommandBufferID m_last_command_buffer = Portal::Graphics::INVALID_COMMAND_BUFFER;
464
465 std::unordered_map<std::string, std::shared_ptr<VKBuffer>> m_bound_buffers;
466 std::shared_ptr<VKBuffer> m_last_processed_buffer;
467
468 std::vector<uint8_t> m_push_constant_data;
469
470 bool m_initialized {};
471 bool m_needs_pipeline_rebuild = true;
472 bool m_needs_descriptor_rebuild = true;
473
474 size_t m_auto_bind_index {};
475
476 /**
477 * @brief Whether the engine reserves set=0 for global resources
478 *
479 * Defaults to false. Only RenderProcessor sets this to true in its
480 * constructor. When true, resolve_ds_index() maps logical set=0 to
481 * nullopt (no user descriptor) and offsets all other sets by -1.
482 * Compute subclasses leave this false: their descriptor sets are
483 * numbered from set=0 with no engine reservation.
484 *
485 * A future subclass that needs engine-owned sets must set this
486 * explicitly and be aware of the index offset applied by resolve_ds_index.
487 */
488 bool m_engine_owns_set_zero {};
489
490 virtual void initialize_pipeline(const std::shared_ptr<VKBuffer>& buffer) = 0;
491 virtual void initialize_descriptors(const std::shared_ptr<VKBuffer>& buffer) = 0;
492 virtual void execute_shader(const std::shared_ptr<VKBuffer>& buffer) = 0;
493
494 virtual void update_descriptors(const std::shared_ptr<VKBuffer>& buffer);
495 virtual void cleanup();
496
497private:
498 //==========================================================================
499 // Internal Implementation
500 //==========================================================================
501
502 void initialize_shader();
503};
504
505template <typename T>
507{
508 const auto size = sizeof(T);
509 static_assert(size <= 128, "Push constants typically limited to 128 bytes");
510 if (m_push_constant_data.size() < size) {
512 }
513
514 std::memcpy(m_push_constant_data.data(), &data, size);
515}
516
517} // namespace MayaFlux::Buffers
size_t b
Range size
virtual void initialize_pipeline(const std::shared_ptr< VKBuffer > &buffer)=0
const std::vector< uint8_t > & get_push_constant_data() const
Get current push constant data.
size_t get_bound_buffer_count() const
Get number of bound buffers.
virtual void execute_shader(const std::shared_ptr< VKBuffer > &buffer)=0
std::unordered_map< std::string, std::shared_ptr< VKBuffer > > m_bound_buffers
std::vector< uint8_t > m_push_constant_data
virtual void initialize_descriptors(const std::shared_ptr< VKBuffer > &buffer)=0
std::vector< uint8_t > & get_push_constant_data()
const std::string & get_shader_path() const
Get current shader path.
void set_push_constant_size()
Set push constant size from type.
const ShaderConfig & get_config() const
Get current configuration.
virtual bool has_executed() const
Check if compute has been executed at least once.
void set_push_constant_data(const T &data)
Update push constant data (type-safe)
bool are_descriptors_ready() const
Check if descriptors are initialized.
std::shared_ptr< VKBuffer > m_last_processed_buffer
bool is_shader_loaded() const
Check if shader is loaded.
virtual std::shared_ptr< VKBuffer > get_output_buffer() const
Get the output buffer after compute dispatch.
BufferUsageHint
Get buffer usage characteristics needed for safe data flow.
virtual void on_before_pipeline_create(Portal::Graphics::ComputePipelineID pipeline_id)
Called before pipeline creation.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
Abstract base class for shader-based buffer processing.
ShaderStage
User-friendly shader stage enum.
DescriptorRole
Semantic descriptor type — maps to Vulkan descriptor types internally.
@ STORAGE
Large arrays or buffers the shader may write (SSBO)
uint32_t binding
Binding point within set.
ShaderBinding(uint32_t s, uint32_t b, vk::DescriptorType t, uint32_t c=1)
Construct with explicit Vulkan type — internal / advanced use only.
uint32_t set
Descriptor set index.
ShaderBinding(uint32_t s, uint32_t b, Portal::Graphics::DescriptorRole role=Portal::Graphics::DescriptorRole::STORAGE, uint32_t c=1)
Construct with semantic role — preferred public API.
uint32_t count
Array count for array descriptors (default 1)
Describes how a VKBuffer binds to a shader descriptor.
std::string shader_path
Path to shader file.
std::unordered_map< uint32_t, uint32_t > specialization_constants
std::unordered_map< std::string, ShaderBinding > bindings
Portal::Graphics::ShaderStage stage