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>
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.
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).
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
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.
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>
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.
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
The default studio environment's level-preserving mean of 1 acted like a
100% white Ambient light: with only the Environment light on, a white floor
rendered at full white (95% of the frame saturated) while its shadow core
went to pitch black — a fog over the scene, nothing like After Effects'
default. Three changes bring the look in line, CPU and GPU alike:
- The default environment is a dim studio now: a solid-angle-weighted mean
of 0.3 (and a stronger key gradient), so a white floor reads a mid gray
and the light's colour x intensity still scales the level.
- The shadow occludes only the image's directional (key) fraction of the
light — |sum of dir x radiance| / sum of radiance, computed beside the
dominant direction and carried in the scene — so the fill keeps shining
inside the shadow: an area-light shadow, partial like After Effects'
(a lone bright spot on black still shadows almost fully; a uniform image
not at all).
- The environment shadow map's penumbra has a floor — eight output pixels,
or four map texels where those are coarser — because the map covers the
whole scene bounding sphere rather than the view, and its texels would
otherwise stair-step the edge (all the more in a draft render's flat 512
budget). Shadow Diffusion widens it further, as before.
Tests: the key fraction tracks directionality; the default keeps its dim
level, its direction and its key; the default and a bright key both shadow
the floor (the default partial, the key deep) with Cast Shadows off lit
everywhere; and the full look — a mid-gray lit floor (no fog), a partial
core, and a soft edge ramp in quality and draft renders alike.
The WGSL grammar test scanner indexed token characters directly, so a malformed shader could panic the test instead of failing it. Checked access turns that into a reported violation.
The Casts Shadows option Environment lights got is only half a fix if
nothing shades for it: an image lights the scene from every direction,
and what occludes that light is the image itself — its dominant bright
region, the sun in a sky map.
- dominant_direction finds it: the radiance-weighted mean texel
direction (solid-angle weighted, rotation applied). A uniform image
cancels to zero — a flat default environment shines from everywhere
at once and casts no shadow, which a uniform environment test pins.
- The shadow is one orthographic map over the scene from that
direction, after the lights' maps, like a parallel light's: PCF
widened by Shadow Diffusion, darkened by Shadow Darkness, cleared
when nothing casts, the image is directionless, or the texel budget
drops it.
- It darkens the environment's own diffuse and specular light on
Accepts Shadows surfaces and counts in Cast Shadows Only. CPU and
GPU (WGSL) do the same; Globals carries the environment's sun
direction and shadow settings (40 → 44 floats, preflight updated).
A bright-disc environment turned to shine from behind the camera now
shadows a sphere onto the floor behind it, Cast Shadows off lights it
evenly again, and the GPU scene's sky gradient takes a shadow-casting
Environment light. Also: the renderer-switch toasts read "...renderer,
which draws 3D models", the dialog's Casts Shadows checkbox gets an
auto id (dialog.light.casts) the tests click, and the docs' Environment
notes cover the shadow.