Ventuz File Format (VFF) is used to store textures and meshes on disk in a simple way, with all the extra features Ventuz needs.
Ventuz Version | VFF Version | Comment |
---|---|---|
5.00.00 | 1.0 | initial release |
6.02.00 | 2.0 | fixed compresssion size bug, public documentation |
V 1.0 saves the uncompressed size in the chunk header for MESH and TEXR, making it impossible to skip them without correctly parsing them. This was a mistake.
V 2.0 saves the compressed size in the chunk header for MESH and TEXR.
If compression is not used, both are identical.
This is a tagged file format for binary serialization of data that is used to feed the Ventuz 5 Engine.
Currently, this is about meshes and textures, but it should be easy to extend this for 3d-scenes and materials.
The goals of this file format are:
All data is little endian
Name | Bits | Interpretation |
---|---|---|
ubyte | 8 | unsigned integer |
ushort | 16 | unsigned integer |
int | 32 | signed integer |
uint | 32 | unsigned integer |
long | 64 | signed integer |
ulong | 64 | unsigned integer |
fourcc | 32 | four character code. 'HEAD' would be stored as 'H'|('E'<<8)|('A'<<16)|('D'<<24). |
A file consists of tagged chunks.
A chunk is composed of:
The descriptor is usually streamed directly into a structure of correct layout.
From the information in the header, one can deduce the presence and size of the (optional) data arrays.
There is no padding or alignment between tag, header, arrays and between chunks. Still the headers are designed so that they are properly aligned, this allows copying the data into a structure that matches he header layout.
The tag has the following format:
offset | type | description |
---|---|---|
+0000 | fourcc | type |
+0004 | ushort | minor version |
+0006 | ushort | major version |
+0008 | ulong | size of this chunk excluding this tag. that is, size of header plus size of the data arrays after compression. |
+0010 |
The initial version number is 1.0. If the file new fields are added to the header or new arrays are added, the major version number is increased and the minor version number is reset to 0. When only new meanings for existing fields in the header are added, like an additional value for an enum, the minor version number is increased. A loader should check all values fall within the expected range.
If a file continues beyond chunked data it needs to have 16 zero bytes as an end marker. Loaders must stop parsing when encountering a zeroes-only chunk. This allows to put data at the end of the file that is only loaded on demand, like for a movie player.
Each chunk gets it's own version information. For instance, when we bumped the file format version from 1.0 to 2.0, only the 'HEAD' chunk gets its version changed.
A loader is supposed to skip unknown chunks using the size information in the tag. To do this, the loader will first load the 16 bytes of the tag, then skip the number of bytes noted in the tag. This is why the chunk-size in the tag does not include the tag itself.
Chunks in capital letters are major chunks, like 'HEAD', 'MESH, 'TEXR'.
Chunks in lower case letters are sub-chunks inside a major chunk.
The size information of a major chunk does not include the size of its sub-chunks. Therefor a reader does not really care about this convention, it just continues ignoring unknown chunks until it finds another major chunks. Sub-chunks may only be interpreted when the major chunk is known. This mechanism makes sub-chunks a namespace of the major chunk and avoids name collisions.
Currently the following chunks are defined.
Tag | Meaning | Status |
---|---|---|
HEAD | head of file | public |
INFO | info and metadata | public |
MESH | mesh | public |
TEXR | texture | public |
TSET | texture set | Ventuz Internal |
TFNT | texture font | Ventuz Internal |
VVID | video | Ventuz Internal |
ARIG | animation rig | Ventuz Internal |
The header and descriptor of the file are never compressed. The data arrays may be compressed as specified in the descriptor.
Chunk : a skippable part of the file, containing tag, header and arrays
Tag : each chunk starts with a 16 byte tag containing type and version, and chunk size (excluding tag) for skipping
fourcc : four character code, used for chunk types
VFF : Ventuz File Format. It is OK to say "VFF file", because that would mean "Ventuz File Format File".
Each file starts with a 'HEAD' chunk that provides additional safety through a magic word and identifies the type of data in the file.
There are no arrays, and the header is
offset | type | description |
---|---|---|
+0000 | fourcc | first half of magic word 'VENT' |
+0004 | fourcc | second half of magic word 'UZ!\0' (last character is null) |
+0008 | fourcc | tag of the file type, either 'MESH' for meshes or 'TEXR' for textures. |
+000c |
Note that this 12 byte fixed portion of the 'HEAD' comes after the 16 bytes of the tag. The size provided by the chunk is only for the 'HEAD' header, not the full file.
To simplify handling of strings, strings can be stored as index into a string-table. The string-table is stored in a 'strt' sub-chunk right after the 'HEAD' chunk.
The first string, referenced by index 0, is always an empty string. If the 'strt' chunk is missing, a stringtable of only an empty string is used, making 0 the only valid string index.
'strt' header:
offset | type | description |
---|---|---|
+0000 | uint | number of strings, >=2 |
+0004 |
The data portion contains the strings as zero terminated UTF-8. It starts with a 0 for the empty string and ends with the zero of the last string.
The info chunk itself is empty. It is used as a container for metadata and thumbnail information.
'thmb' header:
offset | type | description |
---|---|---|
+0000 | uint | width |
+0004 | uint | height |
+0008 |
The thumbnail data is width*height pixels, each pixel. No compression is provided
offset | type | description |
---|---|---|
+0000 | byte | blue |
+0001 | byte | green |
+0002 | byte | red |
+0003 | byte | alpha |
+0004 |
Metadata is stored as key-value pairs. The key is always a string (string-table index) and the value may be a string or an integer.
'meta' header:
offset | type | description |
---|---|---|
+0000 | uint | number of key-value pairs with string-values |
+0004 | uint | number of key-value pairs with int-values |
+0008 |
This is followed by an array of string typed key-values pairs
offset | type | description |
---|---|---|
+0000 | string | string-table index to key |
+0004 | string | string-table index to value |
+0008 |
This is followed by an array of int typed key-values pairs
offset | type | description |
---|---|---|
+0000 | string | string-table index to key |
+0004 | int | value |
+0008 |
No compression is provided.
A mesh file consist of a 'HEAD' and a 'MESH' chunk.
The 'MESH' chunk always contains a vertex array, and may contain an index and a subset array.
In addition to vertices and indices, meshes can have subsets. Subsets mark a range of vertices and indices inside a mesh. All subsets share the same vertex and index array and the same vertex and index format.
Elements in the subset array are:
offset | type | description |
---|---|---|
+0000 | uint | first index |
+0004 | uint | number of indices |
+0008 | uint | first vertex |
+000c | uint | number of vertices |
+0010 |
When no index buffer is used, the first index and number of indices fields must be set to 0.
When indices are used, there are two modes: In normal modes, the first vertex / number of vertices specification is redundant, and all first index fields are relative to the start of the vertex array.
In SubsetStartAtIndexZero mode the first vertex field is used to offset into the vertex buffer, so the indices start at 0 for each subset. This allows very large vertex and index buffer while still using 16 bit indices.
The 'MESH' chunk contains the following elements in this order:
The mesh header:
offset | type | description |
---|---|---|
+0000 | uint | flags, see below |
+0004 | uint | vertex format, see below |
+0008 | uint | index format, see below |
+000c | uint | vertex compression, see below |
+0010 | uint | index compression, see below |
+0014 | uint | topology, see below |
+0018 | ulong | vertex count (required) |
+0020 | ulong | index count, enables indexed primitives |
+0028 | uint | frame count, enables animated meshes |
+002c | uint | subset count, enables subset array |
+0030 |
The vertex format is defined as a group of enums packed into a 32 bit bitmask. Each field defines the existence and type of a vertex attribute. The attributes are always in the order as they are defined here.
0x0000000f Position Mask 0x00000001 float_32[3] 0x00000002 float_32[2] (used for fonts) 0x00000030 Normal Mask 0x00000010 float_32[3] 0x00000020 float_16[4] (with w = 0) 0x00000f00 Vertex Color Mask 0x00000100 unorm_8[4] color 0x00000200 uint_8[4] not really color (used for font shader) 0x0000f000 Texture Coordinate Mask 0x00001000 float_32[2] (one uv set) 0x00002000 float_16[2] (one uv set) 0x00003000 float_32[4] (two uv sets) 0x00004000 float_16[4] (two uv sets) 0x00005000 uint8[4] (used for font shader) 0x00030000 Tangent Mask 0x00010000 float_32[4] (with w = bi-tangent sign) 0x00020000 float_16[4] (with w = bi-tangent sign) 0x00700000 Matrix Palette Skinning Mask 0x00100000 uint_32 (single matrix index, no blending) 0x00200000 uint_8[4] (matrix index) + unorm_8[4] (weight) 0x00300000 uint_16[4] (matrix index) + unorm_8[4] (weight) 0x00400000 uint_16[4] (matrix index) + float_32[4] (weight)
In this table, types are defined as basetype_bits[count], with the count optional.
Basetypes are:
Common vertex formats are
The vertex attributes are in the vertex buffer in the following order, if present at all:
If the index count is set to null, the index format must be '0, no indices'. If the index count is not null, must be either 2 or 4.
A texture file consist of a 'HEAD' and a 'TEXR' chunk.
Texture data is stored in native format.
All specified mipmaps must be stored in the file, there is no automatic mipmap generation.
There is no padding between rows or mipmaps.
The 'TEXR' chunk contains the following elements in this order:
offset | type | description |
---|---|---|
+0000 | ubyte | image type, see below |
+0001 | ubyte | pixel format, see below |
+0002 | ubyte | compression, see below |
+0003 | ubyte | gamma, see below |
+0004 | ubyte | mipmap count, must not be 0 |
+0005 | ubyte | unused, write 0 |
+0006 | ubyte | unused, write 0 |
+0007 | ubyte | unused, write 0 |
+0008 | uint | flags, see below |
+000c | uint | SizeX : width |
+0010 | uint | SizeY : height. Set to 1 for 1d-textures |
+0014 | uint | SizeZ : depth for volume textures. Set to 1 for non-volume textures |
+0018 | uint | count for texture arrays. Set to 1 for non-arrays. Automatically multiplied by 6 for cubemap arrays. |
+001c |
Value | Channels | Bits per Channel | Bits per Pixel | Interpretation | Comment |
---|---|---|---|---|---|
1 | BGRA | 8 | 32 | unsigned normalized | |
2 | RGBA | 16 | 64 | unsigned normalized | |
3 | RGBA | 16 | 64 | float | |
4 | RGBA | 32 | 128 | float | |
5 | RGBA | - | 4 | BC1 / DXT1 compressed | |
6 | - | - | - | reserved | |
7 | RGBA | - | 8 | BC3 / DXT6 compressed | |
8 | RG | 8 | 16 | unsigned normalized | [1] |
9 | R | 8 | 8 | unsigned normalized | [1] |
9 | R | 16 | 16 | unsigned normalized | [1] |
10 | A | 8 | 8 | unsigned normalized | |
11 | R | 16 | 16 | float | |
12 | R | 32 | 16 | float | |
13 | RG | 16 | 32 | float | |
14 | RG | 32 | 64 | float |
Channels are:
Unused channels are set to 0.
[1] These formats have been luma-formats in dx9 where the same value is set to red, green and blue, but that does not work any more in DX11.
Example for a texture file:
Tag "HEAD" 0000 48 45 41 44 "HEAD" : chunk type 0004 00 00 0 : Minor Version 0006 02 00 2 : Major Version 0008 0c 00 00 00 00 00 00 00 12 : head descriptor size head header 0010 56 45 4E 54 55 5A 21 00 "VENTUZ!\0" : magic number 0018 54 45 58 52 "TEXR" : file type tag Texture 001c 54 45 58 52 "TEXR" : chunk type 0020 00 00 0 : Minor Version 0022 01 00 1 : Major Version 0024 82 20 00 00 00 00 00 00 0x2082 : size of image descriptor + compressed pixel array Texture Header 002c 02 2 : 2d texture 002d 01 1 : pixel format BGRA8888 002e 01 1 : compression ZLib 002f 03 3 : gamma : sRGB 0030 0a 10 : number of mipmaps (including biggest one) 0031 00 0 : unused 0032 00 0 : unused 0033 00 0 : unused 0034 00 00 00 00 0 : flags 0038 00 02 00 00 512 : size x (width) 003c 00 02 00 00 512 : size y (height) 0040 01 00 00 00 1 : size z (depth) 0044 01 00 00 00 1 : array count Texture Arrays: (all mipmaps, starting with biggest, as zlib stream)