Turn the GPU into an intelligent pre-processor so a hardware encoder rivals a CPU encoder. Pelorus runs Vulkan compute filters on frames while they stay in VRAM, fixing the psychovisual flaws a fixed-function encoder (NVENC/AMF/QSV) can't — dark-scene banding, noise-tax, missing film grain — before the encoder ever sees the pixels. The result is most of the BD-rate win of a slow x265 / SVT-AV1 encode at hardware speed and power.
Codec-agnostic. The deband, denoise, and motion-hint filters are pure
pre-processing — they improve HEVC (hevc_nvenc / hevc_qsv / hevc_vaapi
/ hevc_amf, rivaling x265) exactly as much as AV1 (av1_nvenc, …). Only
film-grain synthesis is codec-specific, and Pelorus covers both: AV1 (AOM) and
HEVC/H.265 + VVC/H.266 (H.274 / SEI FGC). See
docs/principles.md for the engineering contract.
Pelorus is the GPU sibling of vmafx (the
VMAF fork), under the same vmafx org. vmafx is the quality oracle; Pelorus
is the Vulkan home vmafx is not. They're bidirectionally wired through a
shared side-data ABI and vmafx's VMAF-in-the-loop autotune — see
docs/architecture/overview.md.
# build + install the shared core (the FFmpeg filters link it)
meson setup build && ninja -C build && ninja -C build install
# regenerate and replay the stack at the pinned n9.0.2 commit; both scripts
# first fetch and verify the shared FFmpeg fix series (needs cosign and gh auth)
cd ffmpeg-patches
FFMPEG_REPO=/absolute/path/to/ffmpeg ./generate.sh
FFMPEG_REPO=/absolute/path/to/ffmpeg ./test/build-and-run.sh
# zero-copy: Vulkan decode -> smart deband -> Vulkan Video encode
ffmpeg -init_hw_device vulkan=vk:0 -filter_hw_device vk \
-hwaccel vulkan -hwaccel_device vk -hwaccel_output_format vulkan \
-i input.mkv \
-vf "pelorus_deband_vulkan=range=15:dither=bluenoise:dynamic=1" \
-c:v hevc_vulkan -pix_fmt vulkan -qp 28 out.mkv # or: av1_vulkanNVENC, QSV, VAAPI, and AMF use different FFmpeg hardware-frame domains, so each
needs its own boundary instead of AV_PIX_FMT_VULKAN frames. None of them needs
hwdownload. NVENC takes a VRAM-to-VRAM hwupload to CUDA on a Vulkan device
created with disable_multiplane=1. Behind NVDEC, only graphs that end in one
writing filter pass this hop today (#296); with a software decoder the
measured graphs pass (8-bit). See
NVENC. On Linux, VAAPI and QSV take Pelorus output
through hwmap when the last filter runs with tiling=drm
(Vulkan output pools). AMF has no
Vulkan input in FFmpeg n9.0.2. Only software encoders (libaom, SVT-AV1, x265)
need hwdownload. Recipes and the hardware each ran on:
the zero-copy pipeline.
| # | Rule |
|---|---|
| 1 | NASA/JPL Power of 10 (C, original) — bounded loops, checked returns, page-size functions |
| 2 | SEI CERT C mandatory; MISRA C:2012 informative — cite rule IDs in review |
| 3 | K&R, 4-space, 100 columns (.clang-format, identical to vmafx) |
| 4 | C4 + ADRs for every non-trivial decision (docs/adr/) |
| 5 | Zero-copy Vulkan: frames never leave VRAM mid-pipeline; validation-clean |
| 6 | Append-only interop ABI; never reorder/remove a wire field |
| 7 | Per-surface docs + changelog fragment in the same PR as the code |
Every merged commit: 0 warnings · clang-tidy clean · meson test --suite=fast
green · the deband shader compiles.
| Module | Purpose | Key dep |
|---|---|---|
include/pelorus/interop.h + src/interop.c |
The Pelorus⇄vmafx side-data ABI (pack/parse, append-only, vendored by vmafx) | — |
include/pelorus/deband.h + src/deband_params.c |
Smart-deband parameter contract (shared by filter + autotune) | — |
shaders/pelorus_deband.comp |
Standalone reference deband shader (f3kdb) | glslang |
test/interop_test.c |
Shared ABI conformance fixture | — |
The stack is 22 patches against FFmpeg n9.0.2. It applies on top of the shared
FFmpeg fix series (VMAFx/ffmpeg-patches,
release pinned in build-config.env), which carries the generic FFmpeg fixes
every VMAFx project needs; scripts/fetch-ffmpeg-series.sh fetches and
verifies it (ADR-0185,
build guide).
| Filter | Purpose | Status |
|---|---|---|
pelorus_deband_vulkan |
Smart deband (f3kdb): flatten banding + TPDF/blue-noise dither, detail-protected, zero-copy | Working |
pelorus_dehalo_vulkan |
Anime/2D dehalo + dering: single-pass GPU port of DeHalo_alpha + FineDehalo, removes the ring next to line-art (luma by default; optional selected chroma); foundation of tune=anime |
Built (tuning pending) |
pelorus_denoise_vulkan |
Edge-preserving spatio-temporal denoise (the biggest BD-rate lever): NLM-lite joint bilateral + gated temporal averaging over a causal window, with optional motion-compensated warp | Working |
pelorus_analyze_vulkan |
Measured banding/variance/edge stats and per-cell maps → interop side data (GPU reduction + readback) | Working |
pelorus_grain_estimate_vulkan |
Film-grain param estimation (GPU per-band HF-residual) → PEL_SEC_FILMGRAIN + native AV1 side data | Estimator built |
pelorus_mc_vulkan |
Block-matching motion estimator → per-block motion + confidence side data for denoise warping and encoder hints | Working |
pelorus_fgs (BSF) |
Inserts a static, AVOption-supplied H.274 FGC SEI into HEVC so a decoder re-synthesizes grain; it does not read estimator frame side data inline | Working (static model) |
pelorus_scenecut |
Scene-cut → forced IDR: metadata-only consumer (NOT a Vulkan filter) that reads mc's PEL_SEC_MOTION.has_scene_cut and sets pict_type=I so the encoder opens a fresh GOP at the cut — vendor-neutral, no per-encoder patch; the ffmpeg CLI needs -force_key_frames source |
Built (BD-rate A/B pending) |
pelorus_aa_vulkan |
Anime warp anti-aliasing (awarpsharp2) + optional line-darkening (FastLineDarken): de-jaggies line-art via blurred-edge-map gradient warp, luma by default with optional selected chroma, no side data; 2nd stage of the anime tune chain |
Built (defaults unproven) |
pelorus_deblock_vulkan |
Re-encode deblock/dering: gated [1 2 1] low-pass across the prior codec's DCT block grid, smooths blocking so the new encoder skips it as false residual (luma by default; optional selected chroma); runs early, before deband |
Built (tuning pending) |
pelorus_borderfix_vulkan |
Dirty-line / border repair: clamp the dirty edge band onto the clean interior rect (the zero-copy GPU equivalent of fillborders=smear); all planes, per-plane-pixel widths; runs first, before any other stage |
Built (deterministic) |
Anime tune. For animation, compose the filters into one recommended GPU
pre-encode chain — analyze roi=1 (steer bits to the flats) + dehalo (rings) +
aa (jaggies) + deband (banding) at 10-bit, then -pelorus_roi 1 on the
encoder. A documented, retunable composition, not a new meta-filter. See
docs/usage/anime.md (ADR-0125).
- Step 1 — Core:
libpelorusinterop ABI + deband param contract + tests. - Step 2 — Flagship:
vf_pelorus_deband_vulkansmart deband (Vulkan), build-time SPIR-V from its canonical.comp.glsl, side-data emission, patch stack against n9.0.2. - Step 3 —
vf_pelorus_analyze_vulkan: measured banding/variance/edge stats (GPU reduction + readback) → interop side data. - Step 4 — Temporal denoise (
vf_pelorus_denoise_vulkan). - Step 5 — FGS param estimation (
vf_pelorus_grain_estimate_vulkan): GPU per-band HF-residual estimate → PEL_SEC_FILMGRAIN + native AV1 side data. - Step 6 — Motion-vector hints:
vf_pelorus_mc_vulkanproducer (0007 →PEL_SEC_MOTION+PEL_SEC_MOTION_CONF) + the confidence-gated denoisemc=1warp consumer and the NVENC ME-hint consumer (-pelorus_me_hints, patch 0008). The latter seeds NVENC's external motion search (encode speed only; measured 4090 case showed no gain). ADR-0116 / ADR-0131 / ADR-0114 Tier 3. - Step 7 — FGS round-trip:
pelorus_fgsBSF (patch 0010) inserts the H.274 FGC SEI into HEVC from static AVOptions (manual estimator-to-BSF mapping; no inline frame-side-data bridge);av1_nvenccarries the estimate into NVENC's hardware AV1 film-grain (NV_ENC_FILM_GRAIN_PARAMS_AV1,-pelorus_film_grain, patch 0011); AV1 software encoders round-trip via native side data. Per-frame HEVC + H.264/VVC legs are follow-ups (ADR-0117 / ADR-0118). - Step 8 — Anime dehalo:
vf_pelorus_dehalo_vulkan(patch 0014), a single-pass zero-copy GPU port of HAvsFuncDeHalo_alpha+FineDehalo. Removes the bright/dark ring next to line-art (luma by default, with optional selected chroma; pure transform, no interop). Foundation of the plannedtune=animepipeline. Compile-verified and glslang-clean; on-content tuning + the SSIMULACRA2 / edge-region VMAF-NEG / CAMBI proof are follow-ups (ADR-0123 / ADR-0111). - Step 9 — Scene-cut → forced IDR:
vf_pelorus_scenecut(patch 0016), a metadata-only encoder-steering consumer (NOT a Vulkan filter — no shader, no GPU work). It reads thehas_scene_cutflagvf_pelorus_mc_vulkanalready emits inPEL_SEC_MOTIONand setspict_type=Ion cut frames, so the encoder opens a fresh GOP exactly at the cut. Vendor-neutral (pict_type==Ihonoured by x264/x265/NVENC/QSV/SVT-AV1 with no per-encoder patch); links libpelorus, not gated on Vulkan; runs on Vulkan frames, nohwdownloadneeded (ADR-0186). Producer + consumer + mechanism are build-verified; the end-to-end BD-rate A/B (cut-aligned GOPs vs periodic IDR on multi-shot content) is a documented follow-up — no number claimed (ADR-0126 / ADR-0111). - Encoder steering (ADR-0114, opt-in
-pelorus_roi 1): theanalyze roi=1banding map drives dense per-block delta-QP on NVENC (qpDeltaMap, proven −41% banding), QSV (frame-ownedmfxExtMBQPfor progressive HEVC+CQP on runtime API 1.28 or newer; sanitizer/compile-complete, on-Intel-HW proof pending; ADR-0146), and the native Vulkan-Video encoders viaVK_KHR_video_encode_quantization_map(Tier 2; proven on an RTX 4090 for positive offsets, since that driver accepts no negative delta, and on RADV with Mesa 26.2 for both signs through a host-mappedR32_SINTmap; ADR-0166, ADR-0182). The same map also steers SVT-AV1 (libsvtav1) via its per-superblock ROI segment map (SvtAv1RoiMapEvt, ADR-0121, proven on hardware: CAMBI −1.5% at CRF 35, an honest modest gain on a mild synthetic source). Patches 0004 / 0005 / 0009 / 0012 (libaom) / 0013 (SVT-AV1). - Closed-loop QP feedback (ADR-0114 step 6 / ADR-0119): new append-only interop
section
PEL_SEC_QPREPORT(ABI 1.1) carries the encoder's honored per-block QP/bit decisions back into the side-data blob, plus a vendor-neutral reader stub (pel_qp_report_from_blocks). The ABI + pack/parse + conformance test + stub are working; the libavcodec QSV per-block stat-extraction is the HW follow-up. - Runnable QP-feedback reader (ADR-0122): a SDK-free x265
--csvstat reader (pel_x265_csv_parse+pel_qp_report_from_x265_frames) populatesPEL_SEC_QPREPORTwith the encoder's actual per-frame QP/bits/PSNR and a requested-vs-honoredhonored_fraction— the loop is now demonstrable end-to-end on the HEVC software encoder (thetools/pelorus_qp_reportdemonstrator; measured, not synthetic). The QSV per-block path stays HW-blocked. - Interop ABI 1.4 (ADR-0174 / ADR-0175):
PEL_SEC_ENC_TELEMETRYcarries one normalised encoder-telemetry record per coded frame (native and H.264-equivalent QP, bits, picture type, PSNR/SSIM, optional maps, a presence bit per field) filled through the FFmpeg-freepelorus/telemetry.h;PEL_SEC_ENCODE_RECORDcarries thesha256:digest of the canonical encode record that VMAFx binds to a score (pelorus/encode_record.h); and the motion section names its block edge (block_size_log2). Layout, validation, canonical JSON and digests are tested; the per-encoder adapters and the option-string parsers are follow-ups (#86, #81). - Interop ABI 1.5 (ADR-0183): a zero-free carrier form of the blob (COBS under
its own UUID) that
h264_nvencandhevc_nvencwrite, because NVENC truncates SEI payloads with many zero bytes (#284; rule pinned on an RTX 4090, driver 615.78.08); readers of decoded frames callpel_blob_unwrap(). Across 48 analyze cases on an RTX 4090 no picture loses its side data on either NVENC encoder (hevc_nvencstill strips maps over its budget, ADR-0181). - Step 10 — Anime
tunechain:vf_pelorus_aa_vulkan(patch 0015) — warp anti-aliasing (awarpsharp2) plus optional line-darkening (FastLineDarken). De-jaggies line-art by warping along the gradient of a blurred edge map, luma by default with optional selected chroma, and side-data-free; the 2nd stage of the anime chain after dehalo. - Step 11 — Re-encode deblock:
vf_pelorus_deblock_vulkan(patch 0017), a single-pass zero-copy GPU deblock/dering. At the prior codec's DCT block grid it applies a gated[1 2 1]low-pass, smoothing blocking so the new encoder does not spend bits coding it as false residual (luma by default; optional selected chroma). Runs early in the chain, before deband. - Step 12 — Border repair:
vf_pelorus_borderfix_vulkan(patch 0018), a dirty-line / border repair pass that clamps the dirty edge band onto the clean interior rect — the zero-copy GPU equivalent offillborders=smear. All planes, per-plane-pixel widths; runs first, before any other stage.
meson setup build && ninja -C build # build (libpelorus + tests + shaders)
meson test -C build --suite=fast # pre-push gate
ninja -C build install # install libpelorus (for the patches)
FFMPEG_REPO=/absolute/path/to/ffmpeg ffmpeg-patches/generate.sh
clang-format --dry-run -Werror libpelorus/**/*.{c,h} # format checkPre-1.0 (v0.4.0-rc.2). Public API and the interop ABI may evolve before
v1.0.0; the ABI is append-only from here.
Pelorus is licensed under the EUPL-1.2, a reciprocal licence, like its sibling vmafx. The files the patch stack adds to FFmpeg stay under FFmpeg's LGPL-2.1-or-later, and vendored files keep their own terms (ADR-0171):
| Code | Licence | Text |
|---|---|---|
libpelorus, tools, scripts, docs, build and CI |
EUPL-1.2 | LICENSE |
Filters, bitstream filter, headers and shaders added to FFmpeg (ffmpeg-patches/files/), and the patches |
LGPL-2.1-or-later | LICENSES/LGPL-2.1-or-later.txt |
| Vendored superpowers skills and Praetor's interfig figure engine | MIT | LICENSES/MIT.txt |
REUSE.toml records the licence of every file, and reuse lint
checks it. Releases up to v0.2.2 stay available under BSD-2-Clause-Patent.
What the EUPL-1.2 asks of a product that ships libpelorus, and how the
licences meet in an FFmpeg build, is in docs/licensing.md.
If Pelorus is useful to you: GitHub Sponsors · Ko-fi.
Pelorus adopts the Praetor/HISS policy
(ADR-0145) at engine commit
9615f1b9 with a 51-finding legacy baseline (28 HISS-01, 21 HISS-04, two
HISS-07).
Canonical agent guidance lives in AGENTS.md; generated vendor contexts are
checked for drift, and a locked Markdown gate lints the public documentation.
The hosted Standards and Documentation Governance workflows run both on every
pull request; Standards also fails a baseline that grows against the target
branch unless the increase carries a recorded reason. The audit also requires
every hosted Meson lane to build with --werror and every tracked C unit to
sit in a clang-tidy lane or a dated exception
(ADR-0168). The manifest
declines no adoption step: it commits Lefthook hooks (opt-in with
make hooks-install), a dev container, and a master ruleset that the
maintainer applies with praetorctl sync --remote
(ADR-0153). The pinned
install provides a binary named standardsctl; Praetor's generated text,
including the block below, calls the same program praetorctl. Claude Code,
Codex, and Gemini CLI sessions run Praetor's pre-tool command hook, so agent
users also need a copy named praetorctl on PATH
(CONTRIBUTING.md).
| Gate | Command | Description |
|---|---|---|
| Native verification | make verify-native |
Builds, tests, formats, lints, and checks changelog rendering |
| Full verification | make verify-all |
Adds context, HISS, and documentation checks |
| HISS audit | make audit |
Applies the pinned 51-finding baseline ratchet |
| Documentation | make docs-lint docs-figures |
Runs the locked Markdown and figure checks (Node.js 22.12 or newer) |
| Context sync | make compile-context |
Compiles canonical agent guidance to vendor targets |
| Licensing | reuse lint |
Checks every file's copyright and licence against REUSE.toml (reuse 6.x) |
Praetor manages this repository's declared governance policy. This managed block records adoption state; it is not a verification certificate.
Verification: make verify-all runs the repository's configured
verification cascade.
HISS Audit: praetorctl audit enforces policy, generated-surface
integrity, and the debt ratchet.
Context Sync: praetorctl compile-context --verify verifies every
generated agent context against AGENTS.md.
Documentation: make docs-lint enforces locked Markdown style and the
private scratch-link policy.
Debt Baseline: .standards-baseline.json anchors the debt ratchet at
51 recorded infractions; audit forbids growth.