feat: incremental (damage-region) present for an opt-in root - #7
Merged
Conversation
A root passed to Run may now implement the opt-in capability
RenderDamaged(p painter.Painter, th *toolkit.Theme) []toolkit.Rect
(DamageRenderer, satisfied by scene.HostRoot). When it does, Run draws the
frame through it and packs+presents ONLY the coalesced union of the returned
damaged rectangles via the existing small-rect paths -- X11 MIT-SHM
ShmPutImage over a framebuffer-mirroring segment, or a wl_shm sub-rect
DamageBuffer -- instead of the whole surface every frame. A plain
toolkit.Widget root keeps the full-surface path unchanged. The first frame,
a resize (framebuffer reallocated) and an X11 Expose (server discarded
contents) still present the full surface, then Run resumes incremental.
Wayland double-buffer correctness: because the two wl_shm pool buffers
alternate, the chosen buffer is up to a frame stale outside this frame's
damage; presentDamaged packs everything the buffer OWES (per-buffer damage
age), so every attached buffer holds the whole correct image, while surface
damage marks only this frame's changed rectangles. The framebuffer persists
across frames, so pixels outside the damage are already correct.
Verification (Xvfb + headless-sway VM, arm64/amd64):
- pixel-identity: framebuffer updated only by incremental render stays
byte-identical (fnv1a) to a fresh full repaint over a scripted change log,
each frame touching ~one cell; and the Wayland buffer-age invariant is
asserted at the pool-buffer level (fully current after every frame).
- live lanes: TestLiveX11Damage / TestLiveWaylandDamage flip one quadrant via
a real injected click and assert the clicked quadrant took the new colour
while the other three are pixel-unchanged.
- measurement (800x600, 1200 cells, one-cell change): full 3.06 ms / 1.92 MB
per frame vs incremental 0.75 ms / 1.44 KB -- 4.1x faster, ~1330x fewer
bytes packed/blitted.
- internal/x11 + internal/wayland stay at 100% coverage (-race); damage.go
helpers 100%; all cross-build + arch lanes unaffected.
Requires go-widgets/toolkit v0.132.0 (scene.HostRoot).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Headless sway configures the toplevel to the full 800x600 output, so the four-quadrant live-damage pattern must lay its cells out relative to its bounds (as the existing patternRoot does) instead of fixed 200x160 rects, which left the sampled points on the background. quadBox re-flows its four children into the quadrants on every SetBounds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first xdg configure arrives during the run loop, not at bring-up, so paintInitial ran with the surface not yet configured. presentDamaged then no-opped (correctly), but drawIncremental had already CONSUMED the scene's full-surface seed damage via RenderDamaged -- so once the configure arrived, every later frame reported empty damage and the window presented nothing (a blank, background-only surface on a real compositor). The plain path is immune because it re-renders the whole surface each frame. Gate the incremental path on w.configured: skip render+present until the surface is configured, keeping the pending damage intact so the first configure draws and presents the full seed frame. Proven on headless sway: TestLiveWaylandDamage now flips one quadrant via an injected click and sees the clicked quadrant change while the other three stay pixel-unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the gap between the scene layer's per-frame damage region and the window present path: a live window can now pack+present ONLY the changed rectangle(s) instead of the full surface each frame.
The seam
A root passed to
Runmay implement the opt-in capabilitysatisfied by
scene.HostRoot(go-widgets/toolkit#174, v0.132.0). When the root implements it,Rundraws the frame through it and packs+presents only the coalesced union of the returned rectangles via the existing small-rect paths — X11 MIT-SHMShmPutImageover a framebuffer-mirroring segment, or a wl_shm sub-rectDamageBuffer. A plaintoolkit.Widgetroot keeps the full-surface path unchanged. The first frame, a resize (framebuffer reallocated) and an X11Exposepresent the full surface, thenRunresumes incremental.Wayland double-buffer correctness
The two wl_shm pool buffers alternate, so a re-chosen buffer is up to a frame stale outside this frame's damage.
presentDamagedpacks everything the buffer owes (per-buffer damage age), so every attached buffer holds the whole correct image, while surface damage marks only this frame's rectangles. The framebuffer persists across frames, so pixels outside the damage stay correct.Verification (Xvfb + headless sway, on a Linux VM; arm64 locally, amd64 in CI)
TestLiveX11Damage/TestLiveWaylandDamageflip one quadrant via a real injected click (xdotool / sovereign virtual-pointer) and assert the clicked quadrant took the new colour while the other three are pixel-unchanged.internal/x11+internal/waylandstay at 100% coverage (-race);damage.gohelpers 100%; cross-build (windows/darwin/6-linux) + arch lanes unaffected.Pure-Go, CGO-free.
🤖 Generated with Claude Code