メモリとラグに関する OpenGLES の最適化

OpenGLES には、テクスチャ ピクセルを FrameBuffer から配列にコピーするのに役立つ GLES20.glReadPixels という関数があることはわかっていますが、この方法にはいくつかの欠点があります。 ① 時間がかかり、費やされる時間はファイルのサイズに比例します。スクリーンショット、「
一部の貧弱なデバイスでは多くの時間がかかります。ビデオを再生している場合、明らかな遅延が発生します。
ビットマップの作成時に 1 つ、スクリーンショットを撮るときにもう 1 つ、合計 2 つのメモリのコピーが消費されます。」

次に、これら 2 つの欠点に対する最適化された解決策を示します。

1.時間のかかる問題の最適化

時間のかかる最適化スキームは 2 つあります。
PBO (ピクセル バッファ オブジェクト) を使用する
PBO (ピクセル バッファ オブジェクト) は OpenGL ES 3.0 によって提供されるメソッドで、主にメモリからビデオ メモリにテクスチャをすばやくコピーしたり、ピクセル データをコピーしたりするために使用されます。ビデオメモリからメモリへ。
その原理は、最初に PBO を作成し、スクリーンショットを撮る必要があるときにこの PBO をバインドし、次に glReadPixel を呼び出すことです。glReadPixel は PBO があることを検出し、それが GPU に渡されて完了し、GPU が完了します。 CPU を消費せずに非同期的に実行され、しばらくすると、ピクセルが PBO からマッピングされます。

// 读取像素时先绑定到PBO
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbos[0]) 
// 将数据从fbo读到pbo,由于这个是异步的,我们要等待一段时间再回来拿像素。因为OpenGLES的API glReadPixels需要传个buffer,但又不能传null,所以需要调用C的这个接口
GLHelper.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE)

1 ~ 2 フレーム後、PBO からデータを読み取ります。

// 从PBO里面读取像素
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbos[0])
// glMapBufferRange,映射内存,会等待DMA传输完成,把pbo数据读到buffer里面
val bf = GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER,
    0, width * height * 4, GLES30.GL_MAP_READ_BIT)
//解除映射
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER)
//解除绑定PBO
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, GLES30.GL_NONE)

実はPBOに最適なシーンは画面録画領域であり、ダブルバッファリング技術を用いて、毎回前フレームのreadPixel以降のPBOからピクセルを読み出し、PBOを交互に使用します。

共有テクスチャを使用する
一部のローエンドデバイスでは PBO が使用できないため、テクスチャを共有するという解決策もあります。

自分でスレッドを作成し、スレッド内に Surface があり、この Surface に画像をレンダリングし、この Surface からピクセルを読み取ります。ここでも同じレンダリング パイプラインが使用されます。

同じレンダリング パイプラインのセットを他のスレッドでレンダリングしたい場合は、次の 2 つの条件があります。 ①
OpenGLES のすべての API は EGLContext のある環境で呼び出す必要があります。そうしないと無効になるため、他のスレッドで OpenGLES API を使用する必要があります。スクリーンショットを撮るには、最初にこのスレッドで EGLContext を初期化する必要があります。
②異なるEGLContextではテクスチャや頂点データ等を共有できないため、他のEGLContextを初期化する際には既存のレンダリングパイプラインが作成されたEGLContextを使用する必要があります。

通常、EGLContextはGLSurfaceViewから取得されますが、リフレクションで取得すれば十分です。

// 使用EGL10,是为了和GLSurfaceView使用一样的EGL版本
val mEgl = EGLContext.getEGL() as EGL10
// share_context就是从GLSurfaceView拿出来的
mEgl.eglCreateContext(mEglDisplay, eglConfig, share_context, contextAttr)

次に、スクリーンショットを撮るときに、最初に別のスレッドでレンダリングし、次にジャンプして現在のレンダリング プロセスを続行し、次に別のスレッドでピクセル データを読み取ります。

2. メモリ問題の最適化

メモリ最適化には 2 つの解決策があります:
libjpeg ライブラリを使用する
ファイルに出力したいので Bitmap は必要ありません Bitmap は手段にすぎませんが、この手段では弱すぎます。

JNI でメモリを適用し、glReadPixels を呼び出して、jpeg ライブラリを通じてデータをローカル ファイルに出力できます。

unsigned char* data = new unsigned char[width * height * 3];
glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
const char *filepath = env->GetStringUTFChars(fileName, NULL);
generateJPEG(data, width, height, quality, filepath, false);
delete[] data;

ここには多くの利点があります:
1. ピクセルを予約するために必要なメモリのコピーは 1 つだけです;
2. ネイティブ Opengl インターフェイスはより多くのカラー チャネルをサポートできます;
3. メモリが使い果たされた後、手動でメモリを解放できます;
4.ネイティブ ヒープでは、要求されたメモリは Java ヒープ
5 には影響しません。ネイティブはイメージをより高速に圧縮します。

引き続きビットマップ ソリューションを使用します。
最初にビットマップを作成し、次にネイティブ レイヤーの lock_Pixels でビットマップのデータ アドレスを取り出し、このデータを glReadPixels に詰め込むことができるため、別のメモリを申請する必要はありません。

unsigned char* datas = NULL;
AndroidBitmap_lockPixels(env, bitmap, (void**) &datas);
glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, datas);
AndroidBitmap_unlockPixels(env, bitmap);

おすすめ

転載: blog.csdn.net/aa642531/article/details/106145046