wayland/doc/publican/Content_Updates.xml
Sebastian Wick 1807450a7b doc: Add a chapter on content updates
The behavior of content updates, specifically in combination with sync
subsrufaces and constrains can become quite complicated. This introduces
a chapter in the wayland book which explains the behavior of the core
specification in this regard, and shows examples.

Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
2026-01-22 10:49:37 +02:00

460 lines
15 KiB
XML

<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!ENTITY % BOOK_ENTITIES SYSTEM "Wayland.ent">
%BOOK_ENTITIES;
]>
<chapter id="chap-Content-Updates">
<title>Content Updates</title>
<section id="sect-Content-Updates-preface">
<title>Overview</title>
<para>
In the Wayland protocol, requests are asynchronous but take effect
immediately when the compositor receives them. However, some requests on
surfaces are not applied immediately but are instead double-buffered to
allow atomic changes. These double-buffered changes are committed through
the wl_surface.commit request, which creates a Content Update.
</para>
<para>
Content Updates encapsulate all double-buffered state changes and can be
applied by the compositor. The complexity arises when considering
subsurfaces, which can operate in synchronized mode. When a subsurface is
synchronized, its Content Updates must be applied atomically together with
the parent surface's state. This synchronization can extend through an
entire tree of subsurfaces, where child subsurfaces inherit the
synchronized behavior from their parents.
</para>
<para>
Historically, Content Updates from synchronized subsurfaces were merged
into the pending state of the parent surface on commit. However, the
introduction of constraints—which can defer the application of Content
Updates—necessitated a more sophisticated model. This led to the
implementation of per-surface queues of Content Updates, with dependencies
between Content Updates across different queues. This queuing model
maintains backwards compatibility with the earlier approach of merging
Content Updates into the parent's pending state on commit.
</para>
<para>
The core protocol defines the semantics of Content Updates using
per-surface queues, but compositors that do not need to support constraints
may implement the simpler legacy model where synchronized subsurface states
are merged directly into the parent's pending state.
</para>
</section>
<section id="sect-Content-Updates-rules">
<title>Rules</title>
<para>
The core protocol specifies the behavior in wl_subsurface and
wl_surface.commit. The behavior can be summarized by the following rules:
</para>
<orderedlist numeration="arabic">
<listitem>
<para>
Content Updates (CU) contain all double-buffered state of the surface and
selected state from their direct children.
</para>
</listitem>
<listitem>
<para>
Surfaces which are effectively synchronized create Synchronized
Content Updates (SCU), otherwise they create Desync Content Updates
(DCU).
</para>
</listitem>
<listitem>
<para>
When a CU is created, it gets a dependency on the previous CU of the
same queues (if it exists).
</para>
</listitem>
<listitem>
<para>
When a CU is created, it gets a dependency on the last SCU of direct
child surfaces that are not reachable (if they exists).
</para>
</listitem>
<listitem>
<para>
The CUs and their dependencies form a DAG, where CUs are nodes and
dependencies are edges.
</para>
</listitem>
<listitem>
<para>
All DCUs starting from the front of the queues until the first SCU or
the back of the queue is reached are candidates.
</para>
</listitem>
<listitem>
<para>
If the maximal DAG that's reachable from a candidate (candidate DAG)
does not have any constraints, then this DAG can be applied.
</para>
</listitem>
<listitem>
<para>
A DAG is applied atomically by recursively applying a content update
without dependencies and removing it from the DAG.
</para>
</listitem>
<listitem>
<para>
Surfaces transition from effectively sync to effectively desync after
their parents.
</para>
</listitem>
<listitem>
<para>
When a surface transitions to effectively desync, all SCUs in its
queue which are not reachable by a DCU become DCUs.
</para>
</listitem>
</orderedlist>
</section>
<section id="sect-Content-Updates-examples">
<title>Examples</title>
<para>
These examples should help to build an intuition for how content updates
actually behave. They cover the interesting edge cases, such as
subsurfaces with constraints, and transitioning from a sync subsurface to
a desync one.
</para>
<para>
In all the examples below, the surface T1 refers to a toplevel surface,
SS1 refers to a sub-surface which is a child of T1, and SS2 refers to a
sub-surface which is a child of SS1.
</para>
<figure>
<title>Legend</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/content-update-legend.png"/>
</imageobject>
</mediaobject>
</figure>
<figure>
<title>Simple Desynchronized Case</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-desynchronized-state-1.png"/>
</imageobject>
<caption>
<para>
SS2 is effectively desynchronized and commits. This results in the
desynchronized content update (DCU) <emphasis>1</emphasis>.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-desynchronized-state-2.png"/>
</imageobject>
<caption>
<para>
DCU <emphasis>1</emphasis> is a candidate, and the candidate DAG
reachable from DCU <emphasis>1</emphasis> is only
DCU <emphasis>1</emphasis> itself. DCU <emphasis>1</emphasis> and
thus the candidate DAG does not have any constraints and can be
applied.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-desynchronized-state-3.png"/>
</imageobject>
<caption>
<para>
The content updates of the candidate DAG get applied to the surface
atomically.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-desynchronized-state-4.png"/>
</imageobject>
<caption>
<para>
T1 commits a DCU with a <emphasis>buffer-sync</emphasis> constraint.
It is a candidate but its DAG can't be applied because it contains a
constraint.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-desynchronized-state-5.png"/>
</imageobject>
<caption>
<para>
T1 commits another CU (DCU <emphasis>3</emphasis>) which is added at
the end of the queue, with a dependency to the previous CU (DCU
<emphasis>2</emphasis>). Both DCU <emphasis>2</emphasis> and DCU
<emphasis>3</emphasis> are candidates, but both DAGs contain DCU
<emphasis>2</emphasis> with a constraint, and can't be applied.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-desynchronized-state-6.png"/>
</imageobject>
<caption>
<para>
When the constraint gets cleared, both DAGs can be applied to the
surface atomitcally (either only <emphasis>2</emphasis>, or
<emphasis>2</emphasis> and <emphasis>3</emphasis>).
</para>
</caption>
</mediaobject>
</figure>
<figure>
<title>Simple Synchronized Case</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-synchronized-state-1.png"/>
</imageobject>
<caption>
<para>
SS1 and SS2 are effectively synchronized. SS2 commits SCU <emphasis>1</emphasis>.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-synchronized-state-2.png"/>
</imageobject>
<caption>
<para>
SS1 commits SCU <emphasis>2</emphasis>. The direct child surfaces SS2 has the last SCU <emphasis>1</emphasis> in its queue, which is not reachable. This creates a dependency from SCU <emphasis>2</emphasis> to SCU <emphasis>1</emphasis>.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-synchronized-state-3.png"/>
</imageobject>
<caption>
<para>
SS1 commits SCU <emphasis>3</emphasis>. The direct child surfaces SS2 has the last SCU <emphasis>1</emphasis> in its queue, which is already reachable by SCU <emphasis>2</emphasis>. No dependency to SCU <emphasis>1</emphasis> is created. A dependency to the previous CU of the same queue (SCU <emphasis>2</emphasis>) is created.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-synchronized-state-4.png"/>
</imageobject>
<caption>
<para>
T1 commit DCU <emphasis>4</emphasis>. It is a candidate, its DAG does not contain any constraint and it can be applied.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/simple-synchronized-state-5.png"/>
</imageobject>
<caption>
<para>
The DAG gets applied to the surfaces atomically.
</para>
</caption>
</mediaobject>
</figure>
<figure>
<title>Complex Synchronized Subsurface Case 1</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case1-1.png"/>
</imageobject>
<caption>
<para>
Every DCU (<emphasis>1</emphasis> and <emphasis>6</emphasis>) contain
CUs with constraints in their candidate DAG
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case1-2.png"/>
</imageobject>
<caption>
<para>
Waiting until the <emphasis>buffer-sync</emphasis> constrain on CU
<emphasis>1</emphasis> is cleared, the candidate DAG of CU
<emphasis>1</emphasis> does not contain constraints and can be applied
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case1-3.png"/>
</imageobject>
<caption>
<para>
That leaves the candidate DAG of CU <emphasis>6</emphasis> which still
contains another CU with a <emphasis>buffer-sync</emphasis> constrain
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case1-4.png"/>
</imageobject>
<caption>
<para>
Waiting until the <emphasis>buffer-sync</emphasis> constrain on CU
<emphasis>6</emphasis> is cleared, the candidate DAG of
<emphasis>6</emphasis> does not contain CUs with constraints and can
be applied.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case1-5.png"/>
</imageobject>
<caption>
<para>
There is no DCU left and no constraint remaining. Nothing more can be
applied without a new CU.
</para>
</caption>
</mediaobject>
</figure>
<figure>
<title>Complex Synchronized Subsurface Case 2</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case2-1.png"/>
</imageobject>
<caption>
<para>
Both DCUs (<emphasis>1</emphasis> and <emphasis>6</emphasis>) have a
reachable DAG containing CU <emphasis>1</emphasis> with a constraint
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case2-2.png"/>
</imageobject>
<caption>
<para>
Waiting until the <emphasis>buffer-sync</emphasis> constrain on
<emphasis>1</emphasis> is cleared, both DAGs contain no CU with
constraints and can be applied in any order
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-subsurf-case2-3.png"/>
</imageobject>
<caption>
<para>
That leaves the same state as in the previous case
</para>
</caption>
</mediaobject>
</figure>
<figure>
<title>Synchronized to Desynchronized Subsurface</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-to-desync-subsurf-1.png"/>
</imageobject>
<caption>
<para>
There is one DCU (<emphasis>4</emphasis>) with its reachable DAG
that cannot be applied because CU <emphasis>4</emphasis> contains a
constraint
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-to-desync-subsurf-2.png"/>
</imageobject>
<caption>
<para>
Surface <emphasis>SS1</emphasis> transitions from effectively
synchronized to effectively desynchronized. SCU
<emphasis>2</emphasis> is reachable by DCU <emphasis>4</emphasis> so
nothing changes.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-to-desync-subsurf-3.png"/>
</imageobject>
<caption>
<para>
Surface <emphasis>SS1</emphasis> provides a new DCU
(<emphasis>5</emphasis>) but because the CU before
(<emphasis>2</emphasis>) is a Synchronized CU, it is not a candidate
</para>
</caption>
</mediaobject>
</figure>
<figure>
<title>Synchronized to Desynchronized Transition</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-to-desync-transition-1.png"/>
</imageobject>
<caption>
<para>
There are four SCUs and all surfaces are effectively synchronized.
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-to-desync-transition-2.png"/>
</imageobject>
<caption>
<para>
Surface <emphasis>SS1</emphasis> transitions to effectively
desynchronized and SCU <emphasis>2</emphasis> becomes a DCU because
it is not reachable from a DCU
</para>
</caption>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="images/content-updates/sync-to-desync-transition-3.png"/>
</imageobject>
<caption>
<para>
Surface <emphasis>SS2</emphasis> transitions to effectively
desynchronized. SCUs <emphasis>3</emphasis> and
<emphasis>4</emphasis> become DCUs because they are not reachable
from a DCU. SCU <emphasis>1</emphasis> does not change because it is
reachable by DCU <emphasis>2</emphasis>.
</para>
</caption>
</mediaobject>
</figure>
</section>
</chapter>