Implementando Vulkan

Vulkan é uma API multiplataforma de baixa sobrecarga para gráficos 3D de alto desempenho. Assim como o OpenGL ES (GLES) , o Vulkan fornece ferramentas para criar gráficos de alta qualidade em tempo real em aplicativos. As vantagens de usar o Vulkan incluem reduções na sobrecarga da CPU e suporte para a linguagem SPIR-V Binary Intermediate .

Para implementar o Vulkan com sucesso, um dispositivo deve incluir:

  • O carregador Vulkan, fornecido pelo Android.
  • Um driver Vulkan, fornecido por SoCs como GPU IHVs, que implementa a API Vulkan . Para oferecer suporte à funcionalidade Vulkan, o dispositivo Android precisa de hardware de GPU compatível com Vulkan e do driver associado. A GPU também deve suportar GLES 3.1 e superior. Consulte seu fornecedor de SoC para solicitar suporte de driver.

Se um dispositivo inclui um driver Vulkan, o dispositivo precisa declarar os recursos do sistema FEATURE_VULKAN_HARDWARE_LEVEL e FEATURE_VULKAN_HARDWARE_VERSION , com versões que refletem com precisão os recursos do dispositivo. Isso ajuda a garantir que o dispositivo esteja em conformidade com o Documento de Definição de Compatibilidade (CDD).

carregador Vulkan

A platform/frameworks/native/vulkan do carregador Vulkan é a interface principal entre os aplicativos Vulkan e o driver Vulkan de um dispositivo. O carregador Vulkan está instalado em /system/lib[64]/libvulkan.so . O carregador fornece os principais pontos de entrada da API Vulkan, bem como os pontos de entrada das extensões exigidas pelo CDD do Android. As extensões Window System Integration (WSI) são exportadas pelo carregador e implementadas principalmente no carregador e não no driver. O carregador também oferece suporte à enumeração e carregamento de camadas que podem expor extensões adicionais e interceptar chamadas de API principais no caminho para o driver.

O NDK inclui uma biblioteca stub libvulkan.so para vinculação. A biblioteca exporta os mesmos símbolos que o carregador. Os aplicativos chamam as funções exportadas da biblioteca libvulkan.so real para inserir funções de trampolim no carregador, que despacham para a camada ou driver apropriado com base em seu primeiro argumento. A vkGet*ProcAddr() retorna os ponteiros de função para os quais os trampolins despacham (ou seja, ele chama diretamente no código da API principal). Chamar através dos ponteiros de função, em vez dos símbolos exportados, é mais eficiente, pois pula o trampolim e o despacho.

Enumeração e carregamento do driver

Quando a imagem do sistema é criada, o Android espera que o sistema saiba quais GPUs estão disponíveis. O carregador usa o mecanismo HAL existente em hardware.h para descobrir e carregar o driver. Os caminhos preferidos para drivers Vulkan de 32 e 64 bits são:

/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

No Android 7.0 e superior, o derivado Vulkan hw_module_t envolve uma única estrutura hw_module_t ; apenas um driver é suportado e a string constante HWVULKAN_DEVICE_0 é passada para open() .

O derivado Vulkan hw_device_t corresponde a um único driver que pode suportar vários dispositivos físicos. A estrutura hw_device_t pode ser estendida para exportar as vkGetGlobalExtensionProperties() , vkCreateInstance() e vkGetInstanceProcAddr() . O carregador pode localizar todas as outras VkInstance() , VkPhysicalDevice() e vkGetDeviceProcAddr() chamando hw_device_t vkGetInstanceProcAddr() .

Descoberta e carregamento de camadas

O carregador Vulkan oferece suporte à enumeração e carregamento de camadas que podem expor extensões adicionais e interceptar chamadas de API principais no caminho para o driver. O Android não inclui camadas na imagem do sistema; no entanto, os aplicativos podem incluir camadas em seu APK.

Ao usar camadas, lembre-se de que o modelo e as políticas de segurança do Android diferem significativamente de outras plataformas. Em particular, o Android não permite carregar código externo em um processo não depurável em dispositivos de produção (sem root), nem permite que código externo inspecione ou controle a memória, o estado e assim por diante do processo. Isso inclui a proibição de salvar dumps principais, rastreamentos de API e assim por diante no disco para inspeção posterior. Somente as camadas entregues como parte de aplicativos não depuráveis ​​são habilitadas em dispositivos de produção, e os drivers não devem fornecer funcionalidades que violem essas políticas.

Os casos de uso para camadas incluem:

  • Camadas de tempo de desenvolvimento — Camadas de validação e shims para ferramentas de rastreamento/criação de perfil/depuração não devem ser instaladas na imagem do sistema de dispositivos de produção. Camadas de validação e shims para ferramentas de rastreamento/criação de perfil/depuração devem ser atualizáveis ​​sem uma imagem do sistema. Os desenvolvedores que desejam usar uma dessas camadas durante o desenvolvimento podem modificar o pacote do aplicativo, por exemplo, adicionando um arquivo ao diretório de bibliotecas nativas. Presume-se que os engenheiros de IHV e OEM que desejam diagnosticar falhas no envio de aplicativos não modificáveis ​​tenham acesso a compilações de não produção (enraizadas) da imagem do sistema, a menos que esses aplicativos sejam depuráveis. Para obter mais informações, consulte camadas de validação Vulkan no Android .
  • Camadas de utilitário — Essas camadas expõem extensões, como uma camada que implementa um gerenciador de memória para a memória do dispositivo. Os desenvolvedores escolhem camadas e versões dessas camadas para usar em seu aplicativo; aplicativos diferentes que usam a mesma camada ainda podem usar versões diferentes. Os desenvolvedores escolhem quais dessas camadas devem ser enviadas em seu pacote de aplicativos.
  • Camadas injetadas (implícitas) — Inclui camadas como taxa de quadros, rede social e sobreposições do inicializador de jogos fornecidas pelo usuário ou algum outro aplicativo sem o conhecimento ou consentimento do aplicativo. Eles violam as políticas de segurança do Android e não são compatíveis.

Para aplicativos não depuráveis, o carregador procura camadas apenas no diretório de biblioteca nativa do aplicativo e tenta carregar qualquer biblioteca com um nome que corresponda a um padrão específico (por exemplo, libVKLayer_foo.so ).

Para aplicativos depuráveis, o carregador procura camadas em /data/local/debug/vulkan e tenta carregar qualquer biblioteca que corresponda a um padrão específico.

O Android permite que as camadas sejam portadas com alterações no ambiente de construção entre o Android e outras plataformas. Para obter detalhes sobre a interface entre as camadas e o carregador, consulte Arquitetura das interfaces do carregador Vulkan . As camadas de validação mantidas pelo Khronos são hospedadas nas camadas de validação do Vulkan .

Versões e recursos da API Vulkan

O Android 9 e superior são compatíveis com a API Vulkan versão 1.1. O Android 7 ao Android 9 é compatível com a API Vulkan versão 1.0. Para obter mais informações sobre a API Vulkan 1.1, consulte a especificação da API Vulkan 1.1 .

Visão geral do suporte ao Vulkan 1.1

O Vulkan 1.1 inclui suporte para interoperabilidade de memória/sincronização, o que permite que os OEMs ofereçam suporte ao Vulkan 1.1 em dispositivos. Além disso, a interoperabilidade de memória/sincronização permite que os desenvolvedores determinem se o Vulkan 1.1 é compatível com um dispositivo e o usem efetivamente quando for. O Vulkan 1.1 tem os mesmos requisitos de hardware do Vulkan 1.0, mas a maior parte da implementação está no driver gráfico específico do SOC, não na estrutura.

Os recursos mais importantes do Vulkan 1.1 para Android são:

  • Suporte para importação e exportação de buffers de memória e objetos de sincronização de fora do Vulkan (para interoperabilidade com câmera, codecs e GLES)
  • Suporte para formatos YCbCr

O Vulkan 1.1 também inclui vários recursos menores e aprimoramentos de usabilidade da API.

Implementando Vulkan 1.1

Os dispositivos Android devem oferecer suporte ao Vulkan 1.1 se:

  • Inicie com o Android 10.
  • Suporta uma ABI de 64 bits.
  • Não são memória baixa.

Outros dispositivos podem oferecer suporte opcional ao Vulkan 1.1.

Para implementar o Vulkan 1.1:

  1. Adicione um driver Vulkan compatível com Vulkan 1.1 mais os requisitos adicionais de CDD do Android 1.1 ou atualize o driver Vulkan 1.0 existente.
  2. Certifique-se de que PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x401000) retorne true adicionando uma regra como a seguinte a um arquivo device.mk apropriado:
    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
    

Integração do Sistema de Janelas (WSI)

Em libvulkan.so , o driver implementa as seguintes extensões Window System Integration (WSI):

  • VK_KHR_surface
  • VK_KHR_android_surface
  • VK_KHR_swapchain
  • VK_KHR_driver_properties , implementado para Vulkan 1.1 apenas no Android 10
  • VK_GOOGLE_display_timing , implementado para qualquer versão Vulkan no Android 10

Os objetos VkSurfaceKHR e VkSwapchainKHR e todas as interações com ANativeWindow são tratadas pela plataforma e não são expostas aos drivers. A implementação WSI depende da extensão VK_ANDROID_native_buffer , que deve ser suportada pelo driver; essa extensão é usada apenas pela implementação do WSI e não é exposta aos aplicativos.

Sinalizadores de uso do Gralloc

As implementações do Vulkan podem precisar que os buffers de swapchain sejam alocados com sinalizadores de uso Gralloc privados definidos pela implementação. Ao criar uma cadeia de troca, o Android pede ao driver para traduzir o formato solicitado e os sinalizadores de uso de imagem em sinalizadores de uso Gralloc chamando:

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
);

Os parâmetros format e imageUsage são obtidos da estrutura VkSwapchainCreateInfoKHR . O driver deve preencher *grallocConsumerUsage e *grallocProducerUsage com os sinalizadores de uso Gralloc necessários para o formato e uso. Os sinalizadores de uso retornados pelo driver são combinados com os sinalizadores de uso solicitados pelo consumidor da cadeia de troca ao alocar buffers.

O Android 7.x chama uma versão anterior de VkSwapchainImageUsageFlagsANDROID() , chamada vkGetSwapchainGrallocUsageANDROID() . O Android 8.0 e superior descontinua vkGetSwapchainGrallocUsageANDROID() , mas ainda chama vkGetSwapchainGrallocUsageANDROID() se vkGetSwapchainGrallocUsage2ANDROID() não for fornecido pelo driver:

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

vkGetSwapchainGrallocUsageANDROID() não oferece suporte a sinalizadores de uso de cadeia de troca ou sinalizadores de uso estendido de Gralloc.

Imagens apoiadas por Gralloc

VkNativeBufferANDROID é uma estrutura de extensão vkCreateImage para criar uma imagem apoiada por um buffer Gralloc. VkNativeBufferANDROID é fornecido para vkCreateImage() na cadeia de estrutura VkImageCreateInfo . As chamadas para vkCreateImage() com VkNativeBufferANDROID acontecem durante a chamada para vkCreateSwapchainKHR . A implementação WSI aloca o número de buffers nativos solicitados para o swapchain e cria uma VkImage para cada um:

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;

Ao criar uma imagem baseada em Gralloc, VkImageCreateInfo tem os seguintes dados:

  .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

No Android 8.0 e superior, a plataforma fornece uma estrutura de extensão VkSwapchainImageCreateInfoKHR na cadeia VkImageCreateInfo fornecida a vkCreateImage quando qualquer sinalização de uso de imagem da cadeia de troca é necessária para a cadeia de troca. A estrutura de extensão contém os sinalizadores de uso da imagem da cadeia de troca:

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

    VkSwapchainImageUsageFlagsANDROID      usage;
} VkSwapchainImageCreateInfoANDROID;

No Android 10 e superior, a plataforma oferece suporte a VK_KHR_swapchain v70, portanto, o aplicativo Vulkan pode criar uma VkImage com suporte de memória swapchain. O aplicativo primeiro chama vkCreateImage com uma estrutura VkImageSwapchainCreateInfoKHR encadeada à estrutura VkImageCreateInfo . Em seguida, o aplicativo chama vkBindImageMemory2(KHR) com uma estrutura VkBindImageMemorySwapchainInfoKHR encadeada à estrutura VkBindImageMemoryInfo . O imageIndex especificado na estrutura VkBindImageMemorySwapchainInfoKHR deve ser um índice de imagem de cadeia de troca válido. Enquanto isso, a plataforma fornece uma estrutura de extensão VkNativeBufferANDROID com as informações de buffer Gralloc correspondentes à cadeia VkBindImageMemoryInfo , para que o driver saiba com qual buffer Gralloc associar o VkImage .

Adquirindo imagens

vkAcquireImageANDROID adquire a propriedade de uma imagem de cadeia de troca e importa uma cerca nativa sinalizada externamente para um objeto VkSemaphore existente e um objeto VkFence existente:

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

vkAcquireImageANDROID() é chamado durante vkAcquireNextImageKHR para importar um fence nativo para os objetos VkSemaphore e VkFence fornecidos pelo aplicativo (no entanto, os objetos semáforo e fence são opcionais nesta chamada). O driver também pode usar esta oportunidade para reconhecer e manipular quaisquer alterações externas no estado do buffer Gralloc; muitos motoristas não precisarão fazer nada aqui. Essa chamada coloca o VkSemaphore e o VkFence no mesmo estado pendente como se fosse sinalizado por vkQueueSubmit , para que as filas possam aguardar no semáforo e o aplicativo possa aguardar em cima do muro.

Ambos os objetos são sinalizados quando a cerca nativa subjacente sinaliza; se a cerca nativa já tiver sinalizado, o semáforo estará no estado sinalizado quando esta função retornar. O driver se apropria do descritor de arquivo fence e fecha o descritor de arquivo fence quando não for mais necessário. O driver deve fazer isso mesmo se nenhum semáforo ou objeto fence for fornecido, ou mesmo se vkAcquireImageANDROID falhar e retornar um erro. Se fenceFd for -1, é como se o fence nativo já estivesse sinalizado.

Liberando imagens

vkQueueSignalReleaseImageANDROID prepara uma imagem de cadeia de troca para uso externo, cria uma cerca nativa e agenda a cerca nativa para ser sinalizada após os semáforos de entrada terem sinalizado:

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

vkQueuePresentKHR() chama vkQueueSignalReleaseImageANDROID() na fila fornecida. O driver deve produzir uma cerca nativa que não sinaliza até que todos os semáforos waitSemaphoreCount no sinal pWaitSemaphores e qualquer trabalho adicional necessário para preparar a image para apresentação seja concluído.

Se os semáforos de espera (se houver) já estiverem sinalizados e a queue já estiver ociosa, o driver poderá definir *pNativeFenceFd como -1 em vez de um descritor de arquivo fence nativo real, indicando que não há nada para esperar. O chamador possui e fecha o descritor de arquivo retornado em *pNativeFenceFd .

Muitos drivers podem ignorar o parâmetro de imagem, mas alguns podem precisar preparar estruturas de dados do lado da CPU associadas a um buffer Gralloc para uso por consumidores de imagem externos. A preparação do conteúdo do buffer para uso por consumidores externos deve ser feita de forma assíncrona como parte da transição da imagem para VK_IMAGE_LAYOUT_PRESENT_SRC_KHR .

Se a imagem foi criada com VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID , o driver deve permitir que vkQueueSignalReleaseImageANDROID() seja chamado repetidamente sem intervenções de chamadas para vkAcquireImageANDROID() .

Suporte de imagem apresentável compartilhada

Alguns dispositivos podem compartilhar a propriedade de uma única imagem entre o pipeline de exibição e a implementação do Vulkan para minimizar a latência. No Android 9 e superior, o carregador anuncia condicionalmente a extensão VK_KHR_shared_presentable_image com base na resposta do driver a uma chamada para vkGetPhysicalDeviceProperties2 .

Se o driver não for compatível com a extensão Vulkan 1.1 ou VK_KHR_physical_device_properties2 , o carregador não anunciará suporte para imagens apresentáveis ​​compartilhadas. Caso contrário, o carregador consulta os recursos do driver chamando vkGetPhysicalDeviceProperties2() e incluindo a seguinte estrutura na cadeia VkPhysicalDeviceProperties2::pNext :

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

Se o driver puder compartilhar a propriedade de uma imagem com o sistema de exibição, ele definirá o membro sharedImage como VK_TRUE .

Validação

Os OEMs podem testar sua implementação Vulkan usando o CTS, que inclui:

  • Testes de conformidade Khronos Vulkan no módulo CtsDeqpTestCases , que incluem testes de API funcionais para Vulkan 1.0 e 1.1.
  • O módulo CtsGraphicsTestCases , que testa se o dispositivo está configurado corretamente para os recursos do Vulkan que ele suporta.

Bandeira de recurso Vulkan

Um dispositivo compatível com Android 11 ou superior e compatível com a API Vulkan precisa expor um sinalizador de recurso, android.software.vulkan.deqp.level . O valor deste sinalizador de recurso é uma data, codificada como um valor inteiro. Ele especifica a data associada aos testes Vulkan dEQP que o dispositivo afirma ser aprovado.

Uma data no formato AAAA-MM-DD é codificada como um inteiro de 32 bits da seguinte forma:

  • Bits 0-15 armazenam o ano
  • Bits 16-23 armazenam o mês
  • Bits 24-31 armazenam o dia

O valor mínimo permitido para o sinalizador de recurso é 0x07E30301 , que corresponde à data 2019-03-01, que é a data associada aos testes Vulkan dEQP para Android 10. Se o sinalizador de recurso for pelo menos esse valor, o dispositivo afirma passar em todos os testes do Android 10 Vulkan dEQP.

O valor 0x07E40301 corresponde à data 2020-03-01, que é a data associada aos testes Vulkan dEQP para Android 11. Se o sinalizador de recurso for pelo menos esse valor, o dispositivo afirma passar em todos os testes Android 11 Vulkan dEQP.

Se o valor do sinalizador de recurso for pelo menos 0x07E30301 , mas menor que 0x07E40301 , isso significa que o dispositivo afirma passar em todos os testes do Android 10 Vulkan dEQP, mas não é garantido que ele passe nos testes Vulkan dEQP que foram adicionados para o Android 11.

Vulkan dEQP faz parte do Android CTS. A partir do Android 11, o componente executor de teste dEQP do CTS está ciente do sinalizador de recurso android.software.vulkan.deqp.level e ignora todos os testes Vulkan dEQP que - de acordo com esse sinalizador de recurso - o dispositivo não afirma ser compatível. Tais testes são relatados como trivialmente aprovados.