Skip to content
All guides

Guide 02

Build a native plugin

What a Premation plugin is now, how to build the samples, how an effect is put together against the C SDK, how to test it, and how the engine treats it when it crashes.

15 min readIntermediate

A plugin is a shared library — a .dll on Windows, a .dylib on macOS — plus a small JSON manifest. The engine, the C++ process that draws every Premation frame, loads it and calls it. The API is modelled on After Effects' effect API, so if you have written an After Effects effect the selectors, the parameter types and the smart-render checkouts will be familiar.

This replaces the JavaScript plugin system

The JavaScript and WGSL plugin system was removed in 0.9.0, along with its permissions model. Projects that used it still open; their plugin effects and layers are kept in the project (an effect passes the picture through), and one notice names the missing plugins. Native plugins are the only plugin system now.

Where it stands in 1.0

SDK 1.2 shipped with Premation 1.0. Plugin effects are listed in the Effects browser's Plugins folder, with any presets the bundle ships. A plugin can also be something other than an effect — a menu command, an importer, an export format or a panel — the way After Effects splits AEGP and AEIO plugins from effects. The installers' Dashboard ▸ Plugins has a store with Store, Installed and Publish sections, and a .pplugin file installs with a double-click. SDK 1.0 and 1.1 plugins still load unchanged.

1. What is in the SDK

PieceWhere
Headersnative/sdk/include/premation_sdk/ — a C interface with a versioned ABI; premation_sdk.h includes all of it. pr_command.h and pr_io.h are the SDK 1.2 headers for commands, importers and export formats
Samplesnative/sdk/samples/ — twelve, listed below
Hostnative/engine/src/plugins/ — runs inside premation-engine
Test toolpremation-plugins — list, render and crash-check a plugin folder without the editor
Referencedocs/PLUGIN_SDK.md in the repository, with an After Effects mapping for every part, and docs/PLUGIN_STORE.md for packaging and signing

The samples cover deliberately different parts of the API.

SampleWhat it shows
rippleA CPU effect using smart render, 8, 16 and 32-bit pixels, and rows in parallel through the host
ringsA generator with per-instance sequence data, a button that reshuffles it, an on-canvas handle and a panel
checkoutLayer Displace, which reads another layer, and Time Echo, which reads its own layer at other times
gradeA GPU effect on the engine's device, with a CPU fallback
grademapThe SDK 1.1 parameter types: a tone curve, a gradient and a .cube LUT file
particlesThe comp camera, lights and layer transforms; trails and its own motion blur from other times; presets
model3dThe Element 3D class: a glTF model drawn through the comp camera, hidden behind the scene's own 3D layers, with a handle that moves it in 3D
silkwavesA looping GPU and CPU generator
distributeA menu command that edits the project through the engine API
ppmioAn importer for formats the engine cannot read: PPM / PFM footage, and a sound-only format
ppmseqAn export format, listed in the Export dialog and the Render Queue
statspanelA dockable panel with a command behind it

2. Build and run the samples

You need the repository and the native toolchain described in native/README.md: clang (clang-cl on Windows), CMake, Ninja and vcpkg. The samples build with the engine, from the native directory.

# Windows
cmake --preset windows-clang-cl-engine
cmake --build --preset windows-clang-cl-engine

# macOS
cmake --preset macos-clang-engine
cmake --build --preset macos-clang-engine
  1. 1

    Look in the build tree for a plugins folder.

    What happens Each sample is laid out as an installable bundle: plugins/<name>/ holding premation-plugin.json and the binary it names. The grade sample and the GPU half of checkout need Dawn's headers, so they appear only in the engine build.

  2. 2

    Run premation-plugins --plugins <that folder> list.

    What happens Every plugin the engine finds is listed as loaded, disabled, failed (with the reason) or quarantined.

  3. 3

    Run premation-plugins --plugins <that folder> --bits 32 render com.premation.samples.ripple.

    What happens Renders the effect at 32-bit float without an editor. Use --bits 8 or 16 for the other depths.

3. The manifest

{
  "manifestVersion": 1,
  "id": "com.example.glow",
  "name": "Example Glow",
  "version": "1.2.0",
  "vendor": "Example",
  "sdk": { "major": 1, "minor": 0 },
  "binary": { "windows": "glow.dll", "macos": "libglow.dylib", "linux": "libglow.so" },
  "effects": [ { "matchName": "com.example.glow", "name": "Glow", "category": "Example" } ]
}
RuleWhy
The manifest is read and version-checked before the binary is loadedA malformed manifest, a missing binary or an incompatible SDK leaves the plugin listed as failed with the reason, and none of its code has run
The id is 1 to 100 characters from A-Z, a-z, 0-9, dot, underscore and hyphen, and starts with a letterAn effect's match name is the id, or the id followed by a dot and a name, which keeps match names unique across plugins and apart from the built-in effect types
The plugin's SDK major must equal the host's, and its minor must not exceed the host'sA plugin built against a newer SDK than the engine offers is refused up front

4. The entry point

A module exports one symbol, PremationPluginInfo, which lists its effects. Each effect has its own entry function, which receives a selector and dispatches on it. This is ripple's, trimmed to its shape; the prs:: helpers are the samples' own convenience layer in samples/common, not part of the SDK.

#include <premation_sdk/premation_sdk.h>

PrErr PR_CALL ripple_main(PrCmd cmd, const PrInData* in, PrOutData* out,
                          PrParamDef* const* params, PrWorld*, void*) {
  switch (cmd) {
    case PR_CMD_ABOUT:
      prs::message(out, "Ripple 1.0 — Premation SDK sample (CPU effect, 8/16/32 bpc).");
      return PR_ERR_NONE;
    case PR_CMD_GLOBAL_SETUP:
      out->my_version = PR_VERSION(1, 0, 0);
      out->out_flags = PR_OUT_FLAG_DEEP_COLOR_AWARE | PR_OUT_FLAG_FLOAT_COLOR_AWARE |
                       PR_OUT_FLAG_SMART_RENDER | PR_OUT_FLAG_THREADED_RENDER;
      return PR_ERR_NONE;
    case PR_CMD_PARAMS_SETUP:      return params_setup(in);
    case PR_CMD_SMART_PRE_RENDER:  return in->host->checkout_layer(in->host_ref, 0, 0, in->current_time, nullptr);
    case PR_CMD_SMART_RENDER:      return smart_render(in, out, params);
    default:                       return PR_ERR_NONE;
  }
}

const PrEffectEntry kEffects[] = {{"com.premation.samples.ripple", &ripple_main}};
const PrPluginInfo kInfo = {sizeof(PrPluginInfo), PR_SDK_VERSION,
                            "com.premation.samples.ripple", 1, kEffects};

extern "C" PR_EXPORT const PrPluginInfo* PR_CALL PremationPluginInfo(void) { return &kInfo; }

The plugin id and the match names in that table must equal the manifest's. The selectors map one to one onto After Effects':

SelectorWhat the plugin does
ABOUTPuts text in the return message
GLOBAL_SETUP / GLOBAL_SETDOWNSets the out flags and version, and owns any global data
PARAMS_SETUPCalls add_param for each parameter, in order
SEQUENCE_SETUP / RESETUP / FLATTEN / SETDOWNPer-instance state. FLATTEN hands back the pointer-free bytes the project stores
FRAME_SETUP / RENDER / FRAME_SETDOWNNon-smart render: read the input world, write the output world
SMART_PRE_RENDER / SMART_RENDERCheck out layers in pre-render; read the checkouts and write the output in render
USER_CHANGED_PARAMA button was pressed, or a supervised parameter changed. May set parameter values and sequence data
UPDATE_PARAMS_UIEnable, hide or rename parameters
GPU_DEVICE_SETUP / SETDOWN, SMART_RENDER_GPUSet up and draw on the engine's Dawn device

Out flags declare what the effect can do: DEEP_COLOR_AWARE (16-bit), FLOAT_COLOR_AWARE (32-bit float), SMART_RENDER, GPU_RENDER, SEQUENCE_DATA, GENERATOR, WIDE_TIME_INPUT, NON_PARAM_VARY, SEND_UPDATE_PARAMS_UI and THREADED_RENDER.

5. Parameters

Parameters are declared, and the engine keyframes and evaluates them. At every render the plugin receives each parameter's value at that frame's time; it never interpolates anything itself. In the project each parameter is an ordinary effect property, so undo, keyframes, expressions, copy and paste, save and open, and change events work as they do for a built-in effect.

ParameterIn the document
SLIDER, FLOAT_SLIDER, ANGLEA number (an angle in degrees)
POINT, POINT_3DNumbers, in layer pixels from the layer centre; the plugin receives layer pixels
COLORA colour, delivered as straight RGBA from 0 to 1
POPUPA choice, counted from 1
CHECKBOXA boolean
LAYERA reference to another layer, or none
PATHA mask of the layer, as its bezier at the frame's time
ARBITRARY_DATABytes stored in the project, written by the plugin
BUTTONAn action
GROUP_START / GROUP_ENDA twirl-down section

Parameter flags include CANNOT_ANIMATE, SUPERVISE, HIDDEN, START_COLLAPSED and DISABLED.

6. Pixels and time

TopicWhat to know
The worldFull-frame, premultiplied, in the linear working space, at the size of the effect chain's buffer. in_data->layer_to_world maps layer pixels to world pixels; scale pixel-sized parameters by pixel_scale_x and pixel_scale_y
DepthAs in After Effects, a 32-bit project gives FLOAT_COLOR_AWARE effects a 32-bit float world, DEEP_COLOR_AWARE effects a 16-bit one, and every other effect an 8-bit one. The conversions are pure functions, so the same frame always converts to the same bytes
Rows in paralleliterate(count, fn) runs your function for each row on the host's worker pool, and each job is crash-guarded
TimeTimes are integer ticks, 705,600,000 to the second. current_time is the layer's time and comp_time is the composition's
CheckoutsIn SMART_PRE_RENDER an effect may check out its own input, another layer named by a LAYER parameter, or its own layer at another time (with WIDE_TIME_INPUT)
SDK 1.2checkout_time and checkout_param give parameter values, the comp camera, lights and layer transforms at other times (PR_OUT_FLAG_SAMPLES_TIME); checkout_scene_depth gives the depth of the comp's own 3D layers (PR_OUT_FLAG_USES_SCENE_DEPTH); the shutter fields let an effect render its own motion blur; and the result can extend past the layer, in layer or composition space

Checkouts at another time are resolved from the frame itself, so a frame stays a pure function of the document: preview, export and a restarted engine render the same pixels.

7. Sequence data and buttons

Per-instance state lives in the project, not only in the process. Adding the effect runs SEQUENCE_SETUP, FLATTEN and SETDOWN and stores the flat bytes with the effect in the same undo entry. A render rebuilds the instance from those bytes whenever they change. A button press produces new bytes and any parameter writes as one undo entry, so undo brings back the old bytes and the old bytes render the old picture. This is how the rings sample keeps its palette: shuffle, undo and redo all render exactly.

8. A GPU effect

An effect with GPU_RENDER records WebGPU commands into the host's command encoder, on the engine's own Dawn device. The input is the effect chain's texture as it is, the output is a texture the host owns, and nothing is copied or read back.

RuleReason
Call WebGPU through device->procs, Dawn's procedure tableNever link a second copy of Dawn
Expect the call to run inside a crash guard and a validation and out-of-memory error scopeA GPU error drops the command buffer unsubmitted, and the effect renders on its CPU path for that frame
GPU_DEVICE_SETUP runs once per device; if it fails the CPU path is usedWhen the engine's device changes, the host sends GPU_DEVICE_SETDOWN for the old one first and sets up again on the next frame
Keep the GPU and CPU paths the same mathsAt 16-bit they can still differ on over-range values: the CPU world is integer and clips, the GPU buffer is half float and does not

premation-render --plugins <dir> renders a scene that contains plugin effects outside the editor, and --plugin-gpu 0 forces the CPU path so you can compare the two.

9. Plugins that are not effects

Since SDK 1.2 a bundle can list commands, importers, export formats and panels in its manifest, with or without effects. They map onto the After Effects plugin classes that are not effects.

KindAfter EffectsWhat Premation does with it
Command (pr_command.h)AEGP menu commandListed in the Plugins menu and the command palette. It reads and edits the document through the engine API as JSON; one run is one undo entry, and if it fails or crashes every edit rolls back
Importer (pr_io.h)AEIO inputFiles with its extensions import and render through the plugin, sound included, and the analysis jobs read the frames through it too
Export format (pr_io.h)Output moduleListed in the Export dialog's Plugins category and the Render Queue, with its declared options as controls; it runs in the export job's own process
Panel (manifest)AEGP panelAn HTML page from the bundle, in a sandbox with no Node, storage or network. It docks or pops out into its own window and edits the project only through engine commands, each one an undo entry

A bundle can also ship presets (listed under its effect in the Effects browser) and draw on-canvas handles that drag its parameters. The distribute, ppmio, ppmseq and statspanel samples show one of each kind.

10. What happens when it crashes

Every selector call runs through a guard and a crash journal. Each sample has a Debug ▸ Fault popup that injects each class of fault, and the host tests use it to prove every row below in a child process.

What the plugin doesWhat happens
An access violation, divide by zero, stack overflow, illegal instruction or C++ exception in a render selectorThat instance is disabled: the layer renders as if the effect were off, the failure is recorded against the layer, and the frame and every other layer continue
The same in setup, parameter or sequence selectorsThe plugin fails to load and is listed as failed, with the reason
Returns an errorThe call fails and the frame renders without it; the instance stays enabled
Hangs for longer than the watchdog (10 seconds by default)The plugin is quarantined in the journal and the engine ends; the supervisor restarts it and replays the document
abort(), exit(), or heap corruption that does not faultThe engine dies. The journal survives, and the next start quarantines the plugin

A quarantined plugin is listed but not loaded. Re-enabling it through the engine API retries it and clears the quarantine. On macOS a stack overflow is reported as an access violation, because both arrive as SIGSEGV.

premation-plugins --plugins <dir> crash-check com.premation.samples.ripple 2   # 2 = access violation

This is fault containment, not a sandbox

A native plugin runs inside the engine process with your user's privileges. The guards stop a bug from taking down a frame or the editor; they do not stop hostile code. Install only plugins you trust.

11. Install a bundle

  1. 1

    Copy the bundle folder — premation-plugin.json and its binary together — into the plugins folder inside the app's user-data directory. In the installers, Dashboard ▸ Plugins ▸ Installed ▸ Open plugins folder opens it.

    What happens The desktop app passes that folder to the engine as native-plugins. A folder may be a bundle itself or a folder of bundles. Load order is sorted, so it is deterministic.

  2. 2

    Or set PREMATION_PLUGIN_PATH to one or more folders, separated by semicolons.

    What happens The engine scans those as well, along with a machine-wide Plug-ins folder that a vendor's installer can write to.

  3. 3

    Click Rescan on the Installed page, or restart Premation.

    What happens A new bundle loads without a restart. The engine lists every plugin as loaded, disabled, failed or quarantined; a failed plugin says why and none of its code has run. An update or removal of a plugin that is already loaded applies at the next start.

To hand a plugin to someone else, node scripts/pack-plugin.mjs <bundle> --key <key.json> packs it into a signed .pplugin. They install it with Install from file or a double-click, and the dialog names the publisher, what the plugin adds and how far its signature can be trusted before anything is installed. The Publish section of Dashboard ▸ Plugins lists it in the store, where every third-party plugin is free; a new public plugin needs a verified publisher.

The plugins folder works the same in the installers and in a build made from the repository; the store and publishing need the installers' account. A project that uses a plugin that is missing, disabled or locked keeps the effect's data, passes the layer through and says why, ready for when the plugin comes back.

What is not here

Settled decisions, so you do not plan around them. A plugin cannot add a custom layer type, and there is no scripting language like ExtendScript: automation is a C++ command or a panel that sends engine commands. After Effects, OpenFX and FxPlug plugins do not load. The store only opened with 1.0, so its catalogue is young. The JavaScript plugin permissions model is gone with the system it belonged to, and crash guards are not a security sandbox. If you need any of those today, After Effects has them.

Try it

Free and open source, and everything above is in the build you can download right now.

Read the docs