prgpu 0.1.16

GPU-accelerated rendering utilities for Adobe Premiere Pro and After Effects plugins
# Migrating an existing effect to the high-level API

A typical pre-migration multi-pass effect splits its logic across a
hand-written `lib.rs` (parameter setup + UI visibility + AE selectors +
CPU render) and a `gpu.rs` (Premiere `pr::GpuFilter` impl), running into
the four-figure-line range for non-trivial bloom / blur pipelines.
Migration folds both files into one `impl Effect` with no behavioural
change — typical reduction ratios sit around 60-70%. The mapping is
mechanical.

## Old → new

| Old (per-effect handwritten)                   | New (declarative)                         |
|------------------------------------------------|--------------------------------------------|
| `impl AdobePluginGlobal for Plugin { ... }`    | `Effect::params` + `Effect::ui` + adapter trampoline |
| `impl pr::GpuFilter for PremiereGPU { ... }`   | `pub type PremiereGPU = adobe::premiere::GpuFilterAdapter<E>;` |
| Manual `Cmd_GlobalSetup` (logger / pixel formats / AEGP register) | adapter handles via `EffectDescriptor` |
| Manual `Cmd_About` strings                     | `EffectDescriptor::new("X").about("...").version(...)` |
| Manual `Cmd_UpdateParamsUi` AE PF flag flips + AEGP `set_dynamic_stream_flag` | `Effect::ui` + `ParamApi::visibility` |
| Manual `Cmd_UserChangedParam` if/else by `param_index` | `ParamApi::actions::on_click(P, callback)` |
| Manual `SmartPreRender` expansion arithmetic   | `Effect::expansion` returning `ExpansionExtent` |
| Manual frame-data extractors (`from_cpu` / `from_gpu` per host) | `Effect::frame_data(ctx)` + `Params::from_context(&ctx)` |
| Manual `Configuration::cpu(...)` / `Configuration::effect(...)` mutation per pass | `RenderGraph::add_pass` / `add_mip_chain` + `ConfigBuilder` (internal) |
| Manual `prgpu::cpu::buffer::get_or_create_with_mips` / `prgpu::gpu::buffer::get_or_create_with_mips` | `RenderGraph::declare_mip_pyramid` |
| Manual `prepare_source_copy` for Premiere alias | `RenderGraph::set_source_policy(SourcePolicy::SnapshotIfAliased { tag })` |
| Manual `bloom_prefilter(&cfg, params)` / `bloom_prefilter_cpu(...)` per backend | `Kernel<P>` descriptor + graph executor picks backend |
| `is_premiere()` / `is_after_effects()` checks | `host.supports(Capability::*)` |
| Manual licence init in `Cmd_GlobalSetup` + checks before each render | `LicenseGate::initialize` / `is_valid` |

## Mechanical migration steps

### 1. Define `FrameData`

Pull the per-pass kernel-params + any host-derived state (frame index,
time, ext_x/ext_y) into one `Copy + Send + Sync + 'static` struct.

```rust
#[derive(Clone, Copy)]
pub struct FrameData {
    pub prefilter: PrefilterParams,
    pub upsample:  UpsampleParams,
    pub composite: CompositeParams,
    pub quality: u32,
    pub frame_index: u32,
    pub ext_x: i32,
    pub ext_y: i32,
}
```

### 2. Implement `LicenseGate` (or use `NoLicenseGate`)

If your effect has no licence check:

```rust
impl Effect for MyEffect { type License = prgpu::effect::NoLicenseGate; ... }
```

Otherwise wrap your existing licence calls. See [`license_gate.md`](license_gate.md).

### 3. Implement `Effect`

```rust
#[derive(Default)]
pub struct MyEffect;

impl Effect for MyEffect {
    type Params = Params;
    type FrameData = FrameData;
    type License = MyLicense;

    fn descriptor() -> EffectDescriptor { /* metadata */ }
    fn params(p, in_data, out_data) -> Result<(), ae::Error> { Params::setup(p, in_data, out_data) }
    fn ui(api: &mut ParamApi<Params>) -> Result<(), ae::Error> { /* visibility + actions */ Ok(()) }
    fn frame_data(ctx: FrameDataContext<Params>) -> Result<FrameData, ae::Error> { /* ... */ }
    fn expansion(ctx: ExpansionContext<Params>) -> Result<ExpansionExtent, ae::Error> { /* ... */ }
    fn pipeline(g: &mut RenderGraph<FrameData>) { /* ... */ }
}
```

### 4. Add the adapter trampoline + macros

```rust
#[derive(Default)]
struct Plugin(prgpu::adobe::ae::EffectAdapter<MyEffect>);

impl AdobePluginGlobal for Plugin {
    fn params_setup(&self, p, i, o) -> Result<(), ae::Error> { self.0.params_setup(p, i, o) }
    fn handle_command(&mut self, c, i, o, p) -> Result<(), ae::Error> { self.0.handle_command(c, i, o, p) }
}

ae::define_effect!(Plugin, (), Params);

pub type PremiereGPU = prgpu::adobe::premiere::GpuFilterAdapter<MyEffect>;
premiere::define_gpu_filter!(PremiereGPU);
```

### 5. Delete `gpu.rs`

The Premiere GPU adapter handles the entire `pr::GpuFilter::render` body
through the graph + `Effect::frame_data`. The handwritten `gpu.rs` file
becomes redundant.

### 6. Remove the manual `handle_command` match

Every selector arm collapses into the trait methods above.

### 7. Update tests

Tests that referenced `mod gpu::PremiereGPU` now reference
`crate::PremiereGPU` (the type alias). Tests that used `PremiereGPU` as a
value (the unit struct) must use `PremiereGPU::default()`.

## Backward compatibility for kernels

`declare_kernel!(name, P)` still emits the legacy top-level functions
(`name(cfg, params)`, `name_cpu(...)`, `NAME_CPU_DISPATCH_TILE`) as
`#[deprecated]` wrappers. Effects that haven't migrated yet keep
compiling with deprecation warnings instead of errors.

`kernel_params!` is unchanged from a caller's perspective. The macro
internally now layers `#[gpu_struct]` and auto-implements `KernelParams`,
but `from_cpu` / `from_gpu` keep the same signatures. Migration adds
`from_context` as a host-agnostic shorthand for use inside
`Effect::frame_data`.

## Verifying parity

Run any GPU render tests against a baseline output PNG before and after
migration. The reference output should be byte-identical — every behaviour
the legacy code expressed (popup normalisation, BGRA layout, Premiere
alias snapshot, mip chain dispatch order) lives in prgpu now and produces
the same kernel inputs.

The recommended workflow:

1. Capture the pre-migration test output as a golden PNG.
2. Tag the pre-migration commit (e.g. `git tag pre-prgpu-migration`).
3. Run the migration.
4. Re-run the test; diff the output PNG against the golden.
5. SHA256 should match exactly. If it doesn't, audit each pass's
   `Configuration` fields (output dims, pitch, mip levels, source/dest
   pointers) against the legacy code's manual values.