[PATCH 3/3] macdrv: Improve GDI drawing performance.

Elaine Lefler elaineclefler at gmail.com
Tue Mar 22 20:31:51 CDT 2022


Signed-off-by: Elaine Lefler <elaineclefler at gmail.com>
---

Currently GDI apps are unusably slow, especially on Retina displays.
Running PaintTool SAI on my 2017 Macbook Pro, I'm only able to achieve
4 FPS when drawing or panning the canvas. This change brings it up to
the full 60 Hz on the same hardware.

This diff is slightly smaller if compared to wine-6.16, the last
version where drawRect: appeared. Returning to this method is more
performant as long as it's implemented properly. Some retina code has
been removed because it's not necessary when the view isn't rolling
its own layer. The ability to draw multiple rectangles indepedently has
been removed, as this use case is vanishingly rare and tracking a non-
rectangular region hurts performance in the much more common case of
an app painting one large rectangle.

wineWindows is now an instance variable because we were maintaining
this list anyway and polling the window server can cause cursor lag.
A custom hit-test is more efficient and allows us to work around issues
with asychronous drawing. The WineWindow class no longer stores the
reference to surface->mutex, as CGImageCreate can take a non-trivial
amount of time. The lock is more appropriately moved into
create_surface_image - at which point, the instance variable is no
longer used.
---
 dlls/winemac.drv/cocoa_app.h    |   2 +
 dlls/winemac.drv/cocoa_app.m    |  59 +++-
 dlls/winemac.drv/cocoa_window.h |   6 +-
 dlls/winemac.drv/cocoa_window.m | 290 ++++++++++--------
 dlls/winemac.drv/macdrv.h       |   1 -
 dlls/winemac.drv/macdrv_cocoa.h |  13 +-
 dlls/winemac.drv/surface.c      | 527 +++++++++++++++++++++++++-------
 dlls/winemac.drv/window.c       |   4 +-
 8 files changed, 643 insertions(+), 259 deletions(-)

diff --git a/dlls/winemac.drv/cocoa_app.h b/dlls/winemac.drv/cocoa_app.h
index 52c91c0621f..b55c55f2995 100644
--- a/dlls/winemac.drv/cocoa_app.h
+++ b/dlls/winemac.drv/cocoa_app.h
@@ -124,6 +124,8 @@ @interface WineApplicationController : NSObject <NSApplicationDelegate>
 
     id<WineClipCursorHandler> clipCursorHandler;
 
+    NSMutableArray* wineWindows;
+
     NSImage* applicationIcon;
 
     BOOL beenActive;
diff --git a/dlls/winemac.drv/cocoa_app.m b/dlls/winemac.drv/cocoa_app.m
index 8c525333e8d..e8b88286d24 100644
--- a/dlls/winemac.drv/cocoa_app.m
+++ b/dlls/winemac.drv/cocoa_app.m
@@ -47,6 +47,25 @@ + (void) setAllowsAutomaticWindowTabbing:(BOOL)allows;
 #endif
 
 
+ at interface NSWindow (WineNSPointExtensions)
+
+/* Reimplementation of -convertPointFromScreen: which isn't available on all
+ * supported macOS versions */
+- (NSPoint)wineConvertPointFromScreen:(NSPoint)point;
+
+ at end
+
+ at implementation NSWindow (WineNSPointExtensions)
+
+- (NSPoint)wineConvertPointFromScreen:(NSPoint)point
+{
+    NSPoint origin = self.frame.origin;
+    return NSMakePoint(point.x - origin.x, point.y - origin.y);
+}
+
+ at end
+
+
 /***********************************************************************
  *              WineLocalizedString
  *
@@ -204,6 +223,7 @@ - (void) dealloc
         [keyWindows release];
         [eventQueues release];
         [eventQueuesLock release];
+        [wineWindows release];
         if (requestsManipQueue) dispatch_release(requestsManipQueue);
         [requests release];
         if (requestSource)
@@ -571,7 +591,6 @@ - (WineWindow*) frontWineWindow
     - (void) adjustWindowLevels:(BOOL)active
     {
         NSArray* windowNumbers;
-        NSMutableArray* wineWindows;
         NSNumber* windowNumber;
         NSUInteger nextFloatingIndex = 0;
         __block NSInteger maxLevel = NSIntegerMin;
@@ -582,6 +601,8 @@ - (void) adjustWindowLevels:(BOOL)active
 
         if ([NSApp isHidden]) return;
 
+        [wineWindows release];
+
         windowNumbers = [NSWindow windowNumbersWithOptions:0];
         wineWindows = [[NSMutableArray alloc] initWithCapacity:[windowNumbers count]];
 
@@ -652,8 +673,6 @@ - (void) adjustWindowLevels:(BOOL)active
 
         NSEnableScreenUpdates();
 
-        [wineWindows release];
-
         // The above took care of the visible windows on the current space.  That
         // leaves windows on other spaces, minimized windows, and windows which
         // are not ordered in.  We want to leave windows on other spaces alone
@@ -1337,19 +1356,33 @@ - (void) handleMouseMove:(NSEvent*)anEvent
             targetWindow = (WineWindow*)[anEvent window];
         else
         {
-            /* Because of the way -[NSWindow setAcceptsMouseMovedEvents:] works, the
-               event indicates its window is the main window, even if the cursor is
-               over a different window.  Find the actual WineWindow that is under the
-               cursor and post the event as being for that window. */
+            /* Due to our use of NSTrackingArea and the way Cocoa directs mouse
+             * moves, the window receiving the event is probably not the one
+             * with the cursor. Find the window that actually has the cursor by
+             * hit-testing front to back. */
             CGPoint cgpoint = CGEventGetLocation([anEvent CGEvent]);
             NSPoint point = [self flippedMouseLocation:NSPointFromCGPoint(cgpoint)];
-            NSInteger windowUnderNumber;
+            WineWindow* window;
 
-            windowUnderNumber = [NSWindow windowNumberAtPoint:point
-                                  belowWindowWithWindowNumber:0];
-            targetWindow = (WineWindow*)[NSApp windowWithWindowNumber:windowUnderNumber];
-            if (!NSMouseInRect(point, [targetWindow contentRectForFrameRect:[targetWindow frame]], NO))
-                targetWindow = nil;
+            targetWindow = nil;
+
+            for (window in wineWindows)
+            {
+                NSPoint windowPoint = [window wineConvertPointFromScreen:point];
+                BOOL isHit = ([window.contentView hitTest:windowPoint] != nil);
+
+                /* Windows with transparency must be instructed to ignore
+                 * mouse-downs when the hovered pixel is not visible. The
+                 * window's tracking area still reports events. */
+                if (window.needsTransparency)
+                    [window setIgnoresMouseEvents:!isHit && NSMouseInRect(windowPoint, window.contentView.frame, NO)];
+
+                if (isHit)
+                {
+                    targetWindow = (WineWindow*)window;
+                    break;
+                }
+            }
         }
 
         if ([targetWindow isKindOfClass:[WineWindow class]])
diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h
index a83f2aa803b..675abcc659b 100644
--- a/dlls/winemac.drv/cocoa_window.h
+++ b/dlls/winemac.drv/cocoa_window.h
@@ -45,7 +45,6 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
     WineEventQueue* queue;
 
     void* surface;
-    pthread_mutex_t* surface_mutex;
 
     CGDirectDisplayID _lastDisplayID;
     NSTimeInterval _lastDisplayTime;
@@ -53,10 +52,11 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
     NSRect wineFrame;
     NSRect roundedWineFrame;
 
+    NSData* shapeData;
     BOOL shapeChangedSinceLastDraw;
 
     BOOL colorKeyed;
-    CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
+    uint8_t colorKeyRed, colorKeyGreen, colorKeyBlue;
 
     BOOL usePerPixelAlpha;
 
@@ -94,6 +94,8 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
 @property (readonly, nonatomic) BOOL noForeground;
 @property (readonly, nonatomic) BOOL preventsAppActivation;
 @property (readonly, nonatomic) BOOL floating;
+ at property (readonly, nonatomic) BOOL needsTransparency;
+ at property (readonly, nonatomic) BOOL needsLayerTransparency;
 @property (readonly, getter=isFullscreen, nonatomic) BOOL fullscreen;
 @property (readonly, getter=isFakingClose, nonatomic) BOOL fakingClose;
 @property (readonly, nonatomic) NSRect wine_fractionalFrame;
diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m
index bfa7e2fe8cc..74a84aab630 100644
--- a/dlls/winemac.drv/cocoa_window.m
+++ b/dlls/winemac.drv/cocoa_window.m
@@ -309,32 +309,6 @@ static CVReturn WineDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTi
 @end
 
 
-#ifndef MAC_OS_X_VERSION_10_14
- at protocol NSViewLayerContentScaleDelegate <NSObject>
- at optional
-
-    - (BOOL) layer:(CALayer*)layer shouldInheritContentsScale:(CGFloat)newScale fromWindow:(NSWindow*)window;
-
- at end
-#endif
-
-
- at interface CAShapeLayer (WineShapeMaskExtensions)
-
- at property(readonly, nonatomic, getter=isEmptyShaped) BOOL emptyShaped;
-
- at end
-
- at implementation CAShapeLayer (WineShapeMaskExtensions)
-
-    - (BOOL) isEmptyShaped
-    {
-        return CGRectEqualToRect(CGPathGetBoundingBox(self.path), CGRectZero);
-    }
-
- at end
-
-
 @interface WineBaseView : NSView
 @end
 
@@ -351,10 +325,11 @@ - (id) initWithFrame:(NSRect)frame device:(id<MTLDevice>)device;
 #endif
 
 
- at interface WineContentView : WineBaseView <NSTextInputClient, NSViewLayerContentScaleDelegate>
+ at interface WineContentView : WineBaseView <NSTextInputClient>
 {
     NSMutableArray* glContexts;
     NSMutableArray* pendingGlContexts;
+    BOOL _shouldBeHidden;
     BOOL _everHadGLContext;
     BOOL _cachedHasGLDescendant;
     BOOL _cachedHasGLDescendantValid;
@@ -363,7 +338,6 @@ @interface WineContentView : WineBaseView <NSTextInputClient, NSViewLayerContent
     NSMutableAttributedString* markedText;
     NSRange markedTextSelection;
 
-    BOOL _retinaMode;
     int backingSize[2];
 
 #ifdef HAVE_METAL_METAL_H
@@ -402,13 +376,12 @@ @interface WineWindow ()
 @property (retain, readwrite, nonatomic) WineEventQueue* queue;
 
 @property (nonatomic) void* surface;
- at property (nonatomic) pthread_mutex_t* surface_mutex;
 
+ at property (retain, nonatomic) NSData* shapeData;
 @property (nonatomic) BOOL shapeChangedSinceLastDraw;
- at property (readonly, nonatomic) BOOL needsTransparency;
 
 @property (nonatomic) BOOL colorKeyed;
- at property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
+ at property (nonatomic) uint8_t colorKeyRed, colorKeyGreen, colorKeyBlue;
 @property (nonatomic) BOOL usePerPixelAlpha;
 
 @property (assign, nonatomic) void* imeData;
@@ -416,8 +389,6 @@ @interface WineWindow ()
 
 @property (readonly, copy, nonatomic) NSArray* childWineWindows;
 
-    - (void) setShape:(CGPathRef)newShape;
-
     - (void) updateForGLSubviews;
 
     - (BOOL) becameEligibleParentOrChild;
@@ -491,57 +462,76 @@ - (BOOL) isFlipped
         return YES;
     }
 
-    - (BOOL) wantsUpdateLayer
+    - (BOOL) wantsDefaultClipping
+    {
+        /* Don't need this, we already limit our drawing to the dirty region */
+        return NO;
+    }
+
+    - (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
     {
-        return YES /*!_everHadGLContext*/;
+        /* Returning NSNull ensures the layer will never animate itself */
+        return [NSNull null];
     }
 
-    - (void) updateLayer
+    - (BOOL) isOpaque
     {
         WineWindow* window = (WineWindow*)[self window];
-        CGImageRef image = NULL;
-        CGRect imageRect;
-        CALayer* layer = [self layer];
+        return window.contentView == self && !window.needsLayerTransparency;
+    }
 
-        if ([window contentView] != self)
-            return;
+    - (NSView*) hitTest:(NSPoint)point
+    {
+        WineWindow* window = (WineWindow*)[self window];
+        NSPoint localPoint;
+        CGPoint cgPoint;
 
-        if (window.closing || !window.surface || !window.surface_mutex)
-            return;
+        if (window.contentView != self
+                || (!window.shapeData && !window.needsLayerTransparency))
+            return [super hitTest:point];
 
-        pthread_mutex_lock(window.surface_mutex);
-        if (get_surface_blit_rects(window.surface, NULL, NULL))
-        {
-            imageRect = layer.bounds;
-            imageRect.origin.x *= layer.contentsScale;
-            imageRect.origin.y *= layer.contentsScale;
-            imageRect.size.width *= layer.contentsScale;
-            imageRect.size.height *= layer.contentsScale;
-            image = create_surface_image(window.surface, &imageRect, FALSE, window.colorKeyed,
-                                         window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue);
-        }
-        pthread_mutex_unlock(window.surface_mutex);
+        localPoint = [self convertPoint:point fromView:self.superview];
+        cgPoint = cgpoint_win_from_mac(NSPointToCGPoint(localPoint));
 
-        if (image)
+        if (window.shapeData)
         {
-            layer.contents = (id)image;
-            CFRelease(image);
-            [window windowDidDrawContent];
+            NSPoint nsPoint = NSPointFromCGPoint(cgPoint);
+            const CGRect* rects = (const CGRect*)window.shapeData.bytes;
+            NSUInteger count = window.shapeData.length / sizeof(*rects);
+            BOOL inShape = NO;
+            NSUInteger i;
 
-            // If the window may be transparent, then we have to invalidate the
-            // shadow every time we draw.  Also, if this is the first time we've
-            // drawn since changing from transparent to opaque.
-            if (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw)
+            for (i = 0; i < count; i++)
             {
-                window.shapeChangedSinceLastDraw = FALSE;
-                [window invalidateShadow];
+                if (NSMouseInRect(nsPoint, NSRectFromCGRect(rects[i]), NO))
+                {
+                    inShape = YES;
+                    break;
+                }
             }
+
+            if (!inShape)
+                return nil;
         }
+
+        if (window.needsLayerTransparency)
+        {
+            /* Transparent pixels are not supposed to be clickable, but due to
+             * contentView.layer.drawsAsynchronously, Cocoa does not enforce it.
+             * Therefore, we must perform our own per-pixel hit test. */
+            if (!surface_hit_test(window.surface, cgPoint, window.colorKeyed,
+                                  window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue))
+                return nil;
+        }
+
+        return [super hitTest:point];
     }
 
-    - (void) viewWillDraw
+    - (void) drawRect:(NSRect)rect
     {
-        [super viewWillDraw];
+        WineWindow* window = (WineWindow*)[self window];
+        CGRect imageRect = cgrect_win_from_mac(NSRectToCGRect(rect));
+        CGImageRef image;
 
         for (WineOpenGLContext* context in pendingGlContexts)
         {
@@ -554,6 +544,46 @@ - (void) viewWillDraw
         }
         [glContexts addObjectsFromArray:pendingGlContexts];
         [pendingGlContexts removeAllObjects];
+
+        if ([window contentView] != self)
+            return;
+
+        if ((image = create_surface_image(window.surface, &imageRect, window.colorKeyed,
+                        window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue)) != NULL)
+        {
+            CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+            CGContextSetBlendMode(context, kCGBlendModeCopy);
+            /* HQ interpolation should be used with retina to prevent artifacts
+             * on mixed DPI. Not needed for standard DPI. */
+            CGContextSetInterpolationQuality(context, retina_on ? kCGInterpolationHigh : kCGInterpolationNone);
+
+            CGContextDrawImage(context, cgrect_mac_from_win(imageRect), image);
+            CGImageRelease(image);
+
+            [window windowDidDrawContent];
+        }
+
+        /* If the window may be transparent, then we have to invalidate the
+         * shadow every time we draw.  Also, if this is the first time we've
+         * drawn since changing from transparent to opaque. */
+        if (window.drawnSinceShown && (window.colorKeyed || window.usePerPixelAlpha || window.shapeChangedSinceLastDraw))
+        {
+            window.shapeChangedSinceLastDraw = FALSE;
+            [window invalidateShadow];
+        }
+    }
+
+    - (void) setHidden:(BOOL)hidden
+    {
+        if (self.window.contentView == self)
+        {
+            [super setHidden:hidden];
+            return;
+        }
+
+        /* Client views should always remain hidden, unless we have OpenGL */
+        [super setHidden:hidden || !_everHadGLContext];
+        _shouldBeHidden = hidden;
     }
 
     - (void) addGLContext:(WineOpenGLContext*)context
@@ -582,7 +612,10 @@ - (void) addGLContext:(WineOpenGLContext*)context
 
         _everHadGLContext = YES;
         if (!hadContext)
+        {
+            [super setHidden:_shouldBeHidden];
             [self invalidateHasGLDescendant];
+        }
         [(WineWindow*)[self window] updateForGLSubviews];
     }
 
@@ -686,18 +719,9 @@ - (void) setRetinaMode:(int)mode
         [self setWantsBestResolutionOpenGLSurface:mode];
         [self updateGLContexts];
 
-        _retinaMode = !!mode;
-        [self layer].contentsScale = mode ? 2.0 : 1.0;
-        [self layer].minificationFilter = mode ? kCAFilterLinear : kCAFilterNearest;
-        [self layer].magnificationFilter = mode ? kCAFilterLinear : kCAFilterNearest;
         [super setRetinaMode:mode];
     }
 
-    - (BOOL) layer:(CALayer*)layer shouldInheritContentsScale:(CGFloat)newScale fromWindow:(NSWindow*)window
-    {
-        return (_retinaMode || newScale == 1.0);
-    }
-
     - (void) viewDidHide
     {
         [super viewDidHide];
@@ -957,8 +981,8 @@ @implementation WineWindow
 
     @synthesize disabled, noForeground, preventsAppActivation, floating, fullscreen, fakingClose, closing, latentParentWindow, hwnd, queue;
     @synthesize drawnSinceShown;
-    @synthesize surface, surface_mutex;
-    @synthesize shapeChangedSinceLastDraw;
+    @synthesize surface;
+    @synthesize shapeData, shapeChangedSinceLastDraw;
     @synthesize colorKeyed, colorKeyRed, colorKeyGreen, colorKeyBlue;
     @synthesize usePerPixelAlpha;
     @synthesize imeData, commandDone;
@@ -991,7 +1015,6 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w
         [window disableCursorRects];
         [window setShowsResizeIndicator:NO];
         [window setHasShadow:wf->shadow];
-        [window setAcceptsMouseMovedEvents:YES];
         [window setDelegate:window];
         [window setBackgroundColor:[NSColor clearColor]];
         [window setOpaque:NO];
@@ -1010,12 +1033,10 @@ + (WineWindow*) createWindowWithFeatures:(const struct macdrv_window_features*)w
         if (!contentView)
             return nil;
         [contentView setWantsLayer:YES];
-        [contentView layer].minificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest;
-        [contentView layer].magnificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest;
-        [contentView layer].contentsScale = retina_on ? 2.0 : 1.0;
+        [contentView.layer setDrawsAsynchronously:YES];
         [contentView setAutoresizesSubviews:NO];
 
-        /* We use tracking areas in addition to setAcceptsMouseMovedEvents:YES
+        /* We use tracking areas instead of setAcceptsMouseMovedEvents:YES
            because they give us mouse moves in the background. */
         trackingArea = [[[NSTrackingArea alloc] initWithRect:[contentView bounds]
                                                      options:(NSTrackingMouseMoved |
@@ -1062,6 +1083,7 @@ - (void) dealloc
         [queue release];
         [latentChildWindows release];
         [latentParentWindow release];
+        [shapeData release];
         [super dealloc];
     }
 
@@ -2008,47 +2030,80 @@ - (void) setDisabled:(BOOL)newValue
         }
     }
 
+    - (BOOL) needsLayerTransparency
+    {
+        return self.colorKeyed || self.usePerPixelAlpha;
+    }
+
     - (BOOL) needsTransparency
     {
-        return self.contentView.layer.mask || self.colorKeyed || self.usePerPixelAlpha ||
+        return self.shapeData || self.needsLayerTransparency ||
                 (gl_surface_mode == GL_SURFACE_BEHIND && [(WineContentView*)self.contentView hasGLDescendant]);
     }
 
     - (void) checkTransparency
     {
-        if (![self isOpaque] && !self.needsTransparency)
+        if (!self.opaque && !self.needsTransparency)
         {
             self.shapeChangedSinceLastDraw = TRUE;
-            [[self contentView] setNeedsDisplay:YES];
+            [self.contentView setNeedsDisplay:YES];
             [self setBackgroundColor:[NSColor windowBackgroundColor]];
             [self setOpaque:YES];
+            /* Ensure WineApplicationController hasn't cut off mouse events */
+            [self setIgnoresMouseEvents:NO];
         }
-        else if ([self isOpaque] && self.needsTransparency)
+        else if (self.opaque && self.needsTransparency)
         {
             self.shapeChangedSinceLastDraw = TRUE;
-            [[self contentView] setNeedsDisplay:YES];
+            [self.contentView setNeedsDisplay:YES];
             [self setBackgroundColor:[NSColor clearColor]];
             [self setOpaque:NO];
         }
+
+        [self.contentView.layer setOpaque:!self.needsLayerTransparency];
     }
 
-    - (void) setShape:(CGPathRef)newShape
+    - (void) setShapeData:(NSData*)newShapeData
     {
-        CALayer* layer = [[self contentView] layer];
-        CAShapeLayer* mask = (CAShapeLayer*)layer.mask;
-        if (CGPathEqualToPath(newShape, mask.path)) return;
+        if (shapeData == newShapeData)
+            return;
+
+        if ([newShapeData length] == 0)
+            newShapeData = nil;
+
+        if (shapeData)
+        {
+            CGRect boundingBox = CGPathGetBoundingBox([(CAShapeLayer*)self.contentView.layer.mask path]);
+            [shapeData release];
+            [self.contentView setNeedsDisplayInRect:NSRectFromCGRect(boundingBox)];
+        }
+
+        if (newShapeData)
+        {
+            const CGRect* rects = (const CGRect*)newShapeData.bytes;
+            NSUInteger count = newShapeData.length / sizeof(*rects);
+            NSUInteger i;
+
+            CGMutablePathRef path = CGPathCreateMutable();
+            CAShapeLayer* maskLayer;
 
-        if (newShape && !layer.mask)
-            layer.mask = mask = [CAShapeLayer layer];
-        else if (!newShape)
-            layer.mask = mask = nil;
+            for (i = 0; i < count; i++)
+                CGPathAddRect(path, nil, cgrect_mac_from_win(rects[i]));
 
-        if (mask.path)
-            [[self contentView] setNeedsDisplayInRect:NSRectFromCGRect(CGPathGetBoundingBox(mask.path))];
-        if (newShape)
-            [[self contentView] setNeedsDisplayInRect:NSRectFromCGRect(CGPathGetBoundingBox(newShape))];
+            maskLayer = [CAShapeLayer layer];
+            maskLayer.path = path;
+            self.contentView.layer.mask = maskLayer;
 
-        mask.path = newShape;
+            [self.contentView setNeedsDisplayInRect:NSRectFromCGRect(CGPathGetBoundingBox(path))];
+
+            CFRelease(path);
+        }
+        else
+        {
+            self.contentView.layer.mask = nil;
+        }
+
+        shapeData = [newShapeData retain];
         self.shapeChangedSinceLastDraw = TRUE;
 
         [self checkTransparency];
@@ -2250,8 +2305,7 @@ - (void) checkWineDisplayLink
 
     - (BOOL) isEmptyShaped
     {
-        CAShapeLayer* mask = (CAShapeLayer*)[[self contentView] layer].mask;
-        return ([mask isEmptyShaped]);
+        return (self.shapeData.length == sizeof(CGRectZero) && !memcmp(self.shapeData.bytes, &CGRectZero, sizeof(CGRectZero)));
     }
 
     - (BOOL) canProvideSnapshot
@@ -2653,8 +2707,6 @@ - (void) setRetinaMode:(int)mode
 
         [transform scaleBy:scale];
 
-        [[self contentView] layer].mask.contentsScale = mode ? 2.0 : 1.0;
-
         for (WineBaseView* subview in [self.contentView subviews])
         {
             if ([subview isKindOfClass:[WineBaseView class]])
@@ -3260,6 +3312,7 @@ void macdrv_destroy_cocoa_window(macdrv_window w)
     WineWindow* window = (WineWindow*)w;
 
     OnMainThread(^{
+        window.surface = nil;
         window.closing = TRUE;
         [window doOrderOut];
         [window close];
@@ -3426,14 +3479,13 @@ void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent)
 /***********************************************************************
  *              macdrv_set_window_surface
  */
-void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex)
+void macdrv_set_window_surface(macdrv_window w, void *surface)
 {
     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
     WineWindow* window = (WineWindow*)w;
 
     OnMainThread(^{
         window.surface = surface;
-        window.surface_mutex = mutex;
     });
 
     [pool release];
@@ -3471,19 +3523,15 @@ void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count)
     OnMainThread(^{
         if (!rects || !count)
         {
-            [window setShape:NULL];
-            [window checkEmptyShaped];
+            window.shapeData = nil;
         }
         else
         {
-            CGMutablePathRef path;
-            unsigned int i;
-
-            path = CGPathCreateMutable();
-            for (i = 0; i < count; i++)
-                CGPathAddRect(path, NULL, cgrect_mac_from_win(rects[i]));
-            [window setShape:path];
-            CGPathRelease(path);
+            size_t length = sizeof(*rects) * count;
+            if (window.shapeData.length != length || memcmp(window.shapeData.bytes, rects, length))
+            {
+                window.shapeData = [NSData dataWithBytes:rects length:length];
+            }
         }
     });
 
@@ -3506,8 +3554,8 @@ void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha)
 /***********************************************************************
  *              macdrv_set_window_color_key
  */
-void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
-                                 CGFloat keyBlue)
+void macdrv_set_window_color_key(macdrv_window w, uint8_t keyRed, uint8_t keyGreen,
+                                 uint8_t keyBlue)
 {
     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
     WineWindow* window = (WineWindow*)w;
@@ -3604,9 +3652,7 @@ macdrv_view macdrv_create_view(CGRect rect)
 
         view = [[WineContentView alloc] initWithFrame:NSRectFromCGRect(cgrect_mac_from_win(rect))];
         [view setWantsLayer:YES];
-        [view layer].minificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest;
-        [view layer].magnificationFilter = retina_on ? kCAFilterLinear : kCAFilterNearest;
-        [view layer].contentsScale = retina_on ? 2.0 : 1.0;
+        [view.layer setDrawsAsynchronously:YES];
         [view setAutoresizesSubviews:NO];
         [view setAutoresizingMask:NSViewNotSizable];
         [view setHidden:YES];
diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h
index 3eecb26a01e..7b410fbdef0 100644
--- a/dlls/winemac.drv/macdrv.h
+++ b/dlls/winemac.drv/macdrv.h
@@ -209,7 +209,6 @@ extern DWORD CDECL macdrv_MsgWaitForMultipleObjectsEx(DWORD count, const HANDLE
 extern void activate_on_following_focus(void) DECLSPEC_HIDDEN;
 extern struct window_surface *create_surface(macdrv_window window, const RECT *rect,
                                              struct window_surface *old_surface, BOOL use_alpha) DECLSPEC_HIDDEN;
-extern void set_window_surface(macdrv_window window, struct window_surface *window_surface) DECLSPEC_HIDDEN;
 extern void set_surface_use_alpha(struct window_surface *window_surface, BOOL use_alpha) DECLSPEC_HIDDEN;
 extern void surface_clip_to_visible_rect(struct window_surface *window_surface, const RECT *visible_rect) DECLSPEC_HIDDEN;
 
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h
index c7f87888fdc..34769771fa7 100644
--- a/dlls/winemac.drv/macdrv_cocoa.h
+++ b/dlls/winemac.drv/macdrv_cocoa.h
@@ -580,15 +580,16 @@ extern void macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
 extern void macdrv_set_cocoa_window_frame(macdrv_window w, const CGRect* new_frame) DECLSPEC_HIDDEN;
 extern void macdrv_get_cocoa_window_frame(macdrv_window w, CGRect* out_frame) DECLSPEC_HIDDEN;
 extern void macdrv_set_cocoa_parent_window(macdrv_window w, macdrv_window parent) DECLSPEC_HIDDEN;
-extern void macdrv_set_window_surface(macdrv_window w, void *surface, pthread_mutex_t *mutex) DECLSPEC_HIDDEN;
-extern CGImageRef create_surface_image(void *window_surface, CGRect *rect, int copy_data, int color_keyed,
-        CGFloat key_red, CGFloat key_green, CGFloat key_blue) DECLSPEC_HIDDEN;
-extern int get_surface_blit_rects(void *window_surface, const CGRect **rects, int *count) DECLSPEC_HIDDEN;
+extern void macdrv_set_window_surface(macdrv_window w, void *surface) DECLSPEC_HIDDEN;
+extern CGImageRef create_surface_image(void *window_surface, CGRect *dirty_area, int color_keyed,
+                                       uint8_t key_red, uint8_t key_green, uint8_t key_blue) DECLSPEC_HIDDEN;
+extern int surface_hit_test(void *window_surface, CGPoint point, int color_keyed,
+                            uint8_t key_red, uint8_t key_green, uint8_t key_blue) DECLSPEC_HIDDEN;
 extern void macdrv_window_needs_display(macdrv_window w, CGRect rect) DECLSPEC_HIDDEN;
 extern void macdrv_set_window_shape(macdrv_window w, const CGRect *rects, int count) DECLSPEC_HIDDEN;
 extern void macdrv_set_window_alpha(macdrv_window w, CGFloat alpha) DECLSPEC_HIDDEN;
-extern void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat keyGreen,
-                                        CGFloat keyBlue) DECLSPEC_HIDDEN;
+extern void macdrv_set_window_color_key(macdrv_window w, uint8_t keyRed, uint8_t keyGreen,
+                                        uint8_t keyBlue) DECLSPEC_HIDDEN;
 extern void macdrv_clear_window_color_key(macdrv_window w) DECLSPEC_HIDDEN;
 extern void macdrv_window_use_per_pixel_alpha(macdrv_window w, int use_per_pixel_alpha) DECLSPEC_HIDDEN;
 extern void macdrv_give_cocoa_window_focus(macdrv_window w, int activate) DECLSPEC_HIDDEN;
diff --git a/dlls/winemac.drv/surface.c b/dlls/winemac.drv/surface.c
index 6b0abb10780..0eb1f09d417 100644
--- a/dlls/winemac.drv/surface.c
+++ b/dlls/winemac.drv/surface.c
@@ -64,7 +64,7 @@ struct macdrv_window_surface
     HRGN                    region;
     HRGN                    drawn;
     BOOL                    use_alpha;
-    RGNDATA                *blit_data;
+    struct shadow_surface  *shadow;
     BYTE                   *bits;
     pthread_mutex_t         mutex;
     BITMAPINFO              info;   /* variable size, must be last */
@@ -72,25 +72,248 @@ struct macdrv_window_surface
 
 static struct macdrv_window_surface *get_mac_surface(struct window_surface *surface);
 
+/* Shadow surfaces provide a secondary bitmap to enable multithreaded drawing
+ * without locking the main surface for an extended period of time.
+ *
+ * A shadow may contain one or more bitmaps, allocated on demand. Bitmaps are
+ * kept in a linked list, re-using existing memory whenever possible. This is
+ * important because paging in large regions of memory can have a latency of
+ * >20ms, which is not acceptable for drawing.
+ *
+ * Unlike macdrv_window_surface, shadow data can be allocated or deallocated
+ * from a non-Wine thread, so it requires an independent reference counter and
+ * must not use Windows functions. */
+struct shadow_surface
+{
+    pthread_mutex_t         mutex;
+    int                     refcount;
+    int                     bitmap_length;  /* size of shadow_bitmap in bytes */
+    struct shadow_bitmap   *bitmaps;        /* linked list or NULL */
+};
+
+struct shadow_bitmap
+{
+    union
+    {
+        struct
+        {
+            struct shadow_surface  *parent;
+            struct shadow_bitmap   *next;
+            CFAllocatorRef          cfdata_deallocator;
+        };
+        BYTE pad[32];   /* should be a multiple of 16 */
+    };
+    BYTE bits[0];       /* variable length */
+};
+
+/* Number of shadow bitmaps to keep. Rasterization is asynchronous, so more than
+ * one bitmap may be alive at any given time. Usually 2 is sufficient. This is
+ * a failsafe to ensure the list can't grow indefinitely. */
+#define SHADOW_BITMAP_MAX           3
+
+static struct shadow_bitmap *shadow_bitmap_free(struct shadow_bitmap *bitmap,
+                                                int bitmap_length);
+static void shadow_cfdata_dealloc(void *ptr, void *info);
+
 /***********************************************************************
- *              update_blit_data
+ *              shadow_surface_create
+ *
+ * Creates a new shadow surface. Its refcount is initially set to 1.
+ * Returns NULL on failure.
  */
-static void update_blit_data(struct macdrv_window_surface *surface)
+static struct shadow_surface *shadow_surface_create(struct macdrv_window_surface* parent)
 {
-    HeapFree(GetProcessHeap(), 0, surface->blit_data);
-    surface->blit_data = NULL;
+    /* Note: actual bitmap length is rounded up to the nearest pagesize */
+    int bitmap_length = sizeof(struct shadow_bitmap)
+                        + parent->info.bmiHeader.biSizeImage;
+
+    /* Although this function can only be called from a Windows thread, the
+     * deallocator can be called from a macOS thread, so we must use calloc
+     * instead of HeapAlloc. */
+    struct shadow_surface *shadow = calloc(1, sizeof(struct shadow_surface));
+    if (!shadow)
+        return NULL;
 
-    if (surface->drawn)
+    if (pthread_mutex_init(&shadow->mutex, NULL) != 0)
+    {
+        free(shadow);
+        return NULL;
+    }
+
+    shadow->refcount = 1;
+    shadow->bitmap_length = bitmap_length;
+    return shadow;
+}
+
+/***********************************************************************
+ *              shadow_surface_release
+ *
+ * Decrements shadow surface's refcount. If it becomes zero, the shadow
+ * is freed along with all of its bitmaps.
+ *
+ * shadow->mutex must be held before calling this function. The mutex
+ * will be released on exit and you should not attempt to access the
+ * surface again.
+ *
+ * IMPORTANT: This function is called from non-Wine threads, so it
+ *            must not use Win32 or Wine functions, including debug
+ *            logging.
+ */
+static void shadow_surface_release(struct shadow_surface *shadow)
+{
+    int bitmap_length = shadow->bitmap_length;
+    int refcount = --shadow->refcount;
+    struct shadow_bitmap *bitmap = shadow->bitmaps;
+
+    pthread_mutex_unlock(&shadow->mutex);
+
+    if (refcount != 0)
+        return;
+
+    pthread_mutex_destroy(&shadow->mutex);
+    free(shadow);
+    while (bitmap)
+        bitmap = shadow_bitmap_free(bitmap, bitmap_length);
+}
+
+/***********************************************************************
+ *              shadow_bitmap_take
+ *
+ * Obtains a shadow bitmap from the given shadow surface. Returns the
+ * bitmap on success or NULL on failure.
+ *
+ * On success, shadow->refcount has been incremented. The call must be
+ * balanced with a call to shadow_bitmap_return, usually accomplished by
+ * passing bitmap->cfdata_deallocator to CFDataCreateWithBytesNoCopy as
+ * bytesDeallocator.
+ *
+ * IMPORTANT: This function is called from non-Wine threads, so it
+ *            must not use Win32 or Wine functions, including debug
+ *            logging.
+ */
+static struct shadow_bitmap *shadow_bitmap_take(struct shadow_surface *shadow)
+{
+    int bitmap_length;
+    struct shadow_bitmap *bitmap;
+    CFAllocatorContext cfa_context = {
+        .version = 0, .info = NULL,
+        .retain = NULL, .release = NULL, .copyDescription = NULL,
+        .allocate = NULL, .reallocate = NULL,
+        .deallocate = shadow_cfdata_dealloc, .preferredSize = NULL
+    };
+
+    pthread_mutex_lock(&shadow->mutex);
+    shadow->refcount++;
+
+    bitmap_length = shadow->bitmap_length;
+    bitmap = shadow->bitmaps;
+    if (bitmap)
     {
-        HRGN blit = CreateRectRgn(0, 0, 0, 0);
+        /* Got an existing bitmap, remove it from the linked list */
+        shadow->bitmaps = bitmap->next;
+        bitmap->next = NULL;
+    }
+
+    pthread_mutex_unlock(&shadow->mutex);
+
+    if (bitmap)
+        return bitmap;
+
+    /* Nothing was available, make a new bitmap. Once again we cannot use
+     * Windows functions. mmap is most appropriate due to its low CPU overhead
+     * and the fact that bitmaps are usually large. Pass shadow as a hint so the
+     * bitmap will be allocated in a similar memory region. */
+    bitmap = mmap(shadow, bitmap_length, PROT_READ | PROT_WRITE,
+                  MAP_ANON | MAP_PRIVATE, -1, 0);
+    if (bitmap == MAP_FAILED)
+        goto failed;
+
+    cfa_context.info = bitmap;
+    bitmap->cfdata_deallocator = CFAllocatorCreate(NULL, &cfa_context);
+    if (!bitmap->cfdata_deallocator)
+        goto failed_unmap;
+
+    bitmap->parent = shadow;
+    return bitmap;
+
+failed_unmap:
+    munmap(bitmap, bitmap_length);
+failed:
+    /* Balance refcount++ since shadow_bitmap_return won't be called */
+    pthread_mutex_lock(&shadow->mutex);
+    shadow_surface_release(shadow);
+    return NULL;
+}
+
+/***********************************************************************
+ *              shadow_bitmap_return
+ *
+ * Returns a shadow bitmap to its linked list and decrements the
+ * refcount in the corresponding shadow_surface.
+ *
+ * IMPORTANT: This function is called from non-Wine threads, so it
+ *            must not use Win32 or Wine functions, including debug
+ *            logging.
+ */
+static void shadow_bitmap_return(struct shadow_bitmap *bitmap)
+{
+    struct shadow_surface *shadow = bitmap->parent;
+    struct shadow_bitmap **tail = &shadow->bitmaps;
+    int bitmap_length;
+    int num_bitmaps = 0;
 
-        if (CombineRgn(blit, surface->drawn, 0, RGN_COPY) > NULLREGION &&
-            (!surface->region || CombineRgn(blit, blit, surface->region, RGN_AND) > NULLREGION) &&
-            OffsetRgn(blit, surface->header.rect.left, surface->header.rect.top) > NULLREGION)
-            surface->blit_data = get_region_data(blit, 0);
+    pthread_mutex_lock(&shadow->mutex);
+    bitmap_length = shadow->bitmap_length;
 
-        DeleteObject(blit);
+    /* Append the bitmap to the end of the list */
+    while (*tail)
+    {
+        num_bitmaps++;
+        tail = &(*tail)->next;
     }
+
+    /* Refuse to return this bitmap if there are already too many. Free it
+     * instead. */
+    if (num_bitmaps >= SHADOW_BITMAP_MAX)
+        shadow_bitmap_free(bitmap, bitmap_length);
+    else
+        *tail = bitmap;
+
+    shadow_surface_release(shadow);
+}
+
+/***********************************************************************
+ *              shadow_bitmap_free
+ *
+ * Deletes a shadow bitmap. Returns bitmap->next.
+ *
+ * IMPORTANT: This function is called from non-Wine threads, so it
+ *            must not use Win32 or Wine functions, including debug
+ *            logging.
+ */
+static struct shadow_bitmap *shadow_bitmap_free(struct shadow_bitmap *bitmap,
+                                                int bitmap_length)
+{
+    struct shadow_bitmap *next = bitmap->next;
+
+    CFRelease(bitmap->cfdata_deallocator);
+    munmap(bitmap, bitmap_length);
+
+    return next;
+}
+
+/***********************************************************************
+ *              shadow_cfdata_dealloc
+ *
+ * Wraps shadow_bitmap_return for CFAllocator.
+ *
+ * IMPORTANT: This function is called from non-Wine threads, so it
+ *            must not use Win32 or Wine functions, including debug
+ *            logging.
+ */
+static void shadow_cfdata_dealloc(void *ptr, void *info)
+{
+    shadow_bitmap_return(info);
 }
 
 /***********************************************************************
@@ -156,7 +379,6 @@ static void macdrv_surface_set_region(struct window_surface *window_surface, HRG
         if (surface->region) DeleteObject(surface->region);
         surface->region = 0;
     }
-    update_blit_data(surface);
 
     window_surface->funcs->unlock(window_surface);
 }
@@ -188,7 +410,6 @@ static void macdrv_surface_flush(struct window_surface *window_surface)
         else
             surface->drawn = region;
     }
-    update_blit_data(surface);
     reset_bounds(&surface->bounds);
 
     window_surface->funcs->unlock(window_surface);
@@ -207,8 +428,14 @@ static void macdrv_surface_destroy(struct window_surface *window_surface)
     TRACE("freeing %p bits %p\n", surface, surface->bits);
     if (surface->region) DeleteObject(surface->region);
     if (surface->drawn) DeleteObject(surface->drawn);
-    HeapFree(GetProcessHeap(), 0, surface->blit_data);
-    HeapFree(GetProcessHeap(), 0, surface->bits);
+    if (surface->shadow)
+    {
+        pthread_mutex_lock(&surface->shadow->mutex);
+        shadow_surface_release(surface->shadow);
+    }
+    if (surface->bits && surface->bits != MAP_FAILED)
+        munmap(surface->bits, surface->info.bmiHeader.biSizeImage);
+
     pthread_mutex_destroy(&surface->mutex);
     HeapFree(GetProcessHeap(), 0, surface);
 }
@@ -264,7 +491,7 @@ struct window_surface *create_surface(macdrv_window window, const RECT *rect,
 
     surface->info.bmiHeader.biSize        = sizeof(surface->info.bmiHeader);
     surface->info.bmiHeader.biWidth       = width;
-    surface->info.bmiHeader.biHeight      = -height; /* top-down */
+    surface->info.bmiHeader.biHeight      = height; /* bottom-up */
     surface->info.bmiHeader.biPlanes      = 1;
     surface->info.bmiHeader.biBitCount    = 32;
     surface->info.bmiHeader.biSizeImage   = get_dib_image_size(&surface->info);
@@ -291,15 +518,20 @@ struct window_surface *create_surface(macdrv_window window, const RECT *rect,
             surface->drawn = 0;
         }
     }
-    update_blit_data(surface);
     surface->use_alpha = use_alpha;
-    surface->bits = HeapAlloc(GetProcessHeap(), 0, surface->info.bmiHeader.biSizeImage);
-    if (!surface->bits) goto failed;
+    surface->shadow = shadow_surface_create(surface);
+    if (!surface->shadow) goto failed;
+    /* Map bitmap close to the shadow */
+    surface->bits = mmap(surface->shadow, surface->info.bmiHeader.biSizeImage,
+                         PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
+    if (surface->bits == MAP_FAILED) goto failed;
     window_background = macdrv_window_background_color();
     memset_pattern4(surface->bits, &window_background, surface->info.bmiHeader.biSizeImage);
 
-    TRACE("created %p for %p %s bits %p-%p\n", surface, window, wine_dbgstr_rect(rect),
-          surface->bits, surface->bits + surface->info.bmiHeader.biSizeImage);
+    TRACE("created %p for %p %s bits %p-%p shadow %p\n",
+          surface, window, wine_dbgstr_rect(rect),
+          surface->bits, surface->bits + surface->info.bmiHeader.biSizeImage,
+          surface->shadow);
 
     return &surface->header;
 
@@ -318,121 +550,141 @@ void set_surface_use_alpha(struct window_surface *window_surface, BOOL use_alpha
 }
 
 /***********************************************************************
- *              set_window_surface
- */
-void set_window_surface(macdrv_window window, struct window_surface *window_surface)
-{
-    struct macdrv_window_surface *surface = get_mac_surface(window_surface);
-    macdrv_set_window_surface(window, window_surface, surface ? &surface->mutex : NULL);
-}
-
-/***********************************************************************
- *              get_surface_blit_rects
+ *              create_surface_image
  *
- * Caller must hold the surface lock.  Indirectly returns the surface
- * blit region rects.  Returns zero if the surface has nothing to blit;
- * returns non-zero if the surface does have rects to blit (drawn area
- * which isn't clipped away by a surface region).
+ * Creates a CGImageRef from the given surface. Returns NULL if there is
+ * nothing to draw. On input, *dirty_area is the requested image rect,
+ * relative to the window whole_rect, a.k.a. visible_rect. On output,
+ * it's been intersected with that part backed by the surface and is the
+ * actual size of the returned image.
  *
  * IMPORTANT: This function is called from non-Wine threads, so it
  *            must not use Win32 or Wine functions, including debug
  *            logging.
  */
-int get_surface_blit_rects(void *window_surface, const CGRect **rects, int *count)
+CGImageRef create_surface_image(void *window_surface, CGRect *dirty_area, int color_keyed,
+                                uint8_t key_red, uint8_t key_green, uint8_t key_blue)
 {
+    static CGColorSpaceRef colorspace = NULL;
+    CGImageAlphaInfo alpha_info = kCGImageAlphaNoneSkipFirst;
+    CGImageRef cgimage;
+    CFDataRef data;
+    CGDataProviderRef provider;
+
     struct macdrv_window_surface *surface = get_mac_surface(window_surface);
+    struct shadow_bitmap *shadow;
 
-    if (rects && count)
-    {
-        if (surface->blit_data)
-        {
-            *rects = (const CGRect*)surface->blit_data->Buffer;
-            *count = surface->blit_data->rdh.nCount;
-        }
-        else
-        {
-            *rects = NULL;
-            *count = 0;
-        }
-    }
+    int surface_width, bytes_per_row, bitmap_length;
+    int dirty_width, dirty_height, offset;
 
-    return (surface->blit_data != NULL && surface->blit_data->rdh.nCount > 0);
-}
+    if (!surface)
+        return NULL;
 
-/***********************************************************************
- *              create_surface_image
- *
- * Caller must hold the surface lock.  On input, *rect is the requested
- * image rect, relative to the window whole_rect, a.k.a. visible_rect.
- * On output, it's been intersected with that part backed by the surface
- * and is the actual size of the returned image.  copy_data indicates if
- * the caller will keep the returned image beyond the point where the
- * surface bits can be guaranteed to remain valid and unchanged.  If so,
- * the bits are copied instead of merely referenced by the image.
- *
- * IMPORTANT: This function is called from non-Wine threads, so it
- *            must not use Win32 or Wine functions, including debug
- *            logging.
- */
-CGImageRef create_surface_image(void *window_surface, CGRect *rect, int copy_data, int color_keyed,
-        CGFloat key_red, CGFloat key_green, CGFloat key_blue)
-{
-    CGImageRef cgimage = NULL;
-    struct macdrv_window_surface *surface = get_mac_surface(window_surface);
-    int width, height;
+    if (!colorspace)
+        colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
 
-    width  = surface->header.rect.right - surface->header.rect.left;
-    height = surface->header.rect.bottom - surface->header.rect.top;
-    *rect = CGRectIntersection(cgrect_from_rect(surface->header.rect), *rect);
-    if (!CGRectIsEmpty(*rect))
-    {
-        CGRect visrect;
-        CGColorSpaceRef colorspace;
-        CGDataProviderRef provider;
-        int bytes_per_row, offset, size;
-        CGImageAlphaInfo alphaInfo;
+    pthread_mutex_lock(&surface->mutex);
+    surface_width = surface->header.rect.right - surface->header.rect.left;
+    bytes_per_row = get_dib_stride(surface_width, 32);
+    bitmap_length = surface->info.bmiHeader.biSizeImage;
+    if (surface->use_alpha)
+        alpha_info = kCGImageAlphaPremultipliedFirst;
 
-        visrect = CGRectOffset(*rect, -surface->header.rect.left, -surface->header.rect.top);
+    /* Clip to bitmap area */
+    *dirty_area = CGRectIntersection(cgrect_from_rect(surface->header.rect),
+                                     CGRectIntegral(*dirty_area));
 
-        colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
-        bytes_per_row = get_dib_stride(width, 32);
-        offset = CGRectGetMinX(visrect) * 4 + CGRectGetMinY(visrect) * bytes_per_row;
-        size = min(CGRectGetHeight(visrect) * bytes_per_row,
-                   surface->info.bmiHeader.biSizeImage - offset);
+    dirty_width = CGRectGetWidth(*dirty_area);
+    dirty_height = CGRectGetHeight(*dirty_area);
+
+    if (dirty_width <= 0 || dirty_height <= 0)
+        goto failed;
+
+    shadow = shadow_bitmap_take(surface->shadow);
+    if (!shadow)
+        goto failed;
 
-        if (copy_data)
+    /* Find location from which to read data */
+    offset = (CGRectGetMinX(*dirty_area) - surface->header.rect.left) * 4
+              + (surface->header.rect.bottom - CGRectGetMaxY(*dirty_area))
+                * bytes_per_row;
+
+    if (!color_keyed)
+    {
+        /* Copy pixels to shadow buffer. If the width is close or equal to the
+         * whole bitmap (<= 32 bytes), it's faster to copy one large region. */
+        if (dirty_width >= surface_width - 8)
         {
-            CFDataRef data = CFDataCreate(NULL, (UInt8*)surface->bits + offset, size);
-            provider = CGDataProviderCreateWithCFData(data);
-            CFRelease(data);
+            /* Align to 16-byte boundaries to get the best memcpy performance */
+            int align_offset = offset & ~15;
+            int align_size = ((offset + (dirty_height - 1) * bytes_per_row
+                               + dirty_width * 4 + 15) & ~15) - align_offset;
+            memcpy(shadow->bits + align_offset,
+                   surface->bits + align_offset,
+                   align_size);
         }
         else
-            provider = CGDataProviderCreateWithData(NULL, surface->bits + offset, size, NULL);
-
-        alphaInfo = surface->use_alpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
-        cgimage = CGImageCreate(CGRectGetWidth(visrect), CGRectGetHeight(visrect),
-                                8, 32, bytes_per_row, colorspace,
-                                alphaInfo | kCGBitmapByteOrder32Little,
-                                provider, NULL, retina_on, kCGRenderingIntentDefault);
-        CGDataProviderRelease(provider);
-        CGColorSpaceRelease(colorspace);
+        {
+            /* Copying a smaller width of the bitmap, go line by line */
+            int y;
+            for (y = 0; y < dirty_height; y++)
+            {
+                int line_offset = offset + y * bytes_per_row;
+                int align_offset = line_offset & ~15;
+                int align_size = ((line_offset + dirty_width * 4 + 15) & ~15)
+                                 - align_offset;
+                memcpy(shadow->bits + align_offset,
+                       surface->bits + align_offset,
+                       align_size);
+            }
+        }
+    }
+    else
+    {
+        /* If using a color key, convert those pixels to transparent. This is
+         * the best time to do it since we have to copy the data anyway. */
+        DWORD key = (key_red << 16) | (key_green << 8) | (key_blue);
+        int x, y;
 
-        if (color_keyed)
+        for (y = 0; y < dirty_height; y++)
         {
-            CGImageRef maskedImage;
-            CGFloat components[] = { key_red   - 0.5, key_red   + 0.5,
-                                     key_green - 0.5, key_green + 0.5,
-                                     key_blue  - 0.5, key_blue  + 0.5 };
-            maskedImage = CGImageCreateWithMaskingColors(cgimage, components);
-            if (maskedImage)
+            for (x = 0; x < dirty_width; x++)
             {
-                CGImageRelease(cgimage);
-                cgimage = maskedImage;
+                DWORD  src = *(DWORD*)(surface->bits + offset
+                                       + x * 4 + y * bytes_per_row);
+                DWORD* dst =  (DWORD*)(shadow->bits + offset
+                                       + x * 4 + y * bytes_per_row);
+                if ((src & 0x00FFFFFF) == key)
+                    *dst = 0;
+                else if (!surface->use_alpha)
+                    /* dst must have alpha channel even if src does not */
+                    *dst = src | 0xFF000000;
+                else
+                    *dst = src;
             }
         }
+
+        alpha_info = kCGImageAlphaPremultipliedFirst;
     }
 
+    /* Safe to unlock surface now */
+    pthread_mutex_unlock(&surface->mutex);
+
+    data = CFDataCreateWithBytesNoCopy(NULL, shadow->bits + offset,
+                bitmap_length - offset, shadow->cfdata_deallocator);
+    provider = CGDataProviderCreateWithCFData(data);
+    CFRelease(data);
+
+    cgimage = CGImageCreate(dirty_width, dirty_height, 8, 32, bytes_per_row,
+                            colorspace, alpha_info | kCGBitmapByteOrder32Little,
+                            provider, NULL, FALSE, kCGRenderingIntentDefault);
+    CFRelease(provider);
+
     return cgimage;
+
+failed:
+    pthread_mutex_unlock(&surface->mutex);
+    return NULL;
 }
 
 /***********************************************************************
@@ -460,10 +712,59 @@ void surface_clip_to_visible_rect(struct window_surface *window_surface, const R
         {
             CombineRgn(surface->drawn, surface->drawn, region, RGN_AND);
             DeleteObject(region);
-
-            update_blit_data(surface);
         }
     }
 
     window_surface->funcs->unlock(window_surface);
 }
+
+/***********************************************************************
+ *              surface_hit_test
+ *
+ * Performs a per-pixel hit test on the given surface. Returns FALSE if
+ * the chosen pixel is transparent or keyed out, TRUE if the pixel is
+ * clickable.
+ *
+ * IMPORTANT: This function is called from non-Wine threads, so it
+ *            must not use Win32 or Wine functions, including debug
+ *            logging.
+ */
+int surface_hit_test(void *window_surface, CGPoint point, int color_keyed,
+                     uint8_t key_red, uint8_t key_green, uint8_t key_blue)
+{
+    struct macdrv_window_surface *surface = get_mac_surface(window_surface);
+    DWORD key = (key_red << 16) | (key_green << 8) | (key_blue);
+    DWORD pixel;
+    int surface_width, bytes_per_row;
+    /* Note: coordinates can be non-integers. Truncate. */
+    int point_x = point.x, point_y = point.y;
+    int retval = TRUE;
+
+    if (!surface)
+        return TRUE;
+
+    pthread_mutex_lock(&surface->mutex);
+    surface_width = surface->header.rect.right - surface->header.rect.left;
+    bytes_per_row = get_dib_stride(surface_width, 32);
+
+    if (!surface->use_alpha && !color_keyed)
+        /* Opaque surface always succeeds */
+        goto done;
+
+    if (point_x < surface->header.rect.left || point_x >= surface->header.rect.right
+            || point_y < surface->header.rect.top || point_y >= surface->header.rect.bottom)
+    {
+        retval = FALSE;
+        goto done;
+    }
+
+    pixel = *(DWORD*)(surface->bits + (point_x - surface->header.rect.left) * 4
+                      + (surface->header.rect.bottom - point_y) * bytes_per_row);
+
+    retval = !((color_keyed && (pixel & 0x00FFFFFF) == key)
+               || (surface->use_alpha && (pixel & 0xFF000000) == 0));
+
+done:
+    pthread_mutex_unlock(&surface->mutex);
+    return retval;
+}
diff --git a/dlls/winemac.drv/window.c b/dlls/winemac.drv/window.c
index 9177f493a5f..dba87df37de 100644
--- a/dlls/winemac.drv/window.c
+++ b/dlls/winemac.drv/window.c
@@ -1899,7 +1899,7 @@ BOOL CDECL macdrv_UpdateLayeredWindow(HWND hwnd, const UPDATELAYEREDWINDOWINFO *
     if (!surface || !EqualRect(&surface->rect, &rect))
     {
         data->surface = create_surface(data->cocoa_window, &rect, NULL, TRUE);
-        set_window_surface(data->cocoa_window, data->surface);
+        macdrv_set_window_surface(data->cocoa_window, data->surface);
         if (surface) window_surface_release(surface);
         surface = data->surface;
         if (data->unminimized_surface)
@@ -2139,7 +2139,7 @@ void CDECL macdrv_WindowPosChanged(HWND hwnd, HWND insert_after, UINT swp_flags,
         }
         else
         {
-            set_window_surface(data->cocoa_window, surface);
+            macdrv_set_window_surface(data->cocoa_window, surface);
             if (data->unminimized_surface)
             {
                 window_surface_release(data->unminimized_surface);
-- 
2.32.0 (Apple Git-132)




More information about the wine-devel mailing list