Settings > Previews > Lower Resolution When Playback Can't Keep Up (on by
default): a preview at Resolution Auto that stalls on uncached frames and
shows under 80 % of its frame rate for two seconds plays on at half the
scale, and two seconds later at a quarter if still slow, no lower than the
Adaptive Resolution Limit. Stopping goes back to the viewer's resolution
and Cache Frames When Idle fills it in. An explicit Resolution, the comp's
or the Preview panel's, is kept as set.
First playback of a 1080p comp with a blur (cache_bench --play): 9.0 to
22.1 frames/s on the CPU (about 27 once at Half), 4.1 to 16.1 with Mercury
Auto on an integrated GPU; plain clips that keep up stay at Full.
--play SECS runs the app headless (egui_kittest) on the bench comp, with
the viewer at 100 %, Resolution Auto, nothing cached and Cache Frames When
Idle off, presses play and steps it for SECS seconds of wall time. It
prints the frames shown per second, the time the clock stalled on uncached
frames, the frame reached and the share of passes at each viewer scale.
--gpu composites on the GPU (Mercury Auto) as the desktop app does;
--res N sets the comp's Resolution to every N-th pixel.
An 8 bpc render at Full quantises plain footage straight away, so the
media pool now keeps those movie frames as the 8-bit levels it is
quantised to (a quarter of the memory) and the renderer expands them
into the layer buffer: the same pixels to the bit. Other depths,
reduced renders, masks, colour profiles, fields, frame blending,
Cineon, Invert Alpha, linear light and 10/12-bit or float movies keep
the float path.
First playback of plain 1080p clips at Full caches ~43% faster on the
CPU, and the footage cache holds 4x the frames.
A preview renders up to 12 frames at once, and Auto only learns a frame's
time when it is done. A starting preview sent every lane to the GPU for
its warm-up, and on an integrated GPU 2–4× slower than the CPU each first
playback stalled. Now at most one GPU warm-up frame (and one re-probe frame
per side) runs at once, a frame's time counts per frame running on its side
at once, and the warm-up starts on the CPU, so the frames on screen come
first. Iris Xe, 1080p: Auto went from 13–34% slower than CPU only to 0–17%.
--gpu renders on a headless GPU of its own and reads the frames back, as the
desktop viewer does; --backend cpu|gpu|auto picks the compositor (default
auto, the viewer's Mercury GPU Acceleration). With auto the bench prints
Auto's per-comp timing history: how many frames each side rendered and how
long they took.
A footage layer's frame was copied from the footage cache, converted from
its colour profile, then quantised to the project depth in masked_source:
three passes over a 33 MB 1080p float frame. Footage without masks now
comes quantised from one fused pass (Pipe::copy_converted); other source()
callers still get unquantised pixels. Bit-identical (tested at 8/16/32 bpc).
The sampled Gaussian (sigma < 3) read every tap through a bounds-checked
get, and its vertical pass walked a column per pixel. Taps inside a row are
now one slice, and the vertical pass adds whole input rows in 128-pixel
tiles. Same f64 sums in the same order: bit-identical output (tested
against the per-pixel kernel). Each pass is 15–25% faster.
A comp left alone for a second fills its RAM preview from the current time,
so the first playback is cached instead of rendering frame by frame. Saved
settings keep their value. Two UI tests that assumed the old default set it
explicitly.
A render thread handed each frame to the disk cache already copied pixel by
pixel and LZ4-compressed (~9 ms per 1080p frame) before it could start the
next one, and layer buffers were encoded even when the queue then dropped
them. The queue now holds raw RGBA (one memcpy via ColorImage::as_raw) and
Arc<Buf>s; the writer thread encodes them. Every frame of a 120-frame bench
run now reaches disk (113–119 before). cache_bench prints the flush time.
* AG4: xtask ci --affected
cargo xtask ci --affected [--base REV] [--dry-run] runs fmt, clippy, tests (nextest + doctests when available), layers, assets (only on asset changes) and wasm (only for affected L0-L4/web crates) for the packages a change can affect: changed files map to the owning package and to packages whose sources read them via ../ paths, expanded to reverse dependents. Workspace-wide inputs (root Cargo.toml, Cargo.lock, .cargo/, xtask/, toolchain, clippy.toml) fall back to full CI. Documented in docs/testing.md, AGENTS.md and CLAUDE.md.
(cherry picked from commit f3ca84fc2ee3948582cbc94e4c99ee510e829540)
(cherry picked from commit 8a2cdcadf447eafa57ab14016beae4420f002af0)
(cherry picked from commit d8a4301bc1)
(cherry picked from commit 36402c61ac2f7e29ecd76a6cfc89365cc2572e0a)
* AO1: fast CI profile, looser timing tests
- [profile.ci] (release without LTO, 16 codegen units); `xtask ci` runs clippy
and tests with --profile ci instead of --release.
- Wall-clock asserts in engine memory_tick, gpu OpenGL decline and script
socket tests keep their intent with bounds that survive a loaded machine.
- docs/testing.md and CONTRIBUTING.md mention the profile.
(cherry picked from commit b45c8a379351ebdfda0d7076ad4b740baf6f2087)
(cherry picked from commit fa1ea6a107c4c1936cde89b518a05adc73f2b4b0)
(cherry picked from commit e5d5ed80e9)
(cherry picked from commit d2c67660c70764f9c56880a642ad7f0d004a7f92)
* AG6: journal replay repros
Bug reports become replay scripts and crash regression tests become data files:
- engine::replay: a JSON-lines repro format (start line, command steps with
expect_error, expect queries with JSON pointer + equals, expect_no_error),
a never-panicking runner that reports per-step ok/error and the first failure,
and journal export (from the last project open; saves left out; size-capped).
- journal.export {path?} and help.exportRepro {path?} (not journaled).
- Crash capture: when the last-resort guard catches a panic in the outermost
command, the journal plus that command is saved to Repros/crash-*.jsonl in the
settings store (newest 20 kept) and the error names the file.
- Commands run by a journaled command (script steps) are no longer journaled twice.
- effectcraft-cli replay SCRIPT.jsonl [--json] [--keep-going]: exits 1 on a failed
step, unmet expectation or panic.
- crates/host/tests/repros.rs replays every tests/repros/*.jsonl; three samples
from past crash fixes (zero frame rate, Layer Settings with no selection,
New Comp from Selection with hostile overlap).
- Docs: docs/agents.md "Repro scripts"; AGENTS.md: crash fix regression test =
a .jsonl in tests/repros.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c38509d7dafe507856c48c78db65e00b94b0cebc)
(cherry picked from commit e55b56dbf76716adfcbfe4cc773df19a74edc419)
(cherry picked from commit 96a63855a9)
(cherry picked from commit c59351ab3bd697f0868e41230dd883960d94a540)
* xtask: ci and ci --affected run cargo test and nextest with --no-fail-fast
One run now lists every failing crate instead of stopping at the first.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qXL3GPNnY9BSFF2XoU2Yu
(cherry picked from commit e46c7debe738eca62b35bf7392ab77637f6a33ae)
* Agents: confinement write-checks journal.export, help.exportRepro and file.exportTimeline
Review on #859 (split into this PR): their output paths were only read-checked (relative ones
not at all). Test: confinement_covers_every_command_that_writes_a_file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014d65YdNxQmjRkbXRtP2NwH
* #1224: handle bin-only CI targets and BOM repros without weakening timing tests
* #1224: run the full gate when nextest policy changes
---------
Co-authored-by: Elias <hakifx91@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* docs: track startup adapter recovery investigation (#477)
* Fix launch guard ownership during concurrent startup
* Launch guard (#477): fold the ownership tests into the tests module
Review cleanup, no behaviour change:
- Drop docs/issue-477-startup-recovery.md (investigation notes, not docs).
- Move the investigation_477 and root_patch_477_tests cases into the existing
tests module under behaviour names, without println! output; every
assertion is kept.
- Rewrap the module doc comment.
* #477: preserve inaccessible launch markers on Windows
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Merge current main while preserving continuous audio and prefetch behavior. Fix sibling method visibility, audio-only unmute, queue/cursor handoff, end-of-span replay, and expression-driven audio invalidation. Add mixer and preview-output regression coverage. Formatting checked; builds and tests run in hosted CI.
* #3389: a viewer click selects the topmost layer over a selected one
Clicks in the viewer went through pick(), which prefers a selected layer
under the pointer so a covered layer can be dragged (#230). A large
selected layer lower in the stack therefore captured every click inside
its bounds, and the layers in front of it could not be selected there
(#1535, #3389).
Add click_pick(), the same hit test without that preference: the
topmost layer, keeping the selection when it is already selected. The
left click (Selection and fallback tools, Roto Brush), the double-click
and the right-click menu use it; drag starts keep pick(), so dragging a
covered selected layer works as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3389: rustfmt the viewer's shapes imports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3389: test the right-click where both layers cover the pointer
The right-click check ran after the drag had moved the layer behind out
from under the pointer, so the old pick() passed it too. It now runs
before the drag with the layer behind selected, then closes the menu.
pick()'s doc names the Puppet tool's pin placement as a click that uses it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3389: a click on a covered path of the selected layer keeps that layer
With clicks going to the topmost layer, a click on a mask or shape path
of the selected layer where a layer in front covers it selected the
layer in front. Its paths went away, so a double-click there could no
longer free-transform the path. A plain click on a shown path now stays
on its layer, as pick() did; Shift-click still toggles the topmost one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3389: a right-click on a covered path of the selected layer keeps it
The context menu picked as a left click did before the path fix, so a
right-click on the selected layer's path, under a layer in front,
selected the layer in front. It now keeps the path's layer like a left
click. pick() and click_pick() docs describe the Puppet and path
exceptions exactly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Removes the unused egui_demo_lib, egui_inspection and egui_system_font_provider patches and adds cargo xtask lock-patches to cargo xtask ci. Fixes#3403.
The unit test re-implemented Marquee::selection's box/outline split, so it
only covered `targets`. Move the anchor math into `Anchor::press` /
`Anchor::selection` over a plain `View`, call it from the test, and add a
scrolled-drag case that fails with a screen-fixed anchor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dropping an .aep or .aepx on the window, or picking one in File > Import > File...
or Multiple Files... (also from the Home screen and the Project panel), imports it
as File > Import > Adobe After Effects Project... does, instead of failing as
unsupported footage: the losses prompt first when parts cannot come over, its
items in a folder named after the file, its first composition open.
- Several projects are asked about one at a time: the others wait in the
prompt's queue, and Cancel skips only the one it asks about. A prompt another
dialog replaced (Save changes?) strands nothing: its projects are tried with
the next import or queued behind the next prompt.
- The other files of a drop or pick import as footage, into the Project panel
only when a project is in the drop. While the prompt shows, a Photoshop file
or image sequence imports with its defaults instead of replacing the prompt,
an Interpret Footage question leaves a note instead, and a dropped .ecproj is
skipped with a note. While Save changes? waits for a dropped .ecproj, the rest
of the drop is skipped with a note instead of landing in the replaced project.
- Errors of every picked project reach the status line on both the blocking and
the non-blocking file dialog.
- The Import File dialogs list .aep, .aepx, .ecproj and .ecprojx; Replace
Footage offers footage only.
- For scripts and agents, file.import hands After Effects projects to
file.importAep; a refusal (new EngineError::Refused) names the menu entry and
`file.importAep` with `allowLosses`.
- File > Import / Export name the entry Adobe After Effects Project..., as
Adobe Premiere Pro Project...; File > Import keeps After Effects' order with
the project imports together and the formats only EffectCraft imports after a
separator. All 13 menu catalogs and the losses dialog title are updated.
- Docs: aep-import, aep-export, footage, file-format-parity, agents.
Supersedes #3312.
Since #595 (v0.19.0) a playing preview and Cache Frames When Idle render one frame per
lane, a lane per core, each recording, submitting and reading back a whole frame on the
GPU the window draws with. On a GPU that can't keep up, the window froze for hundreds of
milliseconds to seconds and the playhead jumped ahead when it came back.
Lanes now take a GPU frame only while fewer than a limit of them composite. The limit
starts at one lane and adapts to the window's frame gaps while a preview plays: a stall
(>100 ms) with lanes compositing halves it, half a second of smooth frames (<50 ms) with
every allowed lane busy adds one, up to every lane; a limit that stalled is tried again
only after 10 s. Frames on the CPU (Software Only, no or retired GPU, GPU memory
pressure) and disk-cache hits use every lane; the pool and the viewer's frame are not
limited, and frames ahead of the playhead still start first.