Android 上的 Vulkan 着色器编译器

Vulkan 应用管理着色器的方式必须不同于 OpenGL ES 应用:在 OpenGL ES 中,您将着色器作为一组字符串提供,形成 GLSL 着色器程序的源代码文本。相比之下,Vulkan API 要求您以 SPIR-V 模块中的入口点的形式提供着色器。

NDK Release 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);

Shaderc 编译标志可以在 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 是 NDK 发布时该 GitHub 仓库的一个快照;您可以使用命令 glslc --help 获取该版本支持的确切标志,如下一节所述。

离线命令行编译

GLSL 着色器可以使用 glslc 命令行编译器独立于主应用程序编译为 SPIR-V。NDK Release 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_directoriestarget_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 构建。