/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ /* cairo - a vector graphics library with display and print output * * Copyright © 2012 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is University of Southern * California. * * Contributor(s): * Chris Wilson */ #include "cairoint.h" #if !CAIRO_HAS_XLIB_XCB_FUNCTIONS #include "cairo-xlib-private.h" #include "cairo-xlib-surface-private.h" #if !HAVE_X11_EXTENSIONS_XSHM_H || !(HAVE_X11_EXTENSIONS_SHMPROTO_H || HAVE_X11_EXTENSIONS_SHMSTR_H) void _cairo_xlib_display_init_shm (cairo_xlib_display_t *display) {} cairo_surface_t * _cairo_xlib_surface_get_shm (cairo_xlib_surface_t *surface, cairo_bool_t overwrite) { return NULL; } cairo_int_status_t _cairo_xlib_surface_put_shm (cairo_xlib_surface_t *surface) { assert (!surface->fallback); return CAIRO_INT_STATUS_SUCCESS; } cairo_surface_t * _cairo_xlib_surface_create_shm (cairo_xlib_surface_t *other, pixman_format_code_t format, int width, int height) { return NULL; } cairo_surface_t * _cairo_xlib_surface_create_shm__image (cairo_xlib_surface_t *surface, pixman_format_code_t format, int width, int height) { return NULL; } cairo_surface_t * _cairo_xlib_surface_create_similar_shm (void *other, cairo_format_t format, int width, int height) { return cairo_image_surface_create (format, width, height); } void _cairo_xlib_shm_surface_mark_active (cairo_surface_t *_shm) { ASSERT_NOT_REACHED; } void _cairo_xlib_shm_surface_get_ximage (cairo_surface_t *surface, XImage *ximage) { ASSERT_NOT_REACHED; } void * _cairo_xlib_shm_surface_get_obdata (cairo_surface_t *surface) { ASSERT_NOT_REACHED; return NULL; } Pixmap _cairo_xlib_shm_surface_get_pixmap (cairo_surface_t *surface) { ASSERT_NOT_REACHED; return 0; } XRenderPictFormat * _cairo_xlib_shm_surface_get_xrender_format (cairo_surface_t *surface) { ASSERT_NOT_REACHED; return NULL; } cairo_bool_t _cairo_xlib_shm_surface_is_active (cairo_surface_t *surface) { ASSERT_NOT_REACHED; return FALSE; } cairo_bool_t _cairo_xlib_shm_surface_is_idle (cairo_surface_t *surface) { ASSERT_NOT_REACHED; return TRUE; } void _cairo_xlib_display_fini_shm (cairo_xlib_display_t *display) {} #else #include "cairo-damage-private.h" #include "cairo-default-context-private.h" #include "cairo-image-surface-private.h" #include "cairo-list-inline.h" #include "cairo-mempool-private.h" #include #include #include #if HAVE_X11_EXTENSIONS_SHMPROTO_H #include #elif HAVE_X11_EXTENSIONS_SHMSTR_H #include #endif #include #include #define MIN_PIXMAP_SIZE 4096 #define MIN_BITS 8 #define MIN_SIZE (1<<(MIN_BITS-1)) typedef struct _cairo_xlib_shm cairo_xlib_shm_t; typedef struct _cairo_xlib_shm_info cairo_xlib_shm_info_t; typedef struct _cairo_xlib_shm_surface cairo_xlib_shm_surface_t; struct _cairo_xlib_shm { cairo_mempool_t mem; XShmSegmentInfo shm; unsigned long attached; cairo_list_t link; }; struct _cairo_xlib_shm_info { unsigned long last_request; void *mem; size_t size; cairo_xlib_shm_t *pool; }; struct _cairo_xlib_shm_surface { cairo_image_surface_t image; cairo_list_t link; cairo_xlib_shm_info_t *info; Pixmap pixmap; unsigned long active; int idle; }; /* the parent is always given by index/2 */ #define PQ_PARENT_INDEX(i) ((i) >> 1) #define PQ_FIRST_ENTRY 1 /* left and right children are index * 2 and (index * 2) +1 respectively */ #define PQ_LEFT_CHILD_INDEX(i) ((i) << 1) #define PQ_TOP(pq) ((pq)->elements[PQ_FIRST_ENTRY]) struct pqueue { int size, max_size; cairo_xlib_shm_info_t **elements; }; struct _cairo_xlib_shm_display { int has_pixmaps; int opcode; int event; Window window; unsigned long last_request; unsigned long last_event; cairo_list_t surfaces; cairo_list_t pool; struct pqueue info; }; static inline cairo_bool_t seqno_passed (unsigned long a, unsigned long b) { return (long)(b - a) >= 0; } static inline cairo_bool_t seqno_before (unsigned long a, unsigned long b) { return (long)(b - a) > 0; } static inline cairo_bool_t seqno_after (unsigned long a, unsigned long b) { return (long)(a - b) > 0; } static inline cairo_status_t _pqueue_init (struct pqueue *pq) { pq->max_size = 32; pq->size = 0; pq->elements = _cairo_malloc_ab (pq->max_size, sizeof (cairo_xlib_shm_info_t *)); if (unlikely (pq->elements == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); PQ_TOP(pq) = NULL; return CAIRO_STATUS_SUCCESS; } static inline void _pqueue_fini (struct pqueue *pq) { free (pq->elements); } static cairo_status_t _pqueue_grow (struct pqueue *pq) { cairo_xlib_shm_info_t **new_elements; new_elements = _cairo_realloc_ab (pq->elements, 2 * pq->max_size, sizeof (cairo_xlib_shm_info_t *)); if (unlikely (new_elements == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); pq->elements = new_elements; pq->max_size *= 2; return CAIRO_STATUS_SUCCESS; } static void _pqueue_shrink (struct pqueue *pq, int min_size) { cairo_xlib_shm_info_t **new_elements; if (min_size > pq->max_size) return; new_elements = _cairo_realloc_ab (pq->elements, min_size, sizeof (cairo_xlib_shm_info_t *)); if (unlikely (new_elements == NULL)) return; pq->elements = new_elements; pq->max_size = min_size; } static inline cairo_status_t _pqueue_push (struct pqueue *pq, cairo_xlib_shm_info_t *info) { cairo_xlib_shm_info_t **elements; int i, parent; if (unlikely (pq->size + 1 == pq->max_size)) { cairo_status_t status; status = _pqueue_grow (pq); if (unlikely (status)) return status; } elements = pq->elements; for (i = ++pq->size; i != PQ_FIRST_ENTRY && info->last_request < elements[parent = PQ_PARENT_INDEX (i)]->last_request; i = parent) { elements[i] = elements[parent]; } elements[i] = info; return CAIRO_STATUS_SUCCESS; } static inline void _pqueue_pop (struct pqueue *pq) { cairo_xlib_shm_info_t **elements = pq->elements; cairo_xlib_shm_info_t *tail; int child, i; tail = elements[pq->size--]; if (pq->size == 0) { elements[PQ_FIRST_ENTRY] = NULL; _pqueue_shrink (pq, 32); return; } for (i = PQ_FIRST_ENTRY; (child = PQ_LEFT_CHILD_INDEX (i)) <= pq->size; i = child) { if (child != pq->size && elements[child+1]->last_request < elements[child]->last_request) { child++; } if (elements[child]->last_request >= tail->last_request) break; elements[i] = elements[child]; } elements[i] = tail; } static cairo_bool_t _x_error_occurred; static int _check_error_handler (Display *display, XErrorEvent *event) { _x_error_occurred = TRUE; return False; /* ignored */ } static cairo_bool_t can_use_shm (Display *dpy, int *has_pixmap) { XShmSegmentInfo shm; int (*old_handler) (Display *display, XErrorEvent *event); Status success; int major, minor; if (! XShmQueryExtension (dpy)) return FALSE; XShmQueryVersion (dpy, &major, &minor, has_pixmap); shm.shmid = shmget (IPC_PRIVATE, 0x1000, IPC_CREAT | 0600); if (shm.shmid == -1) return FALSE; shm.readOnly = FALSE; shm.shmaddr = shmat (shm.shmid, NULL, 0); if (shm.shmaddr == (char *) -1) { shmctl (shm.shmid, IPC_RMID, NULL); return FALSE; } assert (CAIRO_MUTEX_IS_LOCKED (_cairo_xlib_display_mutex)); _x_error_occurred = FALSE; XLockDisplay (dpy); XSync (dpy, False); old_handler = XSetErrorHandler (_check_error_handler); success = XShmAttach (dpy, &shm); if (success) XShmDetach (dpy, &shm); XSync (dpy, False); XSetErrorHandler (old_handler); XUnlockDisplay (dpy); shmctl (shm.shmid, IPC_RMID, NULL); shmdt (shm.shmaddr); return success && ! _x_error_occurred; } static inline Display * peek_display (cairo_device_t *device) { return ((cairo_xlib_display_t *)device)->display; } static inline unsigned long peek_processed (cairo_device_t *device) { return LastKnownRequestProcessed (peek_display(device)); } static void _cairo_xlib_display_shm_pool_destroy (cairo_xlib_display_t *display, cairo_xlib_shm_t *pool) { shmdt (pool->shm.shmaddr); if (display->display) /* may be called after CloseDisplay */ XShmDetach (display->display, &pool->shm); _cairo_mempool_fini (&pool->mem); cairo_list_del (&pool->link); free (pool); } static void send_event(cairo_xlib_display_t *display, cairo_xlib_shm_info_t *info, unsigned long seqno) { XShmCompletionEvent ev; if (! seqno_after (seqno, display->shm->last_event)) return; ev.type = display->shm->event; ev.send_event = 1; /* XXX or lie? */ ev.serial = XNextRequest (display->display); ev.drawable = display->shm->window; ev.major_code = display->shm->opcode; ev.minor_code = X_ShmPutImage; ev.shmseg = info->pool->shm.shmid; ev.offset = (char *)info->mem - (char *)info->pool->shm.shmaddr; XSendEvent (display->display, ev.drawable, False, 0, (XEvent *)&ev); display->shm->last_event = ev.serial; } static void _cairo_xlib_display_sync (cairo_xlib_display_t *display) { cairo_xlib_shm_info_t *info; struct pqueue *pq = &display->shm->info; XSync (display->display, False); while ((info = PQ_TOP(pq))) { _cairo_mempool_free (&info->pool->mem, info->mem); _pqueue_pop (&display->shm->info); free (info); } } static void _cairo_xlib_shm_info_cleanup (cairo_xlib_display_t *display) { cairo_xlib_shm_info_t *info; Display *dpy = display->display; struct pqueue *pq = &display->shm->info; unsigned long processed; if (PQ_TOP(pq) == NULL) return; XEventsQueued (dpy, QueuedAfterReading); processed = LastKnownRequestProcessed (dpy); info = PQ_TOP(pq); do { if (! seqno_passed (info->last_request, processed)) { send_event (display, info, display->shm->last_request); return; } _cairo_mempool_free (&info->pool->mem, info->mem); _pqueue_pop (&display->shm->info); free (info); } while ((info = PQ_TOP(pq))); } static cairo_xlib_shm_t * _cairo_xlib_shm_info_find (cairo_xlib_display_t *display, size_t size, void **ptr, unsigned long *last_request) { cairo_xlib_shm_info_t *info; struct pqueue *pq = &display->shm->info; if (PQ_TOP(pq) == NULL) return NULL; info = PQ_TOP(pq); do { cairo_xlib_shm_t *pool = info->pool; *last_request = info->last_request; _pqueue_pop (&display->shm->info); _cairo_mempool_free (&pool->mem, info->mem); free (info); if (pool->mem.free_bytes >= size) { void *mem = _cairo_mempool_alloc (&pool->mem, size); if (mem != NULL) { *ptr = mem; return pool; } } } while ((info = PQ_TOP(pq))); return NULL; } static cairo_xlib_shm_t * _cairo_xlib_shm_pool_find (cairo_xlib_display_t *display, size_t size, void **ptr) { cairo_xlib_shm_t *pool; cairo_list_foreach_entry (pool, cairo_xlib_shm_t, &display->shm->pool, link) { if (pool->mem.free_bytes >= size) { void *mem = _cairo_mempool_alloc (&pool->mem, size); if (mem != NULL) { *ptr = mem; return pool; } } } return NULL; } static void _cairo_xlib_shm_pool_cleanup (cairo_xlib_display_t *display) { cairo_xlib_shm_t *pool, *next; unsigned long processed; processed = LastKnownRequestProcessed (display->display); cairo_list_foreach_entry_safe (pool, next, cairo_xlib_shm_t, &display->shm->pool, link) { if (! seqno_passed (pool->attached, processed)) break; if (pool->mem.free_bytes == pool->mem.max_bytes) _cairo_xlib_display_shm_pool_destroy (display, pool); } } static cairo_xlib_shm_t * _cairo_xlib_shm_pool_create(cairo_xlib_display_t *display, size_t size, void **ptr) { Display *dpy = display->display; cairo_xlib_shm_t *pool; size_t bytes, maxbits = 16, minbits = MIN_BITS; Status success; pool = malloc (sizeof (cairo_xlib_shm_t)); if (pool == NULL) return NULL; bytes = 1 << maxbits; while (bytes <= size) bytes <<= 1, maxbits++; bytes <<= 3; minbits += (maxbits - 16) / 2; pool->shm.shmid = shmget (IPC_PRIVATE, bytes, IPC_CREAT | 0600); while (pool->shm.shmid == -1 && bytes >= 2*size) { bytes >>= 1; pool->shm.shmid = shmget (IPC_PRIVATE, bytes, IPC_CREAT | 0600); } if (pool->shm.shmid == -1) goto cleanup; pool->shm.readOnly = FALSE; pool->shm.shmaddr = shmat (pool->shm.shmid, NULL, 0); if (pool->shm.shmaddr == (char *) -1) { shmctl (pool->shm.shmid, IPC_RMID, NULL); goto cleanup; } pool->attached = XNextRequest (dpy); success = XShmAttach (dpy, &pool->shm); #if !IPC_RMID_DEFERRED_RELEASE XSync (dpy, FALSE); #endif shmctl (pool->shm.shmid, IPC_RMID, NULL); if (! success) goto cleanup_shm; if (_cairo_mempool_init (&pool->mem, pool->shm.shmaddr, bytes, minbits, maxbits - minbits + 1)) goto cleanup_detach; cairo_list_add (&pool->link, &display->shm->pool); *ptr = _cairo_mempool_alloc (&pool->mem, size); assert (*ptr != NULL); return pool; cleanup_detach: XShmDetach (dpy, &pool->shm); cleanup_shm: shmdt (pool->shm.shmaddr); cleanup: free (pool); return NULL; } static cairo_xlib_shm_info_t * _cairo_xlib_shm_info_create (cairo_xlib_display_t *display, size_t size, cairo_bool_t will_sync) { cairo_xlib_shm_info_t *info; cairo_xlib_shm_t *pool; unsigned long last_request = 0; void *mem = NULL; _cairo_xlib_shm_info_cleanup (display); pool = _cairo_xlib_shm_pool_find (display, size, &mem); _cairo_xlib_shm_pool_cleanup (display); if (pool == NULL && will_sync) pool = _cairo_xlib_shm_info_find (display, size, &mem, &last_request); if (pool == NULL) pool = _cairo_xlib_shm_pool_create (display, size, &mem); if (pool == NULL) return NULL; assert (mem != NULL); info = malloc (sizeof (*info)); if (info == NULL) { _cairo_mempool_free (&pool->mem, mem); return NULL; } info->pool = pool; info->mem = mem; info->size = size; info->last_request = last_request; return info; } static cairo_status_t _cairo_xlib_shm_surface_flush (void *abstract_surface, unsigned flags) { cairo_xlib_shm_surface_t *shm = abstract_surface; cairo_xlib_display_t *display; Display *dpy; cairo_status_t status; if (shm->active == 0) return CAIRO_STATUS_SUCCESS; if (shm->image.base._finishing) return CAIRO_STATUS_SUCCESS; if (seqno_passed (shm->active, peek_processed (shm->image.base.device))) { shm->active = 0; return CAIRO_STATUS_SUCCESS; } status = _cairo_xlib_display_acquire (shm->image.base.device, &display); if (unlikely (status)) return status; send_event (display, shm->info, shm->active); dpy = display->display; XEventsQueued (dpy, QueuedAfterReading); while (! seqno_passed (shm->active, LastKnownRequestProcessed (dpy))) { LockDisplay(dpy); _XReadEvents(dpy); UnlockDisplay(dpy); } cairo_device_release (&display->base); shm->active = 0; return CAIRO_STATUS_SUCCESS; } static inline cairo_bool_t active (cairo_xlib_shm_surface_t *shm, Display *dpy) { return (shm->active && ! seqno_passed (shm->active, LastKnownRequestProcessed (dpy))); } static cairo_status_t _cairo_xlib_shm_surface_finish (void *abstract_surface) { cairo_xlib_shm_surface_t *shm = abstract_surface; cairo_xlib_display_t *display; cairo_status_t status; if (shm->image.base.damage) { _cairo_damage_destroy (shm->image.base.damage); shm->image.base.damage = _cairo_damage_create_in_error (CAIRO_STATUS_SURFACE_FINISHED); } status = _cairo_xlib_display_acquire (shm->image.base.device, &display); if (unlikely (status)) return status; if (shm->pixmap) XFreePixmap (display->display, shm->pixmap); if (active (shm, display->display)) { shm->info->last_request = shm->active; _pqueue_push (&display->shm->info, shm->info); if (seqno_before (display->shm->last_request, shm->active)) display->shm->last_request = shm->active; } else { _cairo_mempool_free (&shm->info->pool->mem, shm->info->mem); free (shm->info); _cairo_xlib_shm_pool_cleanup (display); } cairo_list_del (&shm->link); cairo_device_release (&display->base); return _cairo_image_surface_finish (abstract_surface); } static const cairo_surface_backend_t cairo_xlib_shm_surface_backend = { CAIRO_SURFACE_TYPE_IMAGE, _cairo_xlib_shm_surface_finish, _cairo_default_context_create, _cairo_image_surface_create_similar, NULL, /* create similar image */ _cairo_image_surface_map_to_image, _cairo_image_surface_unmap_image, _cairo_image_surface_source, _cairo_image_surface_acquire_source_image, _cairo_image_surface_release_source_image, _cairo_image_surface_snapshot, NULL, /* copy_page */ NULL, /* show_page */ _cairo_image_surface_get_extents, _cairo_image_surface_get_font_options, _cairo_xlib_shm_surface_flush, NULL, _cairo_image_surface_paint, _cairo_image_surface_mask, _cairo_image_surface_stroke, _cairo_image_surface_fill, NULL, /* fill-stroke */ _cairo_image_surface_glyphs, }; static cairo_bool_t has_shm (cairo_xlib_surface_t *surface) { cairo_xlib_display_t *display = (cairo_xlib_display_t *)surface->base.device; return display->shm != NULL; } static int has_shm_pixmaps (cairo_xlib_surface_t *surface) { cairo_xlib_display_t *display = (cairo_xlib_display_t *)surface->base.device; if (!display->shm) return 0; return display->shm->has_pixmaps; } static cairo_xlib_shm_surface_t * _cairo_xlib_shm_surface_create (cairo_xlib_surface_t *other, pixman_format_code_t format, int width, int height, cairo_bool_t will_sync, int create_pixmap) { cairo_xlib_shm_surface_t *shm; cairo_xlib_display_t *display; pixman_image_t *image; int stride, size; stride = CAIRO_STRIDE_FOR_WIDTH_BPP (width, PIXMAN_FORMAT_BPP(format)); size = stride * height; if (size < MIN_SIZE) return NULL; shm = malloc (sizeof (*shm)); if (unlikely (shm == NULL)) return (cairo_xlib_shm_surface_t *)_cairo_surface_create_in_error (CAIRO_STATUS_NO_MEMORY); _cairo_surface_init (&shm->image.base, &cairo_xlib_shm_surface_backend, other->base.device, _cairo_content_from_pixman_format (format), FALSE); /* is_vector */ if (_cairo_xlib_display_acquire (other->base.device, &display)) goto cleanup_shm; shm->info = _cairo_xlib_shm_info_create (display, size, will_sync); if (shm->info == NULL) goto cleanup_display; image = pixman_image_create_bits (format, width, height, (uint32_t *) shm->info->mem, stride); if (image == NULL) goto cleanup_info; _cairo_image_surface_init (&shm->image, image, format); shm->pixmap = 0; if (create_pixmap && size >= create_pixmap) { shm->pixmap = XShmCreatePixmap (display->display, other->drawable, shm->info->mem, &shm->info->pool->shm, shm->image.width, shm->image.height, shm->image.depth); } shm->active = shm->info->last_request; shm->idle = -5; assert (shm->active == 0 || will_sync); cairo_list_add (&shm->link, &display->shm->surfaces); cairo_device_release (&display->base); return shm; cleanup_info: _cairo_mempool_free (&shm->info->pool->mem, shm->info->mem); free(shm->info); cleanup_display: cairo_device_release (&display->base); cleanup_shm: free (shm); return NULL; } static void _cairo_xlib_surface_update_shm (cairo_xlib_surface_t *surface) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *)surface->shm; cairo_xlib_display_t *display; cairo_damage_t *damage; GC gc; damage = _cairo_damage_reduce (surface->base.damage); surface->base.damage = _cairo_damage_create(); if (_cairo_xlib_display_acquire (surface->base.device, &display)) goto cleanup_damage; if (_cairo_xlib_surface_get_gc (display, surface, &gc)) goto cleanup_display; if (! surface->owns_pixmap) { XGCValues gcv; gcv.subwindow_mode = IncludeInferiors; XChangeGC (display->display, gc, GCSubwindowMode, &gcv); } if (damage->region) { XRectangle stack_rects[CAIRO_STACK_ARRAY_LENGTH (XRectangle)]; XRectangle *rects = stack_rects; cairo_rectangle_int_t r; int n_rects, i; n_rects = cairo_region_num_rectangles (damage->region); if (n_rects == 0) { } else if (n_rects == 1) { cairo_region_get_rectangle (damage->region, 0, &r); XCopyArea (display->display, surface->drawable, shm->pixmap, gc, r.x, r.y, r.width, r.height, r.x, r.y); } else { if (n_rects > ARRAY_LENGTH (stack_rects)) { rects = _cairo_malloc_ab (n_rects, sizeof (XRectangle)); if (unlikely (rects == NULL)) { rects = stack_rects; n_rects = ARRAY_LENGTH (stack_rects); } } for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (damage->region, i, &r); rects[i].x = r.x; rects[i].y = r.y; rects[i].width = r.width; rects[i].height = r.height; } XSetClipRectangles (display->display, gc, 0, 0, rects, i, YXBanded); XCopyArea (display->display, surface->drawable, shm->pixmap, gc, 0, 0, shm->image.width, shm->image.height, 0, 0); if (damage->status == CAIRO_STATUS_SUCCESS && damage->region) XSetClipMask (display->display, gc, None); } } else { XCopyArea (display->display, surface->drawable, shm->pixmap, gc, 0, 0, shm->image.width, shm->image.height, 0, 0); } if (! surface->owns_pixmap) { XGCValues gcv; gcv.subwindow_mode = ClipByChildren; XChangeGC (display->display, gc, GCSubwindowMode, &gcv); } _cairo_xlib_display_sync (display); shm->active = 0; shm->idle--; _cairo_xlib_surface_put_gc (display, surface, gc); cleanup_display: cairo_device_release (&display->base); cleanup_damage: _cairo_damage_destroy (damage); } static void _cairo_xlib_surface_clear_shm (cairo_xlib_surface_t *surface) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *)surface->shm; assert (shm->active == 0); _cairo_damage_destroy (surface->base.damage); surface->base.damage = _cairo_damage_create(); memset (shm->image.data, 0, shm->image.stride * shm->image.height); shm->image.base.is_clear = TRUE; } static void inc_idle (cairo_surface_t *surface) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *)surface; shm->idle++; } static void dec_idle (cairo_surface_t *surface) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *)surface; shm->idle--; } cairo_surface_t * _cairo_xlib_surface_get_shm (cairo_xlib_surface_t *surface, cairo_bool_t overwrite) { if (surface->fallback) { assert (surface->base.damage); assert (surface->shm); assert (surface->shm->damage); goto done; } if (surface->shm == NULL) { pixman_format_code_t pixman_format; cairo_bool_t will_sync; if (! has_shm_pixmaps (surface)) return NULL; if ((surface->width | surface->height) < 32) return NULL; pixman_format = _pixman_format_for_xlib_surface (surface); if (pixman_format == 0) return NULL; will_sync = !surface->base.is_clear && !overwrite; surface->shm = &_cairo_xlib_shm_surface_create (surface, pixman_format, surface->width, surface->height, will_sync, 1)->image.base; if (surface->shm == NULL) return NULL; assert (surface->base.damage == NULL); if (surface->base.serial || !surface->owns_pixmap) { cairo_rectangle_int_t rect; rect.x = rect.y = 0; rect.width = surface->width; rect.height = surface->height; surface->base.damage = _cairo_damage_add_rectangle (NULL, &rect); } else surface->base.damage = _cairo_damage_create (); surface->shm->damage = _cairo_damage_create (); } if (overwrite) { _cairo_damage_destroy (surface->base.damage); surface->base.damage = _cairo_damage_create (); } if (!surface->base.is_clear && surface->base.damage->dirty) _cairo_xlib_surface_update_shm (surface); _cairo_xlib_shm_surface_flush (surface->shm, 1); if (surface->base.is_clear && surface->base.damage->dirty) _cairo_xlib_surface_clear_shm (surface); done: dec_idle(surface->shm); return surface->shm; } cairo_int_status_t _cairo_xlib_surface_put_shm (cairo_xlib_surface_t *surface) { cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS; if (!surface->fallback) { if (surface->shm) inc_idle (surface->shm); return CAIRO_INT_STATUS_SUCCESS; } if (surface->shm->damage->dirty) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *) surface->shm; cairo_xlib_display_t *display; cairo_damage_t *damage; GC gc; status = _cairo_xlib_display_acquire (surface->base.device, &display); if (unlikely (status)) return status; damage = _cairo_damage_reduce (shm->image.base.damage); shm->image.base.damage = _cairo_damage_create (); TRACE ((stderr, "%s: flushing damage x %d\n", __FUNCTION__, damage->region ? cairo_region_num_rectangles (damage->region) : 0)); if (damage->status == CAIRO_STATUS_SUCCESS && damage->region) { XRectangle stack_rects[CAIRO_STACK_ARRAY_LENGTH (XRectangle)]; XRectangle *rects = stack_rects; cairo_rectangle_int_t r; int n_rects, i; n_rects = cairo_region_num_rectangles (damage->region); if (n_rects == 0) goto out; status = _cairo_xlib_surface_get_gc (display, surface, &gc); if (unlikely (status)) goto out; if (n_rects == 1) { cairo_region_get_rectangle (damage->region, 0, &r); _cairo_xlib_shm_surface_mark_active (surface->shm); XCopyArea (display->display, shm->pixmap, surface->drawable, gc, r.x, r.y, r.width, r.height, r.x, r.y); } else { if (n_rects > ARRAY_LENGTH (stack_rects)) { rects = _cairo_malloc_ab (n_rects, sizeof (XRectangle)); if (unlikely (rects == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); _cairo_xlib_surface_put_gc (display, surface, gc); goto out; } } for (i = 0; i < n_rects; i++) { cairo_region_get_rectangle (damage->region, i, &r); rects[i].x = r.x; rects[i].y = r.y; rects[i].width = r.width; rects[i].height = r.height; } XSetClipRectangles (display->display, gc, 0, 0, rects, i, YXBanded); _cairo_xlib_shm_surface_mark_active (surface->shm); XCopyArea (display->display, shm->pixmap, surface->drawable, gc, 0, 0, shm->image.width, shm->image.height, 0, 0); if (damage->status == CAIRO_STATUS_SUCCESS && damage->region) XSetClipMask (display->display, gc, None); } _cairo_xlib_surface_put_gc (display, surface, gc); } out: _cairo_damage_destroy (damage); cairo_device_release (&display->base); } return status; } cairo_surface_t * _cairo_xlib_surface_create_shm (cairo_xlib_surface_t *other, pixman_format_code_t format, int width, int height) { cairo_surface_t *surface; surface = NULL; if (has_shm (other)) surface = &_cairo_xlib_shm_surface_create (other, format, width, height, FALSE, has_shm_pixmaps (other))->image.base; return surface; } cairo_surface_t * _cairo_xlib_surface_create_shm__image (cairo_xlib_surface_t *surface, pixman_format_code_t format, int width, int height) { if (! has_shm(surface)) return NULL; return &_cairo_xlib_shm_surface_create (surface, format, width, height, FALSE, 0)->image.base; } cairo_surface_t * _cairo_xlib_surface_create_similar_shm (void *other, cairo_format_t format, int width, int height) { cairo_surface_t *surface; surface = _cairo_xlib_surface_create_shm (other, _cairo_format_to_pixman_format_code (format), width, height); if (surface) { if (! surface->is_clear) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *) surface; assert (shm->active == 0); memset (shm->image.data, 0, shm->image.stride * shm->image.height); shm->image.base.is_clear = TRUE; } } else surface = cairo_image_surface_create (format, width, height); return surface; } void _cairo_xlib_shm_surface_mark_active (cairo_surface_t *_shm) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *) _shm; cairo_xlib_display_t *display = (cairo_xlib_display_t *) _shm->device; shm->active = XNextRequest (display->display); } void _cairo_xlib_shm_surface_get_ximage (cairo_surface_t *surface, XImage *ximage) { cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *) surface; int native_byte_order = _cairo_is_little_endian () ? LSBFirst : MSBFirst; cairo_format_masks_t image_masks; int ret; ret = _pixman_format_to_masks (shm->image.pixman_format, &image_masks); assert (ret); ximage->width = shm->image.width; ximage->height = shm->image.height; ximage->format = ZPixmap; ximage->data = (char *) shm->image.data; ximage->obdata = (char *)&shm->info->pool->shm; ximage->byte_order = native_byte_order; ximage->bitmap_unit = 32; /* always for libpixman */ ximage->bitmap_bit_order = native_byte_order; ximage->bitmap_pad = 32; /* always for libpixman */ ximage->depth = shm->image.depth; ximage->bytes_per_line = shm->image.stride; ximage->bits_per_pixel = image_masks.bpp; ximage->red_mask = image_masks.red_mask; ximage->green_mask = image_masks.green_mask; ximage->blue_mask = image_masks.blue_mask; ximage->xoffset = 0; ret = XInitImage (ximage); assert (ret != 0); } void * _cairo_xlib_shm_surface_get_obdata (cairo_surface_t *surface) { cairo_xlib_display_t *display = (cairo_xlib_display_t *) surface->device; cairo_xlib_shm_surface_t *shm = (cairo_xlib_shm_surface_t *) surface; display->shm->last_event = shm->active = XNextRequest (display->display); return &shm->info->pool->shm; } Pixmap _cairo_xlib_shm_surface_get_pixmap (cairo_surface_t *surface) { cairo_xlib_shm_surface_t *shm; shm = (cairo_xlib_shm_surface_t *) surface; return shm->pixmap; } XRenderPictFormat * _cairo_xlib_shm_surface_get_xrender_format (cairo_surface_t *surface) { cairo_xlib_shm_surface_t *shm; shm = (cairo_xlib_shm_surface_t *) surface; if (shm->image.format != CAIRO_FORMAT_INVALID) return _cairo_xlib_display_get_xrender_format ((cairo_xlib_display_t *)surface->device, shm->image.format); return _cairo_xlib_display_get_xrender_format_for_pixman((cairo_xlib_display_t *)surface->device, shm->image.pixman_format); } cairo_bool_t _cairo_xlib_shm_surface_is_active (cairo_surface_t *surface) { cairo_xlib_shm_surface_t *shm; shm = (cairo_xlib_shm_surface_t *) surface; if (shm->active == 0) return FALSE; if (seqno_passed (shm->active, peek_processed (shm->image.base.device))) { shm->active = 0; return FALSE; } return TRUE; } cairo_bool_t _cairo_xlib_shm_surface_is_idle (cairo_surface_t *surface) { cairo_xlib_shm_surface_t *shm; shm = (cairo_xlib_shm_surface_t *) surface; return shm->idle > 0; } #define XORG_VERSION_ENCODE(major,minor,patch,snap) \ (((major) * 10000000) + ((minor) * 100000) + ((patch) * 1000) + snap) static cairo_bool_t has_broken_send_shm_event (cairo_xlib_display_t *display, cairo_xlib_shm_display_t *shm) { Display *dpy = display->display; int (*old_handler) (Display *display, XErrorEvent *event); XShmCompletionEvent ev; XShmSegmentInfo info; info.shmid = shmget (IPC_PRIVATE, 0x1000, IPC_CREAT | 0600); if (info.shmid == -1) return TRUE; info.readOnly = FALSE; info.shmaddr = shmat (info.shmid, NULL, 0); if (info.shmaddr == (char *) -1) { shmctl (info.shmid, IPC_RMID, NULL); return TRUE; } ev.type = shm->event; ev.send_event = 1; ev.serial = 1; ev.drawable = shm->window; ev.major_code = shm->opcode; ev.minor_code = X_ShmPutImage; ev.shmseg = info.shmid; ev.offset = 0; assert (CAIRO_MUTEX_IS_LOCKED (_cairo_xlib_display_mutex)); _x_error_occurred = FALSE; XLockDisplay (dpy); XSync (dpy, False); old_handler = XSetErrorHandler (_check_error_handler); XShmAttach (dpy, &info); XSendEvent (dpy, ev.drawable, False, 0, (XEvent *)&ev); XShmDetach (dpy, &info); XSync (dpy, False); XSetErrorHandler (old_handler); XUnlockDisplay (dpy); shmctl (info.shmid, IPC_RMID, NULL); shmdt (info.shmaddr); return _x_error_occurred; } static cairo_bool_t xorg_has_buggy_send_shm_completion_event(cairo_xlib_display_t *display, cairo_xlib_shm_display_t *shm) { Display *dpy = display->display; /* As libXext sets the SEND_EVENT bit in the ShmCompletionEvent, * the Xserver may crash if it does not take care when processing * the event type. For instance versions of Xorg prior to 1.11.1 * exhibited this bug, and was fixed by: * * commit 2d2dce558d24eeea0eb011ec9ebaa6c5c2273c39 * Author: Sam Spilsbury * Date: Wed Sep 14 09:58:34 2011 +0800 * * Remove the SendEvent bit (0x80) before doing range checks on event type. */ if (_cairo_xlib_vendor_is_xorg (dpy) && VendorRelease (dpy) < XORG_VERSION_ENCODE(1,11,0,1)) return TRUE; /* For everyone else check that no error is generated */ return has_broken_send_shm_event (display, shm); } void _cairo_xlib_display_init_shm (cairo_xlib_display_t *display) { cairo_xlib_shm_display_t *shm; XSetWindowAttributes attr; XExtCodes *codes; int has_pixmap, scr; display->shm = NULL; if (!can_use_shm (display->display, &has_pixmap)) return; shm = malloc (sizeof (*shm)); if (unlikely (shm == NULL)) return; codes = XInitExtension (display->display, SHMNAME); if (codes == NULL) { free (shm); return; } shm->opcode = codes ->major_opcode; shm->event = codes->first_event; if (unlikely (_pqueue_init (&shm->info))) { free (shm); return; } scr = DefaultScreen (display->display); attr.override_redirect = 1; shm->window = XCreateWindow (display->display, DefaultRootWindow (display->display), -1, -1, 1, 1, 0, DefaultDepth (display->display, scr), InputOutput, DefaultVisual (display->display, scr), CWOverrideRedirect, &attr); shm->last_event = 0; shm->last_request = 0; if (xorg_has_buggy_send_shm_completion_event(display, shm)) has_pixmap = 0; shm->has_pixmaps = has_pixmap ? MIN_PIXMAP_SIZE : 0; cairo_list_init (&shm->pool); cairo_list_init (&shm->surfaces); display->shm = shm; } void _cairo_xlib_display_fini_shm (cairo_xlib_display_t *display) { cairo_xlib_shm_display_t *shm = display->shm; if (shm == NULL) return; while (!cairo_list_is_empty (&shm->surfaces)) cairo_surface_finish (&cairo_list_first_entry (&shm->surfaces, cairo_xlib_shm_surface_t, link)->image.base); _pqueue_fini (&shm->info); while (!cairo_list_is_empty (&shm->pool)) { cairo_xlib_shm_t *pool; pool = cairo_list_first_entry (&shm->pool, cairo_xlib_shm_t, link); _cairo_xlib_display_shm_pool_destroy (display, pool); } if (display->display) XDestroyWindow (display->display, shm->window); free (shm); display->shm = NULL; } #endif #endif