draco-gltf 0.1.0

Load and save full glTF scenes with Draco geometry, bridging gltf-rs and draco-core/draco-io
Documentation

draco-gltf

Load and save full glTF scenes with Draco-compressed geometry.

draco-gltf is a thin bridge between gltf (gltf-rs), which models the whole glTF scene — materials, textures, nodes, animations, skins, lights, and arbitrary extensions — and the Draco crates:

  • decode uses draco-core to decompress KHR_draco_mesh_compression geometry;
  • encode reuses draco-io's document-preserving compressor core, so the compression logic lives in exactly one place — while reading the geometry to compress through gltf-rs, so it depends on draco-io with only its writer, never its glTF reader.

It exists because neither side does the whole job alone: gltf-rs does not decode Draco (its validator even rejects a Draco asset, since KHR_draco_mesh_compression is a required extension it does not implement), while the Draco crates intentionally do not model the rest of a glTF scene.

[dependencies]
draco-gltf = "0.1"

Load a scene and decode geometry

let scene = draco_gltf::import("model.glb")?;

// The full scene is available through the gltf-rs API.
println!("{} materials, {} animations, {} skins",
    scene.document.materials().count(),
    scene.document.animations().count(),
    scene.document.skins().count());

// Decode the Draco-compressed geometry.
for (mesh, prim) in scene.draco_primitives() {
    let geometry = scene.decode_primitive(&prim)?; // draco_core::Mesh
    println!("mesh {:?}: {} faces, {} points",
        mesh.name(), geometry.num_faces(), geometry.num_points());
}
# Ok::<(), draco_gltf::Error>(())

Transparent reading

If you would rather not deal with Draco at all, decompress_in_place replaces every Draco primitive with plain geometry, after which the normal gltf-rs reader works on the whole document:

let mut scene = draco_gltf::import("model.glb")?;
scene.decompress_in_place()?; // Draco primitives become ordinary geometry

for mesh in scene.document.meshes() {
    for prim in mesh.primitives() {
        let reader = prim.reader(|b| scene.buffers.get(b.index()).map(|d| &d[..]));
        let positions: Vec<_> = reader.read_positions().unwrap().collect();
        // ... use positions, just like any uncompressed glTF
    }
}
# Ok::<(), draco_gltf::Error>(())

Compress a scene back to Draco

let scene = draco_gltf::import("model.gltf")?;
let bytes = draco_gltf::compress(&scene.document, &scene.buffers)?;
std::fs::write("model.draco.gltf", bytes)?;
# Ok::<(), draco_gltf::Error>(())

compress preserves everything the geometry change does not touch — materials, textures, images, nodes, animations, skins, extras, and unknown extensions. It runs draco-io's shared document-preserving compressor core, decoding the geometry to compress straight from the gltf-rs accessors through draco-io's reader-agnostic decode_geometry. So there is a single compression path, and draco-gltf depends on draco-io with only its gltf-writer feature — not its glTF reader (gltf-rs already parses the document).

Validation

gltf-rs's own validator rejects Draco assets outright (it treats KHR_draco_mesh_compression as an unsupported required extension). import therefore runs Draco-aware validation: full gltf-rs validation with only the expected Draco errors filtered out (the unsupported-extension error, and the "missing bufferView" on accessors whose data comes from the Draco stream). A structurally invalid asset — out-of-range indices, malformed accessors — is still rejected. The validation is also panic-safe: gltf-rs 1.4's validator can panic on a primitive that references an out-of-range accessor, so validate pre-checks those references and returns a controlled Error::Validation before the panic can happen (this holds even on wasm targets built with panic = "abort", where catch_unwind would not). Use draco_gltf::validate(&document) to check a document you built yourself.

WebAssembly

draco-gltf works on wasm32 for native-Rust web apps that want the full glTF scene model in the browser. Use the byte API — [import_slice], [compress], and [Import::decompress_in_place] — since the filesystem [import] is native-only. Fetch the glTF/GLB bytes (and any external resources) yourself and pass them in. For plugging a Draco decoder into a JavaScript glTF loader instead (three.js, babylon, …), use draco-core directly.

Features

  • image (default): decode embedded/external images into pixels via gltf-rs, exposing them as Import::images. This pulls the image crate (PNG/JPEG codecs), which is the largest part of the build. Disable it with default-features = false when you only need geometry and the scene model — e.g. on wasm, where the host usually decodes textures. Without it, images are not decoded, Import::images is absent, and a small built-in loader resolves buffers (data URIs and the GLB BIN chunk; external files only with a base path). Measured size-optimized wasm: ~384 KB → ~279 KB gzip without image.

glTF 2.1

These crates target glTF 2.0. glTF 2.1 (announced 2026, backward-compatible) is not implemented yet, but a 2.1 asset is handled safely — 2.0 content is supported, new scene-level content is preserved, and attributes using new 2.1 component types are kept verbatim rather than corrupted. See GLTF_2_1.md.

Where it sits

Crate Role depends on gltf-rs
draco-core raw .drc bitstream no
draco-io container plumbing + document-preserving compressor no
draco-gltf full-scene load/save bridging gltf-rs ↔ Draco yes (only here)

If you only need raw .drc or geometry-level glTF I/O, use draco-core / draco-io directly and skip the gltf-rs dependency.

License

Apache-2.0.