Docs · Rendering: what GPUI can show, RenderSurface and its revision, a compiled example; AGENTS.md and README follow

This commit is contained in:
ZacharyZhang-NY committed 2026-10-04 00:30:12 -04:00
1 parent 36d5559682
commit b85955ae06
10 files changed
+123 -4

No files matched your search

+3
View File
@@ -145,6 +145,8 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- A file's icon comes from `primitives::file_icon`, one map from name to icon for every chapter, under the owner's `primitives::IconTheme`, whose rules by name and extension come first; `files::FileIcon` draws it. What fits across a width comes from `layout::columns_for`, and `layout::fit` adds the inset that centers it.
- Badges, tags and avatars live in `data_display`; forms and shell draw theirs from there, and `data_display` imports neither. A hue worn in a row, a label's or an event's, is `data_display::color_mark`, and `Palette::hue` gives the chart color by index. A tag is never wider than its container; a long label ends in an ellipsis beside its remove mark.
- A picture drawn in code reaches gpui as a `RenderImage`. A host that replaces one frees the old through `App::drop_image`, or every frame stays in the sprite atlas (the gallery's model).
- Pixels a host draws at a box's size go through `rendering::RenderSurface`: it asks for one BGRA frame at the box's device pixels in prepaint, draws again only for a new size or revision, and frees the old frame and, on leave, the last one. The revision covers all the frame reads, theme colors too.
- Tests type a shortcut as it is bound: a `secondary-` binding is Command on macOS and Control elsewhere, so its test types `secondary-`; `cmd-` is the platform key on every OS and reaches only handlers that read that modifier (`lists::SelectableList`). A Tab count runs through `forms::EyeDropper` only on macOS, where it is enabled.
- `img()` keeps loading state, and moves an animated picture's frames, only with an id; the frames hold still while its window is inactive or gpui's own `reduce_motion` is on, which the theme's reduced motion does not set. Content masks are rectangles, but `img()` draws only the part of its picture inside its box and rounds that part with its own radius, so a `Cover` crop keeps its corners.
- gpui reads a `&str` picture source with a scheme as a web address, and any other as an embedded asset. A file goes through `Path` (`documents::source` in the crate, `ui::picture` in the gallery).
- Taffy sizes a flex column by the height a ratio box has at its set width, before its cap, so chat's `shaped` pictures sit in a block inside such a column (`chat::ImageMessage`, `VideoMessage`, `ImageGrid`).
@@ -253,3 +255,4 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- 2026-09-28: SystemNotification is `prove`: gpui's `show_system_notification` posts only from an app bundle; unbundled, as `cargo run` starts the gallery, it logs and drops the call, and the web posts nothing.
- 2026-09-29: the ported debug gallery draws its heaviest page about 1.6 times slower than on gpui 0.2.2 (tooling: 9 fps, worst frame 117 ms, against 14 fps and 72 ms), with gpui's accessibility tree on or off (`Application::new_inaccessible` measured the same). Ely's code did not change there; the frame cost is gpui's.
- 2026-09-29: the web gallery runs on one thread, since a static site sends no cross-origin isolation, and draws through WebGL2 where WebGPU is missing. Its pictures are served beside the wasm, not embedded: the wasm stays under Cloudflare's 25 MiB, and a story fetches only its own. `wasm-opt -Oz` takes the wasm from 23.0 to 20.0 MB; gzip and brotli stay near 7.9 and 5.5 MB.
- 2026-10-04: chapter 44, Rendering, holds `RenderSurface` alone. gpui at 1a28cff paints `RenderImage`s everywhere, `CVPixelBuffer`s (NV12) on macOS's Metal renderer alone, and shares no device or shader hook, so shader, camera and GPU views are the host's, feeding the surface, and zero-copy or Metal, OpenGL, Vulkan and Direct3D surfaces are blocked (`tasks/ch44.md`). CI tests the crate and builds the gallery on Linux, Windows and macOS, arm64 and x86_64.
+4
View File
@@ -119,3 +119,7 @@ path = "examples/docs/overlays.rs"
[[example]]
name = "docs_i18n"
path = "examples/docs/i18n.rs"
[[example]]
name = "docs_render"
path = "examples/docs/render.rs"
+1 -1
View File
@@ -55,7 +55,7 @@ pnpm deploy # Cloudflare, through wrangler
## Build notes
- gpui compiles its Metal shaders at runtime here (`runtime_shaders`, on by default), so a full Xcode install is not required. gpui_platform needs `font-kit`, or macOS draws no text.
- Tested on macOS only. The capture tool needs macOS.
- CI tests the crate and builds the gallery on macOS, Windows and Linux, arm64 and x86_64. The capture tool needs macOS.
## License
+1 -1
View File
@@ -183,7 +183,7 @@ Per-component lines live in `tasks/`. Tags there:
- [x] T50c Rendering · Gallery: a Rendering page, its story, `stories.json`; the gallery runs on Linux
- [x] T50d Rendering · Web: the story runs live on the site from the wasm gallery, checked in a browser
- [x] T50e Rendering · Platforms: CI builds and tests the crate on Linux, macOS and Windows, arm64 and x86_64
- [ ] T50f Rendering · Docs: a guide with an example `scripts/check.sh` compiles; AGENTS.md
- [x] T50f Rendering · Docs: a guide with an example `scripts/check.sh` compiles; AGENTS.md
- [ ] T50g Rendering · Acceptance: the checklist, item by item
## T00 detail
+70
View File
@@ -0,0 +1,70 @@
use ely_gpui_component::{
Assets, buttons::Button, primitives::FocusScope, rendering::RenderSurface, theme::ActiveTheme,
};
use gpui::{
App, AppContext, Context, DevicePixels, FocusHandle, IntoElement, ParentElement, Render,
RenderImage, Size, Styled, Window, WindowOptions,
};
use image::{Frame, RgbaImage};
struct Stripes {
focus: FocusHandle,
count: u32,
}
/// `count` vertical stripes across `size`, in BGRA as gpui holds frames.
fn stripes(size: Size<DevicePixels>, count: u32) -> RenderImage {
let (width, height) = (size.width.0 as u32, size.height.0 as u32);
let pixels = RgbaImage::from_fn(width, height, |x, _| {
let lit = (x * count / width).is_multiple_of(2);
let shade = if lit { 220 } else { 40 };
image::Rgba([shade, shade, shade, 255])
});
RenderImage::new(vec![Frame::new(pixels)])
}
impl Render for Stripes {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let colors = &cx.theme().colors;
let count = self.count;
let this = cx.entity();
FocusScope::new(&self.focus)
.root()
.size_full()
.flex()
.flex_col()
.gap_4()
.p_6()
.bg(colors.bg)
.text_color(colors.fg)
.child(
RenderSurface::new("stripes", 2.0, move |size, _| stripes(size, count))
.revision(u64::from(count)),
)
.child(
Button::new("more", "More stripes").on_click(move |_, _, cx| {
this.update(cx, |stripes, cx| {
stripes.count += 2;
cx.notify();
});
}),
)
}
}
fn main() {
gpui_platform::application()
.with_assets(Assets)
.run(|cx: &mut App| {
ely_gpui_component::init(cx);
cx.open_window(WindowOptions::default(), |window, cx| {
cx.new(|cx| {
let focus = cx.focus_handle();
window.focus(&focus, cx);
Stripes { focus, count: 8 }
})
})
.expect("window failed to open");
cx.activate(true);
});
}
+1
View File
@@ -11,6 +11,7 @@ const GUIDES: [string, string][] = [
["motion", "Motion"],
["icons", "Icons, fonts and assets"],
["overlays", "Overlays"],
["rendering", "Rendering"],
["i18n", "Internationalization"],
["webassembly", "WebAssembly"],
["testing", "Testing"],
+1 -1
View File
@@ -6,7 +6,7 @@ import { Marked, type Tokens } from "marked";
const at = (relative: string) => fileURLToPath(new URL(relative, import.meta.url));
/** The guides in reading order, by file name. */
const ORDER = ["introduction", "installation", "getting-started", "theme", "focus", "motion", "icons", "overlays", "i18n", "webassembly", "testing"];
const ORDER = ["introduction", "installation", "getting-started", "theme", "focus", "motion", "icons", "overlays", "rendering", "i18n", "webassembly", "testing"];
export interface Guide {
/** The path segment; the introduction has none. */
+1 -1
View File
@@ -1,6 +1,6 @@
# Introduction
Ely is a component library for [GPUI](https://www.gpui.rs), the Rust UI framework behind Zed. It holds 43 chapters, from primitives and forms to charts, a code editor, maps and a terminal. Every component draws in light and dark, and every one shows on the [components page](/components/): live, or as a native capture where a browser cannot run it.
Ely is a component library for [GPUI](https://www.gpui.rs), the Rust UI framework behind Zed. It holds 44 chapters, from primitives and forms to charts, a code editor, maps, a terminal and render surfaces. Every component draws in light and dark, and every one shows on the [components page](/components/): live, or as a native capture where a browser cannot run it.
The guides run in the order of the rail, each building on the one before.
+39
View File
@@ -0,0 +1,39 @@
# Rendering
`ely_gpui_component::rendering::RenderSurface` shows pixels your app draws: a map's texture layer, a plot, a frame from a camera or another renderer. It runs wherever GPUI runs: macOS, Windows and Linux, on arm64 and x86_64, and in the browser.
## What GPUI can show
GPUI at Ely's revision puts pixels on screen three ways:
| Way | Where | What it takes |
| --- | --- | --- |
| `Window::paint_image` with a `RenderImage` | Every platform and the browser | Frames in BGRA. Each new frame goes up to GPUI's sprite atlas. |
| `Window::paint_surface` with a `CVPixelBuffer` | macOS alone | NV12 video frames. The wgpu and DirectX renderers drop it. |
| A native view laid over the window | Per platform | It draws above everything GPUI paints, and GPUI's clipping does not reach it. |
GPUI shares no GPU device and runs no outside shader. A shader, a 3D scene or a camera runs on your side, on your own device, and hands its frames to `RenderSurface` through the first way. Zero-copy textures and Metal, OpenGL, Vulkan or Direct3D surfaces inside GPUI's own drawing have no way in.
## The surface
`RenderSurface::new(id, ratio, draw)` takes the box's width over its height and a function that draws one frame. It fills the width it is given and keeps the ratio.
| Item | Does |
| --- | --- |
| `draw(size, cx)` | Returns a `RenderImage` of one frame, exactly `size` in device pixels: the box's size times the window's scale factor, so every pixel lands on one screen pixel. |
| `.revision(n)` | Your count of the content. The surface draws again when the size or the revision changes, and only then. |
A frame of the wrong size, or with more than one picture, panics with the surface's id. A box with no width or height draws nothing. The surface frees the old frame from the atlas each time it draws, and its last frame when it leaves the window. Each draw logs its size, revision and time at `debug`.
`draw` runs on the main thread in the surface's prepaint. Keep it to work one frame can afford, or draw elsewhere and copy the finished pixels in under a new revision.
## The revision
The revision covers everything a frame reads. If the frame takes a color from the theme, fold the color into it, or a change of mode leaves the old colors on screen. The gallery's story hashes the slider's place and both colors it paints with.
## A window with a surface
The example draws stripes across a surface and adds two each time the button is pressed.
```rust example=render
```
+2
View File
@@ -356,3 +356,5 @@
| T50c | 1 | PASS | the gallery's missing non-macOS post_scroll stub added |
| T50d | 1 | PASS | the e2e test failed against a gallery whose surface never redraws |
| T50e | 1 | PASS | run 37175728059: seven jobs green; 21 tests typed cmd- where init binds secondary- |
| T50f | 1 | FAIL | AGENTS.md said a literal cmd- is no shortcut off macOS; gpui parses cmd- as the platform key everywhere |
| T50f | 2 | PASS | |