4

長い OpenGL 描画操作を GCD キューに移動して、GPU の処理中に他の処理を実行できるようにしようとしています。アプリに実際のスレッドを追加するよりも、GCD でこれを行う方がはるかに好ましいと思います。文字通り、私がやりたいことは、できるようになることだけです

  • glDrawArrays() 呼び出しでブロックしないため、GL のレンダリングが非常に遅くなっても、残りの UI は応答性を維持できます。
  • とにかく終了していない場合は、glDrawArrays() 呼び出しをドロップします (成長し続けるフレームのキューを構築しないでください)。

Apple の Web サイトで、ドキュメントには次のように記載されています。

GCD および NSOperationQueue オブジェクトは、選択したスレッドでタスクを実行できます。そのタスク専用のスレッドを作成することも、既存のスレッドを再利用することもできます。ただし、いずれの場合も、どのスレッドがタスクを実行するかは保証できません。OpenGL ES アプリの場合、次のことを意味します。

  • 各タスクは、OpenGL ES コマンドを実行する前にコンテキストを設定する必要があります。
  • 同じコンテキストにアクセスする 2 つのタスクが同時に実行されることはありません。
  • 各タスクは、終了する前にスレッドのコンテキストをクリアする必要があります。

かなり簡単に聞こえます。

この質問を簡単にするために、「OpenGL ES」ゲームの「新しいプロジェクト」ダイアログに表示される Apple テンプレートの新しいボーンストック バージョンから始めます。インスタンス化し、コンパイルして実行すると、灰色のフィールド上で回転する 2 つの立方体が表示されます。

そのコードに、GCD キューを追加しました。のインターフェイス セクションから始めますViewController.m

dispatch_queue_t openGLESDrawQueue;

次に、それらを次のように設定しViewController viewDidLoadます。

openGLESDrawQueue = dispatch_queue_create("GLDRAWINGQUEUE", NULL);

drawInRect最後に、 CADisplayLink が最終的にトリガーするメソッドに次の非常に小さな変更を加えます。

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
void (^glDrawBlock)(void) = ^{
    [EAGLContext setCurrentContext:self.context];
    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArrayOES(_vertexArray);

    // Render the object with GLKit
    [self.effect prepareToDraw];

    glDrawArrays(GL_TRIANGLES, 0, 36);

    // Render the object again with ES2
    glUseProgram(_program);

    glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
    glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, _normalMatrix.m);

    glDrawArrays(GL_TRIANGLES, 0, 36);
};
dispatch_async(openGLESDrawQueue, glDrawBlock);
}

これは動作しません。絵が狂います。ただし、 で同じブロックを描画してもdispatch_sync()問題ありません。

Apple のリストを再確認してみましょう。

  • 各タスクは、OpenGL ES コマンドを実行する前にコンテキストを設定する必要があります。
    • Ok。コンテキストを設定しています。いずれにせよ、ブロックよりも寿命が長いのはObjective-Cオブジェクトポインターであるため、問題なく閉じられるはずです。また、デバッガーでそれらを確認できますが、問題ありません。また、dispatch_sync から描画すると機能します。したがって、これは問題ではないようです。
  • 同じコンテキストにアクセスする 2 つのタスクが同時に実行されることはありません。
    • 一度設定された GL コンテキストにアクセスする唯一のコードは、このメソッド内のコードであり、これはこのブロック内にあります。これはシリアル キューであるため、一度に 1 つのインスタンスだけを描画する必要があります。さらに、synchronized(self.context){}ブロックを追加しても何も修正されません。また、描画が非常に遅い他のコードでは、前のブロックが完了していないときにキューへのブロックの追加をスキップするセマフォを追加し、フレームを正常にドロップしました (NSLog()吐き出していたメッセージ)、しかし、それは描画を修正しませんでした. ただし、表示できない GLKit コードの一部が、メイン スレッドからは理解できない方法でコンテキストを操作している可能性があります。synchronized() が問題を変更せず、OpenGL Profiler がスレッドの競合を表示しないという事実にもかかわらず、これは現在 2 番目に評価の高い理論です。
  • 各タスクは、終了する前にスレッドのコンテキストをクリアする必要があります。
    • これが何を意味するのか、私にはよくわかりません。GCD スレッドのコンテキストは? それはいいです。キューのコンテキストには何も追加していないため、クリーンアップするものはありません。描画先の EAGLContext は? 他に何ができるかわかりません。確かに、実際には glClear ではありません。すべてを消去するだけです。また、Sunset Lake の Moleculesには、次のようにレンダリングするコードがいくつかあります。

コード:

dispatch_async(openGLESContextQueue, ^{
    [EAGLContext setCurrentContext:context];        
    GLfloat currentModelViewMatrix[9];
    [self convert3DTransform:&currentCalculatedMatrix to3x3Matrix:currentModelViewMatrix];
    CATransform3D inverseMatrix = CATransform3DInvert(currentCalculatedMatrix);
    GLfloat inverseModelViewMatrix[9];
    [self convert3DTransform:&inverseMatrix to3x3Matrix:inverseModelViewMatrix];

    GLfloat currentTranslation[3];
    currentTranslation[0] = accumulatedModelTranslation[0];
    currentTranslation[1] = accumulatedModelTranslation[1];
    currentTranslation[2] = accumulatedModelTranslation[2];

    GLfloat currentScaleFactor = currentModelScaleFactor;

    [self precalculateAOLookupTextureForInverseMatrix:inverseModelViewMatrix];
    [self renderDepthTextureForModelViewMatrix:currentModelViewMatrix translation:currentTranslation scale:currentScaleFactor];
    [self renderRaytracedSceneForModelViewMatrix:currentModelViewMatrix inverseMatrix:inverseModelViewMatrix translation:currentTranslation scale:currentScaleFactor];

    const GLenum discards[]  = {GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);

    [self presentRenderBuffer];

    dispatch_semaphore_signal(frameRenderingSemaphore);
});

このコードは機能し、追加のクリーンアップは見られません。このコードが私のものとは異なる動作をしていることを理解できません。異なる点の 1 つは、文字通り、GL コンテキストに触れるすべてのものが同じ GCD ディスパッチ キューから実行されているように見えることです。ただし、このようにコードを作成しても、何も修正されません。

最後に異なる点は、このコードが GLKit を使用していないように見えることです。上記のコード (私が実際に興味を持っているコードと一緒に)GLKit を使用しています。

現時点では、この問題について 3 つの理論があります。 1. ブロック、GCD、および OpenGL ES 間の相互作用について概念的な誤りを犯しています。2. GLKitは、への呼び出しの間に何らかの描画または操作をGLKViewController行いGLKViewます。ブロックが処理されている間に、これが発生し、物事が台無しになります。3.私が方法に頼っているという事実は、ITSELFの問題です。私はこの方法を「ねえ、あなたは自動的にEAGLContextdrawInRectdrawInRect- (void)glkView:(GLKView *)view drawInRect:(CGRect)rectCADisplayLinkフレームが必要になるたびに、このメソッドにヒットします。好きなことを何でもしてください。つまり、ここの通常のコードでは、glDrawArrays コマンドを発行するだけです。画面に表示したいものを含むフレームバッファオブジェクトまたは CGImageRef を返すようなものではありません。GL コマンドを発行しています。ただし、これは間違っている可能性があります。とにかく、問題を引き起こさずにこのメソッドで描画を延期することはできません。この理論をテストするために、すべての描画コードを呼び出されたメソッドに移動し、メソッドdrawStuffの本体を次のように置き換えましたdrawRect

[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(drawStuff) userInfo:nil repeats:NO];

アプリが起動し、ビューの色がglClear10 秒間表示された後、通常どおりに描画されます。したがって、その理論もあまり強く見えません。

ここに投稿された同様の質問があり、 1 つの回答があり、支持されて受け入れられています。

ディスパッチ ブロックのコードは機能しません。それが実行されるまでに、そのフレームのすべての OpenGL 状態はかなり前に破棄されています。そのブロックに glGetError() を呼び出すと、同じことがわかるはずです。OpenGL の状態を有効にするには、すべての描画コードがその glkView メソッドで実行されていることを確認する必要があります。そのディスパッチを行うと、基本的に、その描画コードの実行がそのメソッドの範囲外に追いやられます。

なぜこれが真実である必要があるのか​​ わかりません。しかし:

  1. ブロックよりも長く存続するブロック内のものへの参照のみを閉じています。それらは、囲んでいるオブジェクトスコープからのObjective-Cポインターのようなものです
  2. デバッガーで確認できますが、問題ないようです。
  3. すべての GL 操作の後に getGLError() 呼び出しを挿入しましたが、0 以外は返されません。
  4. dispatch_sync を使用したブロックからの描画が機能します。
  5. メソッドのどこでdrawInRectブロックを ivar に保存し、NSTimer を call に設定するかを考えてみましたdrawStuff。ではdrawStuff、ブロックを呼び出すだけです。うまく描きます。

NSTimer ケースは非同期に描画しますが、別のスレッドからの描画は必要ありません。AFAIK NSTimer の呼び出しは、設定スレッドの実行ループでスケジュールされるだけだからです。したがって、スレッドと関係があります。

ここで何が欠けているのか、誰かが私に手がかりを与えることができますか?

4

1 に答える 1

5

borrrden が言うように、GLKit は完了presentRenderbuffer:直後に呼び出しているため、これは機能していません- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect

drawStuff描画サイクルの開始時にメインスレッドでメソッドが呼び出されるため、これはタイマーを使用する場合に機能します。事実- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect上、これがメイン スレッドでさらに 10 秒後に発生するようにスケジュールするだけで、以前にスケジュールされた描画呼び出しがdrawInRect:メソッドの最後にレンダリングされます。これは、描画を 10 秒遅らせる以外は何もしていません。すべてはまだメイン スレッドで行われています。

メイン スレッドから離れたレンダリング ルートを使用する場合、GLKit は適していません。ランループを使用して独自のスレッドをセットアップし、CADisplayLinkこのランループに接続してから、このキューからレンダリングする必要があります。GLKViewControllerこれにはメインのランループを使用するように構成されており、各フレームの最後に常にレンダー バッファーが表示されます。これにより、別のスレッドで実行しているものに大混乱が生じます。

GL のニーズによっては、メイン スレッドですべての GL 処理を実行し、メイン スレッドから「その他の処理」を実行する方が簡単な場合があります。

于 2013-11-09T10:05:42.660 に答える