|
1 /* $Id$ */ |
|
2 |
|
3 /****************************************************************************** |
|
4 * Cocoa video driver * |
|
5 * Known things left to do: * |
|
6 * Scale© the old pixel buffer to the new one when switching resolution. * |
|
7 ******************************************************************************/ |
|
8 |
|
9 #ifdef WITH_COCOA |
|
10 |
|
11 #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_3 |
|
12 #include <AvailabilityMacros.h> |
|
13 |
|
14 #import <Cocoa/Cocoa.h> |
|
15 #import <sys/time.h> /* gettimeofday */ |
|
16 #import <sys/param.h> /* for MAXPATHLEN */ |
|
17 #import <unistd.h> |
|
18 |
|
19 /** |
|
20 * Important notice regarding all modifications!!!!!!! |
|
21 * There are certain limitations because the file is objective C++. |
|
22 * gdb has limitations. |
|
23 * C++ and objective C code can't be joined in all cases (classes stuff). |
|
24 * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information. |
|
25 */ |
|
26 |
|
27 |
|
28 /* From Menus.h (according to Xcode Developer Documentation) */ |
|
29 extern "C" void ShowMenuBar(); |
|
30 extern "C" void HideMenuBar(); |
|
31 |
|
32 /* Defined in stdbool.h */ |
|
33 #ifndef __cplusplus |
|
34 # ifndef __BEOS__ |
|
35 # undef bool |
|
36 # undef false |
|
37 # undef true |
|
38 # endif |
|
39 #endif |
|
40 |
|
41 |
|
42 #include "../../stdafx.h" |
|
43 #include "../../debug.h" |
|
44 #include "../../variables.h" |
|
45 #include "cocoa_v.h" |
|
46 |
|
47 #undef Point |
|
48 #undef Rect |
|
49 |
|
50 |
|
51 /* Structure for rez switch gamma fades |
|
52 * We can hide the monitor flicker by setting the gamma tables to 0 |
|
53 */ |
|
54 #define QZ_GAMMA_TABLE_SIZE 256 |
|
55 |
|
56 struct OTTD_QuartzGammaTable { |
|
57 CGGammaValue red[QZ_GAMMA_TABLE_SIZE]; |
|
58 CGGammaValue green[QZ_GAMMA_TABLE_SIZE]; |
|
59 CGGammaValue blue[QZ_GAMMA_TABLE_SIZE]; |
|
60 }; |
|
61 |
|
62 /* Add methods to get at private members of NSScreen. |
|
63 * Since there is a bug in Apple's screen switching code that does not update |
|
64 * this variable when switching to fullscreen, we'll set it manually (but only |
|
65 * for the main screen). |
|
66 */ |
|
67 @interface NSScreen (NSScreenAccess) |
|
68 - (void) setFrame:(NSRect)frame; |
|
69 @end |
|
70 |
|
71 @implementation NSScreen (NSScreenAccess) |
|
72 - (void) setFrame:(NSRect)frame; |
|
73 { |
|
74 _frame = frame; |
|
75 } |
|
76 @end |
|
77 |
|
78 |
|
79 class FullscreenSubdriver: public CocoaSubdriver { |
|
80 int display_width; |
|
81 int display_height; |
|
82 int display_depth; |
|
83 int screen_pitch; |
|
84 void* screen_buffer; |
|
85 void* pixel_buffer; |
|
86 |
|
87 CGDirectDisplayID display_id; /* 0 == main display (only support single display) */ |
|
88 CFDictionaryRef cur_mode; /* current mode of the display */ |
|
89 CFDictionaryRef save_mode; /* original mode of the display */ |
|
90 CGDirectPaletteRef palette; /* palette of an 8-bit display */ |
|
91 |
|
92 #define MAX_DIRTY_RECTS 100 |
|
93 Rect dirty_rects[MAX_DIRTY_RECTS]; |
|
94 int num_dirty_rects; |
|
95 |
|
96 |
|
97 /* Gamma functions to try to hide the flash from a rez switch |
|
98 * Fade the display from normal to black |
|
99 * Save gamma tables for fade back to normal |
|
100 */ |
|
101 uint32 FadeGammaOut(OTTD_QuartzGammaTable* table) |
|
102 { |
|
103 CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; |
|
104 CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; |
|
105 CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; |
|
106 float percent; |
|
107 int j; |
|
108 unsigned int actual; |
|
109 |
|
110 if (CGGetDisplayTransferByTable( |
|
111 display_id, QZ_GAMMA_TABLE_SIZE, |
|
112 table->red, table->green, table->blue, &actual |
|
113 ) != CGDisplayNoErr || |
|
114 actual != QZ_GAMMA_TABLE_SIZE) { |
|
115 return 1; |
|
116 } |
|
117 |
|
118 memcpy(redTable, table->red, sizeof(redTable)); |
|
119 memcpy(greenTable, table->green, sizeof(greenTable)); |
|
120 memcpy(blueTable, table->blue, sizeof(greenTable)); |
|
121 |
|
122 for (percent = 1.0; percent >= 0.0; percent -= 0.01) { |
|
123 for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { |
|
124 redTable[j] = redTable[j] * percent; |
|
125 greenTable[j] = greenTable[j] * percent; |
|
126 blueTable[j] = blueTable[j] * percent; |
|
127 } |
|
128 |
|
129 if (CGSetDisplayTransferByTable( |
|
130 display_id, QZ_GAMMA_TABLE_SIZE, |
|
131 redTable, greenTable, blueTable |
|
132 ) != CGDisplayNoErr) { |
|
133 CGDisplayRestoreColorSyncSettings(); |
|
134 return 1; |
|
135 } |
|
136 |
|
137 CSleep(10); |
|
138 } |
|
139 |
|
140 return 0; |
|
141 } |
|
142 |
|
143 /* Fade the display from black to normal |
|
144 * Restore previously saved gamma values |
|
145 */ |
|
146 uint32 FadeGammaIn(const OTTD_QuartzGammaTable* table) |
|
147 { |
|
148 CGGammaValue redTable[QZ_GAMMA_TABLE_SIZE]; |
|
149 CGGammaValue greenTable[QZ_GAMMA_TABLE_SIZE]; |
|
150 CGGammaValue blueTable[QZ_GAMMA_TABLE_SIZE]; |
|
151 float percent; |
|
152 int j; |
|
153 |
|
154 memset(redTable, 0, sizeof(redTable)); |
|
155 memset(greenTable, 0, sizeof(greenTable)); |
|
156 memset(blueTable, 0, sizeof(greenTable)); |
|
157 |
|
158 for (percent = 0.0; percent <= 1.0; percent += 0.01) { |
|
159 for (j = 0; j < QZ_GAMMA_TABLE_SIZE; j++) { |
|
160 redTable[j] = table->red[j] * percent; |
|
161 greenTable[j] = table->green[j] * percent; |
|
162 blueTable[j] = table->blue[j] * percent; |
|
163 } |
|
164 |
|
165 if (CGSetDisplayTransferByTable( |
|
166 display_id, QZ_GAMMA_TABLE_SIZE, |
|
167 redTable, greenTable, blueTable |
|
168 ) != CGDisplayNoErr) { |
|
169 CGDisplayRestoreColorSyncSettings(); |
|
170 return 1; |
|
171 } |
|
172 |
|
173 CSleep(10); |
|
174 } |
|
175 |
|
176 return 0; |
|
177 } |
|
178 |
|
179 /* Wait for the VBL to occur (estimated since we don't have a hardware interrupt) */ |
|
180 void WaitForVerticalBlank() |
|
181 { |
|
182 /* The VBL delay is based on Ian Ollmann's RezLib <iano@cco.caltech.edu> */ |
|
183 double refreshRate; |
|
184 double linesPerSecond; |
|
185 double target; |
|
186 double position; |
|
187 double adjustment; |
|
188 CFNumberRef refreshRateCFNumber; |
|
189 |
|
190 refreshRateCFNumber = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayRefreshRate); |
|
191 if (refreshRateCFNumber == NULL) return; |
|
192 |
|
193 if (CFNumberGetValue(refreshRateCFNumber, kCFNumberDoubleType, &refreshRate) == 0) |
|
194 return; |
|
195 |
|
196 if (refreshRate == 0) return; |
|
197 |
|
198 linesPerSecond = refreshRate * display_height; |
|
199 target = display_height; |
|
200 |
|
201 /* Figure out the first delay so we start off about right */ |
|
202 position = CGDisplayBeamPosition(display_id); |
|
203 if (position > target) position = 0; |
|
204 |
|
205 adjustment = (target - position) / linesPerSecond; |
|
206 |
|
207 CSleep((uint32)(adjustment * 1000)); |
|
208 } |
|
209 |
|
210 |
|
211 bool SetVideoMode(int w, int h) |
|
212 { |
|
213 int exact_match; |
|
214 CFNumberRef number; |
|
215 int bpp; |
|
216 int gamma_error; |
|
217 OTTD_QuartzGammaTable gamma_table; |
|
218 NSRect screen_rect; |
|
219 CGError error; |
|
220 NSPoint pt; |
|
221 |
|
222 /* Destroy any previous mode */ |
|
223 if (pixel_buffer != NULL) { |
|
224 free(pixel_buffer); |
|
225 pixel_buffer = NULL; |
|
226 } |
|
227 |
|
228 /* See if requested mode exists */ |
|
229 cur_mode = CGDisplayBestModeForParameters(display_id, display_depth, w, h, &exact_match); |
|
230 |
|
231 /* If the mode wasn't an exact match, check if it has the right bpp, and update width and height */ |
|
232 if (!exact_match) { |
|
233 number = (const __CFNumber*) CFDictionaryGetValue(cur_mode, kCGDisplayBitsPerPixel); |
|
234 CFNumberGetValue(number, kCFNumberSInt32Type, &bpp); |
|
235 if (bpp != display_depth) { |
|
236 DEBUG(driver, 0, "Failed to find display resolution"); |
|
237 goto ERR_NO_MATCH; |
|
238 } |
|
239 |
|
240 number = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayWidth); |
|
241 CFNumberGetValue(number, kCFNumberSInt32Type, &w); |
|
242 |
|
243 number = (const __CFNumber*)CFDictionaryGetValue(cur_mode, kCGDisplayHeight); |
|
244 CFNumberGetValue(number, kCFNumberSInt32Type, &h); |
|
245 } |
|
246 |
|
247 /* Fade display to zero gamma */ |
|
248 gamma_error = FadeGammaOut(&gamma_table); |
|
249 |
|
250 /* Put up the blanking window (a window above all other windows) */ |
|
251 error = CGDisplayCapture(display_id); |
|
252 |
|
253 if (CGDisplayNoErr != error) { |
|
254 DEBUG(driver, 0, "Failed capturing display"); |
|
255 goto ERR_NO_CAPTURE; |
|
256 } |
|
257 |
|
258 /* Do the physical switch */ |
|
259 if (CGDisplaySwitchToMode(display_id, cur_mode) != CGDisplayNoErr) { |
|
260 DEBUG(driver, 0, "Failed switching display resolution"); |
|
261 goto ERR_NO_SWITCH; |
|
262 } |
|
263 |
|
264 screen_buffer = CGDisplayBaseAddress(display_id); |
|
265 screen_pitch = CGDisplayBytesPerRow(display_id); |
|
266 |
|
267 display_width = CGDisplayPixelsWide(display_id); |
|
268 display_height = CGDisplayPixelsHigh(display_id); |
|
269 |
|
270 /* Setup double-buffer emulation */ |
|
271 pixel_buffer = malloc(display_width * display_height * display_depth / 8); |
|
272 if (pixel_buffer == NULL) { |
|
273 DEBUG(driver, 0, "Failed to allocate memory for double buffering"); |
|
274 goto ERR_DOUBLEBUF; |
|
275 } |
|
276 |
|
277 if (display_depth == 8 && !CGDisplayCanSetPalette(display_id)) { |
|
278 DEBUG(driver, 0, "Not an indexed display mode."); |
|
279 goto ERR_NOT_INDEXED; |
|
280 } |
|
281 |
|
282 /* If we don't hide menu bar, it will get events and interrupt the program */ |
|
283 HideMenuBar(); |
|
284 |
|
285 /* Fade the display to original gamma */ |
|
286 if (!gamma_error) FadeGammaIn(&gamma_table); |
|
287 |
|
288 /* There is a bug in Cocoa where NSScreen doesn't synchronize |
|
289 * with CGDirectDisplay, so the main screen's frame is wrong. |
|
290 * As a result, coordinate translation produces incorrect results. |
|
291 * We can hack around this bug by setting the screen rect ourselves. |
|
292 * This hack should be removed if/when the bug is fixed. |
|
293 */ |
|
294 screen_rect = NSMakeRect(0, 0, display_width, display_height); |
|
295 [ [ NSScreen mainScreen ] setFrame:screen_rect ]; |
|
296 |
|
297 |
|
298 pt = [ NSEvent mouseLocation ]; |
|
299 pt.y = display_height - pt.y; |
|
300 if (MouseIsInsideView(&pt)) QZ_HideMouse(); |
|
301 |
|
302 UpdatePalette(0, 256); |
|
303 |
|
304 return true; |
|
305 |
|
306 /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */ |
|
307 ERR_NOT_INDEXED: |
|
308 free(pixel_buffer); |
|
309 pixel_buffer = NULL; |
|
310 ERR_DOUBLEBUF: |
|
311 CGDisplaySwitchToMode(display_id, save_mode); |
|
312 ERR_NO_SWITCH: |
|
313 CGReleaseAllDisplays(); |
|
314 ERR_NO_CAPTURE: |
|
315 if (!gamma_error) FadeGammaIn(&gamma_table); |
|
316 ERR_NO_MATCH: |
|
317 display_width = 0; |
|
318 display_height = 0; |
|
319 |
|
320 return false; |
|
321 } |
|
322 |
|
323 void RestoreVideoMode() |
|
324 { |
|
325 /* Release fullscreen resources */ |
|
326 OTTD_QuartzGammaTable gamma_table; |
|
327 int gamma_error; |
|
328 NSRect screen_rect; |
|
329 |
|
330 gamma_error = FadeGammaOut(&gamma_table); |
|
331 |
|
332 /* Restore original screen resolution/bpp */ |
|
333 CGDisplaySwitchToMode(display_id, save_mode); |
|
334 CGReleaseAllDisplays(); |
|
335 ShowMenuBar(); |
|
336 /* Reset the main screen's rectangle |
|
337 * See comment in SetVideoMode for why we do this |
|
338 */ |
|
339 screen_rect = NSMakeRect(0, 0, CGDisplayPixelsWide(display_id), CGDisplayPixelsHigh(display_id)); |
|
340 [ [ NSScreen mainScreen ] setFrame:screen_rect ]; |
|
341 |
|
342 QZ_ShowMouse(); |
|
343 |
|
344 /* Destroy the pixel buffer */ |
|
345 if (pixel_buffer != NULL) { |
|
346 free(pixel_buffer); |
|
347 pixel_buffer = NULL; |
|
348 } |
|
349 |
|
350 if (!gamma_error) FadeGammaIn(&gamma_table); |
|
351 |
|
352 display_width = 0; |
|
353 display_height = 0; |
|
354 } |
|
355 |
|
356 public: |
|
357 FullscreenSubdriver(int bpp) |
|
358 { |
|
359 if (bpp != 8 && bpp != 32) { |
|
360 error("Cocoa: This video driver only supports 8 and 32 bpp blitters."); |
|
361 } |
|
362 |
|
363 /* Initialize the video settings; this data persists between mode switches */ |
|
364 display_id = kCGDirectMainDisplay; |
|
365 save_mode = CGDisplayCurrentMode(display_id); |
|
366 |
|
367 if (bpp == 8) palette = CGPaletteCreateDefaultColorPalette(); |
|
368 |
|
369 display_width = 0; |
|
370 display_height = 0; |
|
371 display_depth = bpp; |
|
372 pixel_buffer = NULL; |
|
373 |
|
374 num_dirty_rects = MAX_DIRTY_RECTS; |
|
375 } |
|
376 |
|
377 virtual ~FullscreenSubdriver() |
|
378 { |
|
379 RestoreVideoMode(); |
|
380 } |
|
381 |
|
382 virtual void Draw() |
|
383 { |
|
384 const uint8* src = (uint8*) pixel_buffer; |
|
385 uint8* dst = (uint8*) screen_buffer; |
|
386 uint pitch = screen_pitch; |
|
387 uint width = display_width; |
|
388 uint num_dirty = num_dirty_rects; |
|
389 uint bytesperpixel = display_depth / 8; |
|
390 uint i; |
|
391 |
|
392 /* Check if we need to do anything */ |
|
393 if (num_dirty == 0) return; |
|
394 |
|
395 if (num_dirty >= MAX_DIRTY_RECTS) { |
|
396 num_dirty = 1; |
|
397 dirty_rects[0].left = 0; |
|
398 dirty_rects[0].top = 0; |
|
399 dirty_rects[0].right = display_width; |
|
400 dirty_rects[0].bottom = display_height; |
|
401 } |
|
402 |
|
403 WaitForVerticalBlank(); |
|
404 /* Build the region of dirty rectangles */ |
|
405 for (i = 0; i < num_dirty; i++) { |
|
406 uint y = dirty_rects[i].top; |
|
407 uint left = dirty_rects[i].left; |
|
408 uint length = dirty_rects[i].right - left; |
|
409 uint bottom = dirty_rects[i].bottom; |
|
410 |
|
411 for (; y < bottom; y++) { |
|
412 memcpy(dst + y * pitch + left * bytesperpixel, src + y * width * bytesperpixel + left * bytesperpixel, length * bytesperpixel); |
|
413 } |
|
414 } |
|
415 |
|
416 num_dirty_rects = 0; |
|
417 } |
|
418 |
|
419 virtual void MakeDirty(int left, int top, int width, int height) |
|
420 { |
|
421 if (num_dirty_rects < MAX_DIRTY_RECTS) { |
|
422 dirty_rects[num_dirty_rects].left = left; |
|
423 dirty_rects[num_dirty_rects].top = top; |
|
424 dirty_rects[num_dirty_rects].right = left + width; |
|
425 dirty_rects[num_dirty_rects].bottom = top + height; |
|
426 } |
|
427 num_dirty_rects++; |
|
428 } |
|
429 |
|
430 virtual void UpdatePalette(uint first_color, uint num_colors) |
|
431 { |
|
432 CGTableCount index; |
|
433 CGDeviceColor color; |
|
434 |
|
435 if (display_depth != 8) |
|
436 return; |
|
437 |
|
438 for (index = first_color; index < first_color+num_colors; index++) { |
|
439 /* Clamp colors between 0.0 and 1.0 */ |
|
440 color.red = _cur_palette[index].r / 255.0; |
|
441 color.blue = _cur_palette[index].b / 255.0; |
|
442 color.green = _cur_palette[index].g / 255.0; |
|
443 |
|
444 CGPaletteSetColorAtIndex(palette, color, index); |
|
445 } |
|
446 |
|
447 CGDisplaySetPalette(display_id, palette); |
|
448 } |
|
449 |
|
450 virtual uint ListModes(OTTDPoint* modes, uint max_modes) |
|
451 { |
|
452 CFArrayRef mode_list; |
|
453 CFIndex num_modes; |
|
454 CFIndex i; |
|
455 uint count = 0; |
|
456 |
|
457 mode_list = CGDisplayAvailableModes(display_id); |
|
458 num_modes = CFArrayGetCount(mode_list); |
|
459 |
|
460 /* Build list of modes with the requested bpp */ |
|
461 for (i = 0; i < num_modes && count < max_modes; i++) { |
|
462 CFDictionaryRef onemode; |
|
463 CFNumberRef number; |
|
464 int bpp; |
|
465 int intvalue; |
|
466 bool hasMode; |
|
467 uint16 width, height; |
|
468 |
|
469 onemode = (const __CFDictionary*)CFArrayGetValueAtIndex(mode_list, i); |
|
470 number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayBitsPerPixel); |
|
471 CFNumberGetValue (number, kCFNumberSInt32Type, &bpp); |
|
472 |
|
473 if (bpp != display_depth) continue; |
|
474 |
|
475 number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayWidth); |
|
476 CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); |
|
477 width = (uint16)intvalue; |
|
478 |
|
479 number = (const __CFNumber*)CFDictionaryGetValue(onemode, kCGDisplayHeight); |
|
480 CFNumberGetValue(number, kCFNumberSInt32Type, &intvalue); |
|
481 height = (uint16)intvalue; |
|
482 |
|
483 /* Check if mode is already in the list */ |
|
484 { |
|
485 uint i; |
|
486 hasMode = false; |
|
487 for (i = 0; i < count; i++) { |
|
488 if (modes[i].x == width && modes[i].y == height) { |
|
489 hasMode = true; |
|
490 break; |
|
491 } |
|
492 } |
|
493 } |
|
494 |
|
495 if (hasMode) continue; |
|
496 |
|
497 /* Add mode to the list */ |
|
498 modes[count].x = width; |
|
499 modes[count].y = height; |
|
500 count++; |
|
501 } |
|
502 |
|
503 /* Sort list smallest to largest */ |
|
504 { |
|
505 uint i, j; |
|
506 for (i = 0; i < count; i++) { |
|
507 for (j = 0; j < count-1; j++) { |
|
508 if (modes[j].x > modes[j + 1].x || ( |
|
509 modes[j].x == modes[j + 1].x && |
|
510 modes[j].y > modes[j + 1].y |
|
511 )) { |
|
512 uint tmpw = modes[j].x; |
|
513 uint tmph = modes[j].y; |
|
514 |
|
515 modes[j].x = modes[j + 1].x; |
|
516 modes[j].y = modes[j + 1].y; |
|
517 |
|
518 modes[j + 1].x = tmpw; |
|
519 modes[j + 1].y = tmph; |
|
520 } |
|
521 } |
|
522 } |
|
523 } |
|
524 |
|
525 return count; |
|
526 } |
|
527 |
|
528 virtual bool ChangeResolution(int w, int h) |
|
529 { |
|
530 int old_width = display_width; |
|
531 int old_height = display_height; |
|
532 |
|
533 if (SetVideoMode(w, h)) |
|
534 return true; |
|
535 |
|
536 if (old_width != 0 && old_height != 0) |
|
537 SetVideoMode(old_width, old_height); |
|
538 |
|
539 return false; |
|
540 } |
|
541 |
|
542 virtual bool IsFullscreen() |
|
543 { |
|
544 return true; |
|
545 } |
|
546 |
|
547 virtual int GetWidth() |
|
548 { |
|
549 return display_width; |
|
550 } |
|
551 |
|
552 virtual int GetHeight() |
|
553 { |
|
554 return display_height; |
|
555 } |
|
556 |
|
557 virtual void *GetPixelBuffer() |
|
558 { |
|
559 return pixel_buffer; |
|
560 } |
|
561 |
|
562 /* |
|
563 Convert local coordinate to window server (CoreGraphics) coordinate. |
|
564 In fullscreen mode this just means copying the coords. |
|
565 */ |
|
566 virtual CGPoint PrivateLocalToCG(NSPoint* p) |
|
567 { |
|
568 CGPoint cgp; |
|
569 |
|
570 cgp.x = p->x; |
|
571 cgp.y = p->y; |
|
572 |
|
573 return cgp; |
|
574 } |
|
575 |
|
576 virtual NSPoint GetMouseLocation(NSEvent *event) |
|
577 { |
|
578 NSPoint pt; |
|
579 |
|
580 pt = [ NSEvent mouseLocation ]; |
|
581 pt.y = display_height - pt.y; |
|
582 |
|
583 return pt; |
|
584 } |
|
585 |
|
586 virtual bool MouseIsInsideView(NSPoint *pt) |
|
587 { |
|
588 return pt->x >= 0 && pt->y >= 0 && pt->x < display_width && pt->y < display_height; |
|
589 } |
|
590 |
|
591 virtual bool IsActive() |
|
592 { |
|
593 return true; |
|
594 } |
|
595 }; |
|
596 |
|
597 CocoaSubdriver *QZ_CreateFullscreenSubdriver(int width, int height, int bpp) |
|
598 { |
|
599 FullscreenSubdriver *ret; |
|
600 |
|
601 ret = new FullscreenSubdriver(bpp); |
|
602 |
|
603 if (!ret->ChangeResolution(width, height)) { |
|
604 delete ret; |
|
605 return NULL; |
|
606 } |
|
607 |
|
608 return ret; |
|
609 } |
|
610 |
|
611 #endif /* WITH_COCOA */ |