Android 上的 Vulkan 着色器编译器

Vulkan 应用管理着色器的方式必须与 OpenGL ES 应用不同: 在 OpenGL ES 中,您以构成 GLSL 着色器程序源文本的一组字符串形式提供着色器。 与此相反,Vulkan API 要求您以 SPIR-V 模块中的入口点形式提供着色器。

NDK 版本 12 及更高版本包括一个用于将 GLSL 编译到 SPIR-V 中的运行时库。 运行时库与 Shaderc 开放源代码项目中的库相同,并使用相同的 Glslang GLSL 参考编译器作为其后端。 默认情况下,Shaderc 版本的编译器假设您为 Vulkan 编译。 在检查您的代码是否对 Vulkan 有效之后,编译器将自动启用 KHR_vulkan_glsl 扩展。 Shaderc 版本的编译器也会生成与 Vulkan 兼容的 SPIR-V 代码。

您可以选择在开发期间将 SPIR-V 模块编译到您的 Vulkan 应用中,这种做法称为提前(或 AOT)编译。 或者,您也可以让您的应用在运行时根据需要从已发布或者按一定程序生成的着色器源编译这些模块。 此操作被称为运行时编译。 Android Studio 已集成对构建 Vulkan 着色器的支持。

本页面的剩余部分将详细介绍每种做法,然后说明如何将着色器编译集成到您的 Vulkan 应用中。

AOT 编译

您可以通过两种方式实现着色器 AOT 编译,下文将对此作详细介绍。

使用 Android Studio

将着色器置于 app/src/main/shaders/ 后,Android Studio 会通过文件扩展名识别着色器,并完成以下操作:

  • 在该目录下以递归方式编译所有着色器文件。
  • 将 .spv 后缀附加到已编译的 SPIR-V 着色器文件中。
  • 将 SPIRV 着色器封装到 APK 的 assets/shaders/ 目录中。

应用将在运行时从相应的 assets/shaders/ 位置加载已编译的着色器;已编译的 spv 着色器文件结构与 app/src/main/shaders/ 下应用的 GLSL 着色器文件结构相同:

AAsset* file = AAssetManager_open(assetManager,
                     "shaders/tri.vert.spv", AASSET_MODE_BUFFER);
size_t fileLength = AAsset_getLength(file);
char* fileContent = new char[fileLength];
AAsset_read(file, fileContent, fileLength);

您可以在 gradle DSL shaders 块内部配置 Shaderc 编译标记,如以下示例所示:

android {
  defaultConfig {
    shaders {
      glslcArgs.addAll(['-c', '-g'])
      scopedArgs.create('lights') {
        glslcArgs.addAll(['-DLIGHT1=1', '-DLIGHT2=0'])
      }
    }
  }
}

glslcArgs 适用于所有着色器编译;scopedArgs 仅在为该作用域编译时适用。 上述示例将创建一个作用域参数 lights,该参数仅适用于 app/src/main/shaders/lights/ 目录下的 GLSL 着色器。 请参考 glslc,获取可用编译标记的完整列表。 请注意,NDK 内部的 shaderc 是 NDK 发布时该 GitHub 存储区的快照;您可以使用 glslc --help 命令为该版本获取确切的受支持标记,如下一部分所述。 三角形 Vulkan 教程使用此功能。

离线命令行编译

您可以使用 glslc 命令行编译器将 GLSL 着色器编译到独立于主应用的 SPIR-V 中。 NDK 版本 12 及更高版本将一个版本的预构建 glslc 和相关工具封装到 <android-ndk-dir>/shader-tools/ 目录中,以支持这种使用模式。

编辑器也可从 Shaderc 项目中获取;按照项目中的说明构建二进制版本。

glslc 为着色器编译提供一组丰富的 命令行选项,以满足应用的各种要求。

glslc 工具可以将单源文件编译到带一个着色器入口点的 SPIR-V 模块中。 默认情况下,输出文件的名称与源文件的名称相同,但会附加 .spv 扩展名。

使用文件名扩展可以告知 glslc 工具要编译的图形着色器阶段,或者指示某个计算着色器是否正在接受编译。 如需了解如何使用这些文件名扩展,以及您可用于工具的选项的信息,请参阅 glslc 手册中的 着色器阶段规范

运行时编译

对于运行时的着色器 JIT 编译,NDK 提供 libshaderc 库,该库同时拥有 C 和 C++ API。

C++ 应用应当使用 C++ API。 我们建议采用其他语言编写的应用使用 C API,因为 C ABI 的级别较低,可以提供更好的稳定性。

下面的示例显示如何使用 C++ API:

#include <iostream>
#include <string>
#include <vector>
#include <shaderc/shaderc.hpp>

std::vector<uint32_t> compile_file(const std::string& name,
                                   shaderc_shader_kind kind,
                                   const std::string& data) {
  shaderc::Compiler compiler;
  shaderc::CompileOptions options;

  // Like -DMY_DEFINE=1
  options.AddMacroDefinition("MY_DEFINE", "1");

  shaderc::SpvCompilationResult module = compiler.CompileGlslToSpv(
      data.c_str(), data.size(), kind, name.c_str(), options);

  if (module.GetCompilationStatus() !=
      shaderc_compilation_status_success) {
    std::cerr << module.GetErrorMessage();
  }

  std::vector<uint32_t> result(module.cbegin(), module.cend());
  return result;
}

集成到您的项目中

您可以使用项目的 Android.mk 文件或 Gradle,将 Vulkan 着色器编译器集成到应用中。

Android.mk

执行以下步骤以使用您项目的 Android.mk 文件集成着色器编译器。

  1. 在您的 Android.mk 文件中包含以下行:
    include $(CLEAR_VARS)
         ...
    LOCAL_STATIC_LIBRARIES := shaderc
         ...
    include $(BUILD_SHARED_LIBRARY)
    
    $(call import-module, third_party/shaderc)
    
  2. 将 APP_STL 设为 c++_staticc++_sharedgnustl_staticgnustl_shared 之一。

Gradle

  1. 在终端窗口中,导航到 ndk_root/sources/third_party/shaderc/
  2. 运行以下命令:

    $ ../../../ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \
    APP_STL:=<stl_version> APP_ABI=all libshaderc_combined
    

    此命令会将两个文件夹置于 <ndk_root>/sources/third_party/shaderc/。 目录结构如下所示:

    include/
      shaderc/
        shaderc.h
        shaderc.hpp
    libs/
      <stl_version>/
        {all of the abis}
           libshaderc.a
    

  3. 按照您为外部库的操作添加 Include 元素和链接行。
  4. 您用来构建程序的 STL 必须与 stl_version 中指定的 stl 匹配。 仅支持 c++_staticc++_sharedgnustl_staticgnustl_shared