Commit Graph
1136 Commits
Author SHA1 Message Date
08362b9c04 GPU: free effect working textures after each call instead of at frame end (#1523)
* GPU: free effect working textures after each call instead of at frame end

A GPU frame keeps its encoder open while the CPU renderer runs layers with GPU
effects (Gpu::effects -> run_chain). Each call's Enc handed its textures back
to the shared pool, where they waited for every open encoder, so all effect
intermediates of a frame stayed allocated until the frame ended (17.7 GB by
layer 17 of a heavy 4K comp, then OOM). Effect calls now borrow a pool owner
(Enc::lent) that is given back once the call's work is submitted or
discarded; its kept textures are trimmed to the pool budget, so the rest are
freed after each call.

* GPU: one pool budget for idle effect calls, trimmed at the end of a render

Review of #1523: each idle lent owner kept up to POOL_BUDGET of textures of
its own, outside the shared accounting, and there were as many idle owners as
effect calls ever ran at once (16 frame threads: ~12 GB after a 4K render).

Giving a lent owner back now moves its kept textures to the shared free list,
so one POOL_BUDGET bounds what the free list and every finished effect call
keep. They skip Pool::release's wait for open encoders: only the lent Enc
recorded work on them, and that work is submitted or discarded. Each lend is
a fresh owner id, so a lease that outlives its call (an image handed to
another encoder) is released through Pool::release; this does not rely on
the result being read back.

Accelerator::render_end (called when an export ends) drops the kept textures.

* GPU: evict the pool's oldest free textures, guard escaped leases

Review of #1523 (follow-up to 0a884fb6):

- Pool::make_free rejected a texture when the free list was full and never
  evicted, so once sizes that no longer occur (per-frame blur bounds, padded
  layer sizes) filled the budget, every later free was a drop and every take
  an allocation. It now evicts the least recently freed textures until the
  new one fits.
- A lease read by another Enc (dispatch, copy, readback, display) or handed
  to a cache is marked escaped and always released through the guarded
  Pool::release, so a lent call can no longer reuse a texture that a still
  open encoder reads.
- Docs: POOL_BUDGET caps the free list only; textures waiting for open
  encoders and those of live Encs are outside it.
- export_async calls render_end from a drop guard, so the pool is also
  trimmed when an export panics or its future is dropped.
- Tests use a private context with its own pool budget instead of a global
  override; new tests for stale sizes and an image read by an open encoder.

* GPU: evict only stale pool sizes, count evicted textures until the GPU frees them

Evicting the least recently freed texture for every newcomer thrashed when a frame
frees more than the budget in a repeating order (blur/glow intermediates): each free
evicted the size the next call takes first, so misses came back to the old level and
evict + reallocate raised the peak.

The free list now evicts only sizes nobody asked for in the last 2 output frames
(Accelerator::frame_end, called by the export per frame); otherwise the incoming
texture is dropped and the kept set keeps hitting. Evicted bytes count against the
budget until the work submitted so far is done, so evictions run at most one budget
ahead of the GPU.

* GPU: lower the shared pool budget to 256 MB

On 4K RTX 3090 runs 256 MB matches the old code's peak reserved on d_blurglow
(9204 MB) and is 256-512 MB above it on c_effects (6592 vs 6080 MB), while
staying faster.

* GPU: end frames on the multi-frame render paths, route recording through Enc::record

Only the export pipeline called frame_end, so off export (preview, remote
frames) the pool's clock stood still, no size went stale and a full free
list dropped every newcomer. passes::comp_frame (exports), remote frames
and the preview workers now end each frame; test without pool_frame_end.

Docs: the frame clock is coarse with many frames in flight; dying only
throttles eviction, approximately; no cache path escapes images. Enc::record
is the single way to get the encoder outside context.rs and marks the
images it names (Enc::uses).

* GPU: wait for the empty submission while evicted pool textures still count

stale_sizes_do_not_starve_a_steady_size failed with 0 hits and 20 misses on
hardware adapters: pool_frame_end submitted an empty batch and then only did a
non-blocking Poll, and the queue's work-done callback did not run before the
next frame, so Pool::dying never drained and make_free stopped evicting stale
sizes.

While dying is non-zero, pool_frame_end now waits on that empty submission for
up to the readback timeout (the pool lock is already released). A wait timeout
just moves on and does not retire the context or its readbacks; other poll
errors still retire them. Without dying textures it keeps the cheap Poll.

Enc::crop records through Enc::record(&[src, &dst]) so both pooled images are
registered like on every other recording path (review).

* GPU: do not repeat the pool frame-end wait until evicted textures drain

A wait that frees none of the evicted bytes (timeout, or a WebGPU poll that
does not block) marks the pool stalled; later frames only poll until dying
drops. Test covers the no-repeat and partial-progress cases; dying decrement
saturates.

* Remote frames: end a pending frame that a new render replaces

A render arriving while a multi-pass frame waits for readbacks dropped that frame without
`frame_end`, so the GPU pool's clock skipped it. Errors return before `frame_begin`.

* Render: end the frame even when a comp frame panics

`passes::comp_frame` called `frame_end` only after a normal return, so a frame that
panicked (or whose future was dropped between passes) never aged the GPU pool.

* GPU pool: log the reuse rate at render end; test reuse across effect calls and frames

Log cumulative hits/misses (and reuse %) once per render before trimming. Tests
cover reuse within an open frame across effect calls, and across repeated frames.

---------

Co-authored-by: Matej Kozlovský <MatejKozlovsky@users.noreply.github.com>
Co-authored-by: bflatastic <bombayapirat@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-03 21:12:19 +01:00
Dylan M. Taylor 2f31c22a09 fix(prefs): when recovering from a bad value, try only the settings' own pages one by one (#3010) 2026-11-03 14:10:24 -05:00
Brandon Thomas 859808e062 Merge PR #2979 2026-11-03 09:30:23 -05:00
Brandon ThomasandClaude Opus 5.5 26228247e2 Follow-ups to the batch: name numbering keeps no double space (#2908), Material Options error names the parameters (#2924), corrupt file timecodes stay on timecode (#2835), restore #2934's scroll-gutter asserts, #2825's Motion Tile test compiles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:30:22 -05:00
Brandon Thomas f5e81cd1f9 Merge PR #2957 2026-11-03 09:08:08 -05:00
Brandon Thomas 5dc64bb227 Merge PR #2885 2026-11-03 09:04:55 -05:00
Brandon Thomas 3072f009bf Merge PR #2891 2026-11-03 09:04:54 -05:00
Brandon Thomas 0a91567425 Merge PR #2834 2026-11-03 09:04:45 -05:00
Brandon Thomas 0e3d54f982 Merge PR #2909 2026-11-03 09:04:45 -05:00
Brandon Thomas 2fb7003643 Merge PR #2924 2026-11-03 09:04:45 -05:00
Claude a5e0da657e Pre-compose: layers left behind drop links to moved layers, default names are numbered
Unselected layers parented to, or track-matted by, a layer that moved into the
new composition kept ids that no longer exist in the outer comp. The command's
default name was always "Pre-comp 1"; it now picks the lowest unused number.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDvxCS8xcR28VArjzn1x7U
2026-11-03 13:58:54 +00:00
Matej Kozlovský 6b1f91ffb8 Cryptomatte: read an ID's coverage from the kept probe (#473)
Session::crypto_coverage returns the coverage of one Cryptomatte ID over the layer, read from
the probe Session::crypto_probe_job keeps: at most max_side samples on the long side, each at
the centre of its cell, or None until the probe is built. It is what the Cryptomatte
eyedropper's hover highlight will draw.

Tests cover the coverage of an ID from the kept probe and the cost of a highlight with six
ranks.
2026-11-03 07:22:46 +01:00
Bar BorerandClaude Opus 5.5 8a37ff6b43 Tests: trim the midpoint snap tests to what #2855 changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 05:07:59 +02:00
JonRC 583ba084b6 Fix #2917: material.set names the Material Options a layer doesn't have
A 3D model layer keeps its file's materials, so it has no Base Color,
Metallic, Roughness or Emissive. material.set refused them with "no
Material Options property matched (..., baseColor, metallic, roughness,
...)", naming the parameters it had just refused, and next to a
parameter the layer has it dropped them and reported success.

A parameter that none of the given layers has now fails the command
(nothing is set), naming it and the properties the first layer has. A
value of the wrong type is its own error. The parameter list says which
layer kinds take which.
2026-11-02 23:13:02 -03:00
Bar BorerandClaude Opus 5.5 fa0c260630 Puppet: refuse only a mesh that repeats one already there (#2865)
Cut the fix down: a click off every mesh makes nothing only when the art it resolves to
already has a mesh with that outline. The nearest-art fallback in `region` stays, so a
near miss beside separate art still meshes it. `mesh::outline` is as it was and the
`newMesh` option of puppet.addPin is back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:00:19 +02:00
JonRC 0b59d2b8c4 Follow-up to #2907: say that a uid wins over an index for animator
animator_uid matches a uid before an index, so an animator whose uid
equals an index is picked over the one at that position. The four
commands that take `animator` (layer.addTextAnimatorProperty,
layer.addTextSelector, text.addSelector, text.animatorFontAxes) now say
so; text.addSelector also said only `uid|index` before. The test gives a
second animator uid 1 and checks that `animator: 1` picks it.
2026-11-02 21:14:22 -03:00
JonRC 5643f39769 Fix #2907: say that text animator indexes count from 1
layer.addTextAnimatorProperty, layer.addTextSelector and
text.animatorFontAxes take `animator: uid|index`, counted from 1 as the
code and `#n` references do, but the parameters didn't say so and
`animator: 0` failed with only "no animator 0". The parameters now say
1-based, and the error says what the value can be.
2026-11-02 20:32:48 -03:00
Bar BorerandClaude Opus 5.5 f20dd38f94 Puppet: a click on deformed-away space creates nothing (#2865)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:17 +02:00
Bar BorerandClaude Opus 5.5 39119ff9ce Viewer: dragged layers snap to other layers' midpoints and center lines (#2855)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:36:57 +02:00
JonRC ace329d126 Roto Brush: a Freeze job reads the frozen cache once (#2833)
frame_matte looked up the frozen mattes for every frame, and that hashes
the whole serialized cache and recomputes its keys, so freezing a long
span again grew with frames times cache size. The Freeze job now
resolves the applicable cache once (applying_frozen) and passes it to
frame_matte.
2026-11-02 12:32:25 -03:00
JonRC a2f6cd3ed5 3D Camera Tracker: a partial solve is not reported as solved (#2822)
camera.solveStatus said `solved: true` for any stored solve, but the solver
keeps the frames it cannot resect as unsolved and interpolates the camera
there. `solved` now needs every frame, the same check camera.exportBlender
makes, and `partial` marks a solve with gaps, next to `solvedFrames`.
2026-11-02 09:15:05 -03:00
Brandon Thomas a658ec736a Merge PR #1266 2026-11-01 21:50:53 -05:00
tylerjenningswandChewybone-sudo 3ab2167e47 Auto-Trace: traced outlines map back into layer space (#2764)
Co-authored-by: Chewybone-sudo <347238078+Chewybone-sudo@users.noreply.github.com>
2026-11-01 09:05:56 -05:00
tylerjenningswandChewybone-sudo 49774ca5f9 Auto-Trace: test that traced outlines map back into layer space (#2764)
Co-authored-by: Chewybone-sudo <347238078+Chewybone-sudo@users.noreply.github.com>
2026-11-01 09:02:42 -05:00
pravdomilandClaude Opus 5.5 aa149d7bbc Shortcuts: deterministic dispatch between panels, physical and logical ranked together
- shortcuts::pick chooses the binding for a key press: narrowest scope, then a
  physical-key binding, then the first. When two other panels (or two active
  contexts) claim the chord at the same rank, neither runs and the status bar
  says which panels own it.
- Physical and logical bindings compete by scope rank in one pass, so a
  narrower logical binding beats a physical one of an unrelated panel.
- An additional viewer (PanelKind::Viewer) ranks as the Composition panel.
- Two contexts on the same keys are reported as a conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:35:33 +01:00
Brandon ThomasandClaude Opus 5.5 2a7888d2c0 Follow-up to #2716: addItem applies in before out; anchor takes exactly [x, y]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:02:15 -04:00
Brandon ThomasandClaude Opus 5.5 917f312bc2 Follow-up to #2169: keep layer.deleteAllMarkers layer-only
An empty layers list cleared the composition's markers too (CodeRabbit);
the composition-marker menu uses comp.deleteAllMarkers instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:56:36 -04:00
Brandon Thomas e89683c5b5 Merge PR #2712 2026-10-31 10:54:52 -04:00
Brandon Thomas 659cfcd1d0 Merge PR #2716 2026-10-31 10:54:51 -04:00
Brandon Thomas 738f9a3055 Merge PR #1541 2026-10-31 10:54:51 -04:00
marshiyar 38ed20f569 feat(layer): layer.addItem takes in, out and anchor (#2026) 2026-10-30 20:30:34 -07:00
marshiyar 508a2241e9 Help: Expression Reference opens a page that exists (#2696) 2026-10-30 20:07:14 -07:00
maxaeexe b30c644c77 comp.deleteAllMarkers: add Delete All Markers for composition markers 2026-10-31 00:45:35 +03:00
bflatastic 334bf0f785 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	crates/engine/Cargo.toml
2026-10-30 17:22:12 +07:00
BFlat 3d285116d8 Merge pull request #1539 from nanox333/feat/sam2-roto-tracker
Roto Brush: follow the object with SAM 2.1 (Tiny, Small, Base+, Large; optional models)
2026-10-30 17:02:45 +07:00
pravdomilandClaude Opus 5.5 189f31d6d1 Shortcuts: panel and context scopes, one chord per scope
A shortcut belongs to the application, a panel or a context (editing text, a paint or
camera tool active, a mask selected). The narrowest active scope runs a chord: an active
context, the focused panel, the panel sharing its shortcuts (Timeline and Composition),
the application. A context's shortcuts never run outside it.

- shortcuts.rs: scope table for engine commands and menu entries, scope_rank,
  scope_label; UiCommand can state its scope.
- Conflicts: the same chord in two scopes is an override (both keep working), reported
  with overrides: true; shortcuts.conflicts lists same-scope pairs only.
- Scoped now, keys unchanged: Remember / Apply Interpretation and Delete Without
  Confirmation (Project), Duplicate Render Item (Render Queue).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:57:09 +01:00
bflatastic 2a72f03872 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	apps/effectcraft-cli/tests/it/cli.rs
#	crates/engine/src/output_guard.rs
#	docs/agents.md
2026-10-30 16:38:01 +07:00
bflatastic 672c49ab4a Merge remote-tracking branch 'refs/remotes/pr/1829' into HEAD 2026-10-30 16:37:00 +07:00
BFlat 7174c81b2f Merge pull request #1830 from nanox333/fix/1743-duplicate-outputs
#1743, part 3: two Render Queue outputs never write the same file, and default names avoid the queue's other outputs
2026-10-30 16:36:32 +07:00
Patrik LermonandClaude Opus 5.5 46881e0d5f Swedish interface translation (sv)
Adds Swedish (Svenska) as an interface language in the menu, panel and
effect catalogs, written from the English source. The effect rows are
partial like Japanese and Georgian: an effect without a Swedish row draws
its English name, so a new effect does not block other PRs (#2244).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:35:36 +01:00
Kristian Myram 2112e553ec #1743: choose default names around protected overflow paths 2026-10-30 10:13:38 +01:00
Kristian Myram 107b00e538 #1743: compare browser output paths using virtual file keys 2026-10-30 10:07:12 +01:00
Kristian Myram b307c62a7f #1743: reuse protected identities while publishing pre-renders 2026-10-30 10:00:33 +01:00
Kristian Myram a4f5c516ed #1539: validate benchmark inputs and preserve shared refinement strokes 2026-10-30 10:00:33 +01:00
Kristian Myram b7d97c20e9 #1743: check final storage overflow destinations for queued outputs 2026-10-30 09:55:49 +01:00
bflatastic f558305795 Merge remote-tracking branch 'refs/remotes/pr/1830' into HEAD 2026-10-30 15:42:23 +07:00
Kristian Myram 41148e9b72 #1743: validate background and bridged exports in their owning session 2026-10-30 09:41:38 +01:00
bflatastic 18b6308225 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 15:00:44 +07:00
bflatastic 279b0ab4e3 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 15:00:43 +07:00
bflatastic eed23ffa48 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 15:00:42 +07:00