私はビデオ プレーヤー アプリケーションを持っており、ユーザー インタラクションをスムーズに保つために複数のスレッドを使用しています。
ビデオをデコードするスレッドは元々、結果のフレームを 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 にアンロードしようとしました。