MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ShaderFoundry.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "ShaderUtils.hpp"
4
5namespace MayaFlux::Core {
6class VulkanBackend;
7class VKShaderModule;
8class VKComputePipeline;
9class VKDescriptorManager;
10}
11
13
14class ComputePress;
15class RenderFlow;
16
17/**
18 * @class ShaderFoundry
19 * @brief Portal-level shader compilation and caching
20 *
21 * ShaderFoundry is a thin glue layer that:
22 * - Wraps Core::VKShaderModule for convenient shader creation
23 * - Provides caching to avoid redundant compilation
24 * - Supports hot-reload workflows (watch files, recompile)
25 * - Returns VKShaderModule directly for use in pipelines
26 *
27 * Design Philosophy:
28 * - Manages compilation, NOT execution (that's Pipeline/Compute)
29 * - Returns VKShaderModule directly (no wrapping)
30 * - Simple, focused API aligned with VKShaderModule capabilities
31 * - Integrates with existing Core shader infrastructure
32 *
33 * Consumers:
34 * - VKBufferProcessor subclasses (compute shaders)
35 * - Future Yantra::ComputePipeline (compute shaders)
36 * - Future Yantra::RenderPipeline (graphics shaders)
37 * - Future DataProcessors (image processing shaders)
38 *
39 * Usage:
40 * auto& compiler = Portal::Graphics::ShaderFoundry::instance();
41 *
42 * // Compile from file
43 * auto shader = compiler.compile_from_file("shaders/my_kernel.comp");
44 *
45 * // Compile from string
46 * auto shader = compiler.compile_from_source(glsl_code, ShaderStage::COMPUTE);
47 *
48 * // Use in pipeline
49 * my_buffer_processor->set_shader(shader);
50 * my_compute_pipeline->set_shader(shader);
51 */
52class MAYAFLUX_API ShaderFoundry {
53public:
54 enum class CommandBufferType : uint8_t {
56 COMPUTE,
57 TRANSFER
58 };
59
60 enum class CommandBufferLevel : uint8_t {
61 PRIMARY,
62 SECONDARY
63 };
64
65private:
67 vk::DescriptorSet descriptor_set;
68 };
69
71 vk::CommandBuffer cmd;
73 CommandBufferLevel level { CommandBufferLevel::PRIMARY };
75 vk::QueryPool timestamp_pool;
76 std::unordered_map<std::string, uint32_t> timestamp_queries;
77 };
78
79 struct FenceState {
80 vk::Fence fence;
82 };
83
85 vk::Semaphore semaphore;
86 };
87
88 struct ShaderState {
89 std::shared_ptr<Core::VKShaderModule> module;
90 std::string filepath;
92 std::string entry_point;
93 };
94
95public:
97 {
98 static ShaderFoundry compiler;
99 return compiler;
100 }
101
102 ShaderFoundry(const ShaderFoundry&) = delete;
104 ShaderFoundry(ShaderFoundry&&) noexcept = delete;
105 ShaderFoundry& operator=(ShaderFoundry&&) noexcept = delete;
106
107 /**
108 * @brief Initialize shader compiler
109 * @param backend Shared pointer to VulkanBackend
110 * @param config Compiler configuration
111 * @return True if initialization succeeded
112 *
113 * Must be called before compiling any shaders.
114 */
115 bool initialize(
116 const std::shared_ptr<Core::VulkanBackend>& backend,
117 const ShaderCompilerConfig& config = {});
118
119 /**
120 * @brief Stop active command recording and free command buffers
121 *
122 * Frees all command buffers back to pool and destroys query pools.
123 * Call this BEFORE destroying pipelines/resources that command buffers reference.
124 * Does NOT destroy the command pool itself - that happens in shutdown().
125 */
126 void stop();
127
128 /**
129 * @brief Shutdown and cleanup all ShaderFoundry resources
130 *
131 * Destroys sync objects, descriptor resources, and shader modules.
132 * Must be called AFTER stop() and AFTER pipeline consumers (RenderFlow/ComputePress) shutdown.
133 */
134 void shutdown();
135
136 /**
137 * @brief Check if compiler is initialized
138 */
139 [[nodiscard]] bool is_initialized() const { return m_backend != nullptr; }
140
141 //==========================================================================
142 // Shader Compilation - Primary API
143 //==========================================================================
144
145 /**
146 * @brief Universal shader loader - auto-detects source type
147 * @param content File path, GLSL source string, or SPIR-V path
148 * @param stage Optional stage override (auto-detected if omitted)
149 * @param entry_point Entry point function name (default: "main")
150 * @return ShaderID, or INVALID_SHADER on failure
151 *
152 * Supports:
153 * - GLSL files: .comp, .vert, .frag, .geom, .tesc, .tese
154 * - SPIR-V files: .spv
155 *
156 * Stage auto-detection:
157 * .comp → COMPUTE
158 * .vert → VERTEX
159 * .frag → FRAGMENT
160 * .geom → GEOMETRY
161 * .tesc → TESS_CONTROL
162 * .tese → TESS_EVALUATION
163 *
164 * Examples:
165 * load_shader("shaders/kernel.comp"); // File
166 * load_shader("shaders/kernel.spv", COMPUTE); // SPIR-V
167 * load_shader("#version 450\nvoid main(){}", COMPUTE); // Source
168 */
169 ShaderID load_shader(
170 const std::string& content,
171 std::optional<ShaderStage> stage = std::nullopt,
172 const std::string& entry_point = "main");
173
174 /**
175 * @brief Load shader from explicit ShaderSource descriptor
176 * @param source Complete shader source specification
177 * @return ShaderID, or INVALID_SHADER on failure
178 */
179 ShaderID load_shader(const ShaderSource& source);
180
181 /**
182 * @brief Hot-reload shader (returns new ID)
183 */
184 ShaderID reload_shader(const std::string& filepath);
185
186 /**
187 * @brief Destroy shader (cleanup internal state)
188 */
189 void destroy_shader(ShaderID shader_id);
190
191 /**
192 * @brief Compile shader from ShaderSource descriptor
193 * @param shader_source Shader descriptor (path or source + type + stage)
194 * @return Compiled shader module, or nullptr on failure
195 *
196 * Unified interface that dispatches to appropriate compile method.
197 * Useful for abstracted shader loading pipelines.
198 */
199 std::shared_ptr<Core::VKShaderModule> compile(const ShaderSource& shader_source);
200
201 //==========================================================================
202 // Shader Introspection
203 //==========================================================================
204
205 /**
206 * @brief Get reflection info for compiled shader
207 * @param shader_id ID of compiled shader
208 * @return Reflection information
209 *
210 * Extracted during compilation if enabled in config.
211 * Includes descriptor bindings, push constant ranges, workgroup size, etc.
212 */
213 ShaderReflectionInfo get_shader_reflection(ShaderID shader_id);
214
215 /**
216 * @brief Get shader stage for compiled shader
217 * @param shader_id ID of compiled shader
218 * @return Shader stage (COMPUTE, VERTEX, FRAGMENT, etc.)
219 */
220 ShaderStage get_shader_stage(ShaderID shader_id);
221
222 /**
223 * @brief Get entry point name for compiled shader
224 * @param shader_id ID of compiled shader
225 * @return Entry point function name
226 */
227 std::string get_shader_entry_point(ShaderID shader_id);
228
229 //==========================================================================
230 // Hot-Reload Support
231 //==========================================================================
232
233 /**
234 * @brief Invalidate cache for specific shader
235 * @param cache_key File path or cache key
236 *
237 * Forces next compilation to recompile from source.
238 * Useful for hot-reload workflows.
239 */
240 void invalidate_cache(const std::string& cache_key);
241
242 /**
243 * @brief Invalidate entire shader cache
244 *
245 * Forces all subsequent compilations to recompile.
246 * Does NOT destroy existing shader modules (they remain valid).
247 */
248 void clear_cache();
249
250 /**
251 * @brief Hot-reload a shader from file
252 * @param filepath Path to shader file
253 * @return Recompiled shader module, or nullptr on failure
254 *
255 * Convenience method: invalidate_cache() + compile_from_file().
256 * Returns new shader module; consumers must update references.
257 */
258 std::shared_ptr<Core::VKShaderModule> hot_reload(const std::string& filepath);
259
260 //==========================================================================
261 // Configuration
262 //==========================================================================
263
264 /**
265 * @brief Update compiler configuration
266 * @param config New configuration
267 *
268 * Affects future compilations.
269 * Does NOT recompile existing shaders.
270 */
271 void set_config(const ShaderCompilerConfig& config);
272
273 /**
274 * @brief Get current compiler configuration
275 */
276 [[nodiscard]] const ShaderCompilerConfig& get_config() const { return m_config; }
277
278 /**
279 * @brief Add include directory for shader compilation
280 * @param directory Path to directory containing shader includes
281 *
282 * Used for #include resolution in GLSL files.
283 */
284 void add_include_directory(const std::string& directory);
285
286 /**
287 * @brief Add preprocessor define for shader compilation
288 * @param name Macro name
289 * @param value Macro value (optional)
290 *
291 * Example: define("DEBUG", "1") → #define DEBUG 1
292 */
293 void add_define(const std::string& name, const std::string& value = "");
294
295 //==========================================================================
296 // Introspection
297 //==========================================================================
298
299 /**
300 * @brief Check if shader is cached
301 * @param cache_key File path or cache key
302 */
303 [[nodiscard]] bool is_cached(const std::string& cache_key) const;
304
305 /**
306 * @brief Get all cached shader keys
307 */
308 [[nodiscard]] std::vector<std::string> get_cached_keys() const;
309
310 /**
311 * @brief Get number of cached shaders
312 */
313 [[nodiscard]] size_t get_cache_size() const { return m_shader_cache.size(); }
314
315 //==========================================================================
316 // Descriptor Set Management - ShaderFoundry allocates and tracks
317 //==========================================================================
318
319 /**
320 * @brief Allocate descriptor set for a pipeline
321 * @param pipeline_id Which pipeline this is for
322 * @param set_index Which descriptor set (0, 1, 2...)
323 * @return Descriptor set ID
324 */
325 DescriptorSetID allocate_descriptor_set(vk::DescriptorSetLayout layout);
326
327 /**
328 * @brief Update descriptor set with buffer binding
329 * @param descriptor_set_id ID of descriptor set to update
330 * @param binding Binding index within the descriptor set
331 * @param type Descriptor type (e.g., eStorageBuffer, eUniformBuffer)
332 * @param buffer Vulkan buffer to bind
333 * @param offset Offset within the buffer
334 * @param size Size of the buffer region
335 */
336 void update_descriptor_buffer(
337 DescriptorSetID descriptor_set_id,
338 uint32_t binding,
339 vk::DescriptorType type,
340 vk::Buffer buffer,
341 size_t offset,
342 size_t size);
343
344 /**
345 * @brief Update descriptor set with image binding
346 * @param descriptor_set_id ID of descriptor set to update
347 * @param binding Binding index within the descriptor set
348 * @param image_view Vulkan image view to bind
349 * @param sampler Vulkan sampler to bind
350 * @param layout Image layout (default: eShaderReadOnlyOptimal)
351 */
352 void update_descriptor_image(
353 DescriptorSetID descriptor_set_id,
354 uint32_t binding,
355 vk::ImageView image_view,
356 vk::Sampler sampler,
357 vk::ImageLayout layout = vk::ImageLayout::eShaderReadOnlyOptimal);
358
359 /**
360 * @brief Update descriptor set with storage image binding
361 * @param descriptor_set_id ID of descriptor set to update
362 * @param binding Binding index within the descriptor set
363 * @param image_view Vulkan image view to bind
364 * @param layout Image layout (default: eGeneral)
365 */
366 void update_descriptor_storage_image(
367 DescriptorSetID descriptor_set_id,
368 uint32_t binding,
369 vk::ImageView image_view,
370 vk::ImageLayout layout = vk::ImageLayout::eGeneral);
371
372 /**
373 * @brief Get Vulkan descriptor set handle from DescriptorSetID
374 * @param descriptor_set_id Descriptor set ID
375 * @return Vulkan descriptor set handle
376 */
377 vk::DescriptorSet get_descriptor_set(DescriptorSetID descriptor_set_id);
378
379 //==========================================================================
380 // Command Recording - ShaderFoundry manages command buffers
381 //==========================================================================
382
383 /**
384 * @brief Begin recording command buffer
385 * @param type Command buffer type (GRAPHICS, COMPUTE, TRANSFER)
386 * @return Command buffer ID
387 */
388 CommandBufferID begin_commands(CommandBufferType type);
389
390 /**
391 * @brief Begin recording a secondary command buffer for dynamic rendering
392 * @param color_format Format of the color attachment (from swapchain)
393 * @return Command buffer ID
394 *
395 * With dynamic rendering, secondary buffers don't need render pass objects.
396 * They only need to know the attachment formats they'll render to.
397 */
398 CommandBufferID begin_secondary_commands(vk::Format color_format);
399
400 /**
401 * @brief Get Vulkan command buffer handle from CommandBufferID
402 * @param cmd_id Command buffer ID
403 */
404 vk::CommandBuffer get_command_buffer(CommandBufferID cmd_id);
405
406 /**
407 * @brief End recording command buffer
408 * @param cmd_id Command buffer ID to end
409 * @return True if successful, false if invalid ID or not active
410 */
411 bool end_commands(CommandBufferID cmd_id);
412
413 /**
414 * @brief Free all allocated command buffers
415 */
416 void free_all_command_buffers();
417
418 //==========================================================================
419 // Memory Barriers and Synchronization
420 //==========================================================================
421
422 /**
423 * @brief Submit command buffer and wait for completion
424 * @param cmd_id Command buffer ID to submit
425 */
426 void submit_and_wait(CommandBufferID cmd_id);
427
428 /**
429 * @brief Submit command buffer asynchronously, returning a fence
430 * @param cmd_id Command buffer ID to submit
431 * @return Fence ID to wait on later
432 */
433 FenceID submit_async(CommandBufferID cmd_id);
434
435 /**
436 * @brief Submit command buffer asynchronously, returning a semaphore
437 * @param cmd_id Command buffer ID to submit
438 * @return Semaphore ID to wait on later
439 */
440 SemaphoreID submit_with_signal(CommandBufferID cmd_id);
441
442 /**
443 * @brief Wait for fence to be signaled
444 * @param fence_id Fence ID to wait on
445 */
446 void wait_for_fence(FenceID fence_id);
447
448 /**
449 * @brief Wait for multiple fences to be signaled
450 * @param fence_ids Vector of fence IDs to wait on
451 */
452 void wait_for_fences(const std::vector<FenceID>& fence_ids);
453
454 /**
455 * @brief Check if fence is signaled
456 * @param fence_id Fence ID to check
457 * @return True if fence is signaled, false otherwise
458 */
459 bool is_fence_signaled(FenceID fence_id);
460
461 /**
462 * @brief Begin command buffer that waits on a semaphore
463 * @param type Command buffer type (GRAPHICS, COMPUTE, TRANSFER)
464 * @param wait_semaphore Semaphore ID to wait on
465 * @param wait_stage Pipeline stage to wait at
466 * @return Command buffer ID
467 */
468 CommandBufferID begin_commands_with_wait(
469 CommandBufferType type,
470 SemaphoreID wait_semaphore,
471 vk::PipelineStageFlags wait_stage);
472
473 /**
474 * @brief Get Vulkan fence handle from FenceID
475 * @param fence_id Fence ID
476 */
477 vk::Semaphore get_semaphore_handle(SemaphoreID semaphore_id);
478
479 /**
480 * @brief Insert buffer memory barrier
481 */
482 void buffer_barrier(
483 CommandBufferID cmd_id,
484 vk::Buffer buffer,
485 vk::AccessFlags src_access,
486 vk::AccessFlags dst_access,
487 vk::PipelineStageFlags src_stage,
488 vk::PipelineStageFlags dst_stage);
489
490 /**
491 * @brief Insert image memory barrier
492 */
493 void image_barrier(
494 CommandBufferID cmd_id,
495 vk::Image image,
496 vk::ImageLayout old_layout,
497 vk::ImageLayout new_layout,
498 vk::AccessFlags src_access,
499 vk::AccessFlags dst_access,
500 vk::PipelineStageFlags src_stage,
501 vk::PipelineStageFlags dst_stage);
502
503 //==========================================================================
504 // Queue Management
505 //==========================================================================
506
507 /**
508 * @brief Set Vulkan queues for command submission
509 */
510 void set_graphics_queue(vk::Queue queue);
511
512 /**
513 * @brief Set Vulkan queues for command submission
514 */
515 void set_compute_queue(vk::Queue queue);
516
517 /**
518 * @brief Set Vulkan queues for command submission
519 */
520 void set_transfer_queue(vk::Queue queue);
521
522 /**
523 * @brief Get Vulkan graphics queue
524 */
525 [[nodiscard]] vk::Queue get_graphics_queue() const;
526
527 /**
528 * @brief Get Vulkan compute queue
529 */
530 [[nodiscard]] vk::Queue get_compute_queue() const;
531
532 /**
533 * @brief Get Vulkan transfer queue
534 */
535 [[nodiscard]] vk::Queue get_transfer_queue() const;
536
537 //==========================================================================
538 // Profiling
539 //==========================================================================
540
541 void begin_timestamp(CommandBufferID cmd_id, const std::string& label = "");
542 void end_timestamp(CommandBufferID cmd_id, const std::string& label = "");
543
545 std::string label;
546 uint64_t duration_ns;
547 bool valid;
548 };
549
550 TimestampResult get_timestamp_result(CommandBufferID cmd_id, const std::string& label);
551
552 //==========================================================================
553 // Utilities
554 //==========================================================================
555
556 /**
557 * @brief Convert Portal ShaderStage to Vulkan ShaderStageFlagBits
558 */
559 static vk::ShaderStageFlagBits to_vulkan_stage(ShaderStage stage);
560
561 /**
562 * @brief Auto-detect shader stage from file extension
563 * @param filepath Path to shader file
564 * @return Detected stage, or nullopt if unknown
565 *
566 * Delegates to VKShaderModule::detect_stage_from_extension().
567 */
568 static std::optional<ShaderStage> detect_stage_from_extension(const std::string& filepath);
569
570private:
571 /**
572 * @enum DetectedSourceType
573 * @brief Internal enum for source type detection
574 */
575 enum class DetectedSourceType : uint8_t {
576 FILE_GLSL,
577 FILE_SPIRV,
578 SOURCE_STRING,
579 UNKNOWN
580 };
581
582 ShaderFoundry() = default;
584
585 std::shared_ptr<Core::VulkanBackend> m_backend;
587
588 std::unordered_map<std::string, std::shared_ptr<Core::VKShaderModule>> m_shader_cache;
589 std::unordered_map<ShaderID, ShaderState> m_shaders;
590 std::unordered_map<std::string, ShaderID> m_shader_filepath_cache;
591
592 std::shared_ptr<Core::VKDescriptorManager> m_global_descriptor_manager;
593 std::unordered_map<DescriptorSetID, DescriptorSetState> m_descriptor_sets;
594
595 std::unordered_map<CommandBufferID, CommandBufferState> m_command_buffers;
596 std::unordered_map<FenceID, FenceState> m_fences;
597 std::unordered_map<SemaphoreID, SemaphoreState> m_semaphores;
598
602
603 std::atomic<uint64_t> m_next_shader_id { 1 };
604 std::atomic<uint64_t> m_next_descriptor_set_id { 1 };
605 std::atomic<uint64_t> m_next_command_id { 1 };
606 std::atomic<uint64_t> m_next_fence_id { 1 };
607 std::atomic<uint64_t> m_next_semaphore_id { 1 };
608
609 DetectedSourceType detect_source_type(const std::string& content) const;
610 std::optional<std::filesystem::path> resolve_shader_path(const std::string& filepath) const;
611 std::string generate_source_cache_key(const std::string& source, ShaderStage stage) const;
612
613 std::shared_ptr<Core::VKShaderModule> create_shader_module();
614 vk::Device get_device() const;
615
616 void cleanup_sync_objects();
617 void cleanup_descriptor_resources();
618 void cleanup_shader_modules();
619
620 //==========================================================================
621 // INTERNAL Shader Compilation Methods
622 //==========================================================================
623
624 std::shared_ptr<Core::VKShaderModule> compile_from_file(
625 const std::string& filepath,
626 std::optional<ShaderStage> stage = std::nullopt,
627 const std::string& entry_point = "main");
628
629 std::shared_ptr<Core::VKShaderModule> compile_from_source(
630 const std::string& source,
631 ShaderStage stage,
632 const std::string& entry_point = "main");
633
634 std::shared_ptr<Core::VKShaderModule> compile_from_source_cached(
635 const std::string& source,
636 ShaderStage stage,
637 const std::string& cache_key,
638 const std::string& entry_point = "main");
639
640 std::shared_ptr<Core::VKShaderModule> compile_from_spirv(
641 const std::string& spirv_path,
642 ShaderStage stage,
643 const std::string& entry_point = "main");
644
645 std::shared_ptr<Core::VKShaderModule> get_vk_shader_module(ShaderID shader_id);
646
647 friend class ComputePress;
648 friend class RenderFlow;
649
650 static bool s_initialized;
651};
652
653/**
654 * @brief Get the global shader compiler instance
655 * @return Reference to singleton shader compiler
656 *
657 * Must call initialize() before first use.
658 * Thread-safe after initialization.
659 */
660inline MAYAFLUX_API ShaderFoundry& get_shader_foundry()
661{
663}
664
665} // namespace MayaFlux::Portal::Graphics
Compute-specific pipeline and dispatch orchestration.
Graphics pipeline orchestration for dynamic rendering.
ShaderFoundry(const ShaderFoundry &)=delete
ShaderFoundry & operator=(const ShaderFoundry &)=delete
size_t get_cache_size() const
Get number of cached shaders.
void set_compute_queue(vk::Queue queue)
Set Vulkan queues for command submission.
DetectedSourceType
Internal enum for source type detection.
std::unordered_map< std::string, ShaderID > m_shader_filepath_cache
std::unordered_map< FenceID, FenceState > m_fences
void set_graphics_queue(vk::Queue queue)
Set Vulkan queues for command submission.
void set_transfer_queue(vk::Queue queue)
Set Vulkan queues for command submission.
ShaderFoundry(ShaderFoundry &&) noexcept=delete
std::unordered_map< std::string, std::shared_ptr< Core::VKShaderModule > > m_shader_cache
std::unordered_map< DescriptorSetID, DescriptorSetState > m_descriptor_sets
std::unordered_map< CommandBufferID, CommandBufferState > m_command_buffers
std::shared_ptr< Core::VKDescriptorManager > m_global_descriptor_manager
bool is_initialized() const
Check if compiler is initialized.
std::shared_ptr< Core::VulkanBackend > m_backend
std::unordered_map< SemaphoreID, SemaphoreState > m_semaphores
const ShaderCompilerConfig & get_config() const
Get current compiler configuration.
std::unordered_map< ShaderID, ShaderState > m_shaders
Portal-level shader compilation and caching.
void initialize()
Definition main.cpp:11
void stop()
Stop all Portal::Graphics operations.
Definition Graphics.cpp:69
void shutdown()
Shutdown Portal::Graphics subsystem.
Definition Graphics.cpp:87
ShaderStage
User-friendly shader stage enum.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
@ GRAPHICS
Standard real-time graphics processing domain.
Definition Domain.hpp:55
Configuration for shader compilation.
std::unordered_map< std::string, uint32_t > timestamp_queries
std::shared_ptr< Core::VKShaderModule > std::string filepath
Extracted reflection information from compiled shader.
Shader source descriptor for compilation.