MayaFlux 0.4.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(const ShaderSource& shader_source)
251{
252 switch (shader_source.type) {
254 return compile_from_file(shader_source.content, shader_source.stage, shader_source.entry_point);
255
257 return compile_from_source(shader_source.content, shader_source.stage, shader_source.entry_point);
258
260 return compile_from_spirv(shader_source.content, shader_source.stage, shader_source.entry_point);
261
262 default:
264 "Unknown shader source type");
265 return nullptr;
266 }
267}
268
270 const std::string& content,
271 std::optional<ShaderStage> stage,
272 const std::string& entry_point)
273{
274 if (!is_initialized()) {
276 "ShaderFoundry not initialized");
277 return INVALID_SHADER;
278 }
279
280 DetectedSourceType source_type = detect_source_type(content);
281
282 std::string cache_key;
283 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
284 cache_key = content;
285 } else {
286 cache_key = generate_source_cache_key(content, stage.value_or(ShaderStage::COMPUTE));
287 }
288
289 auto id_it = m_shader_filepath_cache.find(cache_key);
290 if (id_it != m_shader_filepath_cache.end()) {
292 "Using cached shader ID for: {}", cache_key);
293 return id_it->second;
294 }
295
296 if (!stage.has_value()) {
297 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
298 if (source_type == DetectedSourceType::FILE_SPIRV) {
299 std::filesystem::path p(content);
300 std::string stem = p.stem().string();
301 stage = detect_stage_from_extension(stem);
302 } else {
303 stage = detect_stage_from_extension(content);
304 }
305 }
306
307 if (!stage.has_value()) {
309 "Cannot auto-detect shader stage from '{}' - must specify explicitly",
310 content);
311 return INVALID_SHADER;
312 }
313 }
314
315 std::shared_ptr<Core::VKShaderModule> shader_module;
316
317 switch (source_type) {
319 shader_module = compile_from_file(content, stage, entry_point);
320 break;
322 shader_module = compile_from_spirv(content, *stage, entry_point);
323 break;
325 shader_module = compile_from_source(content, *stage, entry_point);
326 break;
327 default:
329 "Cannot determine shader source type");
330 return INVALID_SHADER;
331 }
332
333 if (!shader_module) {
334 return INVALID_SHADER;
335 }
336
338
339 ShaderState& state = m_shaders[id];
340 state.module = shader_module;
341 state.filepath = cache_key;
342 state.stage = *stage;
343 state.entry_point = entry_point;
344
345 m_shader_filepath_cache[cache_key] = id;
346
348 "Shader loaded: {} (ID: {}, stage: {})",
349 cache_key, id, static_cast<int>(*stage));
350
351 return id;
352}
353
355{
356 return load_shader(source.content, source.stage, source.entry_point);
357}
358
359std::optional<std::filesystem::path> ShaderFoundry::resolve_shader_path(const std::string& filepath) const
360{
361 namespace fs = std::filesystem;
362
363 fs::path path(filepath);
364
365 if (path.is_absolute() || fs::exists(filepath)) {
366 return path;
367 }
368
369 std::vector<std::string> search_paths = {
370 Core::SHADER_BUILD_OUTPUT_DIR,
371 Core::SHADER_INSTALL_DIR,
372 Core::SHADER_SOURCE_DIR,
373 "./shaders",
374 "../shaders",
375 "data/shaders",
376 "./data/shaders",
377 "../data/shaders"
378 };
379
380 if (std::string_view(Core::SHADER_EXAMPLE_DIR).length() > 0) {
381 search_paths.emplace_back(Core::SHADER_EXAMPLE_DIR);
382 }
383
384#ifdef MAYAFLUX_PROJECT_SHADER_DIR
385 search_paths.emplace_back(MAYAFLUX_PROJECT_SHADER_DIR);
386#endif
387
388 for (const auto& search_path : search_paths) {
389 fs::path full_path = fs::path(search_path) / filepath;
390 if (fs::exists(full_path)) {
391 return full_path;
392 }
393 }
394
395 return std::nullopt;
396}
397
399{
400 auto resolved_path = resolve_shader_path(content);
401
402 if (resolved_path.has_value()) {
403 std::string ext = resolved_path->extension().string();
404 std::ranges::transform(ext, ext.begin(), ::tolower);
405
406 if (ext == ".spv") {
408 }
409
411 }
412
413 if (content.size() > 1024 || content.find('\n') != std::string::npos) {
415 }
416
418}
419
420std::string ShaderFoundry::generate_source_cache_key(const std::string& source, ShaderStage stage) const
421{
422 std::hash<std::string> hasher;
423 size_t hash = hasher(source + std::to_string(static_cast<int>(stage)));
424 return "source_" + std::to_string(hash);
425}
426
427ShaderID ShaderFoundry::reload_shader(const std::string& filepath)
428{
429 invalidate_cache(filepath);
430
431 auto cache_it = m_shader_filepath_cache.find(filepath);
432 if (cache_it != m_shader_filepath_cache.end()) {
433 destroy_shader(cache_it->second);
434 m_shader_filepath_cache.erase(cache_it);
435 }
436
437 return load_shader(filepath);
438}
439
441{
442 auto it = m_shaders.find(shader_id);
443 if (it != m_shaders.end()) {
444 if (!it->second.filepath.empty()) {
445 m_shader_filepath_cache.erase(it->second.filepath);
446 m_shader_cache.erase(it->second.filepath);
447 }
448 if (it->second.module) {
449 it->second.module->cleanup(get_device());
450 }
451 m_shaders.erase(it);
452 }
453}
454
455//==============================================================================
456// Shader Introspection
457//==============================================================================
458
460{
461 auto it = m_shaders.find(shader_id);
462 if (it == m_shaders.end()) {
463 return {};
464 }
465
466 const auto& reflection = it->second.module->get_reflection();
467
469 info.stage = it->second.stage;
470 info.entry_point = it->second.entry_point;
471 info.workgroup_size = reflection.workgroup_size;
472
473 for (const auto& binding : reflection.bindings) {
474 info.descriptor_bindings.push_back({ .set = binding.set,
475 .binding = binding.binding,
476 .type = binding.type,
477 .name = binding.name });
478 }
479
480 for (const auto& pc : reflection.push_constants) {
481 PushConstantRangeInfo pc_info {};
482 pc_info.offset = pc.offset;
483 pc_info.size = pc.size;
484 info.push_constant_ranges.push_back(pc_info);
485 }
486
487 return info;
488}
489
491{
492 auto it = m_shaders.find(shader_id);
493 if (it != m_shaders.end()) {
494 return it->second.stage;
495 }
497}
498
500{
501 auto it = m_shaders.find(shader_id);
502 if (it != m_shaders.end()) {
503 return it->second.entry_point;
504 }
505 return "main";
506}
507
508bool ShaderFoundry::is_cached(const std::string& cache_key) const
509{
510 return m_shader_cache.find(cache_key) != m_shader_cache.end();
511}
512
513std::vector<std::string> ShaderFoundry::get_cached_keys() const
514{
515 std::vector<std::string> keys;
516 keys.reserve(m_shader_cache.size());
517 for (const auto& [key, _] : m_shader_cache) {
518 keys.push_back(key);
519 }
520 return keys;
521}
522
524{
525 auto device = get_device();
526
527 for (auto& [key, shader_module] : m_shader_cache) {
528 if (shader_module) {
529 shader_module->cleanup(device);
530 }
531 }
532
533 m_shader_cache.clear();
534 m_shaders.clear();
536
538 "Cleaned up shader modules");
539}
540
541//==============================================================================
542// Hot-Reload Support
543//==============================================================================
544
545void ShaderFoundry::invalidate_cache(const std::string& cache_key)
546{
547 auto it = m_shader_cache.find(cache_key);
548 if (it != m_shader_cache.end()) {
549 m_shader_cache.erase(it);
551 "Invalidated shader cache: {}", cache_key);
552 }
553}
554
561
562std::shared_ptr<Core::VKShaderModule> ShaderFoundry::hot_reload(const std::string& filepath)
563{
564 invalidate_cache(filepath);
565 return compile_from_file(filepath);
566}
567
568//==============================================================================
569// Configuration
570//==============================================================================
571
573{
574 m_config = config;
576 "Updated shader compiler configuration");
577}
578
579void ShaderFoundry::add_include_directory(const std::string& directory)
580{
581 m_config.include_directories.push_back(directory);
582}
583
584void ShaderFoundry::add_define(const std::string& name, const std::string& value)
585{
586 m_config.defines[name] = value;
587}
588
589//==============================================================================
590// Descriptor Management
591//==============================================================================
592
594{
596
598 state.descriptor_set = m_global_descriptor_manager->allocate_set(get_device(), layout);
599
600 return id;
601}
602
604 DescriptorSetID descriptor_set_id,
605 uint32_t binding,
606 vk::DescriptorType type,
607 vk::Buffer buffer,
608 size_t offset,
609 size_t size)
610{
611 auto it = m_descriptor_sets.find(descriptor_set_id);
612 if (it == m_descriptor_sets.end()) {
613 return;
614 }
615
616 vk::DescriptorBufferInfo buffer_info;
617 buffer_info.buffer = buffer;
618 buffer_info.offset = offset;
619 buffer_info.range = size;
620
621 vk::WriteDescriptorSet write;
622 write.dstSet = it->second.descriptor_set;
623 write.dstBinding = binding;
624 write.dstArrayElement = 0;
625 write.descriptorCount = 1;
626 write.descriptorType = type;
627 write.pBufferInfo = &buffer_info;
628
629 get_device().updateDescriptorSets(1, &write, 0, nullptr);
630}
631
633 DescriptorSetID descriptor_set_id,
634 uint32_t binding,
635 vk::ImageView image_view,
636 vk::Sampler sampler,
637 vk::ImageLayout layout,
638 uint32_t array_element)
639{
640 auto it = m_descriptor_sets.find(descriptor_set_id);
641 if (it == m_descriptor_sets.end()) {
642 return;
643 }
644
645 vk::DescriptorImageInfo image_info;
646 image_info.imageView = image_view;
647 image_info.sampler = sampler;
648 image_info.imageLayout = layout;
649
650 vk::WriteDescriptorSet write;
651 write.dstSet = it->second.descriptor_set;
652 write.dstBinding = binding;
653 write.dstArrayElement = array_element;
654 write.descriptorCount = 1;
655 write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
656 write.pImageInfo = &image_info;
657
658 get_device().updateDescriptorSets(1, &write, 0, nullptr);
659}
660
662 DescriptorSetID descriptor_set_id,
663 uint32_t binding,
664 vk::ImageView image_view,
665 vk::ImageLayout layout)
666{
667 auto it = m_descriptor_sets.find(descriptor_set_id);
668 if (it == m_descriptor_sets.end()) {
669 return;
670 }
671
672 vk::DescriptorImageInfo image_info;
673 image_info.imageView = image_view;
674 image_info.imageLayout = layout;
675
676 vk::WriteDescriptorSet write;
677 write.dstSet = it->second.descriptor_set;
678 write.dstBinding = binding;
679 write.dstArrayElement = 0;
680 write.descriptorCount = 1;
681 write.descriptorType = vk::DescriptorType::eStorageImage;
682 write.pImageInfo = &image_info;
683
684 get_device().updateDescriptorSets(1, &write, 0, nullptr);
685}
686
687vk::DescriptorSet ShaderFoundry::get_descriptor_set(DescriptorSetID descriptor_set_id)
688{
689 auto it = m_descriptor_sets.find(descriptor_set_id);
690 if (it == m_descriptor_sets.end()) {
691 error<std::invalid_argument>(
694 std::source_location::current(),
695 "Invalid DescriptorSetID: {}", descriptor_set_id);
696 }
697 return it->second.descriptor_set;
698}
699
701{
702 auto device = get_device();
703
704 m_descriptor_sets.clear();
705
707 m_global_descriptor_manager->cleanup(device);
709 }
710
712 "Cleaned up descriptor resources");
713}
714
715//==============================================================================
716// Command Recording
717//==============================================================================
718
720{
721 auto& cmd_manager = m_backend->get_command_manager();
722
724
726
727 state.cmd = (type == CommandBufferType::COMPUTE)
728 ? cmd_manager.begin_single_time_commands_compute()
729 : cmd_manager.begin_single_time_commands();
730
731 state.type = type;
732 state.is_active = true;
733
734 return id;
735}
736
738 vk::Format color_format,
739 vk::Format depth_format)
740{
741 auto& cmd_manager = m_backend->get_command_manager();
742
744
745 vk::CommandBuffer cmd = cmd_manager.allocate_command_buffer(vk::CommandBufferLevel::eSecondary);
746
747 vk::CommandBufferInheritanceRenderingInfo inheritance_rendering;
748 inheritance_rendering.colorAttachmentCount = 1;
749 inheritance_rendering.pColorAttachmentFormats = &color_format;
750 inheritance_rendering.depthAttachmentFormat = depth_format;
751 inheritance_rendering.rasterizationSamples = vk::SampleCountFlagBits::e1;
752
753 vk::CommandBufferInheritanceInfo inheritance_info;
754 inheritance_info.pNext = &inheritance_rendering;
755
756 vk::CommandBufferBeginInfo begin_info;
757 begin_info.flags = vk::CommandBufferUsageFlagBits::eRenderPassContinue | vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
758 begin_info.pInheritanceInfo = &inheritance_info;
759
760 cmd.begin(begin_info);
761
763 state.cmd = cmd;
766 state.is_active = true;
767
768 return id;
769}
770
772{
773 auto it = m_command_buffers.find(cmd_id);
774 if (it != m_command_buffers.end()) {
775 return it->second.cmd;
776 }
777 return nullptr;
778}
779
781{
782 auto it = m_command_buffers.find(cmd_id);
783 if (it == m_command_buffers.end() || !it->second.is_active) {
784 return false;
785 }
786
787 it->second.cmd.end();
788 it->second.is_active = false;
789 return true;
790}
791
793{
794 if (!m_backend || !s_initialized) {
795 return;
796 }
797
798 auto& cmd_manager = m_backend->get_command_manager();
799 auto device = get_device();
800 device.waitIdle();
801
802 for (auto& [id, state] : m_command_buffers) {
803 if (state.is_active) {
804 cmd_manager.free_command_buffer(state.cmd);
805 }
806 if (state.timestamp_pool) {
807 device.destroyQueryPool(state.timestamp_pool);
808 }
809 }
810 m_command_buffers.clear();
811
813 "Freed all command buffers");
814}
815
816//==============================================================================
817// Synchronization
818//==============================================================================
819
821{
822 auto it = m_command_buffers.find(cmd_id);
823 if (it == m_command_buffers.end() || !it->second.is_active) {
824 return;
825 }
826
827 auto& cmd_manager = m_backend->get_command_manager();
828
829 it->second.cmd.end();
830
831 vk::SubmitInfo submit_info;
832 submit_info.commandBufferCount = 1;
833 submit_info.pCommandBuffers = &it->second.cmd;
834
835 vk::Queue queue;
836 switch (it->second.type) {
838 queue = m_graphics_queue;
839 break;
841 queue = m_compute_queue;
842 break;
844 queue = m_transfer_queue;
845 break;
846 }
847
848 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
850 "Failed to submit command buffer");
851 return;
852 }
853 queue.waitIdle();
854
855 cmd_manager.free_command_buffer(it->second.cmd);
856
857 it->second.is_active = false;
858 m_command_buffers.erase(it);
859}
860
862{
863 auto cmd_it = m_command_buffers.find(cmd_id);
864 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
865 return INVALID_FENCE;
866 }
867
868 cmd_it->second.cmd.end();
869
870 FenceID fence_id = m_next_fence_id++;
871
872 vk::FenceCreateInfo fence_info;
873 FenceState& fence_state = m_fences[fence_id];
874 fence_state.fence = get_device().createFence(fence_info);
875 fence_state.signaled = false;
876
877 vk::SubmitInfo submit_info;
878 submit_info.commandBufferCount = 1;
879 submit_info.pCommandBuffers = &cmd_it->second.cmd;
880
881 vk::Queue queue;
882 switch (cmd_it->second.type) {
884 queue = m_graphics_queue;
885 break;
887 queue = m_compute_queue;
888 break;
890 queue = m_transfer_queue;
891 break;
892 }
893
894 if (queue.submit(1, &submit_info, fence_state.fence) != vk::Result::eSuccess) {
896 "Failed to submit command buffer");
897 return INVALID_FENCE;
898 }
899
900 cmd_it->second.is_active = false;
901
902 return fence_id;
903}
904
906{
907 auto cmd_it = m_command_buffers.find(cmd_id);
908 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
909 return INVALID_SEMAPHORE;
910 }
911
912 cmd_it->second.cmd.end();
913
914 SemaphoreID semaphore_id = m_next_semaphore_id++;
915
916 vk::SemaphoreCreateInfo semaphore_info;
917 SemaphoreState& semaphore_state = m_semaphores[semaphore_id];
918 semaphore_state.semaphore = get_device().createSemaphore(semaphore_info);
919
920 vk::SubmitInfo submit_info;
921 submit_info.commandBufferCount = 1;
922 submit_info.pCommandBuffers = &cmd_it->second.cmd;
923 submit_info.signalSemaphoreCount = 1;
924 submit_info.pSignalSemaphores = &semaphore_state.semaphore;
925
926 vk::Queue queue;
927 switch (cmd_it->second.type) {
929 queue = m_graphics_queue;
930 break;
932 queue = m_compute_queue;
933 break;
935 queue = m_transfer_queue;
936 break;
937 }
938
939 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
941 "Failed to submit command buffer");
942 return INVALID_SEMAPHORE;
943 }
944
945 cmd_it->second.is_active = false;
946
947 return semaphore_id;
948}
949
951{
952 auto it = m_fences.find(fence_id);
953 if (it == m_fences.end()) {
954 return;
955 }
956
957 if (get_device().waitForFences(1, &it->second.fence, VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
959 "Failed to wait for fence: {}", fence_id);
960 return;
961 }
962 it->second.signaled = true;
963}
964
965void ShaderFoundry::wait_for_fences(const std::vector<FenceID>& fence_ids)
966{
967 std::vector<vk::Fence> fences;
968 for (auto fence_id : fence_ids) {
969 auto it = m_fences.find(fence_id);
970 if (it != m_fences.end()) {
971 fences.push_back(it->second.fence);
972 }
973 }
974
975 if (!fences.empty()) {
976 if (get_device().waitForFences(static_cast<uint32_t>(fences.size()), fences.data(), VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
978 "Failed to wait for fences");
979 return;
980 }
981 }
982
983 for (auto fence_id : fence_ids) {
984 auto it = m_fences.find(fence_id);
985 if (it != m_fences.end()) {
986 it->second.signaled = true;
987 }
988 }
989}
990
992{
993 auto it = m_fences.find(fence_id);
994 if (it == m_fences.end()) {
995 return false;
996 }
997
998 if (it->second.signaled) {
999 return true;
1000 }
1001
1002 auto result = get_device().getFenceStatus(it->second.fence);
1003 it->second.signaled = (result == vk::Result::eSuccess);
1004 return it->second.signaled;
1005}
1006
1008 CommandBufferType type,
1009 SemaphoreID wait_semaphore,
1010 vk::PipelineStageFlags /*wait_stage*/)
1011{
1012 auto sem_it = m_semaphores.find(wait_semaphore);
1013 if (sem_it == m_semaphores.end()) {
1015 }
1016
1017 return begin_commands(type);
1018}
1019
1021{
1022 auto it = m_semaphores.find(semaphore_id);
1023 if (it != m_semaphores.end()) {
1024 return it->second.semaphore;
1025 }
1026 return nullptr;
1027}
1028
1030{
1031 auto device = get_device();
1032
1033 for (auto& [id, state] : m_fences) {
1034 if (state.fence) {
1035 device.destroyFence(state.fence);
1036 }
1037 }
1038 m_fences.clear();
1039
1040 for (auto& [id, state] : m_semaphores) {
1041 if (state.semaphore) {
1042 device.destroySemaphore(state.semaphore);
1043 }
1044 }
1045 m_semaphores.clear();
1046
1048 "Cleaned up sync objects");
1049}
1050
1051//==============================================================================
1052// Memory Barriers
1053//==============================================================================
1054
1056 CommandBufferID cmd_id,
1057 vk::Buffer buffer,
1058 vk::AccessFlags src_access,
1059 vk::AccessFlags dst_access,
1060 vk::PipelineStageFlags src_stage,
1061 vk::PipelineStageFlags dst_stage)
1062{
1063 auto it = m_command_buffers.find(cmd_id);
1064 if (it == m_command_buffers.end()) {
1065 return;
1066 }
1067
1068 vk::BufferMemoryBarrier barrier;
1069 barrier.srcAccessMask = src_access;
1070 barrier.dstAccessMask = dst_access;
1071 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1072 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1073 barrier.buffer = buffer;
1074 barrier.offset = 0;
1075 barrier.size = VK_WHOLE_SIZE;
1076
1077 it->second.cmd.pipelineBarrier(
1078 src_stage,
1079 dst_stage,
1080 vk::DependencyFlags {},
1081 0, nullptr,
1082 1, &barrier,
1083 0, nullptr);
1084}
1085
1087 CommandBufferID cmd_id,
1088 vk::Image image,
1089 vk::ImageLayout old_layout,
1090 vk::ImageLayout new_layout,
1091 vk::AccessFlags src_access,
1092 vk::AccessFlags dst_access,
1093 vk::PipelineStageFlags src_stage,
1094 vk::PipelineStageFlags dst_stage)
1095{
1096 auto it = m_command_buffers.find(cmd_id);
1097 if (it == m_command_buffers.end()) {
1098 return;
1099 }
1100
1101 vk::ImageMemoryBarrier barrier;
1102 barrier.srcAccessMask = src_access;
1103 barrier.dstAccessMask = dst_access;
1104 barrier.oldLayout = old_layout;
1105 barrier.newLayout = new_layout;
1106 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1107 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1108 barrier.image = image;
1109 barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
1110 barrier.subresourceRange.baseMipLevel = 0;
1111 barrier.subresourceRange.levelCount = 1;
1112 barrier.subresourceRange.baseArrayLayer = 0;
1113 barrier.subresourceRange.layerCount = 1;
1114
1115 it->second.cmd.pipelineBarrier(
1116 src_stage,
1117 dst_stage,
1118 vk::DependencyFlags {},
1119 0, nullptr,
1120 0, nullptr,
1121 1, &barrier);
1122}
1123
1124//==============================================================================
1125// Queue Management
1126//==============================================================================
1127
1131
1132//==============================================================================
1133// Profiling
1134//==============================================================================
1135
1136void ShaderFoundry::begin_timestamp(CommandBufferID cmd_id, const std::string& label)
1137{
1138 auto it = m_command_buffers.find(cmd_id);
1139 if (it == m_command_buffers.end()) {
1140 return;
1141 }
1142
1143 if (!it->second.timestamp_pool) {
1144 vk::QueryPoolCreateInfo pool_info;
1145 pool_info.queryType = vk::QueryType::eTimestamp;
1146 pool_info.queryCount = 128;
1147 it->second.timestamp_pool = get_device().createQueryPool(pool_info);
1148 }
1149
1150 auto query_index = static_cast<uint32_t>(it->second.timestamp_queries.size() * 2);
1151 it->second.timestamp_queries[label] = query_index;
1152
1153 it->second.cmd.resetQueryPool(it->second.timestamp_pool, query_index, 2);
1154 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, it->second.timestamp_pool, query_index);
1155}
1156
1157void ShaderFoundry::end_timestamp(CommandBufferID cmd_id, const std::string& label)
1158{
1159 auto it = m_command_buffers.find(cmd_id);
1160 if (it == m_command_buffers.end()) {
1161 return;
1162 }
1163
1164 auto query_it = it->second.timestamp_queries.find(label);
1165 if (query_it == it->second.timestamp_queries.end()) {
1166 return;
1167 }
1168
1169 uint32_t query_index = query_it->second;
1170 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, it->second.timestamp_pool, query_index + 1);
1171}
1172
1174{
1175 auto it = m_command_buffers.find(cmd_id);
1176 if (it == m_command_buffers.end()) {
1177 return { .label = label, .duration_ns = 0, .valid = false };
1178 }
1179
1180 auto query_it = it->second.timestamp_queries.find(label);
1181 if (query_it == it->second.timestamp_queries.end()) {
1182 return { .label = label, .duration_ns = 0, .valid = false };
1183 }
1184
1185 if (!it->second.timestamp_pool) {
1186 return { .label = label, .duration_ns = 0, .valid = false };
1187 }
1188
1189 uint32_t query_index = query_it->second;
1190 uint64_t timestamps[2];
1191
1192 auto result = get_device().getQueryPoolResults(
1193 it->second.timestamp_pool,
1194 query_index,
1195 2,
1196 sizeof(timestamps),
1197 timestamps,
1198 sizeof(uint64_t),
1199 vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
1200
1201 if (result != vk::Result::eSuccess) {
1202 return { .label = label, .duration_ns = 0, .valid = false };
1203 }
1204
1205 auto props = m_backend->get_context().get_physical_device().getProperties();
1206 float timestamp_period = props.limits.timestampPeriod;
1207
1208 auto duration_ns = static_cast<uint64_t>((timestamps[1] - timestamps[0]) * timestamp_period);
1209
1210 return { .label = label, .duration_ns = duration_ns, .valid = true };
1211}
1212
1213//==============================================================================
1214// Internal Access
1215//==============================================================================
1216
1217std::shared_ptr<Core::VKShaderModule> ShaderFoundry::get_vk_shader_module(ShaderID shader_id)
1218{
1219 auto& foundry = ShaderFoundry::instance();
1220 auto it = foundry.m_shaders.find(shader_id);
1221 if (it != foundry.m_shaders.end()) {
1222 return it->second.module;
1223 }
1224 return nullptr;
1225}
1226
1227//==============================================================================
1228// Utilities
1229//==============================================================================
1230
1231vk::ShaderStageFlagBits ShaderFoundry::to_vulkan_stage(ShaderStage stage)
1232{
1233 switch (stage) {
1235 return vk::ShaderStageFlagBits::eCompute;
1237 return vk::ShaderStageFlagBits::eVertex;
1239 return vk::ShaderStageFlagBits::eFragment;
1241 return vk::ShaderStageFlagBits::eGeometry;
1243 return vk::ShaderStageFlagBits::eTessellationControl;
1245 return vk::ShaderStageFlagBits::eTessellationEvaluation;
1246 case ShaderStage::MESH:
1247 return vk::ShaderStageFlagBits::eMeshEXT;
1248 case ShaderStage::TASK:
1249 return vk::ShaderStageFlagBits::eTaskEXT;
1250 default:
1251 return vk::ShaderStageFlagBits::eCompute;
1252 }
1253}
1254
1255std::optional<ShaderStage> ShaderFoundry::detect_stage_from_extension(const std::string& filepath)
1256{
1257 auto vk_stage = Core::VKShaderModule::detect_stage_from_extension(filepath);
1258 if (!vk_stage.has_value()) {
1259 return std::nullopt;
1260 }
1261
1262 switch (*vk_stage) {
1263 case vk::ShaderStageFlagBits::eCompute:
1264 return ShaderStage::COMPUTE;
1265 case vk::ShaderStageFlagBits::eVertex:
1266 return ShaderStage::VERTEX;
1267 case vk::ShaderStageFlagBits::eFragment:
1268 return ShaderStage::FRAGMENT;
1269 case vk::ShaderStageFlagBits::eGeometry:
1270 return ShaderStage::GEOMETRY;
1271 case vk::ShaderStageFlagBits::eTessellationControl:
1273 case vk::ShaderStageFlagBits::eTessellationEvaluation:
1275 case vk::ShaderStageFlagBits::eMeshEXT:
1276 return ShaderStage::MESH;
1277 case vk::ShaderStageFlagBits::eTaskEXT:
1278 return ShaderStage::TASK;
1279 default:
1280 return std::nullopt;
1281 }
1282}
1283
1284//==============================================================================
1285// Private Helpers
1286//==============================================================================
1287
1288std::shared_ptr<Core::VKShaderModule> ShaderFoundry::create_shader_module()
1289{
1290 return std::make_shared<Core::VKShaderModule>();
1291}
1292
1294{
1295 return m_backend->get_context().get_device();
1296}
1297
1298vk::PhysicalDevice ShaderFoundry::get_physical_device() const
1299{
1300 return m_backend->get_context().get_physical_device();
1301}
1302
1303} // 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:57
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
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_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.
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 or SPIR-V path.
enum MayaFlux::Portal::Graphics::ShaderSource::SourceType type
Shader source descriptor for compilation.