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 que tu código sea 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

Al disponer sombreadores en app/src/main/shaders/, Android Studio los reconoce por sus extensiones de archivo, y realizará lo siguiente:

  • Compilará todos los archivos de sombreador que se encuentren en ese directorio.
  • Anexará el sufijo .spv a los archivos del compilador SPIR-V que se hayan compilado.
  • Agrupará los sombreadores SPIRV en el directorio assets/shaders/ del APK.

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

Los indicadores de compilación Shaderc podrían configurarse 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 al realizar compilaciones para ese ámbito. En el ejemplo anterior se crea un argumento de ámbito lights, que solo se aplicará a sombreadores GLSL del directorio app/src/main/shaders/lights/. Consulta glslc para obtener la lista completa de indicadores de compilación disponibles. Ten en cuenta que shaderc dentro del NDK es un resumen de ese repositorio de GitHub en el momento de inicio del NDK; puedes obtener los indicadores exactos admitidos para esa versión con el comando glslc --help, como se describe en la siguiente sección. En el tutorial de Vulkan para crear triángulos se usa esta función.

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

Los sombreadores GLSL se pueden compilar en SPIR-V de forma independiente respecto de la aplicación 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 aplicación.

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.

Usas 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 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 ofrezca 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 los pasos a continuación para usar el archivo Android.mk de tu proyecto al 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. Configura APP_STL en c++_static, c++_shared, gnustl_static o gnustl_shared en el archivo Application.mk de la aplicación.

Integración de CMake de Gradle

  1. En una ventana terminal, ve a 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 las bibliotecas generadas usando target_include_directories y target_link_libraries, como lo harías normalmente con bibliotecas externas similares. El tipo STL de tu aplicación debe coincidir con uno de los tipos stl especificados en stl_version. El NDK recomienda usar c++_shared
  4. or c++_static, although gnustl_static and gnustl_shared are also supported.

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.