Repository navigation
Releases: oops-rs/svga
Release list
svga v0.1.4
This patch reduces temporary encoding allocations and accelerates common typed-decoding records. Public APIs, encoded bytes, format coverage, validation rules, default memory policy, and dependencies are unchanged.
- Write canonical tags, lengths, and payloads into one final field buffer instead of allocating and concatenating temporary buffers.
- Decode exact fixed32 layout/transform records directly, preserving float bits and previous components. Reordered, repeated, unknown, and malformed records retain the general decoder path.
- Add boundary tests for varint widths, negative integers, special float bit patterns, repeated fields, and malformed records.
- Exclude local investigation files from the published crate.
Measured process CPU changes versus 0.1.3 on a fixed 175-file production SVGA corpus on macOS ARM64 with Rust 1.97.1 during the optimization study (paired baseline/candidate sweeps):
| Operation | CPU time change |
|---|---|
| Typed decoding from retained documents | −20.2% |
| Full parse, basic info, and destruction | −2.9% |
| Typed encoding with embedded images | −28.1% |
| Typed encoding plus fast zlib compression | −18.6% |
The frame-heavy encoding case made 59.7% fewer allocation/reallocation calls. Parsing requested-heap measurements were unchanged on the two profiled assets. These are local workload measurements, not playback FPS, device-energy results, or an app-equivalent Rust/Zig comparison.
Validation includes all-feature and no-default-feature tests and Clippy, Rust 1.89 MSRV checks/tests, WASM builds, iOS compilation, and byte-identical round-trip checks over all 175 production files. The crate remains pure Rust by default; no additional SIMD dependency or experimental allocator/inflater policy is included.
Full changelog: v0.1.3...v0.1.4
svga v0.1.3
A second performance release. No API or behaviour changes.
Whole-file parsing (inflate + Document + Movie) drops from 549 µs to 421 µs per file, averaged over 233 real-world files on an Apple M2 Max. The pure-Rust build is now ahead of libsvga 0.2.7 linked against Apple's libz.
| per file | total | inflate + document | typed decode |
|---|---|---|---|
svga 0.1.2 |
549 µs | 438 µs | 98 µs |
svga 0.1.3 |
421 µs | 348 µs | 62 µs |
| libsvga + Apple libz, parse only | 449 µs | 329 µs | 101 µs |
| libsvga, portable inflate | 1225 µs | 1095 µs | 104 µs |
Measured with libsvga's own svga_phase_bench and a matching Rust harness, best of 12 interleaved rounds. Allocated bytes per parse roughly halve (1.68 MB → 0.79 MB).
What changed
- Inflate uses
fdeflate, the safe-Rust inflater behind thepngcrate: about 20% faster than miniz_oxide on SVGA payloads, which are mostly PNG data, and within 5% of libz and libdeflate. - Trailing bytes are still detected exactly.
fdeflateprefetches input and counts it as consumed, so its byte count alone cannot see a few stray bytes after the stream. The last 16 input bytes are fed one at a time instead: a decoder cannot finish on a byte it has not been given. Tests cover 1 to 20 trailing bytes, a doubled stream and a corrupted checksum, and the opt-in corpus test pads and truncates every real file. - No payload copy.
Documentkeeps the inflated buffer as its shared storage. - Leaner typed decoding. Messages are walked with an iterator that decodes each value once; frames are decoded in place; the frame list is sized from the header's frame count, bounded by the sprite's own size and by
Limits::max_elements, so a hostile header cannot make it allocate.
Dependencies
fdeflate is new. miniz_oxide is now used directly, for compression only. flate2 remains only to select the zip crate's pure-Rust backend, and is no longer pulled in without the zip feature.
svga v0.1.2
A performance release. No API or behaviour changes.
Faster typed decoding
Decoding the typed Movie is about a third faster: 148 µs → 98 µs per file, averaged over 233 real-world files on an Apple M2 Max. Whole-file parsing (inflate + Document + Movie) goes from 599 µs to 550 µs.
- Varints take a single-byte fast path, which covers tags and most lengths, and field iteration is inlined.
- The per-frame decoders fill one local value in place instead of rebuilding it for every field.
How it compares with libsvga
Measured against libsvga 0.2.7 with its own svga_phase_bench, same files, best of 10 interleaved rounds:
| per file | inflate | parse |
|---|---|---|
svga 0.1.2 (pure Rust, miniz_oxide) |
429 µs | 106 µs (Document + Movie) |
| libsvga, Apple libz | 330 µs | 97 µs (protobuf), +55 µs for its minimal owned model |
| libsvga, portable Zig inflate | 1079 µs | 99 µs |
Inflate dominates on every side. On large files svga parses faster because image bytes are borrowed from the payload rather than copied (164 µs vs 318 µs on a 2.6 MB file), and peak memory per parse is lower (0.76 MB vs 0.96–2.2 MB).
Also
- The crate now declares
#![forbid(unsafe_code)]. It never contained any.
svga v0.1.1
Additive release: nothing existing changes shape. These came out of moving the first consumer's optimizer and renderer onto the crate.
Added
Document::replace_image_atandDocument::remove_image_ataddress an entry by its position inimages(). They reach what the&str-keyed edits cannot name: repeated keys, and keys that are not valid UTF-8. Out of range isInvalidEdit("svga_image_index_out_of_range").Animation::into_moviehands over the typed movie and releases the file's images and buffers. Fetch the images you need throughAnimation::imagefirst.Animation::image_kind(key)sniffs the bytesAnimation::imageresolves, so it also works for SVGA 1.x and zipped files.svga::png_dimensions(bytes)reads width and height from a PNG header. No pixels are decoded.ValueKind::Jpegfor values starting with a JPEG start-of-image marker.
Behaviour note
A JPEG image value used to sniff as ValueKind::Unknown and now sniffs as ValueKind::Jpeg. Code that treats "not a PNG" as Unknown should test != ValueKind::Png instead. The enum is #[non_exhaustive], so this compiles unchanged.
Documentation
Document::from_byteswalks the whole payload up front, so a malformed file is reported as malformed even if it also contains things a caller would refuse on policy grounds.ValueKindis an ordered heuristic: short text starting withID3reads asMp3, and an empty value isUnknown.
svga v0.1.0
First release of svga, a pure-Rust codec for the SVGA animation format: decode, inspect, edit without loss, encode. It renders nothing and makes no optimization decisions.
[dependencies]
svga = "0.1"What is in it
Document, the lossless layer. Every top-level protobuf field is kept as its original bytes, so unknown fields, field order and float bit patterns survive, and an unedited document re-encodes to exactly the payload it was read from.replace_image,insert_imageandremove_imagereturn a new document and leave every other byte alone.Movie, a typed read-only view decoded on demand: params, sprites, frames, layouts, transforms, shapes, audio. It reports referenced, unused and missing image keys, and the largest drawn size of each image across visible frames.Animation, one entry point for every flavour: SVGA 2.x (zlib + protobuf), zipped 2.x (movie.binary) and SVGA 1.x (movie.specJSON).to_document()turns the zip flavours into a standalone 2.x document.- Built for untrusted input. Configurable
Limits(input size, inflated size, top-level fields, zip entries, 1.x spec size, decoded elements), trailing bytes after the zlib stream are an error, checked arithmetic throughout, and errors carry anErrorKindplus a stablesvga_*code. - Pure Rust by default, so it builds for
wasm32and on docs.rs. The optionallibdeflatefeature makesCompression::Bestuse libdeflate for the smallest output; the defaultzipfeature can be turned off.
Verification
Checked against 233 real-world files (not part of the repository): all 231 zlib files re-encode to a byte-identical inflated payload, and every file's version, view box, fps, frame, image, sprite and audio counts match libsvga's svga_probe. The opt-in corpus test in tests/corpus.rs repeats this on any directory of files.
MSRV 1.89. MIT licensed.