Commit Graph
289 Commits
Author SHA1 Message Date
6df7eabe05 gpu: memory back-pressure for frames in flight (fixes OOM at 3–4 frames in flight) (#1550)
* 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: cap in-flight frame memory on Vulkan and DX12 (#1550)

wgpu destroys a dropped texture or buffer only once the submissions
that used it have completed and the device is maintained. With several
4K frames in flight on an export or effect-heavy render, frames can
record and drop working textures faster than the GPU retires them,
accumulating freed-but-not-destroyed allocations until a 4-8 GB adapter
runs out of memory (#879).

Add a memory gate (MemCap) checked every 8 effect steps or layers:
- Default cap of 3 GiB on discrete GPUs (low enough for 4-8 GB cards
  while leaving headroom above the 1.5 GiB upload cache and 256 MB
  shared pool) and 2 GiB on integrated/software adapters, overridable
  via EFFECTCRAFT_GPU_MEMCAP_MB (0 disables).
- Compare total_allocated_bytes against non-reclaimable bytes (upload
  cache, pool free list, live leases, staging, zeros) with hysteresis
  and a post-wait floor so the gate only drains when a wait can free
  dropped allocations and does not spin when caches or other open
  encoders hold memory over the cap.
- Submit and wait on the current Enc's own SubmissionIndex via a
  dedicated poll_wait helper (serialized by poll_lock) so a timeout
  simply carries on without retiring the context or its readback pool.

* GPU: warn on invalid EFFECTCRAFT_GPU_MEMCAP_MB, register crop sources, require GPU in the mem-cap child test

- memcap: an EFFECTCRAFT_GPU_MEMCAP_MB value that is not a whole number
  of MiB or overflows 64 bits now logs a warning and uses the default
  cap (it used to fall back silently, or wrap on overflow). Unset still
  means the default and 0 still turns the gate off.
- Enc::crop records through Enc::record, so a foreign source lease is
  marked escaped like in copy_into.
- The mem-cap child test starts its GPU through require_gpu, so
  EFFECTCRAFT_REQUIRE_GPU=1 fails when the GPU does not start, and
  prints a skip line (with the adapter name when the backend has no
  allocator report), which the parent passes on.

* 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 memcap: warn once when kept memory alone is over the cap; test the edge frames

A frame whose non-reclaimable memory exceeds the cap never waits when reclaimable
bytes stay below the hysteresis (waiting cannot free the kept set) and now logs
that once. Tests cover a frame over the cap, two frames that fit one at a time, and a
dropped frame giving its memory back.

---------

Co-authored-by: Matej Kozlovský <MatejKozlovsky@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-03 21:46:16 +01:00
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
rahul05ranjan 540ee9fbc5 Motion Tile: mirror folds sample the edge texel, not the transparent outside
A tile boundary that lands exactly on an output pixel centre puts the tile
fraction at exactly 0 or 1, and the sample position at the source's geometric
edge — where sample_bilinear mixes in half a texel of the transparent outside.
The fold column comes out half-faded: a visible seam line along the mirrored
boundary, which sweeps the tile as Tile Center animates across pixel centres
(#2820). Both the CPU path and the GPU kernel switch to the clamped sampler,
which repeats the edge texel there; interior samples are untouched, and the
default single-tile identity mapping already sampled texel centres.

The regression test puts the folds exactly on pixel centres (tileWidth 50%,
centre on the layer, mirror on) and asserts the fold columns stay opaque and
equal the edge texels they mirror — it fails on the unfixed code at alpha 0.5.

Fixes #2820

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
2026-11-02 12:30:58 +00:00
BFlat 2745890b2f Merge pull request #2653 from storytold/fix/2618-adjustment-blend-modes
Adjustment layers blend in their blend mode (#2618)
2026-10-30 12:10:16 +07:00
bflatasticandClaude Opus 5.5 94c8bde240 Adjustment layers blend in their blend mode (#2618)
An adjustment layer replaced the comp below with its effects' result whatever its blend
mode. As in After Effects, a mode other than Normal now blends the adjusted comp onto the
comp below in that mode, inside the footprint, track matte and opacity (an adjustment
layer with no effects in Screen screens the comp with itself). CPU and GPU paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:43:19 +07:00
bflatastic c07decd552 Merge main into #2646; no conflicts; gate UPDATE_APP to non-wasm32 (craft-update is a native-only dependency of ui-egui) 2026-10-30 11:20:26 +07:00
Dylan M. Taylor dd03911442 Update prompt shows the app icon: UPDATE_APP carries the 128 px icon
craft-update's App gains icon_png here: the updater, the self-update helper and
craft-update's startup prompt all get it through crate::UPDATE_APP. The prompt draws it
once craft-libs includes storytold/craft-libs#434; AppImage menu entries get it now.

Clippy fixes for Rust 1.99 in untouched code (same logic):
- crates/gpu/src/tests/wgsl_grammar.rs
2026-10-29 23:35:07 -04:00
bflatastic a683a8074d Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 09:02:01 +07:00
bflatasticandClaude Opus 5.5 43c7cbb9aa Merge main into #2620; clean merge, no conflicts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:47:41 +07:00
bflatasticandClaude Opus 5.5 0967102432 Merge main into #2369; clean
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:41:47 +07:00
bflatastic 5c2e7ad343 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 07:11:39 +07:00
bflatastic 5c89837f91 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 07:10:36 +07:00
bflatastic d6e99c0db8 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 07:03:44 +07:00
bflatastic 0039081952 Merge main into #2567; keep both test modules, and convert four layer stacks main's new render tests assign as Vec<Layer> with .into() 2026-10-30 06:01:06 +07:00
bflatastic 56fde5c3d6 Merge main into #1279; effects/lib.rs: keep the PR's Buf::grow_left_top/align beside main's budgeted Buf::pad, and put cover_plan's doc comment back on cover_plan (the PR's new items had split it off) 2026-10-30 05:53:43 +07:00
bflatastic 335abf42c4 Merge main into #2474; tests/it/main.rs keeps both ui_env_light_gpu and main's ui_expression_values 2026-10-30 05:53:20 +07:00
bflatastic c53dd7f0a0 Merge main into #2600; clean 2026-10-30 05:46:55 +07:00
Jason LukerandClaude Fable 5.1 2a61a05fd4 GPU: the readback deadline worker sleeps until a deadline is due instead of polling every 100 ms
`Readbacks::new` spawned a thread that slept 100 ms and checked for expired readbacks forever,
whether or not anything was pending: ten wakeups a second for the life of every desktop session
with a GPU compositor (12–13 idle wakeups/s measured, against 0.4–1.8 for the sibling apps).

The worker now waits on a condition: indefinitely while nothing is pending, otherwise until the
earliest deadline or until what is pending changes (start, complete, cancel, retire poke it).
It owns the state behind an `Arc<Inner>` rather than the `Readbacks`, so dropping the context
closes it instead of leaking the thread. Timeout, exactly-once callbacks, panic isolation and the
browser's timer path are unchanged.

Measured on an M2 Max (release build, window on screen, 60 s samples, A/B interleaved): idle
wakeups 12.15/s → 2.63/s not frontmost, 12.36/s → 2.72/s frontmost.

Tests: an idle worker does not wake; a due deadline wakes it when due; dropping the context stops
it; the existing panic/strand test moves deadlines through a hook that notifies the worker.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-29 15:40:56 -07:00
becauseno7 0ca3f72ac2 CC Bubbles: bubbles refract the layer's edge pixels on padded buffers too (#2024)
Refraction samples are clamped to the layer rectangle instead of the buffer, so a sample past the
layer's edge in a buffer with transparent padding (a half-resolution preview's, a text or shape
layer's) repeats the layer's edge pixels rather than reaching the pale colour that stands in for
transparent ones. Transparent pixels inside the layer still show it. Same bounds on the GPU path.

Re-pins the four half-resolution CC Bubbles golden hashes (aarch64 macOS); the full-resolution
ones are unchanged. Adds a CPU test and a GPU-vs-CPU parity test on padded buffers.
2026-10-30 04:34:30 +06:00
bflatastic 2e9542dada Merge remote-tracking branch 'origin/main' into fix/2261-pad-budget
# Conflicts:
#	crates/effects/src/lib.rs
2026-10-30 05:17:57 +07:00
BFlat e2128a6743 Merge pull request #2118 from ChristopherLinnett/fix/issue-2092-camera-focus-crash
fix: bound Classic 3D depth-of-field blur and reject non-finite focus distances (#2092)
2026-10-30 05:09:17 +07:00
bflatastic 57b76bcc88 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 05:08:13 +07:00
bflatasticandClaude Opus 5.5 fb2fb169dc Effects: padded buffers stay within a pixel budget (#2261)
Effects that grow their buffer (CC Radial Fast Blur, CC Lens, Magnify,
the distorts and blurs) could pad a 20 x 20 layer by 4096 px a side, a
~8200 x 8200 f32 buffer plus its clone. Buf::pad (CPU and GPU) now cuts
the pad so the grown buffer stays within 16 Mpx or four times its own
area, whichever is larger: small layers are bounded at 256 MiB, big
layers still grow as before in practice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:32:17 +07:00
bflatastic 6af2bedfe0 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 01:24:36 +07:00
BFlat a0bf1b13d8 Merge pull request #1382 from tylerjenningsw/fix/particle-birth-history
Particles: preserve emitted particles when Birth Rate changes (#1373)
2026-10-30 01:20:20 +07:00
BFlat a7f1d11620 Merge pull request #1326 from tylerjenningsw/fix/gpu-frame-validation
GPU: contain frame-local validation failures (refs #1319)
2026-10-30 01:16:36 +07:00
BFlat 2faaa89c63 Merge pull request #2535 from klosixgaming-arch/fix/943-turbulent-displace-evolution
effects: Turbulent Displace Evolution moves the pattern at a constant rate (#943)
2026-10-30 01:15:12 +07:00
bflatastic 89fcd892eb Merge remote-tracking branch 'origin/main' into HEAD 2026-10-30 00:27:26 +07:00
BFlat fbb7dc325a Merge pull request #2466 from dotCooCoo/integration/bugfix-effectcraft-bugfix
Fixes: macOS quit crash, soft shadow jitter, Interpret Footage params, preset upgrade, composition proxies, font menu names
2026-10-30 00:13:49 +07:00
BFlat b62093ae7f Merge pull request #2463 from HEOJUNFO/fix/classic3d-shadow-diffusion
Classic 3D Shadow Diffusion blurs instead of stamping copies (#2444)
2026-10-30 00:13:09 +07:00
BFlat 966fdd3c0a Merge pull request #1776 from sanyamsingh001/fix/hextile-centerline-symmetry
Fix HexTile centerline cell selection
2026-10-30 00:06:21 +07:00
Camille Moine df0a88b795 Fix collapsed precomp Essential Property overrides (#1747)
Carry property, Font and Media Replacement overrides into the collapsed nested EvalCtx so nested layers render beyond the precomp frame instead of falling back to a clipped, flattened image. Key the layer cache on instance overrides to keep distinct collapsed instances isolated.
2026-10-29 15:21:16 +01:00
Christopher Linnett 280a400be4 Merge origin/main into fix/issue-2092-camera-focus-crash
Conflicts were main's new code next to this branch's additions; both kept:
- crates/effects/src/lib.rs: main's Buf::pad_sides stays as main wrote it
  (Transform's transform_grow bounds that padding itself); Buf::pad keeps
  this branch's effective-pad clamp.
- crates/raster/src/lib.rs: main's quantize_px/round_half_up and this
  branch's MAX_PADDED_SIDE (still equal to limits::MAX_SIZE on main).
2026-10-29 18:44:49 +08:00
Z User f47b57d3f8 Merge remote-tracking branch 'origin/main' into fix/environment-light-no-effect-cast-shadows 2026-10-29 08:54:59 +00:00
Vikram Bhaskaran 4a37893284 Merge origin/main into fix/1205-layer-cover. 2026-10-29 17:11:06 +11:00
bflatasticandClaude Opus 5.5 c526bb554a GPU: without_kernel copies the software and yuv fields too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:10:45 +07:00
bflatastic 75e878f178 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-29 10:10:44 +07:00
BFlat b950014850 Merge pull request #1873 from vangel76/rfx-blur-stylize
Glow, Radial Blur, Venetian Blinds, Radial Wipe, Fractal Noise and the Transform effect render as in After Effects
2026-10-29 10:09:36 +07:00
Eduardo EspinosaandClaude Opus 5.5 e1d66a3c5c perf: share unchanged layers between undo states
Every undoable edit keeps the previous project for undo, so the comp it touches
is shared and Arc::make_mut deep-copies it: every layer and every keyframe. An
edit therefore costs time in proportion to the comp's size, and a script that
sets many keyframes in a big comp goes quadratic (a 1,600-layer build with
~100k sampled keys took ~45 minutes).

Comp::layers is now a Layers collection backed by Vec<Arc<Layer>>: copying the
stack copies pointers, and only the layer an edit changes is copied. The API
mirrors Vec<Layer>; serialized form is unchanged (a list of layers).
Comp::layer_mut and the iter_mut().filter/find call sites use find_mut /
iter_mut_where so they copy only the layers they actually change.

- prop.addKey in a 400-layer comp: 1.36 ms -> 0.16 ms per key (flat in comp size)
- the 1,600-layer script build: ~45 min -> 47 s, frames pixel-identical
- regression tests: an edit leaves the other layers shared with undo; layers
  round-trip as a plain list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkpGcKfWyCudfSsQNiWAaG
2026-10-28 22:20:23 -04:00
dotCooCoo 0702cd23ee Merge main 2026-10-28 15:31:46 -07:00
Matej Kozlovský 6bcb76b9dc GPU: Classic 3D builds with FXC, so the GPU compositor starts on DirectX 12 (#613)
occlusion computes its three channels as one vector instead of writing a
vector component through a loop index, which FXC cannot address and tried
to unroll every enclosing loop for (X3550/X3511). classic3d now also
builds like the effect kernels: if a compiler rejects it, 3D runs render
on the CPU instead of the whole compositor failing to start. The ignored
FXC diagnostic test now checks classic3d instead of the stale pointwise
failure.
2026-10-28 17:08:46 +01:00
vangel76andClaude Sonnet 5.5 26798dce42 Merge upstream/main into rfx-blur-stylize
Resolve the conflict in crates/gpu/src/fx_stylize.rs: main moved ops::warp to
the ops::WarpOptions parameter struct; the branch's per-sample transform loop
(motion-blur samples, now passing the sample by reference) uses the new
signature with the branch's reference argument kept.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 15:37:16 +01:00
Matej Kozlovský 6e69b91a80 Export: H.264 frames convert to YUV 4:2:0 on the GPU
Accelerator::comp_frame_yuv420 (default None) renders a top-level comp frame and
converts it over the background to the H.264 export's 8-bit BT.709 limited-range
4:2:0, every byte within one level of the CPU conversion (the GPU kernel follows
it operation for operation, including NaN and half-away-from-zero ties, but a
driver may contract its arithmetic). A 4K frame then reads back 12 MB of planes
instead of 133 MB of RGBA f32.

The H.264 export asks the accelerator for planes when a frame needs nothing after
the render (progressive, project depth, no crop/resize, rendered at the output
size) and otherwise renders and converts on the CPU as before. Deferred
readbacks (a browser worker) keep the pass-based path. Sizes the kernel can't
take (width not a multiple of 8, odd height) are declined before any GPU work.

Like Cx::frames, h264_frames caps GPU frames in flight only while the
accelerator is up (map_frames_while with the same liveness check): once the GPU
stops for good, the remaining frames render on the CPU side by side instead of
one at a time.

rgba_to_yuv420 moves to effectcraft_raster::yuv so the export and the GPU tests
share one reference. The GPU tests skip without an adapter (tests::gpu()), as
the crate's others do.
2026-10-28 13:19:24 +01:00
Dylan M. Taylor 7926b8bd31 Refactor gpu for strict Clippy checks 2026-10-28 06:09:21 +00:00
tylerjenningsw 8d008e53da Merge branch 'fix/particle-birth-history' into fix/particle-birth-history-ps2-mercury
# Conflicts:
#	crates/effects/src/sim.rs
2026-10-27 16:59:26 -04:00
Claude 80b9aacea7 G1: Turbulent Displace Evolution changes the output (#943)
Turbulent Displace read its noise through value_noise, whose quintic fade also
eased the evolution axis. Near every whole revolution the pattern hardly moved:
Evolution 0 and 37 degrees (or 360 and 400) differed by well under a hundredth of
the frame, so different Evolution values looked identical, and the motion sped up
and slowed down within each revolution (the #1816 symptom).

Add value_noise_linear_z (x and y keep the fade, z is linear) on the CPU and in the
WGSL kernels and use it for Turbulent Displace on both paths. value_noise is
unchanged, so the other effects and the pinned simulation goldens are unaffected.

Tests: CPU test that non-equivalent Evolution values (fractional, past 360, past
720) all differ and that Cycle Evolution still matches 0/360; unit test of the
linear z axis; GPU-vs-CPU cases at small and large Evolution.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM
2026-10-27 15:34:34 -05:00
tylerjenningsw cedc727da9 Merge remote-tracking branch 'origin/main' into fix/particle-birth-history
# Conflicts:
#	crates/effects/src/sim.rs
#	crates/render/src/lib.rs
2026-10-27 14:57:00 -04:00
Christopher Linnett 60da124f11 Merge remote-tracking branch 'origin/main' into fix/issue-2092-camera-focus-crash 2026-10-28 00:13:40 +08:00
dotCooCoo 97ef73d592 Preserve bug fixes after main module moves and bound workspace migration 2026-10-27 07:45:09 -07:00
Z User 978ca419b9 Merge remote-tracking branch 'origin/main' into fix/environment-light-no-effect-cast-shadows 2026-10-27 08:54:33 +00:00