M12.2: gpu compositor and gpu effects

New L3 crate effectcraft-gpu: a wgpu compute backend for the per-frame
compositing walk (layer transforms with the CPU's nearest/bilinear/bicubic
sampling and minification pre-filter, motion-blur accumulation, track
mattes, Preserve Transparency, layer style passes and knockout, all 38
blend modes as a WGSL port of color::blend, 8/16 bpc clamp+quantise,
colour-space conversions). 3D runs, adjustment and wireframe layers run on
the CPU between GPU steps. GPU effects with the CPU semantics: Gaussian,
Fast Box and Directional Blur, Glow, Levels, Curves, Hue/Saturation, Tint,
Fill, Gradient Ramp, Fractal Noise, Drop Shadow, Brightness & Contrast,
Exposure, Invert, Transform (registry gpu badge from effects::GPU_EFFECTS).

render: Backend (Cpu/Gpu/Auto) in RenderOpts, the Accelerator trait and
Renderer::accel, GPU effect chains in the effect stack, hooks for the GPU
walk. Project setting gpu_acceleration (Mercury GPU Acceleration / Software
Only), render.backend command, file.projectSettings renderer. Session and
Render Queue exports carry the accelerator. CLI --gpu and bench --gpu.

Tests compare GPU vs CPU renders (blend modes x bit depths, transforms,
masks, mattes, motion blur, colour management, styles, collapse,
adjustment fallback, every GPU effect) and skip without an adapter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-02 10:59:08 -04:00
co-authored by Claude Opus 5.5
parent ab3b4cfb70
commit 485f562e19
30 changed files with 3625 additions and 62 deletions
+5 -2
View File
@@ -23,6 +23,8 @@ pub struct ExportJob<'a> {
pub footage: &'a dyn FootageSource,
pub expr: Option<&'a dyn ExprHost>,
pub item: &'a RenderQueueItem,
/// GPU compositor to render with when the project's renderer is Mercury GPU Acceleration.
pub accel: Option<&'a dyn effectcraft_render::Accelerator>,
/// Resolved output path (templates expanded except the `#` frame-number run).
pub path: &'a str,
}
@@ -120,6 +122,7 @@ struct Work {
project: Arc<Project>,
footage: Arc<dyn FootageSource>,
expr: Option<Arc<dyn ExprHost>>,
accel: Option<Arc<dyn effectcraft_render::Accelerator>>,
exporter: Arc<dyn Exporter>,
/// Per queue item: one (item with that output module, resolved path) per output module.
items: Vec<Vec<(RenderQueueItem, String)>>,
@@ -155,7 +158,7 @@ fn run_work(w: Work, shared: &JobShared) {
// first module's file is the item's output.
let mut r: Result<ExportResult, String> = Err("no output module".into());
for (mi, (mitem, path)) in modules.iter().enumerate() {
let job = ExportJob { project: &w.project, footage: w.footage.as_ref(), expr: w.expr.as_deref(), item: mitem, path };
let job = ExportJob { project: &w.project, footage: w.footage.as_ref(), expr: w.expr.as_deref(), accel: w.accel.as_deref(), item: mitem, path };
let rr = w.exporter.export(&job, &mut |done, total| {
let mut s = lock(&shared.state);
s.done = done;
@@ -288,7 +291,7 @@ impl Session {
return Err("nothing is queued: add a composition (Composition ▸ Add to Render Queue) and tick Render".into());
}
let ids: Vec<u64> = items.iter().filter_map(|m| m.first().map(|(i, _)| i.id)).collect();
let work = Work { project: self.project.clone(), footage: self.footage.clone(), expr: self.expr.clone(), exporter, items };
let work = Work { project: self.project.clone(), footage: self.footage.clone(), expr: self.expr.clone(), accel: self.accel.clone(), exporter, items };
let shared = Arc::new(JobShared::default());
if wait {
run_work(work, &shared);