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