Skip to main content

Decode in Rust

The fourdgs crate is the decoder the native tier is delivered through: the C++ and Swift packages bind to its C ABI rather than reimplementing the format. See the feature matrix for what the conformance suite proves today, and the crate's README for the full surface.

The crate is named fourdgs because Cargo identifiers cannot start with a digit. The format, the CLI and the file extension are always 4dgs.

Two read paths

Both are first-class; neither is an optimization of the other.

// Streamed: front to back. Works on a pipe, on a file with no index, and on a file
// cut short mid-write — everything complete before the cut is recovered.
let scene = fourdgs::read_path("scene.4dgs")?;
let state = scene.gaussians.state_at(1.5, scene.header.cutoff);

// Indexed: the Footer, then the index, then only the byte ranges the instant needs.
let source = fourdgs::FileReadable::open("scene.4dgs")?;
let mut reader = fourdgs::SceneReader::open(source)?;
let state = reader.state_at(1.5, 3)?;

SceneReader uses the index when the file has one and falls back to a front-to-back read when it does not, which is what the specification requires of a reader that meets summary_start == 0.

Transports

The core depends on Readable — something that can report its size and read a byte range — and on nothing else. No HTTP client, no filesystem in the decode path, no platform types.

pub trait Readable {
fn size(&mut self) -> fourdgs::Result<u64>;
fn read(&mut self, offset: u64, length: u64) -> fourdgs::Result<Vec<u8>>;
}

FileReadable and BytesReadable ship with the crate. An HTTP range reader is a dozen lines and belongs in the consumer.

Bounded memory

No path in the crate reads a whole file. The streamed reader takes an io::Read and reads one record at a time, growing a buffer only as bytes actually arrive, so a crafted length cannot make it allocate what the resource does not contain. The indexed reader steps over front-matter records by their headers, so a scene with an embedded audio track costs nothing to open, and fetches each chunk and each spherical-harmonic band by its own byte range.

Writing

let options = fourdgs::WriteOptions {
profile: fourdgs::quantization::Profile::Default,
..Default::default()
};
let bytes = fourdgs::write_to_vec(&gaussians, 8.0, &options, &fourdgs::SceneExtras::default())?;

Two properties are contracts rather than niceties: chunks are independent, which is what makes seeking work at all, and output is deterministic — the same scene and options produce byte-identical files. WriteOptions::verify is on by default and refuses to return a file whose measured deviation exceeds the bounds that file is about to declare.

Codecs

deflate is the format's default and is always available. zstd is behind an off-by-default feature; without it, a zstd file is refused by name rather than misread.

The C ABI

rust/fourdgs/include/fourdgs.h is hand-written, committed, and kept in step with src/capi.rs. Four rules hold across the whole surface: nothing unwinds across the boundary, every fallible call returns a status with results in out parameters, null is always safe to pass, and borrowed pointers have a lifetime documented per function.

Strings read out of file bytes cross as a (pointer, length) pair rather than as C strings, because the format's string is length-prefixed and may legally contain a NUL. The _ex openers take a fourdgs_open_mode so a caller can force the sequential or indexed path rather than accept auto-selection.

Scope

Decoding ends at reconstructed gaussian state at time t. Nothing in the crate describes how that state is drawn, ordered, culled or budgeted — the format is renderer-agnostic and so is the SDK.