Crate is the binary form of a USD layer, used by .usdc files and by
.usd files that start with its signature. It is defined in the binary
section of the OpenUSD Core Specification and implemented in OpenUSD’s
crateFile.cpp; this page follows both. A Crate file holds the same specs
and fields as a text layer, with every token, path and field stored once and shared.
All integers are little-endian and floating-point values are IEEE 754.
Header
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 8 | char[8] | identifier | PXR-USDC (50 58 52 2D 55 53 44 43) |
| 8 | 1 | u8 | major | version, major part: 0 so far |
| 9 | 1 | u8 | minor | version, minor part |
| 10 | 1 | u8 | patch | version, patch part |
| 11 | 5 | bytes | unused | zero is recommended |
| 16 | 8 | u64 | TOC offset | the offset of the table of contents from the start of the file |
| 24 | 8 | bytes | reserved | zero is recommended |
OpenUSD’s writer reserves a further 56 bytes after these, writing an 88-byte header in all, so its first section starts at offset 88 or later. It writes the sections first, then the table of contents, then rewrites the header with the table’s offset.
00000000 50 58 52 2D 55 53 44 43 00 08 00 00 00 00 00 00
00000010 C0 1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Versions
A writer should use the lowest version that can hold its data. The specification requires readers to handle up to 0.13.0 and lets writers emit 0.8.0 to 0.13.0; OpenUSD itself writes 0.8.0 unless newer features are used, and reads older files.
| Version | Change |
|---|---|
| 0.0.1 | the first version, with a padded spec record and an older path layout |
| 0.1.0 | smaller spec records without padding |
| 0.2.0 | list operations with prepended and appended items |
| 0.3.0 | unused; OpenUSD notes it was broken |
| 0.4.0 | compressed structural sections: tokens, fields, field sets, paths and specs |
| 0.5.0 | compressed integer arrays; the rank field before array data is dropped |
| 0.6.0 | compressed floating-point arrays |
| 0.7.0 | 64-bit array element counts, previously 32-bit |
| 0.8.0 | payload list operations and layer offsets on payloads |
| 0.9.0 | time code values |
| 0.10.0 | path expression values |
| 0.11.0 | relocates in layer metadata |
| 0.12.0 | splines |
| 0.13.0 | spline tangent algorithms (none, custom, auto ease) |
| 0.14.0 | array edits (OpenUSD; newer than the specification) |
| 0.15.0 | spline loop boundary time and time-code splines (OpenUSD; newer than the specification) |
Table of contents
The table is at the end of the file: a u64 count, then one 32-byte entry per section.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 16 | char[16] | name | the section’s name in ASCII, ended by a zero byte, at most 15 characters |
| 16 | 8 | u64 | start | its offset from the start of the file |
| 24 | 8 | u64 | size | its length in bytes |
The sections are TOKENS, STRINGS, FIELDS,
FIELDSETS, PATHS and SPECS. Each is optional
unless another needs it; readers find them by name and skip names they do not know.
OpenUSD writes them in that order, after any unknown sections it carried over. Values
that do not fit inline sit between the header and the sections.
Sections
The forms below are those of version 0.4.0 and later. Earlier files store the same arrays uncompressed: a u64 count and the raw items.
| Section | Contents |
|---|---|
TOKENS | u64 token count, u64 uncompressed size, u64 compressed size, then the LZ4-compressed tokens: UTF-8 strings each ended by a zero byte. The specification says token 0 must be an unused placeholder. |
STRINGS | u64 count, then u32 token indices: string values are stored as tokens and referred to through this table |
FIELDS | u64 field count; the fields’ token indices (their names) as compressed integers; a u64 size and the LZ4-compressed array of 64-bit value representations |
FIELDSETS | u64 count, then compressed field indices: each field set is a run of indices into the fields, ended by 0xFFFFFFFF |
PATHS | u64 path count, then three compressed arrays of equal length, below |
SPECS | u64 spec count, then three compressed arrays: each spec’s path index, field set index (the start of its run) and spec type |
Before 0.4.0 a field is 16 bytes (four bytes of padding, the u32 token index, the u64 value representation) and a spec 12 bytes (u32 path index, field set index and type); in 0.0.1 a spec has four bytes of padding in front. All indices are u32.
Spec types
| Value | Spec | Value | Spec |
|---|---|---|---|
| 0 | unknown | 6 | prim |
| 1 | attribute | 7 | pseudo-root: the layer itself |
| 2 | connection | 8 | relationship |
| 3 | expression | 9 | relationship target |
| 4 | mapper | 10 | variant |
| 5 | mapper argument | 11 | variant set |
The specification makes 0, 1, 6, 7, 8, 10 and 11 normative; readers may ignore the others.
Paths
Paths are stored as a tree walked depth first. The three arrays give, for each step:
| Array | Meaning |
|---|---|
| path indices | which entry of the path table this node fills |
| element token indices | signed: the token naming this element; positive for a prim step (/name), negative for a property step (.name); never 0 |
| jumps | signed: more than 0, the node has a child (next in the arrays) and a sibling this many entries ahead; 0, a sibling next and no child; −1, a child next and no sibling; −2, a leaf |
The first entry is the absolute root path /. Because the nodes are not in
path-table order, a reader sizes the table from the count first and fills it in as it
walks. Files before 0.4.0 store the tree as a list of node headers instead.
LZ4 compression
Compressed blocks use OpenUSD’s TfFastCompression wrapper around LZ4’s
block format (no LZ4 frame header or checksum). The first byte is the number of
chunks. Zero means the rest is one LZ4 block. Otherwise each chunk is an i32
compressed size followed by its LZ4 block; each chunk decompresses to at most
LZ4_MAX_INPUT_SIZE (2,113,929,216) bytes, and only the last may be
shorter. The specification’s text and table disagree on the width of the chunk count;
OpenUSD writes one byte.
Compressed integers
Arrays of integers (indices here, and integer arrays in values) are stored as a u64 compressed size, then an LZ4 block that decompresses to:
- The common value: the most frequent difference between neighbours, in the element width (4 bytes for 32-bit arrays, 8 for 64-bit).
- The codes: two bits per element, four to a byte, the last byte padded.
- The other differences, each in the width its code gives.
| Code | 32-bit arrays | 64-bit arrays |
|---|---|---|
00 | the common value; nothing stored | the common value; nothing stored |
01 | int8 | int16 |
10 | int16 | int32 |
11 | int32 | int64 |
Each element is its difference from the one before, the first from 0, so
123, 124, 125, 100125 is stored as 123, 1, 1, 100000. The
element count is not in the block: it comes from the count stored before it.