MayaFlux 1.0.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
15namespace {
16
17 void append_default_include_directories(ShaderCompilerConfig& config)
18 {
19 const std::string defaults[] = {
20 Config::SHADER_SOURCE_DIR,
21 std::string(Config::SHADER_SOURCE_DIR) + "/include",
22 Config::SHADER_BUILD_OUTPUT_DIR
23 };
24
25 for (const auto& directory : defaults) {
26 if (std::ranges::find(config.include_directories, directory) == config.include_directories.end()) {
27 config.include_directories.push_back(directory);
28 }
29 }
30 }
31
32}
33
35 const std::shared_ptr<Core::VulkanBackend>& backend,
36 const ShaderCompilerConfig& config)
37{
38 if (s_initialized) {
40 "ShaderFoundry already initialized (static flag)");
41 return true;
42 }
43
44 if (!backend) {
46 "Cannot initialize ShaderFoundry with null backend");
47 return false;
48 }
49
50 if (m_backend) {
52 "ShaderFoundry already initialized");
53 return true;
54 }
55
56 m_backend = backend;
57 m_config = config;
58
59 append_default_include_directories(m_config);
60
61 m_global_descriptor_manager = std::make_shared<Core::VKDescriptorManager>();
62 m_global_descriptor_manager->initialize(get_device(), 1024);
63
64 m_graphics_queue = m_backend->get_context().get_graphics_queue();
65 m_compute_queue = m_backend->get_context().get_compute_queue();
66 m_transfer_queue = m_backend->get_context().get_transfer_queue();
67
68 s_initialized = true;
69
71 "ShaderFoundry initialized");
72 return true;
73}
74
76{
77 if (!s_initialized || !m_backend) {
78 return;
79 }
80
82 "Stopping ShaderFoundry - freeing command buffers...");
83
84 auto device = get_device();
85
86 device.waitIdle();
87
89
91 "ShaderFoundry stopped - command buffers freed");
92}
93
95{
96 if (!s_initialized) {
97 return;
98 }
99
100 if (!m_backend) {
101 return;
102 }
103
105 "Shutting down ShaderFoundry...");
106
107 if (!m_command_buffers.empty()) {
109 "{} command buffers still exist during shutdown - freeing now",
110 m_command_buffers.size());
112 }
113
115
117
119
120 m_finished_timestamps.reset();
121
122 m_backend = nullptr;
123 s_initialized = false;
124
126 "ShaderFoundry shutdown complete");
127}
128
129//==============================================================================
130// Shader Compilation - Primary API
131//==============================================================================
132
133std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_file(
134 const std::string& filepath,
135 std::optional<ShaderStage> stage,
136 const std::string& entry_point)
137{
138 if (!is_initialized()) {
140 "ShaderFoundry not initialized");
141 return nullptr;
142 }
143
144 auto it = m_shader_cache.find(filepath);
145 if (it != m_shader_cache.end()) {
147 "Using cached shader: {}", filepath);
148 return it->second;
149 }
150
151 std::optional<vk::ShaderStageFlagBits> vk_stage;
152 if (stage.has_value()) {
153 vk_stage = to_vulkan_stage(*stage);
154 }
155
156 auto shader = create_shader_module();
157
158 if (filepath.ends_with(".spv")) {
159 if (!shader->create_from_spirv_file(
160 get_device(), filepath,
161 vk_stage.value_or(vk::ShaderStageFlagBits::eCompute),
162 entry_point, m_config.enable_reflection)) {
164 "Failed to compile SPIR-V shader: {}", filepath);
165 return nullptr;
166 }
167 } else {
168 if (!shader->create_from_glsl_file(
169 get_device(), filepath, vk_stage, entry_point,
171 m_config.defines)) {
173 "Failed to compile GLSL shader: {}", filepath);
174 return nullptr;
175 }
176 }
177
178 m_shader_cache[filepath] = shader;
180 "Compiled shader: {} ({})", filepath, vk::to_string(shader->get_stage()));
181 return shader;
182}
183
184std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_source(
185 const std::string& source,
186 ShaderStage stage,
187 const std::string& entry_point)
188{
189 if (!is_initialized()) {
191 "ShaderFoundry not initialized");
192 return nullptr;
193 }
194
195 auto shader = create_shader_module();
196 auto vk_stage = to_vulkan_stage(stage);
197
198 if (!shader->create_from_glsl(
199 get_device(), source, vk_stage, entry_point,
201 m_config.defines)) {
203 "Failed to compile GLSL source");
204 return nullptr;
205 }
206
208 "Compiled shader from source ({})", vk::to_string(vk_stage));
209 return shader;
210}
211
212std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_source_cached(
213 const std::string& source,
214 ShaderStage stage,
215 const std::string& cache_key,
216 const std::string& entry_point)
217{
218 auto it = m_shader_cache.find(cache_key);
219 if (it != m_shader_cache.end()) {
221 "Using cached shader: {}", cache_key);
222 return it->second;
223 }
224
225 auto shader = compile_from_source(source, stage, entry_point);
226 if (!shader) {
227 return nullptr;
228 }
229
230 m_shader_cache[cache_key] = shader;
231 return shader;
232}
233
234std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_spirv(
235 const std::string& spirv_path,
236 ShaderStage stage,
237 const std::string& entry_point)
238{
239 if (!is_initialized()) {
241 "ShaderFoundry not initialized");
242 return nullptr;
243 }
244
245 auto it = m_shader_cache.find(spirv_path);
246 if (it != m_shader_cache.end()) {
248 "Using cached SPIR-V shader: {}", spirv_path);
249 return it->second;
250 }
251
252 auto shader = create_shader_module();
253 auto vk_stage = to_vulkan_stage(stage);
254
255 if (!shader->create_from_spirv_file(
256 get_device(), spirv_path, vk_stage, entry_point,
259 "Failed to load SPIR-V shader: {}", spirv_path);
260 return nullptr;
261 }
262
263 m_shader_cache[spirv_path] = shader;
265 "Loaded SPIR-V shader: {}", spirv_path);
266 return shader;
267}
268
269std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile_from_spirv_asm(
270 const std::string& spirv_asm,
271 ShaderStage stage,
272 const std::string& entry_point)
273{
274 auto shader = create_shader_module();
275 if (!shader->create_from_spirv_asm(
276 get_device(), spirv_asm, to_vulkan_stage(stage),
277 entry_point, m_config.enable_reflection)) {
279 "Failed to assemble generated SPIR-V");
280 return nullptr;
281 }
282 return shader;
283}
284
285std::shared_ptr<Core::VKShaderModule> ShaderFoundry::compile(const ShaderSource& shader_source)
286{
287 switch (shader_source.type) {
289 return compile_from_file(shader_source.content, shader_source.stage, shader_source.entry_point);
290
292 return compile_from_source(shader_source.content, shader_source.stage, shader_source.entry_point);
293
295 return compile_from_spirv(shader_source.content, shader_source.stage, shader_source.entry_point);
296
298 return compile_from_spirv_asm(shader_source.content, shader_source.stage, shader_source.entry_point);
299
300 default:
302 "Unknown shader source type");
303 return nullptr;
304 }
305}
306
308 const std::string& content,
309 std::optional<ShaderStage> stage,
310 const std::string& entry_point)
311{
312 if (!is_initialized()) {
314 "ShaderFoundry not initialized");
315 return INVALID_SHADER;
316 }
317
318 DetectedSourceType source_type = detect_source_type(content);
319
320 std::string cache_key;
321 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
322 cache_key = content;
323 } else {
324 cache_key = generate_source_cache_key(content, stage.value_or(ShaderStage::COMPUTE));
325 }
326
327 auto id_it = m_shader_filepath_cache.find(cache_key);
328 if (id_it != m_shader_filepath_cache.end()) {
330 "Using cached shader ID for: {}", cache_key);
331 return id_it->second;
332 }
333
334 if (!stage.has_value()) {
335 if (source_type == DetectedSourceType::FILE_GLSL || source_type == DetectedSourceType::FILE_SPIRV) {
336 if (source_type == DetectedSourceType::FILE_SPIRV) {
337 std::filesystem::path p(content);
338 std::string stem = p.stem().string();
339 stage = detect_stage_from_extension(stem);
340 } else {
341 stage = detect_stage_from_extension(content);
342 }
343 }
344
345 if (!stage.has_value()) {
347 "Cannot auto-detect shader stage from '{}' - must specify explicitly",
348 content);
349 return INVALID_SHADER;
350 }
351 }
352
353 std::shared_ptr<Core::VKShaderModule> shader_module;
354
355 switch (source_type) {
357 shader_module = compile_from_file(content, stage, entry_point);
358 break;
360 shader_module = compile_from_spirv(content, *stage, entry_point);
361 break;
363 shader_module = compile_from_source(content, *stage, entry_point);
364 break;
365 default:
367 "Cannot determine shader source type");
368 return INVALID_SHADER;
369 }
370
371 if (!shader_module) {
372 return INVALID_SHADER;
373 }
374
376
377 ShaderState& state = m_shaders[id];
378 state.module = shader_module;
379 state.filepath = cache_key;
380 state.stage = *stage;
381 state.entry_point = entry_point;
382
383 m_shader_filepath_cache[cache_key] = id;
384
386 "Shader loaded: {} (ID: {}, stage: {})",
387 cache_key, id, static_cast<int>(*stage));
388
389 return id;
390}
391
393{
394 return load_shader(source.content, source.stage, source.entry_point);
395}
396
398{
399 if (!is_initialized()) {
401 "ShaderFoundry not initialized");
402 return INVALID_SHADER;
403 }
404
405 if (spec.kernel.has_value()) {
406 const std::string glsl = detail::emit_glsl_kernel(spec);
407 const std::string key = generate_source_cache_key(glsl, ShaderStage::COMPUTE);
408
409 auto kernel_id_it = m_shader_filepath_cache.find(key);
410 if (kernel_id_it != m_shader_filepath_cache.end()) {
412 "Using cached kernel shader: {}", key);
413 return kernel_id_it->second;
414 }
415
416 auto module = compile_from_source_cached(glsl, ShaderStage::COMPUTE, key);
417 if (!module) {
419 "Failed to compile kernel shader");
420 return INVALID_SHADER;
421 }
422 const ShaderID id = m_next_shader_id++;
423 auto& state = m_shaders[id];
424 state.module = module;
425 state.filepath = key;
426 state.stage = ShaderStage::COMPUTE;
427 state.entry_point = "main";
428 m_shader_filepath_cache[key] = id;
430 "Compiled kernel shader (ID: {}, key: {})", id, key);
431 return id;
432 }
433
434 const std::string asm_text = detail::emit_spirv_asm(spec);
435 const std::string key = generate_source_cache_key(asm_text, ShaderStage::COMPUTE);
436
437 auto id_it = m_shader_filepath_cache.find(key);
438 if (id_it != m_shader_filepath_cache.end()) {
440 "Using cached generated shader: {}", key);
441 return id_it->second;
442 }
443
444 auto module = compile_from_spirv_asm(asm_text, ShaderStage::COMPUTE);
445 if (!module) {
447 "Failed to compile generated shader (key: {})", key);
448 return INVALID_SHADER;
449 }
450
451 const ShaderID id = m_next_shader_id++;
452 auto& state = m_shaders[id];
453 state.module = module;
454 state.filepath = key;
455 state.stage = ShaderStage::COMPUTE;
456 state.entry_point = "main";
457
458 m_shader_filepath_cache[key] = id;
459
461 "Compiled generated shader (ID: {}, key: {})", id, key);
462
463 return id;
464}
465
466std::optional<std::filesystem::path> ShaderFoundry::resolve_shader_path(const std::string& filepath) const
467{
468 namespace fs = std::filesystem;
469
470 fs::path path(filepath);
471 std::error_code ec;
472
473 if (path.is_absolute() || fs::exists(path, ec)) {
474 return path;
475 }
476
477 std::vector<std::string> user_paths;
478
479#ifdef MAYAFLUX_PROJECT_SHADER_DIR
480 user_paths.emplace_back(MAYAFLUX_PROJECT_SHADER_DIR);
481#endif
482
483 user_paths.insert(user_paths.end(),
484 { "./shaders", "./data/shaders", "../shaders", "../data/shaders", "data/shaders" });
485
486 if (std::string_view(Config::SHADER_EXAMPLE_DIR).length() > 0) {
487 user_paths.emplace_back(Config::SHADER_EXAMPLE_DIR);
488 }
489
490 const std::vector<std::string> library_paths = {
491 Config::SHADER_BUILD_OUTPUT_DIR,
492 Config::SHADER_INSTALL_DIR,
493 Config::SHADER_SOURCE_DIR
494 };
495
496 for (const auto& search_path : user_paths) {
497 fs::path full_path = fs::path(search_path) / filepath;
498 if (!fs::exists(full_path, ec)) {
499 continue;
500 }
501
502 for (const auto& library_path : library_paths) {
503 fs::path shadowed = fs::path(library_path) / filepath;
504 if (fs::exists(shadowed, ec) && !fs::equivalent(full_path, shadowed, ec)) {
506 "Shader '{}' at '{}' shadows library shader '{}'",
507 filepath, full_path.string(), shadowed.string());
508 break;
509 }
510 }
511 return full_path;
512 }
513
514 for (const auto& search_path : library_paths) {
515 fs::path full_path = fs::path(search_path) / filepath;
516 if (fs::exists(full_path, ec)) {
517 return full_path;
518 }
519 }
520
521 return std::nullopt;
522}
523
525{
526 auto resolved_path = resolve_shader_path(content);
527
528 if (resolved_path.has_value()) {
529 std::string ext = resolved_path->extension().string();
530 std::ranges::transform(ext, ext.begin(), ::tolower);
531
532 if (ext == ".spv") {
534 }
535
537 }
538
539 if (content.size() > 1024 || content.find('\n') != std::string::npos) {
541 }
542
544}
545
546std::string ShaderFoundry::generate_source_cache_key(const std::string& source, ShaderStage stage) const
547{
548 std::hash<std::string> hasher;
549 size_t hash = hasher(source + std::to_string(static_cast<int>(stage)));
550 return "source_" + std::to_string(hash);
551}
552
553ShaderID ShaderFoundry::reload_shader(const std::string& filepath)
554{
555 invalidate_cache(filepath);
556
557 auto cache_it = m_shader_filepath_cache.find(filepath);
558 if (cache_it != m_shader_filepath_cache.end()) {
559 destroy_shader(cache_it->second);
560 m_shader_filepath_cache.erase(filepath);
561 }
562
563 return load_shader(filepath);
564}
565
567{
568 auto it = m_shaders.find(shader_id);
569 if (it != m_shaders.end()) {
570 if (!it->second.filepath.empty()) {
571 m_shader_filepath_cache.erase(it->second.filepath);
572 m_shader_cache.erase(it->second.filepath);
573 }
574 if (it->second.module) {
575 it->second.module->cleanup(get_device());
576 }
577 m_shaders.erase(it);
578 }
579}
580
581//==============================================================================
582// Shader Introspection
583//==============================================================================
584
586{
587 auto it = m_shaders.find(shader_id);
588 if (it == m_shaders.end()) {
589 return {};
590 }
591
592 const auto& reflection = it->second.module->get_reflection();
593
595 info.stage = it->second.stage;
596 info.entry_point = it->second.entry_point;
597 info.workgroup_size = reflection.workgroup_size;
598
599 for (const auto& binding : reflection.bindings) {
600 info.descriptor_bindings.push_back({ .set = binding.set,
601 .binding = binding.binding,
602 .type = binding.type,
603 .name = binding.name });
604 }
605
606 for (const auto& pc : reflection.push_constants) {
607 PushConstantRangeInfo pc_info {};
608 pc_info.offset = pc.offset;
609 pc_info.size = pc.size;
610 info.push_constant_ranges.push_back(pc_info);
611 }
612
613 return info;
614}
615
616std::vector<DescriptorBindingInfo> ShaderFoundry::get_active_descriptor_bindings(ShaderID shader_id)
617{
618 std::vector<DescriptorBindingInfo> bindings;
619
620 auto it = m_shaders.find(shader_id);
621 if (it == m_shaders.end()) {
622 return bindings;
623 }
624
625 for (const auto& binding : it->second.module->get_active_bindings()) {
626 bindings.push_back({ .set = binding.set,
627 .binding = binding.binding,
628 .type = binding.type,
629 .name = binding.name });
630 }
631
632 return bindings;
633}
634
636{
637 auto it = m_shaders.find(shader_id);
638 if (it != m_shaders.end()) {
639 return it->second.stage;
640 }
642}
643
645{
646 auto it = m_shaders.find(shader_id);
647 if (it != m_shaders.end()) {
648 return it->second.entry_point;
649 }
650 return "main";
651}
652
653bool ShaderFoundry::is_cached(const std::string& cache_key) const
654{
655 return m_shader_cache.find(cache_key) != m_shader_cache.end();
656}
657
658std::vector<std::string> ShaderFoundry::get_cached_keys() const
659{
660 std::vector<std::string> keys;
661 keys.reserve(m_shader_cache.size());
662 for (const auto& [key, _] : m_shader_cache) {
663 keys.push_back(key);
664 }
665 return keys;
666}
667
669{
670 auto device = get_device();
671
672 for (auto& [key, shader_module] : m_shader_cache) {
673 if (shader_module) {
674 shader_module->cleanup(device);
675 }
676 }
677
678 for (auto& [id, state] : m_shaders) {
679 if (state.module) {
680 state.module->cleanup(device);
681 }
682 }
683
684 m_shader_cache.clear();
685 m_shaders.clear();
687
689 "Cleaned up shader modules");
690}
691
692//==============================================================================
693// Hot-Reload Support
694//==============================================================================
695
696void ShaderFoundry::invalidate_cache(const std::string& cache_key)
697{
698 auto it = m_shader_cache.find(cache_key);
699 if (it != m_shader_cache.end()) {
700 m_shader_cache.erase(it);
702 "Invalidated shader cache: {}", cache_key);
703 }
704}
705
712
713std::shared_ptr<Core::VKShaderModule> ShaderFoundry::hot_reload(const std::string& filepath)
714{
715 invalidate_cache(filepath);
716 return compile_from_file(filepath);
717}
718
719//==============================================================================
720// Configuration
721//==============================================================================
722
724{
725 m_config = config;
726 append_default_include_directories(m_config);
728 "Updated shader compiler configuration");
729}
730
731void ShaderFoundry::add_include_directory(const std::string& directory)
732{
733 m_config.include_directories.push_back(directory);
734}
735
736void ShaderFoundry::add_define(const std::string& name, const std::string& value)
737{
739}
740
741//==============================================================================
742// Descriptor Management
743//==============================================================================
744
746{
748
750 state.descriptor_set = m_global_descriptor_manager->allocate_set(get_device(), layout);
751
752 return id;
753}
754
756 DescriptorSetID descriptor_set_id,
757 uint32_t binding,
758 vk::DescriptorType type,
759 vk::Buffer buffer,
760 size_t offset,
761 size_t size)
762{
763 auto it = m_descriptor_sets.find(descriptor_set_id);
764 if (it == m_descriptor_sets.end()) {
765 return;
766 }
767
768 vk::DescriptorBufferInfo buffer_info;
769 buffer_info.buffer = buffer;
770 buffer_info.offset = offset;
771 buffer_info.range = size;
772
773 vk::WriteDescriptorSet write;
774 write.dstSet = it->second.descriptor_set;
775 write.dstBinding = binding;
776 write.dstArrayElement = 0;
777 write.descriptorCount = 1;
778 write.descriptorType = type;
779 write.pBufferInfo = &buffer_info;
780
781 get_device().updateDescriptorSets(1, &write, 0, nullptr);
782}
783
784void ShaderFoundry::update_descriptor_buffers(std::span<const DescriptorBufferWrite> writes)
785{
786 if (writes.empty()) {
787 return;
788 }
789
790 std::vector<vk::DescriptorBufferInfo> buffer_infos;
791 buffer_infos.reserve(writes.size());
792 std::vector<vk::WriteDescriptorSet> descriptor_writes;
793 descriptor_writes.reserve(writes.size());
794
795 for (const auto& w : writes) {
796 auto it = m_descriptor_sets.find(w.descriptor_set_id);
797 if (it == m_descriptor_sets.end()) {
798 continue;
799 }
800
801 buffer_infos.emplace_back(w.buffer, w.offset, w.size);
802
803 vk::WriteDescriptorSet write;
804 write.dstSet = it->second.descriptor_set;
805 write.dstBinding = w.binding;
806 write.dstArrayElement = 0;
807 write.descriptorCount = 1;
808 write.descriptorType = w.type;
809 write.pBufferInfo = &buffer_infos.back();
810 descriptor_writes.push_back(write);
811 }
812
813 if (!descriptor_writes.empty()) {
814 get_device().updateDescriptorSets(
815 static_cast<uint32_t>(descriptor_writes.size()), descriptor_writes.data(), 0, nullptr);
816 }
817}
818
820 DescriptorSetID descriptor_set_id,
821 uint32_t binding,
822 vk::ImageView image_view,
823 vk::Sampler sampler,
824 vk::ImageLayout layout,
825 uint32_t array_element)
826{
827 auto it = m_descriptor_sets.find(descriptor_set_id);
828 if (it == m_descriptor_sets.end()) {
829 return;
830 }
831
832 vk::DescriptorImageInfo image_info;
833 image_info.imageView = image_view;
834 image_info.sampler = sampler;
835 image_info.imageLayout = layout;
836
837 vk::WriteDescriptorSet write;
838 write.dstSet = it->second.descriptor_set;
839 write.dstBinding = binding;
840 write.dstArrayElement = array_element;
841 write.descriptorCount = 1;
842 write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
843 write.pImageInfo = &image_info;
844
845 get_device().updateDescriptorSets(1, &write, 0, nullptr);
846}
847
849 DescriptorSetID descriptor_set_id,
850 uint32_t binding,
851 vk::ImageView image_view,
852 vk::ImageLayout layout)
853{
854 auto it = m_descriptor_sets.find(descriptor_set_id);
855 if (it == m_descriptor_sets.end()) {
856 return;
857 }
858
859 vk::DescriptorImageInfo image_info;
860 image_info.imageView = image_view;
861 image_info.imageLayout = layout;
862
863 vk::WriteDescriptorSet write;
864 write.dstSet = it->second.descriptor_set;
865 write.dstBinding = binding;
866 write.dstArrayElement = 0;
867 write.descriptorCount = 1;
868 write.descriptorType = vk::DescriptorType::eStorageImage;
869 write.pImageInfo = &image_info;
870
871 get_device().updateDescriptorSets(1, &write, 0, nullptr);
872}
873
874void ShaderFoundry::update_descriptor_images(std::span<const DescriptorImageWrite> writes)
875{
876 if (writes.empty()) {
877 return;
878 }
879
880 std::vector<vk::DescriptorImageInfo> image_infos;
881 image_infos.reserve(writes.size());
882 std::vector<vk::WriteDescriptorSet> descriptor_writes;
883 descriptor_writes.reserve(writes.size());
884
885 for (const auto& w : writes) {
886 auto it = m_descriptor_sets.find(w.descriptor_set_id);
887 if (it == m_descriptor_sets.end()) {
888 continue;
889 }
890
891 vk::DescriptorImageInfo info;
892 info.imageView = w.image_view;
893 info.sampler = w.sampler;
894 info.imageLayout = w.layout;
895 image_infos.push_back(info);
896
897 vk::WriteDescriptorSet write;
898 write.dstSet = it->second.descriptor_set;
899 write.dstBinding = w.binding;
900 write.dstArrayElement = 0;
901 write.descriptorCount = 1;
902 write.descriptorType = w.type;
903 write.pImageInfo = &image_infos.back();
904 descriptor_writes.push_back(write);
905 }
906
907 if (!descriptor_writes.empty()) {
908 get_device().updateDescriptorSets(
909 static_cast<uint32_t>(descriptor_writes.size()), descriptor_writes.data(), 0, nullptr);
910 }
911}
912
913vk::DescriptorSet ShaderFoundry::get_descriptor_set(DescriptorSetID descriptor_set_id)
914{
915 auto it = m_descriptor_sets.find(descriptor_set_id);
916 if (it == m_descriptor_sets.end()) {
917 error<std::invalid_argument>(
920 std::source_location::current(),
921 "Invalid DescriptorSetID: {}", descriptor_set_id);
922 }
923 return it->second.descriptor_set;
924}
925
927{
928 auto device = get_device();
929
930 m_descriptor_sets.clear();
931
935 }
936
938 "Cleaned up descriptor resources");
939}
940
941//==============================================================================
942// Command Recording
943//==============================================================================
944
946{
947 auto& cmd_manager = m_backend->get_command_manager();
948
950
952
954 ? cmd_manager.begin_single_time_commands_compute()
955 : cmd_manager.begin_single_time_commands();
956
957 state.type = type;
958 state.is_active = true;
959
960 return id;
961}
962
964 vk::Format color_format,
965 vk::Format depth_format)
966{
967 auto& cmd_manager = m_backend->get_command_manager();
968
970
971 vk::CommandBuffer cmd = cmd_manager.allocate_command_buffer(vk::CommandBufferLevel::eSecondary);
972
973 vk::CommandBufferInheritanceRenderingInfo inheritance_rendering;
974 inheritance_rendering.colorAttachmentCount = 1;
975 inheritance_rendering.pColorAttachmentFormats = &color_format;
976 inheritance_rendering.depthAttachmentFormat = depth_format;
977 inheritance_rendering.rasterizationSamples = vk::SampleCountFlagBits::e1;
978
979 vk::CommandBufferInheritanceInfo inheritance_info;
980 inheritance_info.pNext = &inheritance_rendering;
981
982 vk::CommandBufferBeginInfo begin_info;
983 begin_info.flags = vk::CommandBufferUsageFlagBits::eRenderPassContinue | vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
984 begin_info.pInheritanceInfo = &inheritance_info;
985
986 cmd.begin(begin_info);
987
989 state.cmd = cmd;
992 state.is_active = true;
993
994 return id;
995}
996
998{
999 auto it = m_command_buffers.find(cmd_id);
1000 if (it != m_command_buffers.end()) {
1001 return it->second.cmd;
1002 }
1003 return nullptr;
1004}
1005
1007{
1008 auto it = m_command_buffers.find(cmd_id);
1009 if (it == m_command_buffers.end() || !it->second.is_active) {
1010 return false;
1011 }
1012
1013 it->second.cmd.end();
1014 it->second.is_active = false;
1015 return true;
1016}
1017
1019{
1020 if (!m_backend || !s_initialized) {
1021 return;
1022 }
1023
1024 auto& cmd_manager = m_backend->get_command_manager();
1025 auto device = get_device();
1026 device.waitIdle();
1027
1028 for (auto& [id, state] : m_command_buffers) {
1029 if (state.is_active) {
1030 cmd_manager.free_command_buffer(state.cmd);
1031 }
1032 if (state.timestamp_pool) {
1033 device.destroyQueryPool(state.timestamp_pool);
1034 }
1035 }
1036 m_command_buffers.clear();
1037
1039 "Freed all command buffers");
1040}
1041
1042//==============================================================================
1043// Synchronization
1044//==============================================================================
1045
1047{
1048 auto it = m_command_buffers.find(cmd_id);
1049 if (it == m_command_buffers.end() || !it->second.is_active) {
1050 return;
1051 }
1052
1053 auto& cmd_manager = m_backend->get_command_manager();
1054
1055 it->second.cmd.end();
1056
1057 vk::SubmitInfo submit_info;
1058 submit_info.commandBufferCount = 1;
1059 submit_info.pCommandBuffers = &it->second.cmd;
1060
1061 vk::Queue queue;
1062 switch (it->second.type) {
1064 queue = m_graphics_queue;
1065 break;
1067 queue = m_compute_queue;
1068 break;
1070 queue = m_transfer_queue;
1071 break;
1072 }
1073
1074 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
1076 "Failed to submit command buffer");
1077 return;
1078 }
1079 queue.waitIdle();
1080
1081 if (it->second.timestamp_pool) {
1082 for (const auto& query : it->second.timestamp_queries) {
1083 m_finished_timestamps.push({ cmd_id, resolve_timestamp(it->second, query.first, false) });
1084 }
1085
1086 get_device().destroyQueryPool(it->second.timestamp_pool);
1087 it->second.timestamp_pool = nullptr;
1088 }
1089
1090 cmd_manager.free_command_buffer(it->second.cmd);
1091
1092 it->second.is_active = false;
1093 m_command_buffers.erase(it);
1094}
1095
1097{
1098 auto cmd_it = m_command_buffers.find(cmd_id);
1099 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
1100 return INVALID_FENCE;
1101 }
1102
1103 cmd_it->second.cmd.end();
1104
1105 FenceID fence_id = m_next_fence_id++;
1106
1107 vk::FenceCreateInfo fence_info;
1108 FenceState& fence_state = m_fences[fence_id];
1109 fence_state.fence = get_device().createFence(fence_info);
1110 fence_state.signaled = false;
1111 fence_state.cmd_id = cmd_id;
1112
1113 vk::SubmitInfo submit_info;
1114 submit_info.commandBufferCount = 1;
1115 submit_info.pCommandBuffers = &cmd_it->second.cmd;
1116
1117 vk::Queue queue;
1118 switch (cmd_it->second.type) {
1120 queue = m_graphics_queue;
1121 break;
1123 queue = m_compute_queue;
1124 break;
1126 queue = m_transfer_queue;
1127 break;
1128 }
1129
1130 if (queue.submit(1, &submit_info, fence_state.fence) != vk::Result::eSuccess) {
1132 "Failed to submit command buffer");
1133 return INVALID_FENCE;
1134 }
1135
1136 cmd_it->second.is_active = false;
1137
1138 return fence_id;
1139}
1140
1142{
1143 auto cmd_it = m_command_buffers.find(cmd_id);
1144 if (cmd_it == m_command_buffers.end() || !cmd_it->second.is_active) {
1145 return INVALID_SEMAPHORE;
1146 }
1147
1148 cmd_it->second.cmd.end();
1149
1150 SemaphoreID semaphore_id = m_next_semaphore_id++;
1151
1152 vk::SemaphoreCreateInfo semaphore_info;
1153 SemaphoreState& semaphore_state = m_semaphores[semaphore_id];
1154 semaphore_state.semaphore = get_device().createSemaphore(semaphore_info);
1155
1156 vk::SubmitInfo submit_info;
1157 submit_info.commandBufferCount = 1;
1158 submit_info.pCommandBuffers = &cmd_it->second.cmd;
1159 submit_info.signalSemaphoreCount = 1;
1160 submit_info.pSignalSemaphores = &semaphore_state.semaphore;
1161
1162 vk::Queue queue;
1163 switch (cmd_it->second.type) {
1165 queue = m_graphics_queue;
1166 break;
1168 queue = m_compute_queue;
1169 break;
1171 queue = m_transfer_queue;
1172 break;
1173 }
1174
1175 if (queue.submit(1, &submit_info, nullptr) != vk::Result::eSuccess) {
1177 "Failed to submit command buffer");
1178 return INVALID_SEMAPHORE;
1179 }
1180
1181 cmd_it->second.is_active = false;
1182
1183 return semaphore_id;
1184}
1185
1187{
1188 auto it = m_fences.find(fence_id);
1189 if (it == m_fences.end()) {
1190 return;
1191 }
1192
1193 if (get_device().waitForFences(1, &it->second.fence, VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
1195 "Failed to wait for fence: {}", fence_id);
1196 return;
1197 }
1198 it->second.signaled = true;
1199}
1200
1201void ShaderFoundry::wait_for_fences(const std::vector<FenceID>& fence_ids)
1202{
1203 std::vector<vk::Fence> fences;
1204 for (auto fence_id : fence_ids) {
1205 auto it = m_fences.find(fence_id);
1206 if (it != m_fences.end()) {
1207 fences.push_back(it->second.fence);
1208 }
1209 }
1210
1211 if (!fences.empty()) {
1212 if (get_device().waitForFences(static_cast<uint32_t>(fences.size()), fences.data(), VK_TRUE, UINT64_MAX) != vk::Result::eSuccess) {
1214 "Failed to wait for fences");
1215 return;
1216 }
1217 }
1218
1219 for (auto fence_id : fence_ids) {
1220 auto it = m_fences.find(fence_id);
1221 if (it != m_fences.end()) {
1222 it->second.signaled = true;
1223 }
1224 }
1225}
1226
1228{
1229 if (fence_id == INVALID_FENCE)
1230 return;
1231
1232 auto it = m_fences.find(fence_id);
1233 if (it == m_fences.end())
1234 return;
1235
1236 if (it->second.fence) {
1237 get_device().destroyFence(it->second.fence);
1238 it->second.fence = nullptr;
1239 }
1240
1241 const CommandBufferID cmd_id = it->second.cmd_id;
1242 m_fences.erase(it);
1243
1244 if (cmd_id != INVALID_COMMAND_BUFFER) {
1245 auto cmd_it = m_command_buffers.find(cmd_id);
1246 if (cmd_it != m_command_buffers.end()) {
1247 m_backend->get_command_manager().free_command_buffer(cmd_it->second.cmd);
1248 m_command_buffers.erase(cmd_it);
1249 }
1250 }
1251}
1252
1254{
1255 auto it = m_fences.find(fence_id);
1256 if (it == m_fences.end()) {
1257 return false;
1258 }
1259
1260 if (it->second.signaled) {
1261 return true;
1262 }
1263
1264 auto result = get_device().getFenceStatus(it->second.fence);
1265 it->second.signaled = (result == vk::Result::eSuccess);
1266 return it->second.signaled;
1267}
1268
1271 SemaphoreID wait_semaphore,
1272 vk::PipelineStageFlags /*wait_stage*/)
1273{
1274 auto sem_it = m_semaphores.find(wait_semaphore);
1275 if (sem_it == m_semaphores.end()) {
1277 }
1278
1279 return begin_commands(type);
1280}
1281
1283{
1284 auto it = m_semaphores.find(semaphore_id);
1285 if (it != m_semaphores.end()) {
1286 return it->second.semaphore;
1287 }
1288 return nullptr;
1289}
1290
1292{
1293 auto device = get_device();
1294
1295 for (auto& [id, state] : m_fences) {
1296 if (state.fence) {
1297 device.destroyFence(state.fence);
1298 }
1299 }
1300 m_fences.clear();
1301
1302 for (auto& [id, state] : m_semaphores) {
1303 if (state.semaphore) {
1304 device.destroySemaphore(state.semaphore);
1305 }
1306 }
1307 m_semaphores.clear();
1308
1310 "Cleaned up sync objects");
1311}
1312
1313//==============================================================================
1314// Memory Barriers
1315//==============================================================================
1316
1318 CommandBufferID cmd_id,
1319 vk::Buffer buffer,
1320 vk::AccessFlags src_access,
1321 vk::AccessFlags dst_access,
1322 vk::PipelineStageFlags src_stage,
1323 vk::PipelineStageFlags dst_stage)
1324{
1325 auto it = m_command_buffers.find(cmd_id);
1326 if (it == m_command_buffers.end()) {
1327 return;
1328 }
1329
1330 vk::BufferMemoryBarrier barrier;
1331 barrier.srcAccessMask = src_access;
1332 barrier.dstAccessMask = dst_access;
1333 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1334 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1335 barrier.buffer = buffer;
1336 barrier.offset = 0;
1337 barrier.size = VK_WHOLE_SIZE;
1338
1339 it->second.cmd.pipelineBarrier(
1340 src_stage,
1341 dst_stage,
1342 vk::DependencyFlags {},
1343 0, nullptr,
1344 1, &barrier,
1345 0, nullptr);
1346}
1347
1349 CommandBufferID cmd_id,
1350 vk::Image image,
1351 vk::ImageLayout old_layout,
1352 vk::ImageLayout new_layout,
1353 vk::AccessFlags src_access,
1354 vk::AccessFlags dst_access,
1355 vk::PipelineStageFlags src_stage,
1356 vk::PipelineStageFlags dst_stage)
1357{
1358 auto it = m_command_buffers.find(cmd_id);
1359 if (it == m_command_buffers.end()) {
1360 return;
1361 }
1362
1363 vk::ImageMemoryBarrier barrier;
1364 barrier.srcAccessMask = src_access;
1365 barrier.dstAccessMask = dst_access;
1366 barrier.oldLayout = old_layout;
1367 barrier.newLayout = new_layout;
1368 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1369 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1370 barrier.image = image;
1371 barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
1372 barrier.subresourceRange.baseMipLevel = 0;
1373 barrier.subresourceRange.levelCount = 1;
1374 barrier.subresourceRange.baseArrayLayer = 0;
1375 barrier.subresourceRange.layerCount = 1;
1376
1377 it->second.cmd.pipelineBarrier(
1378 src_stage,
1379 dst_stage,
1380 vk::DependencyFlags {},
1381 0, nullptr,
1382 0, nullptr,
1383 1, &barrier);
1384}
1385
1386//==============================================================================
1387// Queue Management
1388//==============================================================================
1389
1393
1394//==============================================================================
1395// Profiling
1396//==============================================================================
1397
1399{
1400 auto it = m_command_buffers.find(cmd_id);
1401 if (it == m_command_buffers.end()) {
1402 return;
1403 }
1404
1405 if (!it->second.timestamp_pool) {
1406 vk::QueryPoolCreateInfo pool_info;
1407 pool_info.queryType = vk::QueryType::eTimestamp;
1408 pool_info.queryCount = 128;
1409 it->second.timestamp_pool = get_device().createQueryPool(pool_info);
1410 }
1411
1412 auto query_index = static_cast<uint32_t>(it->second.timestamp_queries.size() * 2);
1413 it->second.timestamp_queries[label] = query_index;
1414
1415 it->second.cmd.resetQueryPool(it->second.timestamp_pool, query_index, 2);
1416 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, it->second.timestamp_pool, query_index);
1417}
1418
1420{
1421 auto it = m_command_buffers.find(cmd_id);
1422 if (it == m_command_buffers.end()) {
1423 return;
1424 }
1425
1426 auto query_it = it->second.timestamp_queries.find(label);
1427 if (query_it == it->second.timestamp_queries.end()) {
1428 return;
1429 }
1430
1431 uint32_t query_index = query_it->second;
1432 it->second.cmd.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, it->second.timestamp_pool, query_index + 1);
1433}
1434
1436{
1437 auto it = m_command_buffers.find(cmd_id);
1438 if (it != m_command_buffers.end()) {
1439 return resolve_timestamp(it->second, label, true);
1440 }
1441
1442 for (size_t i = 0; i < m_finished_timestamps.capacity(); ++i) {
1443 const auto& entry = m_finished_timestamps[i];
1444 if (entry.cmd_id == cmd_id && entry.result.label == label) {
1445 return entry.result;
1446 }
1447 }
1448
1449 return { .label = label, .duration_ns = 0, .valid = false };
1450}
1451
1453{
1454 auto query_it = state.timestamp_queries.find(label);
1455 if (query_it == state.timestamp_queries.end() || !state.timestamp_pool) {
1456 return { .label = label, .duration_ns = 0, .valid = false };
1457 }
1458
1459 uint32_t query_index = query_it->second;
1460 uint64_t timestamps[2];
1461
1462 vk::QueryResultFlags flags = vk::QueryResultFlagBits::e64;
1463 if (wait) {
1464 flags |= vk::QueryResultFlagBits::eWait;
1465 }
1466
1467 auto result = get_device().getQueryPoolResults(
1468 state.timestamp_pool,
1469 query_index,
1470 2,
1471 sizeof(timestamps),
1472 timestamps,
1473 sizeof(uint64_t),
1474 flags);
1475
1476 if (result != vk::Result::eSuccess) {
1477 return { .label = label, .duration_ns = 0, .valid = false };
1478 }
1479
1480 auto props = m_backend->get_context().get_physical_device().getProperties();
1481 float timestamp_period = props.limits.timestampPeriod;
1482
1483 auto duration_ns = static_cast<uint64_t>((timestamps[1] - timestamps[0]) * timestamp_period);
1484
1485 return { .label = label, .duration_ns = duration_ns, .valid = true };
1486}
1487
1488//==============================================================================
1489// Internal Access
1490//==============================================================================
1491
1492std::shared_ptr<Core::VKShaderModule> ShaderFoundry::get_vk_shader_module(ShaderID shader_id)
1493{
1494 auto& foundry = ShaderFoundry::instance();
1495 auto it = foundry.m_shaders.find(shader_id);
1496 if (it != foundry.m_shaders.end()) {
1497 return it->second.module;
1498 }
1499 return nullptr;
1500}
1501
1502//==============================================================================
1503// Utilities
1504//==============================================================================
1505
1506vk::ShaderStageFlagBits ShaderFoundry::to_vulkan_stage(ShaderStage stage)
1507{
1508 switch (stage) {
1510 return vk::ShaderStageFlagBits::eCompute;
1512 return vk::ShaderStageFlagBits::eVertex;
1514 return vk::ShaderStageFlagBits::eFragment;
1516 return vk::ShaderStageFlagBits::eGeometry;
1518 return vk::ShaderStageFlagBits::eTessellationControl;
1520 return vk::ShaderStageFlagBits::eTessellationEvaluation;
1521 case ShaderStage::MESH:
1522 return vk::ShaderStageFlagBits::eMeshEXT;
1523 case ShaderStage::TASK:
1524 return vk::ShaderStageFlagBits::eTaskEXT;
1525 default:
1526 return vk::ShaderStageFlagBits::eCompute;
1527 }
1528}
1529
1530std::optional<ShaderStage> ShaderFoundry::detect_stage_from_extension(const std::string& filepath)
1531{
1532 auto vk_stage = Core::VKShaderModule::detect_stage_from_extension(filepath);
1533 if (!vk_stage.has_value()) {
1534 return std::nullopt;
1535 }
1536
1537 switch (*vk_stage) {
1538 case vk::ShaderStageFlagBits::eCompute:
1539 return ShaderStage::COMPUTE;
1540 case vk::ShaderStageFlagBits::eVertex:
1541 return ShaderStage::VERTEX;
1542 case vk::ShaderStageFlagBits::eFragment:
1543 return ShaderStage::FRAGMENT;
1544 case vk::ShaderStageFlagBits::eGeometry:
1545 return ShaderStage::GEOMETRY;
1546 case vk::ShaderStageFlagBits::eTessellationControl:
1548 case vk::ShaderStageFlagBits::eTessellationEvaluation:
1550 case vk::ShaderStageFlagBits::eMeshEXT:
1551 return ShaderStage::MESH;
1552 case vk::ShaderStageFlagBits::eTaskEXT:
1553 return ShaderStage::TASK;
1554 default:
1555 return std::nullopt;
1556 }
1557}
1558
1559//==============================================================================
1560// Private Helpers
1561//==============================================================================
1562
1563std::shared_ptr<Core::VKShaderModule> ShaderFoundry::create_shader_module()
1564{
1565 return std::make_shared<Core::VKShaderModule>();
1566}
1567
1569{
1570 return m_backend->get_context().get_device();
1571}
1572
1573vk::PhysicalDevice ShaderFoundry::get_physical_device() const
1574{
1575 return m_backend->get_context().get_physical_device();
1576}
1577
1578} // 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
uint64_t length
Definition Composite.cpp:11
uint64_t offset
Definition Composite.cpp:10
uint32_t label
Definition Contours.cpp:16
IO::ImageData image
Definition Decoder.cpp:47
bool size
std::shared_ptr< NetworkGeometryBuffer > buffer
glm::vec3 value
vk::PhysicalDevice device
Definition VKDevice.cpp:121
vk::PhysicalDeviceType type
Definition VKDevice.cpp:126
std::string name
Definition VKDevice.cpp:123
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.
TimestampResult resolve_timestamp(const CommandBufferState &state, const std::string &label, bool wait)
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.
void update_descriptor_buffers(std::span< const DescriptorBufferWrite > writes)
Update several buffer descriptor slots in one Vulkan call.
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.
std::vector< DescriptorBindingInfo > get_active_descriptor_bindings(ShaderID shader_id)
Get the descriptor bindings a shader's entry point statically uses.
void update_descriptor_images(std::span< const DescriptorImageWrite > writes)
Update several image descriptor slots in one Vulkan call.
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()
Memory::HistoryBuffer< FinishedTimestamp > m_finished_timestamps
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::unordered_map< std::string, uint32_t > timestamp_queries
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.