chromium_spirv-cross:基于 SPIR-V 的着色器语言转换工具项目

可用于将 SPIR-V 转换为 GLSL、MSL、HLSL 等多种着色器语言及 JSON 反射格式,提供反射 API 简化 Vulkan 管线布局创建,支持多种着色器类型,输出代码可读性强。【此简介由AI生成】

分支38Tags54
文件最后提交记录最后更新时间
1 年前
3 年前
3 年前
5 年前
5 年前
5 年前
3 年前
9 个月前
5 年前
10 个月前
9 个月前
10 个月前
10 个月前
10 个月前
8 年前
4 年前
3 年前
3 年前
9 个月前
3 年前
5 年前
1 年前
11 个月前
5 年前
5 年前
7 年前
11 个月前
4 年前
1 年前
5 年前
2 年前
10 个月前
5 年前
11 个月前
10 个月前
10 个月前
1 年前
1 年前
11 个月前
11 个月前
5 年前
10 个月前
10 个月前
2 年前
11 个月前
1 年前
2 年前
2 年前
11 个月前
11 个月前
11 个月前
5 年前
9 个月前
11 个月前
9 个月前
11 个月前
10 个月前
10 个月前
11 个月前
3 年前
11 个月前
11 个月前
9 个月前
3 年前
5 年前

SPIRV-Cross

SPIRV-Cross 是一款用于解析 SPIR-V 并将其转换为其他着色器语言的工具。

CI Build Status

功能特性

  • 将 SPIR-V 转换为可读、可用且高效的 GLSL
    • 将 SPIR-V 转换为可读、可用且高效的 Metal 着色语言(MSL)
    • 将 SPIR-V 转换为可读、可用且高效的 HLSL
    • 将 SPIR-V 转换为 JSON 反射格式
    • 将 SPIR-V 转换为可调试的 C++ [已弃用]
    • 用于简化 Vulkan 管线布局创建的反射 API
    • 用于修改和调整 OpDecorations 的反射 API
    • 支持“所有”顶点、片段、 tessellation、几何和计算着色器。

SPIRV-Cross 致力于从 SPIR-V 生成可读且整洁的输出。其目标是生成看起来如同人工编写的 GLSL 或 MSL,而非生硬的 IR/汇编风格代码。

注意:各项功能虽已基本完善,但某些不常用的 GLSL 特性可能尚未支持。不过,在当前阶段,大多数缺失的特性预计只需“简单”改进即可实现。

构建

SPIRV-Cross 已在 Linux、iOS/OSX、Windows 和 Android 系统上经过测试。CMake 是主要的构建系统。

注意:main 分支重命名

根据 Khronos 政策,2023 年 1 月 12 日,master 分支已重命名为 main

Linux 和 macOS

推荐使用 CMake 进行构建,因为它是唯一在持续集成中经过测试的构建系统。它也是唯一提供安装命令和其他有用构建系统功能的构建系统。

不过,如果你只关心命令行工具,也可以在命令行运行 make 作为备用方案。

由于 SPIRV-Cross 大量使用 C++11,因此需要较新版本的 GCC(4.8 及以上)或 Clang(3.x 及以上)编译器。

Windows

建议使用 CMake 进行构建,这是面向 MSVC 的唯一方式。 基于 MinGW-w64 的编译可使用 make 作为备选方案。

Android

SPIRV-Cross 在此处仅作为库有用。使用 CMake 构建将 SPIRV-Cross 链接到您的项目。

C++ 异常

make 和 CMake 构建方式提供了将异常视为断言的选项。对于 make,只需在命令行追加 SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS=1 即可禁用异常。对于 CMake,追加 -DSPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS=ON。默认情况下启用异常。

静态库、共享库和命令行界面

您可以使用 -DSPIRV_CROSS_STATIC=ON/OFF -DSPIRV_CROSS_SHARED=ON/OFF -DSPIRV_CROSS_CLI=ON/OFF 来控制构建(和安装)哪些模块。

安装 SPIRV-Cross(vcpkg)

或者,您可以使用 vcpkg 依赖管理器构建并安装 SPIRV-Cross:

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install spirv-cross

vcpkg 中的 SPIRV-Cross 移植版本由 Microsoft 团队成员和社区贡献者负责更新。如果版本已过时,请在 vcpkg 代码库上创建 issue 或拉取请求

用法

使用 C++ API

C++ API 是 SPIRV-Cross 的主要 API。如需比本 README 更深入的文档,请查看维基百科注意:不保证此 API 的 ABI 稳定性,强烈建议静态链接此 API。该 API 通常相当稳定,但可能会随时间变化,有关更高稳定性的信息,请参阅 C API。

要执行反射并转换为其他着色器语言,您可以使用 SPIRV-Cross API。 例如:

#include "spirv_glsl.hpp"
#include <vector>
#include <utility>

extern std::vector<uint32_t> load_spirv_file();

int main()
{
	// Read SPIR-V from disk or similar.
	std::vector<uint32_t> spirv_binary = load_spirv_file();

	spirv_cross::CompilerGLSL glsl(std::move(spirv_binary));

	// The SPIR-V is now parsed, and we can perform reflection on it.
	spirv_cross::ShaderResources resources = glsl.get_shader_resources();

	// Get all sampled images in the shader.
	for (auto &resource : resources.sampled_images)
	{
		unsigned set = glsl.get_decoration(resource.id, spv::DecorationDescriptorSet);
		unsigned binding = glsl.get_decoration(resource.id, spv::DecorationBinding);
		printf("Image %s at set = %u, binding = %u\n", resource.name.c_str(), set, binding);

		// Modify the decoration to prepare it for GLSL.
		glsl.unset_decoration(resource.id, spv::DecorationDescriptorSet);

		// Some arbitrary remapping if we want.
		glsl.set_decoration(resource.id, spv::DecorationBinding, set * 16 + binding);
	}

	// Set some options.
	spirv_cross::CompilerGLSL::Options options;
	options.version = 310;
	options.es = true;
	glsl.set_common_options(options);

	// Compile to GLSL, ready to give to GL driver.
	std::string source = glsl.compile();
}

使用 C API 包装器

为了便于实现 C 语言兼容性以及与其他编程语言的兼容性,提供了一个 C89 兼容的 API 包装器。与 C++ API 不同,此包装器的目标是实现 API 和 ABI 的完全稳定性。这是将 SPIRV-Cross 构建为共享库时唯一受支持的接口。

该包装器的一个重要特点是,所有内存分配都包含在 spvc_context 中。这极大地简化了 API 的使用。但是,您应尽快销毁上下文,或者如果打算很快再次重用 spvc_context 对象,请使用 spvc_context_release_allocations()

大多数函数返回 spvc_result,其中 SPVC_SUCCESS 是唯一的成功代码。为简洁起见,下面的代码未执行任何错误检查。

#include <spirv_cross_c.h>

const SpvId *spirv = get_spirv_data();
size_t word_count = get_spirv_word_count();

spvc_context context = NULL;
spvc_parsed_ir ir = NULL;
spvc_compiler compiler_glsl = NULL;
spvc_compiler_options options = NULL;
spvc_resources resources = NULL;
const spvc_reflected_resource *list = NULL;
const char *result = NULL;
size_t count;
size_t i;

// Create context.
spvc_context_create(&context);

// Set debug callback.
spvc_context_set_error_callback(context, error_callback, userdata);

// Parse the SPIR-V.
spvc_context_parse_spirv(context, spirv, word_count, &ir);

// Hand it off to a compiler instance and give it ownership of the IR.
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler_glsl);

// Do some basic reflection.
spvc_compiler_create_shader_resources(compiler_glsl, &resources);
spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);

for (i = 0; i < count; i++)
{
    printf("ID: %u, BaseTypeID: %u, TypeID: %u, Name: %s\n", list[i].id, list[i].base_type_id, list[i].type_id,
           list[i].name);
    printf("  Set: %u, Binding: %u\n",
           spvc_compiler_get_decoration(compiler_glsl, list[i].id, SpvDecorationDescriptorSet),
           spvc_compiler_get_decoration(compiler_glsl, list[i].id, SpvDecorationBinding));
}

// Modify options.
spvc_compiler_create_compiler_options(compiler_glsl, &options);
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 330);
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE);
spvc_compiler_install_compiler_options(compiler_glsl, options);

spvc_compiler_compile(compiler_glsl, &result);
printf("Cross-compiled source: %s\n", result);

// Frees all memory we allocated so far.
spvc_context_destroy(context);

链接

CMake add_subdirectory()

如果您正在使用 CMake 并希望静态链接 SPIRV-Cross,这是推荐的方法。

在自定义构建系统中集成 SPIRV-Cross

要将 SPIRV-Cross 添加到您自己的代码库,只需从根目录复制源文件和头文件,并构建您需要的相关 .cpp 文件。确保使用 C++11 支持进行构建,例如在 GCC 和 Clang 中使用 -std=c++11。或者,Makefile 在构建过程中会生成一个 libspirv-cross.a 静态库,可供链接使用。

将 SPIRV-Cross 作为系统库链接

当 SPIRV-Cross 作为系统库安装时,可以对其进行链接,这在类 Unix 平台上通常较为相关。

pkg-config

对于基于 Unix 的系统,C API 会安装一个 pkg-config,例如:

$ pkg-config spirv-cross-c-shared --libs --cflags
-I/usr/local/include/spirv_cross -L/usr/local/lib -lspirv-cross-c-shared
CMake

如果项目已安装,可以使用 find_package() 来找到它,例如:

cmake_minimum_required(VERSION 3.5)
set(CMAKE_C_STANDARD 99)
project(Test LANGUAGES C)

find_package(spirv_cross_c_shared)
if (spirv_cross_c_shared_FOUND)
        message(STATUS "Found SPIRV-Cross C API! :)")
else()
        message(STATUS "Could not find SPIRV-Cross C API! :(")
endif()

add_executable(test test.c)
target_link_libraries(test spirv-cross-c-shared)

test.c:

#include <spirv_cross_c.h>

int main(void)
{
        spvc_context context;
        spvc_context_create(&context);
        spvc_context_destroy(context);
}

命令行界面

命令行界面适用于基本的交叉编译任务,但无法支持 API 所能提供的全部灵活性。 以下是一些示例。

使用 glslang 从 GLSL 创建 SPIR-V 文件

glslangValidator -H -V -o test.spv test.frag

注意:本文档默认使用 Vulkan GLSL 作为输入。通常情况下,只有现代 GLSL(桌面端需 #version 330 及以上版本)才能编译为 SPIR-V。

在交叉编译 GLSL 时,通常的做法是从现代 GLSL 出发,交叉编译到旧版目标以确保兼容性,而非反向操作。

将 SPIR-V 文件转换为 GLSL ES

glslangValidator -H -V -o test.spv shaders/comp/basic.comp
./spirv-cross --version 310 --es test.spv

转换为桌面端 GLSL

glslangValidator -H -V -o test.spv shaders/comp/basic.comp
./spirv-cross --version 330 --no-es test.spv --output test.comp

禁用美化优化

glslangValidator -H -V -o test.spv shaders/comp/basic.comp
./spirv-cross --version 310 --es test.spv --output test.comp --force-temporary

使用从 C++ 后端生成的着色器

请参见 samples/cpp,其中一些 GLSL 着色器被编译为 SPIR-V,反编译为 C++ 并使用测试数据运行。 阅读这些示例应能说明如何使用 C++ 接口。 目录中包含一个简单的 Makefile,用于构建该目录中的所有着色器。

实现说明

当使用 SPIR-V 和 SPIRV-Cross 作为高级语言之间交叉编译的中间步骤时,需要考虑一些事项, 因为一种高级语言所使用的所有功能不一定都能被目标着色器语言原生支持。 SPIRV-Cross 旨在提供所需的工具,以干净且稳健的方式处理这些场景,但需要一些手动操作来保持兼容性。

HLSL 源到 GLSL

HLSL 入口点

当使用从 HLSL 编译的 SPIR-V 着色器时,有一些额外的事情需要注意。 首先确保入口点使用正确。 如果忘记在 glslangValidator 中正确设置入口点(-e MyFancyEntryPoint), 你很可能会遇到以下错误消息:

Cannot end a function before ending the current block.
Likely cause: If this SPIR-V was created from glslang HLSL, make sure the entry point is valid.
顶点/片段接口链接

HLSL 依赖语义来有效链接各个着色器阶段。在 glslang 生成的 SPIR-V 中,从 HLSL 到 GLSL 的转换最终呈现为

struct VSOutput {
   // SV_Position is rerouted to gl_Position
   float4 position : SV_Position;
   float4 coord : TEXCOORD0;
};

VSOutput main(...) {}
struct VSOutput {
   float4 coord;
}
layout(location = 0) out VSOutput _magicNameGeneratedByGlslang;

虽然这种方法可行,但要注意顶点着色器阶段和片段着色器阶段中使用的结构体类型。

如果顶点着色器阶段和片段着色器阶段中的结构体类型名称不同,可能会出现问题。

您可以利用反射接口来强制指定结构体类型的名称。

// Something like this for both vertex outputs and fragment inputs.
compiler.set_name(varying_resource.base_type_id, "VertexFragmentLinkage");

某些平台可能要求顶点输出和片段输入使用完全相同的变量名称(例如 MacOSX)。 若要基于位置重命名变量,请添加

--rename-interface-variable <in|out> <location> <new_variable_name>

HLSL 源代码转传统 GLSL/ESSL

HLSL 通常会生成 varying 结构体类型,用于在顶点着色器和片段着色器之间传递数据。 传统 GL/GLES 目标不支持这种方式,因此为了实现支持,需要将 varying 结构体进行展平处理。 此过程会自动完成,但 API 用户可能需要了解这一处理过程以支持所有场景。

像这样的现代 GLES 代码:

struct Output {
   vec4 a;
   vec2 b;
};
out Output vout;

转换为:

struct Output {
   vec4 a;
   vec2 b;
};
varying vec4 Output_a;
varying vec2 Output_b;

请注意,现在结构体名称和成员名称都将参与顶点着色器与片段着色器之间的链接接口,因此 API 用户可能需要确保结构体名称和成员名称均匹配,以便顶点输出和片段输入能够正确链接。

为不支持分离图像采样器(HLSL/Vulkan)的后端(GLSL)提供支持

另一件需要记住的事情是,在 HLSL 中使用采样器和纹理时,它们是分离的,与 GLSL 不直接兼容。如果需要在桌面 GL/GLES 中使用此功能,必须在调用 Compiler::compile 之前先调用 Compiler::build_combined_image_samplers,否则将引发异常。

// From main.cpp
// Builds a mapping for all combinations of images and samplers.
compiler->build_combined_image_samplers();

// Give the remapped combined samplers new names.
// Here you can also set up decorations if you want (binding = #N).
for (auto &remap : compiler->get_combined_image_samplers())
{
   compiler->set_name(remap.combined_id, join("SPIRV_Cross_Combined", compiler->get_name(remap.image_id),
            compiler->get_name(remap.sampler_id)));
}

如果您的目标是 Vulkan GLSL,--vulkan-semantics 会按预期生成单独的图像采样器。 命令行客户端会自动调用 Compiler::build_combined_image_samplers,但如果您直接调用库,则需要自己执行此操作。

针对不支持描述符集的后端(HLSL 5.1 之前版本/GLSL)的描述符集(Vulkan GLSL)

描述符集是 Vulkan 特有的,因此请确保将描述符集 + 绑定重新映射为平面绑定方案(集始终为 0),以便其他 API 能够理解这些绑定。 这可以通过 Compiler::set_decoration(id, spv::DecorationDescriptorSet) 来实现。对于 MSL 和 HLSL 等其他后端,描述符集 可以使用,但有一些小的注意事项,详见下文。

MSL 2.0+

Metal 支持间接参数缓冲区(--msl-argument-buffers)。在这种情况下,描述符集成为参数缓冲区, 绑定则映射到参数缓冲区内的 [[id(N)]]。一个特殊之处是,资源数组会占用多个 id,而 Vulkan 不会。这可以在着色器编写阶段解决, 或者根据需要重新映射绑定以避免重叠。 还有一个丰富的 API 用于声明重新映射方案,其目的是像 Vulkan 中的管线布局一样工作。请参见 CompilerMSL::add_msl_resource_binding。例如,在 MSL 中,必须将组合图像采样器的重新映射拆分为两个绑定,因此可以分别为纹理和采样器绑定声明 id。

HLSL - SM 5.1+

在 SM 5.1+ 中,描述符集绑定直接被解释为寄存器空间。然而,在 HLSL 中,资源数组会占用多个绑定槽位,而 Vulkan 不会,因此如果 SPIR-V 编写时未考虑到这一点,可能会出现重叠。这可以在着色器编写阶段解决(不要分配重叠的绑定), 或者在 SPIRV-Cross 中根据需要重新映射绑定以避免重叠。

针对不支持显式位置的目标(旧版 GLSL/ESSL)按名称链接

现代 GLSL 和 HLSL 源文件(以及 SPIR-V)依靠显式的 layout(location) 限定符来指导着色器阶段之间的链接过程, 但较旧的 GLSL 依靠符号名称来执行链接。当生成较旧版本的着色器时,这些 layout 语句将被移除, 因此 API 用户必须确保 I/O 变量的名称经过清理,以便链接能够正常工作。 反射 API 可以使用 Compiler::set_name 及其相关函数来重命名变量、结构体类型和结构体成员,以应对这些情况。

裁剪空间约定

通过启用 CompilerGLSL::Options.vertex.fixup_clipspace,SPIRV-Cross 可以对 gl_Position/SV_Position 执行一些常见的裁剪空间转换。 虽然这很方便,但建议改为修改投影矩阵,因为这也能达到相同的结果。

对于 GLSL 目标,启用此选项会将假设深度范围为 [0, w](Vulkan / D3D / Metal)的着色器转换为 [-w, w] 范围。 对于 MSL 和 HLSL 目标,启用此选项会将深度范围为 [-w, w](OpenGL)的着色器转换为 [0, w] 范围。

默认情况下,CLI 不会启用 fixup_clipspace,但在 API 中,您可能需要使用 CompilerGLSL::set_options() 设置一个显式值。

还支持对 gl_Position 及类似变量进行 Y 轴翻转。 不建议使用此功能,因为依赖顶点着色器进行 Y 轴翻转往往会变得相当混乱。 要启用此功能,请设置 CompilerGLSL::Options.vertex.flip_vert_y 或在 CLI 中使用 --flip-vert-y

保留标识符

在交叉编译时,某些标识符被视为实现保留的。 SPIRV-Cross 生成的代码不能 emit 这些标识符,因为它们是保留的,用于各种内部目的, 此类变量通常会显示为 _RESERVED_IDENTIFIER_FIXUP_ 或类似名称,以更明显地表示某个标识符已被重命名。

反射输出将遵循 SPIR-V 模块中指定的确切名称。它在 C 语言意义上可能不是有效的标识符, 因为它可能包含非字母数字/非下划线字符。

实现当前假设的保留标识符如下(伪正则表达式):

  • _$digit+,例如 _100_2
  • $digit+.+,例如 _100_tmp_2_foobar_2Bar 不是 保留的。
  • gl_ 前缀
  • spv- 前缀
  • SPIRV_Cross 前缀。此前缀通常用于应用程序需要为解决方法提供数据的接口变量。 此标识符不会被重写,但请注意潜在的冲突。
  • 双下划线(所有目标语言都保留)。

结构体的成员也有一个保留标识符:

  • _mdigit+digit+END,例如 _m20_m40 是保留的,但 _m40Foobar 不是。

贡献指南

欢迎为 SPIRV-Cross 项目贡献代码。详情请参见测试和许可部分。

测试

SPIRV-Cross 维护了一个着色器测试套件,其中包含参考输出,用于展示着色器经过 glslangValidator/spirv-as 处理后,再通过 SPIRV-Cross 反向转换后的输出结果。 参考文件存储在代码仓库中,以便跟踪回归问题。

所有拉取请求均应确保测试输出不会发生意外变化。可通过以下方式进行测试:

./checkout_glslang_spirv_tools.sh # Checks out glslang and SPIRV-Tools at a fixed revision which matches the reference output.
./build_glslang_spirv_tools.sh    # Builds glslang and SPIRV-Tools.
./test_shaders.sh                 # Runs over all changes and makes sure that there are no deltas compared to reference files.

./test_shaders.sh 当前需要配置好带有 GCC/Clang 的 Makefile。 不过,在 Windows 上,如果未设置 MinGW 环境,这可能会相当不便。 要使用通过 CMake(或其他方式)构建的 spirv-cross 二进制文件,你可以按如下方式传入环境变量:

SPIRV_CROSS_PATH=path/to/custom/spirv-cross ./test_shaders.sh

然而,在改进 SPIRV-Cross 时,当然存在一些合理情况,需要修改参考输出。 在这些情况下,请运行:

./update_test_shaders.sh          # SPIRV_CROSS_PATH also works here.

要更新参考文件,并将这些更改纳入拉取请求中。

更新参考文件时,务必确保运行的是正确版本的 glslangValidator 和 SPIRV-Tools。

请查看 checkout_glslang_spirv_tools.sh,了解当前预期的版本。这些版本会定期更新。

简而言之,主分支应始终能够成功运行 ./test_shaders.py shaders 及类似命令。 SPIRV-Cross 使用 Travis CI 测试所有拉取请求,因此如果您在本地运行测试遇到问题,并非必须亲自执行测试。 但未通过 Travis 测试的拉取请求将不会被接受。

向 SPIRV-Cross 添加新功能支持时,应添加新的着色器和参考文件,以涵盖所涉及的新着色器功能的用法。 Travis CI 通过运行 ctest 来执行 CMake 测试套件。这是 ./test_shaders.sh 的一种更直接的替代方案。

许可

新文件的贡献者应在每个新源代码文件的顶部添加版权标题,注明自己的版权以及 Apache 2.0 许可存根。

格式

SPIRV-Cross 使用 clang-format 自动格式化代码。 请在提交拉取请求前,使用 .clang-format 中提供的样式表通过 clang-format 自动格式化代码。

为方便起见,可以使用 format_all.sh 脚本格式化库中的所有源文件。在该目录下,从命令行运行以下命令:

./format_all.sh

回归测试

在 shaders/ 目录中,维护了一组用于回归测试的着色器。 当前的参考输出包含在 reference/ 目录中。 可以运行 ./test_shaders.py shaders 来执行回归测试。

有关更多信息,请参见 ./test_shaders.py --help

Metal 后端

要测试 GLSL -> SPIR-V -> MSL 的往返路径,可以添加 --msl,例如 ./test_shaders.py --msl shaders-msl

HLSL 后端

要测试 GLSL -> SPIR-V -> HLSL 的往返路径,可以添加 --hlsl,例如 ./test_shaders.py --hlsl shaders-hlsl

更新回归测试

当发现合理的变更时,使用 --update 标志来更新回归文件。 否则,./test_shaders.py 将失败并返回错误代码。

Mali 离线编译器周期计数

若要获取经过 spirv-cross 处理前后的着色器静态周期计数 CSV,可在 ./test_shaders 中添加 --malisc 标志。这需要 Mali 离线编译器已安装在 PATH 中。

项目介绍

可用于将 SPIR-V 转换为 GLSL、MSL、HLSL 等多种着色器语言及 JSON 反射格式,提供反射 API 简化 Vulkan 管线布局创建,支持多种着色器类型,输出代码可读性强。【此简介由AI生成】

https://gitcode.com/openharmony-sig/chromium_spirv-cross定制我的领域