Configuration options
This page documents the configuration knobs shared by all three build methods (CMake, GNU make, Direct compilation): floating-point precision, the target SIMD instruction set, optional runtime assertions, and the optional viewer window.
Floating-point precision
Nearly every EBGeometry class and function is templated on a floating-point type T, float
or double. Precision is a compile-time choice, not a runtime one –
there is no notion of a “current” precision inside the library itself. Pick T explicitly
when instantiating a class or calling a function template:
EBGeometry::Pool poolDouble(EBGeometry::hostMemoryResource());
EBGeometry::Pool poolFloat(EBGeometry::hostMemoryResource());
auto sdfDouble = EBGeometry::Parser::readIntoTriMeshSDF<double>("bunny.ply", poolDouble);
auto sdfFloat = EBGeometry::Parser::readIntoTriMeshSDF<float>("bunny.ply", poolFloat);
Consuming code (including every example under Examples/) typically reads precision from
a preprocessor define so it can be overridden from the build system without editing source:
#ifndef EBGEOMETRY_PRECISION
#define EBGEOMETRY_PRECISION double
#endif
using T = EBGEOMETRY_PRECISION;
See Building for how to set EBGEOMETRY_PRECISION from each build method.
SIMD acceleration
See SIMD acceleration for a conceptual overview of SIMD acceleration in EBGeometry, SIMD-accelerated classes for exactly which classes it applies to and what it means for each, and Building for the compiler/CMake/Makefile flags that enable it for each build method.
Branching factor and SIMD width
The BVH-backed classes take two size parameters: the branching factor K (children per BVH node)
and, for the triangle and point groups, the SIMD width W (triangles or points evaluated together
in one leaf group). There are two ways to choose them.
The defaults – BVH::DefaultBranchingRatio<T>(), TriangleSoA::DefaultWidth<T>() and
PointSoA::DefaultWidth<T>() – are 4 for float and double. The class templates that have
defaults (PointCloudBVH, PointSoAT, PointAoSoA, TriangleAoSoA,
Parser::readIntoTriMeshSDF) use them. Unless the build defines EBGEOMETRY_HOST_TUNED_DEFAULTS
(below), they never change, so a type spelled with them is the same type in every file: in one
compiled with -mavx and in one compiled without, and in both passes of a CUDA or HIP compile.
That is what lets an object be built on the host and used on a GPU.
The host-tuned values – BVH::HostBranchingRatio<T>(), TriangleSoA::HostWidth<T>() and
PointSoA::HostWidth<T>() – fill one SIMD register under the compiler’s flags: 16 for float
and 8 for double with AVX-512F, 8 and 4 with AVX, and 4 otherwise. They can be spelled into a
type explicitly:
constexpr size_t K = EBGeometry::BVH::HostBranchingRatio<T>();
constexpr size_t W = EBGeometry::TriangleSoA::HostWidth<T>();
auto sdf = EBGeometry::Parser::readIntoTriMeshSDF<T, K, W>("bunny.ply", pool);
The rule is to use the defaults for any type that device code also uses, or that is passed between translation units compiled with different flags. With the host-tuned values, the same spelled type is a different type in two such files – and within one CUDA or HIP compile, the host and device passes can disagree on its layout, which corrupts a kernel’s arguments without any error.
Mixing SIMD flags in one program. Even with the defaults, files compiled with different SIMD
flags compile the inline SIMD code differently: the child-box distances of PackedBVH and the
triangle and point groups each have an AVX-512, an AVX and an SSE4.1 version, and a scalar one. Each
such inline function is then defined differently in different files, and the linker keeps one
file’s version for every caller. The types keep one layout, so objects can still be passed between
the files, but the program can run instructions that only the other file was compiled for (an
illegal-instruction crash on a CPU without them), or get the other file’s results (the SIMD and
scalar paths agree for finite inputs).
Nothing checks this. Compile every file that uses EBGeometry with the same SIMD flags, unless the
program only ever runs on CPUs that have the widest instruction set any of its files uses.
The build switch. Defining EBGEOMETRY_HOST_TUNED_DEFAULTS for a whole build makes
TriangleSoA::DefaultWidth<T>() return TriangleSoA::HostWidth<T>(), so every
readIntoTriMeshSDF that uses the default width, and every TriMeshSDF spelled with
TriangleSoA::DefaultWidth<T>(), evaluates a full register of triangles at
once, without spelling the width into each type. It changes the triangle-group width only: the
branching factor and the point-group width stay 4, since widening them does not make queries faster
(see below). Two conditions come with it:
One set of flags for the whole build. Every translation unit must define the macro and be compiled with the same SIMD flags, since the width then follows them; otherwise one spelled type has two layouts, and an object built in one file is read wrongly in another. Nothing checks this across translation units, so set both for the whole build, as EBGeometry’s CMake option does.
Host-only. The headers refuse the macro in a CUDA or HIP translation unit (a compile error), since the host and device passes of such a compile see different SIMD flags.
EBGeometry’s CMake build sets it through the EBGEOMETRY_HOST_TUNED_DEFAULTS option, which is on
when EBGeometry is the top-level project (its tests, examples and benchmarks) and no GPU backend is
enabled, and off otherwise; asking for it together with a GPU backend is a configure error. Like
EBGEOMETRY_SIMD, it is not passed on to a project that uses EBGeometry through
find_package, add_subdirectory or FetchContent: such a project defines the macro itself
if it wants the wider groups. A wider group also widens TriMeshSDF’s leaves, which hold up to
maxLeafGroups groups.
Which parameter pays off. A wider triangle group is the one change that makes queries clearly
faster, and then mostly in single precision on AVX2: a TriMeshSDF leaf test then evaluates a full
register of triangles per instruction. Widening the branching factor or the point groups does not
make queries faster, and under AVX-512 a branching factor or point-group width of 16 makes them
markedly slower (some CPUs also lower their clock under heavy 512-bit work, which makes measurements
there noisy). This is why the switch widens the triangle groups alone.
To measure on your own machine and mesh, run the HostTuning example, which
builds a point cloud and a mesh with the portable values and with the host-tuned ones and times them.
Build it on its own (with make or a direct compiler invocation in its folder) rather than in
EBGeometry’s CMake build: there the switch above is on, so its “default” triangle width already is
the host-tuned one.
Compile-time assertions (static_assert)
Alongside its two runtime checks (Always-on checks (EBGEOMETRY_REQUIRE) and Runtime assertions (EBGEOMETRY_EXPECT)), EBGeometry
uses ordinary C++ static_assert throughout to enforce template-parameter invariants that are
known at compile time – these are always active and cannot be disabled. A violation fails the build with a
compiler error rather than misbehaving, crashing, or aborting at runtime. Examples of what is
guarded this way:
The floating-point type
Treally isfloat/double(not, say,int), on essentially every class template.BVH branching factors and SoA widths are in range, e.g.
K >= 2.Type constraints between template parameters.
The SIMD data-alignment invariants described in SIMD-accelerated classes.
See the end of this page for an example combining static_assert with the runtime checks in a
custom class.
Always-on checks (EBGEOMETRY_REQUIRE)
EBGeometry checks what a caller controls – constructor arguments, sizes and counts, the memory
resource a pool is built on – with EBGEOMETRY_REQUIRE(cond, message...). These checks are on in
every build, whether or not EBGEOMETRY_ENABLE_ASSERTIONS is defined: without them, a Release
build given bad input would carry on into a wrong answer or undefined behavior. Each runs once,
when an object is built, outside any query loop, so it costs a few comparisons per object.
On failure the program prints what went wrong, the failed condition, the file and the line to
stderr, then calls std::abort():
EBGeometry::BVHUnion: need one bounding volume per primitive (4 primitives, 3 bounding volumes)
check: (a_primitives.size() == a_boundingVolumes.size())
file: Source/EBGeometry_BVHUnionImplem.hpp
line: 92
The message argument is a printf format string literal, starting with the class or function
that checks, followed by its arguments, so the compiler checks the format against the arguments.
A mesh file that cannot be read is not a programming error, so the file readers throw
EBGeometry::Parser::ParseError instead of aborting; see Reading data. A mesh whose
faces are oriented inconsistently or fold back onto each other can be loaded with a warning
instead, with Parser::OnDefect::Warn; see Loading meshes with surface defects.
The viewer window (EBGEOMETRY_VIEWER)
Render::view() (The renderer and the viewer) opens an interactive window on a shape. The window needs
SDL2, a third-party library, so it is compiled only on request; without it view() prints a line
saying how to build it and returns, and nothing else in EBGeometry needs SDL2.
CMake:
-DEBGEOMETRY_VIEWER=SDL2(defaultOFF) finds SDL2 and gives every target of the build that uses EBGeometry the window, like the other settings on this page it is not carried by an installed EBGeometry. Each example’s ownCMakeLists.txttakes the same option when built on its own.GNU make:
make VIEWER=SDL2in an example folder, which usessdl2-config.Directly: define
EBGEOMETRY_VIEWER_SDL2and add SDL2’s flags:g++ -std=c++17 -O3 -DEBGEOMETRY_VIEWER_SDL2 $(sdl2-config --cflags) -I/path/to/EBGeometry main.cpp \ $(sdl2-config --libs) -pthread
For nvcc, also define
SDL_DISABLE_IMMINTRIN_H: SDL’s x86 intrinsics headers do not get through it.
Even with the window built, view() opens none when the program is given --no-view or there is
no display, so programs that call it still run unattended. The renderer itself (Render::Renderer)
needs no option; it draws on std::thread, and the EBGeometry::EBGeometry CMake target links
Threads::Threads for it (add -pthread when compiling by hand).
Runtime assertions (EBGEOMETRY_EXPECT)
EBGEOMETRY_EXPECT(cond) is the standard way to encode preconditions inside
EBGeometry code. When compiling with assertions enabled, EBGeometry will emit an
error when the condition is violated and print the file and corresponding line
where the violation occurred.
When EBGEOMETRY_ENABLE_ASSERTIONS is not defined (the default):
#define EBGEOMETRY_EXPECT(cond) ((void)0)
The condition is not evaluated at all — the macro expands to a no-op, so an
assertion costs exactly nothing in release, including not running its predicate.
A local variable that exists only to be asserted therefore becomes unused; mark
such a declaration [[maybe_unused]] at its site to keep the release build
warning-clean.
When EBGEOMETRY_ENABLE_ASSERTIONS is defined:
EBGEOMETRY_EXPECT(std::isfinite(a_point[0]));
// On failure:
// EBGeometry assertion failed: (std::isfinite(a_point[0]))
// file: EBGeometry_AnalyticDistanceFunctions.hpp
// line: 118
// function: signedDistance
The program prints the failing expression, file, line, and enclosing function name
to stderr, then calls std::abort(). This produces a core dump (on POSIX
systems) that can be loaded directly into a debugger.
Recommended workflow
Build type |
Recommended flags |
|---|---|
Development / testing |
|
CI / integration tests |
|
Production / benchmark |
|
Warning
Assertions add measurable overhead inside hot BVH traversal and SDF evaluation loops. Enable them during development and testing; disable them (the default) for production builds.
Writing your own assertions
If you extend EBGeometry with new functionality classes, use static_assert to guard
preconditions that are known at compile time (from the template parameters alone),
EBGEOMETRY_REQUIRE for argument values checked once when an object is built, and
EBGEOMETRY_EXPECT for invariants on paths that run many times, such as a signed-distance query.
Both macros are available in any translation unit that (directly or transitively) includes
EBGeometry_Macros.hpp, which is pulled in automatically through EBGeometry.hpp. An example combining both is given below. Like the built-in analytic shapes
(Analytic shapes), it is a plain, trivially copyable value type with a
signedDistance() member rather than a subclass of a virtual base, so it can also be evaluated on
a GPU and used as the primitive type of a BVH union (BVH-accelerated unions):
#include "EBGeometry.hpp"
#include <type_traits>
template <class T>
class MySDF
{
public:
static_assert(std::is_floating_point_v<T>, "MySDF requires a floating-point type T");
explicit MySDF(T a_radius) noexcept
{
EBGEOMETRY_REQUIRE(a_radius > T(0), "MySDF: the radius must be positive (%g)", double(a_radius));
m_radius = a_radius;
}
[[nodiscard]] EBGEOMETRY_HOST_DEVICE T
signedDistance(const EBGeometry::Vec3T<T>& a_point) const noexcept
{
return a_point.length() - m_radius;
}
private:
T m_radius = T(1);
};