实现 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() 调用会返回 trampoline 将调度到的函数指针(即它会直接调用核心 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 版本的 Vulkan API 版本。
Android 版本 Vulkan 版本
Android 13 Vulkan 1.3
Android 9 Vulkan 1.1
Android 7 Vulkan 1.0

Vulkan 1.3 功能概览

Vulkan 1.3 将许多之前的可选扩展纳入到 Vulkan 核心功能中。添加这些功能的主要目的在于加强 Vulkan 编程接口的控制力和粒度。单通道渲染通道实例不再需要渲染通道对象或帧缓冲区。可减少管道状态对象的总数,而且 API 内的同步将得到全面修复。Vulkan 1.3 具有与 Vulkan 1.2、1.1 和 1.0 相同的硬件要求;大多数实现都在特定于 SoC 的图形驱动程序(而不是框架)中实现。

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

  • 支持单通道渲染通道实例
  • 支持立即终止着色器调用
  • 更精细地创建、共享和控制管道

Vulkan 1.3 还包含几项较小的功能和 API 易用性增强功能。次要修订版本 1.3 对核心 Vulkan API 所做的所有更改都可以在核心修订记录 (Vulkan 1.3)中找到。

Vulkan 1.2 功能概览

Vulkan 1.2 添加了许多可简化 API Surface 的功能和扩展。其中包括统一的内存模型以及可从设备驱动程序查询的其他信息。Vulkan 1.2 与 Vulkan 1.0 和 Vulkan 1.1 具有相同的硬件要求;所有实现都位于 SoC 专用图形驱动程序(而不是框架)中。

对于 Android,Vulkan 1.2 最重要的功能是支持 8 位存储。

Vulkan 1.2 还包含几项较小的功能和 API 易用性增强功能。次要修订版本 1.2 对核心 Vulkan API 所做的所有更改都可以在核心修订记录 (Vulkan 1.2)中找到。

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 易用性增强功能。次要修订版本 1.1 对核心 Vulkan API 所做的所有更改都可以在核心修订记录 (Vulkan 1.1)中找到。

选择 Vulkan 支持

Android 设备应支持可用的最高级 Vulkan 功能集,前提是它们支持 64 位 ABI 且不是低内存设备。

搭载 Android 13 及更高版本的设备应支持 Vulkan 1.3。

搭载 Android 10 的设备应支持 Vulkan 1.1。

其他设备可以选择支持 Vulkan 1.3、1.2 和 1.1。

支持 Vulkan 版本

如果满足以下条件,Android 设备将支持 Vulkan 版本:

  1. 添加支持所关注 Vulkan 版本的 Vulkan 驱动程序(必须为 Vulkan 1.3、1.1 或 1.0 版之一),以及 Android 版本的其他 CDD 要求。或者,您也可以更新某个较低 Vulkan 版本号的现有 Vulkan 驱动程序。
  2. 对于 Vulkan 1.3 或 1.1,请确保软件包管理器返回的系统功能针对正确的 Vulkan 版本返回 true
    • 对于 Vulkan 1.3,该功能为 PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x403000)
    • 对于 Vulkan 1.1,该功能为 PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x401000)
    针对 Vulkan 1.3 和 Vulkan 1.1,软件包管理器将通过向适当的 device.mk 文件添加规则(如下所示)返回 true
    • 对于 Vulkan 1.3,请添加以下规则:
      PRODUCT_COPY_FILES += frameworks/native/data/etc/android.hardware.vulkan.version-1_3.xml:
      $(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.vulkan.version.xml
      
    • 对于 Vulkan 1.1,请添加以下规则:
      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()。对具有 VkNativeBufferANDROIDvkCreateImage() 的调用发生在调用 vkCreateSwapchainKHR 时。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               = VkSwapchainCreateInfoKHR::imageUsage
  .flags               = 0
  .sharingMode         = VkSwapchainCreateInfoKHR::imageSharingMode
  .queueFamilyCount    = VkSwapchainCreateInfoKHR::queueFamilyIndexCount
  .pQueueFamilyIndices = VkSwapchainCreateInfoKHR::pQueueFamilyIndices

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

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 置于与 vkQueueSubmit 所指示信号相同的待处理状态,因此队列可以等待信号量,应用可以等待栅栏。

当底层的原生栅栏发出信号时,两个对象都处于有信号量的状态;如果原生栅栏已经处于有信号量状态,则当该函数返回时,信号量也处于有信号量状态。驱动程序拥有栅栏文件描述符的所有权,并在其不再需要时关闭栅栏文件描述符。即使没有提供信号量或栅栏对象,或者即使 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、Vulkan 1.2 和 Vulkan 1.3 的 API 功能测试。
  • CtsGraphicsTestCases 模块,用于测试设备是否针对所支持的 Vulkan 功能进行了正确的配置。

Vulkan 功能标志

支持 Android 11 或更高版本且支持 Vulkan API 的设备必须提供功能标志 android.software.vulkan.deqp.level。此功能标志的值为日期,以整数值表示。它指定与设备声明通过的 Vulkan dEQP 测试相关联的日期。

YYYY-MM-DD 格式的日期编码为一个 32 位的整数,如下所示:

  • 第 0-15 位存储年份
  • 第 16-23 位存储月份
  • 第 24-31 位存储日子

功能标志允许的最小值为 0x07E30301,它对应的日期是 2019-03-01,这是与 Android 10 的 Vulkan dEQP 测试相关联的日期。如果功能标志大于或等于此值,表示设备声明通过所有 Android 10 Vulkan dEQP 测试。

0x07E40301 对应的日期是 2020-03-01,这是与 Android 11 的 Vulkan dEQP 测试相关联的日期。如果功能标志大于或等于此值,表示设备声明通过所有 Android 11 Vulkan dEQP 测试。

0x07E60301 对应的日期是 2022-03-01,这是与 Android 13 的 Vulkan dEQP 测试相关联的日期。如果功能标志大于或等于此值,表示设备声明通过所有 Android 13 Vulkan dEQP 测试。

如果设备能提供一个特定的功能标志(即 0x07E303010x07E403010x07E60301),表示设备声明通过该功能标志所对应的所有 Android Vulkan dEQP 测试(分别为 Android 10、Android 11、Android 13)。此设备有可能通过更高 Android 版本的 Vulkan dEQP 测试。

Vulkan dEQP 是 Android CTS 的一部分。从 Android 11 开始,CTS 的 dEQP 测试运行程序组件知道 android.software.vulkan.deqp.level 功能标志,并且会根据此功能标志跳过设备未声明支持的所有 Vulkan dEQP 测试。此类测试在报告中显示为很少通过。