MayaFlux 0.3.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_global_descriptor_manager = std::make_shared<Core::VKDescriptorManager>();
41 m_global_descriptor_manager->initialize(get_device(), 1024);
42
43 m_graphics_queue = m_backend->get_context().get_graphics_queue();
44 m_compute_queue = m_backend->get_context().get_compute_queue();
45 m_transfer_queue = m_backend->get_context().get_transfer_queue();
46
47 s_initialized = true;
48
50 "ShaderFoundry initialized");
51 return true;
52}
53
55{
56 if (!s_initialized || !m_backend) {
57 return;
58 }
59
61 "Stopping ShaderFoundry - freeing command buffers...");
62
63 auto device = get_device();
64
65 device.waitIdle();
66
68
70 "ShaderFoundry stopped - command buffers freed");
71}
72
74{
75 if (!s_initialized) {
76 return;
77 }
78
79 if (!m_backend) {
80 return;
81 }
82
84 "Shutting down ShaderFoundry...");
85
86 if (!m_command_buffers.empty()) {
88 "{} command buffers still exist during shutdown - freeing now",
89 m_command_buffers.size());
91 }
92
94
96
98
99 m_backend = nullptr;
100 s_initialized = false;
101
103 "ShaderFoundry shutdown complete");
104}
105
106//==============================================================================
107// Shader Compilation - Primary API
108//==============================================================================
109
110std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_file(
111 const std::string& filepath,
112 std::optional<ShaderStage> stage,
113 const std::string& entry_point)
114{
115 if (!is_initialized()) {
117 "ShaderFoundry not initialized");
118 return nullptr;
119 }
120
121 auto it = m_shader_cache.find(filepath);
122 if (it != m_shader_cache.end()) {
124 "Using cached shader: {}", filepath);
125 return it->second;
126 }
127
128 std::optional<vk::ShaderStageFlagBits> vk_stage;
129 if (stage.has_value()) {
130 vk_stage = to_vulkan_stage(*stage);
131 }
132
133 auto shader = create_shader_module();
134
135 if (filepath.ends_with(".spv")) {
136 if (!shader->create_from_spirv_file(
137 get_device(), filepath,
138 vk_stage.value_or(vk::ShaderStageFlagBits::eCompute),
139 entry_point, m_config.enable_reflection)) {
141 "Failed to compile SPIR-V shader: {}", filepath);
142 return nullptr;
143 }
144 } else {
145 if (!shader->create_from_glsl_file(
146 get_device(), filepath, vk_stage, entry_point,
148 m_config.defines)) {
150 "Failed to compile GLSL shader: {}", filepath);
151 return nullptr;
152 }
153 }
154
155 m_shader_cache[filepath] = shader;
157 "Compiled shader: {} ({})", filepath, vk::to_string(shader->get_stage()));
158 return shader;
159}
160
161std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_source(
162 const std::string& source,
163 ShaderStage stage,
164 const std::string& entry_point)
165{
166 if (!is_initialized()) {
168 "ShaderFoundry not initialized");
169 return nullptr;
170 }
171
172 auto shader = create_shader_module();
173 auto vk_stage = to_vulkan_stage(stage);
174
175 if (!shader->create_from_glsl(
176 get_device(), source, vk_stage, entry_point,
178 m_config.defines)) {
180 "Failed to compile GLSL source");
181 return nullptr;
182 }
183
185 "Compiled shader from source ({})", vk::to_string(vk_stage));
186 return shader;
187}
188
189std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_source_cached(
190 const std::string& source,
191 ShaderStage stage,
192 const std::string& cache_key,
193 const std::string& entry_point)
194{
195 auto it = m_shader_cache.find(cache_key);
196 if (it != m_shader_cache.end()) {
198 "Using cached shader: {}", cache_key);
199 return it->second;
200 }
201
202 auto shader = compile_from_source(source, stage, entry_point);
203 if (!shader) {
204 return nullptr;
205 }
206
207 m_shader_cache[cache_key] = shader;
208 return shader;
209}
210
211std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_spirv(
212 const std::string& spirv_path,
213 ShaderStage stage,
214 const std::string& entry_point)
215{
216 if (!is_initialized()) {
218 "ShaderFoundry not initialized");
219 return nullptr;
220 }
221
222 auto it = m_shader_cache.find(spirv_path);
223 if (it != m_shader_cache.end()) {
225 "Using cached SPIR-V shader: {}", spirv_path);
226 return it->second;
227 }
228
229 auto shader = create_shader_module();
230 auto vk_stage = to_vulkan_stage(stage);
231
232 if (!shader->create_from_spirv_file(
233 get_device(), spirv_path, vk_stage, entry_point,
236 "Failed to load SPIR-V shader: {}", spirv_path);
237 return nullptr;
238 }
239
240 m_shader_cache[spirv_path] = shader;
242 "Loaded SPIR-V shader: {}", spirv_path);
243 return shader;
244}
245
246std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile(const ShaderSource& shader_source)
247{
248 switch (shader_source.type) {
250 return compile_from_file(shader_source.content, shader_source.stage, shader_source.entry_point);
251
253 return compile_from_source(shader_source.content, shader_source.stage, shader_source.entry_point);
254
256 return compile_from_spirv(shader_source.content, shader_source.stage, shader_source.entry_point);
257
258 default:
260 "Unknown shader source type");
261 return nullptr;
262 }
263}
264
266 const std::string& content,
267 std::optional<ShaderStage> stage,
268 const std::string& entry_point)
269{
270 if (!is_initialized()) {
272 "ShaderFoundry not initialized");
273 return INVALID_SHADER;
274 }
275
276 DetectedSourceType source_type = detect_source_type(content);
277
278 std::string cache_key;
279 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
280 cache_key = content;
281 } else {
282 cache_key = generate_source_cache_key(content, stage.value_or(ShaderStage::COMPUTE));
283 }
284
285 auto id_it = m_shader_filepath_cache.find(cache_key);
286 if (id_it != m_shader_filepath_cache.end()) {
288 "Using cached shader ID for: {}", cache_key);
289 return id_it->second;
290 }
291
292 if (!stage.has_value()) {
293 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
294 if (source_type == DetectedSourceType::FILE_SPIRV) {
295 std::filesystem::path p(content);
296 std::string stem = p.stem().string();
297 stage = detect_stage_from_extension(stem);
298 } else {
299 stage = detect_stage_from_extension(content);
300 }
301 }
302
303 if (!stage.has_value()) {
305 "Cannot auto-detect shader stage from '{}' - must specify explicitly",
306 content);
307 return INVALID_SHADER;
308 }
309 }
310
311 std::shared_ptr<Core::VKShaderModule> shader_module;
312
313 switch (source_type) {
315 shader_module = compile_from_file(content, stage, entry_point);
316 break;
318 shader_module = compile_from_spirv(content, *stage, entry_point);
319 break;
321 shader_module = compile_from_source(content, *stage, entry_point);
322 break;
323 default:
325 "Cannot determine shader source type");
326 return INVALID_SHADER;
327 }
328
329 if (!shader_module) {
330 return INVALID_SHADER;
331 }
332
334
335 ShaderState& state = m_shaders[id];
336 state.module = shader_module;
337 state.filepath = cache_key;
338 state.stage = *stage;
339 state.entry_point = entry_point;
340
341 m_shader_filepath_cache[cache_key] = id;
342
344 "Shader loaded: {} (ID: {}, stage: {})",
345 cache_key, id, static_cast<int>(*stage));
346
347 return id;
348}
349
351{
352 return load_shader(source.content, source.stage, source.entry_point);
353}
354
355std::optional<std::filesystem::path> ShaderFoundry::resolve_shader_path(const std::string& filepath) const
356{
357 namespace fs = std::filesystem;
358
359 fs::path path(filepath);
360
361 if (path.is_absolute() || fs::exists(filepath)) {
362 return path;
363 }
364
365 std::vector<std::string> search_paths = {
366 Core::SHADER_BUILD_OUTPUT_DIR,
367 Core::SHADER_INSTALL_DIR,
368 Core::SHADER_SOURCE_DIR,
369 "./shaders",
370 "../shaders",
371 "data/shaders",
372 "./data/shaders",
373 "../data/shaders"
374 };
375
376 if (std::string_view(Core::SHADER_EXAMPLE_DIR).length() > 0) {
377 search_paths.emplace_back(Core::SHADER_EXAMPLE_DIR);
378 }
379
380#ifdef MAYAFLUX_PROJECT_SHADER_DIR
381 search_paths.emplace_back(MAYAFLUX_PROJECT_SHADER_DIR);
382#endif
383
384 for (const auto& search_path : search_paths) {
385 fs::path full_path = fs::path(search_path) / filepath;
386 if (fs::exists(full_path)) {
387 return full_path;
388 }
389 }
390
391 return std::nullopt;
392}
393
395{
396 auto resolved_path = resolve_shader_path(content);
397
398 if (resolved_path.has_value()) {
399 std::string ext = resolved_path->extension().string();
400 std::ranges::transform(ext, ext.begin(), ::tolower);
401
402 if (ext == ".spv") {
404 }
405
407 }
408
409 if (content.size() > 1024 || content.find('\n') != std::string::npos) {
411 }
412
414}
415
416std::string ShaderFoundry::generate_source_cache_key(const std::string& source, ShaderStage stage) const
417{
418 std::hash<std::string> hasher;
419 size_t hash = hasher(source + std::to_string(static_cast<int>(stage)));
420 return "source_" + std::to_string(hash);
421}
422
423ShaderID ShaderFoundry::reload_shader(const std::string& filepath)
424{
425 invalidate_cache(filepath);
426
427 auto cache_it = m_shader_filepath_cache.find(filepath);
428 if (cache_it != m_shader_filepath_cache.end()) {
429 destroy_shader(cache_it->second);
430 m_shader_filepath_cache.erase(cache_it);
431 }
432
433 return load_shader(filepath);
434}
435
437{
438 auto it = m_shaders.find(shader_id);
439 if (it != m_shaders.end()) {
440 if (!it->second.filepath.empty()) {
441 m_shader_filepath_cache.erase(it->second.filepath);
442 }
443
444 if (!it->second.filepath.empty()) {
445 m_shader_cache.erase(it->second.filepath);
446 }
447
448 m_shaders.erase(it);
449 }
450}
451
452//==============================================================================
453// Shader Introspection
454//==============================================================================
455
457{
458 auto it = m_shaders.find(shader_id);
459 if (it == m_shaders.end()) {
460 return {};
461 }
462
463 const auto& reflection = it->second.module->get_reflection();
464
466 info.stage = it->second.stage;
467 info.entry_point = it->second.entry_point;
468 info.workgroup_size = reflection.workgroup_size;
469
470 for (const auto& binding : reflection.bindings) {
471 info.descriptor_bindings.push_back({ .set = binding.set,
472 .binding = binding.binding,
473 .type = binding.type,
474 .name = binding.name });
475 }
476
477 for (const auto& pc : reflection.push_constants) {
478 PushConstantRangeInfo pc_info {};
479 pc_info.offset = pc.offset;
480 pc_info.size = pc.size;
481 info.push_constant_ranges.push_back(pc_info);
482 }
483
484 return info;
485}
486
488{
489 auto it = m_shaders.find(shader_id);
490 if (it != m_shaders.end()) {
491 return it->second.stage;
492 }
494}
495
497{
498 auto it = m_shaders.find(shader_id);
499 if (it != m_shaders.end()) {
500 return it->second.entry_point;
501 }
502 return "main";
503}
504
505bool ShaderFoundry::is_cached(const std::string& cache_key) const
506{
507 return m_shader_cache.find(cache_key) != m_shader_cache.end();
508}
509
510std::vector<std::string> ShaderFoundry::get_cached_keys() const
511{
512 std::vector<std::string> keys;
513 keys.reserve(m_shader_cache.size());
514 for (const auto& [key, _] : m_shader_cache) {
515 keys.push_back(key);
516 }
517 return keys;
518}
519
521{
522 auto device = get_device();
523
524 for (auto& [key, shader_module] : m_shader_cache) {
525 if (shader_module) {
526 shader_module->cleanup(device);
527 }
528 }
529
530 m_shader_cache.clear();
531 m_shaders.clear();
533
535 "Cleaned up shader modules");
536}
537
538//==============================================================================
539// Hot-Reload Support
540//==============================================================================
541
542void ShaderFoundry::invalidate_cache(const std::string& cache_key)
543{
544 auto it = m_shader_cache.find(cache_key);
545 if (it != m_shader_cache.end()) {
546 m_shader_cache.erase(it);
548 "Invalidated shader cache: {}", cache_key);
549 }
550}
551
558
559std::shared_ptr<Core::VKShaderModule> ShaderFoundry::hot_reload(const std::string& filepath)
560{
561 invalidate_cache(filepath);
562 return compile_from_file(filepath);
563}
564
565//==============================================================================
566// Configuration
567//==============================================================================
568
570{
571 m_config = config;
573 "Updated shader compiler configuration");
574}
575
576void ShaderFoundry::add_include_directory(const std::string& directory)
577{
578 m_config.include_directories.push_back(directory);
579}
580
581void ShaderFoundry::add_define(const std::string& name, const std::string& value)
582{
583 m_config.defines[name] = value;
584}
585
586//==============================================================================
587// Descriptor Management
588//==============================================================================
589
591{
593
595 state.descriptor_set = m_global_descriptor_manager->allocate_set(get_device(), layout);
596
597 return id;
598}
599
601 DescriptorSetID descriptor_set_id,
602 uint32_t binding,
603 vk::DescriptorType type,
604 vk::Buffer buffer,
605 size_t offset,
606 size_t size)
607{
608 auto it = m_descriptor_sets.find(descriptor_set_id);
609 if (it == m_descriptor_sets.end()) {
610 return;
611 }
612
613 vk::DescriptorBufferInfo buffer_info;
614 buffer_info.buffer = buffer;
615 buffer_info.offset = offset;
616 buffer_info.range = size;
617
618 vk::WriteDescriptorSet write;
619 write.dstSet = it->second.descriptor_set;
620 write.dstBinding = binding;
621 write.dstArrayElement = 0;
622 write.descriptorCount = 1;
623 write.descriptorType = type;
624 write.pBufferInfo = &buffer_info;
625
626 get_device().updateDescriptorSets(1, &write, 0, nullptr);
627}
628
630 DescriptorSetID descriptor_set_id,
631 uint32_t binding,
632 vk::ImageView image_view,
633 vk::Sampler sampler,
634 vk::ImageLayout layout)
635{
636 auto it = m_descriptor_sets.find(descriptor_set_id);
637 if (it == m_descriptor_sets.end()) {
638 return;
639 }
640
641 vk::DescriptorImageInfo image_info;
642 image_info.imageView = image_view;
643 image_info.sampler = sampler;
644 image_info.imageLayout = layout;
645
646 vk::WriteDescriptorSet write;
647 write.dstSet = it->second.descriptor_set;
648 write.dstBinding = binding;
649 write.dstArrayElement = 0;
650 write.descriptorCount = 1;
651 write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
652 write.pImageInfo = &image_info;
653
654 get_device().updateDescriptorSets(1, &write, 0, nullptr);
655}
656
658 DescriptorSetID descriptor_set_id,
659 uint32_t binding,
660 vk::ImageView image_view,
661 vk::ImageLayout layout)
662{
663 auto it = m_descriptor_sets.find(descriptor_set_id);
664 if (it == m_descriptor_sets.end()) {
665 return;
666 }
667
668 vk::DescriptorImageInfo image_info;
669 image_info.imageView = image_view;
670 image_info.imageLayout = layout;
671
672 vk::WriteDescriptorSet write;
673 write.dstSet = it->second.descriptor_set;
674 write.dstBinding = binding;
675 write.dstArrayElement = 0;
676 write.descriptorCount = 1;
677 write.descriptorType = vk::DescriptorType::eStorageImage;
678 write.pImageInfo = &image_info;
679
680 get_device().updateDescriptorSets(1, &write, 0, nullptr);
681}
682
683vk::DescriptorSet ShaderFoundry::get_descriptor_set(DescriptorSetID descriptor_set_id)
684{
685 auto it = m_descriptor_sets.find(descriptor_set_id);
686 if (it == m_descriptor_sets.end()) {
687 error<std::invalid_argument>(
690 std::source_location::current(),
691 "Invalid DescriptorSetID: {}", descriptor_set_id);
692 }
693 return it->second.descriptor_set;
694}
695
697{
698 auto device = get_device();
699
700 m_descriptor_sets.clear();
701
703 m_global_descriptor_manager->cleanup(device);
705 }
706
708 "Cleaned up descriptor resources");
709}
710
711//==============================================================================
712// Command Recording
713//==============================================================================
714
716{
717 auto& cmd_manager = m_backend->get_command_manager();
718
720
722
723 state.cmd = (type == CommandBufferType::COMPUTE)
724 ? cmd_manager.begin_single_time_commands_compute()
725 : cmd_manager.begin_single_time_commands();
726
727 state.type = type;
728 state.is_active = true;
729
730 return id;
731}
732
734{
735 auto& cmd_manager = m_backend->get_command_manager();
736
738
739 vk::CommandBuffer cmd = cmd_manager.allocate_command_buffer(vk::CommandBufferLevel::eSecondary);
740
741 vk::CommandBufferInheritanceRenderingInfo inheritance_rendering;
742 inheritance_rendering.colorAttachmentCount = 1;
743 inheritance_rendering.pColorAttachmentFormats = &color_format;
744 inheritance_rendering.rasterizationSamples = vk::SampleCountFlagBits::e1;
745
746 vk::CommandBufferInheritanceInfo inheritance_info;
747 inheritance_info.pNext = &inheritance_rendering;
748
749 vk::CommandBufferBeginInfo begin_info;
750 begin_info.flags = vk::CommandBufferUsageFlagBits::eRenderPassContinue | vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
751 begin_info.pInheritanceInfo = &inheritance_info;
752
753 cmd.begin(begin_info);
754
756 state.cmd = cmd;
759 state.is_active = true;
760
762 "Began secondary command buffer (ID: {}) for dynamic rendering", id);
763
764 return id;
765}
766
768{
769 auto it = m_command_buffers.find(cmd_id);
770 if (it != m_command_buffers.end()) {
771 return it->second.cmd;
772 }
773 return nullptr;
774}
775
777{
778 auto it = m_command_buffers.find(cmd_id);
779 if (it == m_command_buffers.end() || !it->second.is_active) {
780 return false;
781 }
782
783 it->second.cmd.end();
784 it->second.is_active = false;
785 return true;
786}
787
789{
790 if (!m_backend || !s_initialized) {
791 return;
792 }
793
794 auto& cmd_manager = m_backend->get_command_manager();
795 auto device = get_device();
796 device.waitIdle();
797
798 for (auto& [id, state] : m_command_buffers) {
799 if (state.is_active) {
800 cmd_manager.free_command_buffer(state.cmd);
801 }
802 if (state.timestamp_pool) {
803 device.destroyQueryPool(state.timestamp_pool);
804 }
805 }
806 m_command_buffers.clear();
807
809 "Freed all command buffers");
810}
811
812//==============================================================================
813// Synchronization
814//==============================================================================
815
817{
818 auto it = m_command_buffers.find(cmd_id);
819 if (it == m_command_buffers.end() || !it->second.is_active) {
820 return;
821 }
822
823 auto& cmd_manager = m_backend->get_command_manager();
824
825 it->second.cmd.end();
826
827 vk::SubmitInfo submit_info;
828 submit_info.commandBufferCount = 1;
829 submit_info.pCommandBuffers = &it->second.cmd;
830
831 vk::Queue queue;
832 switch (it->second.type) {
834 queue = m_graphics_queue;
835 break;
837 queue = m_compute_queue;
838 break;
840 queue = m_transfer_queue;
841 break;
842 }
843
844 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
846 "Failed to submit command buffer");
847 return;
848 }
849 queue.waitIdle();
850
851 cmd_manager.free_command_buffer(it->second.cmd);
852
853 it->second.is_active = false;
854 m_command_buffers.erase(it);
855}
856
858{
859 auto cmd_it = m_command_buffers.find(cmd_id);
860 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
861 return INVALID_FENCE;
862 }
863
864 cmd_it->second.cmd.end();
865
866 FenceID fence_id = m_next_fence_id++;
867
868 vk::FenceCreateInfo fence_info;
869 FenceState& fence_state = m_fences[fence_id];
870 fence_state.fence = get_device().createFence(fence_info);
871 fence_state.signaled = false;
872
873 vk::SubmitInfo submit_info;
874 submit_info.commandBufferCount = 1;
875 submit_info.pCommandBuffers = &cmd_it->second.cmd;
876
877 vk::Queue queue;
878 switch (cmd_it->second.type) {
880 queue = m_graphics_queue;
881 break;
883 queue = m_compute_queue;
884 break;
886 queue = m_transfer_queue;
887 break;
888 }
889
890 if (queue.submit(1, &submit_info, fence_state.fence) != vk::Result::eSuccess) {
892 "Failed to submit command buffer");
893 return INVALID_FENCE;
894 }
895
896 cmd_it->second.is_active = false;
897
898 return fence_id;
899}
900
902{
903 auto cmd_it = m_command_buffers.find(cmd_id);
904 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
905 return INVALID_SEMAPHORE;
906 }
907
908 cmd_it->second.cmd.end();
909
910 SemaphoreID semaphore_id = m_next_semaphore_id++;
911
912 vk::SemaphoreCreateInfo semaphore_info;
913 SemaphoreState& semaphore_state = m_semaphores[semaphore_id];
914 semaphore_state.semaphore = get_device().createSemaphore(semaphore_info);
915
916 vk::SubmitInfo submit_info;
917 submit_info.commandBufferCount = 1;
918 submit_info.pCommandBuffers = &cmd_it->second.cmd;
919 submit_info.signalSemaphoreCount = 1;
920 submit_info.pSignalSemaphores = &semaphore_state.semaphore;
921
922 vk::Queue queue;
923 switch (cmd_it->second.type) {
925 queue = m_graphics_queue;
926 break;
928 queue = m_compute_queue;
929 break;
931 queue = m_transfer_queue;
932 break;
933 }
934
935 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
937 "Failed to submit command buffer");
938 return INVALID_SEMAPHORE;
939 }
940
941 cmd_it->second.is_active = false;
942
943 return semaphore_id;
944}
945
947{
948 auto it = m_fences.find(fence_id);
949 if (it == m_fences.end()) {
950 return;
951 }
952
953 if (get_device().waitForFences(1, &it->second.fence, VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
955 "Failed to wait for fence: {}", fence_id);
956 return;
957 }
958 it->second.signaled = true;
959}
960
961void ShaderFoundry::wait_for_fences(const std::vector<FenceID>& fence_ids)
962{
963 std::vector<vk::Fence> fences;
964 for (auto fence_id : fence_ids) {
965 auto it = m_fences.find(fence_id);
966 if (it != m_fences.end()) {
967 fences.push_back(it->second.fence);
968 }
969 }
970
971 if (!fences.empty()) {
972 if (get_device().waitForFences(static_cast<uint32_t>(fences.size()), fences.data(), VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
974 "Failed to wait for fences");
975 return;
976 }
977 }
978
979 for (auto fence_id : fence_ids) {
980 auto it = m_fences.find(fence_id);
981 if (it != m_fences.end()) {
982 it->second.signaled = true;
983 }
984 }
985}
986
988{
989 auto it = m_fences.find(fence_id);
990 if (it == m_fences.end()) {
991 return false;
992 }
993
994 if (it->second.signaled) {
995 return true;
996 }
997
998 auto result = get_device().getFenceStatus(it->second.fence);
999 it->second.signaled = (result == vk::Result::eSuccess);
1000 return it->second.signaled;
1001}
1002
1004 CommandBufferType type,
1005 SemaphoreID wait_semaphore,
1006 vk::PipelineStageFlags /*wait_stage*/)
1007{
1008 auto sem_it = m_semaphores.find(wait_semaphore);
1009 if (sem_it == m_semaphores.end()) {
1011 }
1012
1013 return begin_commands(type);
1014}
1015
1017{
1018 auto it = m_semaphores.find(semaphore_id);
1019 if (it != m_semaphores.end()) {
1020 return it->second.semaphore;
1021 }
1022 return nullptr;
1023}
1024
1026{
1027 auto device = get_device();
1028
1029 for (auto& [id, state] : m_fences) {
1030 if (state.fence) {
1031 device.destroyFence(state.fence);
1032 }
1033 }
1034 m_fences.clear();
1035
1036 for (auto& [id, state] : m_semaphores) {
1037 if (state.semaphore) {
1038 device.destroySemaphore(state.semaphore);
1039 }
1040 }
1041 m_semaphores.clear();
1042
1044 "Cleaned up sync objects");
1045}
1046
1047//==============================================================================
1048// Memory Barriers
1049//==============================================================================
1050
1052 CommandBufferID cmd_id,
1053 vk::Buffer buffer,
1054 vk::AccessFlags src_access,
1055 vk::AccessFlags dst_access,
1056 vk::PipelineStageFlags src_stage,
1057 vk::PipelineStageFlags dst_stage)
1058{
1059 auto it = m_command_buffers.find(cmd_id);
1060 if (it == m_command_buffers.end()) {
1061 return;
1062 }
1063
1064 vk::BufferMemoryBarrier barrier;
1065 barrier.srcAccessMask = src_access;
1066 barrier.dstAccessMask = dst_access;
1067 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1068 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1069 barrier.buffer = buffer;
1070 barrier.offset = 0;
1071 barrier.size = VK_WHOLE_SIZE;
1072
1073 it->second.cmd.pipelineBarrier(
1074 src_stage,
1075 dst_stage,
1076 vk::DependencyFlags {},
1077 0, nullptr,
1078 1, &barrier,
1079 0, nullptr);
1080}
1081
1083 CommandBufferID cmd_id,
1084 vk::Image image,
1085 vk::ImageLayout old_layout,
1086 vk::ImageLayout new_layout,
1087 vk::AccessFlags src_access,
1088 vk::AccessFlags dst_access,
1089 vk::PipelineStageFlags src_stage,
1090 vk::PipelineStageFlags dst_stage)
1091{
1092 auto it = m_command_buffers.find(cmd_id);
1093 if (it == m_command_buffers.end()) {
1094 return;
1095 }
1096
1097 vk::ImageMemoryBarrier barrier;
1098 barrier.srcAccessMask = src_access;
1099 barrier.dstAccessMask = dst_access;
1100 barrier.oldLayout = old_layout;
1101 barrier.newLayout = new_layout;
1102 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1103 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1104 barrier.image = image;
1105 barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
1106 barrier.subresourceRange.baseMipLevel = 0;
1107 barrier.subresourceRange.levelCount = 1;
1108 barrier.subresourceRange.baseArrayLayer = 0;
1109 barrier.subresourceRange.layerCount = 1;
1110
1111 it->second.cmd.pipelineBarrier(
1112 src_stage,
1113 dst_stage,
1114 vk::DependencyFlags {},
1115 0, nullptr,
1116 0, nullptr,
1117 1, &barrier);
1118}
1119
1120//==============================================================================
1121// Queue Management
1122//==============================================================================
1123
1127
1128//==============================================================================
1129// Profiling
1130//==============================================================================
1131
1132void ShaderFoundry::begin_timestamp(CommandBufferID cmd_id, const std::string& label)
1133{
1134 auto it = m_command_buffers.find(cmd_id);
1135 if (it == m_command_buffers.end()) {
1136 return;
1137 }
1138
1139 if (!it->second.timestamp_pool) {
1140 vk::QueryPoolCreateInfo pool_info;
1141 pool_info.queryType = vk::QueryType::eTimestamp;
1142 pool_info.queryCount = 128;
1143 it->second.timestamp_pool = get_device().createQueryPool(pool_info);
1144 }
1145
1146 auto query_index = static_cast<uint32_t>(it->second.timestamp_queries.size() * 2);
1147 it->second.timestamp_queries[label] = query_index;
1148
1149 it->second.cmd.resetQueryPool(it->second.timestamp_pool, query_index, 2);
1150 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, it->second.timestamp_pool, query_index);
1151}
1152
1153void ShaderFoundry::end_timestamp(CommandBufferID cmd_id, const std::string& label)
1154{
1155 auto it = m_command_buffers.find(cmd_id);
1156 if (it == m_command_buffers.end()) {
1157 return;
1158 }
1159
1160 auto query_it = it->second.timestamp_queries.find(label);
1161 if (query_it == it->second.timestamp_queries.end()) {
1162 return;
1163 }
1164
1165 uint32_t query_index = query_it->second;
1166 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, it->second.timestamp_pool, query_index + 1);
1167}
1168
1170{
1171 auto it = m_command_buffers.find(cmd_id);
1172 if (it == m_command_buffers.end()) {
1173 return { .label = label, .duration_ns = 0, .valid = false };
1174 }
1175
1176 auto query_it = it->second.timestamp_queries.find(label);
1177 if (query_it == it->second.timestamp_queries.end()) {
1178 return { .label = label, .duration_ns = 0, .valid = false };
1179 }
1180
1181 if (!it->second.timestamp_pool) {
1182 return { .label = label, .duration_ns = 0, .valid = false };
1183 }
1184
1185 uint32_t query_index = query_it->second;
1186 uint64_t timestamps[2];
1187
1188 auto result = get_device().getQueryPoolResults(
1189 it->second.timestamp_pool,
1190 query_index,
1191 2,
1192 sizeof(timestamps),
1193 timestamps,
1194 sizeof(uint64_t),
1195 vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
1196
1197 if (result != vk::Result::eSuccess) {
1198 return { .label = label, .duration_ns = 0, .valid = false };
1199 }
1200
1201 auto props = m_backend->get_context().get_physical_device().getProperties();
1202 float timestamp_period = props.limits.timestampPeriod;
1203
1204 auto duration_ns = static_cast<uint64_t>((timestamps[1] - timestamps[0]) * timestamp_period);
1205
1206 return { .label = label, .duration_ns = duration_ns, .valid = true };
1207}
1208
1209//==============================================================================
1210// Internal Access
1211//==============================================================================
1212
1213std::shared_ptr<Core::VKShaderModule> ShaderFoundry::get_vk_shader_module(ShaderID shader_id)
1214{
1215 auto& foundry = ShaderFoundry::instance();
1216 auto it = foundry.m_shaders.find(shader_id);
1217 if (it != foundry.m_shaders.end()) {
1218 return it->second.module;
1219 }
1220 return nullptr;
1221}
1222
1223//==============================================================================
1224// Utilities
1225//==============================================================================
1226
1227vk::ShaderStageFlagBits ShaderFoundry::to_vulkan_stage(ShaderStage stage)
1228{
1229 switch (stage) {
1231 return vk::ShaderStageFlagBits::eCompute;
1233 return vk::ShaderStageFlagBits::eVertex;
1235 return vk::ShaderStageFlagBits::eFragment;
1237 return vk::ShaderStageFlagBits::eGeometry;
1239 return vk::ShaderStageFlagBits::eTessellationControl;
1241 return vk::ShaderStageFlagBits::eTessellationEvaluation;
1242 case ShaderStage::MESH:
1243 return vk::ShaderStageFlagBits::eMeshEXT;
1244 case ShaderStage::TASK:
1245 return vk::ShaderStageFlagBits::eTaskEXT;
1246 default:
1247 return vk::ShaderStageFlagBits::eCompute;
1248 }
1249}
1250
1251std::optional<ShaderStage> ShaderFoundry::detect_stage_from_extension(const std::string& filepath)
1252{
1253 auto vk_stage = Core::VKShaderModule::detect_stage_from_extension(filepath);
1254 if (!vk_stage.has_value()) {
1255 return std::nullopt;
1256 }
1257
1258 switch (*vk_stage) {
1259 case vk::ShaderStageFlagBits::eCompute:
1260 return ShaderStage::COMPUTE;
1261 case vk::ShaderStageFlagBits::eVertex:
1262 return ShaderStage::VERTEX;
1263 case vk::ShaderStageFlagBits::eFragment:
1264 return ShaderStage::FRAGMENT;
1265 case vk::ShaderStageFlagBits::eGeometry:
1266 return ShaderStage::GEOMETRY;
1267 case vk::ShaderStageFlagBits::eTessellationControl:
1269 case vk::ShaderStageFlagBits::eTessellationEvaluation:
1271 case vk::ShaderStageFlagBits::eMeshEXT:
1272 return ShaderStage::MESH;
1273 case vk::ShaderStageFlagBits::eTaskEXT:
1274 return ShaderStage::TASK;
1275 default:
1276 return std::nullopt;
1277 }
1278}
1279
1280//==============================================================================
1281// Private Helpers
1282//==============================================================================
1283
1284std::shared_ptr<Core::VKShaderModule> ShaderFoundry::create_shader_module()
1285{
1286 return std::make_shared<Core::VKShaderModule>();
1287}
1288
1290{
1291 return m_backend->get_context().get_device();
1292}
1293
1294} // namespace MayaFlux::Portal::Graphics
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_TRACE(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
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.
static std::optional< ShaderStage > detect_stage_from_extension(const std::string &filepath)
Auto-detect shader stage from file extension.
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 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.
void update_descriptor_image(DescriptorSetID descriptor_set_id, uint32_t binding, vk::ImageView image_view, vk::Sampler sampler, vk::ImageLayout layout=vk::ImageLayout::eShaderReadOnlyOptimal)
Update descriptor set with image binding.
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.
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.
CommandBufferID begin_secondary_commands(vk::Format color_format)
Begin recording a secondary command buffer for dynamic rendering.
std::unordered_map< ShaderID, ShaderState > m_shaders
DescriptorSetID allocate_descriptor_set(vk::DescriptorSetLayout layout)
Allocate descriptor set for a pipeline.
@ Rendering
GPU rendering operations (graphics pipeline, frame rendering)
@ 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.