2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997-2009 Sam Lantinga
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "SDL_config.h"
24 #include "SDL_QuartzVideo.h"
25 #include "SDL_QuartzWindow.h"
27 #ifdef __powerpc__ /* I'm gambling they fixed this by 10.4. --ryan. */
29 Add methods to get at private members of NSScreen.
30 Since there is a bug in Apple's screen switching code
31 that does not update this variable when switching
32 to fullscreen, we'll set it manually (but only for the
35 @interface NSScreen (NSScreenAccess)
36 - (void) setFrame:(NSRect)frame;
39 @implementation NSScreen (NSScreenAccess)
40 - (void) setFrame:(NSRect)frame;
45 static inline void QZ_SetFrame(NSScreen *nsscreen, NSRect frame)
47 [nsscreen setFrame:frame];
50 static inline void QZ_SetFrame(NSScreen *nsscreen, NSRect frame)
55 @interface SDLTranslatorResponder : NSTextView
58 - (void) doCommandBySelector:(SEL)myselector;
61 @implementation SDLTranslatorResponder
62 - (void) doCommandBySelector:(SEL) myselector {}
65 /* absent in 10.3.9. */
66 CG_EXTERN CGImageRef CGBitmapContextCreateImage (CGContextRef);
68 /* Bootstrap functions */
69 static int QZ_Available ();
70 static SDL_VideoDevice* QZ_CreateDevice (int device_index);
71 static void QZ_DeleteDevice (SDL_VideoDevice *device);
73 /* Initialization, Query, Setup, and Redrawing functions */
74 static int QZ_VideoInit (_THIS, SDL_PixelFormat *video_format);
76 static SDL_Rect** QZ_ListModes (_THIS, SDL_PixelFormat *format,
78 static void QZ_UnsetVideoMode (_THIS, BOOL to_desktop);
80 static SDL_Surface* QZ_SetVideoMode (_THIS, SDL_Surface *current,
81 int width, int height, int bpp,
83 static int QZ_ToggleFullScreen (_THIS, int on);
84 static int QZ_SetColors (_THIS, int first_color,
85 int num_colors, SDL_Color *colors);
87 static int QZ_LockDoubleBuffer (_THIS, SDL_Surface *surface);
88 static void QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface);
89 static int QZ_ThreadFlip (_THIS);
90 static int QZ_FlipDoubleBuffer (_THIS, SDL_Surface *surface);
91 static void QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects);
93 static void QZ_DirectUpdate (_THIS, int num_rects, SDL_Rect *rects);
94 static void QZ_UpdateRects (_THIS, int num_rects, SDL_Rect *rects);
95 static void QZ_VideoQuit (_THIS);
97 /* Hardware surface functions (for fullscreen mode only) */
98 #if 0 /* Not used (apparently, it's really slow) */
99 static int QZ_FillHWRect (_THIS, SDL_Surface *dst, SDL_Rect *rect, Uint32 color);
101 static int QZ_LockHWSurface(_THIS, SDL_Surface *surface);
102 static void QZ_UnlockHWSurface(_THIS, SDL_Surface *surface);
103 static int QZ_AllocHWSurface(_THIS, SDL_Surface *surface);
104 static void QZ_FreeHWSurface (_THIS, SDL_Surface *surface);
105 /* static int QZ_FlipHWSurface (_THIS, SDL_Surface *surface); */
107 /* Bootstrap binding, enables entry point into the driver */
108 VideoBootStrap QZ_bootstrap = {
109 "Quartz", "Mac OS X CoreGraphics", QZ_Available, QZ_CreateDevice
113 /* Bootstrap functions */
114 static int QZ_Available ()
119 static SDL_VideoDevice* QZ_CreateDevice (int device_index)
121 #pragma unused (device_index)
123 SDL_VideoDevice *device;
124 SDL_PrivateVideoData *hidden;
126 device = (SDL_VideoDevice*) SDL_malloc (sizeof (*device) );
127 hidden = (SDL_PrivateVideoData*) SDL_malloc (sizeof (*hidden) );
129 if (device == NULL || hidden == NULL)
132 SDL_memset (device, 0, sizeof (*device) );
133 SDL_memset (hidden, 0, sizeof (*hidden) );
135 device->hidden = hidden;
137 device->VideoInit = QZ_VideoInit;
138 device->ListModes = QZ_ListModes;
139 device->SetVideoMode = QZ_SetVideoMode;
140 device->ToggleFullScreen = QZ_ToggleFullScreen;
141 device->UpdateMouse = QZ_UpdateMouse;
142 device->SetColors = QZ_SetColors;
143 /* device->UpdateRects = QZ_UpdateRects; this is determined by SetVideoMode() */
144 device->VideoQuit = QZ_VideoQuit;
146 device->LockHWSurface = QZ_LockHWSurface;
147 device->UnlockHWSurface = QZ_UnlockHWSurface;
148 device->AllocHWSurface = QZ_AllocHWSurface;
149 device->FreeHWSurface = QZ_FreeHWSurface;
150 /* device->FlipHWSurface = QZ_FlipHWSurface */;
152 device->SetGamma = QZ_SetGamma;
153 device->GetGamma = QZ_GetGamma;
154 device->SetGammaRamp = QZ_SetGammaRamp;
155 device->GetGammaRamp = QZ_GetGammaRamp;
157 device->GL_GetProcAddress = QZ_GL_GetProcAddress;
158 device->GL_GetAttribute = QZ_GL_GetAttribute;
159 device->GL_MakeCurrent = QZ_GL_MakeCurrent;
160 device->GL_SwapBuffers = QZ_GL_SwapBuffers;
161 device->GL_LoadLibrary = QZ_GL_LoadLibrary;
163 device->FreeWMCursor = QZ_FreeWMCursor;
164 device->CreateWMCursor = QZ_CreateWMCursor;
165 device->ShowWMCursor = QZ_ShowWMCursor;
166 device->WarpWMCursor = QZ_WarpWMCursor;
167 device->MoveWMCursor = QZ_MoveWMCursor;
168 device->CheckMouseMode = QZ_CheckMouseMode;
169 device->InitOSKeymap = QZ_InitOSKeymap;
170 device->PumpEvents = QZ_PumpEvents;
172 device->SetCaption = QZ_SetCaption;
173 device->SetIcon = QZ_SetIcon;
174 device->IconifyWindow = QZ_IconifyWindow;
175 /*device->GetWMInfo = QZ_GetWMInfo;*/
176 device->GrabInput = QZ_GrabInput;
179 * This is a big hassle, needing QuickDraw and QuickTime on older
180 * systems, and god knows what on 10.6, so we immediately fail here,
181 * which causes SDL to make an RGB surface and manage the YUV overlay
182 * in software. Sorry. Use SDL 1.3 if you want YUV rendering in a pixel
185 /*device->CreateYUVOverlay = QZ_CreateYUVOverlay;*/
187 device->free = QZ_DeleteDevice;
192 static void QZ_DeleteDevice (SDL_VideoDevice *device)
194 SDL_free (device->hidden);
198 static int QZ_VideoInit (_THIS, SDL_PixelFormat *video_format)
200 NSRect r = NSMakeRect(0.0, 0.0, 0.0, 0.0);
201 const char *env = NULL;
203 /* Initialize the video settings; this data persists between mode switches */
204 display_id = kCGDirectMainDisplay;
206 #if 0 /* The mouse event code needs to take this into account... */
207 env = getenv("SDL_VIDEO_FULLSCREEN_DISPLAY");
209 int monitor = SDL_atoi(env);
210 CGDirectDisplayID activeDspys [3];
211 CGDisplayCount dspyCnt;
212 CGGetActiveDisplayList (3, activeDspys, &dspyCnt);
213 if ( monitor >= 0 && monitor < dspyCnt ) {
214 display_id = activeDspys[monitor];
219 save_mode = CGDisplayCurrentMode (display_id);
220 mode_list = CGDisplayAvailableModes (display_id);
221 palette = CGPaletteCreateDefaultColorPalette ();
223 /* Allow environment override of screensaver disable. */
224 env = SDL_getenv("SDL_VIDEO_ALLOW_SCREENSAVER");
226 allow_screensaver = SDL_atoi(env);
228 #ifdef SDL_VIDEO_DISABLE_SCREENSAVER
229 allow_screensaver = 0;
231 allow_screensaver = 1;
235 /* Gather some information that is useful to know about the display */
236 CFNumberGetValue (CFDictionaryGetValue (save_mode, kCGDisplayBitsPerPixel),
237 kCFNumberSInt32Type, &device_bpp);
239 CFNumberGetValue (CFDictionaryGetValue (save_mode, kCGDisplayWidth),
240 kCFNumberSInt32Type, &device_width);
242 CFNumberGetValue (CFDictionaryGetValue (save_mode, kCGDisplayHeight),
243 kCFNumberSInt32Type, &device_height);
245 /* Determine the current screen size */
246 this->info.current_w = device_width;
247 this->info.current_h = device_height;
249 /* Determine the default screen depth */
250 video_format->BitsPerPixel = device_bpp;
252 /* Set misc globals */
253 current_grab_mode = SDL_GRAB_OFF;
254 cursor_should_be_visible = YES;
255 cursor_visible = YES;
257 field_edit = [[SDLTranslatorResponder alloc] initWithFrame:r];
259 if ( Gestalt(gestaltSystemVersion, &system_version) != noErr )
262 /* register for sleep notifications so wake from sleep generates SDL_VIDEOEXPOSE */
263 QZ_RegisterForSleepNotifications (this);
265 /* Fill in some window manager capabilities */
266 this->info.wm_available = 1;
271 static SDL_Rect** QZ_ListModes (_THIS, SDL_PixelFormat *format, Uint32 flags)
278 /* Any windowed mode is acceptable */
279 if ( (flags & SDL_FULLSCREEN) == 0 )
280 return (SDL_Rect**)-1;
282 /* Free memory from previous call, if any */
283 if ( client_mode_list != NULL ) {
287 for (i = 0; client_mode_list[i] != NULL; i++)
288 SDL_free (client_mode_list[i]);
290 SDL_free (client_mode_list);
291 client_mode_list = NULL;
294 num_modes = CFArrayGetCount (mode_list);
296 /* Build list of modes with the requested bpp */
297 for (i = 0; i < num_modes; i++) {
299 CFDictionaryRef onemode;
303 onemode = CFArrayGetValueAtIndex (mode_list, i);
304 number = CFDictionaryGetValue (onemode, kCGDisplayBitsPerPixel);
305 CFNumberGetValue (number, kCFNumberSInt32Type, &bpp);
307 if (bpp == format->BitsPerPixel) {
313 number = CFDictionaryGetValue (onemode, kCGDisplayWidth);
314 CFNumberGetValue (number, kCFNumberSInt32Type, &intvalue);
315 width = (Uint16) intvalue;
317 number = CFDictionaryGetValue (onemode, kCGDisplayHeight);
318 CFNumberGetValue (number, kCFNumberSInt32Type, &intvalue);
319 height = (Uint16) intvalue;
321 /* Check if mode is already in the list */
325 for (i = 0; i < list_size; i++) {
326 if (client_mode_list[i]->w == width &&
327 client_mode_list[i]->h == height) {
334 /* Grow the list and add mode to the list */
341 if (client_mode_list == NULL)
342 client_mode_list = (SDL_Rect**)
343 SDL_malloc (sizeof(*client_mode_list) * (list_size+1) );
345 client_mode_list = (SDL_Rect**)
346 SDL_realloc (client_mode_list, sizeof(*client_mode_list) * (list_size+1));
348 rect = (SDL_Rect*) SDL_malloc (sizeof(**client_mode_list));
350 if (client_mode_list == NULL || rect == NULL) {
355 rect->x = rect->y = 0;
359 client_mode_list[list_size-1] = rect;
360 client_mode_list[list_size] = NULL;
365 /* Sort list largest to smallest (by area) */
368 for (i = 0; i < list_size; i++) {
369 for (j = 0; j < list_size-1; j++) {
372 area1 = client_mode_list[j]->w * client_mode_list[j]->h;
373 area2 = client_mode_list[j+1]->w * client_mode_list[j+1]->h;
376 SDL_Rect *tmp = client_mode_list[j];
377 client_mode_list[j] = client_mode_list[j+1];
378 client_mode_list[j+1] = tmp;
383 return client_mode_list;
386 static SDL_bool QZ_WindowPosition(_THIS, int *x, int *y)
388 const char *window = getenv("SDL_VIDEO_WINDOW_POS");
390 if ( sscanf(window, "%d,%d", x, y) == 2 ) {
397 static void QZ_UnsetVideoMode (_THIS, BOOL to_desktop)
399 /* Reset values that may change between switches */
400 this->info.blit_fill = 0;
401 this->FillHWRect = NULL;
402 this->UpdateRects = NULL;
403 this->LockHWSurface = NULL;
404 this->UnlockHWSurface = NULL;
407 CGContextFlush (cg_context);
408 CGContextRelease (cg_context);
412 /* Release fullscreen resources */
413 if ( mode_flags & SDL_FULLSCREEN ) {
417 /* Release double buffer stuff */
418 if ( mode_flags & SDL_DOUBLEBUF) {
421 SDL_WaitThread (thread, NULL);
422 SDL_DestroySemaphore (sem1);
423 SDL_DestroySemaphore (sem2);
424 SDL_free (sw_buffers[0]);
427 /* If we still have a valid window, close it. */
429 NSCAssert([ qz_window delegate ] == nil, @"full screen window shouldn't have a delegate"); /* if that should ever change, we'd have to release it here */
430 [ qz_window close ]; /* includes release because [qz_window isReleasedWhenClosed] */
435 Release the OpenGL context
436 Do this first to avoid trash on the display before fade
438 if ( mode_flags & SDL_OPENGL ) {
440 QZ_TearDownOpenGL (this);
441 CGLSetFullScreen (NULL);
445 /* Restore original screen resolution/bpp */
446 CGDisplaySwitchToMode (display_id, save_mode);
447 CGReleaseAllDisplays ();
449 Reset the main screen's rectangle
450 See comment in QZ_SetVideoFullscreen for why we do this
452 screen_rect = NSMakeRect(0,0,device_width,device_height);
453 QZ_SetFrame([ NSScreen mainScreen ], screen_rect);
456 /* Release window mode resources */
458 id delegate = [ qz_window delegate ];
459 [ qz_window close ]; /* includes release because [qz_window isReleasedWhenClosed] */
460 if (delegate != nil) [ delegate release ];
464 /* Release the OpenGL context */
465 if ( mode_flags & SDL_OPENGL )
466 QZ_TearDownOpenGL (this);
469 /* Signal successful teardown */
470 video_set = SDL_FALSE;
473 static SDL_Surface* QZ_SetVideoFullScreen (_THIS, SDL_Surface *current, int width,
474 int height, int bpp, Uint32 flags)
476 boolean_t exact_match = 0;
480 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
482 /* Fade to black to hide resolution-switching flicker (and garbage
483 that is displayed by a destroyed OpenGL context, if applicable) */
484 if ( CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess ) {
485 CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
488 /* Destroy any previous mode */
489 if (video_set == SDL_TRUE)
490 QZ_UnsetVideoMode (this, FALSE);
492 /* Sorry, QuickDraw was ripped out. */
493 if (getenv("SDL_NSWindowPointer") || getenv("SDL_NSQuickDrawViewPointer")) {
494 SDL_SetError ("Embedded QuickDraw windows are no longer supported");
498 /* See if requested mode exists */
499 mode = CGDisplayBestModeForParameters (display_id, bpp, width,
500 height, &exact_match);
502 /* Require an exact match to the requested mode */
503 if ( ! exact_match ) {
504 SDL_SetError ("Failed to find display resolution: %dx%dx%d", width, height, bpp);
508 /* Put up the blanking window (a window above all other windows) */
509 if (getenv ("SDL_SINGLEDISPLAY"))
510 error = CGDisplayCapture (display_id);
512 error = CGCaptureAllDisplays ();
514 if ( CGDisplayNoErr != error ) {
515 SDL_SetError ("Failed capturing display");
519 /* Do the physical switch */
520 if ( CGDisplayNoErr != CGDisplaySwitchToMode (display_id, mode) ) {
521 SDL_SetError ("Failed switching display resolution");
525 current->pixels = (Uint32*) CGDisplayBaseAddress (display_id);
526 current->pitch = CGDisplayBytesPerRow (display_id);
531 current->flags |= SDL_FULLSCREEN;
532 current->flags |= SDL_HWSURFACE;
533 current->flags |= SDL_PREALLOC;
534 /* current->hwdata = (void *) CGDisplayGetDrawingContext (display_id); */
536 this->UpdateRects = QZ_DirectUpdate;
537 this->LockHWSurface = QZ_LockHWSurface;
538 this->UnlockHWSurface = QZ_UnlockHWSurface;
540 /* Setup double-buffer emulation */
541 if ( flags & SDL_DOUBLEBUF ) {
544 Setup a software backing store for reasonable results when
545 double buffering is requested (since a single-buffered hardware
546 surface looks hideous).
548 The actual screen blit occurs in a separate thread to allow
549 other blitting while waiting on the VBL (and hence results in higher framerates).
551 this->LockHWSurface = NULL;
552 this->UnlockHWSurface = NULL;
553 this->UpdateRects = NULL;
555 current->flags |= (SDL_HWSURFACE|SDL_DOUBLEBUF);
556 this->UpdateRects = QZ_DoubleBufferUpdate;
557 this->LockHWSurface = QZ_LockDoubleBuffer;
558 this->UnlockHWSurface = QZ_UnlockDoubleBuffer;
559 this->FlipHWSurface = QZ_FlipDoubleBuffer;
561 current->pixels = SDL_malloc (current->pitch * current->h * 2);
562 if (current->pixels == NULL) {
567 sw_buffers[0] = current->pixels;
568 sw_buffers[1] = (Uint8*)current->pixels + current->pitch * current->h;
571 sem1 = SDL_CreateSemaphore (0);
572 sem2 = SDL_CreateSemaphore (1);
573 thread = SDL_CreateThread ((int (*)(void *))QZ_ThreadFlip, this);
576 if ( CGDisplayCanSetPalette (display_id) )
577 current->flags |= SDL_HWPALETTE;
579 /* Check if we should recreate the window */
580 if (qz_window == nil) {
581 /* Manually create a window, avoids having a nib file resource */
582 qz_window = [ [ SDL_QuartzWindow alloc ]
583 initWithContentRect:contentRect
585 backing:NSBackingStoreBuffered
588 if (qz_window != nil) {
589 [ qz_window setAcceptsMouseMovedEvents:YES ];
590 [ qz_window setViewsNeedDisplay:NO ];
593 /* We already have a window, just change its size */
595 [ qz_window setContentSize:contentRect.size ];
596 current->flags |= (SDL_NOFRAME|SDL_RESIZABLE) & mode_flags;
597 [ window_view setFrameSize:contentRect.size ];
600 /* Setup OpenGL for a fullscreen context */
601 if (flags & SDL_OPENGL) {
606 if ( ! QZ_SetupOpenGL (this, bpp, flags) ) {
610 /* Initialize the NSView and add it to our window. The presence of a valid window and
611 view allow the cursor to be changed whilst in fullscreen.*/
612 window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
613 [ [ qz_window contentView ] addSubview:window_view ];
614 [ window_view release ];
616 ctx = QZ_GetCGLContextObj (gl_context);
617 err = CGLSetFullScreen (ctx);
620 SDL_SetError ("Error setting OpenGL fullscreen: %s", CGLErrorString(err));
624 [ gl_context makeCurrentContext];
626 glClear (GL_COLOR_BUFFER_BIT);
628 [ gl_context flushBuffer ];
630 current->flags |= SDL_OPENGL;
633 /* If we don't hide menu bar, it will get events and interrupt the program */
636 /* Fade in again (asynchronously) */
637 if ( fade_token != kCGDisplayFadeReservationInvalidToken ) {
638 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
639 CGReleaseDisplayFadeReservation(fade_token);
643 There is a bug in Cocoa where NSScreen doesn't synchronize
644 with CGDirectDisplay, so the main screen's frame is wrong.
645 As a result, coordinate translation produces incorrect results.
646 We can hack around this bug by setting the screen rect
647 ourselves. This hack should be removed if/when the bug is fixed.
649 screen_rect = NSMakeRect(0,0,width,height);
650 QZ_SetFrame([ NSScreen mainScreen ], screen_rect);
652 /* Save the flags to ensure correct tear-down */
653 mode_flags = current->flags;
655 /* Set app state, hide cursor if necessary, ... */
660 /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
662 ERR_DOUBLEBUF: CGDisplaySwitchToMode (display_id, save_mode);
663 ERR_NO_SWITCH: CGReleaseAllDisplays ();
665 ERR_NO_MATCH: if ( fade_token != kCGDisplayFadeReservationInvalidToken ) {
666 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
667 CGReleaseDisplayFadeReservation (fade_token);
672 static SDL_Surface* QZ_SetVideoWindowed (_THIS, SDL_Surface *current, int width,
673 int height, int *bpp, Uint32 flags)
677 int center_window = 1;
678 int origin_x, origin_y;
679 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
685 contentRect = NSMakeRect (0, 0, width, height);
688 Check if we should completely destroy the previous mode
689 - If it is fullscreen
690 - If it has different noframe or resizable attribute
691 - If it is OpenGL (since gl attributes could be different)
692 - If new mode is OpenGL, but previous mode wasn't
694 if (video_set == SDL_TRUE) {
695 if (mode_flags & SDL_FULLSCREEN) {
696 /* Fade to black to hide resolution-switching flicker (and garbage
697 that is displayed by a destroyed OpenGL context, if applicable) */
698 if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess) {
699 CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
701 QZ_UnsetVideoMode (this, TRUE);
703 else if ( ((mode_flags ^ flags) & (SDL_NOFRAME|SDL_RESIZABLE)) ||
704 (mode_flags & SDL_OPENGL) ||
705 (flags & SDL_OPENGL) ) {
706 QZ_UnsetVideoMode (this, TRUE);
710 /* Sorry, QuickDraw was ripped out. */
711 if (getenv("SDL_NSWindowPointer") || getenv("SDL_NSQuickDrawViewPointer")) {
712 SDL_SetError ("Embedded QuickDraw windows are no longer supported");
713 if (fade_token != kCGDisplayFadeReservationInvalidToken) {
714 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
715 CGReleaseDisplayFadeReservation (fade_token);
720 /* Check if we should recreate the window */
721 if (qz_window == nil) {
723 /* Set the window style based on input flags */
724 if ( flags & SDL_NOFRAME ) {
725 style = NSBorderlessWindowMask;
726 current->flags |= SDL_NOFRAME;
728 style = NSTitledWindowMask;
729 style |= (NSMiniaturizableWindowMask | NSClosableWindowMask);
730 if ( flags & SDL_RESIZABLE ) {
731 style |= NSResizableWindowMask;
732 current->flags |= SDL_RESIZABLE;
736 /* Manually create a window, avoids having a nib file resource */
737 qz_window = [ [ SDL_QuartzWindow alloc ]
738 initWithContentRect:contentRect
740 backing:NSBackingStoreBuffered
743 if (qz_window == nil) {
744 SDL_SetError ("Could not create the Cocoa window");
745 if (fade_token != kCGDisplayFadeReservationInvalidToken) {
746 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
747 CGReleaseDisplayFadeReservation (fade_token);
752 /*[ qz_window setReleasedWhenClosed:YES ];*/ /* no need to set this as it's the default for NSWindows */
753 QZ_SetCaption(this, this->wm_title, this->wm_icon);
754 [ qz_window setAcceptsMouseMovedEvents:YES ];
755 [ qz_window setViewsNeedDisplay:NO ];
757 if ( QZ_WindowPosition(this, &origin_x, &origin_y) ) {
758 /* have to flip the Y value (NSPoint is lower left corner origin) */
759 [ qz_window setFrameTopLeftPoint:NSMakePoint((float) origin_x, (float) (this->info.current_h - origin_y))];
761 } else if ( center_window ) {
762 [ qz_window center ];
765 [ qz_window setDelegate:
766 [ [ SDL_QuartzWindowDelegate alloc ] init ] ];
767 [ qz_window setContentView: [ [ [ SDL_QuartzView alloc ] init ] autorelease ] ];
769 /* We already have a window, just change its size */
771 [ qz_window setContentSize:contentRect.size ];
772 current->flags |= (SDL_NOFRAME|SDL_RESIZABLE) & mode_flags;
773 [ window_view setFrameSize:contentRect.size ];
776 /* For OpenGL, we bind the context to a subview */
777 if ( flags & SDL_OPENGL ) {
779 if ( ! QZ_SetupOpenGL (this, *bpp, flags) ) {
780 if (fade_token != kCGDisplayFadeReservationInvalidToken) {
781 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
782 CGReleaseDisplayFadeReservation (fade_token);
787 window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
788 [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
789 [ [ qz_window contentView ] addSubview:window_view ];
790 [ gl_context setView: window_view ];
791 [ window_view release ];
792 [ gl_context makeCurrentContext];
793 [ qz_window makeKeyAndOrderFront:nil ];
794 current->flags |= SDL_OPENGL;
796 /* For 2D, we build a CGBitmapContext */
798 CGColorSpaceRef cgColorspace;
800 /* Only recreate the view if it doesn't already exist */
801 if (window_view == nil) {
803 window_view = [ [ NSView alloc ] initWithFrame:contentRect ];
804 [ window_view setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
805 [ [ qz_window contentView ] addSubview:window_view ];
806 [ window_view release ];
807 [ qz_window makeKeyAndOrderFront:nil ];
810 cgColorspace = CGColorSpaceCreateDeviceRGB();
811 current->pitch = 4 * current->w;
812 current->pixels = SDL_malloc (current->h * current->pitch);
814 cg_context = CGBitmapContextCreate (current->pixels, current->w, current->h,
815 8, current->pitch, cgColorspace,
816 kCGImageAlphaNoneSkipFirst);
817 CGColorSpaceRelease (cgColorspace);
819 current->flags |= SDL_SWSURFACE;
820 current->flags |= SDL_ASYNCBLIT;
821 current->hwdata = (void *) cg_context;
823 this->UpdateRects = QZ_UpdateRects;
824 this->LockHWSurface = QZ_LockHWSurface;
825 this->UnlockHWSurface = QZ_UnlockHWSurface;
828 /* Save flags to ensure correct teardown */
829 mode_flags = current->flags;
831 /* Fade in again (asynchronously) if we came from a fullscreen mode and faded to black */
832 if (fade_token != kCGDisplayFadeReservationInvalidToken) {
833 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
834 CGReleaseDisplayFadeReservation (fade_token);
840 static SDL_Surface* QZ_SetVideoMode (_THIS, SDL_Surface *current, int width,
841 int height, int bpp, Uint32 flags)
844 current->pixels = NULL;
846 /* Setup full screen video */
847 if ( flags & SDL_FULLSCREEN ) {
848 current = QZ_SetVideoFullScreen (this, current, width, height, bpp, flags );
852 /* Setup windowed video */
854 /* Force bpp to 32 */
856 current = QZ_SetVideoWindowed (this, current, width, height, &bpp, flags);
861 /* Setup the new pixel format */
869 case 16: /* (1)-5-5-5 RGB */
876 SDL_SetError ("24bpp is not available");
878 case 32: /* (8)-8-8-8 ARGB */
880 if ( flags & SDL_FULLSCREEN )
888 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
901 if ( ! SDL_ReallocFormat (current, bpp,
902 rmask, gmask, bmask, amask ) ) {
903 SDL_SetError ("Couldn't reallocate pixel format");
908 /* Signal successful completion (used internally) */
909 video_set = SDL_TRUE;
914 static int QZ_ToggleFullScreen (_THIS, int on)
919 static int QZ_SetColors (_THIS, int first_color, int num_colors,
925 for (index = first_color; index < first_color+num_colors; index++) {
927 /* Clamp colors between 0.0 and 1.0 */
928 color.red = colors->r / 255.0;
929 color.blue = colors->b / 255.0;
930 color.green = colors->g / 255.0;
934 CGPaletteSetColorAtIndex (palette, color, index);
937 if ( CGDisplayNoErr != CGDisplaySetPalette (display_id, palette) )
943 static int QZ_LockDoubleBuffer (_THIS, SDL_Surface *surface)
948 static void QZ_UnlockDoubleBuffer (_THIS, SDL_Surface *surface)
952 /* The VBL delay is based on code by Ian R Ollmann's RezLib <iano@cco.caltech.edu> */
953 static AbsoluteTime QZ_SecondsToAbsolute ( double seconds )
961 temp.i = seconds * 1000000000.0;
963 return NanosecondsToAbsolute ( temp.ns );
966 static int QZ_ThreadFlip (_THIS)
972 Give this thread the highest scheduling priority possible,
973 in the hopes that it will immediately run after the VBL delay
976 pthread_t current_thread;
978 struct sched_param param;
980 current_thread = pthread_self ();
981 pthread_getschedparam (current_thread, &policy, ¶m);
983 param.sched_priority = sched_get_priority_max (policy);
984 pthread_setschedparam (current_thread, policy, ¶m);
994 * We have to add SDL_VideoSurface->offset here, since we might be a
995 * smaller surface in the center of the framebuffer (you asked for
996 * a fullscreen resolution smaller than the hardware could supply
997 * so SDL is centering it in a bigger resolution)...
999 dst = (Uint8 *)CGDisplayBaseAddress (display_id) + SDL_VideoSurface->offset;
1000 src = current_buffer + SDL_VideoSurface->offset;
1001 len = SDL_VideoSurface->w * SDL_VideoSurface->format->BytesPerPixel;
1002 h = SDL_VideoSurface->h;
1003 skip = SDL_VideoSurface->pitch;
1005 /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */
1008 /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */
1010 double linesPerSecond;
1014 AbsoluteTime nextTime;
1015 CFNumberRef refreshRateCFNumber;
1017 refreshRateCFNumber = CFDictionaryGetValue (mode, kCGDisplayRefreshRate);
1018 if ( NULL == refreshRateCFNumber ) {
1019 SDL_SetError ("Mode has no refresh rate");
1023 if ( 0 == CFNumberGetValue (refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) ) {
1024 SDL_SetError ("Error getting refresh rate");
1028 if ( 0 == refreshRate ) {
1030 SDL_SetError ("Display has no refresh rate, using 60hz");
1032 /* ok, for LCD's we'll emulate a 60hz refresh, which may or may not look right */
1036 linesPerSecond = refreshRate * h;
1039 /* Figure out the first delay so we start off about right */
1040 position = CGDisplayBeamPosition (display_id);
1041 if (position > target)
1044 adjustment = (target - position) / linesPerSecond;
1046 nextTime = AddAbsoluteToAbsolute (UpTime (), QZ_SecondsToAbsolute (adjustment));
1048 MPDelayUntil (&nextTime);
1052 /* On error, skip VBL delay */
1055 /* TODO: use CGContextDrawImage here too! Create two CGContextRefs the same way we
1056 create two buffers, replace current_buffer with current_context and set it
1057 appropriately in QZ_FlipDoubleBuffer. */
1060 SDL_memcpy (dst, src, len);
1065 /* signal flip completion */
1072 static int QZ_FlipDoubleBuffer (_THIS, SDL_Surface *surface)
1074 /* wait for previous flip to complete */
1077 current_buffer = surface->pixels;
1079 if (surface->pixels == sw_buffers[0])
1080 surface->pixels = sw_buffers[1];
1082 surface->pixels = sw_buffers[0];
1084 /* signal worker thread to do the flip */
1090 static void QZ_DoubleBufferUpdate (_THIS, int num_rects, SDL_Rect *rects)
1092 /* perform a flip if someone calls updaterects on a doublebuferred surface */
1093 this->FlipHWSurface (this, SDL_VideoSurface);
1096 static void QZ_DirectUpdate (_THIS, int num_rects, SDL_Rect *rects)
1098 #pragma unused(this,num_rects,rects)
1102 /* Resize icon, BMP format */
1103 static const unsigned char QZ_ResizeIcon[] = {
1104 0x42,0x4d,0x31,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x28,0x00,
1105 0x00,0x00,0x0d,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x01,0x00,0x18,0x00,0x00,0x00,
1106 0x00,0x00,0xfb,0x01,0x00,0x00,0x13,0x0b,0x00,0x00,0x13,0x0b,0x00,0x00,0x00,0x00,
1107 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1108 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1109 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
1110 0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,
1111 0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,
1112 0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xda,0xda,0xda,0x87,
1113 0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,
1114 0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
1115 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd5,0xd5,0xd5,0x87,0x87,0x87,0xe8,0xe8,0xe8,
1116 0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,
1117 0xda,0xda,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1118 0xff,0xff,0xd7,0xd7,0xd7,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,
1119 0xda,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
1120 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd7,0xd7,0xd7,
1121 0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xda,0xda,0xda,0x87,0x87,0x87,0xe8,
1122 0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1123 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xd7,0xd7,0xd7,0x87,0x87,0x87,0xe8,0xe8,
1124 0xe8,0xff,0xff,0xff,0xdc,0xdc,0xdc,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
1125 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1126 0xff,0xff,0xff,0xd9,0xd9,0xd9,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xdc,
1127 0xdc,0xdc,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1128 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0xdb,
1129 0xdb,0x87,0x87,0x87,0xe8,0xe8,0xe8,0xff,0xff,0xff,0xff,0xff,0xff,0x0b,0xff,0xff,
1130 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1131 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdb,0xdb,0xdb,0x87,0x87,0x87,0xe8,
1132 0xe8,0xe8,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1133 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1134 0xff,0xff,0xff,0xff,0xdc,0xdc,0xdc,0x87,0x87,0x87,0xff,0xff,0xff,0x0b,0xff,0xff,
1135 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1136 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xdc,
1137 0xdc,0xdc,0xff,0xff,0xff,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1138 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
1139 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0b
1142 static void QZ_DrawResizeIcon (_THIS)
1144 /* Check if we should draw the resize icon */
1145 if (SDL_VideoSurface->flags & SDL_RESIZABLE) {
1149 /* Create the icon image */
1150 if (resize_icon == NULL) {
1155 rw = SDL_RWFromConstMem (QZ_ResizeIcon, sizeof(QZ_ResizeIcon));
1156 tmp = SDL_LoadBMP_RW (rw, SDL_TRUE);
1158 resize_icon = SDL_ConvertSurface (tmp, SDL_VideoSurface->format, SDL_SRCCOLORKEY);
1159 SDL_SetColorKey (resize_icon, SDL_SRCCOLORKEY, 0xFFFFFF);
1161 SDL_FreeSurface (tmp);
1164 icon_rect.x = SDL_VideoSurface->w - 13;
1165 icon_rect.y = SDL_VideoSurface->h - 13;
1169 SDL_BlitSurface (resize_icon, NULL, SDL_VideoSurface, &icon_rect);
1173 static void QZ_UpdateRects (_THIS, int numRects, SDL_Rect *rects)
1175 if (SDL_VideoSurface->flags & SDL_OPENGLBLIT) {
1176 QZ_GL_SwapBuffers (this);
1178 else if ( [ qz_window isMiniaturized ] ) {
1180 /* Do nothing if miniaturized */
1184 CGContextRef cgc = (CGContextRef)
1185 [[NSGraphicsContext graphicsContextWithWindow: qz_window]
1187 QZ_DrawResizeIcon (this);
1188 CGContextFlush (cg_context);
1189 CGImageRef image = CGBitmapContextCreateImage (cg_context);
1190 CGRect rectangle = CGRectMake (0,0,[window_view frame].size.width,[window_view frame].size.height);
1192 CGContextDrawImage (cgc, rectangle, image);
1193 CGImageRelease(image);
1194 CGContextFlush (cgc);
1195 CGContextRelease (cgc);
1199 static void QZ_VideoQuit (_THIS)
1201 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
1203 /* Restore gamma settings */
1204 CGDisplayRestoreColorSyncSettings ();
1206 /* Ensure the cursor will be visible and working when we quit */
1207 CGDisplayShowCursor (display_id);
1208 CGAssociateMouseAndMouseCursorPosition (1);
1210 if (mode_flags & SDL_FULLSCREEN) {
1211 /* Fade to black to hide resolution-switching flicker (and garbage
1212 that is displayed by a destroyed OpenGL context, if applicable) */
1213 if (CGAcquireDisplayFadeReservation (5, &fade_token) == kCGErrorSuccess) {
1214 CGDisplayFade (fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
1216 QZ_UnsetVideoMode (this, TRUE);
1217 if (fade_token != kCGDisplayFadeReservationInvalidToken) {
1218 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
1219 CGReleaseDisplayFadeReservation (fade_token);
1223 QZ_UnsetVideoMode (this, TRUE);
1225 CGPaletteRelease (palette);
1227 if (opengl_library) {
1228 SDL_UnloadObject(opengl_library);
1229 opengl_library = NULL;
1231 this->gl_config.driver_loaded = 0;
1234 [field_edit release];
1239 #if 0 /* Not used (apparently, it's really slow) */
1240 static int QZ_FillHWRect (_THIS, SDL_Surface *dst, SDL_Rect *rect, Uint32 color)
1242 CGSDisplayHWFill (display_id, rect->x, rect->y, rect->w, rect->h, color);
1248 static int QZ_LockHWSurface(_THIS, SDL_Surface *surface)
1253 static void QZ_UnlockHWSurface(_THIS, SDL_Surface *surface)
1257 static int QZ_AllocHWSurface(_THIS, SDL_Surface *surface)
1259 return(-1); /* unallowed (no HWSURFACE support here). */
1262 static void QZ_FreeHWSurface (_THIS, SDL_Surface *surface)
1267 int QZ_FlipHWSurface (_THIS, SDL_Surface *surface) {
1272 /* Gamma functions */
1273 int QZ_SetGamma (_THIS, float red, float green, float blue)
1275 const CGGammaValue min = 0.0, max = 1.0;
1285 green = 1.0 / green;
1292 if ( CGDisplayNoErr == CGSetDisplayTransferByFormula
1293 (display_id, min, max, red, min, max, green, min, max, blue) ) {
1303 int QZ_GetGamma (_THIS, float *red, float *green, float *blue)
1306 if ( CGDisplayNoErr == CGGetDisplayTransferByFormula
1307 (display_id, &dummy, &dummy, red,
1308 &dummy, &dummy, green, &dummy, &dummy, blue) )
1315 int QZ_SetGammaRamp (_THIS, Uint16 *ramp)
1317 const CGTableCount tableSize = 255;
1318 CGGammaValue redTable[tableSize];
1319 CGGammaValue greenTable[tableSize];
1320 CGGammaValue blueTable[tableSize];
1324 /* Extract gamma values into separate tables, convert to floats between 0.0 and 1.0 */
1325 for (i = 0; i < 256; i++)
1326 redTable[i % 256] = ramp[i] / 65535.0;
1328 for (i=256; i < 512; i++)
1329 greenTable[i % 256] = ramp[i] / 65535.0;
1331 for (i=512; i < 768; i++)
1332 blueTable[i % 256] = ramp[i] / 65535.0;
1334 if ( CGDisplayNoErr == CGSetDisplayTransferByTable
1335 (display_id, tableSize, redTable, greenTable, blueTable) )
1341 int QZ_GetGammaRamp (_THIS, Uint16 *ramp)
1343 const CGTableCount tableSize = 255;
1344 CGGammaValue redTable[tableSize];
1345 CGGammaValue greenTable[tableSize];
1346 CGGammaValue blueTable[tableSize];
1347 CGTableCount actual;
1350 if ( CGDisplayNoErr != CGGetDisplayTransferByTable
1351 (display_id, tableSize, redTable, greenTable, blueTable, &actual) ||
1352 actual != tableSize)
1356 /* Pack tables into one array, with values from 0 to 65535 */
1357 for (i = 0; i < 256; i++)
1358 ramp[i] = redTable[i % 256] * 65535.0;
1360 for (i=256; i < 512; i++)
1361 ramp[i] = greenTable[i % 256] * 65535.0;
1363 for (i=512; i < 768; i++)
1364 ramp[i] = blueTable[i % 256] * 65535.0;