%BOOK_ENTITIES; ]> Content Updates
Overview 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. 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. 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. 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.
Rules The core protocol specifies the behavior in wl_subsurface and wl_surface.commit. The behavior can be summarized by the following rules: Content Updates (CU) contain all double-buffered state of the surface and selected state from their direct children. Surfaces which are effectively synchronized create Synchronized Content Updates (SCU), otherwise they create Desync Content Updates (DCU). When a CU is created, it gets a dependency on the previous CU of the same queues (if it exists). When a CU is created, it gets a dependency on the last SCU of direct child surfaces that are not reachable (if they exists). The CUs and their dependencies form a DAG, where CUs are nodes and dependencies are edges. All DCUs starting from the front of the queues until the first SCU or the back of the queue is reached are candidates. If the maximal DAG that's reachable from a candidate (candidate DAG) does not have any constraints, then this DAG can be applied. A DAG is applied atomically by recursively applying a content update without dependencies and removing it from the DAG. Surfaces transition from effectively sync to effectively desync after their parents. When a surface transitions to effectively desync, all SCUs in its queue which are not reachable by a DCU become DCUs.
Examples 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. 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.
Legend
Simple Desynchronized Case SS2 is effectively desynchronized and commits. This results in the desynchronized content update (DCU) 1. DCU 1 is a candidate, and the candidate DAG reachable from DCU 1 is only DCU 1 itself. DCU 1 and thus the candidate DAG does not have any constraints and can be applied. The content updates of the candidate DAG get applied to the surface atomically. T1 commits a DCU with a buffer-sync constraint. It is a candidate but its DAG can't be applied because it contains a constraint. T1 commits another CU (DCU 3) which is added at the end of the queue, with a dependency to the previous CU (DCU 2). Both DCU 2 and DCU 3 are candidates, but both DAGs contain DCU 2 with a constraint, and can't be applied. When the constraint gets cleared, both DAGs can be applied to the surface atomitcally (either only 2, or 2 and 3).
Simple Synchronized Case SS1 and SS2 are effectively synchronized. SS2 commits SCU 1. SS1 commits SCU 2. The direct child surfaces SS2 has the last SCU 1 in its queue, which is not reachable. This creates a dependency from SCU 2 to SCU 1. SS1 commits SCU 3. The direct child surfaces SS2 has the last SCU 1 in its queue, which is already reachable by SCU 2. No dependency to SCU 1 is created. A dependency to the previous CU of the same queue (SCU 2) is created. T1 commit DCU 4. It is a candidate, its DAG does not contain any constraint and it can be applied. The DAG gets applied to the surfaces atomically.
Complex Synchronized Subsurface Case 1 Every DCU (1 and 6) contain CUs with constraints in their candidate DAG Waiting until the buffer-sync constrain on CU 1 is cleared, the candidate DAG of CU 1 does not contain constraints and can be applied That leaves the candidate DAG of CU 6 which still contains another CU with a buffer-sync constrain Waiting until the buffer-sync constrain on CU 6 is cleared, the candidate DAG of 6 does not contain CUs with constraints and can be applied. There is no DCU left and no constraint remaining. Nothing more can be applied without a new CU.
Complex Synchronized Subsurface Case 2 Both DCUs (1 and 6) have a reachable DAG containing CU 1 with a constraint Waiting until the buffer-sync constrain on 1 is cleared, both DAGs contain no CU with constraints and can be applied in any order That leaves the same state as in the previous case
Synchronized to Desynchronized Subsurface There is one DCU (4) with its reachable DAG that cannot be applied because CU 4 contains a constraint Surface SS1 transitions from effectively synchronized to effectively desynchronized. SCU 2 is reachable by DCU 4 so nothing changes. Surface SS1 provides a new DCU (5) but because the CU before (2) is a Synchronized CU, it is not a candidate
Synchronized to Desynchronized Transition There are four SCUs and all surfaces are effectively synchronized. Surface SS1 transitions to effectively desynchronized and SCU 2 becomes a DCU because it is not reachable from a DCU Surface SS2 transitions to effectively desynchronized. SCUs 3 and 4 become DCUs because they are not reachable from a DCU. SCU 1 does not change because it is reachable by DCU 2.