Improved fullscreen support on OS X (close #343)

No hard resolution switch is done. Instead a view of the requested
size is displayed in the center of the main screen (letter boxing).

Running a stress test showed that the performance were equal
to a hard switch without the inconvenience of resizing the
user's apps. This also follows the guidelines defined by Apple:

> Avoid changing the display resolution from that chosen by the user.

source:
https://developer.apple.com/library/mac/documentation/graphicsimaging/Conceptual/OpenGL-MacProgGuide/opengl_fullscreen/opengl_cgl.html
This commit is contained in:
Marco Antognini 2014-04-16 18:01:43 +02:00
parent 5a4b54cb9c
commit 8f0037d84d
5 changed files with 172 additions and 183 deletions

View File

@ -75,7 +75,7 @@ m_window(0)
// Create the context. // Create the context.
createContext(shared, VideoMode::getDesktopMode().bitsPerPixel, settings); createContext(shared, VideoMode::getDesktopMode().bitsPerPixel, settings);
// Create a dummy window/view pair (hidden) and asign it our context. // Create a dummy window/view pair (hidden) and assign it our context.
m_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height) m_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
styleMask:NSBorderlessWindowMask styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered backing:NSBackingStoreBuffered

View File

@ -62,7 +62,6 @@ namespace sf {
BOOL m_useKeyRepeat; ///< Key repeat setting BOOL m_useKeyRepeat; ///< Key repeat setting
BOOL m_mouseIsIn; ///< Mouse positional state BOOL m_mouseIsIn; ///< Mouse positional state
NSTrackingArea* m_trackingArea; ///< Mouse tracking area NSTrackingArea* m_trackingArea; ///< Mouse tracking area
NSSize m_realSize; ///< Actual size of the view
// Hidden text view used to convert key event to actual chars. // Hidden text view used to convert key event to actual chars.
// We use a silent responder to prevent sound alerts. // We use a silent responder to prevent sound alerts.
@ -100,18 +99,6 @@ namespace sf {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
-(void)setRequesterTo:(sf::priv::WindowImplCocoa*)requester; -(void)setRequesterTo:(sf::priv::WindowImplCocoa*)requester;
////////////////////////////////////////////////////////////
/// \brief Set the real size of view (it should be the back buffer size)
///
/// If not set, or set to its default value NSZeroSize, the view
/// won't recompute the mouse coordinates before sending them
/// to the requester.
///
/// \param newSize actual size of the view
///
////////////////////////////////////////////////////////////
-(void)setRealSize:(NSSize)newSize;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Compute the position in global coordinate /// \brief Compute the position in global coordinate
/// ///

View File

@ -113,7 +113,6 @@ BOOL isValidTextUnicode(NSEvent* event);
{ {
[self setRequesterTo:0]; [self setRequesterTo:0];
[self enableKeyRepeat]; [self enableKeyRepeat];
m_realSize = NSZeroSize;
// Register for mouse move event // Register for mouse move event
m_mouseIsIn = [self isMouseInside]; m_mouseIsIn = [self isMouseInside];
@ -172,23 +171,9 @@ BOOL isValidTextUnicode(NSEvent* event);
} }
////////////////////////////////////////////////////////
-(void)setRealSize:(NSSize)newSize
{
m_realSize = newSize;
}
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(NSPoint)computeGlobalPositionOfRelativePoint:(NSPoint)point -(NSPoint)computeGlobalPositionOfRelativePoint:(NSPoint)point
{ {
// Recompute the mouse pos if required.
if (!NSEqualSizes(m_realSize, NSZeroSize))
{
point.x = (point.x / m_realSize.width) * [self frame].size.width;
point.y = (point.y / m_realSize.height) * [self frame].size.height;
}
// Note : -[NSWindow convertBaseToScreen:] is deprecated on 10.7 // Note : -[NSWindow convertBaseToScreen:] is deprecated on 10.7
// but the recommended -[NSWindow convertRectToScreen] is not // but the recommended -[NSWindow convertRectToScreen] is not
// available until 10.7. // available until 10.7.
@ -538,13 +523,6 @@ BOOL isValidTextUnicode(NSEvent* event);
float h = [self frame].size.height; float h = [self frame].size.height;
loc.y = h - loc.y; loc.y = h - loc.y;
// Recompute the mouse pos if required.
if (!NSEqualSizes(m_realSize, NSZeroSize))
{
loc.x = (loc.x * m_realSize.width) / [self frame].size.width;
loc.y = (loc.y * m_realSize.height) / [self frame].size.height;
}
return loc; return loc;
} }

View File

@ -58,9 +58,7 @@ namespace sf {
NSWindow* m_window; ///< Underlying Cocoa window to be controlled NSWindow* m_window; ///< Underlying Cocoa window to be controlled
SFOpenGLView* m_oglView; ///< OpenGL view for rendering SFOpenGLView* m_oglView; ///< OpenGL view for rendering
sf::priv::WindowImplCocoa* m_requester; ///< Requester sf::priv::WindowImplCocoa* m_requester; ///< Requester
sf::VideoMode* m_fullscreenMode; ///< Fullscreen mode BOOL m_fullscreen; ///< Indicate whether the window is fullscreen or not
/// Note: C++ ctor/dtor are not called for Obj-C fields! Use manual allocation instead.
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -39,6 +39,24 @@
#import <SFML/Window/OSX/SFWindowController.h> #import <SFML/Window/OSX/SFWindowController.h>
#import <OpenGL/OpenGL.h> #import <OpenGL/OpenGL.h>
////////////////////////////////////////////////////////////
/// SFBlackView is a simple view filled with black, nothing more
///
////////////////////////////////////////////////////////////
@interface SFBlackView : NSView
@end
@implementation SFBlackView
////////////////////////////////////////////////////////////
-(void)drawRect:(NSRect)dirtyRect
{
[[NSColor blackColor] setFill];
NSRectFill(dirtyRect);
}
@end
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// SFWindowController class: private interface /// SFWindowController class: private interface
/// ///
@ -73,15 +91,17 @@
{ {
if ((self = [super init])) if ((self = [super init]))
{ {
m_window = nil;
m_oglView = nil;
m_requester = 0; m_requester = 0;
m_fullscreenMode = new sf::VideoMode(); m_fullscreen = NO;
// Retain the window for our own use. // Retain the window for our own use.
m_window = window; m_window = window;
if (m_window == nil) if (m_window == nil)
{ {
sf::err() << "No window was given to initWithWindow:." << std::endl; sf::err() << "No window was given to -[SFWindowController -initWithWindow:]." << std::endl;
return self; return self;
} }
@ -91,7 +111,7 @@
if (m_oglView == nil) if (m_oglView == nil)
{ {
sf::err() << "Could not create an instance of NSOpenGLView " sf::err() << "Could not create an instance of NSOpenGLView "
<< "in (SFWindowController -initWithWindow:)." << "in -[SFWindowController -initWithWindow:]."
<< std::endl; << std::endl;
return self; return self;
} }
@ -121,133 +141,156 @@
if ((self = [super init])) if ((self = [super init]))
{ {
m_window = nil;
m_oglView = nil;
m_requester = 0; m_requester = 0;
m_fullscreenMode = new sf::VideoMode(); m_fullscreen = style & sf::Style::Fullscreen;
// Create our window size. if (m_fullscreen)
NSRect rect = NSZeroRect; [self setupFullscreenViewWithMode:mode];
if ((style & sf::Style::Fullscreen) && (mode != sf::VideoMode::getDesktopMode()))
{
// We use desktop mode to size the window
// but we set the back buffer size to 'mode' in applyContext method.
*m_fullscreenMode = mode;
sf::VideoMode dm = sf::VideoMode::getDesktopMode();
rect = NSMakeRect(0, 0, dm.width, dm.height);
}
else else
{ [self setupWindowWithMode:mode andStyle:style];
// no fullscreen requested. }
rect = NSMakeRect(0, 0, mode.width, mode.height);
}
// Convert the SFML window style to Cocoa window style.
unsigned int nsStyle = NSBorderlessWindowMask;
// if fullscrean we keep our NSBorderlessWindowMask.
if (!(style & sf::Style::Fullscreen))
{
if (style & sf::Style::Titlebar)
nsStyle |= NSTitledWindowMask | NSMiniaturizableWindowMask;
if (style & sf::Style::Resize)
nsStyle |= NSResizableWindowMask;
if (style & sf::Style::Close)
nsStyle |= NSClosableWindowMask;
}
// Create the window.
m_window = [[SFWindow alloc] initWithContentRect:rect
styleMask:nsStyle
backing:NSBackingStoreBuffered
defer:NO]; // Don't defer it!
/*
"YES" produces some "invalid drawable".
See http://www.cocoabuilder.com/archive/cocoa/152482-nsviews-and-nsopenglcontext-invalid-drawable-error.html
[...]
As best as I can figure, this is happening because the NSWindow (and
hence my view) are not visible on screen yet, and the system doesn't like that.
[...]
*/
if (m_window == nil)
{
sf::err() << "Could not create an instance of NSWindow "
<< "in (SFWindowController -initWithMode:andStyle:)."
<< std::endl;
return self;
}
// Apply special feature for fullscreen window.
if (style & sf::Style::Fullscreen)
{
// We place the window above everything else.
[m_window setOpaque:YES];
[m_window setHidesOnDeactivate:YES];
[m_window setLevel:NSMainMenuWindowLevel+1];
// And hide the menu bar
[NSMenu setMenuBarVisible:NO];
/* ---------------------------
* | Note for future version |
* ---------------------------
*
* starting with OS 10.6 NSView provides
* a new method -enterFullScreenMode:withOptions:
* which could be a good alternative.
*/
}
// Centre the window to be cool =)
[m_window center];
// Create the view.
m_oglView = [[SFOpenGLView alloc] initWithFrame:[[m_window contentView] frame]];
if (m_oglView == nil)
{
sf::err() << "Could not create an instance of NSOpenGLView "
<< "in (SFWindowController -initWithMode:andStyle:)."
<< std::endl;
return self;
}
// If a fullscreen window was requested...
if (style & sf::Style::Fullscreen)
{
/// ... we tell the OpenGL view
[m_oglView enterFullscreen];
// ... and if the resolution is not the default one...
if (mode != sf::VideoMode::getDesktopMode())
{
// ... we set the "real size" of the view (that is the back buffer size).
[m_oglView setRealSize:NSMakeSize(m_fullscreenMode->width, m_fullscreenMode->height)];
}
}
// Set the view to the window as its content view.
[m_window setContentView:m_oglView];
// Register for event.
[m_window setDelegate:self];
[m_window setAcceptsMouseMovedEvents:YES];
[m_window setIgnoresMouseEvents:NO];
// And some other things...
[m_window setAutodisplay:YES];
[m_window setReleasedWhenClosed:NO];
} // if super init ok
return self; return self;
} }
////////////////////////////////////////////////////////
-(void)setupFullscreenViewWithMode:(const sf::VideoMode&)mode
{
// Create a screen-sized window on the main display
sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
NSRect windowRect = NSMakeRect(0, 0, desktop.width, desktop.height);
m_window = [[SFWindow alloc] initWithContentRect:windowRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
if (m_window == nil)
{
sf::err() << "Could not create an instance of NSWindow "
<< "in -[SFWindowController setupFullscreenViewWithMode:]."
<< std::endl;
return;
}
// Set the window level to be above the menu bar
[m_window setLevel:NSMainMenuWindowLevel+1];
// More window configuration...
[m_window setOpaque:YES];
[m_window setHidesOnDeactivate:YES];
[m_window setAutodisplay:YES];
[m_window setReleasedWhenClosed:YES];
// Register for event
[m_window setDelegate:self];
[m_window setAcceptsMouseMovedEvents:YES];
[m_window setIgnoresMouseEvents:NO];
// Create a master view containing our OpenGL view
NSView* masterView = [[SFBlackView alloc] initWithFrame:windowRect];
if (masterView == nil)
{
sf::err() << "Could not create an instance of SFBlackView "
<< "in -[SFWindowController setupFullscreenViewWithMode:]."
<< std::endl;
return;
}
// Create our OpenGL view size and the view
CGFloat x = (desktop.width - mode.width) / 2.0;
CGFloat y = (desktop.height - mode.height) / 2.0;
NSRect oglRect = NSMakeRect(x, y, mode.width, mode.height);
m_oglView = [[SFOpenGLView alloc] initWithFrame:oglRect];
if (m_oglView == nil)
{
sf::err() << "Could not create an instance of NSOpenGLView "
<< "in -[SFWindowController setupFullscreenViewWithMode:]."
<< std::endl;
return;
}
// Populate the window and views
[masterView addSubview:m_oglView];
[m_window setContentView:masterView];
// Finalize setup
[m_oglView enterFullscreen];
}
////////////////////////////////////////////////////////
-(void)setupWindowWithMode:(const sf::VideoMode&)mode andStyle:(unsigned long)style
{
// We know that style & sf::Style::Fullscreen is false.
// Create our window size.
NSRect rect = NSMakeRect(0, 0, mode.width, mode.height);
// Convert the SFML window style to Cocoa window style.
unsigned int nsStyle = NSBorderlessWindowMask;
if (style & sf::Style::Titlebar)
nsStyle |= NSTitledWindowMask | NSMiniaturizableWindowMask;
if (style & sf::Style::Resize)
nsStyle |= NSResizableWindowMask;
if (style & sf::Style::Close)
nsStyle |= NSClosableWindowMask;
// Create the window.
m_window = [[SFWindow alloc] initWithContentRect:rect
styleMask:nsStyle
backing:NSBackingStoreBuffered
defer:NO]; // Don't defer it!
/*
"YES" produces some "invalid drawable".
See http://www.cocoabuilder.com/archive/cocoa/152482-nsviews-and-nsopenglcontext-invalid-drawable-error.html
[...]
As best as I can figure, this is happening because the NSWindow (and
hence my view) are not visible on screen yet, and the system doesn't like that.
[...]
*/
if (m_window == nil)
{
sf::err() << "Could not create an instance of NSWindow "
<< "in -[SFWindowController setupWindowWithMode:andStyle:]."
<< std::endl;
return;
}
// Create the view.
m_oglView = [[SFOpenGLView alloc] initWithFrame:[[m_window contentView] frame]];
if (m_oglView == nil)
{
sf::err() << "Could not create an instance of NSOpenGLView "
<< "in -[SFWindowController setupWindowWithMode:andStyle:]."
<< std::endl;
return;
}
// Set the view to the window as its content view.
[m_window setContentView:m_oglView];
// Register for event.
[m_window setDelegate:self];
[m_window setAcceptsMouseMovedEvents:YES];
[m_window setIgnoresMouseEvents:NO];
// And some other things...
[m_window center];
[m_window setAutodisplay:YES];
[m_window setReleasedWhenClosed:YES];
}
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(void)dealloc -(void)dealloc
{ {
@ -256,7 +299,6 @@
m_window = nil; m_window = nil;
m_oglView = nil; m_oglView = nil;
delete m_fullscreenMode;
} }
@ -335,10 +377,7 @@
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
-(NSSize)size -(NSSize)size
{ {
if (*m_fullscreenMode == sf::VideoMode()) return [m_oglView frame].size;
return [m_oglView frame].size;
else
return NSMakeSize(m_fullscreenMode->width, m_fullscreenMode->height);
} }
@ -472,19 +511,6 @@
{ {
[m_oglView setOpenGLContext:context]; [m_oglView setOpenGLContext:context];
[context setView:m_oglView]; [context setView:m_oglView];
// If fullscreen was requested and the mode used to create the window
// was not the desktop mode, we change the back buffer size of the
// context.
if (*m_fullscreenMode != sf::VideoMode())
{
CGLContextObj cgcontext = (CGLContextObj)[context CGLContextObj];
GLint dim[2] = {m_fullscreenMode->width, m_fullscreenMode->height};
CGLSetParameter(cgcontext, kCGLCPSurfaceBackingSize, dim);
CGLEnable(cgcontext, kCGLCESurfaceBackingSize);
}
} }
@ -516,7 +542,7 @@
m_requester->windowGainedFocus(); m_requester->windowGainedFocus();
if (*m_fullscreenMode != sf::VideoMode()) if (m_fullscreen)
[m_oglView enterFullscreen]; [m_oglView enterFullscreen];
} }
@ -532,7 +558,7 @@
m_requester->windowLostFocus(); m_requester->windowLostFocus();
if (*m_fullscreenMode != sf::VideoMode()) if (m_fullscreen)
[m_oglView exitFullscreen]; [m_oglView exitFullscreen];
} }