summaryrefslogtreecommitdiff
path: root/src/cairo-xcb-shm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cairo-xcb-shm.c')
-rw-r--r--src/cairo-xcb-shm.c576
1 files changed, 576 insertions, 0 deletions
diff --git a/src/cairo-xcb-shm.c b/src/cairo-xcb-shm.c
new file mode 100644
index 00000000..7a47f338
--- /dev/null
+++ b/src/cairo-xcb-shm.c
@@ -0,0 +1,576 @@
+/* Cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2007 Chris Wilson
+ * Copyright © 2009 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Red Hat, Inc.
+ *
+ * Contributors(s):
+ * Chris Wilson <chris@chris-wilson.co.uk>
+ */
+
+#include "cairoint.h"
+
+#include "cairo-xcb-private.h"
+
+#include <xcb/shm.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <errno.h>
+
+/* a simple buddy allocator for memory pools
+ * XXX fragmentation? use Doug Lea's malloc?
+ */
+
+typedef struct _cairo_xcb_shm_mem_block cairo_xcb_shm_mem_block_t;
+
+struct _cairo_xcb_shm_mem_block {
+ unsigned int bits;
+ cairo_list_t link;
+};
+
+struct _cairo_xcb_shm_mem_pool {
+ int shmid;
+ uint32_t shmseg;
+
+ char *base;
+ unsigned int nBlocks;
+ cairo_xcb_shm_mem_block_t *blocks;
+ cairo_list_t free[32];
+ unsigned char *map;
+
+ unsigned int min_bits; /* Minimum block size is 1 << min_bits */
+ unsigned int num_sizes;
+
+ size_t free_bytes;
+ size_t max_bytes;
+ unsigned int max_free_bits;
+
+ cairo_list_t link;
+};
+
+#define BITTEST(p, n) ((p)->map[(n) >> 3] & (128 >> ((n) & 7)))
+#define BITSET(p, n) ((p)->map[(n) >> 3] |= (128 >> ((n) & 7)))
+#define BITCLEAR(p, n) ((p)->map[(n) >> 3] &= ~(128 >> ((n) & 7)))
+
+static void
+clear_bits (cairo_xcb_shm_mem_pool_t *pi, size_t first, size_t last)
+{
+ size_t i, n = last;
+ size_t first_full = (first + 7) & ~7;
+ size_t past_full = last & ~7;
+ size_t bytes;
+
+ if (n > first_full)
+ n = first_full;
+ for (i = first; i < n; i++)
+ BITCLEAR (pi, i);
+
+ if (past_full > first_full) {
+ bytes = past_full - first_full;
+ bytes = bytes >> 3;
+ memset (pi->map + (first_full >> 3), 0, bytes);
+ }
+
+ if (past_full < n)
+ past_full = n;
+ for (i = past_full; i < last; i++)
+ BITCLEAR (pi, i);
+}
+
+static void
+free_bits (cairo_xcb_shm_mem_pool_t *pi,
+ size_t start,
+ unsigned int bits,
+ cairo_bool_t clear)
+{
+ cairo_xcb_shm_mem_block_t *block;
+
+ if (clear)
+ clear_bits (pi, start, start + (1 << bits));
+
+ block = pi->blocks + start;
+ block->bits = bits;
+
+ cairo_list_add (&block->link, &pi->free[bits]);
+
+ pi->free_bytes += 1 << (bits + pi->min_bits);
+ if (bits > pi->max_free_bits)
+ pi->max_free_bits = bits;
+}
+
+/* Add a chunk to the free list */
+static void
+free_blocks (cairo_xcb_shm_mem_pool_t *pi,
+ size_t first,
+ size_t last,
+ cairo_bool_t clear)
+{
+ size_t i;
+ size_t bits = 0;
+ size_t len = 1;
+
+ i = first;
+ while (i < last) {
+ /* To avoid cost quadratic in the number of different
+ * blocks produced from this chunk of store, we have to
+ * use the size of the previous block produced from this
+ * chunk as the starting point to work out the size of the
+ * next block we can produce. If you look at the binary
+ * representation of the starting points of the blocks
+ * produced, you can see that you first of all increase the
+ * size of the blocks produced up to some maximum as the
+ * address dealt with gets offsets added on which zap out
+ * low order bits, then decrease as the low order bits of the
+ * final block produced get added in. E.g. as you go from
+ * 001 to 0111 you generate blocks
+ * of size 001 at 001 taking you to 010
+ * of size 010 at 010 taking you to 100
+ * of size 010 at 100 taking you to 110
+ * of size 001 at 110 taking you to 111
+ * So the maximum total cost of the loops below this comment
+ * is one trip from the lowest blocksize to the highest and
+ * back again.
+ */
+ while (bits < pi->num_sizes - 1) {
+ size_t next_bits = bits + 1;
+ size_t next_len = len << 1;
+
+ if (i + next_bits > last) {
+ /* off end of chunk to be freed */
+ break;
+ }
+
+ if (i & (next_len - 1)) /* block would not be on boundary */
+ break;
+
+ bits = next_bits;
+ len = next_len;
+ }
+
+ do {
+ if (i + len > last) /* off end of chunk to be freed */
+ continue;
+
+ if (i & (len - 1)) /* block would not be on boundary */
+ continue;
+
+ /* OK */
+ break;
+
+ bits--;
+ len >>=1;
+ } while (len > 0);
+
+ if (len == 0)
+ break;
+
+ free_bits (pi, i, bits, clear);
+ i += len;
+ }
+}
+
+static cairo_status_t
+_cairo_xcb_shm_mem_pool_init (cairo_xcb_shm_mem_pool_t *pi,
+ size_t bytes,
+ unsigned int min_bits,
+ unsigned int num_sizes)
+{
+ size_t setBits;
+ int i;
+
+ assert ((((unsigned long) pi->base) & ((1 << min_bits) - 1)) == 0);
+ assert (num_sizes < ARRAY_LENGTH (pi->free));
+
+ pi->free_bytes = 0;
+ pi->max_bytes = bytes;
+ pi->max_free_bits = 0;
+
+ setBits = bytes >> min_bits;
+ pi->blocks = calloc (setBits, sizeof (cairo_xcb_shm_mem_block_t));
+ if (pi->blocks == NULL)
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ pi->nBlocks = setBits;
+ pi->min_bits = min_bits;
+ pi->num_sizes = num_sizes;
+
+ for (i = 0; i < ARRAY_LENGTH (pi->free); i++)
+ cairo_list_init (&pi->free[i]);
+
+ pi->map = malloc ((setBits + 7) >> 3);
+ if (pi->map == NULL) {
+ free (pi->blocks);
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ memset (pi->map, -1, (setBits + 7) >> 3);
+ clear_bits (pi, 0, setBits);
+
+ /* Now add all blocks to the free list */
+ free_blocks (pi, 0, setBits, 1);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_xcb_shm_mem_block_t *
+get_buddy (cairo_xcb_shm_mem_pool_t *pi,
+ size_t offset,
+ unsigned int bits)
+{
+ cairo_xcb_shm_mem_block_t *block;
+
+ assert (offset + (1 << bits) <= pi->nBlocks);
+
+ if (BITTEST (pi, offset + (1 << bits) - 1))
+ return NULL; /* buddy is allocated */
+
+ block = pi->blocks + offset;
+ if (block->bits != bits)
+ return NULL; /* buddy is partially allocated */
+
+ return block;
+}
+
+static void
+merge_buddies (cairo_xcb_shm_mem_pool_t *pi,
+ cairo_xcb_shm_mem_block_t *block,
+ unsigned int max_bits)
+{
+ size_t block_offset = block_offset = block - pi->blocks;
+ unsigned int bits = block->bits;
+
+ while (bits < max_bits - 1) {
+ /* while you can, merge two blocks and get a legal block size */
+ size_t buddy_offset = block_offset ^ (1 << bits);
+
+ block = get_buddy (pi, buddy_offset, bits);
+ if (block == NULL)
+ break;
+
+ cairo_list_del (&block->link);
+
+ /* Merged block starts at buddy */
+ if (buddy_offset < block_offset)
+ block_offset = buddy_offset;
+
+ bits++;
+ }
+
+ block = pi->blocks + block_offset;
+ block->bits = bits;
+ cairo_list_add (&block->link, &pi->free[bits]);
+
+ if (bits > pi->max_free_bits)
+ pi->max_free_bits = bits;
+}
+
+/* attempt to merge all available buddies up to a particular size */
+static unsigned int
+merge_bits (cairo_xcb_shm_mem_pool_t *pi,
+ unsigned int max_bits)
+{
+ cairo_xcb_shm_mem_block_t *block, *buddy, *next;
+ unsigned int bits;
+
+ for (bits = 0; bits < max_bits - 1; bits++) {
+ cairo_list_foreach_entry_safe (block, next,
+ cairo_xcb_shm_mem_block_t,
+ &pi->free[bits],
+ link)
+ {
+ size_t buddy_offset = (block - pi->blocks) ^ (1 << bits);
+
+ buddy = get_buddy (pi, buddy_offset, bits);
+ if (buddy == NULL)
+ continue;
+
+ if (buddy == next) {
+ next = cairo_container_of (buddy->link.next,
+ cairo_xcb_shm_mem_block_t,
+ link);
+ }
+
+ cairo_list_del (&block->link);
+ merge_buddies (pi, block, max_bits);
+ }
+ }
+
+ return pi->max_free_bits;
+}
+
+/* find store for 1 << bits blocks */
+static void *
+buddy_malloc (cairo_xcb_shm_mem_pool_t *pi,
+ unsigned int bits)
+{
+ unsigned int b;
+ size_t offset;
+ size_t past;
+ cairo_xcb_shm_mem_block_t *block;
+
+ if (bits > pi->max_free_bits && bits > merge_bits (pi, bits))
+ return NULL;
+
+ /* Find a list with blocks big enough on it */
+ block = NULL;
+ for (b = bits; b <= pi->max_free_bits; b++) {
+ if (! cairo_list_is_empty (&pi->free[b])) {
+ block = cairo_list_first_entry (&pi->free[b],
+ cairo_xcb_shm_mem_block_t,
+ link);
+ break;
+ }
+ }
+ assert (block != NULL);
+
+ cairo_list_del (&block->link);
+
+ while (cairo_list_is_empty (&pi->free[pi->max_free_bits])) {
+ if (--pi->max_free_bits == 0)
+ break;
+ }
+
+ /* Mark end of allocated area */
+ offset = block - pi->blocks;
+ past = offset + (1 << bits);
+ BITSET (pi, past - 1);
+ block->bits = bits;
+
+ /* If we used a larger free block than we needed, free the rest */
+ pi->free_bytes -= 1 << (b + pi->min_bits);
+ free_blocks (pi, past, offset + (1 << b), 0);
+
+ return pi->base + ((block - pi->blocks) << pi->min_bits);
+}
+
+static void *
+_cairo_xcb_shm_mem_pool_malloc (cairo_xcb_shm_mem_pool_t *pi,
+ size_t bytes)
+{
+ unsigned int bits;
+ size_t size;
+
+ size = 1 << pi->min_bits;
+ for (bits = 0; size < bytes; bits++)
+ size <<= 1;
+ if (bits >= pi->num_sizes)
+ return NULL;
+
+ return buddy_malloc (pi, bits);
+}
+
+static void
+_cairo_xcb_shm_mem_pool_free (cairo_xcb_shm_mem_pool_t *pi,
+ char *storage)
+{
+ size_t block_offset;
+ cairo_xcb_shm_mem_block_t *block;
+
+ block_offset = (storage - pi->base) >> pi->min_bits;
+ block = pi->blocks + block_offset;
+
+ BITCLEAR (pi, block_offset + ((1 << block->bits) - 1));
+ pi->free_bytes += 1 << (block->bits + pi->min_bits);
+
+ merge_buddies (pi, block, pi->num_sizes);
+}
+
+static void
+_cairo_xcb_shm_mem_pool_destroy (cairo_xcb_shm_mem_pool_t *pool)
+{
+ shmdt (pool->base);
+ cairo_list_del (&pool->link);
+
+ free (pool->map);
+ free (pool->blocks);
+ free (pool);
+}
+
+cairo_int_status_t
+_cairo_xcb_connection_allocate_shm_info (cairo_xcb_connection_t *connection,
+ size_t size,
+ cairo_xcb_shm_info_t **shm_info_out)
+{
+ cairo_xcb_shm_info_t *shm_info;
+ cairo_xcb_shm_mem_pool_t *pool, *next;
+ size_t bytes, maxbits = 16, minbits = 8;
+ void *mem = NULL;
+ cairo_status_t status;
+
+ assert (connection->flags & CAIRO_XCB_HAS_SHM);
+
+ CAIRO_MUTEX_LOCK (connection->shm_mutex);
+ cairo_list_foreach_entry_safe (pool, next, cairo_xcb_shm_mem_pool_t,
+ &connection->shm_pools, link)
+ {
+ if (pool->free_bytes > size) {
+ mem = _cairo_xcb_shm_mem_pool_malloc (pool, size);
+ if (mem != NULL) {
+ /* keep the active pools towards the front */
+ cairo_list_move (&pool->link, &connection->shm_pools);
+ goto allocate_shm_info;
+ }
+ }
+ /* scan for old, unused pools */
+ if (pool->free_bytes == pool->max_bytes) {
+ _cairo_xcb_connection_shm_detach (connection,
+ pool->shmseg);
+ _cairo_xcb_shm_mem_pool_destroy (pool);
+ }
+ }
+
+ pool = malloc (sizeof (cairo_xcb_shm_mem_pool_t));
+ if (unlikely (pool == NULL)) {
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ bytes = 1 << maxbits;
+ while (bytes <= size)
+ bytes <<= 1, maxbits++;
+ bytes <<= 3;
+
+ do {
+ pool->shmid = shmget (IPC_PRIVATE, bytes, IPC_CREAT | 0600);
+ if (pool->shmid != -1)
+ break;
+
+ if (errno == EINVAL && bytes > size) {
+ bytes >>= 1;
+ continue;
+ }
+ } while (FALSE);
+ if (pool->shmid == -1) {
+ int err = errno;
+ if (! (err == EINVAL || err == ENOMEM))
+ connection->flags &= ~CAIRO_XCB_HAS_SHM;
+ free (pool);
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+ return CAIRO_INT_STATUS_UNSUPPORTED;
+ }
+
+ pool->base = shmat (pool->shmid, NULL, 0);
+ if (unlikely (pool->base == (char *) -1)) {
+ shmctl (pool->shmid, IPC_RMID, NULL);
+ free (pool);
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ status = _cairo_xcb_shm_mem_pool_init (pool,
+ bytes,
+ minbits,
+ maxbits - minbits + 1);
+ if (unlikely (status)) {
+ shmdt (pool->base);
+ free (pool);
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+ return status;
+ }
+
+ pool->shmseg = _cairo_xcb_connection_shm_attach (connection, pool->shmid, FALSE);
+ shmctl (pool->shmid, IPC_RMID, NULL);
+
+ cairo_list_add (&pool->link, &connection->shm_pools);
+ mem = _cairo_xcb_shm_mem_pool_malloc (pool, size);
+
+ allocate_shm_info:
+ shm_info = _cairo_freepool_alloc (&connection->shm_info_freelist);
+ if (unlikely (shm_info == NULL)) {
+ _cairo_xcb_shm_mem_pool_free (pool, mem);
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ }
+
+ shm_info->connection = connection;
+ shm_info->pool = pool;
+ shm_info->shm = pool->shmseg;
+ shm_info->offset = (char *) mem - (char *) pool->base;
+ shm_info->mem = mem;
+
+ /* scan for old, unused pools */
+ cairo_list_foreach_entry_safe (pool, next, cairo_xcb_shm_mem_pool_t,
+ &connection->shm_pools, link)
+ {
+ if (pool->free_bytes == pool->max_bytes) {
+ _cairo_xcb_connection_shm_detach (connection,
+ pool->shmseg);
+ _cairo_xcb_shm_mem_pool_destroy (pool);
+ }
+ }
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+
+ *shm_info_out = shm_info;
+ return CAIRO_STATUS_SUCCESS;
+}
+
+void
+_cairo_xcb_shm_info_destroy (cairo_xcb_shm_info_t *shm_info)
+{
+ cairo_xcb_connection_t *connection = shm_info->connection;
+
+ CAIRO_MUTEX_LOCK (connection->shm_mutex);
+
+ _cairo_xcb_shm_mem_pool_free (shm_info->pool, shm_info->mem);
+ _cairo_freepool_free (&connection->shm_info_freelist, shm_info);
+
+ /* scan for old, unused pools - hold at least one in reserve */
+ if (! cairo_list_is_singular (&connection->shm_pools) &&
+ _cairo_xcb_connection_take_socket (connection) == CAIRO_STATUS_SUCCESS)
+ {
+ cairo_xcb_shm_mem_pool_t *pool, *next;
+ cairo_list_t head;
+
+ cairo_list_init (&head);
+ cairo_list_move (connection->shm_pools.next, &head);
+
+ cairo_list_foreach_entry_safe (pool, next, cairo_xcb_shm_mem_pool_t,
+ &connection->shm_pools, link)
+ {
+ if (pool->free_bytes == pool->max_bytes) {
+ _cairo_xcb_connection_shm_detach (connection, pool->shmseg);
+ _cairo_xcb_shm_mem_pool_destroy (pool);
+ }
+ }
+
+ cairo_list_move (head.next, &connection->shm_pools);
+ }
+
+ CAIRO_MUTEX_UNLOCK (connection->shm_mutex);
+}
+
+void
+_cairo_xcb_connection_shm_mem_pools_fini (cairo_xcb_connection_t *connection)
+{
+ while (! cairo_list_is_empty (&connection->shm_pools)) {
+ _cairo_xcb_shm_mem_pool_destroy (cairo_list_first_entry (&connection->shm_pools,
+ cairo_xcb_shm_mem_pool_t,
+ link));
+ }
+}