Skip to content

Fork patch: Preview motion blur filter copies - #12

Draft
ocboogie wants to merge 1 commit into
mainfrom
patch/preview-motion-blur-filter-copies
Draft

ocboogie wants to merge 1 commit into
mainfrom
patch/preview-motion-blur-filter-copies

Conversation

@ocboogie

@ocboogie ocboogie commented Nov 5, 2026

Copy link
Copy Markdown

🤖 AI agent: Arcade fork patch. Do not merge.

This draft shows patch/preview-motion-blur-filter-copies against main, the upstream mirror, so the patch has one page for its diff, its revisions and its reason. Arcade's /hyperframes-update rebases the branch onto main and cherry-picks it onto arcade; this PR is not part of that flow. When a rebuild changes the patch, its git range-diff against the previous sync/<date>/preview-motion-blur-filter-copies tag is posted as a comment.

Why

The runtime's filter tier draws motion blur as 32 SVG offset copies of every moving element, and each copy is a full-size filter pass. With dozens of movers, live playback in Chrome without html-in-canvas drops frames. The patch uses fewer copies only while the page plays:

  • Live playback (preview) draws 8 copies per mover; engine-mode seeks (renders) keep 32.
  • The existing softening along the motion closes the gaps between copies, so 8 copies show no visible stepping at native resolution. The streak is about 9% shorter.
  • A page that plays and then renders rebuilds each mover's filter in place, so the render still gets 32.

Checked locally on Chrome with an 82-mover scene: dropped frames fall from 15.7% to 0.1% with GPU, and from 25.4% to 2.3% without.

Upstream

Kept local. Upstream hard-codes 32 copies for both playback and render, and latest (0.9.17) still does.

Drop when

Upstream lets the preview filter tier use fewer copies than renders.

Arcade

Tracked in Arcade's issue tracker.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Edit accuracy: accurate 271 (base branch 271), smooth 202 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 (0)

The filter tier stacks 32 offset copies of every moving element, and each
copy is a full-size filter pass. With dozens of movers, live playback in
Chrome without html-in-canvas drops frames (one 82-mover scene: 16% with GPU,
25% without). Eight copies, with the existing softening of 0.35x the step
between copies, show no stepping at native resolution and drop 0.1% / 2.3%
on the same scene.

Renders (engine-mode seeks) keep 32 copies. A page that plays and then
renders rebuilds each mover's filter in place.
@valeriangalliat
valeriangalliat force-pushed the patch/preview-motion-blur-filter-copies branch from 17e2c6e to 69fa232 Compare November 10, 2026 18:00
@valeriangalliat

Copy link
Copy Markdown
Collaborator

🤖 AI agent: sync/2026-11-10: rebased onto upstream cda84222a (just past v0.9.30). Conflict in filterTier.ts: upstream reworked the readMoverFrames call. The one changed argument (taps) is re-applied on upstream's version.

1:  17e2c6eac ! 1:  69fa23201 perf(core): draw live-playback filter-tier motion blur with 8 copies
    @@ Commit message
         renders rebuilds each mover's filter in place.
     
      ## packages/core/src/runtime/captureMotionBlur/filterTier.ts ##
    -@@ packages/core/src/runtime/captureMotionBlur/filterTier.ts: import { createPoseReader } from "./pose";
    +@@ packages/core/src/runtime/captureMotionBlur/filterTier.ts: import type { MotionBlurFingerprint } from "../../motionBlurFingerprint";
      import type { CaptureMotionBlurHost, Config, Report, Tier } from "./tier";
      
      const NS = "http://www.w3.org/2000/svg";
    @@ packages/core/src/runtime/captureMotionBlur/filterTier.ts: export function creat
            defs.setAttribute("width", "0");
     @@ packages/core/src/runtime/captureMotionBlur/filterTier.ts: export function createFilterTier(
            node(defs, "defs", {});
    -       root.append(defs);
    +       document.documentElement.append(defs);
          }
     -    layer = buildLayer(defs.querySelector("defs") ?? defs, layers.size, el);
     +    const id = old ? old.filter.id : `${FILTER_ID}-${layers.size}`;
    @@ packages/core/src/runtime/captureMotionBlur/filterTier.ts: export function creat
          const base = strip(el) || layer.css;
          el.style.filter = base ? `${base} ${layer.url}` : layer.url;
     @@ packages/core/src/runtime/captureMotionBlur/filterTier.ts: export function createFilterTier(
    -       t,
    -       reads: engineMode ? RENDER_READS : PREVIEW_READS,
    -       shutterSeconds: config.shutterDeg / 360 / host.fps,
    --      taps: COPIES,
    -+      taps: engineMode ? RENDER_COPIES : PREVIEW_COPIES,
    -       frameW: width,
    -       frameH: height,
    -     });
    +         t,
    +         reads: engineMode ? RENDER_READS : PREVIEW_READS,
    +         ...shutterTimes(config.shutterDeg, config.phaseDeg, host.fps),
    +-        taps: COPIES,
    ++        taps: engineMode ? RENDER_COPIES : PREVIEW_COPIES,
    +         frameW: width,
    +         scene: root,
    +         frameH: height,
     
      ## packages/core/src/runtime/captureMotionBlur/index.test.ts ##
     @@ packages/core/src/runtime/captureMotionBlur/index.test.ts: describe("filter fallback", () => {

This branch has not been deployed

No deployments
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.

2 participants