Real-world PyroWave streaming maxed ~2.5 Gbps with sagging fps while raw transport does 4.8. Root-caused to serial per-frame paths at BOTH ends (the transport was never the limit); this fixes the two dominant ones. Host (vendored shim, patch 0004): pyrowave_encoder_encode_gpu_synchronous allocated four Vulkan buffers (meta + bitstream, Device + CachedHost) on EVERY frame. At 240 fps with MB-scale bitstreams that per-frame allocator churn stalled the encode itself. Pool them on the encoder and reuse across frames (recreate only on a size grow); the sizes are session-fixed, so it is pure reuse after frame 1. On an RTX 4090 the 5120x1440 submit+fence-wait drops ~15 ms -> ~1 ms, i.e. the host serial ceiling goes 64 -> 1025 fps (444+HDR 44 -> 614). Safe under the synchronous encode model; re-validated by pyrowave_win_smoke (Windows) and pyrowave_smoke/_444 (Linux). Applies to both host encoder paths (they share the shim). Client (Apple Metal decoder): WaveletBitstream.parse reserved the payload buffer per packet (reserveCapacity(count + words), an exact realloc each of ~3000 packets/frame => O(n²)) and copied word-by-word. Reserve once up front and memcpy each packet's coefficients in one shot (all Apple platforms are little-endian, so the wire's LE u32s land verbatim; memcpy is alignment-free). 5.44 ms -> 0.055 ms per 1.44 MB frame (25x); byte-identical (parser unit tests + golden-frame PSNR unchanged). Also: - native.rs: PUNKTFUNK_PYROWAVE_MAX_MBPS caps PyroWave's open-loop Automatic bitrate pin for hosts on a constrained link (unset => no cap; an explicit client rate bypasses it). The pin is all-intra + ABR-off, so at a high pixel rate it can outrun the fabric (4:4:4+HDR 5120x1440@240 pins ~5.3 Gbps, over a 5 GbE link) and the overshoot just becomes loss. - pf-encode caps(): report the real opened chroma instead of a hardcoded 4:2:0 default, so a genuine 4:4:4 session no longer trips the spurious "encoder chroma disagrees with the negotiated Welcome" warn. Also fix a latent Windows reset() that rebuilt at 4:2:0 for a 4:4:4 session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PyroWave
PyroWave is an intra-only video codec (practially speaking a still-image codec) that is optimized for extremely fast GPU encode (< ~0.1 ms encode and decode at 1080p, < ~0.2 ms at 4K). It is fully implemented in Vulkan compute shaders.
The targeted bit-rates are quite high (~200+ mbit/s) and the intended use case is local network game streaming over ethernet with absolute minimum latency where bandwidth is less of a concern.
It is integrated as part of my pyrofling project.
It is similar in scope to my master thesis from 2014, except the use case now is game streaming instead of adaptation of raw video to ethernet links.
Overview
Colorspace
Currently implemented YCbCr 4:2:0 and 4:4:4.
Wavelets
The images are transformed with the Discrete Wavelet Transform using CDF 9/7 filter. This is basically the exact same as JPEG2000.
Exact rate control
The encoder can target exact maximum bitrate for an encoded image.
Trivial "entropy" coding
The encoding of coefficients is trivial compared to normal image codecs, and is responsible for a large increase in bit-rate, especially at higher compression ratios. This simplicity massively improves encode/decode performance, since it's extremely parallelizable. It also makes it possible to do a single-pass exact rate control for an entire image.
It should be possible in theory to add a more proper entropy coder to the encoded bit-planes to achieve somewhat competent compression, but that has not been explored and is out of scope. High Throughput JPEG2000 is likely a good place to look for that anyway.
Robustness against packet loss
Being intra-only and encoding 64x64 blocks of coefficients in isolation ensures great recovery against packet loss. PyroWave has been battled tested over long distance streaming over fiber links.
Bitstream definition
Building
PyroWave is intended to be built alongside PyroFling with Granite in the normal case.
Standalone C API
NOTE: This API is still under development and the API/ABI is not yet quite stable.
A small portion of Granite needs to be checked out.
bash checkout_granite.sh
Build normally with CMake and a C API is installed.
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=output -DCMAKE_BUILD_TYPE=Release -G Ninja
$ ninja install
[0/1] Install the project...
-- Install configuration: "Release"
-- Installing: ...../build/output/include/pyrowave/pyrowave.h
-- Installing: ...../build/output/lib/libpyrowave-shared.so.0.0.0
-- Installing: ...../build/output/lib/libpyrowave-shared.so.0
-- Installing: ...../build/output/lib/libpyrowave-shared.so
-- Installing: ...../build/output/share/pyrowave-shared/cmake/pyrowave-sharedConfig.cmake
-- Installing: ...../build/output/share/pyrowave-shared/cmake/pyrowave-sharedConfig-release.cmake
-- Installing: ...../build/output/share/pkgconfig/pyrowave-shared.pc
The build is tested on Linux, MinGW, msys2 and MSVC.
See pyrowave-c-test which unit tests the shared C API.
That test also serves as a basic user guide for the API.
build-steamrt.sh builds against the Sniper SDK and is also supported.
Local development and CLI
For the sample and test applications in this repo however, check out
the full https://github.com/Themaister/Granite before invoking CMake.
Build with -DPYROWAVE_DEVEL=ON to get the "full" build.
git clone --depth 1 --recursive --shallow-submodules https://github.com/Themaister/Granite Granite
Basic encoder/decoder CLI
The basic CLI takes a y4m and dumps out a raw bitstream. This is just intended for local testing.
pyrowave-encode test.y4m out.wave 400000
This encodes a raw bitstream where each frame consumes maximum 400 KB. To decode back to y4m:
pyrowave-decode out.wave out.y4m