3

私はビデオ プレーヤー アプリケーションを持っており、ユーザー インタラクションをスムーズに保つために複数のスレッドを使用しています。

ビデオをデコードするスレッドは元々、結果のフレームを BGRA として RAM バッファに書き込んだだけで、glTexSubImage2D によって VRAM にアップロードされ、通常のビデオでは十分に機能しましたが、予想どおり、HD (esp 1920x1080) では遅くなりました。

それを改善するために、リソースをメイン コンテキストと共有する独自の GL コンテキスト (Mac では NSOpenGLContext) を持つ別の種類のプール クラスを実装しました。さらに、使用するようにコードを変更しました

glTextureRangeAPPLE( GL_TEXTURE_RECTANGLE_ARB, m_mappedMemSize, m_mappedMem );

glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_SHARED_APPLE);

VRAMへのアップロードのパフォーマンスを向上させるために、私が使用するテクスチャについて。BGRA テクスチャ (1920x1080 のフレームあたり約 8MB の重さ) をアップロードする代わりに、Y、U、および V の 3 つの個別のテクスチャをアップロードします (それぞれが GL_LUMINANCE、GL_UNSIGNED_BYTE、および元のサイズの Y テクスチャであり、U と V は半分です)。これにより、アップロードされるサイズが約 3 MB に減少し、すでにある程度の改善が見られました。

これらの YUV テクスチャのプールを作成しました (ビデオのサイズに応じて、通常は 3 ~ 8 サーフェス (Y、U、V コンポーネントの 3 倍) の範囲になります) - 各テクスチャは上記の独自の領域にマッピングされますm_mappedMem.

新たにデコードされたビデオ フレームを受信すると、空き YUV サーフェスのセットを見つけ、次のコードで 3 つのコンポーネントをそれぞれ更新します。

glActiveTexture(m_textureUnits[texUnit]);
glEnable(GL_TEXTURE_RECTANGLE_ARB);

glBindTexture(GL_TEXTURE_RECTANGLE_ARB, planeInfo->m_texHandle);

glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_SHARED_APPLE);
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);

memcpy( planeInfo->m_buffer, srcData, planeInfo->m_planeSize );

glTexSubImage2D( GL_TEXTURE_RECTANGLE_ARB, 
                0, 
                0, 
                0, 
                planeInfo->m_width, 
                planeInfo->m_height, 
                GL_LUMINANCE, 
                GL_UNSIGNED_BYTE, 
                planeInfo->m_buffer );

(副次的な質問として: テクスチャごとに異なるテクスチャ ユニットを使用する必要があるかどうかわかりませんか? [ユニット 0 を Y に、1 を U に、2 を V に使用しています])

これが完了したら、使用したテクスチャを使用済みとしてマークし、VideoFrame クラスにそれらの情報 (つまり、テクスチャ番号、バッファ内のどの領域を占めるかなど) を入力し、レンダリングするキューに入れます。キューの最小サイズに達すると、ビデオのレンダリングを開始できることがメイン アプリケーションに通知されます。

その間、メインのレンダリング スレッドは (正しい状態を確認した後など)、このキューにアクセスし (そのキュー クラスのアクセスはミューテックスによって内部的に保護されています)、トップ フレームをレンダリングします。

そのメイン レンダリング スレッドには 2 つのフレーム バッファがあり、glFramebufferTexture2D の 2 つのテクスチャを介してそれらに関連付けられ、ある種のダブル バッファリングを実装します。メイン レンダリング ループでは、フロント バッファーがどれかをチェックし、テクスチャ ユニット 0 を使用してこのフロント バッファーを画面にレンダリングします。

glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_RECTANGLE_ARB);            
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, frontTexHandle);            
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT );
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_TEXTURE_COORD_ARRAY );            
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glVertexPointer(4, GL_FLOAT, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glTexCoordPointer(2, GL_FLOAT, 0, 0);
glDrawArrays(GL_QUADS, 0, 4);
glPopClientAttrib();

現在のフレームの画面にそのレンダリングを行う前に (ビデオの通常のフレームレートは約 24 fps であるため、このフレームは次のビデオフレームがレンダリングされる前に数回レンダリングされる可能性があります。そのため、このアプローチを使用します) ビデオデコーダーを呼び出します新しいフレームが利用可能かどうかをチェックするクラス (つまり、タイムラインに同期し、新しいフレームでバックバッファーを更新する責任があります)。フレームが利用可能な場合は、ビデオデコーダー クラス内からバックバッファー テクスチャにレンダリングしています (このメインのレンダリング スレッドと同じスレッドで発生します):

glBindFramebuffer(GL_FRAMEBUFFER, backbufferFBOHandle);

glPushAttrib(GL_VIEWPORT_BIT);    // need to set viewport all the time?
glViewport(0,0,m_surfaceWidth,m_surfaceHeight);

glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glLoadIdentity();
glScalef( (GLfloat)m_surfaceWidth, (GLfloat)m_surfaceHeight, 1.0f );

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_Y);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_U);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texID_V);

glUseProgram(m_yuv2rgbShader->GetProgram());

glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glEnableVertexAttribArray(m_attributePos);
glVertexAttribPointer(m_attributePos, 4, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glEnableVertexAttribArray(m_attributeTexCoord);
glVertexAttribPointer(m_attributeTexCoord, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_QUADS, 0, 4);

glUseProgram(0);

glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);                

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();                            

glPopAttrib();
glBindFramebuffer(GL_FRAMEBUFFER, 0);

[簡潔にするために、特定の安全チェックとコメントを省略したことに注意してください]

上記の呼び出しの後、ビデオ デコーダーはバッファーをスワップできるフラグを設定し、上記のメインスレッド レンダリング ループの後、そのフラグをチェックし、それに応じて frontBuffer/backBuffer を設定します。また、使用済みのサーフェスは解放され、再び使用できるようにマークされます。

BGRA を使用し、glTexSubImage2D と glBegin と glEnd でアップロードしたときの元のコードでは、問題は発生しませんでしたが、シェーダーを使用して YUV コンポーネントを BGRA に変換し、それらの DMA 転送と glDrawArrays を改善し始めたら、それらの問題が現れ始めました。

基本的には、部分的にテアリング効果のように見えます (更新と同期するために GL スワップ間隔を 1 に設定しています)。

レンダリング先のサーフェスのプールがあり、ターゲット サーフェスへのレンダリング後に解放されること、およびそのターゲット サーフェスをダブル バッファリングすることで十分であると予想していましたが、他の場所でより多くの同期を実行する必要があることは明らかですが、私はそうしません。それを解決する方法が本当にわかりません。

glTexSubImage2D が DMA によって処理されるようになったため (およびドキュメントによると、関数はすぐに返されるはずです)、アップロードがまだ完了していない (そして次のフレームがその上にレンダリングされている) か、忘れていた (または忘れていた) と思います。 OpenGL (Mac) に必要な他の同期メカニズムについては知りません。

コードの最適化を開始する前の OpenGL プロファイラーによると:

  • glTexSubImage2D でほぼ 70% の GLTime (つまり、8MB の BGRA を VRAM にアップロード)
  • CGLFlushDrawable では約 30%

コードを上記のように変更した後、次のようになります。

  • glTexSubImage2D で約 4% GLTime (DMA はうまく機能しているようです)
  • GLCFlushDrawable で 16%
  • glDrawArrays でほぼ 75% (これは私にとって大きな驚きでした)

それらの結果について何かコメントはありますか?

コードの設定方法についてさらに情報が必要な場合は、お知らせください。これを解決する方法についてのヒントをいただければ幸いです。

編集:参照用のシェーダーは次のとおりです

#version 110
attribute vec2 texCoord;
attribute vec4 position;

// the tex coords for the fragment shader
varying vec2 texCoordY;
varying vec2 texCoordUV;

//the shader entry point is the main method
void main()
{   
    texCoordY = texCoord ;
    texCoordUV = texCoordY * 0.5;
    gl_Position = gl_ModelViewProjectionMatrix * position;
}

およびフラグメント:

#version 110

uniform sampler2DRect texY;
uniform sampler2DRect texU;
uniform sampler2DRect texV;

// the incoming tex coord for this vertex
varying vec2 texCoordY;
varying vec2 texCoordUV;

// RGB coefficients
const vec3 R_cf = vec3(1.164383,  0.000000,  1.596027);
const vec3 G_cf = vec3(1.164383, -0.391762, -0.812968);
const vec3 B_cf = vec3(1.164383,  2.017232,  0.000000);

// YUV offset
const vec3 offset = vec3(-0.0625, -0.5, -0.5);

void main()
{
    // get the YUV values
    vec3 yuv;
    yuv.x = texture2DRect(texY, texCoordY).r;
    yuv.y = texture2DRect(texU, texCoordUV).r;
    yuv.z = texture2DRect(texV, texCoordUV).r;
    yuv += offset;

    // set up the rgb result
    vec3 rgb;

    // YUV to RGB transform
    rgb.r = dot(yuv, R_cf);
    rgb.g = dot(yuv, G_cf);
    rgb.b = dot(yuv, B_cf);

    gl_FragColor = vec4(rgb, 1.0);
}

編集 2:補足として、デコードに VDADecoder オブジェクトを使用する別のレンダリング パイプラインがあります。したがって、コードのスレッド化には間違いなく問題があります。これまでのところ、正確に何が問題なのかわかりませんでした。しかし、VDA をサポートしていないマシン用のソフトウェア デコーダー ソリューションも提供する必要があります。そのため、CPU の負荷が非常に高いため、YUV から RGB への変換を GPU にアンロードしようとしました。

4

2 に答える 2

1

OK、さらに多くのテストと調査を行った後、最終的に問題を解決することができました。

何が起こったのかというと、最初にフレーム バッファ (カラー アタッチメント 0 として glFramebufferTexture2D でそのテクスチャにバインド) を使用してターゲット テクスチャに書き込もうとし、同じフレームで、フレームをウィンドウ フレーム バッファにレンダリングするときにそこから読み取ろうとしました。

基本的に、(同じフレームで呼び出され、互いに直接連続して呼び出される) 最初の呼び出しは、次の呼び出しがフレームバッファーから読み取る前に、フレームバッファーへの書き込みを終了すると誤って想定していました。したがって、glFlush (VDADecoder を使用するクラスの場合) と glFinish (ソフトウェア デコーダーを使用するクラスの場合) を呼び出すと、うまくいきました。

補足: 上記のコメントに示されているように、コード全体を変更して、固定されたパイプラインを使用しなくなり、見た目がすっきりするようにしました。OpenGL プロファイラー (Mac OS X 10.7 の下) でのパフォーマンス テストでは、元のコードから現在のコードへの変更により、アプリケーションの合計時間のうち OpenGL の使用時間がほぼ 50% から約 15% に短縮されたことが示されました (より多くのリソースが解放されます)。実際のビデオ デコードの場合 - VDADecoder オブジェクトが利用できない場合)。

于 2012-05-28T08:32:50.057 に答える
1

私が見たもの (つまり、glPushMatrix 呼び出しなど) から、最新のハードウェアではなく、CGLFlushDrawable でこのように古いビデオカードで問題が発生している可能性が非常に高いと思います。(私は VBO を使用しています) .

あなたが言った2番目のことは、明らかにソーステクスチャに何度もアクセスするYUV-> RGBシェーダーであり、これどのビデオカードでも、特に古いビデオカードでは遅くなるはずです。そのため、glDrawArrays() 呼び出しの大きなタイミングは、実際には、シェーダー コードが「無害」に見える場合でも、(メモリ アクセスに関して) 非常に重いシェーダー プログラムを使用しているという事実を反映しています。

シェーダー コードはテクスチャ (およびシステムの RAM) にアクセスし、(このビデオカードの場合) パフォーマンスの点では、RAM->VRAM コピーを実行するのと同じです。

一般的なアドバイス: 長方形や 2 の累乗でないテクスチャは避けてください。これにより、パフォーマンスが低下することもあります。非標準のテクスチャ形式と拡張機能も避ける必要があります。シンプルであるほど良い。本当に FullHD 解像度が必要な場合は、2048x1024 テクスチャまたは 2048x2048 などを使用してみてください (ちなみに、これは純粋な演算によって遅くなるはずです)。

于 2012-05-21T12:46:41.693 に答える