Skip to content

feat(core): motion blur preview draws fewer filter copies at lower preview quality - #7547

Merged
vanceingalls merged 2 commits into
mainfrom
feat/blur-preview-quality
Nov 11, 2026
Merged

vanceingalls merged 2 commits into
mainfrom
feat/blur-preview-quality

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

What

Motion blur's filter tier now follows the host's preview quality. In preview it draws 32 offset copies per mover at Full, 16 at 1/2, and 8 at 1/4 and 1/8. Renders (engineMode) always draw 32. The level arrives through the existing set-preview-quality bridge message, which already drives the lighter video copies.

The runtime's preview facts also change. lighterAt used to list only levels where a heavy video gets a lighter copy. It now also lists 1/2 and 1/4 once the filter tier is on and has movers. Hosts such as HyperFrames Desktop step quality down on choppy playback only to a level listed in lighterAt. Without this, a film whose only load is blur was never stepped down (no_lighter).

Why

On GPU Chrome, preview lag with blur on comes from the GPU process rasterizing each mover's SVG filter: 32 feOffset and 31 arithmetic feComposite. Cost grows with the number of movers moving at once. On a 60 Hz headless Chrome 157 (M4 Pro), 3 interleaved reps, fps with blur off, then 32 / 16 / 8 copies:

  • 50 movers: 59.5, then 17.5 / 29.6 / 52.1.
  • 100 movers: 59.9, then 6.9 / 11.8 / 25.6.
  • k3-promo (106 movers): 59.6, then 42.8 / 43.8 / 52.9.

The machine was loaded (load average 45 to 92), so only the order of the arms is quotable, not the absolute fps.

Picture quality was scored against a sub-frame reference: the blur-off film averaged over 32 instants across the shutter. The measure is the engine oracle's worst 16 px tile; thresholds.ts puts the smallest real symptom at 14.47.

  • 16 copies against 32: worst tile 11.9 or less on every frame measured.
  • 8 copies: passes on translating content, but reaches 28.5 on turning content in k3-promo. That is why 8 waits for 1/4.
  • A single feGaussianBlur was also tested and rejected: worse on every kind of motion, and it still costs GPU time for each mover.

Preview and render

At 1/2 and below, preview and render differ by the numbers above. The motion blur guide now says so. The capture tier is unchanged.

Tests

  • One new case in captureMotionBlur/index.test.ts: 16 copies at 1/2, 8 at 1/8, 32 for an engine frame, and lighterAt levels only once movers are found. It fails if the preview tap count is reverted.
  • All 243 captureMotionBlur tests pass.
  • Core typecheck, oxlint and oxfmt pass.

Rollout

Desktop picks this up after the next OSS release and a pin bump. No Desktop code change is needed: the quality row and the auto step-down already send the level.

🤖 Generated with Claude Code

…eview quality

The filter tier's preview draws 16 copies at preview quality 1/2 and 8 at 1/4 and
below; renders keep 32. Preview facts now list those levels in lighterAt once the
filter tier has movers, so a host that steps quality down on choppy playback can do
so for a film whose only load is blur.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Nov 11, 2026, 8:02 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 112 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head 961dfb6

Preview taps follow the level, renders stay at 32 (engineMode), and planFilter's step-sized softening scales with the wider gaps between copies on its own. CI is green on this head.

Scope
  • Read all 5 files end to end, plus filterPlan.ts, frames.ts/tapsFromReads, mediaProxy.setPreviewQuality, and the Desktop step-down consumer (QualityToast.tsx onChoppy → lighterBelow).
  • Rebuilding the layer in place (layerOf removes the old <filter> and builds a new one under the same id): I checked this in Chromium headless-shell 1237. An element styled filter: url(#hf-mb-0) picks up the replacement filter on the next frame without its style being touched.
  • lighterAt: Desktop only asks for facts when playback turns choppy. By then a paint has run discovery, so the movers > 0 gate is satisfied. At 1/4 only videos can list 1/8, which matches 8 copies at both levels.
  • One thing to watch after the pin bump: the step-down still falls back to busy when long main-thread frames reach 50% or more. On a film with many movers, the filter tier's per-frame pose reads count toward that share, so check Desktop's stepDownDeclined() on a blur-heavy film.

— Genesis

@vanceingalls
vanceingalls added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 6cc734f Nov 11, 2026
102 of 108 checks passed
@vanceingalls
vanceingalls deleted the feat/blur-preview-quality branch November 11, 2026 23:21
@heygengenesis heygengenesis Bot mentioned this pull request Nov 12, 2026
4 of 6 tasks
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.

1 participant