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 |
7.00.00 | 3.0 | added more compresssion size information |
V 1.0 saves the uncompressed size in the tag 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 tag for MESH and TEXR.
If compression is not used, both are identical.
For compressed arrays, V 3.0 adds a 64 bit field containing compressed size of data. Prior to this the compressed size has to be determined by zlib from the data stream and the decompressed size expected.
This simplifies reading files for some zlib implementations that can not unzip based on decompressed size only.
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 header 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 the 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 chunk header, which has a known size for each major version of that chunk, can be followed by data arrays. Examples for this are vertex and index buffers in a mesh or the pixels of a texture.
The size of the data arrays can be easily calculated from the header, as explained in the indiviudal chunks documentation. Data arrays are not padded or aligned in any way.
The header can indicate individual arrays to be compressed. In this case,
The compressed size was added in VFF version 3.0.
The tags and headers are never compressed.
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 subset array, vertex array and index 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.
Frames are used for animated mesh sequences, and subsets are used to split a single mesh into multiple parts that can be enabled / disabled / get a different material.
The number of subsets in the array is subset count * frame count.
Elements in the subset array are:
offset | type | description |
---|---|---|
+0000 | uint | vertex count |
+0004 | uint | index count |
If you have two frames and 3 subsets, the order is
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.
Index compression must be disabled when no indices are used.
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. This also means that the second array slice may be misaligned.
If compression is used, all pixels are stored in a single compression stream.
The 'TEXR' chunk contains the following elements in this order:
Texture Header :
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 | Set to 1 (for future volume textures) |
+0018 | uint | ArrayCount |
+001c |
For flipbooks, set ArrayCount to the number of images in the flipbook.
For cubemaps, set ArrayCount to 6. There are no cubemap array (yet).
Value | Channels | Bits per Channel | Bits per Pixel | Block Size | Bytes per Block | Interpretation | Comment |
---|---|---|---|---|---|---|---|
1 | BGRA | 8 | 32 | 1x1 | 4 | unsigned normalized | |
2 | RGBA | 16 | 64 | 1x1 | 8 | unsigned normalized | |
3 | RGBA | 16 | 64 | 1x1 | 8 | float | |
4 | RGBA | 32 | 128 | 1x1 | 16 | float | |
5 | RGBA | - | 4 | 4x4 | 8 | BC1 / DXT1 compressed | |
6 | - | - | - | - | - | reserved | |
7 | RGBA | - | 8 | 4x4 | 16 | BC3 / DXT5 compressed | |
8 | RG | 8 | 16 | 1x1 | 2 | unsigned normalized | |
9 | R | 8 | 8 | 1x1 | 1 | unsigned normalized | [1] |
9 | R | 16 | 16 | 1x1 | 2 | unsigned normalized | [1] |
10 | A | 8 | 8 | 1x1 | 1 | unsigned normalized | |
11 | R | 16 | 16 | 1x1 | 2 | float | |
12 | R | 32 | 32 | 1x1 | 4 | float | |
13 | RG | 16 | 32 | 1x1 | 4 | float | |
14 | RG | 32 | 64 | 1x1 | 8 | 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.
The size of the pixel array is calculated by
blockx = (SizeX + blocksize - 1) / blocksize; blocky = (SizeY + blocksize - 1) / blocksize;
bytes = blockx * blocky * BytesPerBlock
The pixels are stored in a single array in this order:
This is the gltf example "cube" exported as VFF mesh:
https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Cube/glTF/Cube.gltf
Tag "HEAD" 0000 48 45 41 44 chunk type ("HEAD") 0004 00 00 minor version (0) 0006 03 00 major version (3) 0008 0C 00 00 00 00 00 00 00 header size without tag (12 bytes) Header "HEAD" 0010 56 45 4E 54 55 5A 21 00 magic number 0018 4D 45 53 48 file type ("MESH") Tag "MESH" 001c 4D 45 53 48 chunk type ("MESH") 0020 00 00 minor version (0) 0022 01 00 major version (1) 0024 7F 01 00 00 00 00 00 00 chunk size without tag (383 bytes) Header "MESH" 002c 04 00 00 00 flags (Optimized) 0030 11 10 01 00 vertex format (0x11011, PNTB) 0034 04 00 00 00 index format (uint) 0038 01 00 00 00 vertex compression (on) 003c 01 00 00 00 index compression (on) 0040 00 00 00 00 topology (triangle list) 0044 20 00 00 00 00 00 00 00 vertex count (32) 004c 24 00 00 00 00 00 00 00 index count (36) 0054 01 00 00 00 frame count 0058 01 00 00 00 subset count Subset Array 005c 20 00 00 00 vertex count (32) 0060 24 00 00 00 index count (36) Vertex Array 0064 F1 00 00 00 00 00 00 00 compressed array size (241 bytes) 006c 78 9C 8D 94 ... zlib compressed vertex stream 015d 0x006c + 0x00f1 (array start + array size) Index Array 015d 46 00 00 00 00 00 00 00 compressed array size (70 bytes) 0165 78 9C 2D C8 ... zlib compressed index stream 01ab 0x0165 + 0x0046 (array start + array size) End of file 01ab 0x002c + 0x017f (chunk start + chunk size)
This texture holds distance field font data from Ventuz
"C:\ProgramData\Ventuz7\Cache\Fonts\Arial\V7.000\Adv2D\L8_2048\00.ddz"
Tag "HEAD" 0000 48 45 41 44 chunk type ("HEAD") 0004 00 00 minor version (0) 0006 03 00 major version (3) 0008 0C 00 00 00 00 00 00 00 header size without tag (12 bytes) Header "HEAD" 0010 56 45 4E 54 55 5A 21 00 magic number 0018 4D 45 53 48 file type ("TEXR") Tag "TEXR" 001c 4D 45 53 48 chunk type ("TEXR") 0020 00 00 minor version (0) 0022 01 00 major version (1) 0024 1C 00 40 00 00 00 00 00 chunk size without tag (0x40001c bytes) Header "TEXR" 002c 02 texture type (2d) 002d 09 pixel format (RGBA_8888) 002e 00 compression (off) 002f 00 gamma (unspecified 0030 01 mipmap count (1) 0031 00 unused (set 0) 0032 00 unused (set 0) 0033 00 unused (set 0) 0034 00 00 00 00 flags (none) 0038 00 08 00 00 SizeX (2048) 003c 00 08 00 00 SizeY (2048) 0040 01 00 00 00 Cubemap Count (1) 0044 01 00 00 00 unused (set 1) Pixel Array 0048 00 00 00 00 ... pixel data, uncompressed End of file 400048 0x002c + 0x40001c (chunk start + chunk size)
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 header 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 header + 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 (no cube faces) 0044 01 00 00 00 1 Texture Arrays: (all mipmaps, starting with biggest, as a single zlib stream)