2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file cocoa_v.mm Code related to the cocoa video driver(s). */
10 /******************************************************************************
11 * Cocoa video driver *
12 * Known things left to do: *
13 * Nothing at the moment. *
14 ******************************************************************************/
18 #include "../../stdafx.h"
19 #include "../../os/macosx/macos.h"
22 #define Point OTTDPoint
23 #import <Cocoa/Cocoa.h>
27 #include "../../openttd.h"
28 #include "../../debug.h"
29 #include "../../core/geometry_type.hpp"
30 #include "../../core/math_func.hpp"
32 #include "cocoa_wnd.h"
33 #include "../../blitter/factory.hpp"
34 #include "../../framerate_type.h"
35 #include "../../network/network.h"
36 #include "../../gfx_func.h"
37 #include "../../thread.h"
38 #include "../../core/random_func.hpp"
39 #include "../../progress.h"
40 #include "../../settings_type.h"
41 #include "../../window_func.h"
42 #include "../../window_gui.h"
44 #import <sys/param.h> /* for MAXPATHLEN */
45 #import <sys/time.h> /* gettimeofday */
48 * Important notice regarding all modifications!!!!!!!
49 * There are certain limitations because the file is objective C++.
50 * gdb has limitations.
51 * C++ and objective C code can't be joined in all cases (classes stuff).
52 * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
55 /* On some old versions of MAC OS this may not be defined.
56 * Those versions generally only produce code for PPC. So it should be safe to
58 #ifndef kCGBitmapByteOrder32Host
59 #define kCGBitmapByteOrder32Host 0
62 bool _cocoa_video_started = false;
64 extern bool _tab_is_down;
67 static uint32 _tEvent;
71 /** List of common display/window sizes. */
72 static const Dimension _default_resolutions[] = {
87 static FVideoDriver_Cocoa iFVideoDriver_Cocoa;
91 * Get current realtime.
92 * @return Tick time in milliseconds.
94 static uint32 GetTick()
98 gettimeofday(&tim, NULL);
99 return tim.tv_usec / 1000 + tim.tv_sec * 1000;
103 /** Subclass of NSView for drawing to screen. */
104 @interface OTTD_QuartzView : NSView {
105 VideoDriver_Cocoa *driver;
107 - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv;
111 VideoDriver_Cocoa::VideoDriver_Cocoa()
113 this->window_width = 0;
114 this->window_height = 0;
115 this->window_pitch = 0;
116 this->buffer_depth = 0;
117 this->window_buffer = nullptr;
118 this->pixel_buffer = nullptr;
122 this->cocoaview = nil;
123 this->delegate = nil;
125 this->color_space = nullptr;
126 this->cgcontext = nullptr;
128 this->num_dirty_rects = lengthof(this->dirty_rects);
131 /** Stop Cocoa video driver. */
132 void VideoDriver_Cocoa::Stop()
134 if (!_cocoa_video_started) return;
136 CocoaExitApplication();
138 /* Release window mode resources */
139 if (this->window != nil) [ this->window close ];
140 [ this->cocoaview release ];
141 [ this->delegate release ];
143 CGContextRelease(this->cgcontext);
144 CGColorSpaceRelease(this->color_space);
146 free(this->window_buffer);
147 free(this->pixel_buffer);
149 _cocoa_video_started = false;
152 /** Try to start Cocoa video driver. */
153 const char *VideoDriver_Cocoa::Start(const StringList &parm)
155 if (!MacOSVersionIsAtLeast(10, 7, 0)) return "The Cocoa video driver requires Mac OS X 10.7 or later.";
157 if (_cocoa_video_started) return "Already started";
158 _cocoa_video_started = true;
160 /* Don't create a window or enter fullscreen if we're just going to show a dialog. */
161 if (!CocoaSetupApplication()) return nullptr;
163 this->UpdateAutoResolution();
164 this->orig_res = _cur_resolution;
166 int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
167 if (bpp != 8 && bpp != 32) {
169 return "The cocoa quartz subdriver only supports 8 and 32 bpp.";
172 bool fullscreen = _fullscreen;
173 if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) {
175 return "Could not create window";
178 if (fullscreen) this->ToggleFullscreen(fullscreen);
180 this->GameSizeChanged();
181 this->UpdateVideoModes();
187 * Set dirty a rectangle managed by a cocoa video subdriver.
188 * @param left Left x cooordinate of the dirty rectangle.
189 * @param top Uppder y coordinate of the dirty rectangle.
190 * @param width Width of the dirty rectangle.
191 * @param height Height of the dirty rectangle.
193 void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height)
195 if (this->num_dirty_rects < lengthof(this->dirty_rects)) {
196 dirty_rects[this->num_dirty_rects].left = left;
197 dirty_rects[this->num_dirty_rects].top = top;
198 dirty_rects[this->num_dirty_rects].right = left + width;
199 dirty_rects[this->num_dirty_rects].bottom = top + height;
201 this->num_dirty_rects++;
205 * Start the main programme loop when using a cocoa video driver.
207 void VideoDriver_Cocoa::MainLoop()
209 /* Restart game loop if it was already running (e.g. after bootstrapping),
210 * otherwise this call is a no-op. */
211 [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ];
213 /* Start the main event loop. */
218 * Change the resolution when using a cocoa video driver.
219 * @param w New window width.
220 * @param h New window height.
221 * @return Whether the video driver was successfully updated.
223 bool VideoDriver_Cocoa::ChangeResolution(int w, int h)
225 NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size;
226 w = std::min(w, (int)screen_size.width);
227 h = std::min(h, (int)screen_size.height);
229 NSRect contentRect = NSMakeRect(0, 0, w, h);
230 [ this->window setContentSize:contentRect.size ];
232 /* Ensure frame height - title bar height >= view height */
233 float content_height = [ this->window contentRectForFrameRect:[ this->window frame ] ].size.height;
234 contentRect.size.height = Clamp(h, 0, (int)content_height);
236 if (this->cocoaview != nil) {
237 h = (int)contentRect.size.height;
238 [ this->cocoaview setFrameSize:contentRect.size ];
241 this->window_width = w;
242 this->window_height = h;
244 [ (OTTD_CocoaWindow *)this->window center ];
245 this->AllocateBackingStore();
251 * Toggle between windowed and full screen mode for cocoa display driver.
252 * @param full_screen Whether to switch to full screen or not.
253 * @return Whether the mode switch was successful.
255 bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen)
257 if (this->IsFullscreen() == full_screen) return true;
259 if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
260 [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ];
261 this->UpdateVideoModes();
269 * Callback invoked after the blitter was changed.
270 * @return True if no error.
272 bool VideoDriver_Cocoa::AfterBlitterChange()
274 this->ChangeResolution(_cur_resolution.width, _cur_resolution.height);
275 this->UpdatePalette(0, 256);
280 * An edit box lost the input focus. Abort character compositing if necessary.
282 void VideoDriver_Cocoa::EditBoxLostFocus()
284 [ [ this->cocoaview inputContext ] discardMarkedText ];
285 /* Clear any marked string from the current edit box. */
286 HandleTextInput(NULL, true);
290 * Get the resolution of the main screen.
292 Dimension VideoDriver_Cocoa::GetScreenSize() const
294 NSRect frame = [ [ NSScreen mainScreen ] frame ];
295 return { static_cast<uint>(NSWidth(frame)), static_cast<uint>(NSHeight(frame)) };
298 /** Get DPI scale of our window. */
299 float VideoDriver_Cocoa::GetDPIScale()
301 return this->cocoaview != nil ? [ this->cocoaview getContentsScale ] : 1.0f;
305 * Are we in fullscreen mode?
306 * @return whether fullscreen mode is currently used
308 bool VideoDriver_Cocoa::IsFullscreen()
310 return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0;
314 * Handle a change of the display area.
316 void VideoDriver_Cocoa::GameSizeChanged()
318 /* Tell the game that the resolution has changed */
319 _screen.width = this->window_width;
320 _screen.height = this->window_height;
321 _screen.pitch = this->buffer_depth == 8 ? this->window_width : this->window_pitch;
322 _screen.dst_ptr = this->buffer_depth == 8 ? this->pixel_buffer : this->window_buffer;
324 /* Store old window size if we entered fullscreen mode. */
325 bool fullscreen = this->IsFullscreen();
326 if (fullscreen && !_fullscreen) this->orig_res = _cur_resolution;
327 _fullscreen = fullscreen;
329 BlitterFactory::GetCurrentBlitter()->PostResize();
333 /* We need to store the window size as non-Retina size in
334 * the config file to get same windows size on next start. */
335 _cur_resolution.width = [ this->cocoaview frame ].size.width;
336 _cur_resolution.height = [ this->cocoaview frame ].size.height;
340 * Update the video mode.
342 void VideoDriver_Cocoa::UpdateVideoModes()
344 _resolutions.clear();
346 if (this->IsFullscreen()) {
347 /* Full screen, there is only one possible resolution. */
348 NSSize screen = [ [ this->window screen ] frame ].size;
349 _resolutions.emplace_back((uint)screen.width, (uint)screen.height);
351 /* Windowed; offer a selection of common window sizes up until the
352 * maximum usable screen space. This excludes the menu and dock areas. */
353 NSSize maxSize = [ [ NSScreen mainScreen] visibleFrame ].size;
354 for (const auto &d : _default_resolutions) {
355 if (d.width < maxSize.width && d.height < maxSize.height) _resolutions.push_back(d);
357 _resolutions.emplace_back((uint)maxSize.width, (uint)maxSize.height);
362 * Build window and view with a given size.
363 * @param width Window width.
364 * @param height Window height.
366 bool VideoDriver_Cocoa::MakeWindow(int width, int height)
370 /* Limit window size to screen frame. */
371 NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size;
372 if (width > screen_size.width) width = screen_size.width;
373 if (height > screen_size.height) height = screen_size.height;
375 NSRect contentRect = NSMakeRect(0, 0, width, height);
377 /* Create main window. */
378 unsigned int style = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask;
379 this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO driver:this ];
380 if (this->window == nil) {
381 DEBUG(driver, 0, "Could not create the Cocoa window.");
386 /* Add built in full-screen support when available (OS X 10.7 and higher)
387 * This code actually compiles for 10.5 and later, but only makes sense in conjunction
388 * with the quartz fullscreen support as found only in 10.7 and later. */
389 if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
390 NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ];
391 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
392 [ this->window setCollectionBehavior:behavior ];
394 NSButton* fullscreenButton = [ this->window standardWindowButton:NSWindowFullScreenButton ];
395 [ fullscreenButton setAction:@selector(toggleFullScreen:) ];
396 [ fullscreenButton setTarget:this->window ];
399 this->delegate = [ [ OTTD_CocoaWindowDelegate alloc ] initWithDriver:this ];
400 [ this->window setDelegate:this->delegate ];
402 [ this->window center ];
403 [ this->window makeKeyAndOrderFront:nil ];
405 /* Create wrapper view for input and event handling. */
406 NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ];
407 this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame ];
408 if (this->cocoaview == nil) {
409 DEBUG(driver, 0, "Could not create the event wrapper view.");
413 [ this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
415 /* Create content view. */
416 NSView *draw_view = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ];
417 if (draw_view == nil) {
418 DEBUG(driver, 0, "Could not create the drawing view.");
422 [ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
424 /* Create view chain: window -> input wrapper view -> content view. */
425 [ this->window setContentView:this->cocoaview ];
426 [ this->cocoaview addSubview:draw_view ];
427 [ this->window makeFirstResponder:this->cocoaview ];
428 [ draw_view release ];
430 [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ];
431 CGColorSpaceRelease(this->color_space);
432 this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
433 if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB();
434 if (this->color_space == nullptr) error("Could not get a valid colour space for drawing.");
438 this->UpdatePalette(0, 256);
439 this->AllocateBackingStore();
445 * This function copies 8bpp pixels to the screen buffer in 32bpp windowed mode.
447 * @param left The x coord for the left edge of the box to blit.
448 * @param top The y coord for the top edge of the box to blit.
449 * @param right The x coord for the right edge of the box to blit.
450 * @param bottom The y coord for the bottom edge of the box to blit.
452 void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bottom)
454 const uint32 *pal = this->palette;
455 const uint8 *src = (uint8*)this->pixel_buffer;
456 uint32 *dst = (uint32*)this->window_buffer;
457 uint width = this->window_width;
458 uint pitch = this->window_pitch;
460 for (int y = top; y < bottom; y++) {
461 for (int x = left; x < right; x++) {
462 dst[y * pitch + x] = pal[src[y * width + x]];
469 * @param force_update Whether to redraw unconditionally
471 void VideoDriver_Cocoa::Paint()
473 PerformanceMeasurer framerate(PFE_VIDEO);
475 /* Check if we need to do anything */
476 if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return;
478 if (this->num_dirty_rects >= lengthof(this->dirty_rects)) {
479 this->num_dirty_rects = 1;
480 this->dirty_rects[0].left = 0;
481 this->dirty_rects[0].top = 0;
482 this->dirty_rects[0].right = this->window_width;
483 this->dirty_rects[0].bottom = this->window_height;
486 /* Build the region of dirty rectangles */
487 for (uint i = 0; i < this->num_dirty_rects; i++) {
488 /* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */
489 if (this->buffer_depth == 8) {
491 this->dirty_rects[i].left,
492 this->dirty_rects[i].top,
493 this->dirty_rects[i].right,
494 this->dirty_rects[i].bottom
499 dirtyrect.origin.x = this->dirty_rects[i].left;
500 dirtyrect.origin.y = this->window_height - this->dirty_rects[i].bottom;
501 dirtyrect.size.width = this->dirty_rects[i].right - this->dirty_rects[i].left;
502 dirtyrect.size.height = this->dirty_rects[i].bottom - this->dirty_rects[i].top;
504 /* Normally drawRect will be automatically called by Mac OS X during next update cycle,
505 * and then blitting will occur. */
506 [ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
509 this->num_dirty_rects = 0;
512 /** Update the palette. */
513 void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors)
515 if (this->buffer_depth != 8) return;
517 for (uint i = first_color; i < first_color + num_colors; i++) {
518 uint32 clr = 0xff000000;
519 clr |= (uint32)_cur_palette.palette[i].r << 16;
520 clr |= (uint32)_cur_palette.palette[i].g << 8;
521 clr |= (uint32)_cur_palette.palette[i].b;
522 this->palette[i] = clr;
525 this->num_dirty_rects = lengthof(this->dirty_rects);
528 /** Clear buffer to opaque black. */
529 static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height)
531 uint32 fill = Colour(0, 0, 0).data;
532 for (uint32 y = 0; y < height; y++) {
533 for (uint32 x = 0; x < pitch; x++) {
534 buffer[y * pitch + x] = fill;
539 /** Resize the window. */
540 void VideoDriver_Cocoa::AllocateBackingStore()
542 if (this->window == nil || this->cocoaview == nil || this->setup) return;
544 NSRect newframe = [ this->cocoaview getRealRect:[ this->cocoaview frame ] ];
546 this->window_width = (int)newframe.size.width;
547 this->window_height = (int)newframe.size.height;
548 this->window_pitch = Align(this->window_width, 16 / sizeof(uint32)); // Quartz likes lines that are multiple of 16-byte.
549 this->buffer_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
551 /* Create Core Graphics Context */
552 free(this->window_buffer);
553 this->window_buffer = malloc(this->window_pitch * this->window_height * sizeof(uint32));
554 /* Initialize with opaque black. */
555 ClearWindowBuffer((uint32 *)this->window_buffer, this->window_pitch, this->window_height);
557 CGContextRelease(this->cgcontext);
558 this->cgcontext = CGBitmapContextCreate(
559 this->window_buffer, // data
560 this->window_width, // width
561 this->window_height, // height
562 8, // bits per component
563 this->window_pitch * 4, // bytes per row
564 this->color_space, // color space
565 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host
568 assert(this->cgcontext != NULL);
569 CGContextSetShouldAntialias(this->cgcontext, FALSE);
570 CGContextSetAllowsAntialiasing(this->cgcontext, FALSE);
571 CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone);
573 if (this->buffer_depth == 8) {
574 free(this->pixel_buffer);
575 this->pixel_buffer = malloc(this->window_width * this->window_height);
576 if (this->pixel_buffer == nullptr) usererror("Out of memory allocating pixel buffer");
578 free(this->pixel_buffer);
579 this->pixel_buffer = nullptr;
583 this->num_dirty_rects = lengthof(this->dirty_rects);
584 this->GameSizeChanged();
587 /** Check if palette updates need to be performed. */
588 void VideoDriver_Cocoa::CheckPaletteAnim()
590 if (_cur_palette.count_dirty != 0) {
591 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
593 switch (blitter->UsePaletteAnimation()) {
594 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
595 this->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty);
598 case Blitter::PALETTE_ANIMATION_BLITTER:
599 blitter->PaletteAnimate(_cur_palette);
602 case Blitter::PALETTE_ANIMATION_NONE:
608 _cur_palette.count_dirty = 0;
614 * Poll and handle a single event from the OS.
615 * @return True if there was an event to handle.
617 bool VideoDriver_Cocoa::PollEvent()
620 uint32 et0 = GetTick();
622 NSEvent *event = [ NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[ NSDate distantPast ] inMode:NSDefaultRunLoopMode dequeue:YES ];
624 _tEvent += GetTick() - et0;
627 if (event == nil) return false;
629 [ NSApp sendEvent:event ];
634 void VideoDriver_Cocoa::InputLoop()
636 NSUInteger cur_mods = [ NSEvent modifierFlags ];
638 bool old_ctrl_pressed = _ctrl_pressed;
640 _ctrl_pressed = (cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSControlKeyMask : NSCommandKeyMask)) != 0;
641 _shift_pressed = (cur_mods & NSShiftKeyMask) != 0;
644 if (_shift_pressed) {
648 if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
649 } else if (_fast_forward & 2) {
653 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
656 /** Main game loop. */
657 void VideoDriver_Cocoa::GameLoop()
659 auto cur_ticks = std::chrono::steady_clock::now();
660 auto last_realtime_tick = cur_ticks;
661 auto next_game_tick = cur_ticks;
662 auto next_draw_tick = cur_ticks;
667 InteractiveRandom(); // randomness
669 while (this->PollEvent()) {}
672 /* Restore saved resolution if in fullscreen mode. */
673 if (this->IsFullscreen()) _cur_resolution = this->orig_res;
678 cur_ticks = std::chrono::steady_clock::now();
680 /* If more than a millisecond has passed, increase the _realtime_tick. */
681 if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
682 auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
683 _realtime_tick += delta.count();
684 last_realtime_tick += delta;
687 if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
688 if (_fast_forward && !_pause_mode) {
689 next_game_tick = cur_ticks + this->GetGameInterval();
691 next_game_tick += this->GetGameInterval();
692 /* Avoid next_game_tick getting behind more and more if it cannot keep up. */
693 if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
699 /* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
700 if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
701 next_draw_tick += this->GetDrawInterval();
702 /* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
703 if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
708 this->CheckPaletteAnim();
713 /* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
714 if (!_fast_forward || _pause_mode) {
715 /* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
716 auto next_tick = std::min(next_draw_tick, next_game_tick);
717 auto now = std::chrono::steady_clock::now();
719 if (next_tick > now) {
720 std::this_thread::sleep_for(next_tick - now);
728 @implementation OTTD_QuartzView
730 - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv
732 if (self = [ super initWithFrame:frameRect ]) {
735 /* We manage our content updates ourselves. */
736 self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
737 self.wantsLayer = YES;
739 self.layer.magnificationFilter = kCAFilterNearest;
744 - (BOOL)acceptsFirstResponder
754 - (BOOL)wantsUpdateLayer
761 if (driver->cgcontext == nullptr) return;
763 /* Set layer contents to our backing buffer, which avoids needless copying. */
764 CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext);
765 self.layer.contents = (__bridge id)fullImage;
766 CGImageRelease(fullImage);
769 - (void)viewDidChangeBackingProperties
771 [ super viewDidChangeBackingProperties ];
773 self.layer.contentsScale = [ driver->cocoaview getContentsScale ];
778 #endif /* WITH_COCOA */