通常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:YES
NSOpenGLView
drawRect:
NSOpenGLView
NSTimer
CVDisplayLink
CVDisplayLink
アップデート
階層化されたNSOpenGLViewはやや時代遅れであり、10.6以降、実際には不要になりました。内部的には、NSOpenGLViewは、レイヤー化するとNSOpenGLLayerを作成するため、このようなレイヤーを自分で直接使用して、独自のNSOpenGLViewを「構築」することもできます。
- の独自のサブクラスを作成し、
NSOpenGLLayer
それを呼び出しましょうMyOpenGLLayer
- の独自のサブクラスを作成し、
NSView
それを呼び出しましょうMyGLView
- オーバーライド
- (CALayer *)makeBackingLayer
して、の自動リリースされたインスタンスを返しますMyOpenGLLayer
wantsLayer:YES
に設定MyGLView
これで、独自のレイヤーバックビューが作成され、NSOpenGLLayerサブクラスによってレイヤーバックされます。レイヤーバックであるため、サブビュー(ボタン、テキストフィールドなど)を追加しても問題ありません。
バッキングレイヤーには、基本的に2つのオプションがあります。
オプション1
正しく公式にサポートされている方法は、メインスレッドでレンダリングを維持することです。そのため、次のことを行う必要があります。
- 次のフレームを描画できるかどうかに応じて、オーバーライドして/
canDrawInContext:...
を返します。YES
NO
- オーバーライド
drawInContext:...
して、実際のOpenGLレンダリングを実行します。
- レイヤーを非同期にする(
setAsynchronous:YES
)
- サイズが変更されるたびにレイヤーが「更新」されることを確認してください(
setNeedsDisplayOnBoundsChange:YES
)。そうでない場合、レイヤーのサイズが変更されたときにOpenGLバッキングサーフェスのサイズが変更されません(レイヤーが再描画されるたびに、レンダリングされたOpenGLコンテキストが拡大/縮小される必要があります)
AppleはCVDisplayLink
あなたのためにを作成します。それはcanDrawInContext:...
起動するたびにメインスレッドを呼び出し、このメソッドが戻るYES
と、を呼び出しますdrawInContext:...
。これはあなたがそれを行うべき方法です。
レンダリングのコストが高すぎてメインスレッドで実行できない場合は、次のトリックを実行できます。オーバーライドopenGLContextForPixelFormat:...
して、以前に作成した別のコンテキスト(コンテキストA)と共有されるコンテキスト(コンテキストB)を作成します。コンテキストAでフレームバッファを作成します(コンテキストBの作成前または作成後に作成できますが、実際には重要ではありません)。必要に応じて、深度および/またはステンシルレンダーバッファをアタッチします(選択したビット深度)。ただし、カラーレンダーバッファの代わりに、カラーアタッチメント(glFramebufferTexture()
)として「テクスチャ」(テクスチャX)をアタッチします。これで、すべてのカラーレンダリング出力は、そのフレームバッファにレンダリングするときにそのテクスチャに書き込まれます。選択した任意のスレッドでコンテキストAを使用して、このフレームバッファーへのすべてのレンダリングを実行します。レンダリングが完了したら、canDrawInContext:...
戻りYES
、drawInContext:...
単純なクワッドを描画しますこれはアクティブなフレームバッファ全体を埋め(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のメーリングリストで提案されています。