A Vulkan app must manage shaders differently from the way an OpenGL ES app does: In OpenGL ES, you provide a shader as a set of strings forming the source text of a GLSL shader program. By contrast, the Vulkan API requires you to provide a shader in the form of an entry point in a SPIR-V module.
The NDK Release 12 and later includes a runtime library for compiling GLSL into SPIR-V.
The runtime library is the same as the one in the
Shaderc open source project, and uses the same
Glslang GLSL reference compiler as its
back end. By default, the Shaderc version of the
compiler assumes you are compiling for Vulkan. After checking whether your code is valid for
Vulkan, the compiler automatically enables the KHR_vulkan_glsl
extension. The Shaderc
version of the compiler also generates Vulkan-compliant SPIR-V code.
You can choose to compile SPIR-V modules into your Vulkan app during development, a practice called ahead-of-time, or AOT, compilation. Alternatively, you can have your app compile them from shipped or procedurally generated shader source when needed during runtime. This practice is called runtime compiling. Android Studio has integrated support to build Vulkan shaders.
The rest of this page provides more detail about each practice, and then explains how to integrate shader compilation into your Vulkan app.
AOT compilation
There are two ways to achieve shader AOT compilation, described in the following sections.
Use Android Studio
Putting shaders into app/src/main/shaders/
, Android Studio recognizes shaders by
their file extensions, and will complete the following actions:
- Compile all shader files recursively under that directory.
- Append the .spv suffix to the compiled SPIR-V shader files.
- Pack SPIRV-shaders into the APK's
assets/shaders/
directory.
The application would load the compiled shaders from the corresponding assets/shaders/
location at run time; the compiled spv shader file structure is the same as the application's GLSL shader file structure under app/src/main/shaders/
:
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 compile flags could be configured inside the gradle DSL shaders
block, as shown in the following example:
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
apply to all shader compilations; scopedArgs
only apply when compiling
for that scope. The above example creates a scope argument lights
, which will only apply to
GLSL shaders under the app/src/main/shaders/lights/
directory. Refer to
glslc for the complete list
of available compilation flags. Note that Shaderc inside NDK is a snapshot from that github repo at the
NDK release time; you can get the exact supported flags for that version with the command
glslc --help
, as decribed in the next section.
Offline command-line compilation
GLSL Shaders can be compiled to SPIR-V independent of the main application using the glslc command-line compiler. NDK release 12 and later packs a version of pre-built glslc and
related tools in the <android-ndk-dir>/shader-tools/
directory to support this usage model.
The compiler is also available from the Shaderc project; follow the instructions there to build a binary version.
glslc provides a rich set of command-line options for shader compilation to meet various requirements for an application.
The glslc tool compiles a single-source file to a SPIR-V module with a single shader
entry point. By default, the output file has the same name as that of the source file,
but with the .spv
extension appended.
You use filename extensions to tell the glslc tool which graphics shader stage to compile, or whether a compute shader is being compiled. For information on how to use these filename extensions, and options you can use with the tool, see Shader stage specification in the glslc manual.
Runtime compilation
For JIT compilation of shaders during runtime, the NDK provides the libshaderc library, which has both C and C++ APIs.
C++ applications should use the C++ API. We recommend that apps in other languages use the C API, because the C ABI is lower level, and likely to provide better stability.
The following example shows how to use the 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; }
Integrate into your projects
You can integrate the Vulkan shader compiler into your app using either the project's
Android.mk
file or Gradle.
Android.mk
Perform the following steps to use your project's Android.mk
file to integrate the shader compiler.
-
Include the following lines in your Android.mk file:
include $(CLEAR_VARS) ... LOCAL_STATIC_LIBRARIES := shaderc ... include $(BUILD_SHARED_LIBRARY) $(call import-module, third_party/shaderc)
-
Set APP_STL to one of
c++_static
,c++_shared
,gnustl_static
, orgnustl_shared
in app's Application.mk
Gradle's CMake integration
-
In a terminal window, navigate to
ndk_root/sources/third_party/shaderc/
. -
Run the following command to build NDK's Shaderc. You only need to run this command once on each NDK version that you use:
$ ../../../ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \ APP_STL:=<stl_version> APP_ABI=all libshaderc_combined
This command places two folders in <ndk_root>/sources/third_party/shaderc/. The directory structure is as follows:
include/ shaderc/ shaderc.h shaderc.hpp libs/ <stl_version>/ {all of the abis} libshaderc.a
-
Add the generated includes and libs using
target_include_directories
andtarget_link_libraries
, as you normally do for similar external libraries. Your app's STL type must match one of thestl
types specified instl_version
. The NDK recommends usingc++_shared
orc++_static
, althoughgnustl_static
andgnustl_shared
are also supported.
Get 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:
- Download the latest Shaderc:
git clone https://github.com/google/shaderc.git
- Update dependencies:
./utils/git-sync-deps
- Build Shaderc:
<ndk_dir>/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \ APP_STL:=c++_static APP_ABI=all libshaderc_combined -j16
- Configure your project to use your own Shaderc build in your build script file.