# GPU Frame Extension (xframe)
Effects that sample beyond the input frame boundary (e.g. blur, glow, radial blur)
need an "extended frame" — an output buffer larger than the input to accommodate
pixels that would otherwise be clipped at the edges.
## CPU Path (After Effects SmartFX)
AE's `SmartPreRender` / `SmartRender` API handles xframe natively:
1. In `SmartPreRender`, expand the result rect by the xframe amount on all sides,
then call `extra.union_result_rect()` and `extra.union_max_result_rect()`.
2. AE allocates a larger output buffer automatically.
3. In `SmartRender`, the output layer is larger than the input. Use
`render_cpu_direct` instead of `render_cpu` when the buffers differ in size,
since AE's `iterate_with` requires same-sized in/out layers.
The shader compensates for the offset by subtracting `xframe / src_size` from UV
coordinates, effectively centering the original input within the expanded output.
## GPU Path (Premiere Pro)
Premiere's GPU filter API does **not** automatically allocate expanded frames.
The `out_frame` passed to `GpuFilter::render()` always matches the source dimensions.
### Solution: `GPUDeviceSuite::create_gpu_ppix()`
The Premiere SDK provides `create_gpu_ppix()` which allocates a new GPU pixel buffer
with custom dimensions. This allows the effect to:
1. Compute the required xframe based on maximum blur sample displacement (e.g. `RadialblurParams::compute_xframe()`).
2. Allocate a larger output frame via `filter.gpu_device_suite.create_gpu_ppix()`.
3. Override the `Configuration` with the new buffer's data pointer, pitch, and
expanded dimensions.
4. Dispatch the kernel into the larger buffer.
5. Replace `*out_frame` with the new PPix handle — Premiere accepts this per the
SDK documentation: *"it is allowable for the effect to allocate and return a
different sized outFrame."*
### API Signature
```c
PrGPUDeviceSuite::create_gpu_ppix(
gpu_index: u32,
pixel_format: PrPixelFormat,
width: i32,
height: i32,
par_num: i32,
par_den: i32,
field_type: prFieldType,
) -> Result<PPixHand, Error>
```
### Cleanup
The allocated PPix is owned by Premiere after `*out_frame` replacement — the host
is responsible for disposing it. Do **not** call `PPixSuite::dispose()` on the
new frame yourself.
### Reference Implementation
See `radialblur/src/gpu.rs` for a complete example of GPU xframe allocation
and dispatch.
## UV Mapping in the Shader
Both CPU and GPU paths use the same VEKL shader, which expects:
- `params.xframe` — the xframe amount in pixels (scalar, same for X and Y), computed from maximum blur displacement
- `params.src_width` / `params.src_height` — the original (unexpanded) dimensions
The shader maps output pixel coordinates back to input UV space:
```hlsl
float2 xframe_offset = params.xframe / src_size_f;
float2 uv = tex_coord(gid, src_size) - xframe_offset;
```
This centers the original image within the expanded output, with `xframe` pixels
of padding on each side.
## CPU vs GPU Xframe Differences
| Buffer allocation | Automatic (AE) | Manual (`create_gpu_ppix`) |
| Output frame replacement | N/A | `*out_frame = new_ppix` |
| Dispatch method | `render_cpu_direct` | Standard GPU dispatch |
| Cleanup | AE handles | Premiere handles (after replacement) |
| Symmetric expansion | `2 × xframe` on all sides | `2 × xframe` on all sides |