/* * 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 */ #include #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; }