Google 致力于为黑人社区推动种族平等。查看具体举措

实现 Vulkan

Vulkan 是一套适用于高性能 3D 图形的低开销、跨平台 API。与 OpenGL ES (GLES) 一样,Vulkan 提供用于在应用中创建高品质实时图形的工具。使用 Vulkan 的优势包括降低 CPU 开销以及支持 SPIR-V 二进制中间语言。

要成功实现 Vulkan,设备必须包括:

  • 由 Android 提供的 Vulkan 加载程序。
  • 实现 Vulkan API 的 Vulkan 驱动程序(由 SoC 提供,如 GPU IHV)。为了支持 Vulkan 功能,Android 设备需要支持 Vulkan 的 GPU 硬件和相关驱动程序。GPU 还必须支持 GLES 3.1 及更高版本。请咨询您的 SoC 供应商,以请求获取驱动程序支持。

如果设备包含 Vulkan 驱动程序,则设备需要声明 FEATURE_VULKAN_HARDWARE_LEVELFEATURE_VULKAN_HARDWARE_VERSION 系统功能,并且相关版本能够准确反映设备的功能。这有助于确保设备符合兼容性定义文档 (CDD) 的要求。

Vulkan 加载程序

Vulkan 加载程序 platform/frameworks/native/vulkan 是 Vulkan 应用与设备的 Vulkan 驱动程序之间的主要接口。Vulkan 加载程序安装在 /system/lib[64]/libvulkan.so 中。该加载程序提供核心 Vulkan API 入口点,以及 Android CDD 必需的扩展的入口点。窗口系统集成 (WSI) 扩展由该加载程序导出,并主要在该加载程序(而不是驱动程序)中实现。此外,该加载程序还支持枚举和加载可显示其他扩展且在核心 API 调用到达驱动程序的途中对其进行拦截的层。

NDK 包括一个用于进行关联的存根 libvulkan.so 库。该库可导出与该加载程序相同的符号。应用会调用从真正的 libvulkan.so 库导出的函数,以进入该加载程序中的 trampoline 函数(根据其第一个参数分派到相应的层或驱动程序)。vkGet*ProcAddr() 调用会返回 trampolines 将分派到的函数指针(即它会直接调用核心 API 代码)。由于通过这些函数指针(而非导出的符号)进行调用跳过了 trampoline 和分派,因此其效率更高一些。

驱动程序枚举和加载

编译系统映像时,Android 希望系统知道哪些 GPU 可用。该加载程序使用 hardware.h 中的现有 HAL 机制来发现和加载驱动程序。32 位和 64 位 Vulkan 驱动程序的首选路径分别为:

/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

在 Android 7.0 及更高版本中,Vulkan hw_module_t 衍生封装一个 hw_module_t 结构;仅支持一个驱动程序,并将常量字符串 HWVULKAN_DEVICE_0 传递给 open()

Vulkan hw_device_t 衍生对应可支持多个物理设备的单个驱动程序。hw_device_t 结构可以进行扩展,以导出 vkGetGlobalExtensionProperties()vkCreateInstance()vkGetInstanceProcAddr() 函数。该加载程序可以通过调用 hw_device_t 结构的 vkGetInstanceProcAddr() 找到所有其他的 VkInstance()VkPhysicalDevice()vkGetDeviceProcAddr() 函数。

发现和加载层

Vulkan 加载程序支持枚举和加载可显示其他扩展且在核心 API 调用到达驱动程序的途中对其进行拦截的层。Android 在系统映像上不包含层;不过,应用可以在其 APK 中包含层。

使用层时请注意,Android 的安全模型和政策与其他平台截然不同。特别是,Android 不允许将外部代码加载到正式版(未取得 root 权限)设备上的不可调试进程中,也不允许外部代码检查或控制进程的内存、状态等。这包括禁止将核心转储、API 跟踪等保存到磁盘以供日后进行检查。只有作为不可调试应用一部分提交的层会在正式版设备上启用,而且驱动程序不得提供违反这些政策的功能。

层的使用情形包括:

  • 开发时间层 - 验证层以及用于跟踪/分析/调试工具的 Shim 层不得安装在正式版设备的系统映像上。验证层以及用于跟踪/分析/调试工具的 Shim 层应当可在无需系统映像的情况下进行更新。想要在开发过程中使用这些层之一的开发者可以修改应用包(例如,向其原生库目录中添加一个文件)。对于想要在即将推出的不可修改应用中诊断故障的 IHV 和原始设备制造商 (OEM) 工程师,假定其能够访问系统映像的非正式(已取得 root 权限)版本,除非这些应用可调试。如需了解详情,请参阅 Android 上的 Vulkan 验证层
  • 实用程序层 - 这些层会显示扩展,例如为设备内存实现内存管理器的层。开发者可选择要在其应用中使用的层(以及这些层的版本);使用相同层的不同应用仍可使用不同的版本。开发者可选择要在其应用包中包含哪些层。
  • 注入(隐式)层 - 在应用不知情或未经应用同意的情况下,包含用户或一些其他应用提供的层,如帧速率、社交网络和游戏启动器叠加层。这些违反了 Android 的安全政策,因此不受支持。

对于不可调试应用,该加载程序仅在应用的原生库目录中搜索层,并尝试加载任何名称符合特定格式(例如 libVKLayer_foo.so)的库。

对于可调试应用,该加载程序在 /data/local/debug/vulkan 中搜索层,并尝试加载任何符合特定格式的库。

Android 允许在 Android 与其他平台之间移植层(包括编译环境更改)。有关层与加载程序之间接口的详细信息,请参阅 Vulkan 加载程序接口的架构。Khronos 维护的验证层托管在 Vulkan 验证层中。

Vulkan API 版本和功能

Android 9 及更高版本支持 Vulkan API 1.1 版。Android 7 到 Android 9 支持 Vulkan API 1.0 版。如需详细了解 Vulkan 1.1 API,请参阅 Vulkan 1.1 API 规范

Vulkan 1.1 支持概览

Vulkan 1.1 包括对内存/同步交互操作的支持,使 OEM 能够在设备上支持 Vulkan 1.1。此外,通过内存/同步交互操作,开发者可以确定某个设备是否支持 Vulkan 1.1,并在支持时有效地使用该版本。Vulkan 1.1 具有与 Vulkan 1.0 相同的硬件要求,但相应实现大都位于特定于 SOC 的图形驱动程序(而不是框架)中。

对于 Android,最重要的 Vulkan 1.1 功能包括:

  • 支持从 Vulkan 外部导入和导出内存缓冲区和同步对象(以便与摄像头、编解码器和 GLES 进行交互操作)
  • 支持 YCbCr 格式

Vulkan 1.1 还包含几项较小的功能和 API 易用性增强功能。

实现 Vulkan 1.1

如果 Android 设备符合以下各项,则应支持 Vulkan 1.1:

  • 搭载 Android 10。
  • 支持 64 位 ABI。
  • 内存充足。

其他设备可以选择支持 Vulkan 1.1。

要实现 Vulkan 1.1,请执行以下操作:

  1. 添加支持 Vulkan 1.1 且符合其他 Android 1.1 CDD 要求的 Vulkan 驱动程序,或更新现有的 Vulkan 1.0 驱动程序。
  2. 确保 PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x401000) 返回 true,方法是将如下所示的规则添加到相应的 device.mk 文件:
    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
    

窗口系统集成 (WSI)

libvulkan.so 中,驱动程序实现以下窗口系统集成 (WSI) 扩展:

  • VK_KHR_surface
  • VK_KHR_android_surface
  • VK_KHR_swapchain
  • VK_KHR_driver_properties,仅适用于 Android 10 中的 Vulkan 1.1
  • VK_GOOGLE_display_timing,适用于 Android 10 中的任何 Vulkan 版本

VkSurfaceKHRVkSwapchainKHR 对象以及与 ANativeWindow 的所有互动都由平台处理,不会提供给驱动程序。WSI 实现依赖于必须受驱动程序支持的 VK_ANDROID_native_buffer 扩展;此扩展仅由 WSI 实现使用,不会提供给应用。

Gralloc 用途标记

Vulkan 实现可能需要使用由实现定义的私密 Gralloc 用途标记来分配交换链缓冲区。创建交换链时,Android 会要求驱动程序将请求的格式和图像用途标记转换为 Gralloc 用途标记,具体方法是调用以下内容:

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

formatimageUsage 参数来自 VkSwapchainCreateInfoKHR 结构。驱动程序应使用相关格式和用途所需的 Gralloc 用途标记来填充 *grallocConsumerUsage*grallocProducerUsage。驱动程序返回的用途标记将在系统分配缓冲区时与交换链使用方请求的用途标记配合使用。

Android 7.x 会调用 VkSwapchainImageUsageFlagsANDROID() 的早期版本,名为 vkGetSwapchainGrallocUsageANDROID()。Android 8.0 及更高版本已弃用 vkGetSwapchainGrallocUsageANDROID(),但是,如果驱动程序未提供 vkGetSwapchainGrallocUsage2ANDROID(),则仍会调用 vkGetSwapchainGrallocUsageANDROID()

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

vkGetSwapchainGrallocUsageANDROID() 不支持交换链用途标记或扩展 Gralloc 用途标记。

由 Gralloc 支持的图像

VkNativeBufferANDROID 是一个 vkCreateImage 扩展结构,用于创建由 Gralloc 缓冲区支持的图像。VkNativeBufferANDROID 提供给 VkImageCreateInfo 结构链中的 vkCreateImage()。对具有 VkNativeBufferANDROID 结构的 vkCreateImage() 的调用发生在第一次调用 vkGetSwapChainInfoWSI(.. VK_SWAP_CHAIN_INFO_TYPE_IMAGES_WSI ..) 期间。WSI 实现为交换链分配请求的原生缓冲区数,然后为每个缓冲区创建一个 VkImage

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;

创建由 Gralloc 支持的图像时,VkImageCreateInfo 具有以下数据:

  .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               = VkSwapChainCreateInfoWSI::imageUsageFlags
  .flags               = 0
  .sharingMode         = VkSwapChainCreateInfoWSI::sharingMode
  .queueFamilyCount    = VkSwapChainCreateInfoWSI::queueFamilyCount
  .pQueueFamilyIndices = VkSwapChainCreateInfoWSI::pQueueFamilyIndices

在 Android 8.0 及更高版本中,当交换链需要任何交换链图像用途标记时,则平台会在为 vkCreateImage 提供的 VkImageCreateInfo 链中提供 VkSwapchainImageCreateInfo 扩展结构。该扩展结构包含交换链图像用途标记:

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

    VkSwapchainImageUsageFlagsANDROID      usage;
} VkSwapchainImageCreateInfoANDROID;

在 Android 10 及更高版本中,平台支持 VK_KHR_swapchain v70,因此 Vulkan 应用能够创建由交换链内存支持的 VkImage。该应用首先调用 vkCreateImage,其 VkImageSwapchainCreateInfoKHR 结构已链接至 VkImageCreateInfo 结构。然后,该应用会调用 vkBindImageMemory2(KHR),其 VkBindImageMemorySwapchainInfoKHR 结构已链接到 VkBindImageMemoryInfo 结构。VkBindImageMemorySwapchainInfoKHR 结构中指定的 imageIndex 必须是有效的交换链图像索引。同时,平台为 VkBindImageMemoryInfo 链提供 VkNativeBufferANDROID 扩展结构,其中包含相应的 Gralloc 缓冲区信息,因此驱动程序知道要与 VkImage 绑定哪个 Gralloc 缓冲区。

获取图像

vkAcquireImageANDROID 会获取交换链图像的所有权,并将已收到外部信号的原生栅栏同时导入到现有的 VkSemaphore 对象和现有的 VkFence 对象:

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

vkAcquireImageANDROID()vkAcquireNextImageKHR 期间被调用,以将原生栅栏导入到应用提供的 VkSemaphoreVkFence 对象中(不过,在此调用中,信号量和栅栏对象均是可选的)。驱动程序可能也会借此机会识别和处理 Gralloc 缓冲区状态的所有外部更改;很多驱动程序在此无需进行任何操作。此调用会将 VkSemaphoreVkFence 分别置于与 vkQueueSignalSemaphorevkQueueSubmit 相同的待处理状态,因此队列可以等待信号量,应用可以等待栅栏。

当底层的原生栅栏发出信号时,两个对象都处于有信号量的状态;如果原生栅栏已经处于有信号量状态,则当该函数返回时,信号量也处于有信号量状态。驱动程序拥有栅栏文件描述符的所有权,并在其不再需要时关闭栅栏文件描述符。即使没有提供信号量或栅栏对象,或者即使 vkAcquireImageANDROID 失败并返回错误,驱动程序也必须这样做。如果 fenceFd 为 -1,即如同原生栅栏已经发出信号。

释放图像

vkQueueSignalReleaseImageANDROID 会准备交换链图像以供外部使用,创建一个原生栅栏,并安排其在输入信号量发出信号后收到信号:

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

vkQueuePresentKHR() 在提供的队列上调用 vkQueueSignalReleaseImageANDROID()。驱动程序必须生成一个原生栅栏,该原生栅栏会等待 pWaitSemaphores 中的所有 waitSemaphoreCount 信号量都发出信号,以及准备 image 以进行演示所需的额外工作都完成之后,然后再发出信号。

如果等待信号量(如果有)已经发出信号,并且 queue 已经处于空闲状态,则驱动程序可以将 *pNativeFenceFd 设置为 -1(而不是实际的原生栅栏文件描述符),表示无需等待。调用程序拥有 *pNativeFenceFd 中返回的文件描述符并会将其关闭。

很多驱动程序可以忽略图像参数,但有些驱动程序可能需要准备与 Gralloc 缓冲区相关联的 CPU 端数据结构,以供外部图像使用者使用。作为将图像转换为 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 的一个环节,准备外部使用者使用的缓冲区内容应该异步完成。

如果图像是使用 VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID 创建的,则驱动程序必须允许反复调用 vkQueueSignalReleaseImageANDROID(),而不会干预对 vkAcquireImageANDROID() 的调用。

共享的可呈现图像支持

某些设备可以在显示管道与 Vulkan 实现之间共享单个映像的所有权,以最大限度地缩短延迟。在 Android 9 及更高版本中,加载程序会根据驱动程序如何响应对 vkGetPhysicalDeviceProperties2 的调用来有条件地播发 VK_KHR_shared_presentable_image 扩展。

如果驱动程序不支持 Vulkan 1.1 或 VK_KHR_physical_device_properties2 扩展,则加载程序不会播发对共享可呈现图像的支持。否则,加载程序会通过调用 vkGetPhysicalDeviceProperties2() 并在 VkPhysicalDeviceProperties2::pNext 链中包含以下结构来查询驱动程序功能:

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

如果驱动程序能够与显示系统共享某个图像的所有权,则会将 sharedImage 成员设为 VK_TRUE

验证

OEM 可以使用 CTS 测试其 Vulkan 实现,其中包括:

  • CtsDeqpTestCases 模块中的 Khronos Vulkan 一致性测试,其中包括针对 Vulkan 1.0 和 Vulkan 1.1 的 API 功能测试。
  • CtsGraphicsTestCases 模块,用于测试设备是否针对所支持的 Vulkan 功能进行了正确的配置。