NSTextField 와 겹쳐서 표시할 수 있는 OpenGL 의 View 를 구현하려면



위 그림의 샘플과 같이 OpenGL 로 그린 View 위에 텍스트 박스를 표시시키고 싶은 경우 CAOpenGLLayer 를 사용하면 잘 된다.

OpenGL의 glClearColor에서 배경을 반투명으로 설정하고 싶을 때의 주의점에 대해서도 메모하고 있다.

배경


  • InterfaceBuilder 의 Object Library 에 있는 NSOpenGLView 는, 아이 요소 ( NSButton 등)를 가질 수 없게 되어 있다.
  • NSButton 등을 자식 요소로 하지 않고 NSOpenGLView 위에 겹치도록 설치했다고 해도 OpenGL 그리기로 NSButton 등의 묘화가 무너져 버린다.
  • NSOpenGLView 대신에, NSOpenGLView 오버라이드(override) 한 NSView
  • 구체적으로는 아래 그림과 같은 묘화가 되어 버린다.
  • 텍스트 박스가 뒤에 숨어 있는 것처럼 보이지만, 실제로는 텍스트 박스는 OpenGL 서페이스의 앞에 나와 있어 묘화는 되어 있지 않은 것의 문자는 입력할 수 있는 상태.



  • 해결 방법



    정책


  • NSButton 를 오버라이드(override) 한 커스텀 View 클래스를 작성해, 이 View 의 BackingLayer 로 OpenGL 의 렌더링 처리를 한다.
  • BackingLayer 에는, NSView 를 확장한 커스텀 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 그리기에서 배경색을 반투명하게 만들고 싶을 때


    CAOpenGLLayer NSView CAOpenGLLayer 함수를 오버라이드(override) 하고 있는 경우, 어느쪽도 isOpaque 를 돌려주지 말아 주세요.
  • YES 를 오버라이드(override) 하지 않는 경우는 특별히 아무것도 하지 않아 OK.

  • Mac OSX Yosemite


  • isOpaque 를 계승한 CAOpenGLLayer 라는 클래스가 있지만, 이쪽을 사용하면 Yosemite 에서 기대했던 대로 움직이지 않았다.

  • 참고



  • LayerBackedOpenGLView
  • Apple에 의한 LayerBacked 드로잉 샘플.
  • OpenGL 의 표면 위에 UI 부품을 싣는 경우는 Layer-Backed 묘화로 하면 좋다, 라고 써 있다.


  • Layer-backed OpenGLView redraws only if window is resized
  • 가장 유용한 StackOverflow 질문

  • NSOpenGLLayer 클래스 참조
  • 좋은 웹페이지 즐겨찾기