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)コンパイルと呼ばれます。別の方法として、アプリに、標準搭載されているシェーダー ソースや手続き型で生成されるシェーダー ソースから実行時に必要に応じてコンパイルさせることも可能です。この手法はランタイム コンパイルと呼ばれます。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 ブロック内で構成できます。

    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 API は下位レベルなので、より安定していると考えられるためです。

次の例では、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 バージョンで 1 回のみです。
        $ ../../../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/ に 2 つのフォルダが配置されます。ディレクトリ構造は次のようになります。

        include/
          shaderc/
            shaderc.h
            shaderc.hpp
        libs/
          <stl_version>/
            {all of the abis}
               libshaderc.a
        
  3. 同様の外部ライブラリに対して通常行うように、target_include_directoriestarget_link_libraries を使用して、生成されたインクルードやライブラリを追加します。アプリの STL のタイプは、stl で指定されている stl_version タイプのいずれかに一致する必要があります。NDK では c++_shared
  4. または c++_static の使用をお勧めします(gnustl_staticgnustl_shared もサポートされています)。

Getting The Latest Shaderc

Shaderc in NDK comes from Android Source tree, which is a snapshot of the upstream Shaderc repo. If you need the latest Shaderc, refer to build instruction for details. The high-level steps are as follows:

  1. Download the latest Shaderc:
    git clone https://github.com/google/shaderc.git
  2. Update dependencies:
    ./utils/git-sync-dep
  3. Build Shaderc:
    <ndk_dir>/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \
            APP_STL:=c++_static APP_ABI=all libshaderc_combined -j16
        
  4. Configure your project to use your own Shaderc build in your build script file.