xserver-multidpi/hw/xwayland/xwayland-window-buffers.c

368 lines
13 KiB
C

/*
* Copyright © 2019 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Olivier Fourdan <ofourdan@redhat.com>
*/
#include <xwayland-config.h>
#include "gcstruct.h"
#include "xwayland-window.h"
#include "xwayland-pixmap.h"
#include "xwayland-screen.h"
#include "xwayland-window-buffers.h"
#define BUFFER_TIMEOUT 1 * 1000 /* ms */
struct xwl_window_buffer {
struct xwl_window *xwl_window;
PixmapPtr pixmap;
RegionPtr damage_region;
Bool recycle_on_release;
int refcnt;
uint32_t time;
struct xorg_list link_buffer;
};
static Bool
copy_pixmap_area(PixmapPtr src_pixmap, PixmapPtr dst_pixmap,
int x, int y, int width, int height)
{
GCPtr pGC;
pGC = GetScratchGC(dst_pixmap->drawable.depth,
dst_pixmap->drawable.pScreen);
if (pGC) {
ValidateGC(&dst_pixmap->drawable, pGC);
(void) (*pGC->ops->CopyArea) (&src_pixmap->drawable,
&dst_pixmap->drawable,
pGC,
x, y,
width, height,
x, y);
FreeScratchGC(pGC);
return TRUE;
}
return FALSE;
}
static struct xwl_window_buffer *
xwl_window_buffer_new(struct xwl_window *xwl_window)
{
struct xwl_window_buffer *xwl_window_buffer;
xwl_window_buffer = calloc (1, sizeof(struct xwl_window_buffer));
if (!xwl_window_buffer)
return NULL;
xwl_window_buffer->xwl_window = xwl_window;
xwl_window_buffer->damage_region = RegionCreate(NullBox, 1);
xwl_window_buffer->pixmap = NullPixmap;
xwl_window_buffer->refcnt = 1;
xorg_list_append(&xwl_window_buffer->link_buffer,
&xwl_window->window_buffers_available);
return xwl_window_buffer;
}
static void
xwl_window_buffer_destroy_pixmap(struct xwl_window_buffer *xwl_window_buffer)
{
ScreenPtr pScreen = xwl_window_buffer->pixmap->drawable.pScreen;
xwl_pixmap_del_buffer_release_cb(xwl_window_buffer->pixmap);
(*pScreen->DestroyPixmap) (xwl_window_buffer->pixmap);
xwl_window_buffer->pixmap = NullPixmap;
}
static Bool
xwl_window_buffer_dispose(struct xwl_window_buffer *xwl_window_buffer)
{
assert(xwl_window_buffer->refcnt > 0);
if (--xwl_window_buffer->refcnt)
return FALSE;
RegionDestroy(xwl_window_buffer->damage_region);
if (xwl_window_buffer->pixmap)
xwl_window_buffer_destroy_pixmap (xwl_window_buffer);
xorg_list_del(&xwl_window_buffer->link_buffer);
free(xwl_window_buffer);
return TRUE;
}
static void
xwl_window_buffer_recycle(struct xwl_window_buffer *xwl_window_buffer)
{
RegionEmpty(xwl_window_buffer->damage_region);
xwl_window_buffer->recycle_on_release = FALSE;
if (xwl_window_buffer->pixmap)
xwl_window_buffer_destroy_pixmap (xwl_window_buffer);
}
static void
xwl_window_buffer_add_damage_region(struct xwl_window *xwl_window,
RegionPtr damage_region)
{
struct xwl_window_buffer *xwl_window_buffer;
/* Add damage region to all buffers */
xorg_list_for_each_entry(xwl_window_buffer,
&xwl_window->window_buffers_available,
link_buffer) {
RegionUnion(xwl_window_buffer->damage_region,
xwl_window_buffer->damage_region,
damage_region);
}
xorg_list_for_each_entry(xwl_window_buffer,
&xwl_window->window_buffers_unavailable,
link_buffer) {
RegionUnion(xwl_window_buffer->damage_region,
xwl_window_buffer->damage_region,
damage_region);
}
}
static struct xwl_window_buffer *
xwl_window_buffer_get_available(struct xwl_window *xwl_window)
{
if (xorg_list_is_empty(&xwl_window->window_buffers_available))
return xwl_window_buffer_new(xwl_window);
return xorg_list_last_entry(&xwl_window->window_buffers_available,
struct xwl_window_buffer,
link_buffer);
}
static CARD32
xwl_window_buffer_timer_callback(OsTimerPtr timer, CARD32 time, void *arg)
{
struct xwl_window *xwl_window = arg;
struct xwl_window_buffer *xwl_window_buffer, *tmp;
/* Dispose older available buffers */
xorg_list_for_each_entry_safe(xwl_window_buffer, tmp,
&xwl_window->window_buffers_available,
link_buffer) {
if ((int64_t)(time - xwl_window_buffer->time) >= BUFFER_TIMEOUT)
xwl_window_buffer_dispose(xwl_window_buffer);
}
/* If there are still available buffers, re-arm the timer */
if (!xorg_list_is_empty(&xwl_window->window_buffers_available)) {
struct xwl_window_buffer *oldest_available_buffer =
xorg_list_first_entry(&xwl_window->window_buffers_available,
struct xwl_window_buffer,
link_buffer);
return oldest_available_buffer->time + BUFFER_TIMEOUT - time;
}
/* Don't re-arm the timer */
return 0;
}
static void
xwl_window_buffer_release_callback(void *data)
{
struct xwl_window_buffer *xwl_window_buffer = data;
struct xwl_window *xwl_window = xwl_window_buffer->xwl_window;
struct xwl_window_buffer *oldest_available_buffer;
/* Drop the reference on the buffer we took in get_pixmap. If that
* frees the window buffer, we're done.
*/
if (xwl_window_buffer_dispose(xwl_window_buffer))
return;
if (xwl_window_buffer->recycle_on_release)
xwl_window_buffer_recycle(xwl_window_buffer);
/* We append the buffers to the end of the list, as we pick the last
* entry again when looking for new available buffers, that means the
* least used buffers will remain at the beginning of the list so that
* they can be garbage collected automatically after some time unused.
*/
xorg_list_del(&xwl_window_buffer->link_buffer);
xorg_list_append(&xwl_window_buffer->link_buffer,
&xwl_window->window_buffers_available);
xwl_window_buffer->time = (uint32_t) GetTimeInMillis();
oldest_available_buffer =
xorg_list_first_entry(&xwl_window->window_buffers_available,
struct xwl_window_buffer,
link_buffer);
/* Schedule next timer based on time of the oldest buffer */
xwl_window->window_buffers_timer =
TimerSet(xwl_window->window_buffers_timer,
TimerAbsolute,
oldest_available_buffer->time + BUFFER_TIMEOUT,
&xwl_window_buffer_timer_callback,
xwl_window);
}
void
xwl_window_buffers_init(struct xwl_window *xwl_window)
{
xorg_list_init(&xwl_window->window_buffers_available);
xorg_list_init(&xwl_window->window_buffers_unavailable);
}
void
xwl_window_buffers_recycle(struct xwl_window *xwl_window)
{
struct xwl_window_buffer *xwl_window_buffer, *tmp;
/* Dispose available buffers */
xorg_list_for_each_entry_safe(xwl_window_buffer, tmp,
&xwl_window->window_buffers_available,
link_buffer) {
xwl_window_buffer_dispose(xwl_window_buffer);
}
if (xwl_window->window_buffers_timer)
TimerCancel(xwl_window->window_buffers_timer);
/* Mark the others for recycle on release */
xorg_list_for_each_entry(xwl_window_buffer,
&xwl_window->window_buffers_unavailable,
link_buffer) {
xwl_window_buffer->recycle_on_release = TRUE;
}
}
void
xwl_window_buffers_dispose(struct xwl_window *xwl_window)
{
struct xwl_window_buffer *xwl_window_buffer, *tmp;
/* This is called prior to free the xwl_window, make sure to untie
* the buffers from the xwl_window so that we don't point at freed
* memory if we get a release buffer later.
*/
xorg_list_for_each_entry_safe(xwl_window_buffer, tmp,
&xwl_window->window_buffers_available,
link_buffer) {
xorg_list_del(&xwl_window_buffer->link_buffer);
xwl_window_buffer_dispose(xwl_window_buffer);
}
xorg_list_for_each_entry_safe(xwl_window_buffer, tmp,
&xwl_window->window_buffers_unavailable,
link_buffer) {
xorg_list_del(&xwl_window_buffer->link_buffer);
xwl_window_buffer_dispose(xwl_window_buffer);
}
if (xwl_window->window_buffers_timer) {
TimerFree(xwl_window->window_buffers_timer);
xwl_window->window_buffers_timer = 0;
}
}
PixmapPtr
xwl_window_buffers_get_pixmap(struct xwl_window *xwl_window,
RegionPtr damage_region)
{
struct xwl_screen *xwl_screen = xwl_window->xwl_screen;
struct xwl_window_buffer *xwl_window_buffer;
PixmapPtr window_pixmap;
RegionPtr full_damage;
window_pixmap = (*xwl_screen->screen->GetWindowPixmap) (xwl_window->window);
#ifdef XWL_HAS_GLAMOR
if (!xwl_glamor_needs_n_buffering(xwl_screen))
return window_pixmap;
#endif /* XWL_HAS_GLAMOR */
xwl_window_buffer = xwl_window_buffer_get_available(xwl_window);
if (!xwl_window_buffer)
return window_pixmap;
xwl_window_buffer_add_damage_region(xwl_window, damage_region);
full_damage = xwl_window_buffer->damage_region;
if (xwl_window_buffer->pixmap) {
BoxPtr pBox = RegionRects(full_damage);
int nBox = RegionNumRects(full_damage);
while (nBox--) {
if (!copy_pixmap_area(window_pixmap,
xwl_window_buffer->pixmap,
pBox->x1 + xwl_window->window->borderWidth,
pBox->y1 + xwl_window->window->borderWidth,
pBox->x2 - pBox->x1,
pBox->y2 - pBox->y1))
return window_pixmap;
pBox++;
}
} else {
xwl_window_buffer->pixmap =
(*xwl_screen->screen->CreatePixmap) (window_pixmap->drawable.pScreen,
window_pixmap->drawable.width,
window_pixmap->drawable.height,
window_pixmap->drawable.depth,
CREATE_PIXMAP_USAGE_BACKING_PIXMAP);
if (!xwl_window_buffer->pixmap)
return window_pixmap;
if (!copy_pixmap_area(window_pixmap,
xwl_window_buffer->pixmap,
0, 0,
window_pixmap->drawable.width,
window_pixmap->drawable.height)) {
xwl_window_buffer_recycle(xwl_window_buffer);
return window_pixmap;
}
}
RegionEmpty(xwl_window_buffer->damage_region);
/* Hold a reference on the buffer until it's released by the compositor */
xwl_window_buffer->refcnt++;
xwl_pixmap_set_buffer_release_cb(xwl_window_buffer->pixmap,
xwl_window_buffer_release_callback,
xwl_window_buffer);
xorg_list_del(&xwl_window_buffer->link_buffer);
xorg_list_append(&xwl_window_buffer->link_buffer,
&xwl_window->window_buffers_unavailable);
if (xorg_list_is_empty(&xwl_window->window_buffers_available))
TimerCancel(xwl_window->window_buffers_timer);
return xwl_window_buffer->pixmap;
}