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
# apply the FFmpeg patch stack onto a pristine n8.1.1 checkout
cd ffmpeg-patches && ./generate.sh && ./test/build-and-run.sh
# zero-copy: decode -> smart deband (VRAM) -> hardware encode
ffmpeg -init_hw_device vulkan -hwaccel vulkan -hwaccel_output_format vulkan \
-i input.mkv \
-vf "pelorus_deband_vulkan=range=15:dither=bluenoise:dynamic=1" \
-c:v hevc_nvenc -cq 28 out.mkv # or: av1_nvenc / hevc_qsv / hevc_vaapi| # | 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 | — |
| 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-only); foundation of tune=anime |
Built (tuning pending) |
pelorus_denoise_vulkan |
Edge-preserving spatio-temporal denoise (the biggest BD-rate lever) | Roadmap |
pelorus_analyze_vulkan |
Measured banding/variance/edge stats → 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 MV-hint side data for the encoder (speed, not quality) | Working |
pelorus_fgs (BSF) |
Inserts the H.274 FGC SEI into HEVC so a decoder re-synthesizes grain — the HEVC leg of the FGS round-trip (AV1 round-trips via native side data) | 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 |
Built (BD-rate A/B pending) |
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), inline GLSL, side-data emission, patch stack against n8.1.1. - 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) + the NVENC ME-hint consumer (-pelorus_me_hints, patch 0008): the MV field seeds NVENC's external motion search (encode SPEED; on-HW A/B pending). ADR-0116 / 0114 Tier 3. - Step 7 — FGS round-trip:
pelorus_fgsBSF (patch 0010) inserts the H.274 FGC SEI into HEVC;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. Static AVOption-driven HEVC model; per-frame + H.264/VVC legs are follow-ups (ADR-0117 / ADR-0118). - Step 8 — Anime dehalo:
vf_pelorus_dehalo_vulkan(patch 0012), a single-pass zero-copy GPU port of HAvsFuncDeHalo_alpha+FineDehalo. Removes the bright/dark ring next to line-art (luma-only; 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; run afterhwdownload. 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 (mfxExtMBQP, code-complete; on-Intel-HW proof pending), and the native Vulkan-Video encoders viaVK_KHR_video_encode_quantization_map(Tier 2, compile-verified; on-HW proof blocked by the dev box's driver feedback-flag gap). 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. - 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.
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)
cd ffmpeg-patches && ./generate.sh # regenerate the FFmpeg patch stack
clang-format --dry-run -Werror libpelorus/**/*.{c,h} # format checkPre-alpha (v0.1.0). Public API and the interop ABI may evolve before
v1.0.0; the ABI is append-only from here.
BSD-2-Clause-Patent for libpelorus (so the interop TU is
co-vendorable with vmafx); FFmpeg's LGPL-2.1 for filters that become part of
FFmpeg when applied.
If Pelorus is useful to you: GitHub Sponsors
· Ko-fi.
| pelorus_aa_vulkan | Anime warp anti-aliasing (awarpsharp2) + optional line-darkening (FastLineDarken): de-jaggies line-art via blurred-edge-map gradient warp, luma-only, no side data; 2nd stage of the anime tune chain | Built (defaults unproven) |
- Step 8 — Anime
tunechain:vf_pelorus_aa_vulkan(patch 0012) — warp |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-only); runs early, before deband | Built (tuning pending) | - Step 9 — Re-encode deblock:
vf_pelorus_deblock_vulkan(patch 0016), a single-pass zero-copy GPU deblock/dering. At the prior codec's DCT block |pelorus_borderfix_vulkan| Dirty-line / border repair: clamp 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 | Built (deterministic) | - Step 9 — Border repair:
vf_pelorus_borderfix_vulkan(patch 0016), a