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 編譯標記,如以下範例所示:

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 指令取得對應版本支援的確切標記,詳情請見下一節。

離線指令列編譯

您可以使用 glslc 指令列編譯器,將 GLSL 著色器編譯成不受主要應用程式影響的 SPIR-V。NDK 12 以上版本會將某個版本的預建 glslc 和相關工具封裝至 <android-ndk-dir>/shader-tools/ 目錄,以便支援這種使用模式。

您也可以從 Shaderc 專案取得編譯器,請按照相關操作說明建構二進位檔版本。

glslc 為著色器編譯提供了豐富的指令列選項,能夠滿足應用程式的各種需求。

glslc 工具可將單一來源檔案編譯成包含單一著色器進入點的 SPIR-V 模組。根據預設,輸出檔案的名稱與來源檔案的名稱相同,但附加了 .spv 副檔名。

您可以使用檔案名稱副檔名來告知 glslc 工具要編譯的圖形著色器階段,或者指示是否正在編譯某個運算著色器。如要瞭解如何使用這些檔案名稱副檔名,以及這項工具的運用方式,請參閱 glslc 手冊中的著色器階段規格

執行階段編譯

針對執行階段期間著色器的 JIT 編譯,NDK 提供同時包含 C 和 C++ API 的 Libshaderc 程式庫。

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 新增產生的 include 和 lib。 應用程式的 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 版本。