Refactor render for strict Clippy checks

This commit is contained in:
Dylan M. Taylor
2026-10-28 06:09:21 +00:00
parent 7926b8bd31
commit 5d71dce7fc
14 changed files with 141 additions and 114 deletions
+5 -3
View File
@@ -2,7 +2,9 @@ use super::frames::content_bounds;
use super::fx_host::MAX_FX_DEPTH;
use super::layers::StyledLayer;
use super::opts::LayerTiming;
use super::*;
use super::{
Arc, AutoPick, Buf, EffectCtx, EvalCtx, Image, ItemId, ItemKind, Layer, LayerSource, Mat3, MatteKind, Renderer, Tick, cache, color, masks, three_d, vec2,
};
/// Which compositor renders a frame (Project Settings ▸ Video Rendering and Effects).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
@@ -283,7 +285,7 @@ impl<'a> Renderer<'a> {
/// layers).
pub fn run_effects_on(&self, ctx: &EvalCtx, layer: &Layer, adjustment: bool, target: &mut dyn FxTarget) {
// `target` is used as it is: the comp below for adjustment layers (the callers).
self.run_effect_stack(ctx, layer, adjustment, usize::MAX, None, [0.0; 2], target);
self.run_effect_stack(ctx, layer, crate::pipeline::EffectStack { adjustment, limit: usize::MAX, origin: [0.0; 2] }, None, target);
}
/// [`Renderer::run_effects_on`] for a (non-adjustment) layer's own pixels: `target` holds
@@ -291,7 +293,7 @@ impl<'a> Renderer<'a> {
/// there. Returns the effect-space origin in layer coordinates (rebase by its negation).
pub fn run_layer_effects_on(&self, ctx: &EvalCtx, layer: &Layer, target: &mut dyn FxTarget) -> [f64; 2] {
let origin = ctx.effect_bounds(layer).1;
self.run_effect_stack(ctx, layer, false, usize::MAX, None, origin, target);
self.run_effect_stack(ctx, layer, crate::pipeline::EffectStack { adjustment: false, limit: usize::MAX, origin }, None, target);
origin
}
+11 -15
View File
@@ -25,7 +25,7 @@ const MAX_DEPTH: usize = 16;
/// interleaved (L R L R …).
pub fn mix_comp(project: &Project, footage: &dyn FootageSource, expr: Option<&dyn ExprHost>, comp: ItemId, start: Tick, frames: usize, rate: u32) -> Vec<f32> {
let mut out = vec![0.0f32; frames * 2];
mix_into(project, footage, expr, comp, start, rate, &mut out, 1.0, 1.0, 0);
mix_into(&AudioSources { project, footage, expr, rate }, comp, start, &mut out, 0);
out
}
@@ -151,7 +151,7 @@ impl MixEnv<'_> {
return out;
}
let mut sub = vec![0.0f32; src_n * 2];
mix_into(self.project, self.footage, self.expr, *item, lt, rate, &mut sub, 1.0, 1.0, self.depth + 1);
mix_into(&AudioSources { project: self.project, footage: self.footage, expr: self.expr, rate }, *item, lt, &mut sub, self.depth + 1);
sub
}
_ => return out,
@@ -206,19 +206,16 @@ impl MixEnv<'_> {
}
}
#[allow(clippy::too_many_arguments)]
fn mix_into(
project: &Project,
footage: &dyn FootageSource,
expr: Option<&dyn ExprHost>,
comp_id: ItemId,
start: Tick,
/// Shared inputs for composition audio mixing, including nested compositions.
struct AudioSources<'a> {
project: &'a Project,
footage: &'a dyn FootageSource,
expr: Option<&'a dyn ExprHost>,
rate: u32,
out: &mut [f32],
gl: f32,
gr: f32,
depth: usize,
) {
}
fn mix_into(sources: &AudioSources<'_>, comp_id: ItemId, start: Tick, out: &mut [f32], depth: usize) {
let AudioSources { project, footage, expr, rate } = *sources;
let Some(comp) = project.comp(comp_id) else { return };
if depth >= MAX_DEPTH || rate == 0 {
return;
@@ -239,7 +236,6 @@ fn mix_into(
}
let t_a = sample_tick(s0 + a as i64, rate);
let (ll, lr) = levels(&env, l, t_a);
let (ll, lr) = (ll * gl, lr * gr);
if ll == 0.0 && lr == 0.0 {
continue;
}
-1
View File
@@ -580,7 +580,6 @@ fn key_with(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool, f
/// `blur`: the layer is motion blurred in this render (`Renderer::mb_on`). `effects`: key only
/// the first that many effects (a layer's input, see [`input_key`]).
#[allow(clippy::too_many_arguments)]
fn key_any(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool, footage: bool, effects: Option<usize>) -> Option<u64> {
let mut h = KeyHasher(0xcbf2_9ce4_8422_2325);
ctx.comp.frame_rate.as_f64().to_bits().hash(&mut h);
+1 -1
View File
@@ -1,4 +1,4 @@
use super::*;
use super::{Arc, AtomicBool, Footage, Image, ItemId, Ordering, Tick};
/// How a [`FootageSource`] hands over float footage (OpenEXR, float TIFF), whose values are
/// linear light: encoded with a curve, as the compositor takes footage, or as stored. Other
+1 -1
View File
@@ -1,6 +1,6 @@
use super::footage::NoFootage;
use super::opts::RenderOpts;
use super::*;
use super::{EvalCtx, Footage, FrameBlend, FrameRate, Image, ItemId, Layer, LayerSource, Project, Renderer, TICKS_PER_SECOND, Tick, shapes, source_size, text};
/// The layer's frame blending mode, if the comp's Enable Frame Blending switch is on.
/// One field of an interlaced frame as a full frame: the field's lines (`parity` 0 = even /
+1 -1
View File
@@ -1,4 +1,4 @@
use super::*;
use super::{Arc, Buf, EffectHost, EvalCtx, ItemKind, Layer, LayerPixels, LayerSource, Params, Renderer, Tick, three_d};
/// Layer parameters and audio for effects (see [`effectcraft_effects::EffectHost`]). What it
/// hands out is in effect space (see [`effect_bounds`]): the other layer's for its pixels, the
+15 -14
View File
@@ -1,6 +1,16 @@
use super::frames::adaptive_samples;
use super::opts::{LayerTiming, Through};
use super::*;
use super::{
Arc, BlendMode, Buf, Comp, EvalCtx, Image, IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, Layer, LayerSource, Mat3,
MatteKind, ParallelIterator, Quality, Region, Renderer, Sampling, Tick, WarpOpts, cache, color, composite_warp_changed, masks, source_size, styles, vec2,
};
/// Blend policy for placing a layer, including optional output-depth quantization.
struct PlacementBlend {
mode: BlendMode,
opacity: f32,
quantize: Option<f32>,
}
/// A motion-blur shutter: the comp's, or for a collapsed precomp's layers the outermost
/// containing comp's (they are drawn into its frames).
@@ -342,22 +352,13 @@ impl<'a> Renderer<'a> {
/// Draw a processed layer into `target` (transform, motion blur) with `mode`/`opacity`.
pub(crate) fn place(&self, ctx: &EvalCtx, layer: &Layer, buf: &Buf, target: &mut Image, mode: BlendMode, opacity: f32) {
self.place_changed(ctx, layer, buf, target, mode, opacity, None);
self.place_changed(ctx, layer, buf, target, PlacementBlend { mode, opacity, quantize: None });
}
/// [`Self::place`], returning the target pixels it changed when the warp tracked them (then
/// also quantised to `quantize` levels; see [`composite_warp_changed`]).
#[allow(clippy::too_many_arguments)]
fn place_changed(
&self,
ctx: &EvalCtx,
layer: &Layer,
buf: &Buf,
target: &mut Image,
mode: BlendMode,
opacity: f32,
quantize: Option<f32>,
) -> Option<[u32; 4]> {
fn place_changed(&self, ctx: &EvalCtx, layer: &Layer, buf: &Buf, target: &mut Image, blend: PlacementBlend) -> Option<[u32; 4]> {
let PlacementBlend { mode, opacity, quantize } = blend;
let pl = self.placement(ctx, layer, buf);
let opts = WarpOpts { sampling: pl.sampling, opacity, mode, seed: pl.seed, clip: None };
if let [m] = pl.matrices.as_slice() {
@@ -459,7 +460,7 @@ impl<'a> Renderer<'a> {
fn composite_layer(&self, ctx: &EvalCtx, layer: &Layer, buf: &Buf, canvas: &mut Image, opacity: f32, quantize: Option<f32>) -> Option<[u32; 4]> {
let matte = layer.track_matte.is_some_and(|tm| tm.layer != layer.id && ctx.comp.layer(tm.layer).is_some());
if !matte && !layer.preserve_transparency {
return self.place_changed(ctx, layer, buf, canvas, layer.blend_mode, opacity, quantize);
return self.place_changed(ctx, layer, buf, canvas, PlacementBlend { mode: layer.blend_mode, opacity, quantize });
}
// Render in isolation, then matte / preserve transparency, then blend.
let mut iso = Image::new(canvas.width, canvas.height);
+1 -1
View File
@@ -1,5 +1,5 @@
use super::accel::Backend;
use super::*;
use super::{Comp, EvalCtx, ItemId, Layer, Mat4, Tick, three_d};
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
#[serde(default, rename_all = "camelCase")]
+18 -19
View File
@@ -3,7 +3,18 @@ use super::footage::{FootageSource, is_vector_footage};
use super::frames::{blend_frames, content_bounds, field_frame, frame_blend_mode, frame_position, frame_size, interpret_pixels, roi_inside};
use super::fx_host::{FxHost, essential_overrides};
use super::opts::{Collapse3d, LayerTiming, MAX_THROUGH, RenderOpts, Through};
use super::*;
use super::{
Arc, AutoKey, AutoPick, BlendMode, Buf, Comp, EffectCtx, EffectEnv, EvalCtx, Footage, FootageKind, FrameBlend, FrameRate, GroupKind, Image, ItemId,
ItemKind, Layer, LayerCache, LayerSource, Params, Project, Quality, Region, Renderer, Tick, cache, color, masks, shapes, styles, text, three_d, vec2,
};
/// Coordinate space and stack range shared by CPU and accelerated effect evaluation.
#[derive(Clone, Copy)]
pub(crate) struct EffectStack {
pub adjustment: bool,
pub limit: usize,
pub origin: [f64; 2],
}
impl<'a> Renderer<'a> {
pub fn new(project: &'a Project, footage: &'a dyn FootageSource, opts: RenderOpts) -> Renderer<'a> {
@@ -423,26 +434,16 @@ impl<'a> Renderer<'a> {
// The comp below an adjustment layer is not in its layer space: it stays as it is.
let origin = if adjustment { [0.0; 2] } else { ctx.effect_bounds(layer).1 };
buf.rebase(origin);
let mut out = self.effect_space_stack(ctx, layer, buf, adjustment, limit, timing, origin);
let mut out = self.effect_space_stack(ctx, layer, buf, EffectStack { adjustment, limit, origin }, timing);
out.rebase([-origin[0], -origin[1]]);
out
}
/// [`Renderer::apply_effects_timed`] on a buffer already in effect space (measured from
/// `origin`, in layer coordinates); the result stays in effect space.
#[allow(clippy::too_many_arguments)]
fn effect_space_stack(
&self,
ctx: &EvalCtx,
layer: &Layer,
buf: Buf,
adjustment: bool,
limit: usize,
timing: Option<&mut Vec<(String, f64)>>,
origin: [f64; 2],
) -> Buf {
fn effect_space_stack(&self, ctx: &EvalCtx, layer: &Layer, buf: Buf, stack: EffectStack, timing: Option<&mut Vec<(String, f64)>>) -> Buf {
let mut t = CpuFx { accel: self.active_accel(), levels: self.pipe.levels, buf: Some(buf) };
self.run_effect_stack(ctx, layer, adjustment, limit, timing, origin, &mut t);
self.run_effect_stack(ctx, layer, stack, timing, &mut t);
t.buf.unwrap_or_else(|| Buf { img: Image::new(1, 1), offset: [0.0; 2], scale: self.opts.scale })
}
@@ -450,17 +451,15 @@ impl<'a> Renderer<'a> {
/// in layer coordinates): runs of GPU-capable effects go to [`FxTarget::gpu`] (when an
/// accelerator is active and supports them), the rest to [`FxTarget::cpu`] one by one
/// (quantised to the bit depth after each).
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_effect_stack(
&self,
ctx: &EvalCtx,
layer: &Layer,
adjustment: bool,
limit: usize,
stack: EffectStack,
mut timing: Option<&mut Vec<(String, f64)>>,
origin: [f64; 2],
target: &mut dyn FxTarget,
) {
let EffectStack { adjustment, limit, origin } = stack;
if !layer.switches.effects {
return;
}
@@ -873,7 +872,7 @@ impl<'a> Renderer<'a> {
let origin = ctx.effect_bounds(layer).1;
buf.rebase(origin);
if effects > 0 {
buf = self.effect_space_stack(ctx, layer, buf, false, effects, None, origin);
buf = self.effect_space_stack(ctx, layer, buf, EffectStack { adjustment: false, limit: effects, origin }, None);
}
let buf = Arc::new(buf);
// The buffer the cache keeps (another worker's, if it kept one first).
+9 -9
View File
@@ -211,16 +211,16 @@ fn collect(ctx: &EvalCtx, layer: &Layer, contents: &PropGroup, arena: &mut Vec<B
}
"star" => {
let star = ctx.e(layer, g, "type") == 0;
let p = effectcraft_path::polystar(
let p = effectcraft_path::polystar(effectcraft_path::Polystar {
star,
ctx.f(layer, g, "points", 5.0),
ctx.v2(layer, g, "position", [0.0; 2]),
ctx.f(layer, g, "rotation", 0.0),
ctx.f(layer, g, "innerRadius", 50.0),
ctx.f(layer, g, "outerRadius", 100.0),
ctx.f(layer, g, "innerRoundness", 0.0),
ctx.f(layer, g, "outerRoundness", 0.0),
);
points: ctx.f(layer, g, "points", 5.0),
pos: ctx.v2(layer, g, "position", [0.0; 2]),
rotation: ctx.f(layer, g, "rotation", 0.0),
inner: ctx.f(layer, g, "innerRadius", 50.0),
outer: ctx.f(layer, g, "outerRadius", 100.0),
inner_round: ctx.f(layer, g, "innerRoundness", 0.0),
outer_round: ctx.f(layer, g, "outerRoundness", 0.0),
});
push_shape(ctx, layer, g, p, arena, &mut live);
}
"path" => {
+53 -31
View File
@@ -287,11 +287,13 @@ pub fn char_transforms(ctx: &EvalCtx, layer: &Layer, text: &PropGroup, lay: &Tex
let pos = get("position").map(|v| c3(v, 0.0));
let anchor = get("anchor").map(|v| c3(v, 0.0));
let scale = get("scale").map(|v| c3(v, 100.0));
let rot = get1("rotation");
let rx = get1("rotationX");
let ry = get1("rotationY");
let skew = get1("skew");
let skew_axis = get1("skewAxis");
let angles = CharacterAngles {
rotation: get1("rotation"),
rotation_x: get1("rotationX"),
rotation_y: get1("rotationY"),
skew: get1("skew"),
skew_axis: get1("skewAxis"),
};
let op = get1("opacity");
let fill = props.get("fillColor").map(|p| ctx.value(layer, p).as_color());
let stroke = props.get("strokeColor").map(|p| ctx.value(layer, p).as_color());
@@ -338,21 +340,7 @@ pub fn char_transforms(ctx: &EvalCtx, layer: &Layer, text: &PropGroup, lay: &Tex
c.scale[d] *= 1.0 + (s[d] / 100.0 - 1.0) * k3[d];
}
}
if let Some(r) = rot {
c.rotation += r * k;
}
if let Some(r) = rx {
c.rotation_x += r * k;
}
if let Some(r) = ry {
c.rotation_y += r * k;
}
if let Some(s) = skew {
c.skew += s * k;
}
if let Some(a) = skew_axis {
c.skew_axis = a;
}
angles.apply(c, k);
if let Some(o) = op {
c.opacity *= 1.0 + (o / 100.0 - 1.0) * k;
}
@@ -381,17 +369,7 @@ pub fn char_transforms(ctx: &EvalCtx, layer: &Layer, text: &PropGroup, lay: &Tex
if let Some(w) = sw {
c.stroke_width += w * k;
}
if let Some(t) = tracking {
let t = t * k;
match tracking_type {
1 => c.track_before += t,
2 => c.track_after += t,
_ => {
c.track_before += t * 0.5;
c.track_after += t * 0.5;
}
}
}
apply_tracking(c, tracking, tracking_type, k);
if let Some(a) = line_anchor
&& k.abs() >= c.line_anchor_k
{
@@ -432,6 +410,50 @@ pub fn char_transforms(ctx: &EvalCtx, layer: &Layer, text: &PropGroup, lay: &Tex
out
}
/// Optional animator orientation properties; an omitted property leaves the glyph unchanged.
struct CharacterAngles {
rotation: Option<f64>,
rotation_x: Option<f64>,
rotation_y: Option<f64>,
skew: Option<f64>,
skew_axis: Option<f64>,
}
impl CharacterAngles {
fn apply(&self, c: &mut CharXf, k: f64) {
if let Some(r) = self.rotation {
c.rotation += r * k;
}
if let Some(r) = self.rotation_x {
c.rotation_x += r * k;
}
if let Some(r) = self.rotation_y {
c.rotation_y += r * k;
}
if let Some(s) = self.skew {
c.skew += s * k;
}
if let Some(a) = self.skew_axis {
c.skew_axis = a;
}
}
}
/// Distribute selected tracking before and/or after a glyph according to Tracking Type.
fn apply_tracking(c: &mut CharXf, tracking: Option<f64>, tracking_type: u32, k: f64) {
if let Some(t) = tracking {
let t = t * k;
match tracking_type {
1 => c.track_before += t,
2 => c.track_after += t,
_ => {
c.track_before += t * 0.5;
c.track_after += t * 0.5;
}
}
}
}
/// Character Offset / Character Value substitution (None = unchanged).
pub fn substitute(ch: char, x: &CharXf) -> Option<char> {
if ch.is_whitespace() {
+13 -2
View File
@@ -82,9 +82,20 @@ pub fn f_schlick(f0: [f32; 3], v_h: f32) -> [f32; 3] {
[f0[0] + (1.0 - f0[0]) * k, f0[1] + (1.0 - f0[1]) * k, f0[2] + (1.0 - f0[2]) * k]
}
/// Surface parameters used by the direct-light BRDF after texture sampling.
#[derive(Clone, Copy)]
pub struct BrdfSurface {
pub base: [f32; 3],
pub metallic: f32,
pub roughness: f32,
pub specular: f32,
pub diffuse: f32,
}
/// Cook–Torrance BRDF × N·L × π for one light: (diffuse colour factor, specular colour).
/// Returned as the reflected fraction of the light's irradiance.
pub fn brdf(base: [f32; 3], metallic: f32, rough: f32, spec_k: f32, diff_k: f32, n: [f32; 3], v: [f32; 3], l: [f32; 3]) -> [f32; 3] {
pub fn brdf(surface: BrdfSurface, n: [f32; 3], v: [f32; 3], l: [f32; 3]) -> [f32; 3] {
let BrdfSurface { base, metallic, roughness: rough, specular: spec_k, diffuse: diff_k } = surface;
let n_l = dot(n, l);
if n_l <= 0.0 {
return [0.0; 3];
@@ -484,7 +495,7 @@ pub fn shade(s: &Scene, f: &Frag) -> Option<[f32; 4]> {
continue;
}
let sh = if m.receives_shadows { light_shadow(s, l, f.pos, ng, n_l) } else { 1.0 };
let b = brdf(rgb, metallic, rough, m.specular_k, m.diffuse_k, n, v, to_l);
let b = brdf(BrdfSurface { base: rgb, metallic, roughness: rough, specular: m.specular_k, diffuse: m.diffuse_k }, n, v, to_l);
for c in 0..3 {
out[c] += b[c] * l.color[c] * k * sh;
}
+3 -3
View File
@@ -16,7 +16,7 @@ fn brdf_matches_cook_torrance_formula() {
let v = shade::norm([0.3, 0.1, 1.0]);
let l = shade::norm([-0.4, 0.2, 1.0]);
let (base, metal, rough) = ([0.8, 0.4, 0.2], 0.3f32, 0.45f32);
let got = shade::brdf(base, metal, rough, 1.0, 1.0, n, v, l);
let got = shade::brdf(shade::BrdfSurface { base, metallic: metal, roughness: rough, specular: 1.0, diffuse: 1.0 }, n, v, l);
let h = shade::norm([v[0] + l[0], v[1] + l[1], v[2] + l[2]]);
let (nv, nl, nh, vh) = (shade::dot(n, v), shade::dot(n, l), shade::dot(n, h), shade::dot(v, h));
let a2 = (rough * rough).powi(2);
@@ -30,9 +30,9 @@ fn brdf_matches_cook_torrance_formula() {
assert!((got[c] - expect).abs() < 1e-5, "channel {c}: {} vs {expect}", got[c]);
}
// No light from behind the surface.
assert_eq!(shade::brdf(base, metal, rough, 1.0, 1.0, n, v, [0.0, 0.0, -1.0]), [0.0; 3]);
assert_eq!(shade::brdf(shade::BrdfSurface { base, metallic: metal, roughness: rough, specular: 1.0, diffuse: 1.0 }, n, v, [0.0, 0.0, -1.0]), [0.0; 3]);
// A white rough dielectric lit head-on reflects about its base colour (π folded in).
let w = shade::brdf([1.0; 3], 0.0, 1.0, 1.0, 1.0, n, n, n);
let w = shade::brdf(shade::BrdfSurface { base: [1.0; 3], metallic: 0.0, roughness: 1.0, specular: 1.0, diffuse: 1.0 }, n, n, n);
assert!(w[0] > 0.95 && w[0] < 1.05, "{w:?}");
}
+10 -13
View File
@@ -197,7 +197,7 @@ fn mb_samples(r: &Renderer, ctx: &EvalCtx, layer: &Layer) -> usize {
/// Prepare a layer (buffer, geometry, material). `None` when it shows nothing.
pub(crate) fn prepare<'a>(r: &Renderer, ctx: &EvalCtx<'a>, layer: &'a Layer, order: usize, out: (u32, u32), in_run: bool) -> Option<Item<'a>> {
let buf = r.blend_layer_buf(ctx, layer)?;
prepare_with(r, ctx, layer, order, out, in_run, buf, Mat4::IDENTITY)
prepare_with(r, ctx, layer, order, out, in_run, PlaneInput { buf, local: Mat4::IDENTITY })
}
/// Prepare a layer as planes: one per character for per-character 3D text, else one.
@@ -205,24 +205,21 @@ pub(crate) fn prepare_all<'a>(r: &Renderer, ctx: &EvalCtx<'a>, layer: &'a Layer,
if crate::text::per_char_3d(ctx, layer) {
return crate::text::per_char_planes(ctx, layer, r.opts.scale)
.into_iter()
.filter_map(|(buf, m)| prepare_with(r, ctx, layer, order, out, in_run, r.to_blend(Arc::new(buf), None), m))
.filter_map(|(buf, m)| prepare_with(r, ctx, layer, order, out, in_run, PlaneInput { buf: r.to_blend(Arc::new(buf), None), local: m }))
.collect();
}
prepare(r, ctx, layer, order, out, in_run).into_iter().collect()
}
/// Prepare a plane showing `buf`, whose space maps into the layer through `local`.
#[allow(clippy::too_many_arguments)]
fn prepare_with<'a>(
r: &Renderer,
ctx: &EvalCtx<'a>,
layer: &'a Layer,
order: usize,
out: (u32, u32),
in_run: bool,
mut buf: Arc<Buf>,
/// A processed plane and its mapping into the layer's coordinate system.
struct PlaneInput {
buf: Arc<Buf>,
local: Mat4,
) -> Option<Item<'a>> {
}
/// Prepare the plane's geometry, motion samples and material.
fn prepare_with<'a>(r: &Renderer, ctx: &EvalCtx<'a>, layer: &'a Layer, order: usize, out: (u32, u32), in_run: bool, plane: PlaneInput) -> Option<Item<'a>> {
let PlaneInput { mut buf, local } = plane;
if buf.img.is_empty() {
return None;
}