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 シェーダー ファイルに追加します。
- 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 以降では、この使用モデルをサポートするために、ビルド済みの 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
ファイルを使用してシェーダー コンパイラを統合するには、次の手順を実施します。
- 次の行を Android.mk ファイルに追加します。
include $(CLEAR_VARS) ... LOCAL_STATIC_LIBRARIES := shaderc ... include $(BUILD_SHARED_LIBRARY) $(call import-module, third_party/shaderc)
- アプリの Application.mk 内で APP_STL を
c++_static
、c++_shared
、gnustl_static
、gnustl_shared
のいずれかに設定します。
Gradle の CMake 統合
- ターミナル ウィンドウで、
ndk_root/sources/third_party/shaderc/
に移動します。 -
以下のコマンドを実行して 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
-
同様の外部ライブラリに対して通常行うように、生成されたインクルードやライブラリを
target_include_directories
とtarget_link_libraries
を使って追加します。アプリの STL のタイプは、stl_version
で指定されているstl
タイプのいずれかに一致する必要があります。NDK ではc++_shared
またはc++_static
の使用をおすすめしています。ただし、gnustl_static
とgnustl_shared
もサポートしています。
最新の Shaderc を入手する
NDK の Shaderc は、Shaderc のアップストリーム リポジトリのスナップショットである Android ソースツリーにあります。最新の Shaderc が必要であれば、ビルド手順の詳細をご確認ください。 大まかな手順は次のとおりです。
- 最新の Shaderc をダウンロードします。
git clone https://github.com/google/shaderc.git
- 依存関係を更新します。
./utils/git-sync-deps
- Shaderc をビルドします。
<ndk_dir>/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \ APP_STL:=c++_static APP_ABI=all libshaderc_combined -j16
- ビルド スクリプト ファイル内で、独自の Shaderc ビルドを使用するようにプロジェクトを設定します。