MayaFlux 0.1.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#ifdef MAYAFLUX_USE_SPIRV_REFLECT
12#include <spirv_reflect.h>
13#endif
14
15namespace MayaFlux::Core {
16
17namespace {
18 std::vector<std::string> get_shader_search_paths()
19 {
20 return {
21 SHADER_BUILD_OUTPUT_DIR, // 1. Build directory (development)
22 SHADER_INSTALL_DIR, // 2. Install directory (production)
23 SHADER_SOURCE_DIR, // 3. Source directory (fallback)
24 "./shaders", // 4. Current working directory
25 "../shaders" // 5. Parent directory
26 };
27 }
28
29 std::string resolve_shader_path(const std::string& filename)
30 {
31 namespace fs = std::filesystem;
32
33 if (fs::path(filename).is_absolute() || fs::exists(filename)) {
34 return filename;
35 }
36
37 for (const auto& search_path : get_shader_search_paths()) {
38 fs::path full_path = fs::path(search_path) / filename;
39 if (fs::exists(full_path)) {
41 "Resolved shader '{}' -> '{}'", filename, full_path.string());
42 return full_path.string();
43 }
44 }
45
46 return filename;
47 }
48}
49
50// ============================================================================
51// Lifecycle
52// ============================================================================
53
55{
56 if (m_module) {
58 "VKShaderModule destroyed without cleanup() - potential leak");
59 }
60}
61
63 : m_module(other.m_module)
64 , m_stage(other.m_stage)
65 , m_entry_point(std::move(other.m_entry_point))
66 , m_reflection(std::move(other.m_reflection))
67 , m_spirv_code(std::move(other.m_spirv_code))
68 , m_preserve_spirv(other.m_preserve_spirv)
69 , m_specialization_map(std::move(other.m_specialization_map))
70 , m_specialization_entries(std::move(other.m_specialization_entries))
71 , m_specialization_data(std::move(other.m_specialization_data))
72 , m_specialization_info(other.m_specialization_info)
73{
74 other.m_module = nullptr;
75}
76
78{
79 if (this != &other) {
80 if (m_module) {
82 "VKShaderModule move-assigned without cleanup() - potential leak");
83 }
84
85 m_module = other.m_module;
86 m_stage = other.m_stage;
87 m_entry_point = std::move(other.m_entry_point);
88 m_reflection = std::move(other.m_reflection);
89 m_spirv_code = std::move(other.m_spirv_code);
90 m_preserve_spirv = other.m_preserve_spirv;
91 m_specialization_map = std::move(other.m_specialization_map);
92 m_specialization_entries = std::move(other.m_specialization_entries);
93 m_specialization_data = std::move(other.m_specialization_data);
94 m_specialization_info = other.m_specialization_info;
95
96 other.m_module = nullptr;
97 }
98 return *this;
99}
100
101void VKShaderModule::cleanup(vk::Device device)
102{
103 if (m_module) {
104 device.destroyShaderModule(m_module);
105 m_module = nullptr;
106
108 "Shader module cleaned up ({} stage)", vk::to_string(m_stage));
109 }
110
111 m_spirv_code.clear();
113 m_specialization_map.clear();
115 m_specialization_data.clear();
116}
117
118// ============================================================================
119// Creation from SPIR-V
120// ============================================================================
121
123 vk::Device device,
124 const std::vector<uint32_t>& spirv_code,
125 vk::ShaderStageFlagBits stage,
126 const std::string& entry_point,
127 bool enable_reflection)
128{
129 if (spirv_code.empty()) {
131 "Cannot create shader module from empty SPIR-V code");
132 return false;
133 }
134
135 // if (spirv_code.size() % 4 != 0) {
136 // MF_ERROR(Journal::Component::Core, Journal::Context::GraphicsBackend,
137 // "Invalid SPIR-V code size: {} bytes (must be multiple of 4)",
138 // spirv_code.size());
139 // return false;
140 // }
141
142 if (spirv_code[0] != 0x07230203) {
144 "Invalid SPIR-V magic number: 0x{:08X} (expected 0x07230203)",
145 spirv_code[0]);
146 return false;
147 }
148
149 vk::ShaderModuleCreateInfo create_info;
150 create_info.codeSize = spirv_code.size() * sizeof(uint32_t);
151 create_info.pCode = spirv_code.data();
152
153 try {
154 m_module = device.createShaderModule(create_info);
155 } catch (const vk::SystemError& e) {
157 "Failed to create shader module: {}", e.what());
158 return false;
159 }
160
161 m_stage = stage;
162 m_entry_point = entry_point;
163
164 if (m_preserve_spirv) {
165 m_spirv_code = spirv_code;
166 }
167
168 if (enable_reflection) {
169 if (!reflect_spirv(spirv_code)) {
171 "Shader reflection failed - descriptor layouts must be manually specified");
172 }
173 }
174
176 "Shader module created ({} stage, {} bytes SPIR-V, entry='{}')",
177 vk::to_string(stage), spirv_code.size() * 4, entry_point);
178
179 return true;
180}
181
183 vk::Device device,
184 const std::string& spirv_path,
185 vk::ShaderStageFlagBits stage,
186 const std::string& entry_point,
187 bool enable_reflection)
188{
189 std::string resolved_path = resolve_shader_path(spirv_path);
190
191 auto spirv_code = read_spirv_file(resolved_path);
192 if (spirv_code.empty()) {
194 "Failed to read SPIR-V file: '{}'", spirv_path);
195 return false;
196 }
197
199 "Loaded SPIR-V from file: '{}'", spirv_path);
200
201 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
202}
203
204// ============================================================================
205// Creation from GLSL
206// ============================================================================
207
209 vk::Device device,
210 const std::string& glsl_source,
211 vk::ShaderStageFlagBits stage,
212 const std::string& entry_point,
213 bool enable_reflection,
214 const std::vector<std::string>& include_directories,
215 const std::unordered_map<std::string, std::string>& defines)
216{
217 auto spirv_code = compile_glsl_to_spirv(glsl_source, stage, include_directories, defines);
218 if (spirv_code.empty()) {
220 "Failed to compile GLSL to SPIR-V ({} stage)", vk::to_string(stage));
221 return false;
222 }
223
225 "Compiled GLSL to SPIR-V ({} stage, {} bytes)",
226 vk::to_string(stage), spirv_code.size() * 4);
227
228 return create_from_spirv(device, spirv_code, stage, entry_point, enable_reflection);
229}
230
232 vk::Device device,
233 const std::string& glsl_path,
234 std::optional<vk::ShaderStageFlagBits> stage,
235 const std::string& entry_point,
236 bool enable_reflection,
237 const std::vector<std::string>& include_directories,
238 const std::unordered_map<std::string, std::string>& defines)
239{
240 std::string resolved_path = resolve_shader_path(glsl_path);
241
242 if (!stage.has_value()) {
243 stage = detect_stage_from_extension(glsl_path);
244 if (!stage.has_value()) {
246 "Cannot auto-detect shader stage from file extension: '{}'", glsl_path);
247 return false;
248 }
250 "Auto-detected {} stage from file extension", vk::to_string(*stage));
251 }
252
253 auto glsl_source = read_text_file(resolved_path);
254 if (glsl_source.empty()) {
256 "Failed to read GLSL file: '{}'", glsl_path);
257 return false;
258 }
259
261 "Loaded GLSL from file: '{}' ({} bytes)", glsl_path, glsl_source.size());
262
263 return create_from_glsl(device, glsl_source, *stage, entry_point,
264 enable_reflection, include_directories, defines);
265}
266
267// ============================================================================
268// Pipeline Integration
269// ============================================================================
270
271vk::PipelineShaderStageCreateInfo VKShaderModule::get_stage_create_info() const
272{
273 if (!m_module) {
275 "Cannot get stage create info from invalid shader module");
276 return {};
277 }
278
279 vk::PipelineShaderStageCreateInfo stage_info;
280 stage_info.stage = m_stage;
281 stage_info.module = m_module;
282 stage_info.pName = m_entry_point.c_str();
283
284 if (!m_specialization_entries.empty()) {
285 const_cast<VKShaderModule*>(this)->update_specialization_info();
286 stage_info.pSpecializationInfo = &m_specialization_info;
287 }
288
289 return stage_info;
290}
291
292// ============================================================================
293// Specialization Constants
294// ============================================================================
295
297 const std::unordered_map<uint32_t, uint32_t>& constants)
298{
299 m_specialization_map = constants;
300
302 "Set {} specialization constants for {} stage",
303 constants.size(), vk::to_string(m_stage));
304}
305
307{
308 if (m_specialization_map.empty()) {
310 m_specialization_data.clear();
311 m_specialization_info = vk::SpecializationInfo {};
312 return;
313 }
314
316 m_specialization_data.clear();
317
320
321 uint32_t offset = 0;
322 for (const auto& [constant_id, value] : m_specialization_map) {
323 vk::SpecializationMapEntry entry;
324 entry.constantID = constant_id;
325 entry.offset = offset;
326 entry.size = sizeof(uint32_t);
327 m_specialization_entries.push_back(entry);
328
329 m_specialization_data.push_back(value);
330 offset += sizeof(uint32_t);
331 }
332
333 m_specialization_info.mapEntryCount = static_cast<uint32_t>(m_specialization_entries.size());
335 m_specialization_info.dataSize = m_specialization_data.size() * sizeof(uint32_t);
337}
338
339// ============================================================================
340// Reflection
341// ============================================================================
342
343bool VKShaderModule::reflect_spirv(const std::vector<uint32_t>& spirv_code)
344{
345#ifdef MAYAFLUX_USE_SPIRV_REFLECT
346 SpvReflectShaderModule module;
347 SpvReflectResult result = spvReflectCreateShaderModule(
348 spirv_code.size() * sizeof(uint32_t),
349 spirv_code.data(),
350 &module);
351
352 if (result != SPV_REFLECT_RESULT_SUCCESS) {
354 "SPIRV-Reflect failed: {}", static_cast<int>(result));
355 return false;
356 }
357
358 uint32_t binding_count = 0;
359 result = spvReflectEnumerateDescriptorBindings(&module, &binding_count, nullptr);
360 if (result == SPV_REFLECT_RESULT_SUCCESS && binding_count > 0) {
361 std::vector<SpvReflectDescriptorBinding*> bindings(binding_count);
362 spvReflectEnumerateDescriptorBindings(&module, &binding_count, bindings.data());
363
364 for (const auto* binding : bindings) {
366 desc.set = binding->set;
367 desc.binding = binding->binding;
368 desc.type = static_cast<vk::DescriptorType>(binding->descriptor_type);
369 desc.stage = m_stage;
370 desc.count = binding->count;
371 desc.name = binding->name ? binding->name : "";
372
373 m_reflection.bindings.push_back(desc);
374 }
375
377 "Reflected {} descriptor bindings", binding_count);
378 }
379
380 uint32_t push_constant_count = 0;
381 result = spvReflectEnumeratePushConstantBlocks(&module, &push_constant_count, nullptr);
382 if (result == SPV_REFLECT_RESULT_SUCCESS && push_constant_count > 0) {
383 std::vector<SpvReflectBlockVariable*> push_constants(push_constant_count);
384 spvReflectEnumeratePushConstantBlocks(&module, &push_constant_count, push_constants.data());
385
386 for (const auto* block : push_constants) {
388 range.stage = m_stage;
389 range.offset = block->offset;
390 range.size = block->size;
391
392 m_reflection.push_constants.push_back(range);
393 }
394
396 "Reflected {} push constant blocks", push_constant_count);
397 }
398
399 if (m_stage == vk::ShaderStageFlagBits::eCompute) {
400 m_reflection.workgroup_size = std::array<uint32_t, 3> {
401 module.entry_points[0].local_size.x,
402 module.entry_points[0].local_size.y,
403 module.entry_points[0].local_size.z
404 };
405
407 "Compute shader workgroup size: [{}, {}, {}]",
411 }
412
413 spvReflectDestroyShaderModule(&module);
414 return true;
415
416#else
418 "SPIRV-Reflect not available - shader reflection disabled");
419
420 if (m_stage == vk::ShaderStageFlagBits::eCompute) {
421 // SPIR-V OpExecutionMode LocalSize pattern
422 // This is a simplified parser - production code should use SPIRV-Reflect
423 for (size_t i = 0; i < spirv_code.size() - 4; ++i) {
424 if ((spirv_code[i] & 0xFFFF) == 17) {
425 uint32_t mode = spirv_code[i + 2];
426 if (mode == 17) {
427 m_reflection.workgroup_size = std::array<uint32_t, 3> {
428 spirv_code[i + 3],
429 spirv_code[i + 4],
430 spirv_code[i + 5]
431 };
432
434 "Extracted compute workgroup size: [{}, {}, {}]",
438 break;
439 }
440 }
441 }
442 }
443
444 return false; // Reflection incomplete without SPIRV-Reflect
445#endif
446}
447
448// ============================================================================
449// Utility Functions
450// ============================================================================
451
452std::optional<vk::ShaderStageFlagBits> VKShaderModule::detect_stage_from_extension(
453 const std::string& filepath)
454{
455 std::filesystem::path path(filepath);
456 std::string ext = path.extension().string();
457
458 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
459
460 static const std::unordered_map<std::string, vk::ShaderStageFlagBits> extension_map = {
461 { ".comp", vk::ShaderStageFlagBits::eCompute },
462 { ".vert", vk::ShaderStageFlagBits::eVertex },
463 { ".frag", vk::ShaderStageFlagBits::eFragment },
464 { ".geom", vk::ShaderStageFlagBits::eGeometry },
465 { ".tesc", vk::ShaderStageFlagBits::eTessellationControl },
466 { ".tese", vk::ShaderStageFlagBits::eTessellationEvaluation },
467 { ".rgen", vk::ShaderStageFlagBits::eRaygenKHR },
468 { ".rint", vk::ShaderStageFlagBits::eIntersectionKHR },
469 { ".rahit", vk::ShaderStageFlagBits::eAnyHitKHR },
470 { ".rchit", vk::ShaderStageFlagBits::eClosestHitKHR },
471 { ".rmiss", vk::ShaderStageFlagBits::eMissKHR },
472 { ".rcall", vk::ShaderStageFlagBits::eCallableKHR },
473 { ".mesh", vk::ShaderStageFlagBits::eMeshEXT },
474 { ".task", vk::ShaderStageFlagBits::eTaskEXT }
475 };
476
477 auto it = extension_map.find(ext);
478 if (it != extension_map.end()) {
479 return it->second;
480 }
481
482 return std::nullopt;
483}
484
486 const std::string& glsl_source,
487 vk::ShaderStageFlagBits stage,
488 const std::vector<std::string>& include_directories,
489 const std::unordered_map<std::string, std::string>& defines)
490{
491#ifdef MAYAFLUX_USE_SHADERC
492 shaderc::Compiler compiler;
493 shaderc::CompileOptions options;
494
495 options.SetOptimizationLevel(shaderc_optimization_level_performance);
496
497 for (const auto& dir : include_directories) {
498 // options.AddIncludeDirectory(dir);
499 }
500
501 for (const auto& [name, value] : defines) {
502 options.AddMacroDefinition(name, value);
503 }
504
505 shaderc_shader_kind shader_kind;
506 switch (stage) {
507 case vk::ShaderStageFlagBits::eVertex:
508 shader_kind = shaderc_glsl_vertex_shader;
509 break;
510 case vk::ShaderStageFlagBits::eFragment:
511 shader_kind = shaderc_glsl_fragment_shader;
512 break;
513 case vk::ShaderStageFlagBits::eCompute:
514 shader_kind = shaderc_glsl_compute_shader;
515 break;
516 case vk::ShaderStageFlagBits::eGeometry:
517 shader_kind = shaderc_glsl_geometry_shader;
518 break;
519 case vk::ShaderStageFlagBits::eTessellationControl:
520 shader_kind = shaderc_glsl_tess_control_shader;
521 break;
522 case vk::ShaderStageFlagBits::eTessellationEvaluation:
523 shader_kind = shaderc_glsl_tess_evaluation_shader;
524 break;
525 default:
527 "Unsupported shader stage for GLSL compilation: {}", vk::to_string(stage));
528 return {};
529 }
530
531 shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
532 glsl_source,
533 shader_kind,
534 "shader.glsl",
535 options);
536
537 if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
539 "GLSL compilation failed:\n{}", result.GetErrorMessage());
540 return {};
541 }
542
543 std::vector<uint32_t> spirv(result.cbegin(), result.cend());
544
546 "Compiled GLSL ({} stage) -> {} bytes SPIR-V",
547 vk::to_string(stage), spirv.size() * 4);
548
549 return spirv;
550
551#else
553 "GLSL compilation requested but shaderc not available - use pre-compiled SPIR-V");
554 return {};
555#endif
556}
557
558std::vector<uint32_t> VKShaderModule::read_spirv_file(const std::string& filepath)
559{
560 std::ifstream file(filepath, std::ios::binary | std::ios::ate);
561 if (!file.is_open()) {
563 "Failed to open SPIR-V file: '{}'", filepath);
564 return {};
565 }
566
567 size_t file_size = static_cast<size_t>(file.tellg());
568 if (file_size == 0) {
570 "SPIR-V file is empty: '{}'", filepath);
571 return {};
572 }
573
574 if (file_size % sizeof(uint32_t) != 0) {
576 "SPIR-V file size ({} bytes) is not multiple of 4: '{}'",
577 file_size, filepath);
578 return {};
579 }
580
581 std::vector<uint32_t> buffer(file_size / sizeof(uint32_t));
582 file.seekg(0);
583 file.read(reinterpret_cast<char*>(buffer.data()), file_size);
584
585 if (!file) {
587 "Failed to read SPIR-V file: '{}'", filepath);
588 return {};
589 }
590
591 return buffer;
592}
593
594std::string VKShaderModule::read_text_file(const std::string& filepath)
595{
596 std::ifstream file(filepath);
597 if (!file.is_open()) {
599 "Failed to open file: '{}'", filepath);
600 return {};
601 }
602
603 std::string content(
604 (std::istreambuf_iterator<char>(file)),
605 std::istreambuf_iterator<char>());
606
607 if (content.empty()) {
609 "File is empty: '{}'", filepath);
610 }
611
612 return content;
613}
614
616{
617 switch (m_stage) {
618 case vk::ShaderStageFlagBits::eCompute:
619 return Stage::COMPUTE;
620 case vk::ShaderStageFlagBits::eVertex:
621 return Stage::VERTEX;
622 case vk::ShaderStageFlagBits::eFragment:
623 return Stage::FRAGMENT;
624 case vk::ShaderStageFlagBits::eGeometry:
625 return Stage::GEOMETRY;
626 case vk::ShaderStageFlagBits::eTessellationControl:
627 return Stage::TESS_CONTROL;
628 case vk::ShaderStageFlagBits::eTessellationEvaluation:
630 default:
632 "Unknown shader stage: {}", vk::to_string(m_stage));
633 return Stage::COMPUTE;
634 }
635}
636
637} // 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.
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.
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< DescriptorBinding > bindings
std::vector< PushConstantRange > push_constants
std::optional< std::array< uint32_t, 3 > > workgroup_size
local_size_x/y/z
Metadata extracted from shader module.