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