9

タイプのメインビューと、名前が。NSViewのサブクラスであるサブビューを含むウィンドウがあります。のサブクラスは、Interface Builderで、そのクラスをに設定することで取得されます。これは、Appleサンプルコードレイヤーに裏打ちされたOpenGLViewに従って作成されています。NSOpenGLViewCustomOpenGLViewNSOpenGLViewCustom ViewCustomOpenGLView

このアプリは、たとえば0.05秒ごとにOpenGLContextに何かを描画するように作成されています。Core Animation Layerを無効にすると、ビュー内で移動するオブジェクトを見ることができます。これは、ビューを継続的に再描画した結果です。そして、すべてが完璧に機能します。

CustomOpenGLView再生/停止/eccなどのコントロールボタンを配置するために、上に半透明のビューを表示したいと思います。

これを行うために、にサブビューを追加し、でCoreAnimationLayerをCustomOpenGLView有効にしましたCustomOpenGLView。コントロールボタンは、この新しいサブビューに配置されます。

このようにして、コントロールボタンのあるビューが上に正しく表示されますCustomOpenGLViewが、ビューは再描画されません。これらすべてのビューを含むウィンドウのサイズを変更した場合にのみ描画されます。

その結果、「アニメーション」は表示されません...描画ループの開始時に描画される最初のフレームを表す静止画像のみが表示されます。ウィンドウのサイズを変更すると、ウィンドウのサイズ変更を停止するまで、openGLContextが再描画されます。その後、サイズ変更中に最後の描画が行われた静止画像が再び表示されます。

さらに、描画ループが開始すると、最初の「フレーム」のみが画面に表示されます。たとえば、5秒後にウィンドウのサイズを変更すると、開始から5秒後に描画されるべきものが正確にビューに表示されます。描画ループ。設定する必要があるようです[glView setNeedsDisplay:TRUE]。私はそれをしましたが、何も変わっていません。

間違いはどこにありますか?Core Animation Layerを追加すると再描画が失敗するのはなぜですか?それは私が得ていない何かを意味しますか?

4

1 に答える 1

25

通常NSOpenGLViewの場合は、OpenGLを介して何かを描画し-flushBuffer、のを呼び出すだけでNSOpenGLContext、レンダリングが画面に表示されます。コンテキストがダブルバッファリングされていない場合、これはウィンドウにレンダリングする場合は必要ありません。MacOSXではすべてのウィンドウがすでにダブルバッファリングされているため、glFlush()同様に十分です(実際のフルスクリーンOpenGLレンダリングの場合のみ、アーティファクトを回避するためにダブルバッファリングが必要になります)。次に、OpenGLはビューのピクセルストレージ(実際にはウィンドウのバッキングストレージ)に直接レンダリングします。ダブルバッファリングの場合は、バックバッファーにレンダリングしてから、フロントバッファーと交換します。したがって、新しいコンテンツはすぐに画面に表示されます(実際には、次の画面が更新される前ではありませんが、このような更新は少なくとも1秒間に50〜60回行われます)。

NSOpenGLViewがレイヤーバックの場合、状況は少し異なります。-flushBufferまたはを呼び出すglFlush()と、レンダリングは実際には以前と同じように行われ、画像はビューのピクセルストレージに直接レンダリングされますが、このピクセルストレージはウィンドウのバッキングストレージではなくなります。ビューの「バッキングレイヤー」。つまり、OpenGLイメージが更新され、「レイヤーに描画する」と「画面にレイヤーを表示する」はまったく異なるものであるため、それが発生していることはわかりません。新しいレイヤーコンテンツを表示するにはsetNeedsDisplay:YES、レイヤーバックを呼び出す必要がありますNSOpenGLView

あなたが電話したとき、なぜそれはあなたのために働かなかったのですsetNeedsDisplay:YESか?まず、メインスレッドでこの呼び出しを実行するようにしてください。この呼び出しは任意のスレッドで実行できます。ビューは確実にダーティとしてマークされますが、メインスレッドでこの呼び出しを実行する場合にのみ、再描画呼び出しもスケジュールされます(この呼び出しがないと、ダーティとしてマークされますが、他の親/子ビューが再描画されるまで再描画されません)。別の問題はdrawRect:方法かもしれません。ビューをダーティとしてマークして再描画すると、このメソッドが呼び出され、このメソッドが「描画」するものはすべて、現在レイヤー内にあるコンテンツを上書きします。ビューがレイヤーバックされていない限り、OpenGLコンテンツをどこにレンダリングしたかは問題ではありませんが、レイヤーバックビューの場合は

次のことを試してください。20ミリ秒ごとに起動し、レイヤーバックNSTimerで呼び出すメソッドを呼び出すメインスレッドでを作成します。すべてのOpenGLレンダリングコードをレイヤーバックのメソッドに移動します。それはかなりうまくいくはずです。よりも確実に何かが必要な場合は、 (CV = CoreVideo)を試してください。Aはタイマーのようなものですが、画面が再描画されるたびに起動します。setNeedsDisplay:YESNSOpenGLViewdrawRect:NSOpenGLViewNSTimerCVDisplayLinkCVDisplayLink

アップデート

階層化されたNSOpenGLViewはやや時代遅れであり、10.6以降、実際には不要になりました。内部的には、NSOpenGLViewは、レイヤー化するとNSOpenGLLayerを作成するため、このようなレイヤーを自分で直接使用して、独自のNSOpenGLViewを「構築」することもできます。

  1. の独自のサブクラスを作成し、NSOpenGLLayerそれを呼び出しましょうMyOpenGLLayer
  2. の独自のサブクラスを作成し、NSViewそれを呼び出しましょうMyGLView
  3. オーバーライド- (CALayer *)makeBackingLayerして、の自動リリースされたインスタンスを返しますMyOpenGLLayer
  4. wantsLayer:YESに設定MyGLView

これで、独自のレイヤーバックビューが作成され、NSOpenGLLayerサブクラスによってレイヤーバックされます。レイヤーバックであるため、サブビュー(ボタン、テキストフィールドなど)を追加しても問題ありません。

バッキングレイヤーには、基本的に2つのオプションがあります。

オプション1
正しく公式にサポートされている方法は、メインスレッドでレンダリングを維持することです。そのため、次のことを行う必要があります。

  • 次のフレームを描画できるかどうかに応じて、オーバーライドして/canDrawInContext:...を返します。YESNO
  • オーバーライドdrawInContext:...して、実際のOpenGLレンダリングを実行します。
  • レイヤーを非同期にする(setAsynchronous:YES
  • サイズが変更されるたびにレイヤーが「更新」されることを確認してください(setNeedsDisplayOnBoundsChange:YES)。そうでない場合、レイヤーのサイズが変更されたときにOpenGLバッキングサーフェスのサイズが変更されません(レイヤーが再描画されるたびに、レンダリングされたOpenGLコンテキストが拡大/縮小される必要があります)

AppleはCVDisplayLinkあなたのためにを作成します。それはcanDrawInContext:...起動するたびにメインスレッドを呼び出し、このメソッドが戻るYESと、を呼び出しますdrawInContext:...。これはあなたがそれを行うべき方法です。

レンダリングのコストが高すぎてメインスレッドで実行できない場合は、次のトリックを実行できます。オーバーライドopenGLContextForPixelFormat:...して、以前に作成した別のコンテキスト(コンテキストA)と共有されるコンテキスト(コンテキストB)を作成します。コンテキストAでフレームバッファを作成します(コンテキストBの作成前または作成後に作成できますが、実際には重要ではありません)。必要に応じて、深度および/またはステンシルレンダーバッファをアタッチします(選択したビット深度)。ただし、カラーレンダーバッファの代わりに、カラーアタッチメント(glFramebufferTexture())として「テクスチャ」(テクスチャX)をアタッチします。これで、すべてのカラーレンダリング出力は、そのフレームバッファにレンダリングするときにそのテクスチャに書き込まれます。選択した任意のスレッドでコンテキストAを使用して、このフレームバッファーへのすべてのレンダリングを実行します。レンダリングが完了したら、canDrawInContext:...戻りYESdrawInContext:...単純なクワッドを描画しますこれはアクティブなフレームバッファ全体を埋め(Appleはすでに設定しており、ビューポートも完全に埋めるように設定しています)、Texture Xでテクスチャリングされています。共有コンテキストはすべてのオブジェクト(テクスチャ、フレームバッファなど)も共有するため、これが可能です。等。)。したがって、このdrawInContext:...方法では、単一の単純なテクスチャクワッドを描画する以上のことはできません。それだけです。他のすべての(おそらく高価なレンダリング)は、メインスレッドをブロックすることなく、バックグラウンドスレッドでこのテクスチャに発生します。

オプション2
もう1つのオプションは、Appleによって公式にサポートされておらず、機能する場合と機能しない場合があります。

  • オーバーライドしないでくださいcanDrawInContext:...。デフォルトの実装は常に返さYESれ、それが必要です。
  • オーバーライドdrawInContext:...して、実際のOpenGLレンダリングをすべて実行します。
  • レイヤーを非同期にしないでください。
  • 設定しないでくださいneedsDisplayOnBoundsChange

このレイヤーを再描画するときはいつでも、display直接呼び出して(そうではありません setNeedsDisplay!Appleはそれを呼び出すべきではないと言っていますが、「すべきではない」は「してはいけない」ではありません)、呼び出した後display、を呼び出します[CATransaction flush]。これは、バックグラウンドスレッドから呼び出された場合でも機能します。メソッドは、任意のスレッドdrawInContext:...を呼び出すのと同じスレッドから呼び出されます。display直接呼び出すdisplayと、OpenGLレンダリングコードが実行されますが、新しくレンダリングされたコンテンツはレイヤーのバッキングストレージにのみ表示されます。画面に表示するには、システムにレイヤー合成を実行させる必要があります。[CATransaction flush]まさにそれを行います。クラスメソッドのみを持つクラスCATransaction(インスタンスを作成することはありません)は暗黙的にスレッドセーフであり、いつでもどのスレッドからでも使用できます(必要なときにいつでもどこでも独自にロックを実行します)。

この方法はお勧めしませんが、他のビューで再描画の問題が発生する可能性があるため(メインスレッド以外のスレッドでも再描画される可能性があり、すべてのビューがそれをサポートしているわけではないため)、禁止されておらず、プライベートAPIを使用していません。 Appleの誰もがそれに反対することなく、Appleのメーリングリストで提案されています。

于 2012-06-26T18:07:44.330 に答える