MayaFlux 0.4.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 * .mesh → MESH
164 * .task → TASK
165 *
166 * Examples:
167 * load_shader("shaders/kernel.comp"); // File
168 * load_shader("shaders/kernel.spv", COMPUTE); // SPIR-V
169 * load_shader("#version 450\nvoid main(){}", COMPUTE); // Source
170 */
171 ShaderID load_shader(
172 const std::string& content,
173 std::optional<ShaderStage> stage = std::nullopt,
174 const std::string& entry_point = "main");
175
176 /**
177 * @brief Load shader from explicit ShaderSource descriptor
178 * @param source Complete shader source specification
179 * @return ShaderID, or INVALID_SHADER on failure
180 */
181 ShaderID load_shader(const ShaderSource& source);
182
183 /**
184 * @brief Hot-reload shader (returns new ID)
185 */
186 ShaderID reload_shader(const std::string& filepath);
187
188 /**
189 * @brief Destroy shader (cleanup internal state)
190 */
191 void destroy_shader(ShaderID shader_id);
192
193 /**
194 * @brief Compile shader from ShaderSource descriptor
195 * @param shader_source Shader descriptor (path or source + type + stage)
196 * @return Compiled shader module, or nullptr on failure
197 *
198 * Unified interface that dispatches to appropriate compile method.
199 * Useful for abstracted shader loading pipelines.
200 */
201 std::shared_ptr<Core::VKShaderModule> compile(const ShaderSource& shader_source);
202
203 //==========================================================================
204 // Shader Introspection
205 //==========================================================================
206
207 /**
208 * @brief Get reflection info for compiled shader
209 * @param shader_id ID of compiled shader
210 * @return Reflection information
211 *
212 * Extracted during compilation if enabled in config.
213 * Includes descriptor bindings, push constant ranges, workgroup size, etc.
214 */
215 ShaderReflectionInfo get_shader_reflection(ShaderID shader_id);
216
217 /**
218 * @brief Get shader stage for compiled shader
219 * @param shader_id ID of compiled shader
220 * @return Shader stage (COMPUTE, VERTEX, FRAGMENT, etc.)
221 */
222 ShaderStage get_shader_stage(ShaderID shader_id);
223
224 /**
225 * @brief Get entry point name for compiled shader
226 * @param shader_id ID of compiled shader
227 * @return Entry point function name
228 */
229 std::string get_shader_entry_point(ShaderID shader_id);
230
231 //==========================================================================
232 // Hot-Reload Support
233 //==========================================================================
234
235 /**
236 * @brief Invalidate cache for specific shader
237 * @param cache_key File path or cache key
238 *
239 * Forces next compilation to recompile from source.
240 * Useful for hot-reload workflows.
241 */
242 void invalidate_cache(const std::string& cache_key);
243
244 /**
245 * @brief Invalidate entire shader cache
246 *
247 * Forces all subsequent compilations to recompile.
248 * Does NOT destroy existing shader modules (they remain valid).
249 */
250 void clear_cache();
251
252 /**
253 * @brief Hot-reload a shader from file
254 * @param filepath Path to shader file
255 * @return Recompiled shader module, or nullptr on failure
256 *
257 * Convenience method: invalidate_cache() + compile_from_file().
258 * Returns new shader module; consumers must update references.
259 */
260 std::shared_ptr<Core::VKShaderModule> hot_reload(const std::string& filepath);
261
262 //==========================================================================
263 // Configuration
264 //==========================================================================
265
266 /**
267 * @brief Update compiler configuration
268 * @param config New configuration
269 *
270 * Affects future compilations.
271 * Does NOT recompile existing shaders.
272 */
273 void set_config(const ShaderCompilerConfig& config);
274
275 /**
276 * @brief Get current compiler configuration
277 */
278 [[nodiscard]] const ShaderCompilerConfig& get_config() const { return m_config; }
279
280 /**
281 * @brief Add include directory for shader compilation
282 * @param directory Path to directory containing shader includes
283 *
284 * Used for #include resolution in GLSL files.
285 */
286 void add_include_directory(const std::string& directory);
287
288 /**
289 * @brief Add preprocessor define for shader compilation
290 * @param name Macro name
291 * @param value Macro value (optional)
292 *
293 * Example: define("DEBUG", "1") → #define DEBUG 1
294 */
295 void add_define(const std::string& name, const std::string& value = "");
296
297 //==========================================================================
298 // Introspection
299 //==========================================================================
300
301 /**
302 * @brief Check if shader is cached
303 * @param cache_key File path or cache key
304 */
305 [[nodiscard]] bool is_cached(const std::string& cache_key) const;
306
307 /**
308 * @brief Get all cached shader keys
309 */
310 [[nodiscard]] std::vector<std::string> get_cached_keys() const;
311
312 /**
313 * @brief Get number of cached shaders
314 */
315 [[nodiscard]] size_t get_cache_size() const { return m_shader_cache.size(); }
316
317 //==========================================================================
318 // Descriptor Set Management - ShaderFoundry allocates and tracks
319 //==========================================================================
320
321 /**
322 * @brief Allocate descriptor set for a pipeline
323 * @param pipeline_id Which pipeline this is for
324 * @param set_index Which descriptor set (0, 1, 2...)
325 * @return Descriptor set ID
326 */
327 DescriptorSetID allocate_descriptor_set(vk::DescriptorSetLayout layout);
328
329 /**
330 * @brief Update descriptor set with buffer binding
331 * @param descriptor_set_id ID of descriptor set to update
332 * @param binding Binding index within the descriptor set
333 * @param type Descriptor type (e.g., eStorageBuffer, eUniformBuffer)
334 * @param buffer Vulkan buffer to bind
335 * @param offset Offset within the buffer
336 * @param size Size of the buffer region
337 */
338 void update_descriptor_buffer(
339 DescriptorSetID descriptor_set_id,
340 uint32_t binding,
341 vk::DescriptorType type,
342 vk::Buffer buffer,
343 size_t offset,
344 size_t size);
345
346 /**
347 * @brief Update descriptor set with image binding
348 * @param descriptor_set_id ID of descriptor set to update
349 * @param binding Binding index within the descriptor set
350 * @param image_view Vulkan image view to bind
351 * @param sampler Vulkan sampler to bind
352 * @param layout Image layout (default: eShaderReadOnlyOptimal)
353 * @param array_element Array index for array bindings (default: 0)
354 */
355 void update_descriptor_image(
356 DescriptorSetID descriptor_set_id,
357 uint32_t binding,
358 vk::ImageView image_view,
359 vk::Sampler sampler,
360 vk::ImageLayout layout = vk::ImageLayout::eShaderReadOnlyOptimal,
361 uint32_t array_element = 0);
362
363 /**
364 * @brief Update descriptor set with storage image binding
365 * @param descriptor_set_id ID of descriptor set to update
366 * @param binding Binding index within the descriptor set
367 * @param image_view Vulkan image view to bind
368 * @param layout Image layout (default: eGeneral)
369 */
370 void update_descriptor_storage_image(
371 DescriptorSetID descriptor_set_id,
372 uint32_t binding,
373 vk::ImageView image_view,
374 vk::ImageLayout layout = vk::ImageLayout::eGeneral);
375
376 /**
377 * @brief Get Vulkan descriptor set handle from DescriptorSetID
378 * @param descriptor_set_id Descriptor set ID
379 * @return Vulkan descriptor set handle
380 */
381 vk::DescriptorSet get_descriptor_set(DescriptorSetID descriptor_set_id);
382
383 //==========================================================================
384 // Command Recording - ShaderFoundry manages command buffers
385 //==========================================================================
386
387 /**
388 * @brief Begin recording command buffer
389 * @param type Command buffer type (GRAPHICS, COMPUTE, TRANSFER)
390 * @return Command buffer ID
391 */
392 CommandBufferID begin_commands(CommandBufferType type);
393
394 /**
395 * @brief Begin recording a secondary command buffer for dynamic rendering
396 * @param color_format Format of the color attachment (from swapchain)
397 * @return Command buffer ID
398 *
399 * With dynamic rendering, secondary buffers don't need render pass objects.
400 * They only need to know the attachment formats they'll render to.
401 */
402 CommandBufferID begin_secondary_commands(vk::Format color_format);
403
404 /**
405 * @brief Get Vulkan command buffer handle from CommandBufferID
406 * @param cmd_id Command buffer ID
407 */
408 vk::CommandBuffer get_command_buffer(CommandBufferID cmd_id);
409
410 /**
411 * @brief End recording command buffer
412 * @param cmd_id Command buffer ID to end
413 * @return True if successful, false if invalid ID or not active
414 */
415 bool end_commands(CommandBufferID cmd_id);
416
417 /**
418 * @brief Free all allocated command buffers
419 */
420 void free_all_command_buffers();
421
422 //==========================================================================
423 // Memory Barriers and Synchronization
424 //==========================================================================
425
426 /**
427 * @brief Submit command buffer and wait for completion
428 * @param cmd_id Command buffer ID to submit
429 */
430 void submit_and_wait(CommandBufferID cmd_id);
431
432 /**
433 * @brief Submit command buffer asynchronously, returning a fence
434 * @param cmd_id Command buffer ID to submit
435 * @return Fence ID to wait on later
436 */
437 FenceID submit_async(CommandBufferID cmd_id);
438
439 /**
440 * @brief Submit command buffer asynchronously, returning a semaphore
441 * @param cmd_id Command buffer ID to submit
442 * @return Semaphore ID to wait on later
443 */
444 SemaphoreID submit_with_signal(CommandBufferID cmd_id);
445
446 /**
447 * @brief Wait for fence to be signaled
448 * @param fence_id Fence ID to wait on
449 */
450 void wait_for_fence(FenceID fence_id);
451
452 /**
453 * @brief Wait for multiple fences to be signaled
454 * @param fence_ids Vector of fence IDs to wait on
455 */
456 void wait_for_fences(const std::vector<FenceID>& fence_ids);
457
458 /**
459 * @brief Check if fence is signaled
460 * @param fence_id Fence ID to check
461 * @return True if fence is signaled, false otherwise
462 */
463 bool is_fence_signaled(FenceID fence_id);
464
465 /**
466 * @brief Begin command buffer that waits on a semaphore
467 * @param type Command buffer type (GRAPHICS, COMPUTE, TRANSFER)
468 * @param wait_semaphore Semaphore ID to wait on
469 * @param wait_stage Pipeline stage to wait at
470 * @return Command buffer ID
471 */
472 CommandBufferID begin_commands_with_wait(
473 CommandBufferType type,
474 SemaphoreID wait_semaphore,
475 vk::PipelineStageFlags wait_stage);
476
477 /**
478 * @brief Get Vulkan fence handle from FenceID
479 * @param fence_id Fence ID
480 */
481 vk::Semaphore get_semaphore_handle(SemaphoreID semaphore_id);
482
483 /**
484 * @brief Insert buffer memory barrier
485 */
486 void buffer_barrier(
487 CommandBufferID cmd_id,
488 vk::Buffer buffer,
489 vk::AccessFlags src_access,
490 vk::AccessFlags dst_access,
491 vk::PipelineStageFlags src_stage,
492 vk::PipelineStageFlags dst_stage);
493
494 /**
495 * @brief Insert image memory barrier
496 */
497 void image_barrier(
498 CommandBufferID cmd_id,
499 vk::Image image,
500 vk::ImageLayout old_layout,
501 vk::ImageLayout new_layout,
502 vk::AccessFlags src_access,
503 vk::AccessFlags dst_access,
504 vk::PipelineStageFlags src_stage,
505 vk::PipelineStageFlags dst_stage);
506
507 //==========================================================================
508 // Queue Management
509 //==========================================================================
510
511 /**
512 * @brief Set Vulkan queues for command submission
513 */
514 void set_graphics_queue(vk::Queue queue);
515
516 /**
517 * @brief Set Vulkan queues for command submission
518 */
519 void set_compute_queue(vk::Queue queue);
520
521 /**
522 * @brief Set Vulkan queues for command submission
523 */
524 void set_transfer_queue(vk::Queue queue);
525
526 /**
527 * @brief Get Vulkan graphics queue
528 */
529 [[nodiscard]] vk::Queue get_graphics_queue() const;
530
531 /**
532 * @brief Get Vulkan compute queue
533 */
534 [[nodiscard]] vk::Queue get_compute_queue() const;
535
536 /**
537 * @brief Get Vulkan transfer queue
538 */
539 [[nodiscard]] vk::Queue get_transfer_queue() const;
540
541 //==========================================================================
542 // Profiling
543 //==========================================================================
544
545 void begin_timestamp(CommandBufferID cmd_id, const std::string& label = "");
546 void end_timestamp(CommandBufferID cmd_id, const std::string& label = "");
547
549 std::string label;
550 uint64_t duration_ns;
551 bool valid;
552 };
553
554 TimestampResult get_timestamp_result(CommandBufferID cmd_id, const std::string& label);
555
556 //==========================================================================
557 // Utilities
558 //==========================================================================
559
560 /**
561 * @brief Convert Portal ShaderStage to Vulkan ShaderStageFlagBits
562 */
563 static vk::ShaderStageFlagBits to_vulkan_stage(ShaderStage stage);
564
565 /**
566 * @brief Auto-detect shader stage from file extension
567 * @param filepath Path to shader file
568 * @return Detected stage, or nullopt if unknown
569 *
570 * Delegates to VKShaderModule::detect_stage_from_extension().
571 */
572 static std::optional<ShaderStage> detect_stage_from_extension(const std::string& filepath);
573
574 //==========================================================================
575 // Device Access
576 //==========================================================================
577
578 /**
579 * @brief Get logical device handle
580 */
581 [[nodiscard]] vk::Device get_device() const;
582
583 /**
584 * @brief Get physical device handle
585 *
586 * Required by consumers that allocate raw Vulkan buffers directly
587 * (e.g. GpuComputeOperation) and need memory type selection via
588 * vk::PhysicalDevice::getMemoryProperties().
589 */
590 [[nodiscard]] vk::PhysicalDevice get_physical_device() const;
591
592private:
593 /**
594 * @enum DetectedSourceType
595 * @brief Internal enum for source type detection
596 */
597 enum class DetectedSourceType : uint8_t {
598 FILE_GLSL,
599 FILE_SPIRV,
600 SOURCE_STRING,
601 UNKNOWN
602 };
603
604 ShaderFoundry() = default;
606
607 std::shared_ptr<Core::VulkanBackend> m_backend;
609
610 std::unordered_map<std::string, std::shared_ptr<Core::VKShaderModule>> m_shader_cache;
611 std::unordered_map<ShaderID, ShaderState> m_shaders;
612 std::unordered_map<std::string, ShaderID> m_shader_filepath_cache;
613
614 std::shared_ptr<Core::VKDescriptorManager> m_global_descriptor_manager;
615 std::unordered_map<DescriptorSetID, DescriptorSetState> m_descriptor_sets;
616
617 std::unordered_map<CommandBufferID, CommandBufferState> m_command_buffers;
618 std::unordered_map<FenceID, FenceState> m_fences;
619 std::unordered_map<SemaphoreID, SemaphoreState> m_semaphores;
620
624
625 std::atomic<uint64_t> m_next_shader_id { 1 };
626 std::atomic<uint64_t> m_next_descriptor_set_id { 1 };
627 std::atomic<uint64_t> m_next_command_id { 1 };
628 std::atomic<uint64_t> m_next_fence_id { 1 };
629 std::atomic<uint64_t> m_next_semaphore_id { 1 };
630
631 DetectedSourceType detect_source_type(const std::string& content) const;
632 std::optional<std::filesystem::path> resolve_shader_path(const std::string& filepath) const;
633 std::string generate_source_cache_key(const std::string& source, ShaderStage stage) const;
634
635 std::shared_ptr<Core::VKShaderModule> create_shader_module();
636
637 void cleanup_sync_objects();
638 void cleanup_descriptor_resources();
639 void cleanup_shader_modules();
640
641 //==========================================================================
642 // INTERNAL Shader Compilation Methods
643 //==========================================================================
644
645 std::shared_ptr<Core::VKShaderModule> compile_from_file(
646 const std::string& filepath,
647 std::optional<ShaderStage> stage = std::nullopt,
648 const std::string& entry_point = "main");
649
650 std::shared_ptr<Core::VKShaderModule> compile_from_source(
651 const std::string& source,
652 ShaderStage stage,
653 const std::string& entry_point = "main");
654
655 std::shared_ptr<Core::VKShaderModule> compile_from_source_cached(
656 const std::string& source,
657 ShaderStage stage,
658 const std::string& cache_key,
659 const std::string& entry_point = "main");
660
661 std::shared_ptr<Core::VKShaderModule> compile_from_spirv(
662 const std::string& spirv_path,
663 ShaderStage stage,
664 const std::string& entry_point = "main");
665
666 std::shared_ptr<Core::VKShaderModule> get_vk_shader_module(ShaderID shader_id);
667
668 friend class ComputePress;
669 friend class RenderFlow;
670
671 static bool s_initialized;
672};
673
674/**
675 * @brief Get the global shader compiler instance
676 * @return Reference to singleton shader compiler
677 *
678 * Must call initialize() before first use.
679 * Thread-safe after initialization.
680 */
681inline MAYAFLUX_API ShaderFoundry& get_shader_foundry()
682{
684}
685
686} // namespace MayaFlux::Portal::Graphics
IO::ImageData image
Range size
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.