MayaFlux 0.2.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 return {
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 };
25 }
26
27 std::string resolve_shader_path(const std::string& filename)
28 {
29 namespace fs = std::filesystem;
30
31 if (fs::path(filename).is_absolute() || fs::exists(filename)) {
32 return filename;
33 }
34
35 for (const auto& search_path : get_shader_search_paths()) {
36 fs::path full_path = fs::path(search_path) / filename;
37 if (fs::exists(full_path)) {
39 "Resolved shader '{}' -> '{}'", filename, full_path.string());
40 return full_path.string();
41 }
42 }
43
44 return filename;
45 }
46
47 vk::DescriptorType spirv_to_vk_descriptor_type(spirv_cross::SPIRType::BaseType base_type,
48 const spirv_cross::SPIRType& type,
49 bool is_storage)
50 {
51 if (type.image.dim != spv::DimMax) {
52 if (type.image.sampled == 2) {
53 return vk::DescriptorType::eStorageImage;
54 }
55
56 if (type.image.sampled == 1) {
57 return is_storage ? vk::DescriptorType::eSampledImage
58 : vk::DescriptorType::eCombinedImageSampler;
59 }
60 }
61
62 if (is_storage) {
63 return vk::DescriptorType::eStorageBuffer;
64 }
65
66 return vk::DescriptorType::eUniformBuffer;
67 }
68}
69
70// ============================================================================
71// Lifecycle
72// ============================================================================
73
75{
76 if (m_module) {
78 "VKShaderModule destroyed without cleanup() - potential leak");
79 }
80}
81
83 : m_module(other.m_module)
84 , m_stage(other.m_stage)
85 , m_entry_point(std::move(other.m_entry_point))
86 , m_reflection(std::move(other.m_reflection))
87 , m_spirv_code(std::move(other.m_spirv_code))
88 , m_preserve_spirv(other.m_preserve_spirv)
89 , m_specialization_map(std::move(other.m_specialization_map))
90 , m_specialization_entries(std::move(other.m_specialization_entries))
91 , m_specialization_data(std::move(other.m_specialization_data))
92 , m_specialization_info(other.m_specialization_info)
93{
94 other.m_module = nullptr;
95}
96
98{
99 if (this != &other) {
100 if (m_module) {
102 "VKShaderModule move-assigned without cleanup() - potential leak");
103 }
104
105 m_module = other.m_module;
106 m_stage = other.m_stage;
107 m_entry_point = std::move(other.m_entry_point);
108 m_reflection = std::move(other.m_reflection);
109 m_spirv_code = std::move(other.m_spirv_code);
110 m_preserve_spirv = other.m_preserve_spirv;
111 m_specialization_map = std::move(other.m_specialization_map);
112 m_specialization_entries = std::move(other.m_specialization_entries);
113 m_specialization_data = std::move(other.m_specialization_data);
114 m_specialization_info = other.m_specialization_info;
115
116 other.m_module = nullptr;
117 }
118 return *this;
119}
120
121void VKShaderModule::cleanup(vk::Device device)
122{
123 if (m_module) {
124 device.destroyShaderModule(m_module);
125 m_module = nullptr;
126
128 "Shader module cleaned up ({} stage)", vk::to_string(m_stage));
129 }
130
131 m_spirv_code.clear();
133 m_specialization_map.clear();
135 m_specialization_data.clear();
136}
137
138// ============================================================================
139// Creation from SPIR-V
140// ============================================================================
141
143 vk::Device device,
144 const std::vector<uint32_t>& spirv_code,
145 vk::ShaderStageFlagBits stage,
146 const std::string& entry_point,
147 bool enable_reflection)
148{
149 if (spirv_code.empty()) {
151 "Cannot create shader module from empty SPIR-V code");
152 return false;
153 }
154
155 if (spirv_code[0] != 0x07230203) {
157 "Invalid SPIR-V magic number: 0x{:08X} (expected 0x07230203)",
158 spirv_code[0]);
159 return false;
160 }
161
162 vk::ShaderModuleCreateInfo create_info;
163 create_info.codeSize = spirv_code.size() * sizeof(uint32_t);
164 create_info.pCode = spirv_code.data();
165
166 try {
167 m_module = device.createShaderModule(create_info);
168 } catch (const vk::SystemError& e) {
170 "Failed to create shader module: {}", e.what());
171 return false;
172 }
173
174 m_stage = stage;
175 m_entry_point = entry_point;
176
177 if (m_preserve_spirv) {
178 m_spirv_code = spirv_code;
179 }
180
181 if (enable_reflection) {
182 if (!reflect_spirv(spirv_code)) {
184 "Shader reflection failed - descriptor layouts must be manually specified");
185 }
186 }
187
189 "Shader module created ({} stage, {} bytes SPIR-V, entry='{}')",
190 vk::to_string(stage), spirv_code.size() * 4, entry_point);
191
192 return true;
193}
194
196 vk::Device device,
197 const std::string& spirv_path,
198 vk::ShaderStageFlagBits stage,
199 const std::string& entry_point,
200 bool enable_reflection)
201{
202 std::string resolved_path = resolve_shader_path(spirv_path);
203
204 auto spirv_code = read_spirv_file(resolved_path);
205 if (spirv_code.empty()) {
207 "Failed to read SPIR-V file: '{}'", spirv_path);
208 return false;
209 }
210
212 "Loaded SPIR-V from file: '{}'", spirv_path);
213
214 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
215}
216
217// ============================================================================
218// Creation from GLSL
219// ============================================================================
220
222 vk::Device device,
223 const std::string& glsl_source,
224 vk::ShaderStageFlagBits stage,
225 const std::string& entry_point,
226 bool enable_reflection,
227 const std::vector<std::string>& include_directories,
228 const std::unordered_map<std::string, std::string>& defines)
229{
230 auto spirv_code = compile_glsl_to_spirv(glsl_source, stage, include_directories, defines);
231 if (spirv_code.empty()) {
233 "Failed to compile GLSL to SPIR-V ({} stage)", vk::to_string(stage));
234 return false;
235 }
236
238 "Compiled GLSL to SPIR-V ({} stage, {} bytes)",
239 vk::to_string(stage), spirv_code.size() * 4);
240
241 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
242}
243
245 vk::Device device,
246 const std::string& glsl_path,
247 std::optional<vk::ShaderStageFlagBits> stage,
248 const std::string& entry_point,
249 bool enable_reflection,
250 const std::vector<std::string>& include_directories,
251 const std::unordered_map<std::string, std::string>& defines)
252{
253 std::string resolved_path = resolve_shader_path(glsl_path);
254
255 if (!stage.has_value()) {
256 stage = detect_stage_from_extension(glsl_path);
257 if (!stage.has_value()) {
259 "Cannot auto-detect shader stage from file extension: '{}'", glsl_path);
260 return false;
261 }
263 "Auto-detected {} stage from file extension", vk::to_string(*stage));
264 }
265
266 auto glsl_source = read_text_file(resolved_path);
267 if (glsl_source.empty()) {
269 "Failed to read GLSL file: '{}'", glsl_path);
270 return false;
271 }
272
274 "Loaded GLSL from file: '{}' ({} bytes)", glsl_path, glsl_source.size());
275
276 return create_from_glsl(device, glsl_source, *stage, entry_point,
277 enable_reflection, include_directories, defines);
278}
279
280// ============================================================================
281// Pipeline Integration
282// ============================================================================
283
284vk::PipelineShaderStageCreateInfo VKShaderModule::get_stage_create_info() const
285{
286 if (!m_module) {
288 "Cannot get stage create info from invalid shader module");
289 return {};
290 }
291
292 vk::PipelineShaderStageCreateInfo stage_info;
293 stage_info.stage = m_stage;
294 stage_info.module = m_module;
295 stage_info.pName = m_entry_point.c_str();
296
297 if (!m_specialization_entries.empty()) {
298 const_cast<VKShaderModule*>(this)->update_specialization_info();
299 stage_info.pSpecializationInfo = &m_specialization_info;
300 }
301
302 return stage_info;
303}
304
305// ============================================================================
306// Specialization Constants
307// ============================================================================
308
310 const std::unordered_map<uint32_t, uint32_t>& constants)
311{
312 m_specialization_map = constants;
313
315 "Set {} specialization constants for {} stage",
316 constants.size(), vk::to_string(m_stage));
317}
318
320{
321 if (m_specialization_map.empty()) {
323 m_specialization_data.clear();
324 m_specialization_info = vk::SpecializationInfo {};
325 return;
326 }
327
329 m_specialization_data.clear();
330
333
334 uint32_t offset = 0;
335 for (const auto& [constant_id, value] : m_specialization_map) {
336 vk::SpecializationMapEntry entry;
337 entry.constantID = constant_id;
338 entry.offset = offset;
339 entry.size = sizeof(uint32_t);
340 m_specialization_entries.push_back(entry);
341
342 m_specialization_data.push_back(value);
343 offset += sizeof(uint32_t);
344 }
345
346 m_specialization_info.mapEntryCount = static_cast<uint32_t>(m_specialization_entries.size());
348 m_specialization_info.dataSize = m_specialization_data.size() * sizeof(uint32_t);
350}
351
352// ============================================================================
353// Reflection
354// ============================================================================
355
356bool VKShaderModule::reflect_spirv(const std::vector<uint32_t>& spirv_code)
357{
358 try {
359 spirv_cross::Compiler compiler(spirv_code);
360 spirv_cross::ShaderResources resources = compiler.get_shader_resources();
361
362 auto reflect_resources = [&](const spirv_cross::SmallVector<spirv_cross::Resource>& res_vec,
363 bool is_storage = false) {
364 for (const auto& resource : res_vec) {
366 desc.set = compiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
367 desc.binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
368 desc.stage = m_stage;
369 desc.name = resource.name;
370
371 const auto& type = compiler.get_type(resource.type_id);
372 desc.count = type.array.empty() ? 1 : type.array[0];
373 desc.type = spirv_to_vk_descriptor_type(type.basetype, type, is_storage);
374
375 m_reflection.bindings.push_back(desc);
376 }
377 };
378
379 reflect_resources(resources.uniform_buffers, false);
380
381 reflect_resources(resources.storage_buffers, true);
382
383 reflect_resources(resources.sampled_images, false);
384
385 reflect_resources(resources.storage_images, true);
386
387 reflect_resources(resources.separate_images, false);
388 reflect_resources(resources.separate_samplers, false);
389
390 if (!m_reflection.bindings.empty()) {
392 "Reflected {} descriptor bindings", m_reflection.bindings.size());
393 }
394
395 for (const auto& pc_buffer : resources.push_constant_buffers) {
396 const auto& type = compiler.get_type(pc_buffer.type_id);
397
399 range.stage = m_stage;
400 range.offset = 0;
401 range.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
402
403 m_reflection.push_constants.push_back(range);
404 }
405
406 if (!m_reflection.push_constants.empty()) {
408 "Reflected {} push constant blocks", m_reflection.push_constants.size());
409 }
410
411 auto spec_constants = compiler.get_specialization_constants();
412 for (const auto& spec : spec_constants) {
414 sc.constant_id = spec.constant_id;
415 sc.name = compiler.get_name(spec.id);
416
417 const auto& type = compiler.get_type(compiler.get_constant(spec.id).constant_type);
418 sc.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
419
421 }
422
425 "Reflected {} specialization constants",
427 }
428
429 if (m_stage == vk::ShaderStageFlagBits::eCompute) {
430 auto entry_points = compiler.get_entry_points_and_stages();
431
432 for (const auto& ep : entry_points) {
433 if (ep.name == m_entry_point && ep.execution_model == spv::ExecutionModelGLCompute) {
434
435 std::array<uint32_t, 3> workgroup_size {
436 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 0),
437 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 1),
438 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 2)
439 };
440
441 if (!workgroup_size.empty() && workgroup_size.size() >= 3) {
442 m_reflection.workgroup_size = std::array<uint32_t, 3> {
443 workgroup_size[0],
444 workgroup_size[1],
445 workgroup_size[2]
446 };
447
449 "Compute shader workgroup size: [{}, {}, {}]",
450 workgroup_size[0], workgroup_size[1], workgroup_size[2]);
451 }
452 break;
453 }
454 }
455 }
456
457 if (m_stage == vk::ShaderStageFlagBits::eVertex) {
458 for (const auto& input : resources.stage_inputs) {
459 uint32_t location = compiler.get_decoration(input.id, spv::DecorationLocation);
460 const auto& type = compiler.get_type(input.type_id);
461
462 vk::VertexInputAttributeDescription attr;
463 attr.location = location;
464 attr.binding = 0;
465 attr.format = spirv_type_to_vk_format(type);
466 attr.offset = 0;
467
468 m_reflection.vertex_attributes.push_back(attr);
469 }
470
471 if (!m_reflection.vertex_attributes.empty()) {
473 "Reflected {} vertex input attributes",
475 }
476 }
477
478 return true;
479
480 } catch (const spirv_cross::CompilerError& e) {
482 "SPIRV-Cross reflection failed: {}", e.what());
483 return false;
484 }
485}
486
487vk::Format VKShaderModule::spirv_type_to_vk_format(const spirv_cross::SPIRType& type)
488{
489 using BaseType = spirv_cross::SPIRType::BaseType;
490
491 if (type.columns > 1) {
493 "Matrix types are not valid vertex attributes (columns={})",
494 type.columns);
495 return vk::Format::eUndefined;
496 }
497
498 if (type.width != 32) {
500 "Unsupported SPIR-V vertex attribute width {} (only 32-bit supported)",
501 type.width);
502 return vk::Format::eUndefined;
503 }
504
505 const uint32_t vec_size = type.vecsize;
506 if (type.basetype == BaseType::Float) {
507 switch (vec_size) {
508 case 1:
509 return vk::Format::eR32Sfloat;
510 case 2:
511 return vk::Format::eR32G32Sfloat;
512 case 3:
513 return vk::Format::eR32G32B32Sfloat;
514 case 4:
515 return vk::Format::eR32G32B32A32Sfloat;
516 }
517 } else if (type.basetype == BaseType::Int) {
518 switch (vec_size) {
519 case 1:
520 return vk::Format::eR32Sint;
521 case 2:
522 return vk::Format::eR32G32Sint;
523 case 3:
524 return vk::Format::eR32G32B32Sint;
525 case 4:
526 return vk::Format::eR32G32B32A32Sint;
527 }
528 } else if (type.basetype == BaseType::UInt) {
529 switch (vec_size) {
530 case 1:
531 return vk::Format::eR32Uint;
532 case 2:
533 return vk::Format::eR32G32Uint;
534 case 3:
535 return vk::Format::eR32G32B32Uint;
536 case 4:
537 return vk::Format::eR32G32B32A32Uint;
538 }
539 }
540
542 "Unsupported SPIR-V vertex attribute type (basetype={}, vecsize={})",
543 static_cast<int>(type.basetype), vec_size);
544
545 return vk::Format::eUndefined;
546}
547
548// ============================================================================
549// Utility Functions
550// ============================================================================
551
552std::optional<vk::ShaderStageFlagBits> VKShaderModule::detect_stage_from_extension(
553 const std::string& filepath)
554{
555 std::filesystem::path path(filepath);
556 std::string ext = path.extension().string();
557
558 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
559
560 static const std::unordered_map<std::string, vk::ShaderStageFlagBits> extension_map = {
561 { ".comp", vk::ShaderStageFlagBits::eCompute },
562 { ".vert", vk::ShaderStageFlagBits::eVertex },
563 { ".frag", vk::ShaderStageFlagBits::eFragment },
564 { ".geom", vk::ShaderStageFlagBits::eGeometry },
565 { ".tesc", vk::ShaderStageFlagBits::eTessellationControl },
566 { ".tese", vk::ShaderStageFlagBits::eTessellationEvaluation },
567 { ".rgen", vk::ShaderStageFlagBits::eRaygenKHR },
568 { ".rint", vk::ShaderStageFlagBits::eIntersectionKHR },
569 { ".rahit", vk::ShaderStageFlagBits::eAnyHitKHR },
570 { ".rchit", vk::ShaderStageFlagBits::eClosestHitKHR },
571 { ".rmiss", vk::ShaderStageFlagBits::eMissKHR },
572 { ".rcall", vk::ShaderStageFlagBits::eCallableKHR },
573 { ".mesh", vk::ShaderStageFlagBits::eMeshEXT },
574 { ".task", vk::ShaderStageFlagBits::eTaskEXT }
575 };
576
577 auto it = extension_map.find(ext);
578 if (it != extension_map.end()) {
579 return it->second;
580 }
581
582 return std::nullopt;
583}
584
586 const std::string& glsl_source,
587 vk::ShaderStageFlagBits stage,
588 const std::vector<std::string>& include_directories,
589 const std::unordered_map<std::string, std::string>& defines)
590{
591#ifdef MAYAFLUX_USE_SHADERC
592 shaderc::Compiler compiler;
593 shaderc::CompileOptions options;
594
595 options.SetOptimizationLevel(shaderc_optimization_level_performance);
596
597 for (const auto& dir : include_directories) {
598 // options.AddIncludeDirectory(dir);
599 }
600
601 for (const auto& [name, value] : defines) {
602 options.AddMacroDefinition(name, value);
603 }
604
605 shaderc_shader_kind shader_kind;
606 switch (stage) {
607 case vk::ShaderStageFlagBits::eVertex:
608 shader_kind = shaderc_glsl_vertex_shader;
609 break;
610 case vk::ShaderStageFlagBits::eFragment:
611 shader_kind = shaderc_glsl_fragment_shader;
612 break;
613 case vk::ShaderStageFlagBits::eCompute:
614 shader_kind = shaderc_glsl_compute_shader;
615 break;
616 case vk::ShaderStageFlagBits::eGeometry:
617 shader_kind = shaderc_glsl_geometry_shader;
618 break;
619 case vk::ShaderStageFlagBits::eTessellationControl:
620 shader_kind = shaderc_glsl_tess_control_shader;
621 break;
622 case vk::ShaderStageFlagBits::eTessellationEvaluation:
623 shader_kind = shaderc_glsl_tess_evaluation_shader;
624 break;
625 default:
627 "Unsupported shader stage for GLSL compilation: {}", vk::to_string(stage));
628 return {};
629 }
630
631 shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
632 glsl_source,
633 shader_kind,
634 "shader.glsl",
635 options);
636
637 if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
639 "GLSL compilation failed:\n{}", result.GetErrorMessage());
640 return {};
641 }
642
643 std::vector<uint32_t> spirv(result.cbegin(), result.cend());
644
646 "Compiled GLSL ({} stage) -> {} bytes SPIR-V",
647 vk::to_string(stage), spirv.size() * 4);
648
649 return spirv;
650
651#else
652 return compile_glsl_to_spirv_external(glsl_source, stage, include_directories, defines);
653#endif
654}
655
656std::vector<uint32_t> VKShaderModule::read_spirv_file(const std::string& filepath)
657{
658 std::ifstream file(filepath, std::ios::binary | std::ios::ate);
659 if (!file.is_open()) {
661 "Failed to open SPIR-V file: '{}'", filepath);
662 return {};
663 }
664
665 size_t file_size = static_cast<size_t>(file.tellg());
666 if (file_size == 0) {
668 "SPIR-V file is empty: '{}'", filepath);
669 return {};
670 }
671
672 if (file_size % sizeof(uint32_t) != 0) {
674 "SPIR-V file size ({} bytes) is not multiple of 4: '{}'",
675 file_size, filepath);
676 return {};
677 }
678
679 std::vector<uint32_t> buffer(file_size / sizeof(uint32_t));
680 file.seekg(0);
681 file.read(reinterpret_cast<char*>(buffer.data()), file_size);
682
683 if (!file) {
685 "Failed to read SPIR-V file: '{}'", filepath);
686 return {};
687 }
688
689 return buffer;
690}
691
692std::string VKShaderModule::read_text_file(const std::string& filepath)
693{
694 std::ifstream file(filepath);
695 if (!file.is_open()) {
697 "Failed to open file: '{}'", filepath);
698 return {};
699 }
700
701 std::string content(
702 (std::istreambuf_iterator<char>(file)),
703 std::istreambuf_iterator<char>());
704
705 if (content.empty()) {
707 "File is empty: '{}'", filepath);
708 }
709
710 return content;
711}
712
713#ifndef MAYAFLUX_USE_SHADERC
714namespace {
715 bool is_command_available(const std::string& command)
716 {
717#if defined(MAYAFLUX_PLATFORM_WINDOWS)
718 std::string check_cmd = "where " + command + " >nul 2>&1";
719#else
720 std::string check_cmd = "command -v " + command + " >/dev/null 2>&1";
721#endif
722 return std::system(check_cmd.c_str()) == 0;
723 }
724
725 std::string get_null_device()
726 {
727#if defined(MAYAFLUX_PLATFORM_WINDOWS)
728 return "nul";
729#else
730 return "/dev/null";
731#endif
732 }
733}
734
736 const std::string& glsl_source,
737 vk::ShaderStageFlagBits stage,
738 const std::vector<std::string>& include_directories,
739 const std::unordered_map<std::string, std::string>& defines)
740{
741 namespace fs = std::filesystem;
742
743 if (!is_command_available("glslc")) {
745 "glslc compiler not found in PATH. Install Vulkan SDK or enable MAYAFLUX_USE_SHADERC");
746 return {};
747 }
748
749 fs::path temp_dir = fs::temp_directory_path();
750 fs::path glsl_temp = temp_dir / "mayaflux_shader_temp.glsl";
751 fs::path spirv_temp = temp_dir / "mayaflux_shader_temp.spv";
752
753 {
754 std::ofstream out(glsl_temp);
755 if (!out.is_open()) {
757 "Failed to create temporary GLSL file: '{}'", glsl_temp.string());
758 return {};
759 }
760 out << glsl_source;
761 }
762
763 std::string stage_flag;
764 switch (stage) {
765 case vk::ShaderStageFlagBits::eVertex: stage_flag = "-fshader-stage=vertex"; break;
766 case vk::ShaderStageFlagBits::eFragment: stage_flag = "-fshader-stage=fragment"; break;
767 case vk::ShaderStageFlagBits::eCompute: stage_flag = "-fshader-stage=compute"; break;
768 case vk::ShaderStageFlagBits::eGeometry: stage_flag = "-fshader-stage=geometry"; break;
769 case vk::ShaderStageFlagBits::eTessellationControl: stage_flag = "-fshader-stage=tesscontrol"; break;
770 case vk::ShaderStageFlagBits::eTessellationEvaluation: stage_flag = "-fshader-stage=tesseval"; break;
771 default:
773 "Unsupported shader stage for external compilation: {}", vk::to_string(stage));
774 fs::remove(glsl_temp);
775 return {};
776 }
777
778#if defined(MAYAFLUX_PLATFORM_WINDOWS)
779 std::string cmd = "glslc " + stage_flag + " \"" + glsl_temp.string() + "\" -o \"" + spirv_temp.string() + "\"";
780#else
781 std::string cmd = "glslc " + stage_flag + " '" + glsl_temp.string() + "' -o '" + spirv_temp.string() + "'";
782#endif
783
784 for (const auto& dir : include_directories) {
785#if defined(MAYAFLUX_PLATFORM_WINDOWS)
786 cmd += " -I\"" + dir + "\"";
787#else
788 cmd += " -I'" + dir + "'";
789#endif
790 }
791
792 for (const auto& [name, value] : defines) {
793 cmd += " -D" + name;
794 if (!value.empty()) {
795 cmd += "=" + value;
796 }
797 }
798
799 std::string null_device = get_null_device();
800
802 "Invoking external glslc : {}", cmd);
803
804 int result = std::system(cmd.c_str());
805
806 fs::remove(glsl_temp);
807
808 if (result != 0) {
810 "External glslc compilation failed (exit code: {})", result);
812 "Make sure Vulkan SDK is installed or enable MAYAFLUX_USE_SHADERC for runtime compilation");
813 fs::remove(spirv_temp);
814 return {};
815 }
816
817 auto spirv_code = read_spirv_file(spirv_temp.string());
818
819 fs::remove(spirv_temp);
820
821 if (!spirv_code.empty()) {
823 "Compiled GLSL ({} stage) via external glslc -> {} bytes SPIR-V",
824 vk::to_string(stage), spirv_code.size() * 4);
825 }
826
827 return spirv_code;
828}
829#endif
830
832{
833 switch (m_stage) {
834 case vk::ShaderStageFlagBits::eCompute:
835 return Stage::COMPUTE;
836 case vk::ShaderStageFlagBits::eVertex:
837 return Stage::VERTEX;
838 case vk::ShaderStageFlagBits::eFragment:
839 return Stage::FRAGMENT;
840 case vk::ShaderStageFlagBits::eGeometry:
841 return Stage::GEOMETRY;
842 case vk::ShaderStageFlagBits::eTessellationControl:
843 return Stage::TESS_CONTROL;
844 case vk::ShaderStageFlagBits::eTessellationEvaluation:
846 default:
848 "Unknown shader stage: {}", vk::to_string(m_stage));
849 return Stage::COMPUTE;
850 }
851}
852
853} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
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.)
vk::ShaderStageFlags stage
Stage visibility.
uint32_t offset
Offset in push constant block.
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.