Signed-off-by: Elaine Lefler elaineclefler@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; -@property (nonatomic) CGFloat colorKeyRed, colorKeyGreen, colorKeyBlue; +@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; }
/***********************************************************************