wine/dlls/winemac.drv/surface.c
Ken Thomases 1c94bf396f winemac: Add support for a high-resolution ("Retina") rendering mode.
When this Retina mode is enabled and the primary display is in the user's
default configuration, Wine gets told that screen and window sizes and mouse
coordinates are twice what Cocoa reports them as in its virtual coordinate
system ("points").  The Windows apps then renders at that high resolution and
the Mac driver blits it to screen.  If the screen is actually a Retina display
in a high-DPI mode, then this extra detail will be preserved.  Otherwise, the
rendering will be downsampled and blurry.

This is intended to be combined with increasing the Windows DPI, as via winecfg.
If that is doubled to 192, then, in theory, graphical elements will remain the
same visual size on screen but be rendered with finer detail.  Unfortunately,
many Windows programs don't correctly handle non-standard DPI so the results
are not always perfect.

The registry setting to enable Retina mode is:

[HKEY_CURRENT_USER\Software\Wine\Mac Driver]
"RetinaMode"="y"

Note that this setting is not looked for in the AppDefaults\<exe name> key
because it doesn't make sense for only some processes in a Wine session to see
the high-resolution sizes and coordinates.

Signed-off-by: Ken Thomases <ken@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2016-05-06 11:45:24 +09:00

445 lines
15 KiB
C

/*
* Mac driver window surface implementation
*
* Copyright 1993, 1994, 2011 Alexandre Julliard
* Copyright 2006 Damjan Jovanovic
* Copyright 2012, 2013 Ken Thomases for CodeWeavers, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "config.h"
#include "macdrv.h"
#include "winuser.h"
WINE_DEFAULT_DEBUG_CHANNEL(macdrv);
/* only for use on sanitized BITMAPINFO structures */
static inline int get_dib_info_size(const BITMAPINFO *info, UINT coloruse)
{
if (info->bmiHeader.biCompression == BI_BITFIELDS)
return sizeof(BITMAPINFOHEADER) + 3 * sizeof(DWORD);
if (coloruse == DIB_PAL_COLORS)
return sizeof(BITMAPINFOHEADER) + info->bmiHeader.biClrUsed * sizeof(WORD);
return FIELD_OFFSET(BITMAPINFO, bmiColors[info->bmiHeader.biClrUsed]);
}
static inline int get_dib_stride(int width, int bpp)
{
return ((width * bpp + 31) >> 3) & ~3;
}
static inline int get_dib_image_size(const BITMAPINFO *info)
{
return get_dib_stride(info->bmiHeader.biWidth, info->bmiHeader.biBitCount)
* abs(info->bmiHeader.biHeight);
}
static inline void reset_bounds(RECT *bounds)
{
bounds->left = bounds->top = INT_MAX;
bounds->right = bounds->bottom = INT_MIN;
}
struct macdrv_window_surface
{
struct window_surface header;
macdrv_window window;
RECT bounds;
HRGN region;
HRGN drawn;
BOOL use_alpha;
RGNDATA *blit_data;
BYTE *bits;
pthread_mutex_t mutex;
BITMAPINFO info; /* variable size, must be last */
};
static struct macdrv_window_surface *get_mac_surface(struct window_surface *surface)
{
return (struct macdrv_window_surface *)surface;
}
/***********************************************************************
* update_blit_data
*/
static void update_blit_data(struct macdrv_window_surface *surface)
{
HeapFree(GetProcessHeap(), 0, surface->blit_data);
surface->blit_data = NULL;
if (surface->drawn)
{
HRGN blit = CreateRectRgn(0, 0, 0, 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);
DeleteObject(blit);
}
}
/***********************************************************************
* macdrv_surface_lock
*/
static void macdrv_surface_lock(struct window_surface *window_surface)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
pthread_mutex_lock(&surface->mutex);
}
/***********************************************************************
* macdrv_surface_unlock
*/
static void macdrv_surface_unlock(struct window_surface *window_surface)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
pthread_mutex_unlock(&surface->mutex);
}
/***********************************************************************
* macdrv_surface_get_bitmap_info
*/
static void *macdrv_surface_get_bitmap_info(struct window_surface *window_surface,
BITMAPINFO *info)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
memcpy(info, &surface->info, get_dib_info_size(&surface->info, DIB_RGB_COLORS));
return surface->bits;
}
/***********************************************************************
* macdrv_surface_get_bounds
*/
static RECT *macdrv_surface_get_bounds(struct window_surface *window_surface)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
return &surface->bounds;
}
/***********************************************************************
* macdrv_surface_set_region
*/
static void macdrv_surface_set_region(struct window_surface *window_surface, HRGN region)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
TRACE("updating surface %p with %p\n", surface, region);
window_surface->funcs->lock(window_surface);
if (region)
{
if (!surface->region) surface->region = CreateRectRgn(0, 0, 0, 0);
CombineRgn(surface->region, region, 0, RGN_COPY);
}
else
{
if (surface->region) DeleteObject(surface->region);
surface->region = 0;
}
update_blit_data(surface);
window_surface->funcs->unlock(window_surface);
}
/***********************************************************************
* macdrv_surface_flush
*/
static void macdrv_surface_flush(struct window_surface *window_surface)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
CGRect rect;
HRGN region;
window_surface->funcs->lock(window_surface);
TRACE("flushing %p %s bounds %s bits %p\n", surface, wine_dbgstr_rect(&surface->header.rect),
wine_dbgstr_rect(&surface->bounds), surface->bits);
rect = cgrect_from_rect(surface->bounds);
rect = CGRectOffset(rect, surface->header.rect.left, surface->header.rect.top);
if (!IsRectEmpty(&surface->bounds) && (region = CreateRectRgnIndirect(&surface->bounds)))
{
if (surface->drawn)
{
CombineRgn(surface->drawn, surface->drawn, region, RGN_OR);
DeleteObject(region);
}
else
surface->drawn = region;
}
update_blit_data(surface);
reset_bounds(&surface->bounds);
window_surface->funcs->unlock(window_surface);
if (!CGRectIsEmpty(rect))
macdrv_window_needs_display(surface->window, rect);
}
/***********************************************************************
* macdrv_surface_destroy
*/
static void macdrv_surface_destroy(struct window_surface *window_surface)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
TRACE("freeing %p bits %p\n", surface, surface->bits);
HeapFree(GetProcessHeap(), 0, surface->bits);
pthread_mutex_destroy(&surface->mutex);
HeapFree(GetProcessHeap(), 0, surface);
}
static const struct window_surface_funcs macdrv_surface_funcs =
{
macdrv_surface_lock,
macdrv_surface_unlock,
macdrv_surface_get_bitmap_info,
macdrv_surface_get_bounds,
macdrv_surface_set_region,
macdrv_surface_flush,
macdrv_surface_destroy,
};
/***********************************************************************
* create_surface
*/
struct window_surface *create_surface(macdrv_window window, const RECT *rect,
struct window_surface *old_surface, BOOL use_alpha)
{
struct macdrv_window_surface *surface;
struct macdrv_window_surface *old_mac_surface = get_mac_surface(old_surface);
int width = rect->right - rect->left, height = rect->bottom - rect->top;
DWORD *colors;
pthread_mutexattr_t attr;
int err;
DWORD window_background;
surface = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
FIELD_OFFSET(struct macdrv_window_surface, info.bmiColors[3]));
if (!surface) return NULL;
err = pthread_mutexattr_init(&attr);
if (!err)
{
err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
if (!err)
err = pthread_mutex_init(&surface->mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
if (err)
{
HeapFree(GetProcessHeap(), 0, surface);
return NULL;
}
surface->info.bmiHeader.biSize = sizeof(surface->info.bmiHeader);
surface->info.bmiHeader.biWidth = width;
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);
surface->info.bmiHeader.biCompression = BI_RGB;
surface->info.bmiHeader.biClrUsed = 0;
colors = (DWORD *)((char *)&surface->info + surface->info.bmiHeader.biSize);
colors[0] = 0x00ff0000;
colors[1] = 0x0000ff00;
colors[2] = 0x000000ff;
surface->header.funcs = &macdrv_surface_funcs;
surface->header.rect = *rect;
surface->header.ref = 1;
surface->window = window;
reset_bounds(&surface->bounds);
if (old_mac_surface && old_mac_surface->drawn)
{
surface->drawn = CreateRectRgnIndirect(rect);
OffsetRgn(surface->drawn, -rect->left, -rect->top);
if (CombineRgn(surface->drawn, surface->drawn, old_mac_surface->drawn, RGN_AND) <= NULLREGION)
{
DeleteObject(surface->drawn);
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;
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);
return &surface->header;
failed:
macdrv_surface_destroy(&surface->header);
return NULL;
}
/***********************************************************************
* set_surface_use_alpha
*/
void set_surface_use_alpha(struct window_surface *window_surface, BOOL use_alpha)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
surface->use_alpha = 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
*
* 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).
*
* 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)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
if (surface->blit_data)
{
*rects = (const CGRect*)surface->blit_data->Buffer;
*count = surface->blit_data->rdh.nCount;
}
else
{
*rects = NULL;
*count = 0;
}
return (surface->blit_data != 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)
{
CGImageRef cgimage = NULL;
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
int width, height;
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;
visrect = CGRectOffset(*rect, -surface->header.rect.left, -surface->header.rect.top);
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
bytes_per_row = get_dib_stride(width, 32);
offset = CGRectGetMinX(visrect) * 4 + (height - CGRectGetMaxY(visrect)) * bytes_per_row;
size = min(CGRectGetHeight(visrect) * bytes_per_row,
surface->info.bmiHeader.biSizeImage - offset);
if (copy_data)
{
CFDataRef data = CFDataCreate(NULL, (UInt8*)surface->bits + offset, size);
provider = CGDataProviderCreateWithCFData(data);
CFRelease(data);
}
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);
}
return cgimage;
}
/***********************************************************************
* surface_clip_to_visible_rect
*
* Intersect the accumulated drawn region with a new visible rect,
* effectively discarding stale drawing in the surface slack area.
*/
void surface_clip_to_visible_rect(struct window_surface *window_surface, const RECT *visible_rect)
{
struct macdrv_window_surface *surface = get_mac_surface(window_surface);
window_surface->funcs->lock(window_surface);
if (surface->drawn)
{
RECT rect;
HRGN region;
rect = *visible_rect;
OffsetRect(&rect, -rect.left, -rect.top);
if ((region = CreateRectRgnIndirect(&rect)))
{
CombineRgn(surface->drawn, surface->drawn, region, RGN_AND);
DeleteObject(region);
update_blit_data(surface);
}
}
window_surface->funcs->unlock(window_surface);
}