C++ Plugins & Macros

This section provides an overview of the “skeleton” of a basic plugin definition, including an explanation of the roles of the various MI_* macros. These macros are responsible for importing types, instantiating variants, and providing run-time type information.

Example code

Consider the following hypothetical plugin MyPlugin:

NAMESPACE_BEGIN(mitsuba)

template <typename Float, typename Spectrum>
class MyPlugin : public PluginInterface<Float, Spectrum> {
public:
    MI_IMPORT_BASE(PluginInterface, m_some_member, some_method)
    MI_IMPORT_TYPES()

    MyPlugin();

    Spectrum foo(..., Mask active) const override {
        MI_MASKED_FUNCTION(ProfilerPhase::MyEval, active)
        // ...
    }

    /// Indicate the name of this class (for logging)
    MI_DECLARE_CLASS(MyPlugin)
};

/// Implement RTTI data structures
MI_EXPORT_PLUGIN(MyPlugin)
NAMESPACE_END(mitsuba)

Macros

MI_MASK_ARGUMENT(mask)

This macro typically occurs at the beginning of a function that takes a mask as an argument.

void my_method(..., Mask active) {
    MI_MASK_ARGUMENT(active);
}

Masking is not really needed on scalar variants: if a function is called, we assume that active=true. Masking can unfortunately decrease performance in this case due to the generation of extra branches. Here, MI_MASK_ARGUMENT therefore expands to

if constexpr (is_scalar_v<Float>)
    active = true;

which turns the mask argument into a compile-time constant on scalar targets, allowing the compiler to optimize away the undesired branches. MI_MASK_ARGUMENT is also part of the next macro.

MI_MASKED_FUNCTION(phase, mask)

This macro builds on the MI_MASK_ARGUMENT macro and typically occurs at the beginning of a function that takes a mask as an argument.

void my_method(..., Mask mask) {
    MI_MASKED_FUNCTION(ProfilerPhase::MyMethod, active);
}

Masking is not really needed on scalar variants: if a function is called, we assume that active=true. Masking can unfortunately decrease performance in this case due to the generation of extra branches. Here, MI_MASKED_FUNCTION macro expands to

if constexpr (is_scalar_v<Float>)
    active = true;
ScopedPhase _(ProfilerPhase::MyMethod);

which turns the mask argument into a compile-time constant on scalar targets, allowing the compiler to optimize away the undesired branches.

Mitsuba ships with a powerful sampling profiler that facilitates tracking down hot-spots during rendering. The last line of this macro (ScopedPhase) informs this profiler that we are currently executing a function that belongs to the profiler phase phase.

MI_IMPORT_BASE(Name, …)

Because most Mitsuba classes are templates, attributes and methods of parent classes are not visible by default. They can be imported explicitly via using Base::some_method; statements, but writing many such statements is tiresome. The variadic macro MI_IMPORT_BASE expands into arbitrarily many such using statements. For example,

MI_IMPORT_BASE(Name, m_some_member, some_method)

expands to

using Base = Name<Float, Spectrum>;
using Base::m_some_member;
using Base::some_method;

MI_IMPORT_CORE_TYPES()

This macro will generate a sequence of using declarations to import the Mitsuba core types (e.g. Vector{1/2/3}{i/u/f/d}, Point{1/2/3}{i/u/f/d}, …). They are automatically inferred from the definition of Float.

Note

A type named Float must exist preceding evaluation of this macro.

For example,

using Float = float;

MI_IMPORT_CORE_TYPES()

// expands to:

// ...
using Point2f = Point<Float, 2>;
using Point3f = Point<Float, 3>;
// ...
using BoundingBox3f = BoundingBox<Point3f>;
// ...

MI_IMPORT_TYPES(…)

This macro invokes MI_IMPORT_CORE_TYPES() and furthermore imports rendering-related types, such as Ray3f, SurfaceInteraction3f, BSDF, etc. These templated aliases will depend on the preceding declaration of the Float and Spectrum.

It is also possible to pass other types as arguments, for which templated aliases will be created:

using Float    = float;
using Spectrum = Spectrum<Float, 4>;

MI_IMPORT_TYPES(MyType1, MyType2)

// expands to:

MI_IMPORT_CORE_TYPES()
// ...
using Ray3f = Ray<Point<Float, 3>, Spectrum>;
// ...
using SurfaceInteraction3f = SurfaceInteraction<Float, Spectrum>;
// ...
using MyType1 = MyType1<Float, Spectrum>; // alias for the optional parameters
using MyType2 = MyType2<Float, Spectrum>;

MI_DECLARE_CLASS(Name)

This macro should be invoked within the class declaration of the plugin. The provided Name parameter is picked up by log messages, warnings, or errors that are triggered by the plugin.

MI_EXPORT_PLUGIN(Name)

This macro will explicitly instantiate all enabled variants of a plugin:

MI_EXPORT_PLUGIN(Name)

// expands to:

template class MI_EXPORT Name<float, Color<float, 1>>    // scalar_mono
template class MI_EXPORT Name<float, Spectrum<float, 4>> // scalar_spectral
// ...