MayaFlux 0.3.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 */
354 void update_descriptor_image(
355 DescriptorSetID descriptor_set_id,
356 uint32_t binding,
357 vk::ImageView image_view,
358 vk::Sampler sampler,
359 vk::ImageLayout layout = vk::ImageLayout::eShaderReadOnlyOptimal);
360
361 /**
362 * @brief Update descriptor set with storage image binding
363 * @param descriptor_set_id ID of descriptor set to update
364 * @param binding Binding index within the descriptor set
365 * @param image_view Vulkan image view to bind
366 * @param layout Image layout (default: eGeneral)
367 */
368 void update_descriptor_storage_image(
369 DescriptorSetID descriptor_set_id,
370 uint32_t binding,
371 vk::ImageView image_view,
372 vk::ImageLayout layout = vk::ImageLayout::eGeneral);
373
374 /**
375 * @brief Get Vulkan descriptor set handle from DescriptorSetID
376 * @param descriptor_set_id Descriptor set ID
377 * @return Vulkan descriptor set handle
378 */
379 vk::DescriptorSet get_descriptor_set(DescriptorSetID descriptor_set_id);
380
381 //==========================================================================
382 // Command Recording - ShaderFoundry manages command buffers
383 //==========================================================================
384
385 /**
386 * @brief Begin recording command buffer
387 * @param type Command buffer type (GRAPHICS, COMPUTE, TRANSFER)
388 * @return Command buffer ID
389 */
390 CommandBufferID begin_commands(CommandBufferType type);
391
392 /**
393 * @brief Begin recording a secondary command buffer for dynamic rendering
394 * @param color_format Format of the color attachment (from swapchain)
395 * @return Command buffer ID
396 *
397 * With dynamic rendering, secondary buffers don't need render pass objects.
398 * They only need to know the attachment formats they'll render to.
399 */
400 CommandBufferID begin_secondary_commands(vk::Format color_format);
401
402 /**
403 * @brief Get Vulkan command buffer handle from CommandBufferID
404 * @param cmd_id Command buffer ID
405 */
406 vk::CommandBuffer get_command_buffer(CommandBufferID cmd_id);
407
408 /**
409 * @brief End recording command buffer
410 * @param cmd_id Command buffer ID to end
411 * @return True if successful, false if invalid ID or not active
412 */
413 bool end_commands(CommandBufferID cmd_id);
414
415 /**
416 * @brief Free all allocated command buffers
417 */
418 void free_all_command_buffers();
419
420 //==========================================================================
421 // Memory Barriers and Synchronization
422 //==========================================================================
423
424 /**
425 * @brief Submit command buffer and wait for completion
426 * @param cmd_id Command buffer ID to submit
427 */
428 void submit_and_wait(CommandBufferID cmd_id);
429
430 /**
431 * @brief Submit command buffer asynchronously, returning a fence
432 * @param cmd_id Command buffer ID to submit
433 * @return Fence ID to wait on later
434 */
435 FenceID submit_async(CommandBufferID cmd_id);
436
437 /**
438 * @brief Submit command buffer asynchronously, returning a semaphore
439 * @param cmd_id Command buffer ID to submit
440 * @return Semaphore ID to wait on later
441 */
442 SemaphoreID submit_with_signal(CommandBufferID cmd_id);
443
444 /**
445 * @brief Wait for fence to be signaled
446 * @param fence_id Fence ID to wait on
447 */
448 void wait_for_fence(FenceID fence_id);
449
450 /**
451 * @brief Wait for multiple fences to be signaled
452 * @param fence_ids Vector of fence IDs to wait on
453 */
454 void wait_for_fences(const std::vector<FenceID>& fence_ids);
455
456 /**
457 * @brief Check if fence is signaled
458 * @param fence_id Fence ID to check
459 * @return True if fence is signaled, false otherwise
460 */
461 bool is_fence_signaled(FenceID fence_id);
462
463 /**
464 * @brief Begin command buffer that waits on a semaphore
465 * @param type Command buffer type (GRAPHICS, COMPUTE, TRANSFER)
466 * @param wait_semaphore Semaphore ID to wait on
467 * @param wait_stage Pipeline stage to wait at
468 * @return Command buffer ID
469 */
470 CommandBufferID begin_commands_with_wait(
471 CommandBufferType type,
472 SemaphoreID wait_semaphore,
473 vk::PipelineStageFlags wait_stage);
474
475 /**
476 * @brief Get Vulkan fence handle from FenceID
477 * @param fence_id Fence ID
478 */
479 vk::Semaphore get_semaphore_handle(SemaphoreID semaphore_id);
480
481 /**
482 * @brief Insert buffer memory barrier
483 */
484 void buffer_barrier(
485 CommandBufferID cmd_id,
486 vk::Buffer buffer,
487 vk::AccessFlags src_access,
488 vk::AccessFlags dst_access,
489 vk::PipelineStageFlags src_stage,
490 vk::PipelineStageFlags dst_stage);
491
492 /**
493 * @brief Insert image memory barrier
494 */
495 void image_barrier(
496 CommandBufferID cmd_id,
497 vk::Image image,
498 vk::ImageLayout old_layout,
499 vk::ImageLayout new_layout,
500 vk::AccessFlags src_access,
501 vk::AccessFlags dst_access,
502 vk::PipelineStageFlags src_stage,
503 vk::PipelineStageFlags dst_stage);
504
505 //==========================================================================
506 // Queue Management
507 //==========================================================================
508
509 /**
510 * @brief Set Vulkan queues for command submission
511 */
512 void set_graphics_queue(vk::Queue queue);
513
514 /**
515 * @brief Set Vulkan queues for command submission
516 */
517 void set_compute_queue(vk::Queue queue);
518
519 /**
520 * @brief Set Vulkan queues for command submission
521 */
522 void set_transfer_queue(vk::Queue queue);
523
524 /**
525 * @brief Get Vulkan graphics queue
526 */
527 [[nodiscard]] vk::Queue get_graphics_queue() const;
528
529 /**
530 * @brief Get Vulkan compute queue
531 */
532 [[nodiscard]] vk::Queue get_compute_queue() const;
533
534 /**
535 * @brief Get Vulkan transfer queue
536 */
537 [[nodiscard]] vk::Queue get_transfer_queue() const;
538
539 //==========================================================================
540 // Profiling
541 //==========================================================================
542
543 void begin_timestamp(CommandBufferID cmd_id, const std::string& label = "");
544 void end_timestamp(CommandBufferID cmd_id, const std::string& label = "");
545
547 std::string label;
548 uint64_t duration_ns;
549 bool valid;
550 };
551
552 TimestampResult get_timestamp_result(CommandBufferID cmd_id, const std::string& label);
553
554 //==========================================================================
555 // Utilities
556 //==========================================================================
557
558 /**
559 * @brief Convert Portal ShaderStage to Vulkan ShaderStageFlagBits
560 */
561 static vk::ShaderStageFlagBits to_vulkan_stage(ShaderStage stage);
562
563 /**
564 * @brief Auto-detect shader stage from file extension
565 * @param filepath Path to shader file
566 * @return Detected stage, or nullopt if unknown
567 *
568 * Delegates to VKShaderModule::detect_stage_from_extension().
569 */
570 static std::optional<ShaderStage> detect_stage_from_extension(const std::string& filepath);
571
572private:
573 /**
574 * @enum DetectedSourceType
575 * @brief Internal enum for source type detection
576 */
577 enum class DetectedSourceType : uint8_t {
578 FILE_GLSL,
579 FILE_SPIRV,
580 SOURCE_STRING,
581 UNKNOWN
582 };
583
584 ShaderFoundry() = default;
586
587 std::shared_ptr<Core::VulkanBackend> m_backend;
589
590 std::unordered_map<std::string, std::shared_ptr<Core::VKShaderModule>> m_shader_cache;
591 std::unordered_map<ShaderID, ShaderState> m_shaders;
592 std::unordered_map<std::string, ShaderID> m_shader_filepath_cache;
593
594 std::shared_ptr<Core::VKDescriptorManager> m_global_descriptor_manager;
595 std::unordered_map<DescriptorSetID, DescriptorSetState> m_descriptor_sets;
596
597 std::unordered_map<CommandBufferID, CommandBufferState> m_command_buffers;
598 std::unordered_map<FenceID, FenceState> m_fences;
599 std::unordered_map<SemaphoreID, SemaphoreState> m_semaphores;
600
604
605 std::atomic<uint64_t> m_next_shader_id { 1 };
606 std::atomic<uint64_t> m_next_descriptor_set_id { 1 };
607 std::atomic<uint64_t> m_next_command_id { 1 };
608 std::atomic<uint64_t> m_next_fence_id { 1 };
609 std::atomic<uint64_t> m_next_semaphore_id { 1 };
610
611 DetectedSourceType detect_source_type(const std::string& content) const;
612 std::optional<std::filesystem::path> resolve_shader_path(const std::string& filepath) const;
613 std::string generate_source_cache_key(const std::string& source, ShaderStage stage) const;
614
615 std::shared_ptr<Core::VKShaderModule> create_shader_module();
616 vk::Device get_device() const;
617
618 void cleanup_sync_objects();
619 void cleanup_descriptor_resources();
620 void cleanup_shader_modules();
621
622 //==========================================================================
623 // INTERNAL Shader Compilation Methods
624 //==========================================================================
625
626 std::shared_ptr<Core::VKShaderModule> compile_from_file(
627 const std::string& filepath,
628 std::optional<ShaderStage> stage = std::nullopt,
629 const std::string& entry_point = "main");
630
631 std::shared_ptr<Core::VKShaderModule> compile_from_source(
632 const std::string& source,
633 ShaderStage stage,
634 const std::string& entry_point = "main");
635
636 std::shared_ptr<Core::VKShaderModule> compile_from_source_cached(
637 const std::string& source,
638 ShaderStage stage,
639 const std::string& cache_key,
640 const std::string& entry_point = "main");
641
642 std::shared_ptr<Core::VKShaderModule> compile_from_spirv(
643 const std::string& spirv_path,
644 ShaderStage stage,
645 const std::string& entry_point = "main");
646
647 std::shared_ptr<Core::VKShaderModule> get_vk_shader_module(ShaderID shader_id);
648
649 friend class ComputePress;
650 friend class RenderFlow;
651
652 static bool s_initialized;
653};
654
655/**
656 * @brief Get the global shader compiler instance
657 * @return Reference to singleton shader compiler
658 *
659 * Must call initialize() before first use.
660 * Thread-safe after initialization.
661 */
662inline MAYAFLUX_API ShaderFoundry& get_shader_foundry()
663{
665}
666
667} // 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.