[Spice-devel] [PATCH] RFC: Document display channel drawable trees, etc

Frediano Ziglio fziglio at redhat.com
Thu Jan 26 09:18:17 UTC 2017


> 
> ---
> I started looking into a bug related to surfaces and drawing, but after a
> little initial investigation, I realized that I didn't really understand any
> of
> the code in this part of spice server. I discussed it a little bit with
> several
> others who also didn't have a good understanding of this part of the code. So
> I
> decided it needed to be documented so that we can understand it and work on
> it
> effectively. I've spent several long days trying to understand the details
> and
> trying to document them as well as I can. There is a lot of confusing
> terminology, and there are things I still don't fully understand, but I
> wanted
> to send this out to get feedback from others. Is it helpful? Do you see any
> cases where I misinterpreted things? Can anybody solve any pieces of the
> puzzle
> that I didn't quite figure out? etc. etc.
> 
> I know it's rather dense and will probably require others to spend some
> serious
> time understanding the code, but I'd love to get some feedback on it.
> 

Take some time to find this post, so before get really buried on the ML...

Some part are quite good and should be split and merged, other,
particularly exclude_region have too much questions to solve.

>  server/display-channel-private.h |   5 +-
>  server/display-channel.c         | 256
>  +++++++++++++++++++++++++++++++++++++++
>  server/display-channel.h         |  10 +-
>  server/tree.c                    |  12 +-
>  server/tree.h                    |   6 +
>  5 files changed, 285 insertions(+), 4 deletions(-)
> 
> diff --git a/server/display-channel-private.h
> b/server/display-channel-private.h
> index 6e299b9..dc4d605 100644
> --- a/server/display-channel-private.h
> +++ b/server/display-channel-private.h
> @@ -32,7 +32,10 @@ struct DisplayChannelPrivate
>      int enable_jpeg;
>      int enable_zlib_glz_wrap;
>  
> -    Ring current_list; /* Drawable */
> +    /* A ring of pending drawables for this DisplayChannel, regardless of which
> +     * surface they're associated with. This list is mainly used to flush older
> +     * drawables when we need to make room for new drawables. */
> +    Ring current_list;

is this list containing ALL drawables allocated ?
Could be renamed to drawable_list ?

Wondering what these "current" are...

>      uint32_t current_size;
>  
>      uint32_t drawable_count;
> diff --git a/server/display-channel.c b/server/display-channel.c
> index 6069883..83e76c1 100644
> --- a/server/display-channel.c
> +++ b/server/display-channel.c
> @@ -393,6 +393,8 @@ static void current_add_drawable(DisplayChannel *display,
>      drawable->refs++;
>  }
>  
> +/* Unrefs the drawable and removes it from any rings that it's in, as well as
> + * removing any associated shadow item */
>  static void current_remove_drawable(DisplayChannel *display, Drawable *item)

display_channel_remove_drawable ?
display_channel_unlink_drawable ?

>  {
>      /* todo: move all to unref? */
> @@ -422,6 +424,7 @@ static void drawable_remove_from_pipes(Drawable
> *drawable)
>      }
>  }
>  
> +/* This function should never be called for Shadow TreeItems */
>  static void current_remove(DisplayChannel *display, TreeItem *item)
>  {
>      TreeItem *now = item;
> @@ -439,22 +442,38 @@ static void current_remove(DisplayChannel *display,
> TreeItem *item)
>          } else {
>              Container *now_as_container = CONTAINER(now);
>  
> +            /* This is a questionable assert. It relies several assumptions
> +             * to ensure that this function is never called for a
> +             * TREE_ITEM_TYPE_SHADOW item.  */
>              spice_assert(now->type == TREE_ITEM_TYPE_CONTAINER);
>  
>              if ((ring_item = ring_get_head(&now_as_container->items))) {
> +                /* descend into the container's child ring and continue
> +                 * iterating and removing those children */
>                  now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link);
>                  continue;
>              }
> +            /* This item is a container but it has no children, so reset our
> +             * iterator to the item's previous sibling and free this empty
> +             * container */

Why a container without child? Maybe the result of other
processing?

>              ring_item = now->siblings_link.prev;
>              container_free(now_as_container);
>          }
>          if (now == item) {
> +            /* This is true if the initial @item was a DRAWABLE, or if @item
> +             * was a container and we've finished iterating over all of that
> +             * container's children and returned back up to the parent and
> +             * freed it (see below) */
>              return;
>          }
>  
> +        /* Increment the iterator to the next sibling. Note that if an item was
> +         * removed above, ring_item will have been reset to the item before the
> +         * item that was removed */
>          if ((ring_item = ring_next(&container_of_now->items, ring_item))) {
>              now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link);
>          } else {
> +            /* there is no next sibling, so move one level up the tree */
>              now = &container_of_now->base;
>          }
>      }
> @@ -467,10 +486,23 @@ static void current_remove_all(DisplayChannel *display,
> int surface_id)
>  
>      while ((ring_item = ring_get_head(ring))) {
>          TreeItem *now = SPICE_CONTAINEROF(ring_item, TreeItem,
>          siblings_link);
> +        /* NOTE: current_remove() should never be called on Shadow type items
> +         * or we will hit an assert. Fortunately, the 'current' ring is ordered
> +         * in such a way that a DrawItem will always be placed before its
> +         * associated Shadow in the tree. Since removing a DrawItem will also
> +         * result in the associated Shadow item being removed from the tree,
> +         * this loop will never call current_remove() on a Shadow item unless
> +         * we change the order that items are inserted into the tree */
>          current_remove(display, now);
>      }
>  }
>  
> +/* Replace an existing Drawable in the tree with a new drawable that is
> + * equivalent. The new drawable is also added to the pipe.
> + *
> + * This function can fail if the items aren't actually equivalent (e.g. either
> + * item has a shadow, they have different effects, etc)
> + */
>  static int current_add_equal(DisplayChannel *display, DrawItem *item,
>  TreeItem *other)
>  {
>      DrawItem *other_draw_item;
> @@ -490,6 +522,9 @@ static int current_add_equal(DisplayChannel *display,
> DrawItem *item, TreeItem *
>      other_drawable = SPICE_CONTAINEROF(other_draw_item, Drawable,
>      tree_item);
>  
>      if (item->effect == QXL_EFFECT_OPAQUE) {
> +        /* check whether the new item can safely replace the other drawable at
> +         * the same position in the pipe, or whether it should be added to the
> +         * end of the queue */
>          int add_after = !!other_drawable->stream &&
>                          is_drawable_independent_from_surfaces(drawable);
>          stream_maintenance(display, drawable, other_drawable);

Here questions about how stream are handled raise. Why stream need special
handling?

> @@ -553,6 +588,47 @@ static int current_add_equal(DisplayChannel *display,
> DrawItem *item, TreeItem *
>      return FALSE;
>  }
>  
> +/* This function excludes the region from a single TreeItem
> + *
> + * If there is overlap between @rgn and the @item region, remove the
> + *
> + * overlapping intersection from both @rgn and the item's region (XXX WHY???)
> + *
> + * However, if the item is a DrawItem that has a shadow, we add an additional
> + * region to @rgn: the intersection of the shadow item's region with @rgn if
> + * @rgn was shifted over by the delta between the DrawItem and the Shadow.
> + * [WORKING THEORY: since the destination region for a COPY_BITS operation was
> + * excluded, we no longer need the source region that corresponds with that
> + * copy operation, so we can also exclude any drawables that affect that
> + * region. Not sure if that actually makes sense... ]
> + *
> + * If the item is a Shadow, we store the intersection between @rgn and the
> + * Shadow's region in Shadow::on_hold and remove that region from @rgn. This is
> + * done since a Shadow represents the source region for a COPY_BITS operation,
> + * and we need to make sure that this source region stays up-to-date until the
> + * copy operation is executed.
> + *
> + * Consider the following case:
> + *  1) the surface is fully black at the beginning
> + *  2) we add a new item to the tree which paints region A white
> + *  3) we add a new item to the tree which copies region A to region B
> + *  4) we add another new item to teh tree painting region A blue.
> + *
> + * After all operations are completed, region A should be blue, and region B
> + * should be white. If there were no copy operation (step 3), we could simply
> + * eliminate step 2 when we add item 4 to the tree, since step 4 overwrites the
> + * same region with a different color. However, if we did eliminate step 2,
> + * region B would be black after all operations were completed. So these
> + * regions that would normally be excluded are put "on hold" if they are part
> + * of a source region for a copy operation.
> + *
> + * @display: the display channel
> + * @ring: a fallback toplevel ring???
> + * @item: the tree item to exclude from @rgn
> + * @rgn: the region to exclude (?)
> + * @top_ring: ?
> + * @frame_candidate: ?
> + */

Need time to read again all this but looks like there is a lot of mechanical
"understanding" but no real understanding of the intention of these 2
(__exclude_region and exclude_region) functions.

>  static void __exclude_region(DisplayChannel *display, Ring *ring, TreeItem
>  *item, QRegion *rgn,
>                               Ring **top_ring, Drawable *frame_candidate)
>  {
> @@ -560,51 +636,79 @@ static void __exclude_region(DisplayChannel *display,
> Ring *ring, TreeItem *item
>      stat_start(&display->priv->__exclude_stat, start_time);
>  
>      region_clone(&and_rgn, rgn);
> +    /* find intersection of the @rgn argument with the region of the @item arg */
>      region_and(&and_rgn, &item->rgn);
>      if (!region_is_empty(&and_rgn)) {
>          if (IS_DRAW_ITEM(item)) {
>              DrawItem *draw = DRAW_ITEM(item);
>  
>              if (draw->effect == QXL_EFFECT_OPAQUE) {
> +                /* remove the intersection from the original @rgn */
>                  region_exclude(rgn, &and_rgn);
>              }
>  
>              if (draw->shadow) {
> +                /* this item represents the destination of a COPY_BITS
> +                 * operation. 'shadow' represents the source item for the copy
> +                 * operation */
>                  Shadow *shadow;
>                  int32_t x = item->rgn.extents.x1;
>                  int32_t y = item->rgn.extents.y1;
>  
> +                /* remove the intersection from the item's region */
>                  region_exclude(&draw->base.rgn, &and_rgn);
>                  shadow = draw->shadow;
> +                /* shift the intersected region by the difference between the
> +                 * source and destination regions */
>                  region_offset(&and_rgn, shadow->base.rgn.extents.x1 - x,
>                                shadow->base.rgn.extents.y1 - y);
> +                /* remove the shifted intersection region from the source
> +                 * (shadow) item's region. If the destination is excluded, we
> +                 * can also exclude the corresponding area from the source
> */
>                  region_exclude(&shadow->base.rgn, &and_rgn);
> +                /* find the intersection between the shifted intersection
> +                 * region and the Shadow's 'on_hold' region. This represents
> +                 * the portion of the Shadow's region that we just removed that
> +                 * is currently stored in on_hold. */
>                  region_and(&and_rgn, &shadow->on_hold);
>                  if (!region_is_empty(&and_rgn)) {
> +                    /* Since we removed a portion of the Shadow's region, we
> +                     * can also remove that portion from on_hold */
>                      region_exclude(&shadow->on_hold, &and_rgn);
> +                    /* Since this region is no longer "on hold", add it back to
> +                     * the @rgn argument */
>                      region_or(rgn, &and_rgn);
>                      // in flat representation of current, shadow is always
>                      his owner next
> +                    // XXX: what does that mean?
>                      if (!tree_item_contained_by(&shadow->base, *top_ring)) {
>                          *top_ring = tree_item_container_items(&shadow->base,
>                          ring);
>                      }
>                  }
>              } else {
> +                /* XXX: ??? */
>                  if (frame_candidate) {
>                      Drawable *drawable = SPICE_CONTAINEROF(draw, Drawable,
>                      tree_item);
>                      stream_maintenance(display, frame_candidate, drawable);
>                  }
> +                /* Remove the intersection from the DrawItem's region */
>                  region_exclude(&draw->base.rgn, &and_rgn);
>              }
>          } else if (item->type == TREE_ITEM_TYPE_CONTAINER) {
> +            /* excludes the intersection between 'rgn' and item->rgn from the
> +             * item's region */
>              region_exclude(&item->rgn, &and_rgn);
>  
>              if (region_is_empty(&item->rgn)) {  //assume container removal
>              will follow
>                  Shadow *shadow;
>  
> +                /* exclude the intersection from the 'rgn' argument as well,
> +                 * but only if the item is now empty... WHY??? */
>                  region_exclude(rgn, &and_rgn);
>                  if ((shadow = tree_item_find_shadow(item))) {
> +                    /* add the shadow's on_hold region back to the 'rgn' argument */
>                      region_or(rgn, &shadow->on_hold);
>                      if (!tree_item_contained_by(&shadow->base, *top_ring)) {
> +                        /* what is top_ring used for??? */
>                          *top_ring = tree_item_container_items(&shadow->base,
>                          ring);
>                      }
>                  }
> @@ -614,14 +718,43 @@ static void __exclude_region(DisplayChannel *display,
> Ring *ring, TreeItem *item
>  
>              spice_assert(item->type == TREE_ITEM_TYPE_SHADOW);
>              shadow = SHADOW(item);
> +            /* Since a Shadow represents the source region for a COPY_BITS
> +             * operation, we need to make sure that we don't remove existing
> +             * drawables that draw to this source region. If we did, it would
> +             * affect the copy operation. So we remove the intersection between
> +             * @rgn and item->rgn from the @rgn argument to avoid excluding
> +             * these drawables */
>              region_exclude(rgn, &and_rgn);
> +            /* adds this intersection to on_hold */
>              region_or(&shadow->on_hold, &and_rgn);
>          }
>      }
> +    /* clean up memory */
>      region_destroy(&and_rgn);
>      stat_add(&display->priv->__exclude_stat, start_time);
>  }
>  
> +/* This function iterates through the given @ring starting at @ring_item and
> + * continuing until reaching @last, and calls __exclude_item() on each item.
> + * Any items that have an empty region as a result of the __exclude_region()
> + * call are removed from the tree.
> + *

Just as a ring of goes also inside containers ?
Also... when does a container is created ?
How are items arranged in the tree ?

> + * XXX: I still don't have a great conceptual understanding of what we're
> + * trying to do by calling this function.
> + *

This is the problem.
Maybe is creating an hole in the current "painting" ?
Let's define the painting the result of the screen resulting from the drawing of
the drawables, so at the beginning we could have a screen

+---------------+
|               |
|               |
|               |
|               |
|               |
+---------------+

we draw an icon (very small, Drawable1)

+---------------+
|               |
|   +           |
|               |
|               |
|               |
+---------------+

Then we draw a window a big bigger on it (Drawable2)

+---------------+
|  +---+        |
|  |   |        |
|  +---+        |
|               |
|               |
+---------------+

both Drawable1 and Drawable2 are opaque. Before adding Drawable2 we basically remove all
drawables under Drawable2 region. Maybe this is a call to exclude_region(... region_of_Drawable1 ...) ?

> + * @ring: every time this function is called, @ring is a Surface's 'current'
> + *      ring, or to the ring of children of a container within that ring.
> + * @ring_item: callers usually call this argument 'exclude_base'. We will
> + *      iterate through the tree starting at this item
> + * @rgn: callers usually call this 'exclude_rgn' -- it appears to be the region
> + *      we want to exclude from existing items in the tree. It is an in/out
> + *      parameter and it may be modified as the result of calling this function
> + * @last: We will stop iterating at this item, and the function will return the
> + *      next item after iteration is complete (which may be different than the
> + *      passed value if that item was removed from the tree
> + * @frame_candidate: usually callers pass NULL, sometimes it's the drawable
> + *      that's being added to the 'current' ring. What is its purpose?
> + */
>  static void exclude_region(DisplayChannel *display, Ring *ring, RingItem
>  *ring_item,
>                             QRegion *rgn, TreeItem **last, Drawable
>                             *frame_candidate)
>  {
> @@ -640,40 +773,60 @@ static void exclude_region(DisplayChannel *display,
> Ring *ring, RingItem *ring_i
>  
>          spice_assert(!region_is_empty(&now->rgn));
>  
> +        /* check whether the ring_item item intersects the passed-in region */
>          if (region_intersects(rgn, &now->rgn)) {
> +            /* remove the overlapping portions of region and now->rgn, among
> +             * other things. See documentation for __exclude_region() */
>              __exclude_region(display, ring, now, rgn, &top_ring,
>              frame_candidate);
>  
>              if (region_is_empty(&now->rgn)) {
> +                /* __exclude_region() does not remove the region of shadow-type
> +                 * items */
>                  spice_assert(now->type != TREE_ITEM_TYPE_SHADOW);
>                  ring_item = now->siblings_link.prev;
> +                /* if __exclude_region() removed the entire region for this
> +                 * sibling item, remove it from the 'current' tree */
>                  current_remove(display, now);
>                  if (last && *last == now) {
> +                    /* the caller wanted to stop at this item, but this item
> +                     * has been removed, so we set @last to the next item */
>                      SPICE_VERIFY(SPICE_OFFSETOF(TreeItem, siblings_link) ==
>                      0);
>                      *last = (TreeItem *)ring_next(ring, ring_item);
>                  }
>              } else if (now->type == TREE_ITEM_TYPE_CONTAINER) {
> +                /* if this sibling is a container type, descend into the
> +                 * container's child ring and continue iterating */
>                  Container *container = CONTAINER(now);
>                  if ((ring_item = ring_get_head(&container->items))) {
>                      ring = &container->items;
>                      spice_assert(SPICE_CONTAINEROF(ring_item, TreeItem,
>                      siblings_link)->container);
>                      continue;
>                  }
> +                /* container had no children, so reset ring_item to the
> +                 * container itself */
>                  ring_item = &now->siblings_link;
>              }
>  
>              if (region_is_empty(rgn)) {
> +                /* __exclude_region() removed the entire region from 'rgn', so
> +                 * no need to continue checking further items in the tree */

I fail to understand, maybe I miss some knowledge about __exclude_region,
so __exclude_region changed rgn and now->rgn ?

>                  stat_add(&display->priv->exclude_stat, start_time);
>                  return;
>              }
>          }
>  
>          SPICE_VERIFY(SPICE_OFFSETOF(TreeItem, siblings_link) == 0);
> +        /* if this is the last item to check, or if the current ring is
> +         * completed, don't go any further */
>          while ((last && *last == (TreeItem *)ring_item) ||
>                 !(ring_item = ring_next(ring, ring_item))) {
> +            /* we're currently iterating the top ring, so we're done */
>              if (ring == top_ring) {
>                  stat_add(&display->priv->exclude_stat, start_time);
>                  return;
>              }
> +            /* we're iterating through a container child ring, so climb one
> +             * level up the heirarchy and continue iterating that ring */

Ok, so this function iterate all the tree from top to bottom.

>              ring_item = &container->base.siblings_link;
>              container = container->base.container;
>              ring = (container) ? &container->items : top_ring;
> @@ -681,6 +834,8 @@ static void exclude_region(DisplayChannel *display, Ring
> *ring, RingItem *ring_i
>      }
>  }
>  
> +/* Add a @drawable (with a shadow) to the current ring.

Which current ring are we taking about? Surface, DisplayChannel ?

> + * The return value indicates whether the new item should be added to the pipe */
>  static int current_add_with_shadow(DisplayChannel *display, Ring *ring,
>  Drawable *item)
>  {
>      stat_start(&display->priv->add_stat, start_time);
> @@ -707,11 +862,19 @@ static int current_add_with_shadow(DisplayChannel
> *display, Ring *ring, Drawable
>          stream_detach_behind(display, &shadow->base.rgn, NULL);
>      }
>  
> +    /* Prepend the shadow to the beginning of the current ring */
>      ring_add(ring, &shadow->base.siblings_link);
> +    /* Prepend the draw item to the beginning of the current ring. NOTE: this
> +     * means that the drawable is placed *before* its associated shadow in the
> +     * tree. Changing this order will violate several unstated assumptions
> */
>      current_add_drawable(display, item, ring);
>      if (item->tree_item.effect == QXL_EFFECT_OPAQUE) {
>          QRegion exclude_rgn;
>          region_clone(&exclude_rgn, &item->tree_item.base.rgn);
> +        /* Since the new drawable is opaque, remove overlapped regions from all
> +         * items already in the tree.  Start iterating through the tree
> +         * starting with the shadow item to avoid excluding the new item
> +         * itself */
>          exclude_region(display, ring, &shadow->base.siblings_link,
>          &exclude_rgn, NULL, NULL);
>          region_destroy(&exclude_rgn);
>          streams_update_visible_region(display, item);
> @@ -724,6 +887,8 @@ static int current_add_with_shadow(DisplayChannel
> *display, Ring *ring, Drawable
>      return TRUE;
>  }
>  
> +/* Add a @drawable (without a shadow) to the current ring.
> + * The return value indicates whether the new item should be added to the pipe */
>  static int current_add(DisplayChannel *display, Ring *ring, Drawable
>  *drawable)
>  {
>      DrawItem *item = &drawable->tree_item;
> @@ -736,54 +901,116 @@ static int current_add(DisplayChannel *display, Ring
> *ring, Drawable *drawable)
>      region_init(&exclude_rgn);
>      now = ring_next(ring, ring);
>  
> +    /* check whether the new drawable region intersects any of the items
> +     * already in the 'current' ring */
>      while (now) {
>          TreeItem *sibling = SPICE_CONTAINEROF(now, TreeItem, siblings_link);
>          int test_res;
>  
>          if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) {
> +            /* the bounds of the two items are totally disjoint, so no need to
> +             * check further. check the next item */
>              now = ring_next(ring, now);
>              continue;
>          }
> +        /* bounds overlap, but check whether the regions actually overlap */
>          test_res = region_test(&item->base.rgn, &sibling->rgn,
>          REGION_TEST_ALL);
>          if (!(test_res & REGION_TEST_SHARED)) {
> +            /* there's no overlap of the regions between these two items. Move
> +             * on to the next one. */
>              now = ring_next(ring, now);
>              continue;
>          } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) {
> +            /* there is an overlap between the two regions */
> +            /* NOTE: Shadow types represent a source region for a COPY_BITS
> +             * operation, they don't represent a region that will be drawn.
> +             * Therefore, we don't check for overlap between the new
> +             * DrawItem and any shadow items */
>              if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) &&
>                                                     !(test_res &
>                                                     REGION_TEST_LEFT_EXCLUSIVE)
>                                                     &&
>                                                     current_add_equal(display,
>                                                     item, sibling)) {
> +                /* the regions were equivalent, so we just replaced the other
> +                 * drawable with the new one */
>                  stat_add(&display->priv->add_stat, start_time);
> +                /* Caller doesn't need to add the new drawable to the pipe,
> +                 * since current_add_equal already added it to the pipe */
>                  return FALSE;
>              }
>  
>              if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect ==
>              QXL_EFFECT_OPAQUE) {
> +                /* the new drawable is opaque and entirely contains the sibling item */
>                  Shadow *shadow;
>                  int skip = now == exclude_base;
>  
>                  if ((shadow = tree_item_find_shadow(sibling))) {
> +                    /* The sibling item was the destination of a COPY_BITS operation */
>                      if (exclude_base) {
> +                        /* During a previous iteration through this loop, an
> +                         * obscured sibling item was removed from the tree, and
> +                         * exclude_base was set to the item immediately after
> +                         * the removed item (see below). This time through the
> +                         * loop, we encountered another sibling that was
> +                         * completely obscured, so we call exclude_region()
> +                         * using the previously saved item as our starting
> +                         * point. @exlude_rgn will be the union of any previous
> +                         * 'on_hold' regions from the shadows of previous
> +                         * iterations
> +                         *
> +                         * XXX: why do we only only call exclude_region() for
> +                         * the previous item if the next item is obscured and
> +                         * has a shadow???
> +                         */
>                          TreeItem *next = sibling;
> +                        /* XXX: What is the intent of this call? */

I miss something in this function.

>                          exclude_region(display, ring, exclude_base,
>                          &exclude_rgn, &next, NULL);
>                          if (next != sibling) {
> +                            /* the @next param is only changed if the given item
> +                             * was removed as a side-effect of calling
> +                             * exclude_region(), so update our loop variable
> */
>                              now = next ? &next->siblings_link : NULL;
>                              exclude_base = NULL;
>                              continue;
>                          }
>                      }
> +                    /* Since the destination item (sibling) of the COPY_BITS
> +                     * operation is fully obscured, we no longer need the
> +                     * source item (shadow) anymore. shadow->on_hold represents
> +                     * a region that would normally have been excluded by a
> +                     * previous call to __exclude_region() (see documentation
> +                     * for that function), but was put on hold to make sure we
> +                     * kept the source region up to date. Now that we no longer
> +                     * need this source region, this "on hold" region can be
> +                     * safely excluded again. */
>                      region_or(&exclude_rgn, &shadow->on_hold);
>                  }
>                  now = now->prev;
> +                /* remove the obscured sibling from the 'current' tree, which
> +                 * will also remove its shadow (if any) */
>                  current_remove(display, sibling);
> +                /* advance the loop variable */
>                  now = ring_next(ring, now);
>                  if (shadow || skip) {
> +                    /* XXX: don't understand this exclude_base stuff
> +                     * 'now' is currently set to the the item immediately AFTER
> +                     * the obscured sibling that we just removed. Why is this
> +                     * item used as an 'exclude_base'??
> +                     */
>                      exclude_base = now;
>                  }
>                  continue;
>              }
>  
>              if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) &&
>              is_opaque_item(sibling)) {
> +                /* the sibling item is opaque and entirely contains the new drawable */
>                  Container *container;
>  
> +                /* XXX: still don't understand this exclude_base stuff. The
> +                 * first time through, @exclude_base will be NULL, but
> +                 * subsequent loops may set it to something.
> +                 * In addition, @exclude_rgn starts out empty, but previous
> +                 * iterations of this loop may have added various
> +                 * Shadow::on_hold regions to it.
> +                 */
>                  if (exclude_base) {
>                      exclude_region(display, ring, exclude_base,
>                      &exclude_rgn, NULL, NULL);
>                      region_clear(&exclude_rgn);
> @@ -791,13 +1018,20 @@ static int current_add(DisplayChannel *display, Ring
> *ring, Drawable *drawable)
>                  }
>                  if (sibling->type == TREE_ITEM_TYPE_CONTAINER) {
>                      container = CONTAINER(sibling);
> +                    /* NOTE: here, ring is reset to the ring of the container's children */
>                      ring = &container->items;
> +                    /* if the sibling item is a container, place the new
> +                     * drawable into that container */
>                      item->base.container = container;
> +                    /* Start iterating over the container's children to see if
> +                     * any of them intersect this new drawable */
>                      now = ring_next(ring, ring);
>                      continue;
>                  }
>                  spice_assert(IS_DRAW_ITEM(sibling));
>                  if (!DRAW_ITEM(sibling)->container_root) {
> +                    /* Create a new container to hold the sibling and the new
> +                     * drawable */
>                      container = container_new(DRAW_ITEM(sibling));
>                      if (!container) {
>                          spice_warning("create new container failed");
> @@ -805,17 +1039,32 @@ static int current_add(DisplayChannel *display, Ring
> *ring, Drawable *drawable)
>                          return FALSE;
>                      }
>                      item->base.container = container;
> +                    /* reset 'ring' to the container's children ring, so that
> +                     * we can add the new drawable to this ring below */
>                      ring = &container->items;
>                  }
>              }
>          }
> +        /* If we've gotten here, that means that:
> +         *  - the new item is not opaque
> +         *  - We just created a container to hold the new drawable and the
> +         *    sibling that encloses it
> +         *  - ??? */
>          if (!exclude_base) {
>              exclude_base = now;
>          }
>          break;
>      }
> +    /* we've removed any obscured siblings and figured out which ring the new
> +     * drawable needs to be added to, so let's add it. */
>      if (item->effect == QXL_EFFECT_OPAQUE) {
> +        /* @exclude_rgn may contain the union of on_hold regions from any
> +         * Shadows that were associated with DrawItems that were removed from
> +         * the tree.  Add the new item's region to that */
>          region_or(&exclude_rgn, &item->base.rgn);
> +        /* XXX: Why do we need to call exclude_region() here again? I thought
> +         * we had just iterated the whole list above to check for region
> +         * intersections between this new item and existing items... */
>          exclude_region(display, ring, exclude_base, &exclude_rgn, NULL,
>          drawable);
>          stream_trace_update(display, drawable);
>          streams_update_visible_region(display, drawable);
> @@ -1623,6 +1872,8 @@ static void surface_update_dest(RedSurface *surface,
> const SpiceRect *area)
>      canvas->ops->read_bits(canvas, dest, -stride, area);
>  }
>  
> +/* Draws all drawables associated with @surface, starting from the tail of the
> + * ring, and stopping after it draws @last */

Tail of which ring? And how is this ring sorted? I suppose
we draw from elder to newer.

>  static void draw_until(DisplayChannel *display, RedSurface *surface,
>  Drawable *last)
>  {
>      RingItem *ring_item;
> @@ -1646,6 +1897,11 @@ static void draw_until(DisplayChannel *display,
> RedSurface *surface, Drawable *l
>      } while (now != last);
>  }
>  
> +/* Find the first Drawable in the @current ring that intersects the given
> + * @area, starting at item @from (or the head of the ring if @from is NULL).
> + *
> + * NOTE: this function expects @current to be a ring of Drawables, and more
> + * specifically an instance of Surface::current_list (not Surface::current)

Definitively we need better names, too many current!

> */
>  static Drawable* current_find_intersects_rect(Ring *current, RingItem *from,
>                                                const SpiceRect *area)
>  {
> diff --git a/server/display-channel.h b/server/display-channel.h
> index ba83e8c..d7184a7 100644
> --- a/server/display-channel.h
> +++ b/server/display-channel.h
> @@ -162,8 +162,14 @@ typedef struct DrawContext {
>  
>  typedef struct RedSurface {
>      uint32_t refs;
> -    Ring current; /* TreeItem */
> -    Ring current_list; /* Drawable */
> +    /* A Ring representing a hierarchical tree structure. This tree includes
> +     * DrawItems, Containers, and Shadows. It is used to efficiently determine
> +     * which drawables overlap, and to exclude regions of drawables that are
> +     * obscured by other drawables */
> +    Ring current;
> +    /* A ring of pending Drawables associated with this surface. This ring is
> +     * actually used for drawing */

Is this just containing drawabled, right? Not the tree?
Why this is used for drawing and not the tree?
Does if have a specific order?
I though that the drawing was done using pipe items.

> +    Ring current_list;
>      DrawContext context;
>  
>      Ring depend_on_me;
> diff --git a/server/tree.c b/server/tree.c
> index ea11000..cc3a96f 100644
> --- a/server/tree.c
> +++ b/server/tree.c
> @@ -185,6 +185,9 @@ void tree_item_dump(TreeItem *item)
>      tree_foreach(item, dump_item, &di);
>  }
>  
> +/* Creates a new Shadow item for the given DrawItem, with an offset of @delta.
> + * A shadow represents a source region for a COPY_BITS operation, while the
> + * DrawItem represents the destination region for the operation */
>  Shadow* shadow_new(DrawItem *item, const SpicePoint *delta)
>  {
>      spice_return_val_if_fail(item->shadow == NULL, NULL);
> @@ -205,6 +208,11 @@ Shadow* shadow_new(DrawItem *item, const SpicePoint
> *delta)
>      return shadow;
>  }
>  
> +/* Create a new container to hold @item and insert @item into this container.
> + *
> + * NOTE: This function assumes that @item is already inside a different Ring,
> + * so it removes @item from that ring before inserting it into the new
> + * container */
>  Container* container_new(DrawItem *item)
>  {
>      Container *container = spice_new(Container, 1);
> @@ -268,6 +276,8 @@ Shadow* tree_item_find_shadow(TreeItem *item)
>      return DRAW_ITEM(item)->shadow;
>  }
>  
> +/* return the Ring containing siblings of item, falling back to @ring if @item
> + * does not have a container */
>  Ring *tree_item_container_items(TreeItem *item, Ring *ring)
>  {
>      return (item->container) ? &item->container->items : ring;
> @@ -281,7 +291,7 @@ int tree_item_contained_by(TreeItem *item, Ring *ring)
>          if (now == ring) {
>              return TRUE;
>          }
> -    } while ((item = &item->container->base));
> +    } while ((item = &item->container->base)); /* move up one level */
>  
>      return FALSE;
>  }

Comments on this file seems ready to be pushed.

> diff --git a/server/tree.h b/server/tree.h
> index 35b78ab..e579dd0 100644
> --- a/server/tree.h
> +++ b/server/tree.h
> @@ -43,12 +43,18 @@ struct TreeItem {
>      RingItem siblings_link;
>      uint32_t type;
>      Container *container;
> +    /* rgn holds the region of the item. As additional items get added to the
> +     * tree, this region may be modified to represent the portion of the item
> +     * that is not obscured by other items */

so it's the visible region, not the original one.

>      QRegion rgn;
>  };
>  
>  /* A region "below" a copy, or the src region of the copy */
>  struct Shadow {
>      TreeItem base;
> +    /* holds the union of all parts of this source region that have been
> +     * obscured by a drawable item that has been subsequently added to the tree
> +     */
>      QRegion on_hold;

basically here, opposite to rgn we hold the not visible region...
I would ask why.

>  };
>  

Frediano


More information about the Spice-devel mailing list