Implémentation de Vulkan

Vulkan est une API multiplateforme à faible surcharge pour les graphiques 3D hautes performances. Comme OpenGL ES (GLES) , Vulkan fournit des outils pour créer des graphiques en temps réel de haute qualité dans les applications. Les avantages de l'utilisation de Vulkan incluent la réduction de la surcharge du processeur et la prise en charge du langage SPIR-V Binary Intermediate .

Pour implémenter Vulkan avec succès, un appareil doit inclure :

  • Le chargeur Vulkan, fourni par Android.
  • Un pilote Vulkan, fourni par des SoC tels que les GPU IHV, qui implémente l' API Vulkan . Pour prendre en charge la fonctionnalité Vulkan, l'appareil Android a besoin d'un matériel GPU compatible Vulkan et du pilote associé. Le GPU doit également prendre en charge GLES 3.1 et supérieur. Consultez votre fournisseur de SoC pour demander l'assistance du pilote.

Si un appareil inclut un pilote Vulkan, l'appareil doit déclarer les fonctionnalités système FEATURE_VULKAN_HARDWARE_LEVEL et FEATURE_VULKAN_HARDWARE_VERSION , avec des versions qui reflètent avec précision les capacités de l'appareil. Cela permet de s'assurer que l'appareil est conforme au document de définition de compatibilité (CDD).

Chargeur Vulkan

La platform/frameworks/native/vulkan du chargeur Vulkan est l'interface principale entre les applications Vulkan et le pilote Vulkan d'un appareil. Le chargeur Vulkan est installé dans /system/lib[64]/libvulkan.so . Le chargeur fournit les principaux points d'entrée de l'API Vulkan, ainsi que les points d'entrée des extensions requises par le CDD Android. Les extensions WSI (Window System Integration) sont exportées par le chargeur et principalement implémentées dans le chargeur plutôt que dans le pilote. Le chargeur prend également en charge l'énumération et le chargement des couches qui peuvent exposer des extensions supplémentaires et intercepter les appels de l'API principale sur leur chemin vers le pilote.

Le NDK inclut une bibliothèque stub libvulkan.so pour la liaison. La bibliothèque exporte les mêmes symboles que le chargeur. Les applications appellent les fonctions exportées depuis la vraie bibliothèque libvulkan.so pour entrer des fonctions de trampoline dans le chargeur, qui sont distribuées à la couche ou au pilote approprié en fonction de leur premier argument. L' vkGet*ProcAddr() renvoie les pointeurs de fonction vers lesquels les trampolines sont envoyés (c'est-à-dire qu'il appelle directement le code de l'API principale). L'appel via les pointeurs de fonction, plutôt que les symboles exportés, est plus efficace car il saute le trampoline et la répartition.

Énumération et chargement des pilotes

Lorsque l'image système est créée, Android s'attend à ce que le système sache quels GPU sont disponibles. Le chargeur utilise le mécanisme HAL existant dans hardware.h pour découvrir et charger le pilote. Les chemins préférés pour les pilotes Vulkan 32 bits et 64 bits sont :

/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

Dans Android 7.0 et versions ultérieures, le dérivé Vulkan hw_module_t une seule structure hw_module_t ; un seul pilote est pris en charge et la chaîne constante HWVULKAN_DEVICE_0 est transmise à open() .

Le dérivé Vulkan hw_device_t correspond à un pilote unique pouvant prendre en charge plusieurs périphériques physiques. La structure hw_device_t peut s'étendre pour exporter les vkGetGlobalExtensionProperties() , vkCreateInstance() et vkGetInstanceProcAddr() . Le chargeur peut trouver toutes les autres VkInstance() , VkPhysicalDevice() et vkGetDeviceProcAddr() en appelant hw_device_t () de la structure vkGetInstanceProcAddr() .

Découverte et chargement des calques

Le chargeur Vulkan prend en charge l'énumération et le chargement des couches qui peuvent exposer des extensions supplémentaires et intercepter les appels de l'API principale sur leur chemin vers le pilote. Android n'inclut pas de couches sur l'image système ; cependant, les applications peuvent inclure des couches dans leur APK.

Lorsque vous utilisez des couches, gardez à l'esprit que le modèle et les politiques de sécurité d'Android diffèrent considérablement des autres plates-formes. En particulier, Android n'autorise pas le chargement de code externe dans un processus non débogable sur des appareils de production (non rootés), et n'autorise pas non plus le code externe à inspecter ou contrôler la mémoire, l'état, etc. du processus. Cela inclut une interdiction d'enregistrer des vidages mémoire, des traces d'API, etc. sur le disque pour une inspection ultérieure. Seules les couches fournies dans le cadre d'applications non débogables sont activées sur les appareils de production, et les pilotes ne doivent pas fournir de fonctionnalités qui enfreignent ces politiques.

Les cas d'utilisation des calques incluent :

  • Couches de développement — Les couches de validation et les shims pour les outils de traçage/profilage/débogage ne doivent pas être installés sur l'image système des appareils de production. Les couches de validation et les shims pour les outils de traçage/profilage/débogage doivent pouvoir être mis à jour sans image système. Les développeurs qui souhaitent utiliser l'une de ces couches lors du développement peuvent modifier le package de l'application, par exemple en ajoutant un fichier à leur répertoire de bibliothèques natives. Les ingénieurs IHV et OEM qui souhaitent diagnostiquer les échecs lors de l'expédition d'applications non modifiables sont supposés avoir accès aux versions de non-production (enracinées) de l'image système, à moins que ces applications ne soient déboguables. Pour plus d'informations, consultez Couches de validation Vulkan sur Android .
  • Couches utilitaires — Ces couches exposent des extensions, telles qu'une couche qui implémente un gestionnaire de mémoire pour la mémoire de l'appareil. Les développeurs choisissent les couches et les versions de ces couches à utiliser dans leur application ; différentes applications utilisant la même couche peuvent toujours utiliser des versions différentes. Les développeurs choisissent laquelle de ces couches doit être livrée dans leur package d'application.
  • Couches injectées (implicites) - Inclut des couches telles que la fréquence d'images, le réseau social et les superpositions de lanceur de jeu fournies par l'utilisateur ou une autre application à l'insu de l'application ou sans son consentement. Ceux-ci violent les politiques de sécurité d'Android et ne sont pas pris en charge.

Pour les applications non débogables, le chargeur recherche les couches uniquement dans le répertoire de la bibliothèque native de l'application et tente de charger toute bibliothèque dont le nom correspond à un modèle particulier (par exemple, libVKLayer_foo.so ).

Pour les applications débogables, le chargeur recherche des couches dans /data/local/debug/vulkan et tente de charger toute bibliothèque correspondant à un modèle particulier.

Android permet aux couches d'être portées avec des changements d'environnement de construction entre Android et d'autres plates-formes. Pour plus de détails sur l'interface entre les couches et le chargeur, consultez Architecture of the Vulkan Loader Interfaces . Les couches de validation maintenues par Khronos sont hébergées dans Vulkan Validation Layers .

Versions et capacités de l'API Vulkan

Android 9 et supérieur prennent en charge l'API Vulkan version 1.1. Android 7 à Android 9 prennent en charge l'API Vulkan version 1.0. Pour plus d'informations sur l'API Vulkan 1.1, consultez la spécification de l'API Vulkan 1.1 .

Présentation de la prise en charge de Vulkan 1.1

Vulkan 1.1 inclut la prise en charge de l'interopérabilité mémoire/synchronisation, ce qui permet aux OEM de prendre en charge Vulkan 1.1 sur les appareils. De plus, l'interopérabilité mémoire/synchronisation permet aux développeurs de déterminer si Vulkan 1.1 est pris en charge sur un appareil et de l'utiliser efficacement lorsqu'il l'est. Vulkan 1.1 a les mêmes exigences matérielles que Vulkan 1.0, mais la majeure partie de l'implémentation se trouve dans le pilote graphique spécifique au SOC, pas dans le framework.

Les fonctionnalités les plus importantes de Vulkan 1.1 pour Android sont :

  • Prise en charge de l'importation et de l'exportation de mémoires tampons et d'objets de synchronisation depuis l'extérieur de Vulkan (pour l'interopérabilité avec la caméra, les codecs et GLES)
  • Prise en charge des formats YCbCr

Vulkan 1.1 inclut également plusieurs fonctionnalités plus petites et améliorations de la convivialité de l'API.

Implémentation de Vulkan 1.1

Les appareils Android doivent prendre en charge Vulkan 1.1 s'ils :

  • Lancez avec Android 10.
  • Prend en charge un ABI 64 bits.
  • Ne sont pas à faible mémoire.

D'autres appareils peuvent éventuellement prendre en charge Vulkan 1.1.

Pour implémenter Vulkan 1.1 :

  1. Ajoutez un pilote Vulkan qui prend en charge Vulkan 1.1 plus les exigences CDD supplémentaires d'Android 1.1, ou mettez à jour le pilote Vulkan 1.0 existant.
  2. Assurez-vous que PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x401000) renvoie true en ajoutant une règle telle que la suivante à un fichier device.mk approprié :
    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
    

Intégration du système de fenêtres (WSI)

Dans libvulkan.so , le pilote implémente les extensions WSI (Windows System Integration) suivantes :

  • VK_KHR_surface
  • VK_KHR_android_surface
  • VK_KHR_swapchain
  • VK_KHR_driver_properties , implémenté pour Vulkan 1.1 dans Android 10 uniquement
  • VK_GOOGLE_display_timing , implémenté pour toute version de Vulkan dans Android 10

Les objets VkSurfaceKHR et VkSwapchainKHR et toutes les interactions avec ANativeWindow sont gérés par la plateforme et ne sont pas exposés aux pilotes. L'implémentation WSI repose sur l'extension VK_ANDROID_native_buffer , qui doit être prise en charge par le pilote ; cette extension est utilisée uniquement par l'implémentation WSI et n'est pas exposée aux applications.

Indicateurs d'utilisation de Gralloc

Les implémentations de Vulkan peuvent nécessiter que des tampons de chaîne d'échange soient alloués avec des indicateurs d'utilisation privés de Gralloc définis par l'implémentation. Lors de la création d'une swapchain, Android demande au pilote de traduire les drapeaux d'utilisation de format et d'image demandés en drapeaux d'utilisation de Gralloc en appelant :

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

Les paramètres format et imageUsage sont extraits de la structure VkSwapchainCreateInfoKHR . Le pilote doit remplir *grallocConsumerUsage et *grallocProducerUsage avec les indicateurs d'utilisation Gralloc requis pour le format et l'utilisation. Les indicateurs d'utilisation renvoyés par le pilote sont combinés avec les indicateurs d'utilisation demandés par le consommateur de la chaîne d'échange lors de l'allocation des tampons.

Android 7.x appelle une version antérieure de VkSwapchainImageUsageFlagsANDROID() , nommée vkGetSwapchainGrallocUsageANDROID() . Android 8.0 et supérieur déprécie vkGetSwapchainGrallocUsageANDROID() mais appelle toujours vkGetSwapchainGrallocUsageANDROID() si vkGetSwapchainGrallocUsage2ANDROID() n'est pas fourni par le pilote :

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

vkGetSwapchainGrallocUsageANDROID() ne prend pas en charge les drapeaux d'utilisation de la chaîne d'échange ou les drapeaux d'utilisation étendus de Gralloc.

Images soutenues par Gralloc

VkNativeBufferANDROID est une structure d'extension vkCreateImage pour créer une image soutenue par un tampon Gralloc. VkNativeBufferANDROID est fourni à vkCreateImage() dans la chaîne de structure VkImageCreateInfo . Les appels à vkCreateImage() avec VkNativeBufferANDROID se produisent pendant l'appel à vkCreateSwapchainKHR . L'implémentation WSI alloue le nombre de tampons natifs demandés pour la swapchain, puis crée un VkImage pour chacun :

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;

Lors de la création d'une image basée sur Gralloc, VkImageCreateInfo contient les données suivantes :

  .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

Dans Android 8.0 et versions ultérieures, la plate-forme fournit une structure d'extension VkSwapchainImageCreateInfoKHR dans la chaîne VkImageCreateInfo fournie à vkCreateImage lorsque des indicateurs d'utilisation d'image de chaîne d'échange sont requis pour la chaîne d'échange. La structure d'extension contient les indicateurs d'utilisation de l'image de la chaîne d'échange :

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

    VkSwapchainImageUsageFlagsANDROID      usage;
} VkSwapchainImageCreateInfoANDROID;

Dans Android 10 et versions ultérieures, la plate-forme prend en charge VK_KHR_swapchain v70, de sorte que l'application Vulkan est capable de créer un VkImage soutenu par la mémoire swapchain. L'application appelle d'abord vkCreateImage avec une structure VkImageSwapchainCreateInfoKHR chaînée à la structure VkImageCreateInfo . Ensuite, l'application appelle vkBindImageMemory2(KHR) avec une structure VkBindImageMemorySwapchainInfoKHR chaînée à la structure VkBindImageMemoryInfo . L' imageIndex spécifié dans la structure VkBindImageMemorySwapchainInfoKHR doit être un index d'image de chaîne d'échange valide. Pendant ce temps, la plate-forme fournit une structure d'extension VkNativeBufferANDROID avec les informations de tampon Gralloc correspondantes à la chaîne VkBindImageMemoryInfo , afin que le pilote sache avec quel tampon Gralloc lier le VkImage .

Acquisition d'images

vkAcquireImageANDROID acquiert la propriété d'une image de chaîne d'échange et importe une clôture native signalée de manière externe à la fois dans un objet VkSemaphore existant et dans un objet VkFence existant :

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

vkAcquireImageANDROID() est appelé pendant vkAcquireNextImageKHR pour importer une clôture native dans les objets VkSemaphore et VkFence fournis par l'application (cependant, les objets sémaphore et clôture sont facultatifs dans cet appel). Le pilote peut également utiliser cette opportunité pour reconnaître et gérer toute modification externe de l'état du tampon Gralloc ; de nombreux conducteurs n'auront rien à faire ici. Cet appel place VkSemaphore et VkFence dans le même état d'attente que s'il était signalé par vkQueueSubmit , de sorte que les files d'attente peuvent attendre sur le sémaphore et l'application peut attendre sur la clôture.

Les deux objets sont signalés lorsque la clôture native sous-jacente signale ; si la clôture native a déjà signalé, alors le sémaphore est dans l'état signalé lorsque cette fonction revient. Le pilote s'approprie le descripteur de fichier fence et ferme le descripteur de fichier fence lorsqu'il n'est plus nécessaire. Le pilote doit le faire même si aucun objet sémaphore ou clôture n'est fourni, ou même si vkAcquireImageANDROID échoue et renvoie une erreur. Si fenceFd vaut -1, c'est comme si la clôture native était déjà signalée.

Libérer des images

vkQueueSignalReleaseImageANDROID prépare une image de chaîne d'échange pour une utilisation externe, crée une clôture native et planifie la clôture native à signaler après que les sémaphores d'entrée ont signalé :

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

vkQueuePresentKHR() appelle vkQueueSignalReleaseImageANDROID() sur la file d'attente fournie. Le pilote doit produire une clôture native qui ne signale pas tant que tous les sémaphores waitSemaphoreCount dans pWaitSemaphores signalé et que tout travail supplémentaire requis pour préparer image pour la présentation n'est pas terminé.

Si les sémaphores d'attente (le cas échéant) ont déjà été signalés et que queue est déjà inactive, le pilote peut définir *pNativeFenceFd sur -1 au lieu d'un descripteur de fichier de clôture natif réel, indiquant qu'il n'y a rien à attendre. L'appelant possède et ferme le descripteur de fichier renvoyé dans *pNativeFenceFd .

De nombreux pilotes peuvent ignorer le paramètre d'image, mais certains peuvent avoir besoin de préparer des structures de données côté CPU associées à un tampon Gralloc pour une utilisation par des consommateurs d'images externes. La préparation du contenu du tampon pour une utilisation par des consommateurs externes doit être effectuée de manière asynchrone dans le cadre de la transition de l'image vers VK_IMAGE_LAYOUT_PRESENT_SRC_KHR .

Si l'image a été créée avec VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID , le pilote doit permettre à vkQueueSignalReleaseImageANDROID() d'être appelé à plusieurs reprises sans appels intermédiaires à vkAcquireImageANDROID() .

Prise en charge des images présentables partagées

Certains appareils peuvent partager la propriété d'une seule image entre le pipeline d'affichage et l'implémentation Vulkan afin de minimiser la latence. Dans Android 9 et versions ultérieures, le chargeur annonce conditionnellement l'extension VK_KHR_shared_presentable_image en fonction de la réponse du pilote à un appel à vkGetPhysicalDeviceProperties2 .

Si le pilote ne prend pas en charge Vulkan 1.1 ou l'extension VK_KHR_physical_device_properties2 , le chargeur n'annonce pas la prise en charge des images présentables partagées. Sinon, le chargeur interroge les capacités du pilote en appelant vkGetPhysicalDeviceProperties2() et en incluant la structure suivante dans la chaîne VkPhysicalDeviceProperties2::pNext :

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

Si le pilote peut partager la propriété d'une image avec le système d'affichage, il définit le membre sharedImage sur VK_TRUE .

Validation

Les OEM peuvent tester leur implémentation Vulkan à l'aide de CTS, qui comprend :

  • Tests de conformité Khronos Vulkan dans le module CtsDeqpTestCases , qui incluent des tests API fonctionnels pour Vulkan 1.0 et 1.1.
  • Le module CtsGraphicsTestCases , qui teste que l'appareil est correctement configuré pour les fonctionnalités Vulkan qu'il prend en charge.

Drapeau de fonctionnalité Vulkan

Un appareil qui prend en charge Android 11 ou supérieur et qui prend en charge l'API Vulkan est nécessaire pour exposer un indicateur de fonctionnalité, android.software.vulkan.deqp.level . La valeur de cet indicateur de fonctionnalité est une date, codée sous la forme d'une valeur entière. Il précise la date associée aux tests Vulkan dEQP que l'appareil prétend réussir.

Une date au format AAAA-MM-JJ est codée sous la forme d'un entier 32 bits comme suit :

  • Les bits 0-15 stockent l'année
  • Les bits 16-23 stockent le mois
  • Les bits 24 à 31 stockent le jour

La valeur minimale autorisée pour l'indicateur de fonctionnalité est 0x07E30301 , ce qui correspond à la date du 01/03/2019, qui est la date associée aux tests Vulkan dEQP pour Android 10. Si l'indicateur de fonctionnalité est au moins égal à cette valeur, l'appareil prétend réussir tous les tests Android 10 Vulkan dEQP.

La valeur 0x07E40301 correspond à la date 2020-03-01, qui est la date associée aux tests Vulkan dEQP pour Android 11. Si l'indicateur de fonctionnalité est au moins égal à cette valeur, l'appareil prétend réussir tous les tests Android 11 Vulkan dEQP.

Si la valeur de l'indicateur de fonctionnalité est au moins 0x07E30301 mais inférieure à 0x07E40301 , cela signifie que l'appareil prétend réussir tous les tests Android 10 Vulkan dEQP mais n'est pas garanti pour réussir les tests Vulkan dEQP qui ont été ajoutés pour Android 11.

Vulkan dEQP fait partie d'Android CTS. À partir d'Android 11, le composant d'exécution de test dEQP de CTS est conscient de l'indicateur de fonctionnalité android.software.vulkan.deqp.level et ignore tous les tests Vulkan dEQP que - selon cet indicateur de fonctionnalité - l'appareil ne prétend pas prendre en charge. De tels tests sont rapportés comme passant trivialement.