如何通过SPIRV-Cross获取Vulkan完整顶点布局信息?
解决SPIRV-Cross反射顶点布局丢失未使用属性的问题
核心问题原因
SPIR-V编译器(如glslang)默认会剔除着色器中未被使用的顶点输入,导致SPIRV-Cross只能获取到被实际使用的属性,无法拿到完整的顶点布局信息。同时,GLSL默认不会将顶点属性的偏移量编译到SPIR-V中,除非显式指定offset装饰,所以直接调用get_decoration获取偏移会返回0。
解决方案步骤
1. 编译SPIR-V时保留所有顶点输入
在将GLSL编译为SPIR-V的阶段,必须禁用“未使用输入剔除”的优化,确保所有顶点属性都被保留在SPIR-V字节码中:
- 若使用glslangValidator命令行工具,添加参数:
-fpreserve-all-glsl-inputs - 若使用glslang代码API,在创建
SpvBuildOptions时设置:SpvBuildOptions options; options.preserveAllInputs = true;
2. 通过SPIRV-Cross获取完整的顶点属性列表
当SPIR-V保留了所有输入后,调用compiler.get_shader_resources().stage_inputs就能拿到所有定义的顶点属性。注意返回的列表顺序不一定按location排序,需要手动按location值从小到大排序:
spirv_cross::Compiler compiler(src); spirv_cross::ShaderResources resources = compiler.get_shader_resources(); // 提取所有顶点输入并按location排序 std::vector<spirv_cross::Resource> sorted_inputs = resources.stage_inputs; std::sort(sorted_inputs.begin(), sorted_inputs.end(), [&compiler](const auto& a, const auto& b) { uint32_t loc_a = compiler.get_decoration(a.id, spv::DecorationLocation); uint32_t loc_b = compiler.get_decoration(b.id, spv::DecorationLocation); return loc_a < loc_b; });
3. 计算顶点属性的偏移量与总步长
由于GLSL默认不会将偏移写入SPIR-V,需要根据属性类型对应的Vulkan格式,手动计算偏移和步长(需遵循Vulkan顶点输入的对齐规则):
- 先定义类型到Vulkan格式的映射(示例):
VkFormat get_vulkan_format(const spirv_cross::Type& type) { if (type.basetype == spirv_cross::Type::Float) { switch (type.vecsize) { case 3: return VK_FORMAT_R32G32B32_SFLOAT; case 2: return VK_FORMAT_R32G32_SFLOAT; case 4: return VK_FORMAT_R32G32B32A32_SFLOAT; } } else if (type.basetype == spirv_cross::Type::Int) { if (type.vecsize == 4) return VK_FORMAT_R32G32B32A32_SINT; } // 根据实际需求补充更多类型映射 return VK_FORMAT_UNDEFINED; } - 计算每个属性的大小和对齐值,再依次计算偏移:
注:uint32_t current_offset = 0; uint32_t vertex_stride = 0; std::vector<VkVertexInputAttributeDescription> attrs; for (const auto& resource : sorted_inputs) { uint32_t location = compiler.get_decoration(resource.id, spv::DecorationLocation); auto type = compiler.get_type(resource.type_id); VkFormat format = get_vulkan_format(type); // 获取格式对应的大小和对齐值 VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(physical_device, format, &props); uint32_t format_size = get_format_size(format); // 需自行实现,比如根据格式计算字节数 uint32_t align = props.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT ? props.minTexelBufferAlignment : 4; // 顶点缓冲区的最小对齐通常为4字节 // 对齐当前偏移 current_offset = (current_offset + align - 1) & ~(align - 1); // 添加属性描述 VkVertexInputAttributeDescription attr{}; attr.location = location; attr.binding = 0; // 假设使用单个绑定 attr.format = format; attr.offset = current_offset; attrs.push_back(attr); // 更新偏移和总步长 current_offset += format_size; vertex_stride = current_offset; }get_format_size函数需要根据Vulkan格式手动实现,比如VK_FORMAT_R32G32B32_SFLOAT对应12字节,VK_FORMAT_R32G32_SFLOAT对应8字节等。
可选方案:显式指定顶点属性偏移
如果不想依赖自动计算,可以在GLSL中为每个顶点属性添加offset装饰,强制指定偏移量:
layout(location = 0, offset = 0) in vec3 a_Position; layout(location = 1, offset = 12) in vec2 a_TexCoords; layout(location = 2, offset = 20) in vec3 a_Normal; // ... 其余属性依次指定偏移
这种情况下,编译后的SPIR-V会包含DecorationOffset信息,直接通过compiler.get_decoration(resource.id, spv::DecorationOffset)即可获取偏移,总步长为最后一个属性的偏移加上其类型大小。但此方法需要手动维护偏移,适合固定布局的场景。
内容的提问来源于stack exchange,提问作者Mr_Ins1der
相关产品推荐
相关产品推荐

