/* $Id: DevVGA-SVGA3d-cocoa.m 54638 2015-03-05 00:33:47Z vboxsync $ */ /** @file * VirtualBox OpenGL Cocoa Window System Helper Implementation. * * @remarks Inspired by HostServices/SharedOpenGL/render/renderspu_cocoa_helper.m. */ /* * Copyright (C) 2009-2014 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ #include "DevVGA-SVGA3d-cocoa.h" #import #import #include #include /* Debug macros */ #if 0 /*def DEBUG_VERBOSE*/ /*# error "should be disabled!"*/ # define DEBUG_INFO(text) do { \ crWarning text ; \ Assert(0); \ } while (0) # define DEBUG_MSG(text) \ printf text # define DEBUG_WARN(text) do { \ crWarning text ; \ Assert(0); \ } while (0) # define DEBUG_MSG_1(text) \ DEBUG_MSG(text) # define DEBUG_FUNC_ENTER() \ int cchDebugFuncEnter = printf("==>%s\n", __PRETTY_FUNCTION__) #define DEBUG_FUNC_LEAVE() do { \ DEBUG_MSG(("<==%s\n", __PRETTY_FUNCTION__)); \ } while (0) #define DEBUG_FUNC_RET(valuefmtnl) do { \ DEBUG_MSG(("<==%s returns", __PRETTY_FUNCTION__)); \ DEBUG_MSG(valuefmtnl); \ } while (0) #else # define DEBUG_INFO(text) do { \ crInfo text ; \ } while (0) # define DEBUG_MSG(text) \ do {} while (0) # define DEBUG_WARN(text) do { \ crWarning text ; \ } while (0) # define DEBUG_MSG_1(text) \ do {} while (0) # define DEBUG_FUNC_ENTER() int cchDebugFuncEnter = 0 # define DEBUG_FUNC_LEAVE() NOREF(cchDebugFuncEnter) # define DEBUG_FUNC_RET(valuefmtnl) DEBUG_FUNC_LEAVE() #endif # define CHECK_GL_ERROR()\ do {} while (0) /** Custom OpenGL context class. * * This implementation doesn't allow to set a view to the * context, but save the view for later use. Also it saves a copy of the * pixel format used to create that context for later use. */ @interface VMSVGA3DOpenGLContext: NSOpenGLContext { @private NSOpenGLPixelFormat *m_pPixelFormat; NSView *m_pView; } - (NSOpenGLPixelFormat*)openGLPixelFormat; @end @interface VMSVGA3DOverlayView: NSView { @private NSView *m_pParentView; NSWindow *m_pOverlayWin; NSOpenGLContext *m_pGLCtx; /* Position/Size tracking */ NSPoint m_Pos; NSSize m_Size; /** This is necessary for clipping on the root window */ NSRect m_RootRect; float m_yInvRootOffset; } - (id)initWithFrame:(NSRect)frame parentView:(NSView*)pparentView; - (void)setGLCtx:(NSOpenGLContext*)pCtx; - (NSOpenGLContext*)glCtx; - (void)setOverlayWin: (NSWindow*)win; - (NSWindow*)overlayWin; - (void)setPos:(NSPoint)pos; - (NSPoint)pos; - (void)setSize:(NSSize)size; - (NSSize) size; - (void)updateViewportCS; - (void)reshape; @end /** Helper view. * * This view is added as a sub view of the parent view to track * main window changes. Whenever the main window is changed * (which happens on fullscreen/seamless entry/exit) the overlay * window is informed & can add them self as a child window * again. */ @class VMSVGA3DOverlayWindow; @interface VMSVGA3DOverlayHelperView: NSView { @private VMSVGA3DOverlayWindow *m_pOverlayWindow; } -(id)initWithOverlayWindow:(VMSVGA3DOverlayWindow*)pOverlayWindow; @end /** Custom window class. * * This is the overlay window which contains our custom NSView. * Its a direct child of the Qt Main window. It marks its background * transparent & non opaque to make clipping possible. It also disable mouse * events and handle frame change events of the parent view. */ @interface VMSVGA3DOverlayWindow: NSWindow { @private NSView *m_pParentView; VMSVGA3DOverlayView *m_pOverlayView; VMSVGA3DOverlayHelperView *m_pOverlayHelperView; NSThread *m_Thread; } - (id)initWithParentView:(NSView*)pParentView overlayView:(VMSVGA3DOverlayView*)pOverlayView; - (void)parentWindowFrameChanged:(NSNotification *)note; - (void)parentWindowChanged:(NSWindow*)pWindow; @end /******************************************************************************** * * VMSVGA3DOpenGLContext class implementation * ********************************************************************************/ @implementation VMSVGA3DOpenGLContext -(id)initWithFormat:(NSOpenGLPixelFormat*)format shareContext:(NSOpenGLContext*)share { DEBUG_FUNC_ENTER(); m_pPixelFormat = NULL; m_pView = NULL; self = [super initWithFormat:format shareContext:share]; if (self) m_pPixelFormat = format; DEBUG_MSG(("OCTX(%p): init VMSVGA3DOpenGLContext\n", (void*)self)); DEBUG_FUNC_RET(("%p\n", (void *)self)); return self; } - (void)dealloc { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OCTX(%p): dealloc VMSVGA3DOpenGLContext\n", (void*)self)); [m_pPixelFormat release]; m_pPixelFormat = NULL; m_pView = NULL; [super dealloc]; DEBUG_FUNC_LEAVE(); } -(bool)isDoubleBuffer { DEBUG_FUNC_ENTER(); GLint val; [m_pPixelFormat getValues:&val forAttribute:NSOpenGLPFADoubleBuffer forVirtualScreen:0]; DEBUG_FUNC_RET(("%d\n", val == 1 ? YES : NO)); return val == 1 ? YES : NO; } -(void)setView:(NSView*)view { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OCTX(%p): setView: new view: %p\n", (void*)self, (void*)view)); m_pView = view; DEBUG_FUNC_LEAVE(); } -(NSView*)view { DEBUG_FUNC_ENTER(); DEBUG_FUNC_RET(("%p\n", (void *)m_pView)); return m_pView; } -(void)clearDrawable { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OCTX(%p): clearDrawable\n", (void*)self)); m_pView = NULL; [super clearDrawable]; DEBUG_FUNC_LEAVE(); } -(NSOpenGLPixelFormat*)openGLPixelFormat { DEBUG_FUNC_ENTER(); DEBUG_FUNC_RET(("%p\n", (void *)m_pPixelFormat)); return m_pPixelFormat; } @end /******************************************************************************** * * VMSVGA3DOverlayHelperView class implementation * ********************************************************************************/ @implementation VMSVGA3DOverlayHelperView -(id)initWithOverlayWindow:(VMSVGA3DOverlayWindow*)pOverlayWindow { DEBUG_FUNC_ENTER(); self = [super initWithFrame:NSZeroRect]; m_pOverlayWindow = pOverlayWindow; DEBUG_MSG(("OHVW(%p): init OverlayHelperView\n", (void*)self)); DEBUG_FUNC_RET(("%p\n", (void *)self)); return self; } -(void)viewDidMoveToWindow { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OHVW(%p): viewDidMoveToWindow: new win: %p\n", (void*)self, (void*)[self window])); [m_pOverlayWindow parentWindowChanged:[self window]]; DEBUG_FUNC_LEAVE(); } @end /******************************************************************************** * * VMSVGA3DOverlayWindow class implementation * ********************************************************************************/ @implementation VMSVGA3DOverlayWindow - (id)initWithParentView:(NSView*)pParentView overlayView:(VMSVGA3DOverlayView*)pOverlayView { DEBUG_FUNC_ENTER(); NSWindow *pParentWin = nil; if((self = [super initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO])) { m_pParentView = pParentView; m_pOverlayView = pOverlayView; m_Thread = [NSThread currentThread]; [m_pOverlayView setOverlayWin: self]; m_pOverlayHelperView = [[VMSVGA3DOverlayHelperView alloc] initWithOverlayWindow:self]; /* Add the helper view as a child of the parent view to get notifications */ [pParentView addSubview:m_pOverlayHelperView]; /* Make sure this window is transparent */ #ifdef SHOW_WINDOW_BACKGROUND /* For debugging */ [self setBackgroundColor:[NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:0.7]]; #else [self setBackgroundColor:[NSColor clearColor]]; #endif [self setOpaque:NO]; [self setAlphaValue:.999]; /* Disable mouse events for this window */ [self setIgnoresMouseEvents:YES]; pParentWin = [m_pParentView window]; /* Initial set the position to the parents view top/left (Compiz fix). */ [self setFrameOrigin: [pParentWin convertBaseToScreen: [m_pParentView convertPoint:NSZeroPoint toView:nil]]]; /* Set the overlay view as our content view */ [self setContentView:m_pOverlayView]; /* Add ourself as a child to the parent views window. Note: this has to * be done last so that everything else is setup in * parentWindowChanged. */ [pParentWin addChildWindow:self ordered:NSWindowAbove]; } DEBUG_MSG(("OWIN(%p): init OverlayWindow\n", (void*)self)); DEBUG_FUNC_RET(("%p\n", (void *)self)); return self; } - (void)dealloc { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OWIN(%p): dealloc OverlayWindow\n", (void*)self)); [[NSNotificationCenter defaultCenter] removeObserver:self]; [m_pOverlayHelperView removeFromSuperview]; [m_pOverlayHelperView release]; [super dealloc]; DEBUG_FUNC_LEAVE(); } - (void)parentWindowFrameChanged:(NSNotification*)pNote { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OWIN(%p): parentWindowFrameChanged\n", (void*)self)); [m_pOverlayView reshape]; DEBUG_FUNC_LEAVE(); } - (void)parentWindowChanged:(NSWindow*)pWindow { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OWIN(%p): parentWindowChanged\n", (void*)self)); [[NSNotificationCenter defaultCenter] removeObserver:self]; if(pWindow != nil) { /* Ask to get notifications when our parent window frame changes. */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentWindowFrameChanged:) name:NSWindowDidResizeNotification object:pWindow]; /* Add us self as child window */ [pWindow addChildWindow:self ordered:NSWindowAbove]; [m_pOverlayView reshape]; } DEBUG_FUNC_LEAVE(); } @end /******************************************************************************** * * VMSVGA3DOverlayView class implementation * ********************************************************************************/ @implementation VMSVGA3DOverlayView - (id)initWithFrame:(NSRect) frame parentView:(NSView*)pParentView { DEBUG_FUNC_ENTER(); m_pParentView = pParentView; /* Make some reasonable defaults */ m_pGLCtx = nil; m_Pos = NSZeroPoint; m_Size = NSMakeSize(1, 1); m_RootRect = NSMakeRect(0, 0, m_Size.width, m_Size.height); m_yInvRootOffset = 0; self = [super initWithFrame: frame]; DEBUG_MSG(("OVIW(%p): init VMSVGA3DOverlayView\n", (void*)self)); DEBUG_FUNC_RET(("%p\n", (void *)self)); return self; } - (void)cleanupData { DEBUG_FUNC_ENTER(); /*[self deleteDockTile];*/ [self setGLCtx:nil]; #if 0 if (m_pSharedGLCtx) { if ([m_pSharedGLCtx view] == self) [m_pSharedGLCtx clearDrawable]; [m_pSharedGLCtx release]; m_pSharedGLCtx = nil; CrBltTerm(m_pBlitter); RTMemFree(m_pBlitter); m_pBlitter = nil; } #endif /*[self clearVisibleRegions];*/ DEBUG_FUNC_LEAVE(); } - (void)dealloc { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OVIW(%p): dealloc OverlayView\n", (void*)self)); [self cleanupData]; [super dealloc]; DEBUG_FUNC_LEAVE(); } - (void)setGLCtx:(NSOpenGLContext*)pCtx { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OVIW(%p): setGLCtx: new ctx: %p (old: %p)\n", (void*)self, (void*)pCtx, (void *)m_pGLCtx)); if (m_pGLCtx == pCtx) { DEBUG_FUNC_LEAVE(); return; } /* ensure the context drawable is cleared to avoid holding a reference to inexistent view */ if (m_pGLCtx) { [m_pGLCtx clearDrawable]; [m_pGLCtx release]; /*[m_pGLCtx performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];*/ } m_pGLCtx = pCtx; if (pCtx) [pCtx retain]; DEBUG_FUNC_LEAVE(); } - (NSOpenGLContext*)glCtx { DEBUG_FUNC_ENTER(); DEBUG_FUNC_RET(("%p\n", (void *)m_pGLCtx)); return m_pGLCtx; } - (void)setOverlayWin:(NSWindow*)pWin { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OVIW(%p): setOverlayWin: new win: %p\n", (void*)self, (void*)pWin)); m_pOverlayWin = pWin; DEBUG_FUNC_LEAVE(); } - (NSWindow*)overlayWin { DEBUG_FUNC_ENTER(); DEBUG_FUNC_RET(("%p\n", (void *)m_pOverlayWin)); return m_pOverlayWin; } - (void)setPos:(NSPoint)pos { DEBUG_FUNC_ENTER(); m_Pos = pos; [self reshape]; DEBUG_FUNC_LEAVE(); } - (NSPoint)pos { DEBUG_FUNC_ENTER(); DEBUG_FUNC_RET(("%f,%f\n", m_Pos.x, m_Pos.y)); return m_Pos; } - (void)setSize:(NSSize)size { DEBUG_FUNC_ENTER(); m_Size = size; [self reshape]; DEBUG_FUNC_LEAVE(); } - (NSSize)size { DEBUG_FUNC_ENTER(); DEBUG_FUNC_RET(("%f,%f\n", m_Size.width, m_Size.height)); return m_Size; } - (void)updateViewportCS { DEBUG_FUNC_ENTER(); DEBUG_MSG(("OVIW(%p): updateViewport\n", (void*)self)); /* Update the viewport for our OpenGL view */ /* [m_pSharedGLCtx update]; */ /* Clear background to transparent */ /* glClearColor(0.0f, 0.0f, 0.0f, 0.0f);*/ DEBUG_FUNC_LEAVE(); } - (void)reshape { DEBUG_FUNC_ENTER(); NSRect parentFrame = NSZeroRect; NSPoint parentPos = NSZeroPoint; NSPoint childPos = NSZeroPoint; NSRect childFrame = NSZeroRect; NSRect newFrame = NSZeroRect; DEBUG_MSG(("OVIW(%p): reshape\n", (void*)self)); /* Getting the right screen coordinates of the parents frame is a little bit * complicated. */ parentFrame = [m_pParentView frame]; DEBUG_MSG(("FIXED parentFrame [%f:%f], [%f:%f]\n", parentFrame.origin.x, parentFrame.origin.y, parentFrame.size.width, parentFrame.size.height)); parentPos = [[m_pParentView window] convertBaseToScreen:[[m_pParentView superview] convertPointToBase:NSMakePoint(parentFrame.origin.x, parentFrame.origin.y + parentFrame.size.height)]]; parentFrame.origin.x = parentPos.x; parentFrame.origin.y = parentPos.y; /* Calculate the new screen coordinates of the overlay window. */ childPos = NSMakePoint(m_Pos.x, m_Pos.y + m_Size.height); childPos = [[m_pParentView window] convertBaseToScreen:[[m_pParentView superview] convertPointToBase:childPos]]; DEBUG_MSG(("FIXED childPos(screen) [%f:%f]\n", childPos.x, childPos.y)); /* Make a frame out of it. */ childFrame = NSMakeRect(childPos.x, childPos.y, m_Size.width, m_Size.height); DEBUG_MSG(("FIXED childFrame [%f:%f], [%f:%f]\n", childFrame.origin.x, childFrame.origin.y, childFrame.size.width, childFrame.size.height)); /* We have to make sure that the overlay window will not be displayed out * of the parent window. So intersect both frames & use the result as the new * frame for the window. */ newFrame = NSIntersectionRect(parentFrame, childFrame); DEBUG_MSG(("[%p]: parentFrame pos[%f : %f] size[%f : %f]\n", (void*)self, parentFrame.origin.x, parentFrame.origin.y, parentFrame.size.width, parentFrame.size.height)); DEBUG_MSG(("[%p]: childFrame pos[%f : %f] size[%f : %f]\n", (void*)self, childFrame.origin.x, childFrame.origin.y, childFrame.size.width, childFrame.size.height)); DEBUG_MSG(("[%p]: newFrame pos[%f : %f] size[%f : %f]\n", (void*)self, newFrame.origin.x, newFrame.origin.y, newFrame.size.width, newFrame.size.height)); /* Later we have to correct the texture position in the case the window is * out of the parents window frame. So save the shift values for later use. */ m_RootRect.origin.x = newFrame.origin.x - childFrame.origin.x; m_RootRect.origin.y = childFrame.size.height + childFrame.origin.y - (newFrame.size.height + newFrame.origin.y); m_RootRect.size = newFrame.size; m_yInvRootOffset = newFrame.origin.y - childFrame.origin.y; DEBUG_MSG(("[%p]: m_RootRect pos[%f : %f] size[%f : %f]\n", (void*)self, m_RootRect.origin.x, m_RootRect.origin.y, m_RootRect.size.width, m_RootRect.size.height)); /* Set the new frame. */ [[self window] setFrame:newFrame display:YES]; #if 0 /* Make sure the context is updated according */ /* [self updateViewport]; */ if (m_pSharedGLCtx) { VBOX_CR_RENDER_CTX_INFO CtxInfo; vboxCtxEnter(m_pSharedGLCtx, &CtxInfo); [self updateViewportCS]; vboxCtxLeave(&CtxInfo); } #endif DEBUG_FUNC_LEAVE(); } @end void vmsvga3dCocoaCreateContext(NativeNSOpenGLContextRef *ppCtx, NativeNSOpenGLContextRef pShareCtx, bool fOtherProfile) { DEBUG_FUNC_ENTER(); NSOpenGLPixelFormat *pFmt = nil; NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; // Consider to remove it and check if it's harmless. NSOpenGLPixelFormatAttribute attribs[] = { NSOpenGLPFAOpenGLProfile, (NSOpenGLPixelFormatAttribute)0, //NSOpenGLPFAWindow, - obsolete/deprecated, try work without it... NSOpenGLPFAAccelerated, NSOpenGLPFADoubleBuffer, NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)24, NSOpenGLPFAAlphaSize, (NSOpenGLPixelFormatAttribute)8, NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)24, 0 }; attribs[1] = fOtherProfile ? NSOpenGLProfileVersion3_2Core : NSOpenGLProfileVersionLegacy; /* Choose a pixel format */ pFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; if (pFmt) { *ppCtx = [[VMSVGA3DOpenGLContext alloc] initWithFormat:pFmt shareContext:pShareCtx]; DEBUG_MSG(("New context %p\n", (void *)*ppCtx)); } else { AssertFailed(); *ppCtx = NULL; } [pPool release]; DEBUG_FUNC_LEAVE(); } void vmsvga3dCocoaDestroyContext(NativeNSOpenGLContextRef pCtx) { DEBUG_FUNC_ENTER(); NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; [pCtx release]; [pPool release]; DEBUG_FUNC_LEAVE(); } void vmsvga3dCocoaCreateView(NativeNSViewRef *ppView, NativeNSViewRef pParentView) { DEBUG_FUNC_ENTER(); NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; /* Create our worker view */ VMSVGA3DOverlayView* pView = [[VMSVGA3DOverlayView alloc] initWithFrame:NSZeroRect parentView:pParentView]; if (pView) { /* We need a real window as container for the view */ [[VMSVGA3DOverlayWindow alloc] initWithParentView:pParentView overlayView:pView]; /* Return the freshly created overlay view */ *ppView = pView; } [pPool release]; DEBUG_FUNC_LEAVE(); } void vmsvga3dCocoaDestroyView(NativeNSViewRef pView) { DEBUG_FUNC_ENTER(); NSWindow *pWin = nil; NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; [pView setHidden: YES]; [pWin release]; [pView release]; [pPool release]; DEBUG_FUNC_LEAVE(); } void vmsvga3dCocoaViewSetPosition(NativeNSViewRef pView, NativeNSViewRef pParentView, int x, int y) { DEBUG_FUNC_ENTER(); NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; [(VMSVGA3DOverlayView*)pView setPos:NSMakePoint(x, y)]; [pPool release]; } void vmsvga3dCocoaViewSetSize(NativeNSViewRef pView, int w, int h) { DEBUG_FUNC_ENTER(); NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; [(VMSVGA3DOverlayView*)pView setSize:NSMakeSize(w, h)]; [pPool release]; DEBUG_FUNC_LEAVE(); } void vmsvga3dCocoaViewMakeCurrentContext(NativeNSViewRef pView, NativeNSOpenGLContextRef pCtx) { DEBUG_FUNC_ENTER(); NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; DEBUG_MSG(("cocoaViewMakeCurrentContext(%p, %p)\n", (void*)pView, (void*)pCtx)); /* Always flush before flush. glXMakeCurrent and wglMakeCurrent does this implicitly, seemingly NSOpenGLContext::makeCurrentContext doesn't. */ if ([NSOpenGLContext currentContext] != 0) glFlush(); if (pView) { [(VMSVGA3DOverlayView*)pView setGLCtx:pCtx]; /* [(VMSVGA3DOverlayView*)pView makeCurrentFBO];*/ [pCtx makeCurrentContext]; } else { [NSOpenGLContext clearCurrentContext]; } [pPool release]; DEBUG_FUNC_LEAVE(); } void vmsvga3dCocoaSwapBuffers(NativeNSViewRef pView, NativeNSOpenGLContextRef pCtx) { DEBUG_FUNC_ENTER(); NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; [pCtx flushBuffer]; [pPool release]; DEBUG_FUNC_LEAVE(); }