[cairo] API proposal: Damage Tracking

Behdad Esfahbod behdad at behdad.org
Sat Dec 13 17:25:40 PST 2008


Soeren Sandmann wrote:
> Behdad Esfahbod <behdad at behdad.org> writes:
> 
>> In cairo, may be better to be able to turn each on/off.  So
>> you can do all of:
>>
>>   - Draw to a local buffer, get update region and push that to your
>> remote.  It can be used internally to improve xlib fallback backend as
>> well as directfb kind of backends.  We were talking about this recently.
>>
>>   - Don't draw, just compute bounds.  Right now for this kind of needs
>> you have to create a similar surface and draw to it.  You have to use a
>> similar surface to get the same font options, etc.  Though most of the
>> times when you need the bounds you need it to create a surface of the
>> right size.  So I'm not sure how useful this will be.
>>
>>   - No draw, just get update region for purposes like yours?
> 
> Another thing we may want to track:
> 
>     - Keep track of "solid" areas that are guaranteed to be completely
>       covered with an alpha=1 source, and therefore obscure the
>       underlying pixels.

This one is more delicate, but sure, has its uses.  But then wouldn't you also
need another request for "the rest"?  Or you want to compute the rest using
region calculus?

[...]
> It seems that the things you might want to track are all orthogonal:
> 
>     BOUNDING_BOX   /* Track bounding box */
>     UDPATES        /* Track region covering updates */
>     HIT_MASK       /* Track input region */
>     SOLID          /* Track solid painting */

How would we do hit masking?  Based on alpha?  May make sense to add an api
like what Keith added to Xrender, that takes a min/max alpha that is
considered hit.

> We wouldn't have to implement all of these initially, but it probably
> makes sense to make the interface extensible.

Indeed.

> At the summit, Carl suggested that we could make cairo_region_t
> public, then have API like this:
> 
>     cairo_tracking_id_t 
>     cairo_begin_tracking (cairo_t *cr,
>                           tracking_type_t type,
>                           cairo_region_t *region);
> 
>     void
>     cairo_end_tracking (cairo_tracking_id_t id);
> 
> Allowing the user to pass in the region solves both the hierarchy
> problem

What's the hierarchy problem?

> and allows the application to keep one update region around
> and have it updated several times.

Not sure what that would be used for.

> This is similar in spirit to the X11 Damage extension.

I suggest something like this instead:


cairo_tracking_t *
cairo_tracking_create ();

void
cairo_tracking_reference (cairo_tracking_t *tracking);

void
cairo_tracking_destroy (cairo_tracking_t *tracking);

void
cairo_tracking_reset (cairo_tracking_t *tracking);

void
cairo_tracking_extents (cairo_tracking_t *tracking,
                        double *x1, double *y1,
                        double *x2, double *y2);

cairo_rectangle_list_t *
cairo_tracking_copy_rectangle_list (cairo_tracking_t *tracking)


/* maybe add x_offset,y_offset here? */
void
cairo_surface_subscribe (cairo_surface_t       *surface,
                         cairo_tracking_t      *tracking
			 cairo_tracking_type_t  what);

void
cairo_surface_unsubscribe (cairo_surface_t  *surface,
                           cairo_tracking_t *tracking);

This is essentially exactly what you propose, with cairo_tracking_t being the
opaque cairo_region_t.  I like how this way of doing it obviates the need for
a separate _BOUNDING_BOX mode, as that's simply _extents on UPDATES.  However,
it will be wasteful as it will keep track of the full region internally.  One
day we may add _copy_path() too.  Maybe we can change the tracker constructor
to tell it what kind of formats we're interested in.  Options can be:

  bounding-box
  rectangle-list
  path
  surface A1
  surface A8
  best

If you choose path, you can later copy_path and get the path for all
operations.  This will do what _cairo_meta_surface_get_path does now.  If you
set surface-A8, you can later copy_surface and get a A8 mask of all the
updates, etc.  If you use best, it will automatically use the most compact
format needed to fully represent the update areas, like what the clipping code
does currently (path clipping, mask clipping, ...).

You can also subscribe one tracker to multiple surfaces.  Not sure how useful
that would be.

Now, better names help.

> A separate 
> 
>     cairo_set_updates (cairo_t *cr, cairo_bool_t updates); 
> 
> would be required so that you could get the regions without actually
> painting anything.

Right, With the getter.  Humm, this then has to be on the surface too, right?
   *BUT*, I think this should work more like a stack.  Such that:

  disable_updates();
  disable_updates();
  enable_updates();
  /* updates are still disabled here */
  enable_updates();

It doesn't have to be like that: my library code for example can query the
current updates stat, set it, then set it back to what it was before.  But it
would be easier to get it right if it's a stack.  On second thought, it has to
be a stack.  If I disable it and call your _render, your could must not be
able to enable it again unless it does something wrong (pop something it
didn't push).  Donno.

Though, I'm not sure how useful the nested use case will be, unless we make it
better integrate with the tracker stuff.

Consider this example: you have a canvas implementation, and I'm implementing
a thumbnailing canvas item.  The thumbnailing canvas item takes another item
(which can be a hierarchy of multiple items itself), measures it, then sets a
cairo_scale() and draws it as a 100x100 box.

Then, your canvas disables updates to get hit tracking bounds/mask/etc, then
calls into my item's _draw.  My _draw method itself then disables updates and
calls into _draw method of the item it's thumbnailing, to get its bounding box
such that it can compute the scale factor, then scales, enables updates, and
calls the other item's _draw.

Now, in the above situation, the hit-tracker that your canvas installs should
not see the drawing operations performed by the other item, that I was using
for bounding box calculation.

Not sure how to fix this.  Maybe when we disable updates, any tracker
installed before this point should not receive events anymore, until the
matching enable.



> A couple of questions:
> 
>     - Does this approach makes sense?

I like where we're heading to.  How about my suggestions, make sense?

>     - What coordinate system do you get updates in? For my purposes
>       device coordinates are the most useful since that's the space I
>       get input and expose events in.

Humm, I'd say device space is fine.  We can later add cairo_tracker_set_matrix().

>     - Currently cairo_region_t is a user allocated structure that is
>       initialized with cairo_region_init(). It almost certainly makes
>       sense to make at least the exposed version a malloc()ed object
>       since we don't want to expose the pixman_region_t, let alone set
>       that representation in stone.

No more public structs please.

> I do want this feature in 1.10, so any feedback is much appreciated.
> 
> 
> Thanks,
> Soren


behdad



More information about the cairo mailing list