Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

Vulkan の実装

Vulkan は、高パフォーマンスの 3D グラフィックを実現する、低オーバーヘッドのクロス プラットフォーム API です。OpenGL ES(GLES)と同様に、Vulkan はアプリで高品質のリアルタイム グラフィックを作成するためのツールを提供します。Vulkan を使用するメリットとしては、CPU オーバーヘッドを削減できることや SPIR-V バイナリ中間言語をサポートしていることなどが挙げられます。

Vulkan を実装するには、デバイスに次のものが必要です。

  • Vulkan ローダー(Android で提供)
  • Vulkan ドライバ(Vulkan API を実装する GPU IHV などの SoC で提供)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 ライブラリからエクスポートされた関数を呼び出して、ローダーのトランポリン関数に入り、トランポリン関数は最初の引数に基づいて適切なレイヤまたはドライバにディスパッチします。vkGet*ProcAddr() の呼び出しは、トランポリンがディスパッチする先の関数ポインタを返します(つまりコア API コードを直接呼び出します)。エクスポートされたシンボルではなく、関数ポインタを使って呼び出すと、トランポリンとディスパッチが省略されるので効率的です。

ドライバの列挙と読み込み

システム イメージをビルドする際に、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 の派生 API は単一の hw_module_t 構造体をラップしています。1 つのドライバのみがサポートされ、定数文字列 HWVULKAN_DEVICE_0open() に渡されます。

Vulkan hw_device_t の派生 API は、複数の物理デバイスをサポートできる 1 つのドライバに対応します。hw_device_t 構造体を拡張して vkGetGlobalExtensionProperties()vkCreateInstance()vkGetInstanceProcAddr() の関数をエクスポートできます。ローダーは、hw_device_t 構造体の vkGetInstanceProcAddr() を呼び出すことにより、他のすべての VkInstance() 関数、VkPhysicalDevice() 関数、vkGetDeviceProcAddr() 関数を見つけることができます。

レイヤの検出と読み込み

Vulkan ローダーは、追加の拡張機能を提供したり、ドライバに渡る途中でコア API の呼び出しをインターセプトしたりできるレイヤの列挙と読み込みにも対応しています。Android ではシステム イメージにレイヤは含まれませんが、アプリでは APK にレイヤを含めることができます。

レイヤを使用する際には、Android のセキュリティ モデルとセキュリティ ポリシーが他のプラットフォームと大きく異なる点に注意してください。特に、Android では、外部コードを製品版の(ルート権限を取得していない)デバイスのデバッグ不可能なプロセスに読み込むことも、外部コードがプロセスのメモリや状態などを調査したり制御したりすることも許されません。後で調査するためにコアダンプ、API トレースなどをディスクに保存することも禁止されています。デバッグ不可能なアプリの一部として配信されるレイヤのみが製品版のデバイスで有効になります。ドライバがこれらのポリシーに違反する機能を提供してはなりません。

レイヤには次のユースケースがあります。

  • 開発時レイヤ - トレースツール、プロファイリング ツール、デバッグツール用の検証レイヤと shim は、製品版のシステム イメージにインストールすべきではありません。トレースツール、プロファイリング ツール、デバッグツール用の検証レイヤと shim は、システム イメージなしで更新できるようにしてください。開発中にこれらのレイヤのいずれかを使用するデベロッパーは、たとえばネイティブ ライブラリ ディレクトリにファイルを追加するなどして、アプリ パッケージを変更できます。変更不可能なアプリを配信する際にエラーを診断する IHV と OEM のエンジニアは、アプリがデバッグ可能でない限り、システム イメージの製品版でない(ルート権限を取得した)ビルドにアクセスできると想定されます。詳細については、Android の Vulkan 検証レイヤをご覧ください。
  • ユーティリティ レイヤ - デバイスメモリのメモリ マネージャーを実装するレイヤなどの拡張機能を公開します。デベロッパーは、アプリで使用するレイヤやそのバージョンを選択します。同じレイヤを使用する別のアプリでも異なるバージョンを使用できます。デベロッパーは、アプリ パッケージに同梱するレイヤを選択します。
  • 注入(非明示的)レイヤ - フレームレート、ソーシャル ネットワーク、アプリが認識または同意することなくユーザーや他のアプリによって提供されたゲーム ランチャー オーバーレイなどのレイヤです。これらは Android のセキュリティ ポリシーに違反しているためサポートされていません。

デバッグ不可能なアプリの場合、ローダーはアプリのネイティブ ライブラリ ディレクトリのみでレイヤを検索し、特定のパターンに一致する名前のライブラリを読み込みます(たとえば、libVKLayer_foo.so)。

デバッグ可能なアプリの場合、ローダーは /data/local/debug/vulkan でレイヤを検索し、特定のパターンに一致するライブラリを読み込みます。

Android では、Android と他のプラットフォームとの間でのビルド環境の変更を含めてレイヤを移植できます。レイヤとローダーとの間のインターフェースの詳細については、Architecture of the Vulkan Loader Interfaces をご覧ください。Khronos でメンテナンスされている検証レイヤは、Vulkan Validation Layers でホストされています。

Vulkan API のバージョンと機能

Android 9 以降では、Vulkan API バージョン 1.1 をサポートしています。Android 7 から Android 9 は、Vulkan API バージョン 1.0 をサポートしています。Vulkan 1.1 API の詳細については、Vulkan 1.1 API spec をご覧ください。

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. 次のようなルールを適切な device.mk ファイルに追加することにより、PackageManager#hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x401000)true を返すようにする。
    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 に実装)

VkSurfaceKHR および VkSwapchainKHR のオブジェクト、さらに 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
    );
    

format パラメータと imageUsage パラメータは 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 は、Gralloc バッファに基づいた画像を作成するための vkCreateImage 拡張構造体です。VkNativeBufferANDROID は、VkImageCreateInfo 構造体チェーンの中の vkCreateImage() に与えられます。vkGetSwapChainInfoWSI(.. VK_SWAP_CHAIN_INFO_TYPE_IMAGES_WSI ..) への最初の呼び出し中に、VkNativeBufferANDROID を伴う vkCreateImage() の呼び出しが発生します。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 を呼び出し、VkImageCreateInfo 構造体にチェーンされている VkImageSwapchainCreateInfoKHR 構造体を渡します。次に、アプリは vkBindImageMemory2(KHR) を呼び出し、VkBindImageMemoryInfo 構造体にチェーンされている VkBindImageMemorySwapchainInfoKHR を渡します。VkBindImageMemorySwapchainInfoKHR 構造体で指定されている imageIndex は、有効なスワップチェーン画像インデックスである必要があります。その一方で、プラットフォームが対応する Gralloc バッファ情報を持つ VkNativeBufferANDROID 拡張構造体を VkBindImageMemoryInfo チェーンに与えるため、ドライバは VkImage とバインドする Gralloc バッファを認識します。

画像の確保

vkAcquireImageANDROID はスワップチェーン画像の所有権を取得し、外部からシグナル状態にされたネイティブ フェンスを既存の VkSemaphore オブジェクトと既存の VkFence オブジェクトにインポートします。

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

vkAcquireNextImageKHR の最中に vkAcquireImageANDROID() が呼び出されて、ネイティブ フェンスがアプリによって作成された VkSemaphore オブジェクトと VkFence オブジェクトにインポートされます。ただし、この呼び出しではセマフォ オブジェクトとフェンス オブジェクトはどちらも任意です。ドライバはこの機会を利用して、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 がすでにアイドル状態である場合、ドライバは、実際のネイティブ フェンスのファイル記述子ではなく -1*pNativeFenceFd を設定し、待機する対象がないことを示します。呼び出し元は、*pNativeFenceFd に返されたファイル記述子の所有権を取得して閉じます。

多くのドライバは、image パラメータを無視できますが、一部のドライバでは、外部の画像コンシューマが使用するために Gralloc バッファに関連付けられた CPU 側のデータ構造を準備する必要があります。外部コンシューマが使用するバッファ コンテンツの準備は、イメージから VK_IMAGE_LAYOUT_PRESENT_SRC_KHR への遷移の一環として非同期に行う必要があります。

VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID を指定して画像が作成された場合、ドライバは vkAcquireImageANDROID() への呼び出しを妨げることなく vkQueueSignalReleaseImageANDROID() を繰り返し呼び出せるようにする必要があります。

共有表示可能画像のサポート

一部のデバイスでは、ディスプレイ パイプラインと Vulkan の実装との間で、1 つのイメージの所有権を共有して、レイテンシを最小化できます。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 と 1.1 の API の機能テストが含まれます)。
  • CtsGraphicsTestCases モジュール(デバイスがサポートする Vulkan の機能が正しく設定されているかどうかをテストします)。