MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ShaderFoundry.cpp
Go to the documentation of this file.
1#include "ShaderFoundry.hpp"
2
10
12
14
16 const std::shared_ptr<Core::VulkanBackend>& backend,
17 const ShaderCompilerConfig& config)
18{
19 if (s_initialized) {
21 "ShaderFoundry already initialized (static flag)");
22 return true;
23 }
24
25 if (!backend) {
27 "Cannot initialize ShaderFoundry with null backend");
28 return false;
29 }
30
31 if (m_backend) {
33 "ShaderFoundry already initialized");
34 return true;
35 }
36
37 m_backend = backend;
38 m_config = config;
39
40 m_config.include_directories.emplace_back(Core::SHADER_SOURCE_DIR);
41 m_config.include_directories.emplace_back(std::string(Core::SHADER_SOURCE_DIR) + "/include");
42 m_config.include_directories.emplace_back(Core::SHADER_BUILD_OUTPUT_DIR);
43
44 m_global_descriptor_manager = std::make_shared<Core::VKDescriptorManager>();
45 m_global_descriptor_manager->initialize(get_device(), 1024);
46
47 m_graphics_queue = m_backend->get_context().get_graphics_queue();
48 m_compute_queue = m_backend->get_context().get_compute_queue();
49 m_transfer_queue = m_backend->get_context().get_transfer_queue();
50
51 s_initialized = true;
52
54 "ShaderFoundry initialized");
55 return true;
56}
57
59{
60 if (!s_initialized || !m_backend) {
61 return;
62 }
63
65 "Stopping ShaderFoundry - freeing command buffers...");
66
67 auto device = get_device();
68
69 device.waitIdle();
70
72
74 "ShaderFoundry stopped - command buffers freed");
75}
76
78{
79 if (!s_initialized) {
80 return;
81 }
82
83 if (!m_backend) {
84 return;
85 }
86
88 "Shutting down ShaderFoundry...");
89
90 if (!m_command_buffers.empty()) {
92 "{} command buffers still exist during shutdown - freeing now",
93 m_command_buffers.size());
95 }
96
98
100
102
103 m_backend = nullptr;
104 s_initialized = false;
105
107 "ShaderFoundry shutdown complete");
108}
109
110//==============================================================================
111// Shader Compilation - Primary API
112//==============================================================================
113
114std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_file(
115 const std::string& filepath,
116 std::optional<ShaderStage> stage,
117 const std::string& entry_point)
118{
119 if (!is_initialized()) {
121 "ShaderFoundry not initialized");
122 return nullptr;
123 }
124
125 auto it = m_shader_cache.find(filepath);
126 if (it != m_shader_cache.end()) {
128 "Using cached shader: {}", filepath);
129 return it->second;
130 }
131
132 std::optional<vk::ShaderStageFlagBits> vk_stage;
133 if (stage.has_value()) {
134 vk_stage = to_vulkan_stage(*stage);
135 }
136
137 auto shader = create_shader_module();
138
139 if (filepath.ends_with(".spv")) {
140 if (!shader->create_from_spirv_file(
141 get_device(), filepath,
142 vk_stage.value_or(vk::ShaderStageFlagBits::eCompute),
143 entry_point, m_config.enable_reflection)) {
145 "Failed to compile SPIR-V shader: {}", filepath);
146 return nullptr;
147 }
148 } else {
149 if (!shader->create_from_glsl_file(
150 get_device(), filepath, vk_stage, entry_point,
152 m_config.defines)) {
154 "Failed to compile GLSL shader: {}", filepath);
155 return nullptr;
156 }
157 }
158
159 m_shader_cache[filepath] = shader;
161 "Compiled shader: {} ({})", filepath, vk::to_string(shader->get_stage()));
162 return shader;
163}
164
165std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_source(
166 const std::string& source,
167 ShaderStage stage,
168 const std::string& entry_point)
169{
170 if (!is_initialized()) {
172 "ShaderFoundry not initialized");
173 return nullptr;
174 }
175
176 auto shader = create_shader_module();
177 auto vk_stage = to_vulkan_stage(stage);
178
179 if (!shader->create_from_glsl(
180 get_device(), source, vk_stage, entry_point,
182 m_config.defines)) {
184 "Failed to compile GLSL source");
185 return nullptr;
186 }
187
189 "Compiled shader from source ({})", vk::to_string(vk_stage));
190 return shader;
191}
192
193std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_source_cached(
194 const std::string& source,
195 ShaderStage stage,
196 const std::string& cache_key,
197 const std::string& entry_point)
198{
199 auto it = m_shader_cache.find(cache_key);
200 if (it != m_shader_cache.end()) {
202 "Using cached shader: {}", cache_key);
203 return it->second;
204 }
205
206 auto shader = compile_from_source(source, stage, entry_point);
207 if (!shader) {
208 return nullptr;
209 }
210
211 m_shader_cache[cache_key] = shader;
212 return shader;
213}
214
215std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_spirv(
216 const std::string& spirv_path,
217 ShaderStage stage,
218 const std::string& entry_point)
219{
220 if (!is_initialized()) {
222 "ShaderFoundry not initialized");
223 return nullptr;
224 }
225
226 auto it = m_shader_cache.find(spirv_path);
227 if (it != m_shader_cache.end()) {
229 "Using cached SPIR-V shader: {}", spirv_path);
230 return it->second;
231 }
232
233 auto shader = create_shader_module();
234 auto vk_stage = to_vulkan_stage(stage);
235
236 if (!shader->create_from_spirv_file(
237 get_device(), spirv_path, vk_stage, entry_point,
240 "Failed to load SPIR-V shader: {}", spirv_path);
241 return nullptr;
242 }
243
244 m_shader_cache[spirv_path] = shader;
246 "Loaded SPIR-V shader: {}", spirv_path);
247 return shader;
248}
249
250std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_spirv_asm(
251 const std::string& spirv_asm,
252 ShaderStage stage,
253 const std::string& entry_point)
254{
255 auto shader = create_shader_module();
256 if (!shader->create_from_spirv_asm(
257 get_device(), spirv_asm, to_vulkan_stage(stage),
258 entry_point, m_config.enable_reflection)) {
260 "Failed to assemble generated SPIR-V");
261 return nullptr;
262 }
263 return shader;
264}
265
266std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile(const ShaderSource& shader_source)
267{
268 switch (shader_source.type) {
270 return compile_from_file(shader_source.content, shader_source.stage, shader_source.entry_point);
271
273 return compile_from_source(shader_source.content, shader_source.stage, shader_source.entry_point);
274
276 return compile_from_spirv(shader_source.content, shader_source.stage, shader_source.entry_point);
277
279 return compile_from_spirv_asm(shader_source.content, shader_source.stage, shader_source.entry_point);
280
281 default:
283 "Unknown shader source type");
284 return nullptr;
285 }
286}
287
289 const std::string& content,
290 std::optional<ShaderStage> stage,
291 const std::string& entry_point)
292{
293 if (!is_initialized()) {
295 "ShaderFoundry not initialized");
296 return INVALID_SHADER;
297 }
298
299 DetectedSourceType source_type = detect_source_type(content);
300
301 std::string cache_key;
302 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
303 cache_key = content;
304 } else {
305 cache_key = generate_source_cache_key(content, stage.value_or(ShaderStage::COMPUTE));
306 }
307
308 auto id_it = m_shader_filepath_cache.find(cache_key);
309 if (id_it != m_shader_filepath_cache.end()) {
311 "Using cached shader ID for: {}", cache_key);
312 return id_it->second;
313 }
314
315 if (!stage.has_value()) {
316 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
317 if (source_type == DetectedSourceType::FILE_SPIRV) {
318 std::filesystem::path p(content);
319 std::string stem = p.stem().string();
320 stage = detect_stage_from_extension(stem);
321 } else {
322 stage = detect_stage_from_extension(content);
323 }
324 }
325
326 if (!stage.has_value()) {
328 "Cannot auto-detect shader stage from '{}' - must specify explicitly",
329 content);
330 return INVALID_SHADER;
331 }
332 }
333
334 std::shared_ptr<Core::VKShaderModule> shader_module;
335
336 switch (source_type) {
338 shader_module = compile_from_file(content, stage, entry_point);
339 break;
341 shader_module = compile_from_spirv(content, *stage, entry_point);
342 break;
344 shader_module = compile_from_source(content, *stage, entry_point);
345 break;
346 default:
348 "Cannot determine shader source type");
349 return INVALID_SHADER;
350 }
351
352 if (!shader_module) {
353 return INVALID_SHADER;
354 }
355
357
358 ShaderState& state = m_shaders[id];
359 state.module = shader_module;
360 state.filepath = cache_key;
361 state.stage = *stage;
362 state.entry_point = entry_point;
363
364 m_shader_filepath_cache[cache_key] = id;
365
367 "Shader loaded: {} (ID: {}, stage: {})",
368 cache_key, id, static_cast<int>(*stage));
369
370 return id;
371}
372
374{
375 return load_shader(source.content, source.stage, source.entry_point);
376}
377
379{
380 if (!is_initialized()) {
382 "ShaderFoundry not initialized");
383 return INVALID_SHADER;
384 }
385
386 if (spec.kernel.has_value()) {
387 const std::string glsl = detail::emit_glsl_kernel(spec);
388 const std::string key = generate_source_cache_key(glsl, ShaderStage::COMPUTE);
389 auto module = compile_from_source_cached(glsl, ShaderStage::COMPUTE, key);
390 if (!module) {
392 "Failed to compile kernel shader");
393 return INVALID_SHADER;
394 }
395 const ShaderID id = m_next_shader_id++;
396 auto& state = m_shaders[id];
397 state.module = module;
398 state.filepath = key;
399 state.stage = ShaderStage::COMPUTE;
400 state.entry_point = "main";
401 m_shader_filepath_cache[key] = id;
403 "Compiled kernel shader (ID: {}, key: {})", id, key);
404 return id;
405 }
406
407 const std::string asm_text = detail::emit_spirv_asm(spec);
408 const std::string key = generate_source_cache_key(asm_text, ShaderStage::COMPUTE);
409
410 auto id_it = m_shader_filepath_cache.find(key);
411 if (id_it != m_shader_filepath_cache.end()) {
413 "Using cached generated shader: {}", key);
414 return id_it->second;
415 }
416
417 auto module = compile_from_spirv_asm(asm_text, ShaderStage::COMPUTE);
418 if (!module) {
420 "Failed to compile generated shader (key: {})", key);
421 return INVALID_SHADER;
422 }
423
424 const ShaderID id = m_next_shader_id++;
425 auto& state = m_shaders[id];
426 state.module = module;
427 state.filepath = key;
428 state.stage = ShaderStage::COMPUTE;
429 state.entry_point = "main";
430
431 m_shader_filepath_cache[key] = id;
432
434 "Compiled generated shader (ID: {}, key: {})", id, key);
435
436 return id;
437}
438
439std::optional<std::filesystem::path> ShaderFoundry::resolve_shader_path(const std::string& filepath) const
440{
441 namespace fs = std::filesystem;
442
443 fs::path path(filepath);
444
445 if (path.is_absolute() || fs::exists(filepath)) {
446 return path;
447 }
448
449 std::vector<std::string> search_paths = {
450 Core::SHADER_BUILD_OUTPUT_DIR,
451 Core::SHADER_INSTALL_DIR,
452 Core::SHADER_SOURCE_DIR,
453 "./shaders",
454 "../shaders",
455 "data/shaders",
456 "./data/shaders",
457 "../data/shaders"
458 };
459
460 if (std::string_view(Core::SHADER_EXAMPLE_DIR).length() > 0) {
461 search_paths.emplace_back(Core::SHADER_EXAMPLE_DIR);
462 }
463
464#ifdef MAYAFLUX_PROJECT_SHADER_DIR
465 search_paths.emplace_back(MAYAFLUX_PROJECT_SHADER_DIR);
466#endif
467
468 for (const auto& search_path : search_paths) {
469 fs::path full_path = fs::path(search_path) / filepath;
470 if (fs::exists(full_path)) {
471 return full_path;
472 }
473 }
474
475 return std::nullopt;
476}
477
479{
480 auto resolved_path = resolve_shader_path(content);
481
482 if (resolved_path.has_value()) {
483 std::string ext = resolved_path->extension().string();
484 std::ranges::transform(ext, ext.begin(), ::tolower);
485
486 if (ext == ".spv") {
488 }
489
491 }
492
493 if (content.size() > 1024 || content.find('\n') != std::string::npos) {
495 }
496
498}
499
500std::string ShaderFoundry::generate_source_cache_key(const std::string& source, ShaderStage stage) const
501{
502 std::hash<std::string> hasher;
503 size_t hash = hasher(source + std::to_string(static_cast<int>(stage)));
504 return "source_" + std::to_string(hash);
505}
506
507ShaderID ShaderFoundry::reload_shader(const std::string& filepath)
508{
509 invalidate_cache(filepath);
510
511 auto cache_it = m_shader_filepath_cache.find(filepath);
512 if (cache_it != m_shader_filepath_cache.end()) {
513 destroy_shader(cache_it->second);
514 m_shader_filepath_cache.erase(cache_it);
515 }
516
517 return load_shader(filepath);
518}
519
521{
522 auto it = m_shaders.find(shader_id);
523 if (it != m_shaders.end()) {
524 if (!it->second.filepath.empty()) {
525 m_shader_filepath_cache.erase(it->second.filepath);
526 m_shader_cache.erase(it->second.filepath);
527 }
528 if (it->second.module) {
529 it->second.module->cleanup(get_device());
530 }
531 m_shaders.erase(it);
532 }
533}
534
535//==============================================================================
536// Shader Introspection
537//==============================================================================
538
540{
541 auto it = m_shaders.find(shader_id);
542 if (it == m_shaders.end()) {
543 return {};
544 }
545
546 const auto& reflection = it->second.module->get_reflection();
547
549 info.stage = it->second.stage;
550 info.entry_point = it->second.entry_point;
551 info.workgroup_size = reflection.workgroup_size;
552
553 for (const auto& binding : reflection.bindings) {
554 info.descriptor_bindings.push_back({ .set = binding.set,
555 .binding = binding.binding,
556 .type = binding.type,
557 .name = binding.name });
558 }
559
560 for (const auto& pc : reflection.push_constants) {
561 PushConstantRangeInfo pc_info {};
562 pc_info.offset = pc.offset;
563 pc_info.size = pc.size;
564 info.push_constant_ranges.push_back(pc_info);
565 }
566
567 return info;
568}
569
571{
572 auto it = m_shaders.find(shader_id);
573 if (it != m_shaders.end()) {
574 return it->second.stage;
575 }
577}
578
580{
581 auto it = m_shaders.find(shader_id);
582 if (it != m_shaders.end()) {
583 return it->second.entry_point;
584 }
585 return "main";
586}
587
588bool ShaderFoundry::is_cached(const std::string& cache_key) const
589{
590 return m_shader_cache.find(cache_key) != m_shader_cache.end();
591}
592
593std::vector<std::string> ShaderFoundry::get_cached_keys() const
594{
595 std::vector<std::string> keys;
596 keys.reserve(m_shader_cache.size());
597 for (const auto& [key, _] : m_shader_cache) {
598 keys.push_back(key);
599 }
600 return keys;
601}
602
604{
605 auto device = get_device();
606
607 for (auto& [key, shader_module] : m_shader_cache) {
608 if (shader_module) {
609 shader_module->cleanup(device);
610 }
611 }
612
613 m_shader_cache.clear();
614 m_shaders.clear();
616
618 "Cleaned up shader modules");
619}
620
621//==============================================================================
622// Hot-Reload Support
623//==============================================================================
624
625void ShaderFoundry::invalidate_cache(const std::string& cache_key)
626{
627 auto it = m_shader_cache.find(cache_key);
628 if (it != m_shader_cache.end()) {
629 m_shader_cache.erase(it);
631 "Invalidated shader cache: {}", cache_key);
632 }
633}
634
641
642std::shared_ptr<Core::VKShaderModule> ShaderFoundry::hot_reload(const std::string& filepath)
643{
644 invalidate_cache(filepath);
645 return compile_from_file(filepath);
646}
647
648//==============================================================================
649// Configuration
650//==============================================================================
651
653{
654 m_config = config;
656 "Updated shader compiler configuration");
657}
658
659void ShaderFoundry::add_include_directory(const std::string& directory)
660{
661 m_config.include_directories.push_back(directory);
662}
663
664void ShaderFoundry::add_define(const std::string& name, const std::string& value)
665{
666 m_config.defines[name] = value;
667}
668
669//==============================================================================
670// Descriptor Management
671//==============================================================================
672
674{
676
678 state.descriptor_set = m_global_descriptor_manager->allocate_set(get_device(), layout);
679
680 return id;
681}
682
684 DescriptorSetID descriptor_set_id,
685 uint32_t binding,
686 vk::DescriptorType type,
687 vk::Buffer buffer,
688 size_t offset,
689 size_t size)
690{
691 auto it = m_descriptor_sets.find(descriptor_set_id);
692 if (it == m_descriptor_sets.end()) {
693 return;
694 }
695
696 vk::DescriptorBufferInfo buffer_info;
697 buffer_info.buffer = buffer;
698 buffer_info.offset = offset;
699 buffer_info.range = size;
700
701 vk::WriteDescriptorSet write;
702 write.dstSet = it->second.descriptor_set;
703 write.dstBinding = binding;
704 write.dstArrayElement = 0;
705 write.descriptorCount = 1;
706 write.descriptorType = type;
707 write.pBufferInfo = &buffer_info;
708
709 get_device().updateDescriptorSets(1, &write, 0, nullptr);
710}
711
713 DescriptorSetID descriptor_set_id,
714 uint32_t binding,
715 vk::ImageView image_view,
716 vk::Sampler sampler,
717 vk::ImageLayout layout,
718 uint32_t array_element)
719{
720 auto it = m_descriptor_sets.find(descriptor_set_id);
721 if (it == m_descriptor_sets.end()) {
722 return;
723 }
724
725 vk::DescriptorImageInfo image_info;
726 image_info.imageView = image_view;
727 image_info.sampler = sampler;
728 image_info.imageLayout = layout;
729
730 vk::WriteDescriptorSet write;
731 write.dstSet = it->second.descriptor_set;
732 write.dstBinding = binding;
733 write.dstArrayElement = array_element;
734 write.descriptorCount = 1;
735 write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
736 write.pImageInfo = &image_info;
737
738 get_device().updateDescriptorSets(1, &write, 0, nullptr);
739}
740
742 DescriptorSetID descriptor_set_id,
743 uint32_t binding,
744 vk::ImageView image_view,
745 vk::ImageLayout layout)
746{
747 auto it = m_descriptor_sets.find(descriptor_set_id);
748 if (it == m_descriptor_sets.end()) {
749 return;
750 }
751
752 vk::DescriptorImageInfo image_info;
753 image_info.imageView = image_view;
754 image_info.imageLayout = layout;
755
756 vk::WriteDescriptorSet write;
757 write.dstSet = it->second.descriptor_set;
758 write.dstBinding = binding;
759 write.dstArrayElement = 0;
760 write.descriptorCount = 1;
761 write.descriptorType = vk::DescriptorType::eStorageImage;
762 write.pImageInfo = &image_info;
763
764 get_device().updateDescriptorSets(1, &write, 0, nullptr);
765}
766
767vk::DescriptorSet ShaderFoundry::get_descriptor_set(DescriptorSetID descriptor_set_id)
768{
769 auto it = m_descriptor_sets.find(descriptor_set_id);
770 if (it == m_descriptor_sets.end()) {
771 error<std::invalid_argument>(
774 std::source_location::current(),
775 "Invalid DescriptorSetID: {}", descriptor_set_id);
776 }
777 return it->second.descriptor_set;
778}
779
781{
782 auto device = get_device();
783
784 m_descriptor_sets.clear();
785
787 m_global_descriptor_manager->cleanup(device);
789 }
790
792 "Cleaned up descriptor resources");
793}
794
795//==============================================================================
796// Command Recording
797//==============================================================================
798
800{
801 auto& cmd_manager = m_backend->get_command_manager();
802
804
806
807 state.cmd = (type == CommandBufferType::COMPUTE)
808 ? cmd_manager.begin_single_time_commands_compute()
809 : cmd_manager.begin_single_time_commands();
810
811 state.type = type;
812 state.is_active = true;
813
814 return id;
815}
816
818 vk::Format color_format,
819 vk::Format depth_format)
820{
821 auto& cmd_manager = m_backend->get_command_manager();
822
824
825 vk::CommandBuffer cmd = cmd_manager.allocate_command_buffer(vk::CommandBufferLevel::eSecondary);
826
827 vk::CommandBufferInheritanceRenderingInfo inheritance_rendering;
828 inheritance_rendering.colorAttachmentCount = 1;
829 inheritance_rendering.pColorAttachmentFormats = &color_format;
830 inheritance_rendering.depthAttachmentFormat = depth_format;
831 inheritance_rendering.rasterizationSamples = vk::SampleCountFlagBits::e1;
832
833 vk::CommandBufferInheritanceInfo inheritance_info;
834 inheritance_info.pNext = &inheritance_rendering;
835
836 vk::CommandBufferBeginInfo begin_info;
837 begin_info.flags = vk::CommandBufferUsageFlagBits::eRenderPassContinue | vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
838 begin_info.pInheritanceInfo = &inheritance_info;
839
840 cmd.begin(begin_info);
841
843 state.cmd = cmd;
846 state.is_active = true;
847
848 return id;
849}
850
852{
853 auto it = m_command_buffers.find(cmd_id);
854 if (it != m_command_buffers.end()) {
855 return it->second.cmd;
856 }
857 return nullptr;
858}
859
861{
862 auto it = m_command_buffers.find(cmd_id);
863 if (it == m_command_buffers.end() || !it->second.is_active) {
864 return false;
865 }
866
867 it->second.cmd.end();
868 it->second.is_active = false;
869 return true;
870}
871
873{
874 if (!m_backend || !s_initialized) {
875 return;
876 }
877
878 auto& cmd_manager = m_backend->get_command_manager();
879 auto device = get_device();
880 device.waitIdle();
881
882 for (auto& [id, state] : m_command_buffers) {
883 if (state.is_active) {
884 cmd_manager.free_command_buffer(state.cmd);
885 }
886 if (state.timestamp_pool) {
887 device.destroyQueryPool(state.timestamp_pool);
888 }
889 }
890 m_command_buffers.clear();
891
893 "Freed all command buffers");
894}
895
896//==============================================================================
897// Synchronization
898//==============================================================================
899
901{
902 auto it = m_command_buffers.find(cmd_id);
903 if (it == m_command_buffers.end() || !it->second.is_active) {
904 return;
905 }
906
907 auto& cmd_manager = m_backend->get_command_manager();
908
909 it->second.cmd.end();
910
911 vk::SubmitInfo submit_info;
912 submit_info.commandBufferCount = 1;
913 submit_info.pCommandBuffers = &it->second.cmd;
914
915 vk::Queue queue;
916 switch (it->second.type) {
918 queue = m_graphics_queue;
919 break;
921 queue = m_compute_queue;
922 break;
924 queue = m_transfer_queue;
925 break;
926 }
927
928 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
930 "Failed to submit command buffer");
931 return;
932 }
933 queue.waitIdle();
934
935 cmd_manager.free_command_buffer(it->second.cmd);
936
937 it->second.is_active = false;
938 m_command_buffers.erase(it);
939}
940
942{
943 auto cmd_it = m_command_buffers.find(cmd_id);
944 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
945 return INVALID_FENCE;
946 }
947
948 cmd_it->second.cmd.end();
949
950 FenceID fence_id = m_next_fence_id++;
951
952 vk::FenceCreateInfo fence_info;
953 FenceState& fence_state = m_fences[fence_id];
954 fence_state.fence = get_device().createFence(fence_info);
955 fence_state.signaled = false;
956 fence_state.cmd_id = cmd_id;
957
958 vk::SubmitInfo submit_info;
959 submit_info.commandBufferCount = 1;
960 submit_info.pCommandBuffers = &cmd_it->second.cmd;
961
962 vk::Queue queue;
963 switch (cmd_it->second.type) {
965 queue = m_graphics_queue;
966 break;
968 queue = m_compute_queue;
969 break;
971 queue = m_transfer_queue;
972 break;
973 }
974
975 if (queue.submit(1, &submit_info, fence_state.fence) != vk::Result::eSuccess) {
977 "Failed to submit command buffer");
978 return INVALID_FENCE;
979 }
980
981 cmd_it->second.is_active = false;
982
983 return fence_id;
984}
985
987{
988 auto cmd_it = m_command_buffers.find(cmd_id);
989 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
990 return INVALID_SEMAPHORE;
991 }
992
993 cmd_it->second.cmd.end();
994
995 SemaphoreID semaphore_id = m_next_semaphore_id++;
996
997 vk::SemaphoreCreateInfo semaphore_info;
998 SemaphoreState& semaphore_state = m_semaphores[semaphore_id];
999 semaphore_state.semaphore = get_device().createSemaphore(semaphore_info);
1000
1001 vk::SubmitInfo submit_info;
1002 submit_info.commandBufferCount = 1;
1003 submit_info.pCommandBuffers = &cmd_it->second.cmd;
1004 submit_info.signalSemaphoreCount = 1;
1005 submit_info.pSignalSemaphores = &semaphore_state.semaphore;
1006
1007 vk::Queue queue;
1008 switch (cmd_it->second.type) {
1010 queue = m_graphics_queue;
1011 break;
1013 queue = m_compute_queue;
1014 break;
1016 queue = m_transfer_queue;
1017 break;
1018 }
1019
1020 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
1022 "Failed to submit command buffer");
1023 return INVALID_SEMAPHORE;
1024 }
1025
1026 cmd_it->second.is_active = false;
1027
1028 return semaphore_id;
1029}
1030
1032{
1033 auto it = m_fences.find(fence_id);
1034 if (it == m_fences.end()) {
1035 return;
1036 }
1037
1038 if (get_device().waitForFences(1, &it->second.fence, VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
1040 "Failed to wait for fence: {}", fence_id);
1041 return;
1042 }
1043 it->second.signaled = true;
1044}
1045
1046void ShaderFoundry::wait_for_fences(const std::vector<FenceID>& fence_ids)
1047{
1048 std::vector<vk::Fence> fences;
1049 for (auto fence_id : fence_ids) {
1050 auto it = m_fences.find(fence_id);
1051 if (it != m_fences.end()) {
1052 fences.push_back(it->second.fence);
1053 }
1054 }
1055
1056 if (!fences.empty()) {
1057 if (get_device().waitForFences(static_cast<uint32_t>(fences.size()), fences.data(), VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
1059 "Failed to wait for fences");
1060 return;
1061 }
1062 }
1063
1064 for (auto fence_id : fence_ids) {
1065 auto it = m_fences.find(fence_id);
1066 if (it != m_fences.end()) {
1067 it->second.signaled = true;
1068 }
1069 }
1070}
1071
1073{
1074 if (fence_id == INVALID_FENCE)
1075 return;
1076
1077 auto it = m_fences.find(fence_id);
1078 if (it == m_fences.end())
1079 return;
1080
1081 if (it->second.fence) {
1082 get_device().destroyFence(it->second.fence);
1083 it->second.fence = nullptr;
1084 }
1085
1086 const CommandBufferID cmd_id = it->second.cmd_id;
1087 m_fences.erase(it);
1088
1089 if (cmd_id != INVALID_COMMAND_BUFFER) {
1090 auto cmd_it = m_command_buffers.find(cmd_id);
1091 if (cmd_it != m_command_buffers.end()) {
1092 m_backend->get_command_manager().free_command_buffer(cmd_it->second.cmd);
1093 m_command_buffers.erase(cmd_it);
1094 }
1095 }
1096}
1097
1099{
1100 auto it = m_fences.find(fence_id);
1101 if (it == m_fences.end()) {
1102 return false;
1103 }
1104
1105 if (it->second.signaled) {
1106 return true;
1107 }
1108
1109 auto result = get_device().getFenceStatus(it->second.fence);
1110 it->second.signaled = (result == vk::Result::eSuccess);
1111 return it->second.signaled;
1112}
1113
1115 CommandBufferType type,
1116 SemaphoreID wait_semaphore,
1117 vk::PipelineStageFlags /*wait_stage*/)
1118{
1119 auto sem_it = m_semaphores.find(wait_semaphore);
1120 if (sem_it == m_semaphores.end()) {
1122 }
1123
1124 return begin_commands(type);
1125}
1126
1128{
1129 auto it = m_semaphores.find(semaphore_id);
1130 if (it != m_semaphores.end()) {
1131 return it->second.semaphore;
1132 }
1133 return nullptr;
1134}
1135
1137{
1138 auto device = get_device();
1139
1140 for (auto& [id, state] : m_fences) {
1141 if (state.fence) {
1142 device.destroyFence(state.fence);
1143 }
1144 }
1145 m_fences.clear();
1146
1147 for (auto& [id, state] : m_semaphores) {
1148 if (state.semaphore) {
1149 device.destroySemaphore(state.semaphore);
1150 }
1151 }
1152 m_semaphores.clear();
1153
1155 "Cleaned up sync objects");
1156}
1157
1158//==============================================================================
1159// Memory Barriers
1160//==============================================================================
1161
1163 CommandBufferID cmd_id,
1164 vk::Buffer buffer,
1165 vk::AccessFlags src_access,
1166 vk::AccessFlags dst_access,
1167 vk::PipelineStageFlags src_stage,
1168 vk::PipelineStageFlags dst_stage)
1169{
1170 auto it = m_command_buffers.find(cmd_id);
1171 if (it == m_command_buffers.end()) {
1172 return;
1173 }
1174
1175 vk::BufferMemoryBarrier barrier;
1176 barrier.srcAccessMask = src_access;
1177 barrier.dstAccessMask = dst_access;
1178 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1179 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1180 barrier.buffer = buffer;
1181 barrier.offset = 0;
1182 barrier.size = VK_WHOLE_SIZE;
1183
1184 it->second.cmd.pipelineBarrier(
1185 src_stage,
1186 dst_stage,
1187 vk::DependencyFlags {},
1188 0, nullptr,
1189 1, &barrier,
1190 0, nullptr);
1191}
1192
1194 CommandBufferID cmd_id,
1195 vk::Image image,
1196 vk::ImageLayout old_layout,
1197 vk::ImageLayout new_layout,
1198 vk::AccessFlags src_access,
1199 vk::AccessFlags dst_access,
1200 vk::PipelineStageFlags src_stage,
1201 vk::PipelineStageFlags dst_stage)
1202{
1203 auto it = m_command_buffers.find(cmd_id);
1204 if (it == m_command_buffers.end()) {
1205 return;
1206 }
1207
1208 vk::ImageMemoryBarrier barrier;
1209 barrier.srcAccessMask = src_access;
1210 barrier.dstAccessMask = dst_access;
1211 barrier.oldLayout = old_layout;
1212 barrier.newLayout = new_layout;
1213 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1214 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1215 barrier.image = image;
1216 barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
1217 barrier.subresourceRange.baseMipLevel = 0;
1218 barrier.subresourceRange.levelCount = 1;
1219 barrier.subresourceRange.baseArrayLayer = 0;
1220 barrier.subresourceRange.layerCount = 1;
1221
1222 it->second.cmd.pipelineBarrier(
1223 src_stage,
1224 dst_stage,
1225 vk::DependencyFlags {},
1226 0, nullptr,
1227 0, nullptr,
1228 1, &barrier);
1229}
1230
1231//==============================================================================
1232// Queue Management
1233//==============================================================================
1234
1238
1239//==============================================================================
1240// Profiling
1241//==============================================================================
1242
1243void ShaderFoundry::begin_timestamp(CommandBufferID cmd_id, const std::string& label)
1244{
1245 auto it = m_command_buffers.find(cmd_id);
1246 if (it == m_command_buffers.end()) {
1247 return;
1248 }
1249
1250 if (!it->second.timestamp_pool) {
1251 vk::QueryPoolCreateInfo pool_info;
1252 pool_info.queryType = vk::QueryType::eTimestamp;
1253 pool_info.queryCount = 128;
1254 it->second.timestamp_pool = get_device().createQueryPool(pool_info);
1255 }
1256
1257 auto query_index = static_cast<uint32_t>(it->second.timestamp_queries.size() * 2);
1258 it->second.timestamp_queries[label] = query_index;
1259
1260 it->second.cmd.resetQueryPool(it->second.timestamp_pool, query_index, 2);
1261 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, it->second.timestamp_pool, query_index);
1262}
1263
1264void ShaderFoundry::end_timestamp(CommandBufferID cmd_id, const std::string& label)
1265{
1266 auto it = m_command_buffers.find(cmd_id);
1267 if (it == m_command_buffers.end()) {
1268 return;
1269 }
1270
1271 auto query_it = it->second.timestamp_queries.find(label);
1272 if (query_it == it->second.timestamp_queries.end()) {
1273 return;
1274 }
1275
1276 uint32_t query_index = query_it->second;
1277 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, it->second.timestamp_pool, query_index + 1);
1278}
1279
1281{
1282 auto it = m_command_buffers.find(cmd_id);
1283 if (it == m_command_buffers.end()) {
1284 return { .label = label, .duration_ns = 0, .valid = false };
1285 }
1286
1287 auto query_it = it->second.timestamp_queries.find(label);
1288 if (query_it == it->second.timestamp_queries.end()) {
1289 return { .label = label, .duration_ns = 0, .valid = false };
1290 }
1291
1292 if (!it->second.timestamp_pool) {
1293 return { .label = label, .duration_ns = 0, .valid = false };
1294 }
1295
1296 uint32_t query_index = query_it->second;
1297 uint64_t timestamps[2];
1298
1299 auto result = get_device().getQueryPoolResults(
1300 it->second.timestamp_pool,
1301 query_index,
1302 2,
1303 sizeof(timestamps),
1304 timestamps,
1305 sizeof(uint64_t),
1306 vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
1307
1308 if (result != vk::Result::eSuccess) {
1309 return { .label = label, .duration_ns = 0, .valid = false };
1310 }
1311
1312 auto props = m_backend->get_context().get_physical_device().getProperties();
1313 float timestamp_period = props.limits.timestampPeriod;
1314
1315 auto duration_ns = static_cast<uint64_t>((timestamps[1] - timestamps[0]) * timestamp_period);
1316
1317 return { .label = label, .duration_ns = duration_ns, .valid = true };
1318}
1319
1320//==============================================================================
1321// Internal Access
1322//==============================================================================
1323
1324std::shared_ptr<Core::VKShaderModule> ShaderFoundry::get_vk_shader_module(ShaderID shader_id)
1325{
1326 auto& foundry = ShaderFoundry::instance();
1327 auto it = foundry.m_shaders.find(shader_id);
1328 if (it != foundry.m_shaders.end()) {
1329 return it->second.module;
1330 }
1331 return nullptr;
1332}
1333
1334//==============================================================================
1335// Utilities
1336//==============================================================================
1337
1338vk::ShaderStageFlagBits ShaderFoundry::to_vulkan_stage(ShaderStage stage)
1339{
1340 switch (stage) {
1342 return vk::ShaderStageFlagBits::eCompute;
1344 return vk::ShaderStageFlagBits::eVertex;
1346 return vk::ShaderStageFlagBits::eFragment;
1348 return vk::ShaderStageFlagBits::eGeometry;
1350 return vk::ShaderStageFlagBits::eTessellationControl;
1352 return vk::ShaderStageFlagBits::eTessellationEvaluation;
1353 case ShaderStage::MESH:
1354 return vk::ShaderStageFlagBits::eMeshEXT;
1355 case ShaderStage::TASK:
1356 return vk::ShaderStageFlagBits::eTaskEXT;
1357 default:
1358 return vk::ShaderStageFlagBits::eCompute;
1359 }
1360}
1361
1362std::optional<ShaderStage> ShaderFoundry::detect_stage_from_extension(const std::string& filepath)
1363{
1364 auto vk_stage = Core::VKShaderModule::detect_stage_from_extension(filepath);
1365 if (!vk_stage.has_value()) {
1366 return std::nullopt;
1367 }
1368
1369 switch (*vk_stage) {
1370 case vk::ShaderStageFlagBits::eCompute:
1371 return ShaderStage::COMPUTE;
1372 case vk::ShaderStageFlagBits::eVertex:
1373 return ShaderStage::VERTEX;
1374 case vk::ShaderStageFlagBits::eFragment:
1375 return ShaderStage::FRAGMENT;
1376 case vk::ShaderStageFlagBits::eGeometry:
1377 return ShaderStage::GEOMETRY;
1378 case vk::ShaderStageFlagBits::eTessellationControl:
1380 case vk::ShaderStageFlagBits::eTessellationEvaluation:
1382 case vk::ShaderStageFlagBits::eMeshEXT:
1383 return ShaderStage::MESH;
1384 case vk::ShaderStageFlagBits::eTaskEXT:
1385 return ShaderStage::TASK;
1386 default:
1387 return std::nullopt;
1388 }
1389}
1390
1391//==============================================================================
1392// Private Helpers
1393//==============================================================================
1394
1395std::shared_ptr<Core::VKShaderModule> ShaderFoundry::create_shader_module()
1396{
1397 return std::make_shared<Core::VKShaderModule>();
1398}
1399
1401{
1402 return m_backend->get_context().get_device();
1403}
1404
1405vk::PhysicalDevice ShaderFoundry::get_physical_device() const
1406{
1407 return m_backend->get_context().get_physical_device();
1408}
1409
1410} // namespace MayaFlux::Portal::Graphics
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
vk::CommandBuffer cmd
IO::ImageData image
Definition Decoder.cpp:64
float value
float offset
static std::optional< vk::ShaderStageFlagBits > detect_stage_from_extension(const std::string &filepath)
Auto-detect shader stage from file extension.
vk::DescriptorSet get_descriptor_set(DescriptorSetID descriptor_set_id)
Get Vulkan descriptor set handle from DescriptorSetID.
vk::Queue get_graphics_queue() const
Get Vulkan graphics queue.
void wait_for_fences(const std::vector< FenceID > &fence_ids)
Wait for multiple fences to be signaled.
CommandBufferID begin_secondary_commands(vk::Format color_format, vk::Format depth_format=vk::Format::eUndefined)
Begin recording a secondary command buffer for dynamic rendering.
static std::optional< ShaderStage > detect_stage_from_extension(const std::string &filepath)
Auto-detect shader stage from file extension.
vk::Device get_device() const
Get logical device handle.
void shutdown()
Shutdown and cleanup all ShaderFoundry resources.
void wait_for_fence(FenceID fence_id)
Wait for fence to be signaled.
bool is_cached(const std::string &cache_key) const
Check if shader is cached.
std::shared_ptr< Core::VKShaderModule > compile(const ShaderSource &shader_source)
Compile shader from ShaderSource descriptor.
ShaderID reload_shader(const std::string &filepath)
Hot-reload shader (returns new ID)
std::shared_ptr< Core::VKShaderModule > hot_reload(const std::string &filepath)
Hot-reload a shader from file.
void set_config(const ShaderCompilerConfig &config)
Update compiler configuration.
void update_descriptor_storage_image(DescriptorSetID descriptor_set_id, uint32_t binding, vk::ImageView image_view, vk::ImageLayout layout=vk::ImageLayout::eGeneral)
Update descriptor set with storage image binding.
CommandBufferID begin_commands(CommandBufferType type)
Begin recording command buffer.
vk::Semaphore get_semaphore_handle(SemaphoreID semaphore_id)
Get Vulkan fence handle from FenceID.
std::shared_ptr< Core::VKShaderModule > get_vk_shader_module(ShaderID shader_id)
DetectedSourceType
Internal enum for source type detection.
bool initialize(const std::shared_ptr< Core::VulkanBackend > &backend, const ShaderCompilerConfig &config={})
Initialize shader compiler.
std::unordered_map< std::string, ShaderID > m_shader_filepath_cache
void release_fence(FenceID fence_id)
Destroy the fence and free its associated command buffer.
DetectedSourceType detect_source_type(const std::string &content) const
std::unordered_map< FenceID, FenceState > m_fences
std::shared_ptr< Core::VKShaderModule > create_shader_module()
void free_all_command_buffers()
Free all allocated command buffers.
void invalidate_cache(const std::string &cache_key)
Invalidate cache for specific shader.
TimestampResult get_timestamp_result(CommandBufferID cmd_id, const std::string &label)
void update_descriptor_image(DescriptorSetID descriptor_set_id, uint32_t binding, vk::ImageView image_view, vk::Sampler sampler, vk::ImageLayout layout=vk::ImageLayout::eShaderReadOnlyOptimal, uint32_t array_element=0)
Update descriptor set with image binding.
void add_define(const std::string &name, const std::string &value="")
Add preprocessor define for shader compilation.
std::unordered_map< std::string, std::shared_ptr< Core::VKShaderModule > > m_shader_cache
static vk::ShaderStageFlagBits to_vulkan_stage(ShaderStage stage)
Convert Portal ShaderStage to Vulkan ShaderStageFlagBits.
void update_descriptor_buffer(DescriptorSetID descriptor_set_id, uint32_t binding, vk::DescriptorType type, vk::Buffer buffer, size_t offset, size_t size)
Update descriptor set with buffer binding.
CommandBufferID begin_commands_with_wait(CommandBufferType type, SemaphoreID wait_semaphore, vk::PipelineStageFlags wait_stage)
Begin command buffer that waits on a semaphore.
std::optional< std::filesystem::path > resolve_shader_path(const std::string &filepath) const
void stop()
Stop active command recording and free command buffers.
std::unordered_map< DescriptorSetID, DescriptorSetState > m_descriptor_sets
std::unordered_map< CommandBufferID, CommandBufferState > m_command_buffers
SemaphoreID submit_with_signal(CommandBufferID cmd_id)
Submit command buffer asynchronously, returning a semaphore.
std::shared_ptr< Core::VKDescriptorManager > m_global_descriptor_manager
vk::Queue get_compute_queue() const
Get Vulkan compute queue.
FenceID submit_async(CommandBufferID cmd_id)
Submit command buffer asynchronously, returning a fence.
std::string get_shader_entry_point(ShaderID shader_id)
Get entry point name for compiled shader.
std::shared_ptr< Core::VKShaderModule > compile_from_source_cached(const std::string &source, ShaderStage stage, const std::string &cache_key, const std::string &entry_point="main")
bool end_commands(CommandBufferID cmd_id)
End recording command buffer.
bool is_initialized() const
Check if compiler is initialized.
void destroy_shader(ShaderID shader_id)
Destroy shader (cleanup internal state)
void begin_timestamp(CommandBufferID cmd_id, const std::string &label="")
void image_barrier(CommandBufferID cmd_id, vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, vk::AccessFlags src_access, vk::AccessFlags dst_access, vk::PipelineStageFlags src_stage, vk::PipelineStageFlags dst_stage)
Insert image memory barrier.
void submit_and_wait(CommandBufferID cmd_id)
Submit command buffer and wait for completion.
vk::CommandBuffer get_command_buffer(CommandBufferID cmd_id)
Get Vulkan command buffer handle from CommandBufferID.
vk::Queue get_transfer_queue() const
Get Vulkan transfer queue.
void add_include_directory(const std::string &directory)
Add include directory for shader compilation.
std::vector< std::string > get_cached_keys() const
Get all cached shader keys.
void clear_cache()
Invalidate entire shader cache.
ShaderReflectionInfo get_shader_reflection(ShaderID shader_id)
Get reflection info for compiled shader.
vk::PhysicalDevice get_physical_device() const
Get physical device handle.
std::shared_ptr< Core::VKShaderModule > compile_from_source(const std::string &source, ShaderStage stage, const std::string &entry_point="main")
void buffer_barrier(CommandBufferID cmd_id, vk::Buffer buffer, vk::AccessFlags src_access, vk::AccessFlags dst_access, vk::PipelineStageFlags src_stage, vk::PipelineStageFlags dst_stage)
Insert buffer memory barrier.
std::atomic< uint64_t > m_next_descriptor_set_id
std::shared_ptr< Core::VKShaderModule > compile_from_spirv_asm(const std::string &spirv_asm, ShaderStage stage, const std::string &entry_point="main")
std::shared_ptr< Core::VKShaderModule > compile_from_file(const std::string &filepath, std::optional< ShaderStage > stage=std::nullopt, const std::string &entry_point="main")
void end_timestamp(CommandBufferID cmd_id, const std::string &label="")
std::shared_ptr< Core::VKShaderModule > compile_from_spirv(const std::string &spirv_path, ShaderStage stage, const std::string &entry_point="main")
ShaderID load_shader(const std::string &content, std::optional< ShaderStage > stage=std::nullopt, const std::string &entry_point="main")
Universal shader loader - auto-detects source type.
std::string generate_source_cache_key(const std::string &source, ShaderStage stage) const
std::shared_ptr< Core::VulkanBackend > m_backend
bool is_fence_signaled(FenceID fence_id)
Check if fence is signaled.
std::unordered_map< SemaphoreID, SemaphoreState > m_semaphores
ShaderStage get_shader_stage(ShaderID shader_id)
Get shader stage for compiled shader.
std::unordered_map< ShaderID, ShaderState > m_shaders
DescriptorSetID allocate_descriptor_set(vk::DescriptorSetLayout layout)
Allocate descriptor set for a pipeline.
@ ShaderCompilation
Shader compilation tasks (Portal::Graphics::ShaderCompiler)
@ Portal
High-level user-facing API layer.
std::string emit_glsl_kernel(const ShaderSpec &spec)
Emit a complete GLSL compute shader from spec metadata and a KernelSource body.
std::string emit_spirv_asm(const ShaderSpec &spec)
Emit complete SPIR-V assembly text for a generated compute kernel.
constexpr ShaderID INVALID_SHADER
ShaderStage
User-friendly shader stage enum.
constexpr FenceID INVALID_FENCE
constexpr SemaphoreID INVALID_SEMAPHORE
constexpr CommandBufferID INVALID_COMMAND_BUFFER
Extracted push constant range from shader reflection.
bool enable_reflection
Extract descriptor bindings and metadata.
std::vector< std::string > include_directories
Paths for #include resolution.
std::unordered_map< std::string, std::string > defines
Preprocessor macros.
Configuration for shader compilation.
std::shared_ptr< Core::VKShaderModule > std::string filepath
std::optional< std::array< uint32_t, 3 > > workgroup_size
std::vector< PushConstantRangeInfo > push_constant_ranges
std::vector< DescriptorBindingInfo > descriptor_bindings
Extracted reflection information from compiled shader.
std::string content
Shader source code, SPIR-V path, or SPIR-V assembly text.
enum MayaFlux::Portal::Graphics::ShaderSource::SourceType type
@ SPIRV_ASM
In-memory SPIR-V assembly text (assembled via SPIRV-Tools, no shaderc)
Shader source descriptor for compilation.
std::optional< KernelSource > kernel
When set, KernelOp is ignored.
Complete declarative description of a generated compute shader.