Android GraphicBufferの魔法---直接テクスチャ

私はAndroid GraphicBufferをしばらく研究してきましたが、Android GraphicBufferの利点は何ですか?
私はインターネットで検索し、この問題を紹介する優れた記事を転載しました。
ANDROIDのEGL拡張機能の概要
次のとおりです。OpenGLES
では、テクスチャ(glTexImage2D()、glSubTexImage2D())のアップロード非常に時間がかかるプロセスあり、画面サイズ1080×1920の全画面をアップロードします。テクスチャは20〜60ms必要です。この場合、SurfaceFlingerは60fpsで実行できません。したがって、Androidは画像のネイティブバッファーを使用し、グラフィックバッファーをダイレクトテクスチャとして直接操作します。

[转载] Androidでの
直接テクスチャの使用Androidでの直接テクスチャの使用
2011年12月16日
私は、Firefox MobileのMozillaで数ヶ月働いています。新しいネイティブUIの目標の1つは、常にスムーズでスムーズなスクロールとパンを実現することです。当然のことながら、これを行うには、OpenGLテクスチャに描画し、画面上で移動します。これは、テクスチャのコンテンツが不足して更新する必要があるまで、かなり高速です。Geckoは別のスレッドで実行され、ブロックすることなくそこのバッファーに描画できますが、そのデータをテクスチャーにアップロードすると問題が発生します。現在、1つの非常に大きなテクスチャ(通常は2048x2048)のみを使用しており、glTexSubImage2Dは25ミリ秒から60ミリ秒の範囲で取得できます。ターゲットが60fpsの場合、フレームを描画するのに約16msかかります。これは、アップロードするたびに少なくとも1つのフレームを見逃すことが保証されていることを意味しますが、それ以上になる可能性があります。これは、直接テクスチャが役立ちます

Androidグラフィックスタックに関するDianne Hackbornの最近の投稿をまだ読んでいない場合は、見逃していることになります(パート1、パート2)。彼女が説明するウィンドウ合成システム(SurfaceFlingerと呼ばれます)は、Firefoxで発生している問題に近いため、特に興味深いものです。Androidがウィンドウを描画するために使用する要素の1つは、grallocモジュールです。ご想像のとおり、grallocは「graphics alloc」の略です。そのための短くてシンプルなAPIはここにあります。Androidには、GraphicBufferと呼ばれるこれへのアクセスをカプセル化するラッパークラスがあります。ここには、さらに優れたAPIがあります。使い方はとても簡単です。必要なサイズとピクセル形式でGraphicBufferを作成し、ロックして、ビットを書き込んで、ロックを解除するだけです。ここでの大きなメリットの1つは、任意のスレッドからGraphicBufferインスタンスを使用できることです。これにより、画像のコピーが削減されるだけでなく、レンダリングループをブロックせずに画像をアップロードできるようになります。

OpenGLを使用して画面に表示するには、GraphicBufferからEGLImageKHRを作成し、それをテクスチャーにバインドします。

#define EGL_NATIVE_BUFFER_ANDROID 0x3140
#define EGL_IMAGE_PRESERVED_KHR   0x30D2

GraphicBuffer* buffer = new GraphicBuffer(1024, 1024, PIXEL_FORMAT_RGB_565,
                                          GraphicBuffer::USAGE_SW_WRITE_OFTEN |
                                          GraphicBuffer::USAGE_HW_TEXTURE);

unsigned char* bits = NULL;
buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, (void**)&bits);

// Write bitmap data into 'bits' here

buffer->unlock();

// Create the EGLImageKHR from the native buffer
EGLint eglImgAttrs[] = {
    
     EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE, EGL_NONE };
EGLImageKHR img = eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_CONTEXT,
                                    EGL_NATIVE_BUFFER_ANDROID,
                                    (EGLClientBuffer)buffer->getNativeBuffer(),
                                    eglImgAttrs);

// Create GL texture, bind to GL_TEXTURE_2D, etc.

// Attach the EGLImage to whatever texture is bound to GL_TEXTURE_2D
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img);

結果のテクスチャは通常のテクスチャとして使用できますが、注意事項が1つあります。ピクセルデータを操作するたびに、ロック解除直後に変更が画面に反映されます。ここでの問題を回避するために、おそらくダブルバッファを使用する必要があります。

これまでにAndroid NDKを使用したことがある場合、GraphicBuffer(または類似のもの)がそこに存在しないことは驚くに値しません。これをアプリで使用するには、dlopenハックに頼る必要があります。それはかなり憂鬱な状況です。GoogleはこれをOS全体で使用していますが、アプリに高性能APIが必要だとは思わないようです。しかし、待ってください、それは悪化します。これらのフープを飛び越えた後でも、一部のgrallocドライバーは、通常のアプリがボールをプレーすることを許可しません。これまでのところ、テストでは、これがAdrenoおよびMali GPUに当てはまることを示しています。ありがたいことに、PowerVRとTegraはそれを許可しており、かなりの数のデバイスをカバーしています。

運がよければ、今日これを使用するパッチをFirefox Mobileにリリースします。その結果、grallocが動作することが許可されているデバイスで、よりスムーズなパンとズームのエクスペリエンスが得られます。

[再版] ANDROIDのEGL拡張

オリジナル:http : //tangzm.com/blog/?p=167

Googleは、ディスプレイレンダリングソフトウェアシステム全体をより効率的に実行できるように、Androidのeglにいくつかの拡張を行いました。SurfaceFlingerコードの分析と変更では、Androidネイティブフェンス、KHR画像など、これらのegl拡張機能に関連するコードをよく見ることができます。これらのコンテンツをスキップしても、SurfaceFlinger自体の理解にはほとんど影響しませんが、コードを読むとき、これらの「小さな石」を見るたびに不快に感じます。したがって、これらのEGL拡張機能の基本を理解するために、SurfaceFlingerソースコードと組み合わせたKHRONOSドキュメントを見つけるのに少し時間がかかりました。

BufferQueueのプロデューサー/コンシューマーモデル

これらの拡張機能の説明に入る前に、Andriod BufferQueueの操作メカニズムを簡単に確認しましょう。

Android(3.0以降)では、アプリケーション、Surfaceflingerまで、すべての描画プロセスはOpenGL ESを使用して行われます。各ペインター(プロデューサー、コンテンツプロデューサー)の手順はほぼ同じです。

(1)サーフェスを取得する(通常は、SurfaceControlを使用)

(2)このサーフェスをパラメータとして使用して、egl描画サーフェスと読み取りサーフェスを取得します。通常、これら2つは同じオブジェクトです

(3)egl configを構成してから、eglMakeCurrent()を呼び出して現在の描画コンテキストを構成します。

(4)通常はglDrawArray()またはglDrawElemens()を使用してコンテンツの描画を開始します。

(5)使用するeglSwapBuffers()バックバッファーとフロントバッファーを入れ替えます。

(6)手順4に戻り、次のフレームを描画します。

すべての描画の最終結果は、RGBまたはYUV値を格納するピクセルメモリの一部にすぎないことがわかっています。このメモリは、Surfaceのバックエンドです。Androidでは、コンテンツのプロデューサーとコンシューマーが並行して作業できるようにするため、各Surfaceの背後でメモリバッファーの3倍(MTKはバッファーの4倍)が使用されるため、ペインターが描画しているときに現在のバッファーに影響を与えません。画面が表示され、ペインターがフレームの描画を終了した後、通常は新しいバッファーを取得して、待たずに次のフレームを描画できます。

ここに画像の説明を挿入
この図では、BufferQueueのバッファーは4つの状態にマークされています。

(1)FREEは、バッファーが使用されておらず、その内容が空であることを意味します

(2)DEQUEUEDは、バッファーがコンテンツプロデューサーによって描画されていることを示します

(3)QUEUEDは、バッファが描画されてBufferQueueに入れられ、表示されるのを待っている(または次のステップ)ことを意味します

(4)ACQUIREDは、バッファが表示(または次のステップの処理)のためにコンシューマによって使用されていることを意味します

移行の状態は常にFREE => DEQUEUED => QUEUED => ACQUIRED => FREEです。

プロデューサーがeglMakeCurrent()を実行すると、BufferQueueからFREEでアイテムが検索され、DEQUEUEDとしてマークされ、現在の描画ターゲットとして使用されます。描画後、プロデューサーがeglSwapBuffers()を呼び出すと、現在のDEQUEUEDバッファーがQUEUEDに設定され、表示できるようにマークされます(または次のステップ)と同時に、BufferQueueからステータスがFREEである別のバッファーアイテムを探して設定します要求され、描画を続けます。

一方、BufferQueueのコンシューマー(通常はSurfaceFlingerまたは一部のImageProcessor)は、表示または次の処理のために、BufferQueueからQUEUEDとしてマークされたアイテムを探します。プロデューサーとコンシューマーの関係は、次の図を参照できます。

ここに画像の説明を挿入

EGL_ANDROID_image_native_buffer

BufferQueueのメカニズムについて説明した後、トピックに入ることができます。Andriodによって導入された最初の拡張はEGL_ANDROID_image_native_bufferです。OpenGL ESでは、テクスチャのアップロード(glTexImage2D()、glSubTexImage2D())は非常に時間がかかるプロセスであり、1080×1920の画面サイズでフルスクリーンテクスチャをアップロードするには、20〜60ミリ秒かかります。この場合、SurfaceFlingerは60fpsで実行できません。したがって、Androidは画像のネイティブバッファーを使用し、グラフィックバッファーをテクスチャー(直接テクスチャーとして直接操作します。

Androidでの画像ネイティブバッファーの使用は非常に便利です。

まず、グラフィックバッファーを使用してEGLImageKHRを作成します。

次に、eglイメージをtexture2D OESにバインドします

OpenGL ESでテクスチャを参照する必要がある場合は、glslのタイプをsampler2DからsamplerExternalOESに変更する必要があります。次に例を示します。

EGL_ANDROID_native_fence_sync

Androidのネイティブフェンスの同期について説明する前に、CPUとGPUを同期して調整する方法を最初に検討しましょう。OpenGL ESのAPI呼び出しは実際には一連のコマンドであることはわかっています。ユーザーがこれらのAPIを呼び出すと、これらのコマンドはOpenGL libライブラリー(または基になるドライバー?)によってキャッシュされます。手動でglFlush()を呼び出すと、現在のコンテキスト内のすべてのコマンドが強制的にGPUに送信されて実行されますが、これらのコマンドが実行される前にglFlush()が返されます。glFinish()はすべてのコマンドをGPUに送信し、戻る前にこれらのコマンドが実行されるのを待ちます。

glFinish()は、CPUとGPUの作業を同期する実行可能な方法のようです。しかし、glFinish()の欠陥も明らかです。GPUの動作中、現在のスレッドは待機しています。他のスレッドが実行する(CPUによってスケジュールされた)スレッドがない場合、この間、CPUは浪費されます。さらに、他のスレッドがGPUタスクの完了を待機している場合は、glFinish()の後にConditionを通じて手動で通知する必要もあります。したがって、KHRONOSはこれに基づいてEGL_KHR_fence_sync(フェンス同期オブジェクト)を追加しました。eglCreateSyncKHR()を通じて、タイプEGLSyncKHRのフェンスオブジェクトが現在のGLコンテキストの現在の位置に作成されます(以前に呼び出されたすべてのコマンドの後)。その後、現在のスレッドまたはその他のスレッドは、Conditionを待機するのと同じように、eglClientWaitSyncKHR()を介してこのフェンス同期オブジェクトを待機できます。条件と同様に、待機関数もタイムアウトパラメータを受け入れて、タイムアウト条件を指定できます。以下の図は、eglフェンスがGPUと複数のスレッドを同期する方法を示しています。

ここに画像の説明を挿入
AndroidネイティブフェンスがKHRフェンスでさらに一歩進んだ。フェンスオブジェクトからfd(ファイル記述子)を取得するか、fdから同期オブジェクトを生成できます。この機能により、Androidは同期の範囲を複数のスレッドから複数のプロセスに拡張します!これはAndroidにとって非常に重要です。これは、BufferQueueのプロデューサーとコンシューマーのほとんどが同じプロセス内にないことがわかっているためです。Androidネイティブフェンスを使用すると、マルチプロセス環境でのバッファーの使用を同期できます。

誰かが質問するかもしれませんが、なぜそんなに面倒なのですか?BufferQueueアイテムはステートフルではありませんか?状態によってグラフィックバッファの使用を判断するだけで十分ではありませんか?実際、AndroidでCPUとGPUを並行して動作させるために、BufferQueue Itemの状態は、(GPU側の)グラフィックバッファーの操作が完全に完了するまで待機しませんでした。つまり、プロデューサーがeglSwapBuffers()、BufferQueueを呼び出すとアイテムのステータスは、設定前にすべての操作が終了するのを待つためにglFinish()を使用せずに、すぐに書き換えられます(DEQUEUED => QUEUED、FREE => DEQUEUED)。このようにして、プロデューサーとコンシューマーの両方が、できるだけ早く次のステップに進むことができます。このようにして、問題が発生します。グラフィックバッファが「実際に」書き込まれたことをコンシューマはどのようにして知り、グラフィックバッファが実際にコンシューマによって使用されて解放されたことをプロデューサはどのように知るのですか。答えはもちろんアンドロイドのネイティブフェンスです。

一方では、プロデューサーはeglSwapBuffers()の後に取得フェンスを挿入し、コンシューマーは、このフェンスを待機することによってグラフィックバッファーが実際に書き込まれることを認識し、消費します(表示...)。一方、コンシューマの処理が完了すると、相対フェンスが挿入され、プロデューサがdequeueBuffer()に入ると、このフェンスを待機しますが、実際には描画可能な状態になります。

これらのフェンスが生成され、コードから待機している特定の場所を見てみましょう

フェンスを取得

  1. EGLドライバー(lib)は、queueBuffrerのときに取得フェンスに渡されます(コードを参照
    )。
  2. Androidシステムは、Binderを介してSurfaceFlingerのBufferQueue :: queueBuffer()を呼び出し、
    フェンスをパラメーターとして渡します。BufferQueueはフェンスをスロットに記録しますコードを参照
  3. SurfaceTexture(ConsumerBase)acquireBufferの場合、取得
    フェンスはスロットに格納されますコードを参照
  4. このSurfaceがSurfaceFlingerによって表示される場合、2つの状況があります。合成にGLESを使用して追加し、Layer :: onDraw()の場合、SurfaceTextureのフェンスで待機します(コードを参照

    。HWCによって合成された場合、SurfaceFlingerはフェンスをHWCに渡しますコードを参照
    )。HWCのコード実装を確認することはできませんが、正しく実装されたHWCは、すべてのフェンスが内部的に通知されるのを待ってから合成されます
  5. このサーフェスが別のGL
    コンテキストで描画するためのテクスチャとして使用される場合。SurfaceTextureはテクスチャが実際にいつ使用されるかを認識できないため、更新を完了する前に、SurfaceTexture :: updateTexImage()でフェンスがトリガーされるのを待ちます(コードを参照
    )。

リリースフェンス

次に、リリースフェンスを使用して、コンシューマがグラフィックバッファの使用を終了したときにプロデューサに通知します。

  1. SurfaceがSurfaceFlingerによってLayerコンポジットディスプレイとして使用されている場合、SurfaceFlinger :: postComposition()で、SurfaceFlingerは各Surfaceにリリース
    フェンスを設定します。このリリースフェンスの生成は、HWCまたはFrameBuffer GLESによって行われます。HWCとFB
    GLESの両方をリリースフェンスの適切な場所に生成する必要があります
  2. Surfaceをテクスチャとして使用する場合、グラフィック
    バッファは2つの運命があります。1つは、SurfaceTexture :: updateTexImage()で古いグラフィック
    バッファーが新しいものに置き換えられること、もう1つは、SurfaceTexture :: detachFromContext()で現在のGLコンテキストから
    グラフィック
    バッファーが切り離されることです。いずれの場合も、SurfaceTextureは、リリースを挿入するsyncForReleaseLockedを呼び出します
    FENEを
  3. 最後に、BufferQueue :: dequeueBuffer()で、BufferQueueはeglClientWaitSyncKHR()を使用してリリース
    フェンスが通知されるのを待ちます;この後、プロデューサーは実際にグラフィックバッファーをデキューして描画を開始できます。

EGL_ANDROID_framebuffer_target

HWCに加えて、SurfaceFlingerはGLESメソッド(FrameBufferメソッド)を使用してSurfaceを合成します。通常のEGLコンテキスト(レンダリング結果はHWC入力またはGLESテクスチャとして使用される)とFrameBuffer専用EGLコンテキスト(レンダリング結果はFrameBufferに出力される)を区別するために、AndroidはEGL_FRAMEBUFFER_TARGET_ANDROID CONFIG属性を使用して、FrameBufferを必要とするEGLコンテキストをマークします。特定の情報はKHRONOS標準を参照できます

EGL_ANDROID_recordable

KHRONOS規格を参照

EGL_ANDROID_blob_cache

KHRONOS規格を参照

おすすめ

転載: blog.csdn.net/u010116586/article/details/100665537