summaryrefslogtreecommitdiff
path: root/src/video/cocoa/fullscreen.mm
diff options
context:
space:
mode:
authoregladil <egladil@openttd.org>2007-11-22 21:48:17 +0000
committeregladil <egladil@openttd.org>2007-11-22 21:48:17 +0000
commit3ad488e98f12529ebbca3f43ba5eedae4f46d049 (patch)
treec16e83ac270622e3c3730669fd186602c869d1a2 /src/video/cocoa/fullscreen.mm
parent2653d94bcb5ee9238764e129eb504fa62c428916 (diff)
downloadopenttd-3ad488e98f12529ebbca3f43ba5eedae4f46d049.tar.xz
(svn r11492) -Codechange: [OSX] Split the cocoa video driver into several files. The reason for this is that the fullscreen and windowed mode api are separate from each other in OS X and thus the driver actual is two drivers in one. This split is to make the code more readable and to prepare for replacing the Quickdraw windowed mode code which uses apis deprecated as of OS X 10.5 (and maybe earlier).
Diffstat (limited to 'src/video/cocoa/fullscreen.mm')
-rw-r--r--src/video/cocoa/fullscreen.mm617
1 files changed, 617 insertions, 0 deletions
diff --git a/src/video/cocoa/fullscreen.mm b/src/video/cocoa/fullscreen.mm
new file mode 100644
index 000000000..c60e0c58a
--- /dev/null
+++ b/src/video/cocoa/fullscreen.mm
@@ -0,0 +1,617 @@
+/* $Id$ */
+
+/******************************************************************************
+ * Cocoa video driver *
+ * Known things left to do: *
+ * Scale&copy the old pixel buffer to the new one when switching resolution. *
+ ******************************************************************************/
+
+#ifdef WITH_COCOA
+
+#include <AvailabilityMacros.h>
+
+#import <Cocoa/Cocoa.h>
+#import <sys/time.h> /* gettimeofday */
+#import <sys/param.h> /* for MAXPATHLEN */
+#import <unistd.h>
+
+/**
+ * Important notice regarding all modifications!!!!!!!
+ * There are certain limitations because the file is objective C++.
+ * gdb has limitations.
+ * C++ and objective C code can't be joined in all cases (classes stuff).
+ * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
+ */
+
+
+/* From Menus.h (according to Xcode Developer Documentation) */
+extern "C" void ShowMenuBar();
+extern "C" void HideMenuBar();
+
+/* Defined in stdbool.h */
+#ifndef __cplusplus
+# ifndef __BEOS__
+# undef bool
+# undef false
+# undef true
+# endif
+#endif
+
+
+#include "../../stdafx.h"
+#include "../../openttd.h"
+#include "../../debug.h"
+#include "../../macros.h"
+#include "../../os/macosx/splash.h"
+#include "../../variables.h"
+#include "../../gfx.h"
+#include "cocoa_v.h"
+#include "cocoa_keys.h"
+#include "../../blitter/factory.hpp"
+#include "../../fileio.h"
+
+#undef Point
+#undef Rect
+
+
+/* Structure for rez switch gamma fades
+ * We can hide the monitor flicker by setting the gamma tables to 0
+ */
+#define QZ_GAMMA_TABLE_SIZE 256
+
+struct OTTD_QuartzGammaTable {
+ CGGammaValue red[QZ_GAMMA_TABLE_SIZE];
+ CGGammaValue green[QZ_GAMMA_TABLE_SIZE];
+ CGGammaValue blue[QZ_GAMMA_TABLE_SIZE];
+};
+
+/* Add methods to get at private members of NSScreen.
+ * Since there is a bug in Apple's screen switching code that does not update
+ * this variable when switching to fullscreen, we'll set it manually (but only
+ * for the main screen).
+ */
+@interface NSScreen (NSScreenAccess)
+ - (void) setFrame:(NSRect)frame;
+@end
+
+@implementation NSScreen (NSScreenAccess)
+- (void) setFrame:(NSRect)frame;
+{
+ _frame = frame;
+}
+@end
+
+
+class FullscreenSubdriver: public CocoaSubdriver {
+ int display_width;
+ int display_height;
+ int display_depth;
+ int screen_pitch;
+ void* screen_buffer;
+ void* pixel_buffer;
+
+ CGDirectDisplayID display_id; /* 0 == main display (only support single display) */
+ CFDictionaryRef cur_mode; /* current mode of the display */
+ CFDictionaryRef save_mode; /* original mode of the display */
+ CGDirectPaletteRef palette; /* palette of an 8-bit display */
+
+ #define MAX_DIRTY_RECTS 100
+ Rect dirty_rects[MAX_DIRTY_RECTS];
+ int num_dirty_rects;
+
+
+ /* Gamma functions to try to hide the flash from a rez switch
+ * Fade the display from normal to black
+ * Save gamma tables for fade back to normal
+ */
+ uint32 FadeGammaOut(OTTD_QuartzGammaTable* table)
+ {
+ CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE];
+ CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE];
+ CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE];
+ float percent;
+ int j;
+ unsigned int actual;
+
+ if (CGGetDisplayTransferByTable(
+ display_id, QZ_GAMMA_TABLE_SIZE,
+ table->red, table->green, table->blue, &actual
+ ) != CGDisplayNoErr ||
+ actual != QZ_GAMMA_TABLE_SIZE) {
+ return 1;
+ }
+
+ memcpy(redTable, table->red, sizeof(redTable));
+ memcpy(greenTable, table->green, sizeof(greenTable));
+ memcpy(blueTable, table->blue, sizeof(greenTable));
+
+ for (percent = 1.0; percent >= 0.0; percent -= 0.01) {
+ for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) {
+ redTable[j] = redTable[j] * percent;
+ greenTable[j] = greenTable[j] * percent;
+ blueTable[j] = blueTable[j] * percent;
+ }
+
+ if (CGSetDisplayTransferByTable(
+ display_id, QZ_GAMMA_TABLE_SIZE,
+ redTable, greenTable, blueTable
+ ) != CGDisplayNoErr) {
+ CGDisplayRestoreColorSyncSettings();
+ return 1;
+ }
+
+ CSleep(10);
+ }
+
+ return 0;
+ }
+
+ /* Fade the display from black to normal
+ * Restore previously saved gamma values
+ */
+ uint32 FadeGammaIn(const OTTD_QuartzGammaTable* table)
+ {
+ CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE];
+ CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE];
+ CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE];
+ float percent;
+ int j;
+
+ memset(redTable, 0, sizeof(redTable));
+ memset(greenTable, 0, sizeof(greenTable));
+ memset(blueTable, 0, sizeof(greenTable));
+
+ for (percent = 0.0; percent <= 1.0; percent += 0.01) {
+ for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) {
+ redTable[j] = table->red[j] * percent;
+ greenTable[j] = table->green[j] * percent;
+ blueTable[j] = table->blue[j] * percent;
+ }
+
+ if (CGSetDisplayTransferByTable(
+ display_id, QZ_GAMMA_TABLE_SIZE,
+ redTable, greenTable, blueTable
+ ) != CGDisplayNoErr) {
+ CGDisplayRestoreColorSyncSettings();
+ return 1;
+ }
+
+ CSleep(10);
+ }
+
+ return 0;
+ }
+
+ /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */
+ void WaitForVerticalBlank()
+ {
+ /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */
+ double refreshRate;
+ double linesPerSecond;
+ double target;
+ double position;
+ double adjustment;
+ CFNumberRef refreshRateCFNumber;
+
+ refreshRateCFNumber = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayRefreshRate);
+ if (refreshRateCFNumber == NULL) return;
+
+ if (CFNumberGetValue(refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0)
+ return;
+
+ if (refreshRate == 0) return;
+
+ linesPerSecond = refreshRate * display_height;
+ target = display_height;
+
+ /* Figure out the first delay so we start off about right */
+ position = CGDisplayBeamPosition(display_id);
+ if (position > target) position = 0;
+
+ adjustment = (target - position) / linesPerSecond;
+
+ CSleep((uint32)(adjustment * 1000));
+ }
+
+
+ bool SetVideoMode(int w, int h)
+ {
+ int exact_match;
+ CFNumberRef number;
+ int bpp;
+ int gamma_error;
+ OTTD_QuartzGammaTable gamma_table;
+ NSRect screen_rect;
+ CGError error;
+ NSPoint pt;
+
+ /* Destroy any previous mode */
+ if (pixel_buffer != NULL) {
+ free(pixel_buffer);
+ pixel_buffer = NULL;
+ }
+
+ /* See if requested mode exists */
+ cur_mode = CGDisplayBestModeForParameters(display_id, display_depth, w, h, &exact_match);
+
+ /* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */
+ if (!exact_match) {
+ number = (const __CFNumber*) CFDictionaryGetValue(cur_mode, kCGDisplayBitsPerPixel);
+ CFNumberGetValue(number, kCFNumberSInt32Type, &bpp);
+ if (bpp != display_depth) {
+ DEBUG(driver, 0, "Failed to find display resolution");
+ goto ERR_NO_MATCH;
+ }
+
+ number = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayWidth);
+ CFNumberGetValue(number, kCFNumberSInt32Type, &w);
+
+ number = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayHeight);
+ CFNumberGetValue(number, kCFNumberSInt32Type, &h);
+ }
+
+ /* Fade display to zero gamma */
+ gamma_error = FadeGammaOut(&gamma_table);
+
+ /* Put up the blanking window (a window above all other windows) */
+ error = CGDisplayCapture(display_id);
+
+ if (CGDisplayNoErr != error) {
+ DEBUG(driver, 0, "Failed capturing display");
+ goto ERR_NO_CAPTURE;
+ }
+
+ /* Do the physical switch */
+ if (CGDisplaySwitchToMode(display_id, cur_mode) != CGDisplayNoErr) {
+ DEBUG(driver, 0, "Failed switching display resolution");
+ goto ERR_NO_SWITCH;
+ }
+
+ screen_buffer = CGDisplayBaseAddress(display_id);
+ screen_pitch = CGDisplayBytesPerRow(display_id);
+
+ display_width = CGDisplayPixelsWide(display_id);
+ display_height = CGDisplayPixelsHigh(display_id);
+
+ /* Setup double-buffer emulation */
+ pixel_buffer = malloc(display_width * display_height * display_depth / 8);
+ if (pixel_buffer == NULL) {
+ DEBUG(driver, 0, "Failed to allocate memory for double buffering");
+ goto ERR_DOUBLEBUF;
+ }
+
+ if (display_depth == 8 && !CGDisplayCanSetPalette(display_id)) {
+ DEBUG(driver, 0, "Not an indexed display mode.");
+ goto ERR_NOT_INDEXED;
+ }
+
+ /* If we don't hide menu bar, it will get events and interrupt the program */
+ HideMenuBar();
+
+ /* Fade the display to original gamma */
+ if (!gamma_error) FadeGammaIn(&gamma_table);
+
+ /* There is a bug in Cocoa where NSScreen doesn't synchronize
+ * with CGDirectDisplay, so the main screen's frame is wrong.
+ * As a result, coordinate translation produces incorrect results.
+ * We can hack around this bug by setting the screen rect ourselves.
+ * This hack should be removed if/when the bug is fixed.
+ */
+ screen_rect = NSMakeRect(0, 0, display_width, display_height);
+ [ [ NSScreen mainScreen ] setFrame:screen_rect ];
+
+
+ pt = [ NSEvent mouseLocation ];
+ pt.y = display_height - pt.y;
+ if (MouseIsInsideView(&pt)) QZ_HideMouse();
+
+ UpdatePalette(0, 256);
+
+ return true;
+
+ /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
+ERR_NOT_INDEXED:
+ free(pixel_buffer);
+ pixel_buffer = NULL;
+ERR_DOUBLEBUF:
+ CGDisplaySwitchToMode(display_id, save_mode);
+ERR_NO_SWITCH:
+ CGReleaseAllDisplays();
+ERR_NO_CAPTURE:
+ if (!gamma_error) FadeGammaIn(&gamma_table);
+ERR_NO_MATCH:
+ display_width = 0;
+ display_height = 0;
+
+ return false;
+ }
+
+ void RestoreVideoMode()
+ {
+ /* Release fullscreen resources */
+ OTTD_QuartzGammaTable gamma_table;
+ int gamma_error;
+ NSRect screen_rect;
+
+ gamma_error = FadeGammaOut(&gamma_table);
+
+ /* Restore original screen resolution/bpp */
+ CGDisplaySwitchToMode(display_id, save_mode);
+ CGReleaseAllDisplays();
+ ShowMenuBar();
+ /* Reset the main screen's rectangle
+ * See comment in SetVideoMode for why we do this
+ */
+ screen_rect = NSMakeRect(0, 0, CGDisplayPixelsWide(display_id), CGDisplayPixelsHigh(display_id));
+ [ [ NSScreen mainScreen ] setFrame:screen_rect ];
+
+ QZ_ShowMouse();
+
+ /* Destroy the pixel buffer */
+ if (pixel_buffer != NULL) {
+ free(pixel_buffer);
+ pixel_buffer = NULL;
+ }
+
+ if (!gamma_error) FadeGammaIn(&gamma_table);
+
+ display_width = 0;
+ display_height = 0;
+ }
+
+public:
+ FullscreenSubdriver(int bpp)
+ {
+ if (bpp != 8 && bpp != 32) {
+ error("Cocoa: This video driver only supports 8 and 32 bpp blitters.");
+ }
+
+ /* Initialize the video settings; this data persists between mode switches */
+ display_id = kCGDirectMainDisplay;
+ save_mode = CGDisplayCurrentMode(display_id);
+
+ if (bpp == 8) palette = CGPaletteCreateDefaultColorPalette();
+
+ display_width = 0;
+ display_height = 0;
+ display_depth = bpp;
+ pixel_buffer = NULL;
+
+ num_dirty_rects = MAX_DIRTY_RECTS;
+ }
+
+ virtual ~FullscreenSubdriver()
+ {
+ RestoreVideoMode();
+ }
+
+ virtual void Draw()
+ {
+ const uint8* src = (uint8*) pixel_buffer;
+ uint8* dst = (uint8*) screen_buffer;
+ uint pitch = screen_pitch;
+ uint width = display_width;
+ uint num_dirty = num_dirty_rects;
+ uint bytesperpixel = display_depth / 8;
+ uint i;
+
+ /* Check if we need to do anything */
+ if (num_dirty == 0) return;
+
+ if (num_dirty >= MAX_DIRTY_RECTS) {
+ num_dirty = 1;
+ dirty_rects[0].left = 0;
+ dirty_rects[0].top = 0;
+ dirty_rects[0].right = display_width;
+ dirty_rects[0].bottom = display_height;
+ }
+
+ WaitForVerticalBlank();
+ /* Build the region of dirty rectangles */
+ for (i = 0; i < num_dirty; i++) {
+ uint y = dirty_rects[i].top;
+ uint left = dirty_rects[i].left;
+ uint length = dirty_rects[i].right - left;
+ uint bottom = dirty_rects[i].bottom;
+
+ for (; y < bottom; y++) {
+ memcpy(dst + y * pitch + left * bytesperpixel, src + y * width * bytesperpixel + left * bytesperpixel, length * bytesperpixel);
+ }
+ }
+
+ num_dirty_rects = 0;
+ }
+
+ virtual void MakeDirty(int left, int top, int width, int height)
+ {
+ if (num_dirty_rects < MAX_DIRTY_RECTS) {
+ dirty_rects[num_dirty_rects].left = left;
+ dirty_rects[num_dirty_rects].top = top;
+ dirty_rects[num_dirty_rects].right = left + width;
+ dirty_rects[num_dirty_rects].bottom = top + height;
+ }
+ num_dirty_rects++;
+ }
+
+ virtual void UpdatePalette(uint first_color, uint num_colors)
+ {
+ CGTableCount index;
+ CGDeviceColor color;
+
+ if (display_depth != 8)
+ return;
+
+ for (index = first_color; index < first_color+num_colors; index++) {
+ /* Clamp colors between 0.0 and 1.0 */
+ color.red = _cur_palette[index].r / 255.0;
+ color.blue = _cur_palette[index].b / 255.0;
+ color.green = _cur_palette[index].g / 255.0;
+
+ CGPaletteSetColorAtIndex(palette, color, index);
+ }
+
+ CGDisplaySetPalette(display_id, palette);
+ }
+
+ virtual uint ListModes(OTTDPoint* modes, uint max_modes)
+ {
+ CFArrayRef mode_list;
+ CFIndex num_modes;
+ CFIndex i;
+ uint count = 0;
+
+ mode_list = CGDisplayAvailableModes(display_id);
+ num_modes = CFArrayGetCount(mode_list);
+
+ /* Build list of modes with the requested bpp */
+ for (i = 0; i < num_modes && count < max_modes; i++) {
+ CFDictionaryRef onemode;
+ CFNumberRef number;
+ int bpp;
+ int intvalue;
+ bool hasMode;
+ uint16 width, height;
+
+ onemode = (const __CFDictionary*)CFArrayGetValueAtIndex(mode_list, i);
+ number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayBitsPerPixel);
+ CFNumberGetValue (number, kCFNumberSInt32Type, &bpp);
+
+ if (bpp != display_depth) continue;
+
+ number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayWidth);
+ CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue);
+ width = (uint16)intvalue;
+
+ number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayHeight);
+ CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue);
+ height = (uint16)intvalue;
+
+ /* Check if mode is already in the list */
+ {
+ uint i;
+ hasMode = false;
+ for (i = 0; i < count; i++) {
+ if (modes[i].x == width && modes[i].y == height) {
+ hasMode = true;
+ break;
+ }
+ }
+ }
+
+ if (hasMode) continue;
+
+ /* Add mode to the list */
+ modes[count].x = width;
+ modes[count].y = height;
+ count++;
+ }
+
+ /* Sort list smallest to largest */
+ {
+ uint i, j;
+ for (i = 0; i < count; i++) {
+ for (j = 0; j < count-1; j++) {
+ if (modes[j].x > modes[j + 1].x || (
+ modes[j].x == modes[j + 1].x &&
+ modes[j].y > modes[j + 1].y
+ )) {
+ uint tmpw = modes[j].x;
+ uint tmph = modes[j].y;
+
+ modes[j].x = modes[j + 1].x;
+ modes[j].y = modes[j + 1].y;
+
+ modes[j + 1].x = tmpw;
+ modes[j + 1].y = tmph;
+ }
+ }
+ }
+ }
+
+ return count;
+ }
+
+ virtual bool ChangeResolution(int w, int h)
+ {
+ int old_width = display_width;
+ int old_height = display_height;
+
+ if (SetVideoMode(w, h))
+ return true;
+
+ if (old_width != 0 && old_height != 0)
+ SetVideoMode(old_width, old_height);
+
+ return false;
+ }
+
+ virtual bool IsFullscreen()
+ {
+ return true;
+ }
+
+ virtual int GetWidth()
+ {
+ return display_width;
+ }
+
+ virtual int GetHeight()
+ {
+ return display_height;
+ }
+
+ virtual void *GetPixelBuffer()
+ {
+ return pixel_buffer;
+ }
+
+ /*
+ Convert local coordinate to window server (CoreGraphics) coordinate.
+ In fullscreen mode this just means copying the coords.
+ */
+ virtual CGPoint PrivateLocalToCG(NSPoint* p)
+ {
+ CGPoint cgp;
+
+ cgp.x = p->x;
+ cgp.y = p->y;
+
+ return cgp;
+ }
+
+ virtual NSPoint GetMouseLocation(NSEvent *event)
+ {
+ NSPoint pt;
+
+ pt = [ NSEvent mouseLocation ];
+ pt.y = display_height - pt.y;
+
+ return pt;
+ }
+
+ virtual bool MouseIsInsideView(NSPoint *pt)
+ {
+ return pt->x >= 0 && pt->y >= 0 && pt->x < display_width && pt->y < display_height;
+ }
+
+ virtual bool IsActive()
+ {
+ return true;
+ }
+};
+
+CocoaSubdriver *QZ_CreateFullscreenSubdriver(int width, int height, int bpp)
+{
+ FullscreenSubdriver *ret;
+
+ ret = new FullscreenSubdriver(bpp);
+
+ if (!ret->ChangeResolution(width, height)) {
+ delete ret;
+ return NULL;
+ }
+
+ return ret;
+}
+
+#endif /* WITH_COCOA */