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 アプリにコンパイルすることができ、この手法は Ahead-of-time(AOT)コンパイルと呼ばれます。 または必要に応じて、アプリに、公開されているシェーダー ソースや手続き的に生成されるシェーダー ソースから実行時にコンパイルさせることも可能です。 この手法はランタイム コンパイルと呼ばれます。Gradle の試験的なプラグインが追加されている Android Studio には、Vulkan シェーダーをビルドするためのサポートが組み込まれています。

ここから、各手法について詳しく説明します。また、シェーダー コンパイルを Vulkan アプリに統合する方法についても説明します。

AOT コンパイル

次のセクションで説明するように、シェーダーの AOT コンパイルを実行する方法は 2 つあります。

Android Studio の使用

シェーダーを app/src/main/shaders/ に追加すると、Android Studio はファイル拡張子でシェーダーを認識し、次の操作を完了します。

  • このディレクトリ内のすべてのシェーダー ファイルを再帰的にコンパイルします。
  • .spv という接尾語をコンパイル済みの SPIR-V シェーダー ファイルに追加します。
  • SPIR-V シェーダーを 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 ブロック内で構成できます。

model {
  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 の triangle チュートリアルではこの機能を使用します。

オフライン コマンドライン コンパイル

GLSL シェーダーは、glslc コマンドライン コンパイラを使用することで、メインアプリから独立して SPIR-V にコンパイルできます。NDK Release 12 以降では、この使用モデルをサポートするために、事前にビルドされた glslc のバージョンと関連ツールが <android-ndk-dir>/shader-tools/ ディレクトリにまとめられています。

このコンパイラは、Shaderc プロジェクトからも使用できます。バイナリ バージョンをビルドするには、サイトの手順に従ってください。

glslc には、シェーダー コンパイルのコマンドライン オプションが豊富に用意されているため、アプリのさまざまな要件を満たすことが可能です。

glslc ツールは、シェーダー エントリ ポイントが 1 つある、1 つのソースファイルを SPIR-V モジュールにコンパイルします。 デフォルトでは、出力ファイルはソースファイルのものと同じ名前になりますが、.spv という拡張子が付きます。

どのグラフィック シェーダー ステージをコンパイルするか、または演算シェーダーがコンパイルされているかどうかを glslc ツールに通知するには、ファイル拡張子を使用します。 ファイル拡張子の使い方と、ツールで使用できるオプションについては、glslc マニュアルのシェーダー ステージの仕様を参照してください。

ランタイム コンパイル

NDK では、実行中のシェーダーの JIT コンパイル用に、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_static、または gnustl_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 ルート>/sources/third_party/shaderc/ に 2 つのフォルダが配置されます。ディレクトリ構造は次のようになります。

    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 のみになります。