Skip to main content

Module retained_rendering

Module retained_rendering 

Source
Expand description

§Retained view rendering

A view keeps what it drew between frames. When the window draws again, GPUI skips the render call of each view where nothing it read has changed, and draws that view’s previous output instead: its layout, the state its elements kept from prepaint, its painted primitives and its accessibility nodes. An Entity or AnyView passed as a child is kept this way; there’s nothing to turn on.

This page explains what a kept view depends on, where its output can be reused, and how layout is carried between renders. The frame describes the phases this work skips.

§What a view depends on

While a view renders, prepaints and paints, GPUI records what it reads:

  • the entities it reads or updates, including through a context;
  • the globals it reads;
  • the pointer position, modifiers and input modality, when it asks the window for them;
  • the text style, rem size and scale factor it inherits.

A view nested in another adds its reads to its parent’s, so a parent that was kept also covers the views inside it. The view renders again on the next frame when its own entity or an entity it read is notified with Context::notify, when a global it read is updated, when an input it read changes, or when its inherited text style, rem size or scale factor changes. Window::refresh renders every view.

A change that GPUI can’t see doesn’t render a view again. Mutating an entity without calling notify, or a value behind an Rc<RefCell<_>> or an atomic, leaves a kept view showing what it drew before. Notify the view’s entity after such a change. The performance guide has a test that shows both cases.

§Where output is reused

A view’s previous output is drawn again only where it was drawn last frame: at the same bounds, inside the same content mask, with the same text direction and Unicode bidirectional mode. A view that moved or changed size renders again, even when nothing it read changed. Output is also rebuilt when a screen reader connects and the view drew without the accessibility tree, and on every frame while the inspector is picking an element.

Reusing output costs a check of each recorded dependency, and a copy of the view’s recorded prepaint state and primitives into the new frame. Both grow with what the view drew, not with what its render call would have done. A view notified every frame gains nothing from being kept.

§Choose how a view is laid out

An entity passed as a child is laid out by its content, in the parent’s layout. Give it a fixed root style with Entity::cached or AnyView::cached when the parent should decide its size instead:

use gpui::{Context, Entity, IntoElement, Render, StyleRefinement, Window, div, prelude::*, px};

struct Sidebar;

impl Render for Sidebar {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div().child("Navigation")
    }
}

fn sized_by_content(sidebar: Entity<Sidebar>) -> impl IntoElement {
    div().child(sidebar)
}

fn sized_by_parent(sidebar: Entity<Sidebar>) -> impl IntoElement {
    div().child(sidebar.cached(StyleRefinement::default().w(px(240.)).h_full()))
}

With cached, the parent lays out a box with the style you pass, and the view’s content is laid out inside the size that box gets. The content can’t change the box’s size, so choose a style that sizes the box without its content, such as a fixed width or size_full.

§How layout carries over

Each view keeps its own layout nodes between frames, and its parent reaches them through one node. A kept view’s nodes stay as they were laid out. When a view renders again, each node whose style and children match the node at its place in the previous render keeps that node’s computed layout. Text whose content, style and fonts are unchanged keeps its measurement too, so it isn’t shaped again. A custom element that measures itself with Window::request_measured_layout is measured again whenever its view renders.

§Check a change against a full render

randomized_element_tree_matches_full_render draws seeded trees of elements and views, applies random mutations, and compares each frame with one drawn from scratch. Run it after changing how views are kept or invalidated:

cargo test -p gpui-ce --lib randomized_element_tree