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 块中配置,如下例所示

Groovy

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

Kotlin

android {
  defaultConfig {
    shaders {
        glslcArgs += listOf("-c", "-g")
        glslcScopedArgs("lights", "-DLIGHT1=1", "-DLIGHT2=0")
    }
  }
}

glslcArgs 应用于所有着色器编译;scopedArgs 仅在为该范围编译时应用。以上示例创建了一个范围参数 lights,它仅应用于 app/src/main/shaders/lights/ 目录下的 GLSL 着色器。有关可用编译标志的完整列表,请参阅 glslc。请注意,NDK 中的 Shaderc 是从该 github 存储库在 NDK 发布时获取的快照;您可以使用命令 glslc --help 获取该版本的精确支持标志,如下一节所述。

离线命令行编译

GLSL 着色器可以独立于主应用程序使用 glslc 命令行编译器编译为 SPIR-V。NDK 12 版及更高版本在 <android-ndk-dir>/shader-tools/ 目录中打包了一个预构建的 glslc 和相关工具版本,以支持此使用模型。

编译器也可从 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. 在应用的 Application.mk 中将 APP_STL 设置为 c++_staticc++_sharedgnustl_staticgnustl_shared 之一。

Gradle 的 CMake 集成

  1. 在终端窗口中,导航到 ndk_root/sources/third_party/shaderc/
  2. 运行以下命令以构建 NDK 的 Shaderc。您只需要在使用的每个 NDK 版本上运行此命令一次。
    $ ../../../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. 使用 target_include_directories target_link_libraries 添加生成的包含文件和库,就像您通常对类似的 外部库 所做的那样。您的应用的 STL 类型必须与 stl_version 中指定的 stl 类型之一匹配。NDK 建议使用 c++_sharedc++_static,尽管也支持 gnustl_staticgnustl_shared

获取最新的 Shaderc

NDK 中的 Shaderc 来自 Android 源代码树,它是 上游 Shaderc 仓库 的快照。如果您需要最新的 Shaderc,请参阅 构建说明以获取详细信息。高级步骤如下所示

  1. 下载最新的 Shaderc
    git clone https://github.com/google/shaderc.git
  2. 更新依赖项
    ./utils/git-sync-deps
  3. 构建 Shaderc
    <ndk_dir>/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \
        APP_STL:=c++_static APP_ABI=all libshaderc_combined -j16
    
  4. 配置您的项目以在构建脚本文件中使用您自己的 Shaderc 构建。