[PATCH v2 11/12] macdrv/surface: Make data copies explicit so memory pages are reusable.

Elaine Lefler elaineclefler at gmail.com
Thu Mar 24 23:46:25 CDT 2022


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

v2: Was PATCH 3/3. Splitting into smaller patches as per feedback.
---
 dlls/winemac.drv/cocoa_window.h |   2 +-
 dlls/winemac.drv/cocoa_window.m |   8 +-
 dlls/winemac.drv/macdrv_cocoa.h |   8 +-
 dlls/winemac.drv/surface.c      | 425 ++++++++++++++++++++++++++++----
 4 files changed, 381 insertions(+), 62 deletions(-)

diff --git a/dlls/winemac.drv/cocoa_window.h b/dlls/winemac.drv/cocoa_window.h
index b1f7e595ec9..596e3c52b3e 100644
--- a/dlls/winemac.drv/cocoa_window.h
+++ b/dlls/winemac.drv/cocoa_window.h
@@ -57,7 +57,7 @@ @interface WineWindow : NSPanel <NSWindowDelegate>
     BOOL shapeChangedSinceLastDraw;
 
     BOOL colorKeyed;
-    CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue;
+    uint8_t colorKeyRed, colorKeyGreen, colorKeyBlue;
 
     BOOL usePerPixelAlpha;
 
diff --git a/dlls/winemac.drv/cocoa_window.m b/dlls/winemac.drv/cocoa_window.m
index 1b66c97b19a..656a5ba2283 100644
--- a/dlls/winemac.drv/cocoa_window.m
+++ b/dlls/winemac.drv/cocoa_window.m
@@ -409,7 +409,7 @@ @interface WineWindow ()
 @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;
@@ -552,7 +552,7 @@ - (void) updateLayer
             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,
+            image = create_surface_image(window.surface, &imageRect, window.colorKeyed,
                                          window.colorKeyRed, window.colorKeyGreen, window.colorKeyBlue);
         }
         pthread_mutex_unlock(window.surface_mutex);
@@ -3561,8 +3561,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;
diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h
index c7f87888fdc..b0eb86133c4 100644
--- a/dlls/winemac.drv/macdrv_cocoa.h
+++ b/dlls/winemac.drv/macdrv_cocoa.h
@@ -581,14 +581,14 @@ extern void macdrv_order_cocoa_window(macdrv_window w, macdrv_window prev,
 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 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 get_surface_blit_rects(void *window_surface, const CGRect **rects, int *count) 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..eed31229473 100644
--- a/dlls/winemac.drv/surface.c
+++ b/dlls/winemac.drv/surface.c
@@ -65,6 +65,7 @@ struct macdrv_window_surface
     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,6 +73,250 @@ 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);
+
+/***********************************************************************
+ *              shadow_surface_create
+ *
+ * Creates a new shadow surface. Its refcount is initially set to 1.
+ * Returns NULL on failure.
+ */
+static struct shadow_surface *shadow_surface_create(struct macdrv_window_surface* parent)
+{
+    /* 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 (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)
+    {
+        /* 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;
+
+    pthread_mutex_lock(&shadow->mutex);
+    bitmap_length = shadow->bitmap_length;
+
+    /* 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);
+}
+
 /***********************************************************************
  *              update_blit_data
  */
@@ -207,8 +452,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);
+    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);
     HeapFree(GetProcessHeap(), 0, surface->blit_data);
-    HeapFree(GetProcessHeap(), 0, surface->bits);
     pthread_mutex_destroy(&surface->mutex);
     HeapFree(GetProcessHeap(), 0, surface);
 }
@@ -293,13 +544,19 @@ struct window_surface *create_surface(macdrv_window window, const RECT *rect,
     }
     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;
 
@@ -362,77 +619,139 @@ int get_surface_blit_rects(void *window_surface, const CGRect **rects, int *coun
 /***********************************************************************
  *              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.
+ * 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.
  */
-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 create_surface_image(void *window_surface, CGRect *dirty_area, int color_keyed,
+                                uint8_t key_red, uint8_t key_green, uint8_t key_blue)
 {
-    CGImageRef cgimage = NULL;
+    static CGColorSpaceRef colorspace = NULL;
+    CGImageAlphaInfo alpha_info = kCGImageAlphaNoneSkipFirst;
+    CGImageRef cgimage;
+    CFDataRef data;
+    CGDataProviderRef provider;
+
     struct macdrv_window_surface *surface = get_mac_surface(window_surface);
-    int width, height;
+    struct shadow_bitmap *shadow;
 
-    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;
+    int surface_width, bytes_per_row, bitmap_length;
+    int dirty_width, dirty_height, offset;
 
-        visrect = CGRectOffset(*rect, -surface->header.rect.left, -surface->header.rect.top);
+    if (!surface)
+        return NULL;
 
+    if (!colorspace)
         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);
 
-        if (copy_data)
+    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;
+
+    /* Clip to bitmap area */
+    *dirty_area = CGRectIntersection(cgrect_from_rect(surface->header.rect),
+                                     CGRectIntegral(*dirty_area));
+
+    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;
+
+    /* 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;
 }
 
 /***********************************************************************
-- 
2.32.0 (Apple Git-132)




More information about the wine-devel mailing list