MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VKShaderModule.cpp
Go to the documentation of this file.
1#include "VKShaderModule.hpp"
2
4
5#include <fstream>
6
7#ifdef MAYAFLUX_USE_SHADERC
8#include <shaderc/shaderc.hpp>
9#endif
10
11#include <spirv_cross/spirv_cross.hpp>
12
13namespace MayaFlux::Core {
14
15namespace {
16 std::vector<std::string> get_shader_search_paths()
17 {
18 std::vector<std::string> paths {
19 SHADER_BUILD_OUTPUT_DIR, // 1. Build directory (development)
20 SHADER_INSTALL_DIR, // 2. Install directory (production)
21 SHADER_SOURCE_DIR, // 3. Source directory (fallback)
22 "./shaders", // 4. Current working directory
23 "../shaders", // 5. Parent directory
24 "data/shaders", // 6. Weave project root convention
25 "./data/shaders", // 6. Weave project root convention
26 "../data/shaders" // 7. if running from build/
27 };
28
29 if (std::string_view(SHADER_EXAMPLE_DIR).length() > 0) {
30 paths.emplace_back(SHADER_EXAMPLE_DIR);
31 }
32
33#ifdef MAYAFLUX_PROJECT_SHADER_DIR
34 paths.emplace_back(MAYAFLUX_PROJECT_SHADER_DIR);
35#endif
36
37 return paths;
38 }
39
40 std::string resolve_shader_path(const std::string& filename)
41 {
42 namespace fs = std::filesystem;
43
44 if (fs::path(filename).is_absolute() || fs::exists(filename)) {
45 return filename;
46 }
47
48 for (const auto& search_path : get_shader_search_paths()) {
49 fs::path full_path = fs::path(search_path) / filename;
50 if (fs::exists(full_path)) {
52 "Resolved shader '{}' -> '{}'", filename, full_path.string());
53 return full_path.string();
54 }
55 }
56
57 return filename;
58 }
59
60 vk::DescriptorType spirv_to_vk_descriptor_type(spirv_cross::SPIRType::BaseType base_type,
61 const spirv_cross::SPIRType& type,
62 bool is_storage)
63 {
64 if (type.image.dim != spv::DimMax) {
65 if (type.image.sampled == 2) {
66 return vk::DescriptorType::eStorageImage;
67 }
68
69 if (type.image.sampled == 1) {
70 return is_storage ? vk::DescriptorType::eSampledImage
71 : vk::DescriptorType::eCombinedImageSampler;
72 }
73 }
74
75 if (is_storage) {
76 return vk::DescriptorType::eStorageBuffer;
77 }
78
79 return vk::DescriptorType::eUniformBuffer;
80 }
81
82 class FileIncluder : public shaderc::CompileOptions::IncluderInterface {
83 public:
84 explicit FileIncluder(const std::vector<std::string>& dirs)
85 : m_dirs(dirs)
86 {
87 }
88
89 shaderc_include_result* GetInclude(
90 const char* requested_source,
91 shaderc_include_type /*type*/,
92 const char* /*requesting_source*/,
93 size_t /*include_depth*/) override
94 {
95 namespace fs = std::filesystem;
96 auto* result = new shaderc_include_result {};
97
98 for (const auto& dir : m_dirs) {
99 fs::path full = fs::path(dir) / requested_source;
100 if (fs::exists(full)) {
101 auto* container = new std::string(full.string());
102 auto* content = new std::string(read_file(full.string()));
103 result->source_name = container->c_str();
104 result->source_name_length = container->size();
105 result->content = content->c_str();
106 result->content_length = content->size();
107 result->user_data = new std::pair<std::string*, std::string*>(container, content);
108 return result;
109 }
110 }
111
112 static const std::string err = "include not found";
113 result->source_name = "";
114 result->source_name_length = 0;
115 result->content = err.c_str();
116 result->content_length = err.size();
117 result->user_data = nullptr;
118 return result;
119 }
120
121 void ReleaseInclude(shaderc_include_result* result) override
122 {
123 if (result->user_data) {
124 auto* p = static_cast<std::pair<std::string*, std::string*>*>(result->user_data);
125 delete p->first;
126 delete p->second;
127 delete p;
128 }
129 delete result;
130 }
131
132 private:
133 const std::vector<std::string>& m_dirs;
134
135 static std::string read_file(const std::string& path)
136 {
137 std::ifstream f(path);
138 return { std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>() };
139 }
140 };
141}
142
143// ============================================================================
144// Lifecycle
145// ============================================================================
146
148{
149 if (m_module) {
151 "VKShaderModule destroyed without cleanup() - potential leak");
152 }
153}
154
156 : m_module(other.m_module)
157 , m_stage(other.m_stage)
158 , m_entry_point(std::move(other.m_entry_point))
159 , m_reflection(std::move(other.m_reflection))
160 , m_spirv_code(std::move(other.m_spirv_code))
161 , m_preserve_spirv(other.m_preserve_spirv)
162 , m_specialization_map(std::move(other.m_specialization_map))
163 , m_specialization_entries(std::move(other.m_specialization_entries))
164 , m_specialization_data(std::move(other.m_specialization_data))
165 , m_specialization_info(other.m_specialization_info)
166{
167 other.m_module = nullptr;
168}
169
171{
172 if (this != &other) {
173 if (m_module) {
175 "VKShaderModule move-assigned without cleanup() - potential leak");
176 }
177
178 m_module = other.m_module;
179 m_stage = other.m_stage;
180 m_entry_point = std::move(other.m_entry_point);
181 m_reflection = std::move(other.m_reflection);
182 m_spirv_code = std::move(other.m_spirv_code);
183 m_preserve_spirv = other.m_preserve_spirv;
184 m_specialization_map = std::move(other.m_specialization_map);
185 m_specialization_entries = std::move(other.m_specialization_entries);
186 m_specialization_data = std::move(other.m_specialization_data);
187 m_specialization_info = other.m_specialization_info;
188
189 other.m_module = nullptr;
190 }
191 return *this;
192}
193
194void VKShaderModule::cleanup(vk::Device device)
195{
196 if (m_module) {
197 device.destroyShaderModule(m_module);
198 m_module = nullptr;
199
201 "Shader module cleaned up ({} stage)", vk::to_string(m_stage));
202 }
203
204 m_spirv_code.clear();
206 m_specialization_map.clear();
208 m_specialization_data.clear();
209}
210
211// ============================================================================
212// Creation from SPIR-V
213// ============================================================================
214
216 vk::Device device,
217 const std::vector<uint32_t>& spirv_code,
218 vk::ShaderStageFlagBits stage,
219 const std::string& entry_point,
220 bool enable_reflection)
221{
222 if (spirv_code.empty()) {
224 "Cannot create shader module from empty SPIR-V code");
225 return false;
226 }
227
228 if (spirv_code[0] != 0x07230203) {
230 "Invalid SPIR-V magic number: 0x{:08X} (expected 0x07230203)",
231 spirv_code[0]);
232 return false;
233 }
234
235 vk::ShaderModuleCreateInfo create_info;
236 create_info.codeSize = spirv_code.size() * sizeof(uint32_t);
237 create_info.pCode = spirv_code.data();
238
239 try {
240 m_module = device.createShaderModule(create_info);
241 } catch (const vk::SystemError& e) {
243 "Failed to create shader module: {}", e.what());
244 return false;
245 }
246
247 m_stage = stage;
248 m_entry_point = entry_point;
249
250 if (m_preserve_spirv) {
251 m_spirv_code = spirv_code;
252 }
253
254 if (enable_reflection) {
255 if (!reflect_spirv(spirv_code)) {
257 "Shader reflection failed - descriptor layouts must be manually specified");
258 }
259 }
260
262 "Shader module created ({} stage, {} bytes SPIR-V, entry='{}')",
263 vk::to_string(stage), spirv_code.size() * 4, entry_point);
264
265 return true;
266}
267
269 vk::Device device,
270 const std::string& spirv_path,
271 vk::ShaderStageFlagBits stage,
272 const std::string& entry_point,
273 bool enable_reflection)
274{
275 std::string resolved_path = resolve_shader_path(spirv_path);
276
277 auto spirv_code = read_spirv_file(resolved_path);
278 if (spirv_code.empty()) {
280 "Failed to read SPIR-V file: '{}'", spirv_path);
281 return false;
282 }
283
285 "Loaded SPIR-V from file: '{}'", spirv_path);
286
287 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
288}
289
290// ============================================================================
291// Creation from GLSL
292// ============================================================================
293
295 vk::Device device,
296 const std::string& glsl_source,
297 vk::ShaderStageFlagBits stage,
298 const std::string& entry_point,
299 bool enable_reflection,
300 const std::vector<std::string>& include_directories,
301 const std::unordered_map<std::string, std::string>& defines)
302{
303 auto spirv_code = compile_glsl_to_spirv(glsl_source, stage, include_directories, defines);
304 if (spirv_code.empty()) {
306 "Failed to compile GLSL to SPIR-V ({} stage)", vk::to_string(stage));
307 return false;
308 }
309
311 "Compiled GLSL to SPIR-V ({} stage, {} bytes)",
312 vk::to_string(stage), spirv_code.size() * 4);
313
314 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
315}
316
318 vk::Device device,
319 const std::string& glsl_path,
320 std::optional<vk::ShaderStageFlagBits> stage,
321 const std::string& entry_point,
322 bool enable_reflection,
323 const std::vector<std::string>& include_directories,
324 const std::unordered_map<std::string, std::string>& defines)
325{
326 std::string resolved_path = resolve_shader_path(glsl_path);
327
328 if (!stage.has_value()) {
329 stage = detect_stage_from_extension(glsl_path);
330 if (!stage.has_value()) {
332 "Cannot auto-detect shader stage from file extension: '{}'", glsl_path);
333 return false;
334 }
336 "Auto-detected {} stage from file extension", vk::to_string(*stage));
337 }
338
339 auto glsl_source = read_text_file(resolved_path);
340 if (glsl_source.empty()) {
342 "Failed to read GLSL file: '{}'", glsl_path);
343 return false;
344 }
345
347 "Loaded GLSL from file: '{}' ({} bytes)", glsl_path, glsl_source.size());
348
349 return create_from_glsl(device, glsl_source, *stage, entry_point,
350 enable_reflection, include_directories, defines);
351}
352
353// ============================================================================
354// Pipeline Integration
355// ============================================================================
356
357vk::PipelineShaderStageCreateInfo VKShaderModule::get_stage_create_info() const
358{
359 if (!m_module) {
361 "Cannot get stage create info from invalid shader module");
362 return {};
363 }
364
365 vk::PipelineShaderStageCreateInfo stage_info;
366 stage_info.stage = m_stage;
367 stage_info.module = m_module;
368 stage_info.pName = m_entry_point.c_str();
369
370 if (!m_specialization_entries.empty()) {
371 const_cast<VKShaderModule*>(this)->update_specialization_info();
372 stage_info.pSpecializationInfo = &m_specialization_info;
373 }
374
375 return stage_info;
376}
377
378// ============================================================================
379// Specialization Constants
380// ============================================================================
381
383 const std::unordered_map<uint32_t, uint32_t>& constants)
384{
385 m_specialization_map = constants;
386
388 "Set {} specialization constants for {} stage",
389 constants.size(), vk::to_string(m_stage));
390}
391
393{
394 if (m_specialization_map.empty()) {
396 m_specialization_data.clear();
397 m_specialization_info = vk::SpecializationInfo {};
398 return;
399 }
400
402 m_specialization_data.clear();
403
406
407 uint32_t offset = 0;
408 for (const auto& [constant_id, value] : m_specialization_map) {
409 vk::SpecializationMapEntry entry;
410 entry.constantID = constant_id;
411 entry.offset = offset;
412 entry.size = sizeof(uint32_t);
413 m_specialization_entries.push_back(entry);
414
415 m_specialization_data.push_back(value);
416 offset += sizeof(uint32_t);
417 }
418
419 m_specialization_info.mapEntryCount = static_cast<uint32_t>(m_specialization_entries.size());
421 m_specialization_info.dataSize = m_specialization_data.size() * sizeof(uint32_t);
423}
424
425// ============================================================================
426// Reflection
427// ============================================================================
428
429bool VKShaderModule::reflect_spirv(const std::vector<uint32_t>& spirv_code)
430{
431 try {
432 spirv_cross::Compiler compiler(spirv_code);
433 spirv_cross::ShaderResources resources = compiler.get_shader_resources();
434
435 auto reflect_resources = [&](const spirv_cross::SmallVector<spirv_cross::Resource>& res_vec,
436 bool is_storage = false) {
437 for (const auto& resource : res_vec) {
439 desc.set = compiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
440 desc.binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
441 desc.stage = m_stage;
442 desc.name = resource.name;
443
444 const auto& type = compiler.get_type(resource.type_id);
445 desc.count = type.array.empty() ? 1 : type.array[0];
446 desc.type = spirv_to_vk_descriptor_type(type.basetype, type, is_storage);
447
448 m_reflection.bindings.push_back(desc);
449 }
450 };
451
452 reflect_resources(resources.uniform_buffers, false);
453
454 reflect_resources(resources.storage_buffers, true);
455
456 reflect_resources(resources.sampled_images, false);
457
458 reflect_resources(resources.storage_images, true);
459
460 reflect_resources(resources.separate_images, false);
461 reflect_resources(resources.separate_samplers, false);
462
463 if (!m_reflection.bindings.empty()) {
465 "Reflected {} descriptor bindings", m_reflection.bindings.size());
466 }
467
468 for (const auto& pc_buffer : resources.push_constant_buffers) {
469 const auto& type = compiler.get_type(pc_buffer.type_id);
470
472 range.stage = m_stage;
473 range.offset = 0;
474 range.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
475
476 m_reflection.push_constants.push_back(range);
477 }
478
479 if (!m_reflection.push_constants.empty()) {
481 "Reflected {} push constant blocks", m_reflection.push_constants.size());
482 }
483
484 auto spec_constants = compiler.get_specialization_constants();
485 for (const auto& spec : spec_constants) {
487 sc.constant_id = spec.constant_id;
488 sc.name = compiler.get_name(spec.id);
489
490 const auto& type = compiler.get_type(compiler.get_constant(spec.id).constant_type);
491 sc.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
492
494 }
495
498 "Reflected {} specialization constants",
500 }
501
502 if (m_stage == vk::ShaderStageFlagBits::eCompute
503 || m_stage == vk::ShaderStageFlagBits::eMeshEXT
504 || m_stage == vk::ShaderStageFlagBits::eTaskEXT) {
505 auto entry_points = compiler.get_entry_points_and_stages();
506
507 for (const auto& ep : entry_points) {
508 if (ep.name == m_entry_point && ep.execution_model == spv::ExecutionModelGLCompute) {
509
510 std::array<uint32_t, 3> workgroup_size {
511 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 0),
512 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 1),
513 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 2)
514 };
515
516 if (!workgroup_size.empty() && workgroup_size.size() >= 3) {
517 m_reflection.workgroup_size = std::array<uint32_t, 3> {
518 workgroup_size[0],
519 workgroup_size[1],
520 workgroup_size[2]
521 };
522
524 "Compute shader workgroup size: [{}, {}, {}]",
525 workgroup_size[0], workgroup_size[1], workgroup_size[2]);
526 }
527 break;
528 }
529 }
530 }
531
532 if (m_stage == vk::ShaderStageFlagBits::eVertex) {
533 for (const auto& input : resources.stage_inputs) {
534 uint32_t location = compiler.get_decoration(input.id, spv::DecorationLocation);
535 const auto& type = compiler.get_type(input.type_id);
536
537 vk::VertexInputAttributeDescription attr;
538 attr.location = location;
539 attr.binding = 0;
540 attr.format = spirv_type_to_vk_format(type);
541 attr.offset = 0;
542
543 m_reflection.vertex_attributes.push_back(attr);
544 }
545
546 if (!m_reflection.vertex_attributes.empty()) {
548 "Reflected {} vertex input attributes",
550 }
551 }
552
553 return true;
554
555 } catch (const spirv_cross::CompilerError& e) {
557 "SPIRV-Cross reflection failed: {}", e.what());
558 return false;
559 }
560}
561
562vk::Format VKShaderModule::spirv_type_to_vk_format(const spirv_cross::SPIRType& type)
563{
564 using BaseType = spirv_cross::SPIRType::BaseType;
565
566 if (type.columns > 1) {
568 "Matrix types are not valid vertex attributes (columns={})",
569 type.columns);
570 return vk::Format::eUndefined;
571 }
572
573 if (type.width != 32) {
575 "Unsupported SPIR-V vertex attribute width {} (only 32-bit supported)",
576 type.width);
577 return vk::Format::eUndefined;
578 }
579
580 const uint32_t vec_size = type.vecsize;
581 if (type.basetype == BaseType::Float) {
582 switch (vec_size) {
583 case 1:
584 return vk::Format::eR32Sfloat;
585 case 2:
586 return vk::Format::eR32G32Sfloat;
587 case 3:
588 return vk::Format::eR32G32B32Sfloat;
589 case 4:
590 return vk::Format::eR32G32B32A32Sfloat;
591 }
592 } else if (type.basetype == BaseType::Int) {
593 switch (vec_size) {
594 case 1:
595 return vk::Format::eR32Sint;
596 case 2:
597 return vk::Format::eR32G32Sint;
598 case 3:
599 return vk::Format::eR32G32B32Sint;
600 case 4:
601 return vk::Format::eR32G32B32A32Sint;
602 }
603 } else if (type.basetype == BaseType::UInt) {
604 switch (vec_size) {
605 case 1:
606 return vk::Format::eR32Uint;
607 case 2:
608 return vk::Format::eR32G32Uint;
609 case 3:
610 return vk::Format::eR32G32B32Uint;
611 case 4:
612 return vk::Format::eR32G32B32A32Uint;
613 }
614 }
615
617 "Unsupported SPIR-V vertex attribute type (basetype={}, vecsize={})",
618 static_cast<int>(type.basetype), vec_size);
619
620 return vk::Format::eUndefined;
621}
622
623// ============================================================================
624// Utility Functions
625// ============================================================================
626
627std::optional<vk::ShaderStageFlagBits> VKShaderModule::detect_stage_from_extension(
628 const std::string& filepath)
629{
630 std::filesystem::path path(filepath);
631 std::string ext = path.extension().string();
632
633 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
634
635 static const std::unordered_map<std::string, vk::ShaderStageFlagBits> extension_map = {
636 { ".comp", vk::ShaderStageFlagBits::eCompute },
637 { ".vert", vk::ShaderStageFlagBits::eVertex },
638 { ".frag", vk::ShaderStageFlagBits::eFragment },
639 { ".geom", vk::ShaderStageFlagBits::eGeometry },
640 { ".tesc", vk::ShaderStageFlagBits::eTessellationControl },
641 { ".tese", vk::ShaderStageFlagBits::eTessellationEvaluation },
642 { ".rgen", vk::ShaderStageFlagBits::eRaygenKHR },
643 { ".rint", vk::ShaderStageFlagBits::eIntersectionKHR },
644 { ".rahit", vk::ShaderStageFlagBits::eAnyHitKHR },
645 { ".rchit", vk::ShaderStageFlagBits::eClosestHitKHR },
646 { ".rmiss", vk::ShaderStageFlagBits::eMissKHR },
647 { ".rcall", vk::ShaderStageFlagBits::eCallableKHR },
648 { ".mesh", vk::ShaderStageFlagBits::eMeshEXT },
649 { ".task", vk::ShaderStageFlagBits::eTaskEXT }
650 };
651
652 auto it = extension_map.find(ext);
653 if (it != extension_map.end()) {
654 return it->second;
655 }
656
657 return std::nullopt;
658}
659
661 const std::string& glsl_source,
662 vk::ShaderStageFlagBits stage,
663 const std::vector<std::string>& include_directories,
664 const std::unordered_map<std::string, std::string>& defines)
665{
666#ifdef MAYAFLUX_USE_SHADERC
667 shaderc::Compiler compiler;
668 shaderc::CompileOptions options;
669
670 options.SetOptimizationLevel(shaderc_optimization_level_performance);
671 options.SetIncluder(std::make_unique<FileIncluder>(include_directories));
672
673 for (const auto& [name, value] : defines) {
674 options.AddMacroDefinition(name, value);
675 }
676
677 shaderc_shader_kind shader_kind;
678 switch (stage) {
679 case vk::ShaderStageFlagBits::eVertex:
680 shader_kind = shaderc_glsl_vertex_shader;
681 break;
682 case vk::ShaderStageFlagBits::eFragment:
683 shader_kind = shaderc_glsl_fragment_shader;
684 break;
685 case vk::ShaderStageFlagBits::eCompute:
686 shader_kind = shaderc_glsl_compute_shader;
687 break;
688 case vk::ShaderStageFlagBits::eGeometry:
689 shader_kind = shaderc_glsl_geometry_shader;
690 break;
691 case vk::ShaderStageFlagBits::eTessellationControl:
692 shader_kind = shaderc_glsl_tess_control_shader;
693 break;
694 case vk::ShaderStageFlagBits::eTessellationEvaluation:
695 shader_kind = shaderc_glsl_tess_evaluation_shader;
696 break;
697 case vk::ShaderStageFlagBits::eMeshEXT:
698 shader_kind = shaderc_glsl_mesh_shader;
699 break;
700 case vk::ShaderStageFlagBits::eTaskEXT:
701 shader_kind = shaderc_glsl_task_shader;
702 break;
703 default:
705 "Unsupported shader stage for GLSL compilation: {}", vk::to_string(stage));
706 return {};
707 }
708
709 shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
710 glsl_source,
711 shader_kind,
712 "shader.glsl",
713 options);
714
715 if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
717 "GLSL compilation failed:\n{}", result.GetErrorMessage());
718 return {};
719 }
720
721 std::vector<uint32_t> spirv(result.cbegin(), result.cend());
722
724 "Compiled GLSL ({} stage) -> {} bytes SPIR-V",
725 vk::to_string(stage), spirv.size() * 4);
726
727 return spirv;
728
729#else
730 return compile_glsl_to_spirv_external(glsl_source, stage, include_directories, defines);
731#endif
732}
733
734std::vector<uint32_t> VKShaderModule::read_spirv_file(const std::string& filepath)
735{
736 std::ifstream file(filepath, std::ios::binary | std::ios::ate);
737 if (!file.is_open()) {
739 "Failed to open SPIR-V file: '{}'", filepath);
740 return {};
741 }
742
743 size_t file_size = static_cast<size_t>(file.tellg());
744 if (file_size == 0) {
746 "SPIR-V file is empty: '{}'", filepath);
747 return {};
748 }
749
750 if (file_size % sizeof(uint32_t) != 0) {
752 "SPIR-V file size ({} bytes) is not multiple of 4: '{}'",
753 file_size, filepath);
754 return {};
755 }
756
757 std::vector<uint32_t> buffer(file_size / sizeof(uint32_t));
758 file.seekg(0);
759 file.read(reinterpret_cast<char*>(buffer.data()), file_size);
760
761 if (!file) {
763 "Failed to read SPIR-V file: '{}'", filepath);
764 return {};
765 }
766
767 return buffer;
768}
769
770std::string VKShaderModule::read_text_file(const std::string& filepath)
771{
772 std::ifstream file(filepath);
773 if (!file.is_open()) {
775 "Failed to open file: '{}'", filepath);
776 return {};
777 }
778
779 std::string content(
780 (std::istreambuf_iterator<char>(file)),
781 std::istreambuf_iterator<char>());
782
783 if (content.empty()) {
785 "File is empty: '{}'", filepath);
786 }
787
788 return content;
789}
790
791#ifndef MAYAFLUX_USE_SHADERC
792namespace {
793 bool is_command_available(const std::string& command)
794 {
795#if defined(MAYAFLUX_PLATFORM_WINDOWS)
796 std::string check_cmd = "where " + command + " >nul 2>&1";
797#else
798 std::string check_cmd = "command -v " + command + " >/dev/null 2>&1";
799#endif
800 return std::system(check_cmd.c_str()) == 0;
801 }
802
803 std::string get_null_device()
804 {
805#if defined(MAYAFLUX_PLATFORM_WINDOWS)
806 return "nul";
807#else
808 return "/dev/null";
809#endif
810 }
811}
812
814 const std::string& glsl_source,
815 vk::ShaderStageFlagBits stage,
816 const std::vector<std::string>& include_directories,
817 const std::unordered_map<std::string, std::string>& defines)
818{
819 namespace fs = std::filesystem;
820
821 if (!is_command_available("glslc")) {
823 "glslc compiler not found in PATH. Install Vulkan SDK or enable MAYAFLUX_USE_SHADERC");
824 return {};
825 }
826
827 fs::path temp_dir = fs::temp_directory_path();
828 fs::path glsl_temp = temp_dir / "mayaflux_shader_temp.glsl";
829 fs::path spirv_temp = temp_dir / "mayaflux_shader_temp.spv";
830
831 {
832 std::ofstream out(glsl_temp);
833 if (!out.is_open()) {
835 "Failed to create temporary GLSL file: '{}'", glsl_temp.string());
836 return {};
837 }
838 out << glsl_source;
839 }
840
841 std::string stage_flag;
842 switch (stage) {
843 case vk::ShaderStageFlagBits::eVertex:
844 stage_flag = "-fshader-stage=vertex";
845 break;
846 case vk::ShaderStageFlagBits::eFragment:
847 stage_flag = "-fshader-stage=fragment";
848 break;
849 case vk::ShaderStageFlagBits::eCompute:
850 stage_flag = "-fshader-stage=compute";
851 break;
852 case vk::ShaderStageFlagBits::eGeometry:
853 stage_flag = "-fshader-stage=geometry";
854 break;
855 case vk::ShaderStageFlagBits::eTessellationControl:
856 stage_flag = "-fshader-stage=tesscontrol";
857 break;
858 case vk::ShaderStageFlagBits::eTessellationEvaluation:
859 stage_flag = "-fshader-stage=tesseval";
860 break;
861 case vk::ShaderStageFlagBits::eMeshEXT:
862 stage_flag = "-fshader-stage=mesh";
863 break;
864 case vk::ShaderStageFlagBits::eTaskEXT:
865 stage_flag = "-fshader-stage=task";
866 break;
867 default:
869 "Unsupported shader stage for external compilation: {}", vk::to_string(stage));
870 fs::remove(glsl_temp);
871 return {};
872 }
873
874#if defined(MAYAFLUX_PLATFORM_WINDOWS)
875 std::string cmd = "glslc " + stage_flag + " \"" + glsl_temp.string() + "\" -o \"" + spirv_temp.string() + "\"";
876#else
877 std::string cmd = "glslc " + stage_flag + " '" + glsl_temp.string() + "' -o '" + spirv_temp.string() + "'";
878#endif
879
880 for (const auto& dir : include_directories) {
881#if defined(MAYAFLUX_PLATFORM_WINDOWS)
882 cmd += " -I\"" + dir + "\"";
883#else
884 cmd += " -I'" + dir + "'";
885#endif
886 }
887
888 for (const auto& [name, value] : defines) {
889 cmd += " -D" + name;
890 if (!value.empty()) {
891 cmd += "=" + value;
892 }
893 }
894
895 std::string null_device = get_null_device();
896
898 "Invoking external glslc : {}", cmd);
899
900 int result = std::system(cmd.c_str());
901
902 fs::remove(glsl_temp);
903
904 if (result != 0) {
906 "External glslc compilation failed (exit code: {})", result);
908 "Make sure Vulkan SDK is installed or enable MAYAFLUX_USE_SHADERC for runtime compilation");
909 fs::remove(spirv_temp);
910 return {};
911 }
912
913 auto spirv_code = read_spirv_file(spirv_temp.string());
914
915 fs::remove(spirv_temp);
916
917 if (!spirv_code.empty()) {
919 "Compiled GLSL ({} stage) via external glslc -> {} bytes SPIR-V",
920 vk::to_string(stage), spirv_code.size() * 4);
921 }
922
923 return spirv_code;
924}
925#endif
926
928{
929 switch (m_stage) {
930 case vk::ShaderStageFlagBits::eCompute:
931 return Stage::COMPUTE;
932 case vk::ShaderStageFlagBits::eVertex:
933 return Stage::VERTEX;
934 case vk::ShaderStageFlagBits::eFragment:
935 return Stage::FRAGMENT;
936 case vk::ShaderStageFlagBits::eGeometry:
937 return Stage::GEOMETRY;
938 case vk::ShaderStageFlagBits::eTessellationControl:
939 return Stage::TESS_CONTROL;
940 case vk::ShaderStageFlagBits::eTessellationEvaluation:
942 case vk::ShaderStageFlagBits::eMeshEXT:
943 return Stage::MESH;
944 case vk::ShaderStageFlagBits::eTaskEXT:
945 return Stage::TASK;
946 default:
948 "Unknown shader stage: {}", vk::to_string(m_stage));
949 return Stage::COMPUTE;
950 }
951}
952
953} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
vk::CommandBuffer cmd
Core::GlobalInputConfig input
Definition Config.cpp:36
const std::vector< std::string > & m_dirs
vk::SpecializationInfo m_specialization_info
bool create_from_glsl_file(vk::Device device, const std::string &glsl_path, std::optional< vk::ShaderStageFlagBits > stage=std::nullopt, const std::string &entry_point="main", bool enable_reflection=true, const std::vector< std::string > &include_directories={}, const std::unordered_map< std::string, std::string > &defines={})
Create shader module from GLSL file.
bool create_from_glsl(vk::Device device, const std::string &glsl_source, vk::ShaderStageFlagBits stage, const std::string &entry_point="main", bool enable_reflection=true, const std::vector< std::string > &include_directories={}, const std::unordered_map< std::string, std::string > &defines={})
Create shader module from GLSL source string.
static std::optional< vk::ShaderStageFlagBits > detect_stage_from_extension(const std::string &filepath)
Auto-detect shader stage from file extension.
bool reflect_spirv(const std::vector< uint32_t > &spirv_code)
Perform reflection on SPIR-V bytecode.
bool create_from_spirv(vk::Device device, const std::vector< uint32_t > &spirv_code, vk::ShaderStageFlagBits stage, const std::string &entry_point="main", bool enable_reflection=true)
Create shader module from SPIR-V binary.
vk::PipelineShaderStageCreateInfo get_stage_create_info() const
Get pipeline shader stage create info.
static std::vector< uint32_t > compile_glsl_to_spirv_external(const std::string &glsl_source, vk::ShaderStageFlagBits stage, const std::vector< std::string > &include_directories={}, const std::unordered_map< std::string, std::string > &defines={})
std::vector< vk::SpecializationMapEntry > m_specialization_entries
VKShaderModule & operator=(const VKShaderModule &)=delete
vk::ShaderStageFlagBits m_stage
bool create_from_spirv_file(vk::Device device, const std::string &spirv_path, vk::ShaderStageFlagBits stage, const std::string &entry_point="main", bool enable_reflection=true)
Create shader module from SPIR-V file.
void update_specialization_info()
Update specialization info from current map Called before get_stage_create_info() to ensure fresh dat...
std::vector< uint32_t > compile_glsl_to_spirv(const std::string &glsl_source, vk::ShaderStageFlagBits stage, const std::vector< std::string > &include_directories, const std::unordered_map< std::string, std::string > &defines)
Compile GLSL to SPIR-V using shaderc.
static std::string read_text_file(const std::string &filepath)
Read text file into string.
void set_specialization_constants(const std::unordered_map< uint32_t, uint32_t > &constants)
Set specialization constants.
static std::vector< uint32_t > read_spirv_file(const std::string &filepath)
Read binary file into vector.
static vk::Format spirv_type_to_vk_format(const spirv_cross::SPIRType &type)
Convert SPIRV-Cross type to Vulkan vertex attribute format.
std::vector< uint32_t > m_specialization_data
void cleanup(vk::Device device)
Cleanup shader module.
std::unordered_map< uint32_t, uint32_t > m_specialization_map
Stage get_stage_type() const
Get shader stage type.
std::vector< uint32_t > m_spirv_code
Preserved SPIR-V (if enabled)
Wrapper for Vulkan shader module with lifecycle and reflection.
@ GraphicsBackend
Graphics/visual rendering backend (Vulkan, OpenGL)
@ Core
Core engine, backend, subsystems.
vk::ShaderStageFlags stage
Stage visibility.
uint32_t count
Array size (1 for non-arrays)
vk::DescriptorType type
Type (uniform buffer, storage buffer, etc.)
std::vector< SpecializationConstant > specialization_constants
std::vector< DescriptorBinding > bindings
std::vector< PushConstantRange > push_constants
std::vector< vk::VertexInputAttributeDescription > vertex_attributes
std::optional< std::array< uint32_t, 3 > > workgroup_size
local_size_x/y/z
Metadata extracted from shader module.