These will replace the previous gtk_snapshot_new_with_parent(), which
allocated an entirely new GObject just to push()/pop() some state. This
is already a problem but will be more important in the future as we
start using this more.
Instead of gtk_snapshot_offset(), provide a full set of functions
kept in sync with GtkTransform APIs.
On top of that, add gtk_snapshot_save() and gtk_snapshot_restore()
mirroring cairo_save()/restore() that allow saving a snapshot's
transform state.
The code didn't change, it was just shuffled around to make the
with_bounds() versions of the text rendering unnecessary and instead
pass through the generic append_node() path.
Instead of just tracking 2 integer translate_x/y coordinates, tracka a
full GtkTransform.
When creating actual nodes, if the transform is simple enough, just
create the node in a way that makes use of the transform. If the
node, can't represent the transform, just push a transform node instead
and automatically pop that node with the next gtk_snapshot_pop() call.
Most of the time, the GtkSnapshot objects we create while snapshotting
widgets don't end up containing all that many nodes or states in their
respective node or state stack. This undermines the amortized allocation
behavior of the G(Ptr)Array we use for the stacks. So instead, use the
(until now unused) parent_snapshot GtkSnapshot* passed to
gtk_widget_create_render_node and reuse its node and state stack.
We do not avoid allocating a new GtkSnapshot object, but we do avoid
allocating a ton of G(Ptr)Array objects and we also avoid realloc'ing
their storage.
Any data that is later fed to graphene must be
allocated with proper alignment, if graphene
uses SSE2 or GCC vector instructions.
This adds custom array code (a streamlined copy
of GArray with all unnecessary bells and whistles removed),
which is then used for the state_stack instead of GArray.
There's also a runtime check for the size of GtkSnapshotState
itself being a multiple of 16. If that is not so, any array
elements past the 0th element will lose alignment.
There are probably struct attributes that can
make GtkSnapshotState always have size that is a multiple
of 16, but we'll burn that bridge if we cross it.
If widgets want to clip things, they now need to do it themselves.
By not taking care of clip, we avoid the need to track clip. And by not
tracking clip, we can avoid all unnecessary cache invalidations that we
were doing for render nodes whenever the clip changed.
And when you are scrolling, the clip changes *a lot*.
When the clip changes that is passed to a snapshot function, we need to
create eventual cached render nodes because they might not have drawn
their whole area before.
Fixes issues with redrawing when scrolling.
This requires a bunch of refactorings:
1. Don't pass the current clip region to gtk_widget_snapshot()
so we don't create full widget contents
3. Have a widget->priv->draw_needed that we invalidate on every
queue_draw() call and set on every snapshot()
2. In queue_draw(), walk the widget chain to invalidate the
render nodes of all parents
This is a neat trick to get around the circularity between GDK, GSK and
GTK that we inherit with the GdkPaintable interface.
GdkPaintable uses GtkSnapshot
GtkSnapshot creates GskRenderNodes
GskRenderNodes use GdkTextures
GdkTexture will soon implement GdkPaintable
This causes a loop that spans GDK, GSK and GTK and this is the easiest
way to resolve it without breaking bindings (at least that's the idea).
Instead of creating one GPtrArray per GtkSnapshotState and saving nodes
in there, create one GPtrArray per snapshot and assign a
start_node_index to every GtkSnapshotState as well as a n_nodes variable
so every state knows which nodes belong to it.
... and use it.
The function is a bit awkward because it requires 2 calls to
gtk_snapshot_pop(), but once you accept that, it's very convenient to
use, as can be seen by the 2 implementations.
We already take ints when setting the translation, so it can't
currently take any other values. Additionally, I was seeing large
costs in int -> double -> int for the rects in
gtk_snapshot_clips_rect(), as all callers really are ints (widget
allocations) and the clip region is int-based.
This change completely cleared a 2% rectangle_init_from_graphene from
the profile and is likely to have nice performance effects elsewhere
too.
This means we allocate the collect data with the state, avoiding
an extra allocation. Also, a union means every state object
is the same size and we could reuse the state objects.
It is now possible to call push() subfunctions for simple container
nodes with just a single child. So you can for example
gtk_snapshot_push_clip() a clip region that all the nodes that get
appended later will then obey.
gtk_snapshot_pop() will then not return a container node, but a clip
node containing the container node (and similar for the transform
example).
This is implemented internally by providing a "collect function" when
pushing that is called when popping to collects all the accumulated
nodes and combine them into the single node that gets returned.
To simplify things even more, gtk_snapshot_pop_and_append() has been
added, which pops the currently pushed node and appends it to the
parent.
The icon rendering code has been converted to this approach.
Instead of appending a container node and adding the nodes to it as they
come in, we now collect the nodes until gtk_snapshot_pop() is called and
then hand them out in a container node.
The caller of gtk_snapshot_push() is then responsible for doing whatever
he wants with the created node.
Another addigion is the keep_coordinates flag to gtk_snapshot_push()
which allows callers to keep the current offset and clip region or
discard it. Discarding is useful when doing transforms, keeping it is
useful when inserting effect nodes (like the ones I'm about to add).
Instead of having a setter for the transform, have a GskTransformNode.
Most of the oprations that GTK does do not require a transform, so it
doesn't make sense to have it as a primary attribute.
Also, changing the transform requires updating the uniforms of the GL
renderer, so we're happy if we can avoid that.
And use this to cull widgets and gadgets that are completely outside the
clip region.
A potential optimization is to apply this clip region to cairo contexts
created with gtk_snapshot_append_cairo_node(), but for that we'd need to
apply the inverse matrix to the clip region, and that causes rounding
errors.
Plus, I hope that cairo drawing becomes exceedingly rare so it won't be
used for the whole widget factory like today (which might also explain
why no culling happens in the widget factory outside the header bar.
We now try to emulate cairo_t:
We keep a stack of nodes via push/pop and a transform matrix.
So whenever a new node is added to the snapshot, we transform it
by the current transform matrix and append it to the current node.