Skip to content

Releases: oops-rs/svga

svga v0.1.4

Choose a tag to compare

@WendellXY WendellXY released this 12 Oct 04:33

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

Choose a tag to compare

@WendellXY WendellXY released this 19 Sep 08:31

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 the png crate: 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. fdeflate prefetches 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. Document keeps 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

Choose a tag to compare

@WendellXY WendellXY released this 19 Sep 07:26

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

Choose a tag to compare

@WendellXY WendellXY released this 19 Sep 06:13

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_at and Document::remove_image_at address an entry by its position in images(). They reach what the &str-keyed edits cannot name: repeated keys, and keys that are not valid UTF-8. Out of range is InvalidEdit("svga_image_index_out_of_range").
  • Animation::into_movie hands over the typed movie and releases the file's images and buffers. Fetch the images you need through Animation::image first.
  • Animation::image_kind(key) sniffs the bytes Animation::image resolves, 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::Jpeg for 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_bytes walks 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.
  • ValueKind is an ordered heuristic: short text starting with ID3 reads as Mp3, and an empty value is Unknown.

svga v0.1.0

Choose a tag to compare

@WendellXY WendellXY released this 19 Sep 04:53

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_image and remove_image return 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.spec JSON). 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 an ErrorKind plus a stable svga_* code.
  • Pure Rust by default, so it builds for wasm32 and on docs.rs. The optional libdeflate feature makes Compression::Best use libdeflate for the smallest output; the default zip feature 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.