[cairo] [RFC] replacing quartz image surface with generic cached representation concept

Paolo Bonzini bonzini at gnu.org
Tue Nov 25 03:22:38 PST 2008

The quartz image surface seems to me like a special case of a more
generic "cached representation" concept usable by other OS-specific
surface types (e.g. glitz).

I'll present the concept at the same time as the possible API.

cairo_bool_t cairo_image_surface_get_cacheable (cairo_surface_t surf);
void cairo_image_surface_set_cacheable (cairo_surface_t surf,
                                        cairo_bool_t cache);

   These two functions mark the image as cacheable.  Image surfaces
   whose data is only modified using cairo, or that are explicitly
   flushed after they're modified externally, can be cached.

   The cache is a set of (reconstruct-func, destroy-func, cached-value,
   up-to-date) tuples indexed by an integer key (usually another surface

   In the future, the cairo_bool_t might become an enumerator specifying
   different cache policies.

void *_cairo_image_surface_get_cache (cairo_surface_t surf,
                                      int key,
                                      cairo_cache_reconstruct_func r,
                                      cairo_cache_destroy_func d);

   This function returns the cached representation of SURF that is
   indexed by KEY.  If the image is not cacheable, NULL is returned.

   If there is a cached representation for KEY in SURF (either
   up-to-date or not), R and D are not used and instead the
   reconstruct-func and destroy-func in the cache are used.

   If there isn't an up-to-date cached representation corresponding to
   SURF, the reconstruct function is called immediately.  If the
   reconstruct function returns NULL, NULL is returned.

   Otherwise the tuple (reconstruct-func, destroy-func, RESULT, true) is
   stored in the cache.

void _cairo_image_surface_flush_cache (cairo_surface_t surf, int key);

   This function eliminates the cached representation of SURF that is
   indexed by KEY.

typedef void *(*cairo_cache_reconstruct_func) (void *old,
                                               cairo_bool_t needed,
                                            cairo_image_surface_t src);

   The reconstruct function takes an image surface and returns the
   value that _cairo_image_surface_get_cache will return.  In this
   case, it is called with NEEDED = true.  OLD is the previous cached
   value if there was a non-up-to-date cached representation, or NULL
   if there was none.

   Besides when _cairo_image_surface_get_cache is called, the
   reconstruct function is called also when the surface is flushed.
   In this case the NEEDED parameter will be false.  If NULL is
   returned, the cached representation will be marked as not up-to-date,
   but the result field will *not* be modified (i.e. OLD will be passed
   unchanged on the next call to the reconstruct function).  If a
   non-NULL value is returned, it replaces OLD in the cache and the
   representation is marked up-to-date.

   In either case, if NULL is returned but a non-up-to-date cached
   representation was there, it is not eliminated (this may change
   if different caching policies are implemented).  The reconstruct
   function can call _cairo_image_surface_flush_cache and return NULL
   if it wants to achieve that.

typedef void (*cairo_cache_destroy_func) (void *data);

   The destroy function is called by _cairo_image_surface_flush_cache
   and upon surface destruction to free resources occupied by the
   cached representation DATA.  It is never called with NULL data.


An example usecase is given as changes to the cairo-quartz-surface.c
file.  In _cairo_surface_to_cgimage, instead of special casing

        cairo_quartz_image_surface_t *surface =
           (cairo_quartz_image_surface_t *) source;
        *image_out = CGImageRetain (surface->image);
        return CAIRO_STATUS_SUCCESS;

you would special case CAIRO_SURFACE_TYPE_IMAGE:

    if (stype == CAIRO_SURFACE_TYPE_IMAGE) {
        CGImageRef image =
            _cairo_image_surface_get_cache (source,
                       (cairo_cache_destroy_func) CGImageRelease);
        if (image) {
            *image_out = CGImageRetain (image);
            return CAIRO_STATUS_SUCCESS;

And here is the reconstruct image callback, based on

    CGImageRef oldImage = old;
    CGImageRef newImage = NULL;
    if (!needed)
        return NULL;

    if (!_cairo_quartz_verify_surface_size(width, height))
        return NULL;

    if (width == 0 || height == 0)
        return NULL;

    if (format != CAIRO_FORMAT_ARGB32 && format != CAIRO_FORMAT_RGB24)
        return NULL;

    /* To be released by the ReleaseCallback */
    cairo_surface_reference (src);
    newImage = _cairo_quartz_create_cgimage
            (src->format, src->width, src->height, src->stride,
             src->data, TRUE, NULL, DataProviderReleaseCallback, src);

    /* Also calls the ReleaseCallback */
    CGImageRelease (oldImage);
    return newImage;

The cairo_quartz_image_surface constructors could (for backwards API
compatibility) just create an image surface and make it cacheable.


Thoughts?  Should I implement this?

More information about the cairo mailing list