Files
Eduardo Andrade Machado fc0e73e342 render, gpu: adjustment layers composited on the GPU (#3032)
* render, gpu: adjustment layers composited on the GPU

An adjustment layer anywhere at a time sent the whole frame to the CPU
renderer, so GPU rendering did nothing for the frames under one. A
full-frame adjustment layer at 100 % opacity, Normal, unmasked and with
only effects the GPU stage runs is now a PlanStep::Adjust: its effects
run in the effect stage on the accumulator (the composite of the tracks
below) and the result replaces it before the tracks above are drawn.
Every other adjustment layer keeps the whole-frame CPU fallback.

The export renderer takes Composite plans without transitions; frames
with a Push still go to the CPU there, as before. perf.stats gpu gains
adjustments / cpuAdjustments.

* docs: adjustment layers on the GPU compositor in parity and performance

hardware-parity.md gains an Adjustment layers row; performance.md gains
the main-against-branch export timings with an adjustment layer.

* render, gpu: adjustment layer review fixes

- is_cpu_frame and plan_frame now ask the same adjustment_ops (evaluated
  at the render scale, which is_cpu_frame takes from the RenderOptions):
  a Transform that shrinks the frame no longer takes a pooled export
  renderer only to be rendered on the CPU and counted as a GPU frame.
- FxOp::eval leaves a Transform with its shutter angle on (motion blur)
  to the CPU; the GPU drew it sharp, on clips and adjustment layers.
- The effect stage's texture pool spans the whole composite frame, so an
  adjustment step's working textures are reused instead of made again
  every frame. Composite plans honour resolve = false (export).
- ExportRenderer::render hands frames with an adjustment layer back to
  the CPU when the device has no effect stage, instead of compositing
  them on the CPU and uploading the result to read it back.
- adjustment_covers_output checks the full-frame geometry without
  building the coverage image.
- The GPU adjustment test also runs without an effect stage.

* render, gpu, export, ui: plan adjustment layers for the device's caps

plan_frame and is_cpu_frame take plan::DeviceCaps (how large a frame
the device runs adjustment layers on; none without an effect stage), so
a frame the compositing device can't adjust becomes a CPU image on the
thread that plans it, never on the UI thread or inside a renderer slot.

- Monitor: the caps are part of the frame key
  (Target::SequencePlan(seq, caps)), so a plan made for another device
  is never reused.
- Export: FrameRenderer::caps is required; the renderer pool plans with
  the smallest caps of its renderers before it takes one.
- The compositor's own CPU fallback stays as a safety net for a plan
  made for other caps; the tests check that nothing reaches it.
- Main's own-shutter Transform check replaces the duplicate guard in
  gpufx.

Tests: a Push under an adjustment layer exported on the GPU against
render_sequence (forward and reversed, scales 1 and 0.5); routing
without an effect stage in the export pipeline and in the monitor's
frame server; GPU parity against render_sequence with colour and alpha
for a partly transparent canvas, stacked adjustment layers, an empty
canvas, a nested sequence below and one inside a nest, at scales 1 and
0.5; caps boundaries and is_cpu_frame agreeing with plan_frame.

* gpu: end a refused effect image's frame; count steps once drawn

effect_image began an effect stage frame and returned without ending it
when the stage refused the job, so the stage stayed mid-frame and kept
its working images. The compositor's step counters are added once the
whole frame is drawn on the GPU, so a frame that falls back half way
no longer counts the steps it threw away.
2026-11-06 10:57:26 +01:00
..