all about animations



So,

there's a branch innocently named wip/cssvalue in git master that
implements what I teasered in
http://blogs.gnome.org/otte/2012/04/02/gtk-3-6/ and IMO is ready for
master (after 3.4 branched off of course).

What does this branch do? Here's the TL;DR version:
- Speed up CSS by a factor of 10 or so
- Improve correctness of our CSS handling
- Implement proper support for sibling selectors
- Reduce memory taken by the CSS machinery by a factor of 2
- Implement CSS transitions (see http://dev.w3.org/csswg/css3-transitions/ )
- Set groundwork for CSS animations (see
http://dev.w3.org/csswg/css3-animations/ )

And here's the long version for the people who want to review this
branch (it's 160 commits, yay...) or just want to understand what I've
done (links go to current code in the wip/cssvalue branch, not to the
commits where things were introduced).

A big problem with proper CSS handling is that a random change
somewhere can potentially introduce updates to all siblings and their
children of a widget. Say, if you click on a button and the button
becomes active, a selector like ":active ~ * .something" will suddenly
change things in a not-really-related element. In GTK 3.4, whenever
such a thing happens, we do a gtk_widget_reset_styles(), which emits
style_updated(), which blows all caches and calls
gtk_widget_queue_resize(). Of course, almost nobody ever writes a
selector like that in practice. So the first thing I did was classify
potential changes to CSS using GtkCssChange, see
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcsstypesprivate.h?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n26
then check which changes actually are relevant for a given widget
using _gtk_style_provider_get_change() and ignore changes that are not
relevant:
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n3047
This provided quite a good speedup.

Then I realized that with all those potential changes going on, a big
problem is that there can be lots of changes at once. If you add 30
widgets into a GtkBox, every other widget in the box will get its
style invalidated 30 times (because it got a new sibling and its
position changed). And if we actually can't optimize this change away,
we get to update the style of every widget in the box 30 times. This
is rather slow. (Glade does this to update its properties view, you
can notice how slow it is when you select a different widget).
So the next thing I did was add _gtk_style_context_queue_invalidate().
What that means is that instead of instantly updating the style
whenever a change happens, GTK now waits until it is actually going to
repaint the widget and only then updates the style. Until then, it
just keeps the old values. This was kind of tricky to get right,
because of course we have lots of code that causes style invalidations
from the style_updated signal, which would then cause a new
style_updated signal... But I hope I got it working well enough. The
interesting things here are
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n3129
or rather the callers of this function, and
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n3026
which is _gtk_style_context_validate(), the function that does the
actual invalidation. It does a lot more, but I'll get to that later.
All of this is called from the core update loop in GTK, see
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcontainer.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n1624
which essentially makes updating the screen in GTK a 3-step process
(the first step being new):
(1) update styles
(2) recalculate sizes
(3) draw
Each of those steps can trigger updates in the later steps. (As
mentioned above, also the other way, but you'll only see this in the
next update - your fault! :))

By now we do a lot less invalidations, but there's still one thing
going on. Because we still have a lot of cases where we can't manage
to optimize things away yet, but still nothing changes. After we've
queried all the new values from CSS, they are all the same. In this
case, we could still avoid blowing all the widget's caches if we
actually knew that all the values were still the same.

This is were Alex' work on GtkCssValue comes in. GtkCssValue so far
was essentially a refcounted GValue. He added this to avoid lots of
GValue copies that happened when you look up properties from CSS and
then copy them into all the style contexts. We saved megabytes of
memory on rather simple applications like gtk-demo or Nautilus with
that.
  http://git.gnome.org/browse/gtk+/commit/gtk/gtkcssvalue.c?id=0ece7a5de3eae5f4d7e4d1623d191a0a0628e652
is the commit that originally introduced it. It's part of GTK 3.4 already.

Now, if I made GtkCssValue not just a GValue, but more like an object
by adding a vtable, I could move a lot of functionality there, like
the equality check I mentioned. And save even more memory by tailoring
those values directly to the values that matter.
This took a lot of refactoring, but in the end I got this:
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssvalueprivate.h?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n36
which is a tiny API that is implemented by the actual different
values. Some examples:
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssnumbervalueprivate.h?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssrgbavalueprivate.h?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssshadowvalueprivate.h?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
or their implementations (which are rather short, too):
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssnumbervalue.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssrgbavalue.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssshadowvalue.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
In essence, every CSS "type" got its own GtkCssValue class.

An important interlude right here, should you ever need to debug CSS values:
GtkCssValue is an opaque type that resolves to something different
depending on what source file you are in (I know, that sucks). But,
should you ever need to inspect a GtkCssValue,
_gtk_css_value_to_string() will give you a CSS string representation
instantly, and you can go from there.

Another interesting value type is the array type:
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssarrayvalueprivate.h?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkcssarrayvalue.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85
It's used whenever a CSS property is a list of values. you then need
to query the actual subvalues from that list. font-family or the
transition properties use that already. The background properties
aren't converted yet.

Now if you actually want to use CSS values inside GTK, the way to do
it is to call _gtk_style_context_peek_property() (or
_gtk_theming_engine_peek_property(). Not only do these functions give
you the real value from the CSS, they are also a lot faster than
gtk_style_context_get() / gtk_theming_engine_get(). Unfortunately they
usually result in long incantations of function calls, like
  _gtk_css_number_value_get (
      _gtk_css_array_value_get_nth (
          _gtk_style_context_peek_property (context,
GTK_CSS_PROPERTY_TRANSITION_DURATION),
          i),
      100);
but that's what the power of CSS gets you. And I hope those function
calls describe well enough what's actually happening right there.

Anyway, once that conversion was complete, I had a
_gtk_css_value_equal() function and could implement the actual check
for equality of CSS changes. This is what the "changes" bitmask
variable in _gtk_style_context_validate() tracks. And as you can see
in the code, we don't call style_updated (via do_invalidate()) if no
value actually changed.

After this work, I spent a bit of time running my favorite Glade
benchmark. The results of which can be seen in the commit message of
this commit:
  http://git.gnome.org/browse/gtk+/commit/?id=d9e574da9eccb7b1decddde2c01516c84085089c
In short, the total runtime of that test went down by a factor of 4 to
5. Not only that, but I threw sysprof at it and the remaining time was
mostly spent actually drawing or inside Glade. Only 10% of time was
still spent updating CSS values. So style updates are no longer the
bottleneck.

So now that I had per-value classes and really fast CSS updates that
even tracked what value was updated, it was time to make use of them.
And this is were CSS animations come in. So I spent some more work
adding them. I had to remove the old animations framework because the
two were conflicting in interesting ways (including both claiming the
'transition' CSS property name). After I had done so, it turned out
there were a bunch of restrictions that applied and I had to fix them,
which makes this not quite as awesome as it could have been. Read
about it here:
  http://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=781d2c11bdd435f37ab8e364d59b3465b61a8d85#n1653
In short: Once your drawing code calls gtk_style_context_save(),
nothing will get animated. And we do that 100s of times even in GTK.
Go us. We need to make the whole rendering process more stateful to
make full use of animations. For now, the "simple" widgets work fine
though. And that is buttons, menuitems, toolbars and labels. Which
should be quite enough to make things exciting for a while.

So that's all for now. If there's questions, you know how to ask. :)

Benjamin


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]