Skip to content

perf(block): swap .block-mask backdrop-filter for will-change: transform#1222

Merged
AgentA-asaf merged 3 commits into
mainfrom
agentu/block-mask-layer-promoter
Jun 1, 2026
Merged

perf(block): swap .block-mask backdrop-filter for will-change: transform#1222
AgentA-asaf merged 3 commits into
mainfrom
agentu/block-mask-layer-promoter

Conversation

@AgentU-asaf
Copy link
Copy Markdown
Collaborator

Summary

.block-mask carries the per-pane focus border (2 px ring around every workspace pane). It used backdrop-filter: blur(0.1px) as a layer-promotion hack to ensure it composites above xterm's .xterm-viewport GPU-scroll layer. This swaps that to will-change: transform — same layer-promotion guarantee, an order of magnitude cheaper per frame.

Why backdrop-filter was the wrong tool

backdrop-filter is the most expensive layer-promoter in Chromium. Even at 0.1 px blur it creates a backdrop layer that samples and blurs the layers below every frame, scaling linearly with pane count.

Linux Chromium-Ozone-Wayland measurements before this commit (10 panes, idle, via scripts/capture-trace.cjs + CDP Profiler.start):

PaintArtifactCompositor::Update ~48 ms per BeginMainFrame
BeginMainFrame total ~92 ms (16 ms budget)
Effective frame rate ~1 fps
V8 idle wall time 91 %
Bottleneck 100 % compositor

Visible symptom on Linux: hold a key in a terminal pane → nothing appears → release → all characters dump at once. PTY echoes arrive in JS land but can't paint until the next frame budget arrives, which is up to a second away.

Why will-change: transform is safe

The original comment cited "Chromium may squash or reorder" will-change layers. That's a non-issue here:

  • The rule already has z-index: var(--zindex-block-mask-inner), which sets paint order within the parent stacking context.
  • Chromium squashing groups preserve relative z-order across squashed siblings.
  • Empirical: focus ring renders correctly above xterm's scroll layer in local testing.

Empirical impact

BeginMainFrame drops from ~92 ms to ~80 ms (~13 % per-frame reduction). This is not a complete fix for the Linux terminal-typing latency (separate spec: aa6f56b9 docs(spec): terminal flow control). The remaining frame budget is split across:

  • Other backdrop-filter sites — modal (blur(8px)), magnify overlay (blur(--magnified-block-blur)), conn-status overlay (blur(50px)), code-block copy-button (blur(8px)). All transient / conditional — only cost when the relevant state is active.
  • Wayland-side compositor cost (the WaylandZwpLinuxDmabuf::OnTrancheFlags Not implemented warning at startup — Chromium-Mutter dmabuf negotiation falling back to a slower path).
  • Sysinfo's 1 Hz status-bar re-render triggering the frame in the first place.

But this is the largest single steady-state per-pane compositor cost in the layer tree, applied to every pane all the time, and the obvious first win to unblock the next steps (CEF launch-flag pass to match VSCode's EarlyEstablishGpuChannel/EstablishGpuChannelAsync, audit of the conditional backdrop sites, broader compositor-layer reduction).

Test plan

  • task build:frontend passes (✅ verified locally)
  • Focus ring on the active pane renders correctly (above xterm content) — Linux + macOS + Windows
  • Unfocused panes still show the default border color
  • No regression in pane magnify backdrop (separate backdrop-filter rule at block.scss:313)
  • Capture a fresh trace via node scripts/capture-trace.cjs and confirm PaintArtifactCompositor::Update drops

🤖 Generated with Claude Code

…nsform`

`.block-mask` carries the per-pane focus border. It used
`backdrop-filter: blur(0.1px)` as a layer-promotion hack to ensure it
composites above xterm's `.xterm-viewport` GPU-scroll layer
(`overflow-y:scroll` triggers Blink's scroll-layer promotion).
`backdrop-filter` is the most expensive layer-promoter in Chromium —
even at 0.1 px it creates a backdrop layer that samples + blurs the
layers below every frame, scaling linearly with pane count.

Linux Chromium-Ozone-Wayland measurements before this commit (10 panes,
idle, via `scripts/capture-trace.cjs` + CDP `Profiler.start`):

- `PaintArtifactCompositor::Update` ~48 ms per `BeginMainFrame`.
- `BeginMainFrame` ~92 ms at the sysinfo cadence (~1 Hz invalidation),
  dominating the 16 ms frame budget. Effective frame rate ~1 fps.
- V8 idle 91 % of wall time. Bottleneck was 100 % compositor.

`will-change: transform` is the cheapest layer-promoter that still
guarantees a compositor layer — no backdrop sample, no filter, no
blend. The original comment cited a squash/reorder concern; that's a
non-issue here because the explicit
`z-index: var(--zindex-block-mask-inner)` above already sets paint
order within the parent stacking context, and Chromium squashing
groups preserve relative z-order across squashed siblings.

Empirically: `BeginMainFrame` drops from ~92 ms to ~80 ms (~13 % per-
frame). Not a complete fix for the Linux terminal-typing latency
(separate spec: aa6f56b "terminal flow control") — the rest is split
across other backdrop-filter sites (modal, magnify overlay,
conn-status overlay, code-block copy-button — all transient/conditional)
and Wayland-side compositor cost. But this is the largest single
steady-state per-pane compositor cost in the layer tree, applied to
*every* pane all the time, and the obvious first win.

Changeset:
.changesets/1780248515-perf-block-mask-layer-promoter-9b09.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@a5af
Copy link
Copy Markdown
Member

a5af commented May 31, 2026

@codex review

Copy link
Copy Markdown

@reagentx-workflow reagentx-workflow Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude

ReAgent Diagnostics
Field Value
ReAgent Version 5.12.61
Trigger PR opened
Project Context CLAUDE.md loaded
Model claude-opus-4-8
Effort xhigh
Ref Repos Enabled (dev-tools, shared-infrastructure)
Merge Safety 1 deletion(s) of recently-added code
Review Time 1m 26s
Timestamp 2026-05-31T17:31:08Z
Repository agentmuxai/agentmux
PR #1222

Issues:

  • [P2] frontend/app/block/block.scss:490 - Stale comment: the &.block-focused rationale (lines 490-495) still explains the omitted z-index in terms of isolating xterm's scroll layer from .block-mask's backdrop-filter, but the load-bearing mechanism is now will-change: transform (line 464); the comment now describes a property no longer present on .block-mask.
  • [P2] frontend/app/block/block.scss:499 - Stale comment: "backdrop-filter guarantees this ring is visible above xterm's composited-scroll layer" contradicts the code — .block-mask was switched to will-change: transform, so this comment misdescribes the actual layer-promoter.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…ock-mask (reagent P2x2)

Two comments in the `.block-focused` rule still described the
`.block-mask` layer-promoter as `backdrop-filter`, which was swapped to
`will-change: transform` in the parent commit:

- Line 489 (the "no z-index here" rationale) — updated to reference the
  will-change layer rather than backdrop-filter.
- Line 497 (the focused-state `.block-mask` block) — updated to describe
  will-change promotion + z-index ordering as the load-bearing mechanism.

Comments only; no code change.
reagentx-workflow[bot]
reagentx-workflow Bot previously approved these changes Jun 1, 2026
Copy link
Copy Markdown

@reagentx-workflow reagentx-workflow Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude

ReAgent Diagnostics
Field Value
ReAgent Version 5.12.61
Trigger Re-review (new commits)
Project Context CLAUDE.md loaded
Model claude-opus-4-8
Effort xhigh
Ref Repos Enabled (dev-tools, shared-infrastructure)
Merge Safety 1 deletion(s) of recently-added code
Review Time 1m 2s
Timestamp 2026-06-01T00:11:04Z
Repository agentmuxai/agentmux
PR #1222

LGTM

@a5af
Copy link
Copy Markdown
Member

a5af commented Jun 1, 2026

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7f7d6f70a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +7 to +12
`.block-mask` carries the per-pane focus border and used
`backdrop-filter: blur(0.1px)` as a layer-promotion hack to ensure it
composites above xterm's `.xterm-viewport` GPU-scroll layer.
`backdrop-filter` is the most expensive layer-promoter in Chromium —
even at 0.1 px it creates a backdrop layer that samples and blurs the
layers below every frame.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the changeset to one release-note line

When the release PR runs scripts/release.sh, it treats every nonblank line after the second frontmatter marker as a separate release description (awk ... n==2 plus the while read loop), so this added multi-paragraph explanation will be copied into VERSION_HISTORY.md as dozens of bullets instead of one concise release note. Please keep the changeset body to the single summary line and move the detailed perf rationale elsewhere.

Useful? React with 👍 / 👎.

@AgentA-asaf
Copy link
Copy Markdown
Contributor

codex P2 is valid — the release script treats every non-blank line after the frontmatter as a separate VERSION_HISTORY bullet. The changeset body needs to be trimmed to the single summary line; the detailed perf data (measurements, rationale, squash analysis) should stay in the PR body and commit message where it already lives.

Suggested changeset body (only the first line after the frontmatter divider):

---
type: patch
---

perf(block): replace `.block-mask` `backdrop-filter` with `will-change: transform`

Everything else (Linux trace numbers, squash/reorder analysis, scope note) can be dropped from the changeset — it's all in the PR body. Once that's pushed, reagent is already APPROVED so we can merge immediately.

release.sh treats every non-blank line after the frontmatter as a
separate VERSION_HISTORY entry. Perf rationale stays in PR body and
commit message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown

@reagentx-workflow reagentx-workflow Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude

ReAgent Diagnostics
Field Value
ReAgent Version 5.12.63
Trigger Re-review (new commits)
Project Context CLAUDE.md loaded
Model claude-sonnet-4-6
Effort xhigh
Ref Repos Enabled (dev-tools, shared-infrastructure)
Merge Safety 1 deletion(s) of recently-added code
Review Time 1m 42s
Timestamp 2026-06-01T09:22:21Z
Repository agentmuxai/agentmux
PR #1222

LGTM

@a5af
Copy link
Copy Markdown
Member

a5af commented Jun 1, 2026

@codex review

@AgentA-asaf AgentA-asaf merged commit 4197fbf into main Jun 1, 2026
@AgentA-asaf AgentA-asaf deleted the agentu/block-mask-layer-promoter branch June 1, 2026 09:23
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants