ozz run-time library are thread safe, and was design as such from the ground. Ozz does not impose a way to distribute the workload across different threads, but rather propose thread-safe data structures and processing unit, that can be distributed. This data-oriented approach makes everything clear about thread safety:
The multi-threading sample demonstrates how ozz-animation runtime can (naively yet safely) be used in a multi-threaded context, using openmp to distribute jobs execution across threads.
IO operations in ozz are done through the ozz::io::Stream
interface, which conforms to crt FILE API.
ozz propose two implementations of the Stream interface:
ozz::IO::File
: Allows to read and write to a file. Opening is done at construction time, using the same API as the crt fopen function. The File destructor automatically closes the file if it was opened.ozz::IO::MemoryStream
: Implements an in-memory Stream. It allows to seamlessly use a memory buffer as a Stream. The opening mode is equivalent to fopen w+b (binary read/write).One can implement ozz::io::Stream
interface to remap IO operations to his own IO management strategy. This is useful for example to create a read or write stream mapped to a sub-part of a file (or a package) that contains many other data but ozz data. Overloading ozz::io::Stream
interface requires to implement the following functions:
Serialization is based on the concept of archives. Archives are similar to c++ iostream
. Data can be saved to a OArchive with the << operator
, or loaded from a IArchive with the >> operator
. Archives read or write data to ozz::io::Stream
objects.
Primitive data types are simply saved/loaded to/from archives, while struct and class are saved/loaded through Save/Load intrusive or non-intrusive functions:
Arrays of struct/class or primitive types can be saved/loaded with the helper function ozz::io::MakeArray()
which is then streamed in or out using <<
and >>
archive operators:
Versioning can be done using OZZ_IO_TYPE_VERSION
macros. Type version is saved in the OArchive, and is given back to Load functions to allow to manually handle version modifications.
Versioning can be disabled using OZZ_IO_TYPE_NOT_VERSIONABLE
like macros. It can be used to optimize native/simple object types. It can not be re-enabled afterward without braking version compatibility.
Objects can be assigned a tag (a string) using OZZ_IO_TYPE_TAG macros. A tag allows to check the type of the object to read from an archive.
When reading from an IArchive, an automatic assertion check is performed for each object that has a tag declared. The check can also be done manually, before actually reading and object, to ensure an archive contains the expected object type.
Endianness (big or little endian) can be specified while constructing an output archive (ozz::io::OArchive). Input archives automatically handle endianness conversion if the native platform endian mode differs from the archive one.
IArchive and OArchive expect valid streams as argument, respectively opened for reading and writing. Archives do NOT perform error detection while reading or writing. All errors are considered programming errors. This leads to the following assertions on the user side:
Dynamic memory allocations, from any ozz library, all go through a custom allocator. This allocator can be used to redirect all allocations to your own memory manager, defining your own memory management strategy. To redirect memory allocations, override ozz::memory::Allocator object and implement its interface:
… and replace ozz default allocator with your own, using ozz::memory::SetDefaulAllocator() function.
ozz provides different math libraries, depending on how math data are intended to be used and processed:
Provides math objects (vectors, primitives…) and operations. These are intended to be used when access simplicity and storage size are more important than algorithm performance. The ozz::math::Float3 (x, y, z components vector) math objects for example provides a simple access to each of its components (x, y and z), simple initialization functions and constructors, simple and usual math/geometric operations (additions, cross-product…) which make it very simple to use. On the other end the implementation relies on fpu scalar operations, with no vectorial (SIMD, SSE…) optimizations. The following files are provided:
Provides SIMD optimized math objects (matrices, vectors…) and operations, intended to be used when performance is important and most of all data access pattern cope with SIMD restrictions:
This library is implemented in a single file ozz/base/maths/simd_math.h which provides the following math structure:
Current version has SSE2 to SSE4 implementation, and a reference fpu one used as fallback when SSE isn’t available. Adding other SIMD implementation (VMX…) is doable thanks to the complete existing unit-tests suite.
This library provides math objects and operations with an SoA (Struct Of Array) data layout. That means that every component of a vector for example, is in fact an array of 4 values (standard SIMD size). A single ozz::math::SoaFloat3 is in fact storing 4 vectors in the form xxxx, yyyy and zzzz. Data access simplicity is of course limited, but performance benefit is important because this data layout removes some of the SIMD constraints:
Furthermore instruction throughput is maximized over AoS (Array of Struct) as dependencies between instructions during math standard operations is reduced. The drawback is that it’s impossible to execute conditional operations on one element of the array inside a SoA data (as they are SIMD registers). The solution in this case is to execute operations for the 2 code paths of the condition and use masks to compose the result (see ozz::math::Select functions). SoA comparison expressions returns ozz::math::SimdInt4 values (instead of bools for AoS) which can directly be used for masking or Select like functions. This library relies on these following files to provides SoA structures similar with the fpu-based library:
The library is implemented over the SIMD math library and takes all the benefits of SSE or other SIMD implementations. ozz-animation make an heavy use of SoA data structures (see ozz::animation::BlendingJob for example), which could be used as examples. Data oriented programming is very important to maximize benefits of SoA structures, allowing to arrange the data according to the processing that’s operating on them.
ozz base library remaps stl standard containers (vectors, maps..) to ozz memory allocators. The purpose is to track memory allocations and avoid leaks.
Note that these containers are not used by the runtime libraries.
Base library implement an linked node list (ozz::container::IntrusiveList
), with the benefit of many O1 algorithms and a different memory scheme compared to std::list. It’s templatized for easier reuse and compatible with std::list API and stl algorithms.
This library implements a command line option processing utility. It helps with command line parsing by converting arguments to c++ objects of type bool, int, float or c-string.
Unlike getogt()
, program options can be scattered in the source files (a la google-gflags). Options are collected by a parser which then automatically generate the help/usage screen based on registered options.
This library is made of a single .h and .cc with no other dependency, to make it simple to be reused.
To set an option from the command line, use the form --option=value for non-boolean options, and --option/–nooption for booleans. For example, --var=46 will set var variable to 46. If var type is not compatible with the specified argument type (in this case an integer, a float or a string), then the parser displays the help message and requires application to exit.
Boolean options can be set using different syntax:
Specifying an option (in the command line) that has not been registered is an error, the parsing library will request the application to exit.
As in getopt()
and gflags
, – by itself terminates flags processing. So in: foo --f1=1 -- --f2=2, f1 is considered but f2 is not.
Parsing is invoked through ozz::options::ParseCommandLine()
function, providing argc and argv arguments of the main function. This function also takes as argument two strings to specify the version and usage message.
To declare/register a new option, use OZZ_OPTIONS_DECLARE_xxx
like macros. Supported options types are bool, int, float and string (c string).
OZZ_OPTIONS_DECLARE_xxx
macros arguments allow to give the option a:
So for example, in order to define a boolean “verbose” option, that is false by default and optional (ie: not required):
This option can then be referenced from the code using OPTIONS_verbose
c++ global variable, that implement an automatic cast operator to the option’s type (bool in this case).
The parser also integrates built-in options: