mirror of
https://gitlab.winehq.org/wine/wine.git
synced 2025-08-29 02:33:58 +02:00
This avoids using buffers with an alpha channel for windows that
use SetLayeredWindowAttributes() and don't really utilize
per-pixel alpha.
Fixes: 6f8f5fe7bf
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58504
531 lines
19 KiB
C
531 lines
19 KiB
C
/*
|
|
* Wayland window surface implementation
|
|
*
|
|
* Copyright 2020 Alexandros Frantzis for Collabora Ltd
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#if 0
|
|
#pragma makedep unix
|
|
#endif
|
|
|
|
#include "config.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "waylanddrv.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
|
|
|
|
struct wayland_buffer_queue
|
|
{
|
|
struct wl_event_queue *wl_event_queue;
|
|
struct wl_list buffer_list;
|
|
int width;
|
|
int height;
|
|
uint32_t format;
|
|
};
|
|
|
|
struct wayland_window_surface
|
|
{
|
|
struct window_surface header;
|
|
struct wayland_buffer_queue *wayland_buffer_queue;
|
|
BOOL layered;
|
|
};
|
|
|
|
static struct wayland_window_surface *wayland_window_surface_cast(
|
|
struct window_surface *window_surface)
|
|
{
|
|
return (struct wayland_window_surface *)window_surface;
|
|
}
|
|
|
|
static void buffer_release(void *data, struct wl_buffer *buffer)
|
|
{
|
|
struct wayland_shm_buffer *shm_buffer = data;
|
|
TRACE("shm_buffer=%p\n", shm_buffer);
|
|
shm_buffer->busy = FALSE;
|
|
wayland_shm_buffer_unref(shm_buffer);
|
|
}
|
|
|
|
static const struct wl_buffer_listener buffer_listener = { buffer_release };
|
|
|
|
/**********************************************************************
|
|
* wayland_buffer_queue_destroy
|
|
*
|
|
* Destroys a buffer queue and any contained buffers.
|
|
*/
|
|
static void wayland_buffer_queue_destroy(struct wayland_buffer_queue *queue)
|
|
{
|
|
struct wayland_shm_buffer *shm_buffer, *next;
|
|
|
|
wl_list_for_each_safe(shm_buffer, next, &queue->buffer_list, link)
|
|
{
|
|
wl_list_remove(&shm_buffer->link);
|
|
wl_list_init(&shm_buffer->link);
|
|
/* Since this buffer may still be busy, attach it to the per-process
|
|
* wl_event_queue to handle any future buffer release events. */
|
|
wl_proxy_set_queue((struct wl_proxy *)shm_buffer->wl_buffer,
|
|
process_wayland.wl_event_queue);
|
|
wayland_shm_buffer_unref(shm_buffer);
|
|
}
|
|
|
|
if (queue->wl_event_queue)
|
|
{
|
|
/* Dispatch the event queue before destruction to process any
|
|
* pending buffer release events. This is required after changing
|
|
* the buffer proxy event queue in the previous step, to avoid
|
|
* missing any events. */
|
|
wl_display_dispatch_queue_pending(process_wayland.wl_display,
|
|
queue->wl_event_queue);
|
|
wl_event_queue_destroy(queue->wl_event_queue);
|
|
}
|
|
|
|
free(queue);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* wayland_buffer_queue_create
|
|
*
|
|
* Creates a buffer queue containing buffers with the specified width and height.
|
|
*/
|
|
static struct wayland_buffer_queue *wayland_buffer_queue_create(int width, int height,
|
|
uint32_t format)
|
|
{
|
|
struct wayland_buffer_queue *queue;
|
|
|
|
queue = calloc(1, sizeof(*queue));
|
|
if (!queue) goto err;
|
|
|
|
queue->wl_event_queue = wl_display_create_queue(process_wayland.wl_display);
|
|
if (!queue->wl_event_queue) goto err;
|
|
queue->width = width;
|
|
queue->height = height;
|
|
queue->format = format;
|
|
|
|
wl_list_init(&queue->buffer_list);
|
|
|
|
return queue;
|
|
|
|
err:
|
|
if (queue) wayland_buffer_queue_destroy(queue);
|
|
return NULL;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* wayland_buffer_queue_get_free_buffer
|
|
*
|
|
* Gets a free buffer from the buffer queue. If no free buffers
|
|
* are available this function blocks until it can provide one.
|
|
*/
|
|
static struct wayland_shm_buffer *wayland_buffer_queue_get_free_buffer(struct wayland_buffer_queue *queue)
|
|
{
|
|
struct wayland_shm_buffer *shm_buffer;
|
|
|
|
TRACE("queue=%p\n", queue);
|
|
|
|
while (TRUE)
|
|
{
|
|
int nbuffers = 0;
|
|
|
|
/* Dispatch any pending buffer release events. */
|
|
wl_display_dispatch_queue_pending(process_wayland.wl_display,
|
|
queue->wl_event_queue);
|
|
|
|
/* Search through our buffers to find an available one. */
|
|
wl_list_for_each(shm_buffer, &queue->buffer_list, link)
|
|
{
|
|
if (!shm_buffer->busy) goto out;
|
|
nbuffers++;
|
|
}
|
|
|
|
/* Dynamically create up to 3 buffers. */
|
|
if (nbuffers < 3)
|
|
{
|
|
shm_buffer = wayland_shm_buffer_create(queue->width, queue->height,
|
|
queue->format);
|
|
if (shm_buffer)
|
|
{
|
|
/* Buffer events go to their own queue so that we can dispatch
|
|
* them independently. */
|
|
wl_proxy_set_queue((struct wl_proxy *) shm_buffer->wl_buffer,
|
|
queue->wl_event_queue);
|
|
wl_buffer_add_listener(shm_buffer->wl_buffer, &buffer_listener,
|
|
shm_buffer);
|
|
wl_list_insert(&queue->buffer_list, &shm_buffer->link);
|
|
goto out;
|
|
}
|
|
else if (nbuffers < 2)
|
|
{
|
|
/* If we failed to allocate a new buffer, but we have at least two
|
|
* buffers busy, there is a good chance the compositor will
|
|
* eventually release one of them, so dispatch events and wait
|
|
* below. Otherwise, give up and return a NULL buffer. */
|
|
ERR(" => failed to acquire buffer\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* We don't have any buffers available, so block waiting for a buffer
|
|
* release event. */
|
|
if (wl_display_dispatch_queue(process_wayland.wl_display,
|
|
queue->wl_event_queue) == -1)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
out:
|
|
TRACE(" => %p %dx%d map=[%p, %p)\n",
|
|
shm_buffer, shm_buffer->width, shm_buffer->height, shm_buffer->map_data,
|
|
(unsigned char*)shm_buffer->map_data + shm_buffer->map_size);
|
|
|
|
return shm_buffer;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* wayland_buffer_queue_add_damage
|
|
*/
|
|
static void wayland_buffer_queue_add_damage(struct wayland_buffer_queue *queue, HRGN damage)
|
|
{
|
|
struct wayland_shm_buffer *shm_buffer;
|
|
|
|
wl_list_for_each(shm_buffer, &queue->buffer_list, link)
|
|
{
|
|
NtGdiCombineRgn(shm_buffer->damage_region, shm_buffer->damage_region,
|
|
damage, RGN_OR);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* wayland_window_surface_set_clip
|
|
*/
|
|
static void wayland_window_surface_set_clip(struct window_surface *window_surface,
|
|
const RECT *rects, UINT count)
|
|
{
|
|
/* TODO */
|
|
}
|
|
|
|
/**********************************************************************
|
|
* get_region_data
|
|
*/
|
|
RGNDATA *get_region_data(HRGN region)
|
|
{
|
|
RGNDATA *data;
|
|
DWORD size;
|
|
|
|
if (!region) return NULL;
|
|
if (!(size = NtGdiGetRegionData(region, 0, NULL))) return NULL;
|
|
if (!(data = malloc(size))) return NULL;
|
|
if (!NtGdiGetRegionData(region, size, data))
|
|
{
|
|
free(data);
|
|
return NULL;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* copy_pixel_region
|
|
*/
|
|
static void copy_pixel_region(const char *src_pixels, RECT *src_rect,
|
|
char *dst_pixels, RECT *dst_rect,
|
|
HRGN region, BOOL force_opaque)
|
|
{
|
|
static const int bpp = WINEWAYLAND_BYTES_PER_PIXEL;
|
|
RGNDATA *rgndata = get_region_data(region);
|
|
RECT *rgn_rect;
|
|
RECT *rgn_rect_end;
|
|
int src_stride, dst_stride;
|
|
|
|
if (!rgndata) return;
|
|
|
|
src_stride = (src_rect->right - src_rect->left) * bpp;
|
|
dst_stride = (dst_rect->right - dst_rect->left) * bpp;
|
|
|
|
rgn_rect = (RECT *)rgndata->Buffer;
|
|
rgn_rect_end = rgn_rect + rgndata->rdh.nCount;
|
|
|
|
for (;rgn_rect < rgn_rect_end; rgn_rect++)
|
|
{
|
|
const char *src;
|
|
char *dst;
|
|
int x, y, width, height;
|
|
RECT rc;
|
|
|
|
TRACE("rect %s\n", wine_dbgstr_rect(rgn_rect));
|
|
|
|
if (!intersect_rect(&rc, rgn_rect, src_rect)) continue;
|
|
if (!intersect_rect(&rc, &rc, dst_rect)) continue;
|
|
|
|
src = src_pixels + (rc.top - src_rect->top) * src_stride + (rc.left - src_rect->left) * bpp;
|
|
dst = dst_pixels + (rc.top - dst_rect->top) * dst_stride + (rc.left - dst_rect->left) * bpp;
|
|
width = rc.right - rc.left;
|
|
height = rc.bottom - rc.top;
|
|
|
|
/* Fast path for full width rectangles. */
|
|
if (width * bpp == src_stride && src_stride == dst_stride)
|
|
{
|
|
if (force_opaque)
|
|
{
|
|
for (x = 0; x < height * width; ++x)
|
|
((UINT32 *)dst)[x] = ((UINT32 *)src)[x] | 0xff000000;
|
|
}
|
|
else memcpy(dst, src, height * width * 4);
|
|
continue;
|
|
}
|
|
|
|
if (force_opaque)
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
for (x = 0; x < width; ++x)
|
|
((UINT32 *)dst)[x] = ((UINT32 *)src)[x] | 0xff000000;
|
|
src += src_stride;
|
|
dst += dst_stride;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
memcpy(dst, src, width * 4);
|
|
src += src_stride;
|
|
dst += dst_stride;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(rgndata);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* wayland_shm_buffer_copy_data
|
|
*/
|
|
static void wayland_shm_buffer_copy_data(struct wayland_shm_buffer *buffer,
|
|
const char *bits, RECT *rect,
|
|
HRGN region, BOOL force_opaque)
|
|
{
|
|
RECT buffer_rect = {0, 0, buffer->width, buffer->height};
|
|
TRACE("buffer=%p bits=%p rect=%s\n", buffer, bits, wine_dbgstr_rect(rect));
|
|
copy_pixel_region(bits, rect, buffer->map_data, &buffer_rect, region, force_opaque);
|
|
}
|
|
|
|
static void wayland_shm_buffer_copy(struct wayland_shm_buffer *src,
|
|
struct wayland_shm_buffer *dst,
|
|
HRGN region)
|
|
{
|
|
RECT src_rect = {0, 0, src->width, src->height};
|
|
RECT dst_rect = {0, 0, dst->width, dst->height};
|
|
TRACE("src=%p dst=%p\n", src, dst);
|
|
copy_pixel_region(src->map_data, &src_rect, dst->map_data, &dst_rect, region,
|
|
src->format == WL_SHM_FORMAT_XRGB8888 && dst->format == WL_SHM_FORMAT_ARGB8888);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* wayland_shm_buffer_copy_shape
|
|
*/
|
|
static void wayland_shm_buffer_copy_shape(struct wayland_shm_buffer *buffer, const RECT *dirty,
|
|
const BITMAPINFO *shape_info, const void *shape_bits)
|
|
{
|
|
RECT dst_rect = {0, 0, buffer->width, buffer->height};
|
|
UINT32 *color, shape_stride, color_stride, x, y;
|
|
const BYTE *shape;
|
|
RECT rect;
|
|
|
|
shape_stride = shape_info->bmiHeader.biSizeImage / abs(shape_info->bmiHeader.biHeight);
|
|
color_stride = dst_rect.right - dst_rect.left;
|
|
|
|
if (!intersect_rect(&rect, &dst_rect, dirty)) return;
|
|
|
|
color = (UINT32 *)buffer->map_data + rect.top * color_stride;
|
|
shape = (const BYTE *)shape_bits + rect.top * shape_stride;
|
|
|
|
for (y = rect.top; y < rect.bottom; y++, color += color_stride, shape += shape_stride)
|
|
{
|
|
for (x = rect.left; x < rect.right; x++)
|
|
{
|
|
if (!(shape[x / 8] & (1 << (7 - (x & 7))))) color[x] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* wayland_window_surface_flush
|
|
*/
|
|
static BOOL wayland_window_surface_flush(struct window_surface *window_surface, const RECT *rect, const RECT *dirty,
|
|
const BITMAPINFO *color_info, const void *color_bits, BOOL shape_changed,
|
|
const BITMAPINFO *shape_info, const void *shape_bits)
|
|
{
|
|
RECT surface_rect = {.right = color_info->bmiHeader.biWidth, .bottom = abs(color_info->bmiHeader.biHeight)};
|
|
struct wayland_window_surface *wws = wayland_window_surface_cast(window_surface);
|
|
struct wayland_shm_buffer *shm_buffer = NULL, *latest_buffer;
|
|
BOOL flushed = FALSE;
|
|
HRGN surface_damage_region = NULL;
|
|
HRGN copy_from_window_region;
|
|
uint32_t buffer_format;
|
|
|
|
surface_damage_region = NtGdiCreateRectRgn(rect->left + dirty->left, rect->top + dirty->top,
|
|
rect->left + dirty->right, rect->top + dirty->bottom);
|
|
if (!surface_damage_region)
|
|
{
|
|
ERR("failed to create surface damage region\n");
|
|
goto done;
|
|
}
|
|
|
|
buffer_format = (shape_bits || wws->layered) ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888;
|
|
if (wws->wayland_buffer_queue->format != buffer_format)
|
|
{
|
|
int width = wws->wayland_buffer_queue->width;
|
|
int height = wws->wayland_buffer_queue->height;
|
|
TRACE("recreating buffer queue with format %d\n", buffer_format);
|
|
wayland_buffer_queue_destroy(wws->wayland_buffer_queue);
|
|
wws->wayland_buffer_queue = wayland_buffer_queue_create(width, height, buffer_format);
|
|
}
|
|
|
|
wayland_buffer_queue_add_damage(wws->wayland_buffer_queue, surface_damage_region);
|
|
|
|
shm_buffer = wayland_buffer_queue_get_free_buffer(wws->wayland_buffer_queue);
|
|
if (!shm_buffer)
|
|
{
|
|
ERR("failed to acquire Wayland SHM buffer, returning\n");
|
|
goto done;
|
|
}
|
|
|
|
if ((latest_buffer = get_window_surface_contents(window_surface->hwnd)))
|
|
{
|
|
TRACE("latest_window_buffer=%p\n", latest_buffer);
|
|
/* If we have a latest buffer, use it as the source of all pixel
|
|
* data that are not contained in the bounds of the flush... */
|
|
if (latest_buffer != shm_buffer)
|
|
{
|
|
HRGN copy_from_latest_region = NtGdiCreateRectRgn(0, 0, 0, 0);
|
|
if (!copy_from_latest_region)
|
|
{
|
|
ERR("failed to create copy_from_latest region\n");
|
|
goto done;
|
|
}
|
|
NtGdiCombineRgn(copy_from_latest_region, shm_buffer->damage_region,
|
|
surface_damage_region, RGN_DIFF);
|
|
wayland_shm_buffer_copy(latest_buffer,
|
|
shm_buffer, copy_from_latest_region);
|
|
NtGdiDeleteObjectApp(copy_from_latest_region);
|
|
}
|
|
/* ... and use the window_surface as the source of pixel data contained
|
|
* in the flush bounds. */
|
|
copy_from_window_region = surface_damage_region;
|
|
wayland_shm_buffer_unref(latest_buffer);
|
|
}
|
|
else
|
|
{
|
|
TRACE("latest_window_buffer=NULL\n");
|
|
/* If we don't have a latest buffer, use the window_surface as
|
|
* the source of all pixel data. */
|
|
copy_from_window_region = shm_buffer->damage_region;
|
|
}
|
|
|
|
wayland_shm_buffer_copy_data(shm_buffer, color_bits, &surface_rect, copy_from_window_region,
|
|
shape_bits && !wws->layered);
|
|
if (shape_bits) wayland_shm_buffer_copy_shape(shm_buffer, rect, shape_info, shape_bits);
|
|
|
|
NtGdiSetRectRgn(shm_buffer->damage_region, 0, 0, 0, 0);
|
|
|
|
flushed = set_window_surface_contents(window_surface->hwnd, shm_buffer, surface_damage_region);
|
|
wl_display_flush(process_wayland.wl_display);
|
|
|
|
done:
|
|
if (surface_damage_region) NtGdiDeleteObjectApp(surface_damage_region);
|
|
return flushed;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* wayland_window_surface_destroy
|
|
*/
|
|
static void wayland_window_surface_destroy(struct window_surface *window_surface)
|
|
{
|
|
struct wayland_window_surface *wws = wayland_window_surface_cast(window_surface);
|
|
|
|
TRACE("surface=%p\n", wws);
|
|
|
|
wayland_buffer_queue_destroy(wws->wayland_buffer_queue);
|
|
}
|
|
|
|
static const struct window_surface_funcs wayland_window_surface_funcs =
|
|
{
|
|
wayland_window_surface_set_clip,
|
|
wayland_window_surface_flush,
|
|
wayland_window_surface_destroy
|
|
};
|
|
|
|
/***********************************************************************
|
|
* wayland_window_surface_create
|
|
*/
|
|
static struct window_surface *wayland_window_surface_create(HWND hwnd, const RECT *rect,
|
|
BOOL layered)
|
|
{
|
|
char buffer[FIELD_OFFSET(BITMAPINFO, bmiColors[256])];
|
|
BITMAPINFO *info = (BITMAPINFO *)buffer;
|
|
struct wayland_window_surface *wws;
|
|
int width = rect->right - rect->left;
|
|
int height = rect->bottom - rect->top;
|
|
struct window_surface *window_surface;
|
|
|
|
TRACE("hwnd %p rect %s\n", hwnd, wine_dbgstr_rect(rect));
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
info->bmiHeader.biSize = sizeof(info->bmiHeader);
|
|
info->bmiHeader.biWidth = width;
|
|
info->bmiHeader.biHeight = -height; /* top-down */
|
|
info->bmiHeader.biPlanes = 1;
|
|
info->bmiHeader.biBitCount = 32;
|
|
info->bmiHeader.biSizeImage = width * height * 4;
|
|
info->bmiHeader.biCompression = BI_RGB;
|
|
|
|
if ((window_surface = window_surface_create(sizeof(*wws), &wayland_window_surface_funcs, hwnd, rect, info, 0)))
|
|
{
|
|
struct wayland_window_surface *wws = wayland_window_surface_cast(window_surface);
|
|
wws->wayland_buffer_queue =
|
|
wayland_buffer_queue_create(width, height,
|
|
layered ? WL_SHM_FORMAT_ARGB8888 :
|
|
WL_SHM_FORMAT_XRGB8888);
|
|
wws->layered = layered;
|
|
}
|
|
|
|
return window_surface;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* WAYLAND_CreateWindowSurface
|
|
*/
|
|
BOOL WAYLAND_CreateWindowSurface(HWND hwnd, BOOL layered, const RECT *surface_rect, struct window_surface **surface)
|
|
{
|
|
struct window_surface *previous;
|
|
struct wayland_win_data *data;
|
|
|
|
TRACE("hwnd %p, layered %u, surface_rect %s, surface %p\n", hwnd, layered, wine_dbgstr_rect(surface_rect), surface);
|
|
|
|
if ((previous = *surface) && previous->funcs == &wayland_window_surface_funcs) return TRUE;
|
|
if (!(data = wayland_win_data_get(hwnd))) return TRUE; /* use default surface */
|
|
if (previous) window_surface_release(previous);
|
|
|
|
if (layered) data->layered_attribs_set = TRUE;
|
|
*surface = wayland_window_surface_create(data->hwnd, surface_rect, layered);
|
|
|
|
wayland_win_data_release(data);
|
|
return TRUE;
|
|
}
|