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