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>
24 #import <QuartzCore/QuartzCore.h>
28 #include "../../openttd.h"
29 #include "../../debug.h"
30 #include "../../error_func.h"
31 #include "../../core/geometry_func.hpp"
32 #include "../../core/math_func.hpp"
34 #include "cocoa_wnd.h"
35 #include "../../blitter/factory.hpp"
36 #include "../../framerate_type.h"
37 #include "../../gfx_func.h"
38 #include "../../thread.h"
39 #include "../../core/random_func.hpp"
40 #include "../../progress.h"
41 #include "../../settings_type.h"
42 #include "../../window_func.h"
43 #include "../../window_gui.h"
45 #import <sys/param.h> /* for MAXPATHLEN */
46 #import <sys/time.h> /* gettimeofday */
48 /* The 10.12 SDK added new names for some enum constants and
49 * deprecated the old ones. As there's no functional change in any
50 * way, just use a define for older SDKs to the old names. */
51 #ifndef HAVE_OSX_1012_SDK
52 # define NSEventModifierFlagCommand NSCommandKeyMask
53 # define NSEventModifierFlagControl NSControlKeyMask
54 # define NSEventModifierFlagOption NSAlternateKeyMask
55 # define NSEventModifierFlagShift NSShiftKeyMask
56 # define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
60 * Important notice regarding all modifications!!!!!!!
61 * There are certain limitations because the file is objective C++.
62 * gdb has limitations.
63 * C++ and objective C code can't be joined in all cases (classes stuff).
64 * Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
67 bool _cocoa_video_started = false;
68 static Palette _local_palette; ///< Current palette to use for drawing.
70 extern bool _tab_is_down;
73 /** List of common display/window sizes. */
74 static const Dimension _default_resolutions[] = {
90 VideoDriver_Cocoa::VideoDriver_Cocoa(bool uses_hardware_acceleration)
91 : VideoDriver(uses_hardware_acceleration)
94 this->buffer_locked = false;
96 this->refresh_sys_sprites = true;
99 this->cocoaview = nil;
100 this->delegate = nil;
102 this->color_space = nullptr;
104 this->dirty_rect = {};
107 /** Stop Cocoa video driver. */
108 void VideoDriver_Cocoa::Stop()
110 if (!_cocoa_video_started) return;
112 CocoaExitApplication();
114 /* Release window mode resources */
115 if (this->window != nil) [ this->window close ];
116 [ this->cocoaview release ];
117 [ this->delegate release ];
119 CGColorSpaceRelease(this->color_space);
121 _cocoa_video_started = false;
124 /** Common driver initialization. */
125 std::optional<std::string_view> VideoDriver_Cocoa::Initialize()
127 if (!MacOSVersionIsAtLeast(10, 7, 0)) return "The Cocoa video driver requires Mac OS X 10.7 or later.";
129 if (_cocoa_video_started) return "Already started";
130 _cocoa_video_started = true;
132 /* Don't create a window or enter fullscreen if we're just going to show a dialog. */
133 if (!CocoaSetupApplication()) return std::nullopt;
135 this->UpdateAutoResolution();
136 this->orig_res = _cur_resolution;
142 * Set dirty a rectangle managed by a cocoa video subdriver.
143 * @param left Left x cooordinate of the dirty rectangle.
144 * @param top Uppder y coordinate of the dirty rectangle.
145 * @param width Width of the dirty rectangle.
146 * @param height Height of the dirty rectangle.
148 void VideoDriver_Cocoa::MakeDirty(int left, int top, int width, int height)
150 Rect r = {left, top, left + width, top + height};
151 this->dirty_rect = BoundingRect(this->dirty_rect, r);
155 * Start the main programme loop when using a cocoa video driver.
157 void VideoDriver_Cocoa::MainLoop()
159 /* Restart game loop if it was already running (e.g. after bootstrapping),
160 * otherwise this call is a no-op. */
161 [ [ NSNotificationCenter defaultCenter ] postNotificationName:OTTDMainLaunchGameEngine object:nil ];
163 /* Start the main event loop. */
168 * Change the resolution when using a cocoa video driver.
169 * @param w New window width.
170 * @param h New window height.
171 * @return Whether the video driver was successfully updated.
173 bool VideoDriver_Cocoa::ChangeResolution(int w, int h)
175 NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size;
176 w = std::min(w, (int)screen_size.width);
177 h = std::min(h, (int)screen_size.height);
179 NSRect contentRect = NSMakeRect(0, 0, w, h);
180 [ this->window setContentSize:contentRect.size ];
182 /* Ensure frame height - title bar height >= view height */
183 float content_height = [ this->window contentRectForFrameRect:[ this->window frame ] ].size.height;
184 contentRect.size.height = Clamp(h, 0, (int)content_height);
186 if (this->cocoaview != nil) {
187 h = (int)contentRect.size.height;
188 [ this->cocoaview setFrameSize:contentRect.size ];
191 [ (OTTD_CocoaWindow *)this->window center ];
192 this->AllocateBackingStore();
198 * Toggle between windowed and full screen mode for cocoa display driver.
199 * @param full_screen Whether to switch to full screen or not.
200 * @return Whether the mode switch was successful.
202 bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen)
204 if (this->IsFullscreen() == full_screen) return true;
206 if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
207 [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ];
209 /* Hide the menu bar and the dock */
210 [ NSMenu setMenuBarVisible:!full_screen ];
212 this->UpdateVideoModes();
213 InvalidateWindowClassesData(WC_GAME_OPTIONS, 3);
220 void VideoDriver_Cocoa::ClearSystemSprites()
222 this->refresh_sys_sprites = true;
225 void VideoDriver_Cocoa::PopulateSystemSprites()
227 if (this->refresh_sys_sprites && this->window != nil) {
228 [ this->window refreshSystemSprites ];
229 this->refresh_sys_sprites = false;
234 * Callback invoked after the blitter was changed.
235 * @return True if no error.
237 bool VideoDriver_Cocoa::AfterBlitterChange()
239 this->AllocateBackingStore(true);
244 * An edit box lost the input focus. Abort character compositing if necessary.
246 void VideoDriver_Cocoa::EditBoxLostFocus()
248 [ [ this->cocoaview inputContext ] performSelectorOnMainThread:@selector(discardMarkedText) withObject:nil waitUntilDone:[ NSThread isMainThread ] ];
249 /* Clear any marked string from the current edit box. */
250 HandleTextInput(nullptr, true);
254 * Get refresh rates of all connected monitors.
256 std::vector<int> VideoDriver_Cocoa::GetListOfMonitorRefreshRates()
258 std::vector<int> rates{};
260 if (MacOSVersionIsAtLeast(10, 6, 0)) {
261 std::array<CGDirectDisplayID, 16> displays;
264 CGGetActiveDisplayList(displays.size(), displays.data(), &count);
266 for (uint32_t i = 0; i < count; i++) {
267 CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
268 int rate = (int)CGDisplayModeGetRefreshRate(mode);
269 if (rate > 0) rates.push_back(rate);
270 CGDisplayModeRelease(mode);
278 * Get the resolution of the main screen.
280 Dimension VideoDriver_Cocoa::GetScreenSize() const
282 NSRect frame = [ [ NSScreen mainScreen ] frame ];
283 return { static_cast<uint>(NSWidth(frame)), static_cast<uint>(NSHeight(frame)) };
286 /** Get DPI scale of our window. */
287 float VideoDriver_Cocoa::GetDPIScale()
289 return this->cocoaview != nil ? [ this->cocoaview getContentsScale ] : 1.0f;
292 /** Lock video buffer for drawing if it isn't already mapped. */
293 bool VideoDriver_Cocoa::LockVideoBuffer()
295 if (this->buffer_locked) return false;
296 this->buffer_locked = true;
298 _screen.dst_ptr = this->GetVideoPointer();
299 assert(_screen.dst_ptr != nullptr);
304 /** Unlock video buffer. */
305 void VideoDriver_Cocoa::UnlockVideoBuffer()
307 if (_screen.dst_ptr != nullptr) {
308 /* Hand video buffer back to the drawing backend. */
309 this->ReleaseVideoPointer();
310 _screen.dst_ptr = nullptr;
313 this->buffer_locked = false;
317 * Are we in fullscreen mode?
318 * @return whether fullscreen mode is currently used
320 bool VideoDriver_Cocoa::IsFullscreen()
322 return this->window != nil && ([ this->window styleMask ] & NSWindowStyleMaskFullScreen) != 0;
326 * Handle a change of the display area.
328 void VideoDriver_Cocoa::GameSizeChanged()
330 /* Store old window size if we entered fullscreen mode. */
331 bool fullscreen = this->IsFullscreen();
332 if (fullscreen && !_fullscreen) this->orig_res = _cur_resolution;
333 _fullscreen = fullscreen;
335 BlitterFactory::GetCurrentBlitter()->PostResize();
339 /* We need to store the window size as non-Retina size in
340 * the config file to get same windows size on next start. */
341 _cur_resolution.width = [ this->cocoaview frame ].size.width;
342 _cur_resolution.height = [ this->cocoaview frame ].size.height;
346 * Update the video mode.
348 void VideoDriver_Cocoa::UpdateVideoModes()
350 _resolutions.clear();
352 if (this->IsFullscreen()) {
353 /* Full screen, there is only one possible resolution. */
354 NSSize screen = [ [ this->window screen ] frame ].size;
355 _resolutions.emplace_back((uint)screen.width, (uint)screen.height);
357 /* Windowed; offer a selection of common window sizes up until the
358 * maximum usable screen space. This excludes the menu and dock areas. */
359 NSSize maxSize = [ [ NSScreen mainScreen] visibleFrame ].size;
360 for (const auto &d : _default_resolutions) {
361 if (d.width < maxSize.width && d.height < maxSize.height) _resolutions.push_back(d);
363 _resolutions.emplace_back((uint)maxSize.width, (uint)maxSize.height);
368 * Build window and view with a given size.
369 * @param width Window width.
370 * @param height Window height.
372 bool VideoDriver_Cocoa::MakeWindow(int width, int height)
376 /* Limit window size to screen frame. */
377 NSSize screen_size = [ [ NSScreen mainScreen ] frame ].size;
378 if (width > screen_size.width) width = screen_size.width;
379 if (height > screen_size.height) height = screen_size.height;
381 NSRect contentRect = NSMakeRect(0, 0, width, height);
383 /* Create main window. */
384 #ifdef HAVE_OSX_1012_SDK
385 unsigned int style = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskClosable;
387 unsigned int style = NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask;
389 this->window = [ [ OTTD_CocoaWindow alloc ] initWithContentRect:contentRect styleMask:style backing:NSBackingStoreBuffered defer:NO driver:this ];
390 if (this->window == nil) {
391 Debug(driver, 0, "Could not create the Cocoa window.");
396 /* Add built in full-screen support when available (OS X 10.7 and higher)
397 * This code actually compiles for 10.5 and later, but only makes sense in conjunction
398 * with the quartz fullscreen support as found only in 10.7 and later. */
399 if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
400 NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ];
401 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
402 [ this->window setCollectionBehavior:behavior ];
404 NSButton *fullscreenButton = [ this->window standardWindowButton:NSWindowZoomButton ];
405 [ fullscreenButton setAction:@selector(toggleFullScreen:) ];
406 [ fullscreenButton setTarget:this->window ];
409 this->delegate = [ [ OTTD_CocoaWindowDelegate alloc ] initWithDriver:this ];
410 [ this->window setDelegate:this->delegate ];
412 [ this->window center ];
413 [ this->window makeKeyAndOrderFront:nil ];
415 /* Create wrapper view for input and event handling. */
416 NSRect view_frame = [ this->window contentRectForFrameRect:[ this->window frame ] ];
417 this->cocoaview = [ [ OTTD_CocoaView alloc ] initWithFrame:view_frame ];
418 if (this->cocoaview == nil) {
419 Debug(driver, 0, "Could not create the event wrapper view.");
423 [ this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
425 /* Create content view. */
426 NSView *draw_view = this->AllocateDrawView();
427 if (draw_view == nil) {
428 Debug(driver, 0, "Could not create the drawing view.");
432 [ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
434 /* Create view chain: window -> input wrapper view -> content view. */
435 [ this->window setContentView:this->cocoaview ];
436 [ this->cocoaview addSubview:draw_view ];
437 [ this->window makeFirstResponder:this->cocoaview ];
438 [ draw_view release ];
440 [ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ];
441 CGColorSpaceRelease(this->color_space);
442 this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
443 if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB();
444 if (this->color_space == nullptr) FatalError("Could not get a valid colour space for drawing.");
453 * Poll and handle a single event from the OS.
454 * @return True if there was an event to handle.
456 bool VideoDriver_Cocoa::PollEvent()
458 #ifdef HAVE_OSX_1012_SDK
459 NSEventMask mask = NSEventMaskAny;
461 NSEventMask mask = NSAnyEventMask;
463 NSEvent *event = [ NSApp nextEventMatchingMask:mask untilDate:[ NSDate distantPast ] inMode:NSDefaultRunLoopMode dequeue:YES ];
465 if (event == nil) return false;
467 [ NSApp sendEvent:event ];
472 void VideoDriver_Cocoa::InputLoop()
474 NSUInteger cur_mods = [ NSEvent modifierFlags ];
476 bool old_ctrl_pressed = _ctrl_pressed;
478 _ctrl_pressed = (cur_mods & ( _settings_client.gui.right_mouse_btn_emulation != RMBE_CONTROL ? NSEventModifierFlagControl : NSEventModifierFlagCommand)) != 0;
479 _shift_pressed = (cur_mods & NSEventModifierFlagShift) != 0;
481 this->fast_forward_key_pressed = _tab_is_down;
483 if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
486 /** Main game loop. */
487 void VideoDriver_Cocoa::MainLoopReal()
489 this->StartGameThread();
494 /* Restore saved resolution if in fullscreen mode. */
495 if (this->IsFullscreen()) _cur_resolution = this->orig_res;
500 this->SleepTillNextTick();
504 this->StopGameThread();
508 /* Subclass of OTTD_CocoaView to fix Quartz rendering */
509 @interface OTTD_QuartzView : NSView {
510 VideoDriver_CocoaQuartz *driver;
512 - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_CocoaQuartz *)drv;
515 @implementation OTTD_QuartzView
517 - (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_CocoaQuartz *)drv
519 if (self = [ super initWithFrame:frameRect ]) {
522 /* We manage our content updates ourselves. */
523 self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
524 self.wantsLayer = YES;
526 self.layer.magnificationFilter = kCAFilterNearest;
527 self.layer.opaque = YES;
532 - (BOOL)acceptsFirstResponder
542 - (BOOL)wantsUpdateLayer
549 if (driver->cgcontext == nullptr) return;
551 /* Set layer contents to our backing buffer, which avoids needless copying. */
552 CGImageRef fullImage = CGBitmapContextCreateImage(driver->cgcontext);
553 self.layer.contents = (__bridge id)fullImage;
554 CGImageRelease(fullImage);
557 - (void)viewDidChangeBackingProperties
559 [ super viewDidChangeBackingProperties ];
561 self.layer.contentsScale = [ driver->cocoaview getContentsScale ];
567 static FVideoDriver_CocoaQuartz iFVideoDriver_CocoaQuartz;
569 /** Clear buffer to opaque black. */
570 static void ClearWindowBuffer(uint32_t *buffer, uint32_t pitch, uint32_t height)
572 uint32_t fill = Colour(0, 0, 0).data;
573 for (uint32_t y = 0; y < height; y++) {
574 for (uint32_t x = 0; x < pitch; x++) {
575 buffer[y * pitch + x] = fill;
580 VideoDriver_CocoaQuartz::VideoDriver_CocoaQuartz()
582 this->window_width = 0;
583 this->window_height = 0;
584 this->window_pitch = 0;
585 this->buffer_depth = 0;
586 this->window_buffer = nullptr;
587 this->pixel_buffer = nullptr;
589 this->cgcontext = nullptr;
592 std::optional<std::string_view> VideoDriver_CocoaQuartz::Start(const StringList ¶m)
594 auto err = this->Initialize();
597 int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
598 if (bpp != 8 && bpp != 32) {
600 return "The cocoa quartz subdriver only supports 8 and 32 bpp.";
603 bool fullscreen = _fullscreen;
604 if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) {
606 return "Could not create window";
609 this->AllocateBackingStore(true);
611 if (fullscreen) this->ToggleFullscreen(fullscreen);
613 this->GameSizeChanged();
614 this->UpdateVideoModes();
616 this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
622 void VideoDriver_CocoaQuartz::Stop()
624 this->VideoDriver_Cocoa::Stop();
626 CGContextRelease(this->cgcontext);
628 free(this->window_buffer);
629 free(this->pixel_buffer);
632 NSView *VideoDriver_CocoaQuartz::AllocateDrawView()
634 return [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ];
637 /** Resize the window. */
638 void VideoDriver_CocoaQuartz::AllocateBackingStore(bool)
640 if (this->window == nil || this->cocoaview == nil || this->setup) return;
642 this->UpdatePalette(0, 256);
644 NSRect newframe = [ this->cocoaview getRealRect:[ this->cocoaview frame ] ];
646 this->window_width = (int)newframe.size.width;
647 this->window_height = (int)newframe.size.height;
648 this->window_pitch = Align(this->window_width, 16 / sizeof(uint32_t)); // Quartz likes lines that are multiple of 16-byte.
649 this->buffer_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
651 /* Create Core Graphics Context */
652 free(this->window_buffer);
653 this->window_buffer = malloc(this->window_pitch * this->window_height * sizeof(uint32_t));
654 /* Initialize with opaque black. */
655 ClearWindowBuffer((uint32_t *)this->window_buffer, this->window_pitch, this->window_height);
657 CGContextRelease(this->cgcontext);
658 this->cgcontext = CGBitmapContextCreate(
659 this->window_buffer, // data
660 this->window_width, // width
661 this->window_height, // height
662 8, // bits per component
663 this->window_pitch * 4, // bytes per row
664 this->color_space, // color space
665 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host
668 assert(this->cgcontext != nullptr);
669 CGContextSetShouldAntialias(this->cgcontext, FALSE);
670 CGContextSetAllowsAntialiasing(this->cgcontext, FALSE);
671 CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone);
673 if (this->buffer_depth == 8) {
674 free(this->pixel_buffer);
675 this->pixel_buffer = malloc(this->window_width * this->window_height);
676 if (this->pixel_buffer == nullptr) UserError("Out of memory allocating pixel buffer");
678 free(this->pixel_buffer);
679 this->pixel_buffer = nullptr;
682 /* Tell the game that the resolution has changed */
683 _screen.width = this->window_width;
684 _screen.height = this->window_height;
685 _screen.pitch = this->buffer_depth == 8 ? this->window_width : this->window_pitch;
686 _screen.dst_ptr = this->GetVideoPointer();
689 this->MakeDirty(0, 0, _screen.width, _screen.height);
690 this->GameSizeChanged();
694 * This function copies 8bpp pixels from the screen buffer in 32bpp windowed mode.
696 * @param left The x coord for the left edge of the box to blit.
697 * @param top The y coord for the top edge of the box to blit.
698 * @param right The x coord for the right edge of the box to blit.
699 * @param bottom The y coord for the bottom edge of the box to blit.
701 void VideoDriver_CocoaQuartz::BlitIndexedToView32(int left, int top, int right, int bottom)
703 const uint32_t *pal = this->palette;
704 const uint8_t *src = (uint8_t*)this->pixel_buffer;
705 uint32_t *dst = (uint32_t*)this->window_buffer;
706 uint width = this->window_width;
707 uint pitch = this->window_pitch;
709 for (int y = top; y < bottom; y++) {
710 for (int x = left; x < right; x++) {
711 dst[y * pitch + x] = pal[src[y * width + x]];
716 /** Update the palette */
717 void VideoDriver_CocoaQuartz::UpdatePalette(uint first_color, uint num_colors)
719 if (this->buffer_depth != 8) return;
721 for (uint i = first_color; i < first_color + num_colors; i++) {
722 uint32_t clr = 0xff000000;
723 clr |= (uint32_t)_local_palette.palette[i].r << 16;
724 clr |= (uint32_t)_local_palette.palette[i].g << 8;
725 clr |= (uint32_t)_local_palette.palette[i].b;
726 this->palette[i] = clr;
729 this->MakeDirty(0, 0, _screen.width, _screen.height);
732 void VideoDriver_CocoaQuartz::CheckPaletteAnim()
734 if (!CopyPalette(_local_palette)) return;
736 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
738 switch (blitter->UsePaletteAnimation()) {
739 case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
740 this->UpdatePalette(_local_palette.first_dirty, _local_palette.count_dirty);
743 case Blitter::PALETTE_ANIMATION_BLITTER:
744 blitter->PaletteAnimate(_local_palette);
747 case Blitter::PALETTE_ANIMATION_NONE:
756 void VideoDriver_CocoaQuartz::Paint()
758 PerformanceMeasurer framerate(PFE_VIDEO);
760 /* Check if we need to do anything */
761 if (IsEmptyRect(this->dirty_rect) || [ this->window isMiniaturized ]) return;
763 /* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */
764 if (this->buffer_depth == 8) {
766 this->dirty_rect.left,
767 this->dirty_rect.top,
768 this->dirty_rect.right,
769 this->dirty_rect.bottom
774 dirtyrect.origin.x = this->dirty_rect.left;
775 dirtyrect.origin.y = this->window_height - this->dirty_rect.bottom;
776 dirtyrect.size.width = this->dirty_rect.right - this->dirty_rect.left;
777 dirtyrect.size.height = this->dirty_rect.bottom - this->dirty_rect.top;
779 /* Notify OS X that we have new content to show. */
780 [ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
782 /* Tell the OS to get our contents to screen as soon as possible. */
783 [ CATransaction flush ];
785 this->dirty_rect = {};
788 #endif /* WITH_COCOA */