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