上図のサンプルのように OpenGL で描画した View の上にテキストボックスを表示させたい場合、CAOpenGLLayer
を使うとうまくいく。
OpenGL の glClearColor
で背景を半透明に設定したいときの注意点についてもメモしてある。
背景
- InterfaceBuilder の Object Library にある
NSOpenGLView
は、子要素 (NSButton
など) を持たせられないようになっている。 -
NSButton
などを子要素とせずにNSOpenGLView
の上に重ねるように設置したとしても、OpenGL の描画でNSButton
などの描画が潰れてしまう。 -
NSOpenGLView
の代わりに、NSOpenGLView
オーバーライドしたNSView
を使って子要素を持たせられるようにしても、結局NSButton
などの描画が潰れてしまう。 - 具体的には下図のような描画になってしまう。
- テキストボックスが裏に隠れているように見えるが、実際にはテキストボックスは OpenGL サーフェスの手前に出ており、描画はされていないものの文字は入力できる状態。
解決方法
方針
-
NSView
をオーバーライドしたカスタム View クラスを作り、この View の BackingLayer で OpenGL の描画処理をやる。 - BackingLayer には、
CAOpenGLLayer
を拡張したカスタム Layer クラスを使う。
サンプル実装
/* 指定した opacity の背景の上に、glut の teapot を表示するコードを何処かで実装する */
void DrawTeapotWithBackgroundOpacity(GLfloat opacity);
@interface LayerBackedGLView : NSView
{
}
@end
@interface CustomOpenGLLayer : CAOpenGLLayer
{
}
@end
@implementation CustomOpenGLLayer
-(BOOL)canDrawInCGLContext:(CGLContextObj)ctx
pixelFormat:(CGLPixelFormatObj)pf
forLayerTime:(CFTimeInterval)t
displayTime:(const CVTimeStamp *)ts
{
return YES;
}
-(void)drawInCGLContext:(CGLContextObj)ctx
pixelFormat:(CGLPixelFormatObj)pf
forLayerTime:(CFTimeInterval)t
displayTime:(const CVTimeStamp *)ts
{
CGLSetCurrentContext(ctx);
DrawTeapotWithBackgroundOpacity(0.5);
[super drawInCGLContext:ctx
pixelFormat:pf
forLayerTime:t
displayTime:ts];
}
-(BOOL)isOpaque
{
/* glClearColor で半透明な背景色を指定している場合、YES を返すとうまくいかない。 */
return NO;
}
@end
@implementation LayerBackedGLView : NSView
-(id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
/* ここを YES にすると、layer が必要になったタイミングで makeBackingLayer が呼ばれる */
[self setWantsLayer:YES];
/* Retina 対応. 以下を参照
https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSViewOpenGLAdditions
*/
[self setWantsBestResolutionOpenGLSurface:YES];
return self;
}
-(CALayer*)makeBackingLayer
{
CustomOpenGLLayer* layer = [[CustomOpenGLLayer alloc] init];
[layer setNeedsDisplayOnBoundsChange:YES];
return layer;
}
-(BOOL)isOpaque
{
/* glClearColor で半透明な背景色を指定している場合、YES を返すとうまくいかない。 */
return NO;
}
@end
注意点
OpenGL の描画で、背景色を半透明にしたい時
- サンプル実装のコードコメントにあるように、カスタマイズした
NSView
、CAOpenGLLayer
のisOpaque
関数をオーバーライドしている場合、どちらもYES
を返してはいけない。 -
isOpaque
をオーバーライドしていない場合は特に何もしなくて OK。
Mac OSX Yosemite
-
CAOpenGLLayer
を継承したNSOpenGLLayer
というクラスがあるが、こちらを使うと Yosemite で期待したとおり動かなかった。
参考
- LayerBackedOpenGLView
- Apple による LayerBacked 描画のサンプル。
- OpenGL のサーフェスの上に UI 部品を乗せる場合は Layer-Backed 描画にするといいよ、と書いてある。
- Layer-backed OpenGLView redraws only if window is resized
- 最も参考になった StackOverflow の問答
- NSOpenGLLayer Class Reference