A tool to generate glam
's source code.
glam
is not a generic library, in part because some types are backed by SIMD
when it is available and also to avoid code complexity. However to support a
number of different number types combined with a number of different SIMD
architectures there is a lot of duplicate code and comments. Previously macros
were used to avoid a lot of the duplication. The down side of this was macros
were also quite complex and glam
users often needed to navigate the macros
when viewing source or debugging. Contributors also had to find their way around
the macros to fix bugs or add features to glam
.
This tool replaces the majority of macros in glam
by instead generating all of
the different containers and SIMD implementations offline. These generated files
are committed to the glam
repo and this is what is compiled, read and debugged
by glam
users.
Source files are generated using Tera
templates. These kinds of templates
are typically used to generate static web pages, but here we are using them to
generate Rust source. The main advantages are a lot of the template looks like
Rust code. The templates are fairly declarative and easier to follow than the
macros (in my opinion). I try to stick to basic features of the templating
language to keep things simple.
There codegen
program maps a template to an output file. There are a small
number of variables that are usually set before the template is rendered to
control output:
scalar_t
- the container's scalar type, e.g.f32
,f64
,i32
,u32
dim
- the container's dimension, e.g.2
,3
or4
is_scalar
- generate regular Rust code using arithmetic operatorsis_sse2
- generate code usingsse2
intrinsicsis_wasm32
- generate code usingwasm32
simd128
intrinsics
The templates for swizzles and vector masks behave slightly differently but otherwise this is the common setup.
The following templates are used:
affine.rs
- generates 2D and 3D affine transformation typesmat.rs
- generates all matrix typesquat.rs
- generates all quaternion typesvec.rs
- generates all vector typesvec_mask.rs
- generates all vector mask typesswizzle_traits.rs
- generates swizzle traitsswizzle_impl.rs
- generates impls of swizzle traits for all vector types
Not all types have sse2 or wasm32 implementations, if a type does have these the
scalar and SIMD types will be in their respective modules. For example the f32
Vec3
type has no SIMD implementation, but Vec4
does, the generated source
files are structured like so:
src/f32/vec3.rs
src/f32/scalar/vec4.rs
src/f32/sse2/vec4.rs
src/f32/wasm32/vec4.rs
The appropriate implementation module will be included when glam
is compiled.
Each template starts with setting up a number of common variables based on the
inputs from the codegen
program. Commonly used variables are:
self_t
- the name of the type being generatedinner_t
- the inner storage type used by this type (e.g.__m128
orcore::storage::XYZ<f32>
)deref_t
- the type used by theDeref
andDerefMut
implementation - not always the same asinner_t
col_t
- the column type - used by matrix and affine transform templatesquat_t
- thescalar_t
quaternion type nameaffine2_t
- thescalar_t
2D affine transform type nameaffine3_t
- thescalar_t
3D affine transform type namevec2_t
- thescalar_t
2D vector type namevec3_t
- thescalar_t
3D vector type namevec4_t
- thescalar_t
4D vector type namemat2_t
- thescalar_t
2D matrix type namemat3_t
- thescalar_t
3D matrix type namemat4_t
- thescalar_t
4D matrix type name
The easiest way to run codegen
is via cargo
:
cargo run -p codegen
To pass additional parameters, e.g. -h
for help:
cargo r -p codegen -- -h
codegen
will generate all files by default or if a glob pattern is specified,
only output files that match the glob.
The following steps will be performed for each output file:
- Check
git
status to see if the output is already modified. This is to avoid overriding changes by accident. The-f
flag can be used to force override. - Render the
Tera
template in memory - Rust format the template output
- Write the formatted output to the output file path
The Tera
error output leaves a little to be desired. If the template fails to
render with no error it is usually because a variable that doesn't exist is
being referenced somewhere in the template.
If the Rust format step fails it is usually because the template generated
invalid Rust. The -n
flag can be used to skip the Rust format step so that the
file may be compiled which typically shows what the problem is.
When working on a template it is often easiest to generate one or two output
files using a glob pattern to check that template changes are working as
expected. When finished editing generate everything (most files should not
change unexpectedly) and run the test suite using build_and_test_features.sh
and build_and_test_wasm32_chrome.sh
.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or [http://www.apache.org/licenses/LICENSE-2.0])
- MIT license (LICENSE-MIT or [http://opensource.org/licenses/MIT])
at your option.