MayaFlux 0.5.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#include <shaderc/shaderc.hpp>
8
9#include <spirv-tools/libspirv.h>
10#include <spirv_cross/spirv_cross.hpp>
11
12namespace MayaFlux::Core {
13
14namespace {
15 std::vector<std::string> get_shader_search_paths()
16 {
17 std::vector<std::string> paths {
18 SHADER_BUILD_OUTPUT_DIR, // 1. Build directory (development)
19 SHADER_INSTALL_DIR, // 2. Install directory (production)
20 SHADER_SOURCE_DIR, // 3. Source directory (fallback)
21 "./shaders", // 4. Current working directory
22 "../shaders", // 5. Parent directory
23 "data/shaders", // 6. Weave project root convention
24 "./data/shaders", // 6. Weave project root convention
25 "../data/shaders" // 7. if running from build/
26 };
27
28 if (std::string_view(SHADER_EXAMPLE_DIR).length() > 0) {
29 paths.emplace_back(SHADER_EXAMPLE_DIR);
30 }
31
32#ifdef MAYAFLUX_PROJECT_SHADER_DIR
33 paths.emplace_back(MAYAFLUX_PROJECT_SHADER_DIR);
34#endif
35
36 return paths;
37 }
38
39 std::string resolve_shader_path(const std::string& filename)
40 {
41 namespace fs = std::filesystem;
42
43 if (fs::path(filename).is_absolute() || fs::exists(filename)) {
44 return filename;
45 }
46
47 for (const auto& search_path : get_shader_search_paths()) {
48 fs::path full_path = fs::path(search_path) / filename;
49 if (fs::exists(full_path)) {
51 "Resolved shader '{}' -> '{}'", filename, full_path.string());
52 return full_path.string();
53 }
54 }
55
56 return filename;
57 }
58
59 vk::DescriptorType spirv_to_vk_descriptor_type(spirv_cross::SPIRType::BaseType base_type,
60 const spirv_cross::SPIRType& type,
61 bool is_storage)
62 {
63 if (type.image.dim != spv::DimMax) {
64 if (type.image.sampled == 2) {
65 return vk::DescriptorType::eStorageImage;
66 }
67
68 if (type.image.sampled == 1) {
69 return is_storage ? vk::DescriptorType::eSampledImage
70 : vk::DescriptorType::eCombinedImageSampler;
71 }
72 }
73
74 if (is_storage) {
75 return vk::DescriptorType::eStorageBuffer;
76 }
77
78 return vk::DescriptorType::eUniformBuffer;
79 }
80
81 class FileIncluder : public shaderc::CompileOptions::IncluderInterface {
82 public:
83 explicit FileIncluder(const std::vector<std::string>& dirs)
84 : m_dirs(dirs)
85 {
86 }
87
88 shaderc_include_result* GetInclude(
89 const char* requested_source,
90 shaderc_include_type /*type*/,
91 const char* /*requesting_source*/,
92 size_t /*include_depth*/) override
93 {
94 namespace fs = std::filesystem;
95 auto* result = new shaderc_include_result {};
96
97 for (const auto& dir : m_dirs) {
98 fs::path full = fs::path(dir) / requested_source;
99 if (fs::exists(full)) {
100 auto* container = new std::string(full.string());
101 auto* content = new std::string(read_file(full.string()));
102 result->source_name = container->c_str();
103 result->source_name_length = container->size();
104 result->content = content->c_str();
105 result->content_length = content->size();
106 result->user_data = new std::pair<std::string*, std::string*>(container, content);
107 return result;
108 }
109 }
110
111 static const std::string err = "include not found";
112 result->source_name = "";
113 result->source_name_length = 0;
114 result->content = err.c_str();
115 result->content_length = err.size();
116 result->user_data = nullptr;
117 return result;
118 }
119
120 void ReleaseInclude(shaderc_include_result* result) override
121 {
122 if (result->user_data) {
123 auto* p = static_cast<std::pair<std::string*, std::string*>*>(result->user_data);
124 delete p->first;
125 delete p->second;
126 delete p;
127 }
128 delete result;
129 }
130
131 private:
132 const std::vector<std::string>& m_dirs;
133
134 static std::string read_file(const std::string& path)
135 {
136 std::ifstream f(path);
137 return { std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>() };
138 }
139 };
140}
141
142// ============================================================================
143// Lifecycle
144// ============================================================================
145
147{
148 if (m_module) {
150 "VKShaderModule destroyed without cleanup() - potential leak");
151 }
152}
153
155 : m_module(other.m_module)
156 , m_stage(other.m_stage)
157 , m_entry_point(std::move(other.m_entry_point))
158 , m_reflection(std::move(other.m_reflection))
159 , m_spirv_code(std::move(other.m_spirv_code))
160 , m_preserve_spirv(other.m_preserve_spirv)
161 , m_specialization_map(std::move(other.m_specialization_map))
162 , m_specialization_entries(std::move(other.m_specialization_entries))
163 , m_specialization_data(std::move(other.m_specialization_data))
164 , m_specialization_info(other.m_specialization_info)
165{
166 other.m_module = nullptr;
167}
168
170{
171 if (this != &other) {
172 if (m_module) {
174 "VKShaderModule move-assigned without cleanup() - potential leak");
175 }
176
177 m_module = other.m_module;
178 m_stage = other.m_stage;
179 m_entry_point = std::move(other.m_entry_point);
180 m_reflection = std::move(other.m_reflection);
181 m_spirv_code = std::move(other.m_spirv_code);
182 m_preserve_spirv = other.m_preserve_spirv;
183 m_specialization_map = std::move(other.m_specialization_map);
184 m_specialization_entries = std::move(other.m_specialization_entries);
185 m_specialization_data = std::move(other.m_specialization_data);
186 m_specialization_info = other.m_specialization_info;
187
188 other.m_module = nullptr;
189 }
190 return *this;
191}
192
193void VKShaderModule::cleanup(vk::Device device)
194{
195 if (m_module) {
196 device.destroyShaderModule(m_module);
197 m_module = nullptr;
198
200 "Shader module cleaned up ({} stage)", vk::to_string(m_stage));
201 }
202
203 m_spirv_code.clear();
205 m_specialization_map.clear();
207 m_specialization_data.clear();
208}
209
210// ============================================================================
211// Creation from SPIR-V
212// ============================================================================
213
215 vk::Device device,
216 const std::vector<uint32_t>& spirv_code,
217 vk::ShaderStageFlagBits stage,
218 const std::string& entry_point,
219 bool enable_reflection)
220{
221 if (spirv_code.empty()) {
223 "Cannot create shader module from empty SPIR-V code");
224 return false;
225 }
226
227 if (spirv_code[0] != 0x07230203) {
229 "Invalid SPIR-V magic number: 0x{:08X} (expected 0x07230203)",
230 spirv_code[0]);
231 return false;
232 }
233
234 vk::ShaderModuleCreateInfo create_info;
235 create_info.codeSize = spirv_code.size() * sizeof(uint32_t);
236 create_info.pCode = spirv_code.data();
237
238 try {
239 m_module = device.createShaderModule(create_info);
240 } catch (const vk::SystemError& e) {
242 "Failed to create shader module: {}", e.what());
243 return false;
244 }
245
246 m_stage = stage;
247 m_entry_point = entry_point;
248
249 if (m_preserve_spirv) {
250 m_spirv_code = spirv_code;
251 }
252
253 if (enable_reflection) {
254 if (!reflect_spirv(spirv_code)) {
256 "Shader reflection failed - descriptor layouts must be manually specified");
257 }
258 }
259
261 "Shader module created ({} stage, {} bytes SPIR-V, entry='{}')",
262 vk::to_string(stage), spirv_code.size() * 4, entry_point);
263
264 return true;
265}
266
268 vk::Device device,
269 const std::string& spirv_path,
270 vk::ShaderStageFlagBits stage,
271 const std::string& entry_point,
272 bool enable_reflection)
273{
274 std::string resolved_path = resolve_shader_path(spirv_path);
275
276 auto spirv_code = read_spirv_file(resolved_path);
277 if (spirv_code.empty()) {
279 "Failed to read SPIR-V file: '{}'", spirv_path);
280 return false;
281 }
282
284 "Loaded SPIR-V from file: '{}'", spirv_path);
285
286 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
287}
288
290 vk::Device device,
291 const std::string& spirv_asm,
292 vk::ShaderStageFlagBits stage,
293 const std::string& entry_point,
294 bool enable_reflection)
295{
296 spv_context ctx = spvContextCreate(SPV_ENV_VULKAN_1_3);
297 spv_binary binary = nullptr;
298 spv_diagnostic diag = nullptr;
299
300 const spv_result_t result = spvTextToBinary(
301 ctx,
302 spirv_asm.c_str(),
303 spirv_asm.size(),
304 &binary,
305 &diag);
306
307 if (result != SPV_SUCCESS) {
309 "SPIR-V assembly failed: {}",
310 (diag && diag->error) ? diag->error : "unknown error");
311 spvDiagnosticDestroy(diag);
312 spvContextDestroy(ctx);
313 return false;
314 }
315
316 std::vector<uint32_t> words(binary->code, binary->code + binary->wordCount);
317
318 spvBinaryDestroy(binary);
319 spvDiagnosticDestroy(diag);
320
321 spv_const_binary_t bin { .code = words.data(), .wordCount = words.size() };
322 spv_diagnostic val_diag = nullptr;
323 const spv_result_t val = spvValidate(ctx, &bin, &val_diag);
324 if (val != SPV_SUCCESS) {
326 "SPIR-V validation failed: {}",
327 (val_diag && val_diag->error) ? val_diag->error : "unknown");
328 spvDiagnosticDestroy(val_diag);
329 spvContextDestroy(ctx);
330 return false;
331 }
332 spvDiagnosticDestroy(val_diag);
333 spvContextDestroy(ctx);
334
336 "Assembled and validated SPIR-V ({} words)", words.size());
337
338 return create_from_spirv(device, words, stage, entry_point, enable_reflection);
339}
340
341// ============================================================================
342// Creation from GLSL
343// ============================================================================
344
346 vk::Device device,
347 const std::string& glsl_source,
348 vk::ShaderStageFlagBits stage,
349 const std::string& entry_point,
350 bool enable_reflection,
351 const std::vector<std::string>& include_directories,
352 const std::unordered_map<std::string, std::string>& defines)
353{
354 auto spirv_code = compile_glsl_to_spirv(glsl_source, stage, include_directories, defines);
355 if (spirv_code.empty()) {
357 "Failed to compile GLSL to SPIR-V ({} stage)", vk::to_string(stage));
358 return false;
359 }
360
362 "Compiled GLSL to SPIR-V ({} stage, {} bytes)",
363 vk::to_string(stage), spirv_code.size() * 4);
364
365 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
366}
367
369 vk::Device device,
370 const std::string& glsl_path,
371 std::optional<vk::ShaderStageFlagBits> stage,
372 const std::string& entry_point,
373 bool enable_reflection,
374 const std::vector<std::string>& include_directories,
375 const std::unordered_map<std::string, std::string>& defines)
376{
377 std::string resolved_path = resolve_shader_path(glsl_path);
378
379 if (!stage.has_value()) {
380 stage = detect_stage_from_extension(glsl_path);
381 if (!stage.has_value()) {
383 "Cannot auto-detect shader stage from file extension: '{}'", glsl_path);
384 return false;
385 }
387 "Auto-detected {} stage from file extension", vk::to_string(*stage));
388 }
389
390 auto glsl_source = read_text_file(resolved_path);
391 if (glsl_source.empty()) {
393 "Failed to read GLSL file: '{}'", glsl_path);
394 return false;
395 }
396
398 "Loaded GLSL from file: '{}' ({} bytes)", glsl_path, glsl_source.size());
399
400 return create_from_glsl(device, glsl_source, *stage, entry_point,
401 enable_reflection, include_directories, defines);
402}
403
404// ============================================================================
405// Pipeline Integration
406// ============================================================================
407
408vk::PipelineShaderStageCreateInfo VKShaderModule::get_stage_create_info() const
409{
410 if (!m_module) {
412 "Cannot get stage create info from invalid shader module");
413 return {};
414 }
415
416 vk::PipelineShaderStageCreateInfo stage_info;
417 stage_info.stage = m_stage;
418 stage_info.module = m_module;
419 stage_info.pName = m_entry_point.c_str();
420
421 if (!m_specialization_entries.empty()) {
422 const_cast<VKShaderModule*>(this)->update_specialization_info();
423 stage_info.pSpecializationInfo = &m_specialization_info;
424 }
425
426 return stage_info;
427}
428
429// ============================================================================
430// Specialization Constants
431// ============================================================================
432
434 const std::unordered_map<uint32_t, uint32_t>& constants)
435{
436 m_specialization_map = constants;
437
439 "Set {} specialization constants for {} stage",
440 constants.size(), vk::to_string(m_stage));
441}
442
444{
445 if (m_specialization_map.empty()) {
447 m_specialization_data.clear();
448 m_specialization_info = vk::SpecializationInfo {};
449 return;
450 }
451
453 m_specialization_data.clear();
454
457
458 uint32_t offset = 0;
459 for (const auto& [constant_id, value] : m_specialization_map) {
460 vk::SpecializationMapEntry entry;
461 entry.constantID = constant_id;
462 entry.offset = offset;
463 entry.size = sizeof(uint32_t);
464 m_specialization_entries.push_back(entry);
465
466 m_specialization_data.push_back(value);
467 offset += sizeof(uint32_t);
468 }
469
470 m_specialization_info.mapEntryCount = static_cast<uint32_t>(m_specialization_entries.size());
472 m_specialization_info.dataSize = m_specialization_data.size() * sizeof(uint32_t);
474}
475
476// ============================================================================
477// Reflection
478// ============================================================================
479
480bool VKShaderModule::reflect_spirv(const std::vector<uint32_t>& spirv_code)
481{
482 try {
483 spirv_cross::Compiler compiler(spirv_code);
484 spirv_cross::ShaderResources resources = compiler.get_shader_resources();
485
486 auto reflect_resources = [&](const spirv_cross::SmallVector<spirv_cross::Resource>& res_vec,
487 bool is_storage = false) {
488 for (const auto& resource : res_vec) {
490 desc.set = compiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
491 desc.binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
492 desc.stage = m_stage;
493 desc.name = resource.name;
494
495 const auto& type = compiler.get_type(resource.type_id);
496 desc.count = type.array.empty() ? 1 : type.array[0];
497 desc.type = spirv_to_vk_descriptor_type(type.basetype, type, is_storage);
498
499 m_reflection.bindings.push_back(desc);
500 }
501 };
502
503 reflect_resources(resources.uniform_buffers, false);
504
505 reflect_resources(resources.storage_buffers, true);
506
507 reflect_resources(resources.sampled_images, false);
508
509 reflect_resources(resources.storage_images, true);
510
511 reflect_resources(resources.separate_images, false);
512 reflect_resources(resources.separate_samplers, false);
513
514 if (!m_reflection.bindings.empty()) {
516 "Reflected {} descriptor bindings", m_reflection.bindings.size());
517 }
518
519 for (const auto& pc_buffer : resources.push_constant_buffers) {
520 const auto& type = compiler.get_type(pc_buffer.type_id);
521
523 range.stage = m_stage;
524 range.offset = 0;
525 range.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
526
527 m_reflection.push_constants.push_back(range);
528 }
529
530 if (!m_reflection.push_constants.empty()) {
532 "Reflected {} push constant blocks", m_reflection.push_constants.size());
533 }
534
535 auto spec_constants = compiler.get_specialization_constants();
536 for (const auto& spec : spec_constants) {
538 sc.constant_id = spec.constant_id;
539 sc.name = compiler.get_name(spec.id);
540
541 const auto& type = compiler.get_type(compiler.get_constant(spec.id).constant_type);
542 sc.size = static_cast<uint32_t>(compiler.get_declared_struct_size(type));
543
545 }
546
549 "Reflected {} specialization constants",
551 }
552
553 if (m_stage == vk::ShaderStageFlagBits::eCompute
554 || m_stage == vk::ShaderStageFlagBits::eMeshEXT
555 || m_stage == vk::ShaderStageFlagBits::eTaskEXT) {
556 auto entry_points = compiler.get_entry_points_and_stages();
557
558 for (const auto& ep : entry_points) {
559 if (ep.name == m_entry_point && ep.execution_model == spv::ExecutionModelGLCompute) {
560
561 std::array<uint32_t, 3> workgroup_size {
562 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 0),
563 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 1),
564 compiler.get_execution_mode_argument(spv::ExecutionModeLocalSize, 2)
565 };
566
567 if (!workgroup_size.empty() && workgroup_size.size() >= 3) {
568 m_reflection.workgroup_size = std::array<uint32_t, 3> {
569 workgroup_size[0],
570 workgroup_size[1],
571 workgroup_size[2]
572 };
573
575 "Compute shader workgroup size: [{}, {}, {}]",
576 workgroup_size[0], workgroup_size[1], workgroup_size[2]);
577 }
578 break;
579 }
580 }
581 }
582
583 if (m_stage == vk::ShaderStageFlagBits::eVertex) {
584 for (const auto& input : resources.stage_inputs) {
585 uint32_t location = compiler.get_decoration(input.id, spv::DecorationLocation);
586 const auto& type = compiler.get_type(input.type_id);
587
588 vk::VertexInputAttributeDescription attr;
589 attr.location = location;
590 attr.binding = 0;
591 attr.format = spirv_type_to_vk_format(type);
592 attr.offset = 0;
593
594 m_reflection.vertex_attributes.push_back(attr);
595 }
596
597 if (!m_reflection.vertex_attributes.empty()) {
599 "Reflected {} vertex input attributes",
601 }
602 }
603
604 return true;
605
606 } catch (const spirv_cross::CompilerError& e) {
608 "SPIRV-Cross reflection failed: {}", e.what());
609 return false;
610 }
611}
612
613vk::Format VKShaderModule::spirv_type_to_vk_format(const spirv_cross::SPIRType& type)
614{
615 using BaseType = spirv_cross::SPIRType::BaseType;
616
617 if (type.columns > 1) {
619 "Matrix types are not valid vertex attributes (columns={})",
620 type.columns);
621 return vk::Format::eUndefined;
622 }
623
624 if (type.width != 32) {
626 "Unsupported SPIR-V vertex attribute width {} (only 32-bit supported)",
627 type.width);
628 return vk::Format::eUndefined;
629 }
630
631 const uint32_t vec_size = type.vecsize;
632 if (type.basetype == BaseType::Float) {
633 switch (vec_size) {
634 case 1:
635 return vk::Format::eR32Sfloat;
636 case 2:
637 return vk::Format::eR32G32Sfloat;
638 case 3:
639 return vk::Format::eR32G32B32Sfloat;
640 case 4:
641 return vk::Format::eR32G32B32A32Sfloat;
642 }
643 } else if (type.basetype == BaseType::Int) {
644 switch (vec_size) {
645 case 1:
646 return vk::Format::eR32Sint;
647 case 2:
648 return vk::Format::eR32G32Sint;
649 case 3:
650 return vk::Format::eR32G32B32Sint;
651 case 4:
652 return vk::Format::eR32G32B32A32Sint;
653 }
654 } else if (type.basetype == BaseType::UInt) {
655 switch (vec_size) {
656 case 1:
657 return vk::Format::eR32Uint;
658 case 2:
659 return vk::Format::eR32G32Uint;
660 case 3:
661 return vk::Format::eR32G32B32Uint;
662 case 4:
663 return vk::Format::eR32G32B32A32Uint;
664 }
665 }
666
668 "Unsupported SPIR-V vertex attribute type (basetype={}, vecsize={})",
669 static_cast<int>(type.basetype), vec_size);
670
671 return vk::Format::eUndefined;
672}
673
674// ============================================================================
675// Utility Functions
676// ============================================================================
677
678std::optional<vk::ShaderStageFlagBits> VKShaderModule::detect_stage_from_extension(
679 const std::string& filepath)
680{
681 std::filesystem::path path(filepath);
682 std::string ext = path.extension().string();
683
684 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
685
686 static const std::unordered_map<std::string, vk::ShaderStageFlagBits> extension_map = {
687 { ".comp", vk::ShaderStageFlagBits::eCompute },
688 { ".vert", vk::ShaderStageFlagBits::eVertex },
689 { ".frag", vk::ShaderStageFlagBits::eFragment },
690 { ".geom", vk::ShaderStageFlagBits::eGeometry },
691 { ".tesc", vk::ShaderStageFlagBits::eTessellationControl },
692 { ".tese", vk::ShaderStageFlagBits::eTessellationEvaluation },
693 { ".rgen", vk::ShaderStageFlagBits::eRaygenKHR },
694 { ".rint", vk::ShaderStageFlagBits::eIntersectionKHR },
695 { ".rahit", vk::ShaderStageFlagBits::eAnyHitKHR },
696 { ".rchit", vk::ShaderStageFlagBits::eClosestHitKHR },
697 { ".rmiss", vk::ShaderStageFlagBits::eMissKHR },
698 { ".rcall", vk::ShaderStageFlagBits::eCallableKHR },
699 { ".mesh", vk::ShaderStageFlagBits::eMeshEXT },
700 { ".task", vk::ShaderStageFlagBits::eTaskEXT }
701 };
702
703 auto it = extension_map.find(ext);
704 if (it != extension_map.end()) {
705 return it->second;
706 }
707
708 return std::nullopt;
709}
710
712 const std::string& glsl_source,
713 vk::ShaderStageFlagBits stage,
714 const std::vector<std::string>& include_directories,
715 const std::unordered_map<std::string, std::string>& defines)
716{
717 shaderc::Compiler compiler;
718 shaderc::CompileOptions options;
719
720 options.SetTargetEnvironment(shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_3);
721 options.SetTargetSpirv(shaderc_spirv_version_1_6);
722 options.SetOptimizationLevel(shaderc_optimization_level_performance);
723 options.SetIncluder(std::make_unique<FileIncluder>(include_directories));
724
725 for (const auto& [name, value] : defines) {
726 options.AddMacroDefinition(name, value);
727 }
728
729 shaderc_shader_kind shader_kind;
730 switch (stage) {
731 case vk::ShaderStageFlagBits::eVertex:
732 shader_kind = shaderc_glsl_vertex_shader;
733 break;
734 case vk::ShaderStageFlagBits::eFragment:
735 shader_kind = shaderc_glsl_fragment_shader;
736 break;
737 case vk::ShaderStageFlagBits::eCompute:
738 shader_kind = shaderc_glsl_compute_shader;
739 break;
740 case vk::ShaderStageFlagBits::eGeometry:
741 shader_kind = shaderc_glsl_geometry_shader;
742 break;
743 case vk::ShaderStageFlagBits::eTessellationControl:
744 shader_kind = shaderc_glsl_tess_control_shader;
745 break;
746 case vk::ShaderStageFlagBits::eTessellationEvaluation:
747 shader_kind = shaderc_glsl_tess_evaluation_shader;
748 break;
749 case vk::ShaderStageFlagBits::eMeshEXT:
750 shader_kind = shaderc_glsl_mesh_shader;
751 break;
752 case vk::ShaderStageFlagBits::eTaskEXT:
753 shader_kind = shaderc_glsl_task_shader;
754 break;
755 default:
757 "Unsupported shader stage for GLSL compilation: {}", vk::to_string(stage));
758 return {};
759 }
760
761 shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
762 glsl_source,
763 shader_kind,
764 "shader.glsl",
765 options);
766
767 if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
769 "GLSL compilation failed:\n{}", result.GetErrorMessage());
770 return {};
771 }
772
773 std::vector<uint32_t> spirv(result.cbegin(), result.cend());
774
776 "Compiled GLSL ({} stage) -> {} bytes SPIR-V",
777 vk::to_string(stage), spirv.size() * 4);
778
779 return spirv;
780}
781
782std::vector<uint32_t> VKShaderModule::read_spirv_file(const std::string& filepath)
783{
784 std::ifstream file(filepath, std::ios::binary | std::ios::ate);
785 if (!file.is_open()) {
787 "Failed to open SPIR-V file: '{}'", filepath);
788 return {};
789 }
790
791 size_t file_size = static_cast<size_t>(file.tellg());
792 if (file_size == 0) {
794 "SPIR-V file is empty: '{}'", filepath);
795 return {};
796 }
797
798 if (file_size % sizeof(uint32_t) != 0) {
800 "SPIR-V file size ({} bytes) is not multiple of 4: '{}'",
801 file_size, filepath);
802 return {};
803 }
804
805 std::vector<uint32_t> buffer(file_size / sizeof(uint32_t));
806 file.seekg(0);
807 file.read(reinterpret_cast<char*>(buffer.data()), file_size);
808
809 if (!file) {
811 "Failed to read SPIR-V file: '{}'", filepath);
812 return {};
813 }
814
815 return buffer;
816}
817
818std::string VKShaderModule::read_text_file(const std::string& filepath)
819{
820 std::ifstream file(filepath);
821 if (!file.is_open()) {
823 "Failed to open file: '{}'", filepath);
824 return {};
825 }
826
827 std::string content(
828 (std::istreambuf_iterator<char>(file)),
829 std::istreambuf_iterator<char>());
830
831 if (content.empty()) {
833 "File is empty: '{}'", filepath);
834 }
835
836 return content;
837}
838
840{
841 switch (m_stage) {
842 case vk::ShaderStageFlagBits::eCompute:
843 return Stage::COMPUTE;
844 case vk::ShaderStageFlagBits::eVertex:
845 return Stage::VERTEX;
846 case vk::ShaderStageFlagBits::eFragment:
847 return Stage::FRAGMENT;
848 case vk::ShaderStageFlagBits::eGeometry:
849 return Stage::GEOMETRY;
850 case vk::ShaderStageFlagBits::eTessellationControl:
851 return Stage::TESS_CONTROL;
852 case vk::ShaderStageFlagBits::eTessellationEvaluation:
854 case vk::ShaderStageFlagBits::eMeshEXT:
855 return Stage::MESH;
856 case vk::ShaderStageFlagBits::eTaskEXT:
857 return Stage::TASK;
858 default:
860 "Unknown shader stage: {}", vk::to_string(m_stage));
861 return Stage::COMPUTE;
862 }
863}
864
865} // namespace MayaFlux::Core
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
Core::GlobalInputConfig input
Definition Config.cpp:38
const std::vector< std::string > & m_dirs
float value
float offset
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.
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.
bool create_from_spirv_asm(vk::Device device, const std::string &spirv_asm, vk::ShaderStageFlagBits stage, const std::string &entry_point="main", bool enable_reflection=true)
Create shader module from SPIR-V assembly text.
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.