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* 命令行编译器独立于主应用程序进行编译。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 版本。