Compiladores de sombreadores Vulkan en Android

Una app Vulkan se diferencia de una app de OpenGL ES respecto de la administración de sombreadores: en OpenGL ES, se proporciona un sombreador como un conjunto de strings que forman el texto de origen de un programa sombreador GLSL. Por el contrario, para la API de Vulkan debes proporcionar un sombreador en forma de punto de entrada en un módulo SPIR-V.

La versión 12 del NDK y las versiones posteriores incluyen una biblioteca en tiempo de ejecución para compilar GLSL en SPIR-V. La biblioteca en tiempo de ejecución es la misma que la del proyecto de código abierto Shaderc y usa el mismo compilador de referencia Glslang GLSL como backend. De forma predeterminada, la versión Shaderc del compilador prevé que realizas la compilación para Vulkan. Después de comprobar si tu código es válido para Vulkan, el compilador habilita automáticamente la extensión KHR_vulkan_glsl. La versión Shaderc del compilador también genera código SPIR-V compatible con Vulkan.

Puedes decidir compilar módulos SPIR-V en tu app de Vulkan durante el desarrollo, una práctica llamada compilación anticipada o AOT (por su sigla en inglés, ahead-of-time). Como alternativa, puedes hacer que tu app los compile a partir de código fuente del sombreador incluido o generado mediante procedimientos cuando los necesites durante el tiempo de ejecución. Esta práctica se denomina compilación en tiempo de ejecución. Android Studio sumó compatibilidad para compilar sombreadores de Vulkan.

El resto de esta página contiene más detalles sobre cada práctica, y luego explica la manera de integrar la compilación de sombreadores en tu app de Vulkan.

Compilación AOT

Hay dos maneras de lograr la compilación AOT del sombreador, y se describen en las secciones que siguen.

Uso de Android Studio

Cuando se colocan sombreadores en app/src/main/shaders/, Android Studio los reconoce por las extensiones de los archivos y realiza las siguientes acciones:

  • Compila todos los archivos de sombreador que se encuentran en ese directorio.
  • Anexa el sufijo .spv a los archivos del compilador SPIR-V que se compilaron.
  • Agrupa los sombreadores SPIRV en el directorio assets/shaders/ del APK.

La app debería cargar los sombreadores compilados desde la ubicación assets/shaders/ correspondiente en el tiempo de ejecución; la estructura de archivos de sombreado del compilador SPV es la misma que la de los archivos del sombreador GLSL de la app, que se encuentran en 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);

Se pueden configurar las marcas de compilación de Shaderc dentro del bloque shaders de DSL de Gradle, como se muestra en el siguiente ejemplo:

android {
  defaultConfig {
    shaders {
      glslcArgs.addAll(['-c', '-g'])
      scopedArgs.create('lights') {
        glslcArgs.addAll(['-DLIGHT1=1', '-DLIGHT2=0'])
      }
    }
  }
}

glslcArgs se aplica a todas las compilaciones de sombreadores; scopedArgs solo se aplica cuando se compila para ese alcance. En el ejemplo anterior, se crea un argumento de alcance lights, que solo se aplicará a sombreadores GLSL del directorio app/src/main/shaders/lights/. Consulta glslc para obtener la lista completa de las marcas de compilación disponibles. Ten en cuenta que, dentro del NDK, shaderc es un resumen de ese repositorio de GitHub en el momento de inicio del NDK; puedes obtener las marcas exactas admitidas para esa versión con el comando glslc --help, como se describe en la siguiente sección.

Compilación de la línea de comandos sin conexión

Se pueden compilar los sombreadores GLSL en SPIR-V de forma independiente respecto de la app principal usando el compilador de línea de comandos glslc. La versión 12 del NDK y las versiones posteriores incluyen una versión de glslc compilada previamente y herramientas relacionadas en el directorio <android-ndk-dir>/shader-tools/ a fin de admitir este modelo de uso.

El compilador también está disponible en el proyecto Shaderc; sigue las instrucciones que allí se proporcionan para compilar una versión binaria.

glslc proporciona un conjunto completo de opciones de líneas de comandos para que la compilación de sombreadores cumpla con varios de los requisitos de una app.

La herramienta glslc compila un solo archivo de origen en un módulo SPIR-V con un solo punto de entrada para el sombreador. De forma predeterminada, el archivo resultante tiene el mismo nombre que el archivo de origen, aunque con la extensión .spv anexada.

Usa extensiones de nombre de archivo para indicar a la herramienta glslc los gráficos de la etapa del sombreador que se compilarán, o si se va a compilar un sombreador de cómputos. Para obtener información sobre cómo usar estas extensiones de nombre de archivo y sobre las opciones que puedes utilizar con la herramienta, consulta Especificación de etapas del sombreador en el manual de glslc.

Compilación en tiempo de ejecución

Para la compilación JIT de sombreadores en tiempo de ejecución, el NDK proporciona la biblioteca libshaderc, que cuenta con API C y C++.

Las aplicaciones C++ deben usar la API C++. Recomendamos que las apps en otros idiomas{}usen la API C, ya que la ABI C es de un nivel inferior, y es probable que proporcione mayor estabilidad.

En el siguiente ejemplo, se muestra la manera de usar la API C++:

#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;
}

Integración en tus proyectos

Puedes integrar el compilador de sombreadores Vulkan a tu app usando el archivo Android.mk del proyecto o Gradle.

Android.mk

Sigue estos pasos para usar el archivo Android.mk de tu proyecto a fin de integrar el compilador de sombreadores.

  1. Incluye las siguientes líneas en tu archivo Android.mk:
    include $(CLEAR_VARS)
         ...
    LOCAL_STATIC_LIBRARIES := shaderc
         ...
    include $(BUILD_SHARED_LIBRARY)
    
    $(call import-module, third_party/shaderc)
    
  2. Establece APP_STL en c++_static, c++_shared, gnustl_static o gnustl_shared en el archivo Application.mk de la app

Integración de CMake de Gradle

  1. En una ventana de terminal, navega hasta ndk_root/sources/third_party/shaderc/.
  2. Ejecuta el siguiente comando para compilar Shaderc de NDK. Solo necesitas ejecutar este comando una vez en cada versión del NDK que uses:
    $ ../../../ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \
    APP_STL:=<stl_version> APP_ABI=all libshaderc_combined
    

    Este comando dispone dos carpetas en <ndk_root>/sources/third_party/shaderc/. La estructura del directorio es la siguiente:

    include/
      shaderc/
        shaderc.h
        shaderc.hpp
    libs/
      <stl_version>/
        {all of the abis}
           libshaderc.a
    
  3. Agrega las inclusiones y bibliotecas generadas usando target_include_directories y target_link_libraries, como lo harías normalmente con bibliotecas externas similares. El tipo STL de tu app debe coincidir con uno de los tipos stl especificados en stl_version. Para el NDK, se recomienda usar c++_shared o c++_static, aunque también se admiten gnustl_static y gnustl_shared.

Cómo obtener la versión más reciente de Shaderc

En NDK, Shaderc proviene del árbol de fuentes de Android, que es una instantánea del repositorio de Shaderc ascendente. Si necesitas la versión más reciente de Shaderc, consulta las instrucciones de compilación para obtener detalles. Los pasos de alto nivel son los siguientes:

  1. Descarga la versión más reciente de Shaderc:
    git clone https://github.com/google/shaderc.git
  2. Actualiza las dependencias:
    ./utils/git-sync-dep
  3. Compila Shaderc:
    <ndk_dir>/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=Android.mk \
        APP_STL:=c++_static APP_ABI=all libshaderc_combined -j16
    
  4. Configura tu proyecto para que use tu propia compilación de Shaderc en tu archivo de secuencia de comandos de compilación.