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