159 lines
7.0 KiB
Markdown
159 lines
7.0 KiB
Markdown
Title: Overview of the drawing model
|
||
Slug: drawing-overview
|
||
|
||
This chapter describes the GTK drawing model in detail. If you
|
||
are interested in the procedure which GTK follows to draw its
|
||
widgets and windows, you should read this chapter; this will be
|
||
useful to know if you decide to implement your own widgets. This
|
||
chapter will also clarify the reasons behind the ways certain
|
||
things are done in GTK.
|
||
|
||
## Windows and events
|
||
|
||
Applications that use a windowing system generally create
|
||
rectangular regions in the screen called _surfaces_ (GTK is
|
||
following the Wayland terminology, other windowing systems
|
||
such as X11 may call these _windows_). Traditional windowing
|
||
systems do not automatically save the graphical content of
|
||
surfaces, and instead ask applications to provide new content
|
||
whenever it is needed. For example, if a window that is stacked
|
||
below other windows gets raised to the top, then the application
|
||
has to repaint it, so the previously obscured area can be shown.
|
||
When the windowing system asks an application to redraw a window,
|
||
it sends a _frame event_ (_expose event_ in X11 terminology)
|
||
for that window.
|
||
|
||
Each GTK toplevel window or dialog is associated with a
|
||
windowing system surface. Child widgets such as buttons or
|
||
entries don't have their own surface; they use the surface
|
||
of their toplevel.
|
||
|
||
Generally, the drawing cycle begins when GTK receives a frame event
|
||
from the underlying windowing system: if the user drags a window
|
||
over another one, the windowing system will tell the underlying
|
||
surface that it needs to repaint itself. The drawing cycle can
|
||
also be initiated when a widget itself decides that it needs to
|
||
update its display. For example, when the user types a character
|
||
in an entry widget, the entry asks GTK to queue a redraw operation
|
||
for itself.
|
||
|
||
The windowing system generates frame events for surfaces. The GDK
|
||
interface to the windowing system translates such events into
|
||
emissions of the ::render signal on the affected surfaces. The GTK
|
||
toplevel window connects to that signal, and reacts appropriately.
|
||
|
||
The following sections describe how GTK decides which widgets
|
||
need to be repainted in response to such events, and how widgets
|
||
work internally in terms of the resources they use from the
|
||
windowing system.
|
||
|
||
## The frame clock
|
||
|
||
All GTK applications are mainloop-driven, which means that most
|
||
of the time the app is idle inside a loop that just waits for
|
||
something to happen and then calls out to the right place when
|
||
it does. On top of this GTK has a frame clock that gives a
|
||
“pulse” to the application. This clock beats at a steady rate,
|
||
which is tied to the framerate of the output (this is synced to
|
||
the monitor via the window manager/compositor). A typical
|
||
refresh rate is 60 frames per second, so a new “pulse” happens
|
||
roughly every 16 milliseconds.
|
||
|
||
The clock has several phases:
|
||
|
||
- Events
|
||
- Update
|
||
- Layout
|
||
- Paint
|
||
|
||
The phases happens in this order and we will always run each
|
||
phase through before going back to the start.
|
||
|
||
The Events phase is a stretch of time between each redraw where
|
||
GTK processes input events from the user and other events
|
||
(like e.g. network I/O). Some events, like mouse motion are
|
||
compressed so that only a single mouse motion event per clock
|
||
cycle needs to be handled.
|
||
|
||
Once the Events phase is over, external events are paused and
|
||
the redraw loop is run. First is the Update phase, where all
|
||
animations are run to calculate the new state based on the
|
||
estimated time the next frame will be visible (available via
|
||
the frame clock). This often involves geometry changes which
|
||
drive the next phase, Layout. If there are any changes in
|
||
widget size requirements the new layout is calculated for the
|
||
widget hierarchy (i.e. sizes and positions for all widgets are
|
||
determined). Then comes the Paint phase, where we redraw the
|
||
regions of the window that need redrawing.
|
||
|
||
If nothing requires the Update/Layout/Paint phases we will
|
||
stay in the Events phase forever, as we don’t want to redraw
|
||
if nothing changes. Each phase can request further processing
|
||
in the following phases (e.g. the Update phase will cause there
|
||
to be layout work, and layout changes cause repaints).
|
||
|
||
There are multiple ways to drive the clock, at the lowest level you
|
||
can request a particular phase with gdk_frame_clock_request_phase()
|
||
which will schedule a clock beat as needed so that it eventually
|
||
reaches the requested phase. However, in practice most things
|
||
happen at higher levels:
|
||
|
||
- If you are doing an animation, you can use
|
||
gtk_widget_add_tick_callback() which will cause a regular
|
||
beating of the clock with a callback in the Update phase
|
||
until you stop the tick.
|
||
- If some state changes that causes the size of your widget to
|
||
change you call gtk_widget_queue_resize() which will request
|
||
a Layout phase and mark your widget as needing relayout.
|
||
- If some state changes so you need to redraw some area of
|
||
your widget you use the normal gtk_widget_queue_draw()
|
||
set of functions. These will request a Paint phase and
|
||
mark the region as needing redraw.
|
||
|
||
There are also a lot of implicit triggers of these from the
|
||
CSS layer (which does animations, resizes and repaints as needed).
|
||
|
||
## The scene graph
|
||
|
||
The first step in “drawing” a window is that GTK creates
|
||
_render nodes_ for all the widgets in the window. The render
|
||
nodes are combined into a tree that you can think of as a
|
||
_scene graph_ describing your window contents.
|
||
|
||
Render nodes belong to the GSK layer, and there are various kinds
|
||
of them, for the various kinds of drawing primitives you are likely
|
||
to need when translating widget content and CSS styling. Typical
|
||
examples are text nodes, gradient nodes, texture nodes or clip nodes.
|
||
|
||
In the past, all drawing in GTK happened via cairo. It is still possible
|
||
to use cairo for drawing your custom widget contents, by using a cairo
|
||
render node.
|
||
|
||
A GSK _renderer_ takes these render nodes, transforms them into
|
||
rendering commands for the drawing API it targets, and arranges
|
||
for the resulting drawing to be associated with the right surface.
|
||
GSK has renderers for OpenGL, Vulkan and cairo.
|
||
|
||
## Hierarchical drawing
|
||
|
||
During the Paint phase GTK receives a single ::render signal on the
|
||
toplevel surface. The signal handler will create a snapshot object
|
||
(which is a helper for creating a scene graph) and call the
|
||
GtkWidget snapshot() vfunc, which will propagate down the widget
|
||
hierarchy. This lets each widget snapshot its content at the right
|
||
place and time, correctly handling things like partial transparencies
|
||
and overlapping widgets.
|
||
|
||
During the snapshotting of each widget, GTK automatically handles
|
||
the CSS rendering according to the CSS box model. It snapshots first
|
||
the background, then the border, then the widget content itself, and
|
||
finally the outline.
|
||
|
||
To avoid excessive work when generating scene graphs, GTK caches render
|
||
nodes. Each widget keeps a reference to its render node (which in turn,
|
||
will refer to the render nodes of children, and grandchildren, and so
|
||
on), and will reuse that node during the Paint phase. Invalidating a
|
||
widget (by calling gtk_widget_queue_draw()) discards the cached render
|
||
node, forcing the widget to regenerate it the next time it needs to
|
||
produce a snapshot.
|