Implementando Vulkan

Vulkan es una API multiplataforma de bajo costo para gráficos 3D de alto rendimiento. Al igual que OpenGL ES (GLES) , Vulkan proporciona herramientas para crear gráficos en tiempo real de alta calidad en las aplicaciones. Las ventajas de usar Vulkan incluyen reducciones en la sobrecarga de la CPU y soporte para el lenguaje SPIR-V Binary Intermediate .

Para implementar Vulkan con éxito, un dispositivo debe incluir:

  • El cargador Vulkan, proporcionado por Android.
  • Un controlador Vulkan, proporcionado por SoC como GPU IHV, que implementa la API de Vulkan . Para admitir la funcionalidad de Vulkan, el dispositivo Android necesita hardware de GPU compatible con Vulkan y el controlador asociado. La GPU también debe ser compatible con GLES 3.1 y superior. Consulte a su proveedor de SoC para solicitar soporte de controladores.

Si un dispositivo incluye un controlador Vulkan, el dispositivo debe declarar las características del sistema FEATURE_VULKAN_HARDWARE_LEVEL y FEATURE_VULKAN_HARDWARE_VERSION , con versiones que reflejen con precisión las capacidades del dispositivo. Esto ayuda a garantizar que el dispositivo cumpla con el Documento de definición de compatibilidad (CDD).

cargadora Vulkan

La platform/frameworks/native/vulkan del cargador Vulkan es la interfaz principal entre las aplicaciones Vulkan y el controlador Vulkan de un dispositivo. El cargador Vulkan está instalado en /system/lib[64]/libvulkan.so . El cargador proporciona los puntos de entrada principales de la API de Vulkan, así como los puntos de entrada de las extensiones requeridas por el CDD de Android. El cargador exporta las extensiones de Windows System Integration (WSI) y se implementan principalmente en el cargador en lugar de en el controlador. El cargador también admite la enumeración y la carga de capas que pueden exponer extensiones adicionales e interceptar llamadas de API principales en su camino hacia el controlador.

El NDK incluye una biblioteca stub libvulkan.so para la vinculación. La biblioteca exporta los mismos símbolos que el cargador. Las aplicaciones llaman a las funciones exportadas de la biblioteca real libvulkan.so para ingresar funciones de trampolín en el cargador, que se envían a la capa o controlador apropiado según su primer argumento. La vkGet*ProcAddr() devuelve los punteros de función a los que se envían los trampolines (es decir, llama directamente al código API central). Llamar a través de los punteros de función, en lugar de los símbolos exportados, es más eficiente ya que omite el trampolín y el envío.

Enumeración y carga de controladores

Cuando se crea la imagen del sistema, Android espera que el sistema sepa qué GPU están disponibles. El cargador usa el mecanismo HAL existente en hardware.h para descubrir y cargar el controlador. Las rutas preferidas para los controladores Vulkan de 32 y 64 bits son:

/vendor/lib/hw/vulkan.<ro.hardware.vulkan>.so
/vendor/lib/hw/vulkan.<ro.product.platform>.so
/vendor/lib64/hw/vulkan.<ro.hardware.vulkan>.so
/vendor/lib64/hw/vulkan.<ro.product.platform>.so

En Android 7.0 y versiones posteriores, el derivado Vulkan hw_module_t envuelve una sola estructura hw_module_t ; solo se admite un controlador y la cadena constante HWVULKAN_DEVICE_0 se pasa a open() .

El derivado Vulkan hw_device_t corresponde a un solo controlador que puede admitir múltiples dispositivos físicos. La estructura hw_device_t puede extenderse para exportar las vkGetGlobalExtensionProperties() , vkCreateInstance() y vkGetInstanceProcAddr() . El cargador puede encontrar todas las demás VkInstance() , VkPhysicalDevice() y vkGetDeviceProcAddr() llamando a hw_device_t () de la estructura vkGetInstanceProcAddr() .

Descubrimiento y carga de capas

El cargador Vulkan admite la enumeración y la carga de capas que pueden exponer extensiones adicionales e interceptar llamadas de API principales en su camino hacia el controlador. Android no incluye capas en la imagen del sistema; sin embargo, las aplicaciones pueden incluir capas en su APK.

Al usar capas, tenga en cuenta que el modelo y las políticas de seguridad de Android difieren significativamente de otras plataformas. En particular, Android no permite cargar código externo en un proceso no depurable en dispositivos de producción (no rooteados), ni permite que el código externo inspeccione o controle la memoria, el estado, etc. del proceso. Esto incluye la prohibición de guardar volcados de núcleo, seguimientos de API, etc. en el disco para una inspección posterior. Solo las capas entregadas como parte de las aplicaciones no depurables están habilitadas en los dispositivos de producción y los controladores no deben proporcionar una funcionalidad que infrinja estas políticas.

Los casos de uso para capas incluyen:

  • Capas de tiempo de desarrollo: las capas de validación y las correcciones de compatibilidad para las herramientas de seguimiento/perfilado/depuración no deben instalarse en la imagen del sistema de los dispositivos de producción. Las capas de validación y las correcciones de compatibilidad para las herramientas de seguimiento/perfilado/depuración deben poder actualizarse sin una imagen del sistema. Los desarrolladores que deseen utilizar una de estas capas durante el desarrollo pueden modificar el paquete de la aplicación, por ejemplo, agregando un archivo a su directorio de bibliotecas nativas. Se supone que los ingenieros de IHV y OEM que desean diagnosticar fallas en el envío de aplicaciones no modificables tienen acceso a compilaciones no productivas (rooteadas) de la imagen del sistema, a menos que esas aplicaciones sean depurables. Para obtener más información, consulte Capas de validación de Vulkan en Android .
  • Capas de utilidad : estas capas exponen extensiones, como una capa que implementa un administrador de memoria para la memoria del dispositivo. Los desarrolladores eligen capas y versiones de esas capas para usar en su aplicación; diferentes aplicaciones que usan la misma capa aún pueden usar diferentes versiones. Los desarrolladores eligen cuál de estas capas enviar en el paquete de su aplicación.
  • Capas inyectadas (implícitas) : incluye capas como la velocidad de fotogramas, la red social y las superposiciones del iniciador de juegos proporcionadas por el usuario o alguna otra aplicación sin el conocimiento o consentimiento de la aplicación. Estos violan las políticas de seguridad de Android y no son compatibles.

Para las aplicaciones que no se pueden depurar, el cargador busca capas solo en el directorio de la biblioteca nativa de la aplicación e intenta cargar cualquier biblioteca con un nombre que coincida con un patrón particular (por ejemplo, libVKLayer_foo.so ).

Para aplicaciones depurables, el cargador busca capas en /data/local/debug/vulkan e intenta cargar cualquier biblioteca que coincida con un patrón particular.

Android permite que las capas se transfieran con cambios en el entorno de compilación entre Android y otras plataformas. Para obtener detalles sobre la interfaz entre las capas y el cargador, consulte Arquitectura de las interfaces del cargador Vulkan . Las capas de validación mantenidas por Khronos están alojadas en las capas de validación de Vulkan .

Versiones y capacidades de la API de Vulkan

Android 9 y versiones posteriores son compatibles con la versión 1.1 de la API de Vulkan. Android 7 a Android 9 es compatible con la versión 1.0 de la API de Vulkan. Para obtener más información sobre la API de Vulkan 1.1, consulte las especificaciones de la API de Vulkan 1.1 .

Descripción general del soporte de Vulkan 1.1

Vulkan 1.1 incluye compatibilidad con la interoperabilidad de memoria/sincronización, lo que permite a los OEM admitir Vulkan 1.1 en los dispositivos. Además, la interoperabilidad de memoria/sincronización permite a los desarrolladores determinar si Vulkan 1.1 es compatible con un dispositivo y usarlo de manera efectiva cuando lo sea. Vulkan 1.1 tiene los mismos requisitos de hardware que Vulkan 1.0, pero la mayor parte de la implementación se encuentra en el controlador de gráficos específico del SOC, no en el marco.

Las características más importantes de Vulkan 1.1 para Android son:

  • Soporte para importar y exportar búferes de memoria y objetos de sincronización desde fuera de Vulkan (para interoperabilidad con cámara, códecs y GLES)
  • Soporte para formatos YCbCr

Vulkan 1.1 también incluye varias funciones más pequeñas y mejoras en la usabilidad de la API.

Implementando Vulkan 1.1

Los dispositivos Android deberían ser compatibles con Vulkan 1.1 si:

  • Lanzamiento con Android 10.
  • Admite una ABI de 64 bits.
  • No son de poca memoria.

Otros dispositivos pueden admitir opcionalmente Vulkan 1.1.

Para implementar Vulkan 1.1:

  1. Agregue un controlador Vulkan que admita Vulkan 1.1 más los requisitos adicionales de CDD de Android 1.1 o actualice el controlador Vulkan 1.0 existente.
  2. Asegúrese de que PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x401000) devuelva true agregando una regla como la siguiente a un archivo device.mk adecuado:
    PRODUCT_COPY_FILES += frameworks/native/data/etc/android.hardware.vulkan.version-1_1.xml:
    $(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.version.xml
    

Integración del sistema de ventanas (WSI)

En libvulkan.so , el controlador implementa las siguientes extensiones de Integración del sistema de ventanas (WSI):

  • VK_KHR_surface
  • VK_KHR_android_surface
  • VK_KHR_swapchain
  • VK_KHR_driver_properties , implementado solo para Vulkan 1.1 en Android 10
  • VK_GOOGLE_display_timing , implementado para cualquier versión de Vulkan en Android 10

La plataforma maneja los objetos VkSurfaceKHR y VkSwapchainKHR y todas las interacciones con ANativeWindow y no están expuestos a los controladores. La implementación de WSI se basa en la extensión VK_ANDROID_native_buffer , que debe ser compatible con el controlador; esta extensión solo la usa la implementación de WSI y no está expuesta a las aplicaciones.

Indicadores de uso de Gralloc

Las implementaciones de Vulkan pueden necesitar que se asignen búferes de cadena de intercambio con indicadores de uso de Gralloc privados definidos por la implementación. Al crear una cadena de intercambio, Android le pide al controlador que traduzca el formato solicitado y las banderas de uso de la imagen en banderas de uso de Gralloc llamando:

typedef enum VkSwapchainImageUsageFlagBitsANDROID {
    VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID = 0x00000001,
    VK_SWAPCHAIN_IMAGE_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkSwapchainImageUsageFlagBitsANDROID;
typedef VkFlags VkSwapchainImageUsageFlagsANDROID;

VkResult VKAPI vkGetSwapchainGrallocUsage2ANDROID(
    VkDevice                          device,
    VkFormat                          format,
    VkImageUsageFlags                 imageUsage,
    VkSwapchainImageUsageFlagsANDROID swapchainUsage,
    uint64_t*                         grallocConsumerUsage,
    uint64_t*                         grallocProducerUsage
);

Los parámetros format e imageUsage se toman de la estructura VkSwapchainCreateInfoKHR . El controlador debe llenar *grallocConsumerUsage y *grallocProducerUsage con los indicadores de uso de Gralloc necesarios para el formato y el uso. Los indicadores de uso devueltos por el controlador se combinan con los indicadores de uso solicitados por el consumidor de la cadena de intercambio al asignar búferes.

Android 7.x llama a una versión anterior de VkSwapchainImageUsageFlagsANDROID() , denominada vkGetSwapchainGrallocUsageANDROID() . Android 8.0 y versiones posteriores desaprueban vkGetSwapchainGrallocUsageANDROID() pero siguen llamando a vkGetSwapchainGrallocUsageANDROID() si el controlador no proporciona vkGetSwapchainGrallocUsage2ANDROID() :

VkResult VKAPI vkGetSwapchainGrallocUsageANDROID(
    VkDevice            device,
    VkFormat            format,
    VkImageUsageFlags   imageUsage,
    int*                grallocUsage
);

vkGetSwapchainGrallocUsageANDROID() no admite indicadores de uso de swapchain ni indicadores de uso extendido de Gralloc.

Imágenes respaldadas por Gralloc

VkNativeBufferANDROID es una estructura de extensión vkCreateImage para crear una imagen respaldada por un búfer Gralloc. VkNativeBufferANDROID se proporciona a vkCreateImage() en la cadena de estructura VkImageCreateInfo . Las llamadas a vkCreateImage() con VkNativeBufferANDROID ocurren durante la llamada a vkCreateSwapchainKHR . La implementación de WSI asigna la cantidad de búferes nativos solicitados para la cadena de intercambio y luego crea una VkImage para cada uno:

typedef struct {
    VkStructureType             sType; // must be VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID
    const void*                 pNext;

    // Buffer handle and stride returned from gralloc alloc()
    buffer_handle_t             handle;
    int                         stride;

    // Gralloc format and usage requested when the buffer was allocated.
    int                         format;
    int                         usage;
    // Beginning in Android 8.0, the usage field above is deprecated and the
    // usage2 struct below was added. The usage field is still filled in for
    // compatibility with Android 7.0 drivers. Drivers for Android 8.0
    // should prefer the usage2 struct, especially if the
    // android.hardware.graphics.allocator HAL uses the extended usage bits.
    struct {
        uint64_t                consumer;
        uint64_t                producer;
    } usage2;
} VkNativeBufferANDROID;

Al crear una imagen respaldada por Gralloc, VkImageCreateInfo tiene los siguientes datos:

  .sType               = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO
  .pNext               = the above VkNativeBufferANDROID structure
  .imageType           = VK_IMAGE_TYPE_2D
  .format              = a VkFormat matching the format requested for the gralloc buffer
  .extent              = the 2D dimensions requested for the gralloc buffer
  .mipLevels           = 1
  .arraySize           = 1
  .samples             = 1
  .tiling              = VK_IMAGE_TILING_OPTIMAL
  .usage               = VkSwapchainCreateInfoKHR::imageUsage
  .flags               = 0
  .sharingMode         = VkSwapchainCreateInfoKHR::imageSharingMode
  .queueFamilyCount    = VkSwapchainCreateInfoKHR::queueFamilyIndexCount
  .pQueueFamilyIndices = VkSwapchainCreateInfoKHR::pQueueFamilyIndices

En Android 8.0 y versiones posteriores, la plataforma proporciona una estructura de extensión VkSwapchainImageCreateInfoKHR en la cadena VkImageCreateInfo proporcionada a vkCreateImage cuando se requieren marcas de uso de imágenes de la cadena de intercambio para la cadena de intercambio. La estructura de la extensión contiene los indicadores de uso de la imagen de la cadena de intercambio:

typedef struct {
    VkStructureType                        sType; // must be VK_STRUCTURE_TYPE_SWAPCHAIN_IMAGE_CREATE_INFO_ANDROID
    const void*                            pNext;

    VkSwapchainImageUsageFlagsANDROID      usage;
} VkSwapchainImageCreateInfoANDROID;

En Android 10 y versiones posteriores, la plataforma es compatible con VK_KHR_swapchain , por lo que la aplicación Vulkan puede crear una VkImage respaldada por memoria de cadena de intercambio. La aplicación primero llama a vkCreateImage con una estructura VkImageSwapchainCreateInfoKHR encadenada a la estructura VkImageCreateInfo . Luego, la aplicación llama a vkBindImageMemory2(KHR) con una estructura VkBindImageMemorySwapchainInfoKHR encadenada a la estructura VkBindImageMemoryInfo . El índice de imagen especificado en la estructura imageIndex debe ser un índice de imagen de cadena de VkBindImageMemorySwapchainInfoKHR válido. Mientras tanto, la plataforma proporciona una estructura de extensión VkNativeBufferANDROID con la información del búfer Gralloc correspondiente a la cadena VkBindImageMemoryInfo , de modo que el controlador sepa con qué búfer Gralloc enlazar VkImage .

Adquisición de imágenes

vkAcquireImageANDROID adquiere la propiedad de una imagen de cadena de intercambio e importa una cerca nativa señalada externamente en un objeto VkSemaphore existente y un objeto VkFence existente:

VkResult VKAPI vkAcquireImageANDROID(
    VkDevice            device,
    VkImage             image,
    int                 nativeFenceFd,
    VkSemaphore         semaphore,
    VkFence             fence
);

Se llama a vkAcquireImageANDROID() durante vkAcquireNextImageKHR para importar una valla nativa en los objetos VkSemaphore y VkFence proporcionados por la aplicación (sin embargo, tanto los objetos de semáforo como de valla son opcionales en esta llamada). El controlador también puede aprovechar esta oportunidad para reconocer y manejar cualquier cambio externo en el estado del búfer de Gralloc; muchos conductores no necesitarán hacer nada aquí. Esta llamada pone a VkSemaphore y VkFence en el mismo estado pendiente como si vkQueueSubmit lo señalara, por lo que las colas pueden esperar en el semáforo y la aplicación puede esperar en la cerca.

Ambos objetos se señalan cuando la valla nativa subyacente señala; si la cerca nativa ya ha señalado, entonces el semáforo está en el estado señalado cuando esta función regresa. El controlador toma posesión del descriptor del archivo de vallas y cierra el descriptor de archivos de vallas cuando ya no se necesita. El controlador debe hacerlo incluso si no se proporciona un semáforo ni un objeto de valla, o incluso si vkAcquireImageANDROID falla y devuelve un error. Si fenceFd es -1, es como si la cerca nativa ya estuviera señalada.

Publicación de imágenes

vkQueueSignalReleaseImageANDROID prepara una imagen de cadena de intercambio para uso externo, crea una valla nativa y programa la valla nativa para que se señalice después de que los semáforos de entrada hayan señalado:

VkResult VKAPI vkQueueSignalReleaseImageANDROID(
    VkQueue             queue,
    uint32_t            waitSemaphoreCount,
    const VkSemaphore*  pWaitSemaphores,
    VkImage             image,
    int*                pNativeFenceFd
);

vkQueuePresentKHR() llama a vkQueueSignalReleaseImageANDROID() en la cola proporcionada. El controlador debe producir una valla nativa que no señalice hasta que todos los semáforos waitSemaphoreCount en pWaitSemaphores , y se complete cualquier trabajo adicional necesario para preparar image para la presentación.

Si los semáforos de espera (si los hay) ya se señalaron y la queue ya está inactiva, el controlador puede establecer *pNativeFenceFd en -1 en lugar de un descriptor de archivo de valla nativo real, lo que indica que no hay nada que esperar. La persona que llama posee y cierra el descriptor de archivo devuelto en *pNativeFenceFd .

Muchos controladores pueden ignorar el parámetro de la imagen, pero algunos pueden necesitar preparar estructuras de datos del lado de la CPU asociadas con un búfer Gralloc para que las usen los consumidores de imágenes externos. La preparación del contenido del búfer para que lo usen los consumidores externos debe realizarse de forma asíncrona como parte de la transición de la imagen a VK_IMAGE_LAYOUT_PRESENT_SRC_KHR .

Si la imagen se creó con VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID , entonces el controlador debe permitir que vkQueueSignalReleaseImageANDROID() se llame repetidamente sin que intervengan llamadas a vkAcquireImageANDROID() .

Compatibilidad con imagen presentable compartida

Algunos dispositivos pueden compartir la propiedad de una sola imagen entre la canalización de visualización y la implementación de Vulkan para minimizar la latencia. En Android 9 y versiones posteriores, el cargador anuncia de forma condicional la extensión VK_KHR_shared_presentable_image en función de la respuesta del controlador a una llamada a vkGetPhysicalDeviceProperties2 .

Si el controlador no es compatible con Vulkan 1.1 o la extensión VK_KHR_physical_device_properties2 , el cargador no anuncia la compatibilidad con imágenes presentables compartidas. De lo contrario, el cargador consulta las capacidades del controlador llamando a vkGetPhysicalDeviceProperties2() e incluye la siguiente estructura en la cadena VkPhysicalDeviceProperties2::pNext :

typedef struct {
    VkStructureType sType; // must be VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENTATION_PROPERTIES_ANDROID
    const void*     pNext;
    VkBool32        sharedImage;
} VkPhysicalDevicePresentationPropertiesANDROID;

Si el controlador puede compartir la propiedad de una imagen con el sistema de visualización, establece el miembro sharedImage en VK_TRUE .

Validación

Los OEM pueden probar su implementación de Vulkan utilizando CTS, que incluye:

  • Pruebas de conformidad de Khronos Vulkan en el módulo CtsDeqpTestCases , que incluyen pruebas API funcionales para Vulkan 1.0 y 1.1.
  • El módulo CtsGraphicsTestCases , que prueba que el dispositivo está configurado correctamente para las capacidades de Vulkan que admite.

Indicador de características de Vulkan

Se requiere un dispositivo que admita Android 11 o superior y que admita la API de Vulkan para exponer un indicador de función, android.software.vulkan.deqp.level . El valor de este indicador de característica es una fecha, codificada como un valor entero. Especifica la fecha asociada con las pruebas de Vulkan dEQP que el dispositivo afirma haber superado.

Una fecha con el formato AAAA-MM-DD se codifica como un número entero de 32 bits de la siguiente manera:

  • Los bits 0-15 almacenan el año
  • Bits 16-23 almacenan el mes
  • Los bits 24-31 almacenan el día

El valor mínimo permitido para el indicador de funciones es 0x07E30301 , que corresponde a la fecha 2019-03-01, que es la fecha asociada con las pruebas Vulkan dEQP para Android 10. Si el indicador de funciones tiene al menos este valor, el dispositivo afirma Pase todas las pruebas de Android 10 Vulkan dEQP.

El valor 0x07E40301 corresponde a la fecha 2020-03-01, que es la fecha asociada con las pruebas de Vulkan dEQP para Android 11. Si el indicador de función tiene al menos este valor, el dispositivo afirma pasar todas las pruebas de Android 11 Vulkan dEQP.

Si el valor del indicador de característica es al menos 0x07E30301 pero menor que 0x07E40301 , significa que el dispositivo afirma pasar todas las pruebas de Vulkan dEQP de Android 10, pero no se garantiza que pase las pruebas de Vulkan dEQP que se agregaron para Android 11.

Vulkan dEQP forma parte de Android CTS. A partir de Android 11, el componente de ejecutor de pruebas dEQP de CTS reconoce el indicador de función android.software.vulkan.deqp.level y omite cualquier prueba de Vulkan dEQP que, de acuerdo con este indicador de función, el dispositivo no afirma admitir. Tales pruebas se reportan como trivialmente aprobadas.