MDL SDK API nvidia_logo_transpbg.gif Up
Example for custom Distiller plugin
[Previous] [Up] [Next]

This example describes the creation of custom Distiller plugins and how to use them in an application. Distiller plugins implement one or more custom distilling target material(s) to which materials can be distilled.

New Topics

  • Loading a custom Distiller plugin
  • Custom Distiller plugin implementation
  • Distiller rule definition

Detailed Description

Custom Distiller plugin loading

A custom Distiller plugin is a shared library that is loaded into the MDL-SDK and provides one or more custom distilling target(s) to the an application. The application is responsible for loading the plugin and can then use the target(s) through the Distiller API, just as for the targets from the mdl_distiller plugin which is part of the MDL-SDK.

The example program described below loads an example plugin and distills a material to the target implemented by the plugin.

Custom distiller plugin implementation

The example distiller plugin implements a simple distiller target called "mini_glossy" which only performs a very simple transformation on any material it processes. "mini_glossy" represents a material model that consists only of the nodes simple_glossy_bsdf(), tint() and normalized mixers and color mixers. All other nodes are replaced by combinations of these in an attempt to simulate the appearance of the original material.

The plugin in file distilling_target_plugin.cpp (see below) implements the Distiller plugin interface. In its distill() function, it applies several transformation rules to a compiled material. These rules are defined in the domain-specific MDL Transformation Language (MDLTL), which allows the specification of rule-based graph transformations. The rule file is also described below.

Note that the example plugin implements one target. The plugin interface is general enough so that plugins could implement more than one in the same plugin. The distiller plugin mdl_distilller that comes with the MDL-SDK does exactly that and implements multiple targets.

Distiller rule definition

Distiller rule definitions specify transformations on material graph nodes using a domain-specific declarative language, called MDL Transformation Language (MDLTL).

Each MDLTL file contains a number of rule sets, and each rule set contains a number of rules. The Distiller plugin decides which rule sets are applied to a material graph, and in which order.

Each rule consists of a left- and a right-hadn side. The left-hand side is a pattern that matches a graph node and binds variables, and the right-hand side specifies the replacement for the matched rule.

When a rule set is applied to a material, the Distiller will attempt to match all rules of a rule set, in order, to each node in the material graph. When it matches, the variables in the lefthand pattern of the rule are bound to subexpressions of the current node, and the node on the right-hand side is constructed, possibly including expressions bound by the left-hand pattern. After a successful match, the Distiller will continue with matching other nodes of the materials. Keywords in MDLTL specify the used rule application strategy (such as top-down or bottom-up) as well as repeated matching after a successful match.

Example Source

Source Code Location: examples/mdl_sdk/distilling_target/example_distilling_target.cpp

The following example program accepts the following command line options:

show command line help
--material_file FILE>
File containing fully qualified names of materials to distill
--module MODULE_NAME
distill all materials from the module, can occur multiple times
--mdl_path PATH
mdl search path, can occur multiple times
dump structure of distilled material
dump structure of distilled material
* Copyright 2024 NVIDIA Corporation. All rights reserved.
// examples/mdl_sdk/distilling/example_distilling_target.cpp
// Introduces the distillation of mdl using a custom distilling target.
#include "example_shared.h"
#include "example_shared_dump.h"
// Custom timing output
#include <chrono>
struct Timing
explicit Timing(std::string operation)
: m_operation(operation)
m_start = std::chrono::steady_clock::now();
auto stop = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = stop - m_start;
printf("info: %s time: %.2f ms.\n",
elapsed_seconds.count() * 1000);
std::string m_operation;
std::chrono::steady_clock::time_point m_start;
// Creates an instance of the given material.
mi::neuraylib::IFunction_call* create_material_instance(
const std::string& module_qualified_name,
const std::string& material_simple_name)
// Load the module.
mdl_impexp_api->load_module(transaction, module_qualified_name.c_str(), context);
if (!print_messages(context))
exit_failure("Loading module '%s' failed.", module_qualified_name.c_str());
// Get the database name for the module we loaded
if (!module)
exit_failure("Failed to access the loaded module.");
// Attach the material name
std::string material_db_name
= std::string(module_db_name->get_c_str()) + "::" + material_simple_name;
material_db_name = mi::examples::mdl::add_missing_material_signature(
module.get(), material_db_name);
if (material_db_name.empty())
exit_failure("Failed to find the material %s in the module %s.",
material_simple_name.c_str(), module_qualified_name.c_str());
// Get the material definition from the database
if (!material_definition)
exit_failure("Accessing definition '%s' failed.", material_db_name.c_str());
// Create a material instance from the material definition with
// the default arguments.
mi::Sint32 result;
mi::neuraylib::IFunction_call* material_instance =
material_definition->create_function_call(0, &result);
if (result != 0)
exit_failure("Instantiating '%s' failed.", material_db_name.c_str());
return material_instance;
// Compiles the given material instance in the given compilation modes
// and stores it in the DB.
mi::neuraylib::ICompiled_material* compile_material_instance(
const mi::neuraylib::IFunction_call* material_instance,
bool class_compilation)
Timing timing("Compiling");
mi::Uint32 flags = class_compilation
mi::neuraylib::ICompiled_material* compiled_material =
material_instance2->create_compiled_material(flags, context);
return compiled_material;
// Distills the given compiled material to the requested target model,
// and returns it
const mi::neuraylib::ICompiled_material* create_distilled_material(
const mi::neuraylib::ICompiled_material* compiled_material,
const char* target_model)
mi::Sint32 result = 0;
distiller_api->distill_material(compiled_material, target_model, nullptr, &result));
std::cerr << "result: " << result << "\n";
check_success(result == 0);
return distilled_material.get();
// Prints program usage
static void usage(const char *name)
<< "usage: " << name << " [options] [<material_name1> ...]\n"
<< "-h print this text\n"
<< "--material_file <file> file containing fully qualified names of materials to distill\n"
<< "--module <module_name> distill all materials from the module, can occur multiple times\n"
<< "--mdl_path <path> mdl search path, can occur multiple times.\n"
<< "--dump-original-material dump structure of distilled material\n"
<< "--dump-distilled-material dump structure of distilled material\n";
void load_materials_from_file(const std::string & material_file, std::vector<std::string> & material_names)
std::fstream file;, std::fstream::in);
if (!file)
std::cout << "Invalid file: " + material_file;
std::string fn;
while (getline(file, fn))
void load_materials_from_modules(
, mi::neuraylib::IMdl_impexp_api * mdl_impexp_api
, const std::vector<std::string> & module_names
, std::vector<std::string> & material_names)
for (auto module_name : module_names)
// Sanity check
if (module_name.find("::") != 0)
module_name = std::string("::") + module_name;
mi::Sint32 rtn(mdl_impexp_api->load_module(transaction, module_name.c_str(), context.get()));
check_success(rtn == 0 || rtn == 1);
mi::Size material_count = module->get_material_count();
for (mi::Size i = 0; i < material_count; i++)
std::string mname(module->get_material(i));
int MAIN_UTF8(int argc, char* argv[])
std::string target_model = "mini_glossy";
std::vector<std::string> material_names;
std::vector<std::string> module_names;
std::string material_file;
bool dump_distilled_material = false;
bool dump_original_material = false;
Timing timing("Elapsed");
mi::examples::mdl::Configure_options configure_options;
// Collect command line arguments, if any.
for (int i = 1; i < argc; ++i) {
const char *opt = argv[i];
if (opt[0] == '-') {
if (strcmp(opt, "--mdl_path") == 0) {
if (i < argc - 1)
else if (strcmp(opt, "--material_file") == 0) {
if (i < argc - 1)
material_file = argv[++i];
else if (strcmp(opt, "--module") == 0) {
if (i < argc - 1)
else if (strcmp(opt, "--dump-distilled-material") == 0) {
dump_distilled_material = true;
else if (strcmp(opt, "--dump-original-material") == 0) {
dump_original_material = true;
if (!material_file.empty())
load_materials_from_file(material_file, material_names);
// Access the MDL SDK.
mi::base::Handle<mi::neuraylib::INeuray> neuray(mi::examples::mdl::load_and_get_ineuray());
if (!neuray.is_valid_interface())
exit_failure("Failed to load the SDK.");
// Configure the MDL SDK.
if (!mi::examples::mdl::configure(neuray.get(), configure_options))
exit_failure("Failed to initialize the SDK.");
// Load the custom example distilling plugin.
if (mi::examples::mdl::load_plugin(neuray.get(),
"distilling_target_plugin" MI_BASE_DLL_FILE_EXT) != 0)
exit_failure("Failed to load the 'distilling_target_plugin' plugin.");
// Start the MDL SDK
mi::Sint32 ret = neuray->start();
if (ret != 0)
exit_failure("Failed to initialize the SDK. Result code: %d", ret);
// Get MDL factory.
// Create a transaction.
mi::base::Handle<mi::neuraylib::IScope> scope(database->get_global_scope());
mi::base::Handle<mi::neuraylib::ITransaction> transaction(scope->create_transaction());
if (!module_names.empty())
load_materials_from_modules(mdl_factory.get(), transaction.get(), mdl_impexp_api.get(), module_names, material_names);
if (material_names.empty())
for (const auto& m : material_names)
// split module and material name
std::string module_qualified_name, material_simple_name;
if (!mi::examples::mdl::parse_cmd_argument_material_name(
m, module_qualified_name, material_simple_name, true))
// Create an execution context
// Load mdl module and create a material instance
// Compile the material instance
compile_material_instance(instance.get(), context.get(), false));
if (dump_original_material) {
std::cout << "[[ Original material: " << m << " ]]\n";
// Acquire distilling API used for material distilling and baking
if (!distilling_api.is_valid_interface()) {
exit_failure("Failed to obtain distiller component.");
// Distill compiled material to "mini_glossy" material model
if (dump_distilled_material) {
std::cout << "[[ Distilled material: " << m << " (distilled to " <<
target_model << ") ]]\n";
// Shut down the MDL SDK
if (neuray->shutdown() != 0)
exit_failure("Failed to shutdown the SDK.");
// Unload the MDL SDK
neuray = nullptr;
if (!mi::examples::mdl::unload())
exit_failure("Failed to unload the SDK.");
// Convert command line arguments to UTF8 on Windows
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
This interface represents a compiled material.
Definition: icompiled_material.h:97
This interface is used to interact with the distributed database.
Definition: idatabase.h:289
This interface represents a function call.
Definition: ifunction_call.h:52
This interface represents a function definition.
Definition: ifunction_definition.h:44
This interface represents a material instance.
Definition: imaterial_instance.h:34
Selects class compilation instead of instance compilation.
Definition: imaterial_instance.h:41
Default compilation options (e.g., instance compilation).
Definition: imaterial_instance.h:40
Provides access to various functionality related to MDL distilling.
Definition: imdl_distiller_api.h:47
virtual ICompiled_material * distill_material(const ICompiled_material *material, const char *target, const IMap *distiller_options=0, Sint32 *errors=0) const =0
Distills a material.
The execution context can be used to query status information like error and warning messages concern...
Definition: imdl_execution_context.h:131
Factory for various MDL interfaces and functions.
Definition: imdl_factory.h:53
virtual IMdl_execution_context * create_execution_context()=0
Creates an execution context.
virtual const IString * get_db_module_name(const char *mdl_name)=0
Returns the DB name for the MDL name of a module (or file path for MDLE modules).
API component for MDL related import and export operations.
Definition: imdl_impexp_api.h:43
virtual Sint32 load_module(ITransaction *transaction, const char *argument, IMdl_execution_context *context=0)=0
Loads an MDL module from disk (or a builtin module) into the database.
This interface represents an MDL module.
Definition: imodule.h:634
A transaction provides a consistent view on the database.
Definition: itransaction.h:84
virtual const base::IInterface * access(const char *name)=0
Retrieves an element from the database.
virtual Sint32 commit()=0
Commits the transaction.
The operating system specific default filename extension for shared libraries (DLLs)
Definition: config.h:340
virtual const IInterface * get_interface(const Uuid &interface_id) const =0
Acquires a const interface from another.
unsigned int Uint32
32-bit unsigned integer.
Definition: types.h:49
Uint64 Size
Unsigned integral type that is large enough to hold the size of all types.
Definition: types.h:112
signed int Sint32
32-bit signed integer.
Definition: types.h:46

Example Source for Distiller Plugin

Source Code Location: examples/mdl_sdk/distilling_target/distilling_target_plugin.cpp

* Copyright 2024 NVIDIA Corporation. All rights reserved.
#include "pch.h"
#include <string>
#include <iostream>
#include "distilling_target_plugin.h"
// This header is generated from distilling_target_plugin_rules.mdltl
// and contains the declaration of the matcher classes that are used
// in method 'distill()' below.
#include "distilling_target_plugin_rules.h"
namespace MI {
namespace DIST {
// Supported distilling targets in this plugin
// Note: these need to match the switch( target_index) statement below
static const char* s_targets[] = {
template<typename T, size_t n>
inline size_t dimension_of(T (&c)[n]) { return n; }
// Global logger
// Support function to log messages. Skips if no logger is available
void log( mi::base::Message_severity severity, const char* message)
if( g_logger.is_valid_interface())
g_logger->message( severity, "DISTILLER:COMPILER", message);
bool Distilling_target_plugin::init( mi::base::ILogger* logger) {
g_logger = mi::base::make_handle_dup(logger);
std::string message = "Plugin \"";
message += "\" initialized";
return true;
bool Distilling_target_plugin::exit() {
g_logger = 0;
return true;
mi::Size Distilling_target_plugin::get_target_count() const {
return dimension_of( s_targets);
const char* Distilling_target_plugin::get_target_name( mi::Size index) const {
return (index < dimension_of( s_targets)) ? s_targets[index] : nullptr;
mi::Size Distilling_target_plugin::get_required_module_count(mi::Size target_index) const {
return 0;
const char *Distilling_target_plugin::get_required_module_code(mi::Size target_index, mi::Size index) const {
return nullptr;
const char *Distilling_target_plugin::get_required_module_name(mi::Size target_index, mi::Size index) const {
return nullptr;
const mi::mdl::IMaterial_instance* Distilling_target_plugin::distill(
const mi::mdl::IMaterial_instance* material_instance,
mi::Size target_index,
mi::Sint32* p_error) const
// Switch from error pointer to reference to simplify later code for the case of p_error == 0.
mi::Sint32 dummy;
mi::Sint32 &error = p_error != NULL ? *p_error : dummy;
error = 0;
// Check the preconditions on the input parameters
if ( (!material_instance) || (!options) || (target_index >= get_target_count())) {
error = -3;
return nullptr;
#define CHECK_RESULT if(error != 0) { return NULL; }
// Note: the case numbers must match the cardinal order of targets in s_targets above
switch ( target_index) {
case 0: // "mini_glossy"
log(mi::base::MESSAGE_SEVERITY_INFO, "Distilling to target 'mini_glossy'.");
res = mi::base::make_handle_dup(material_instance);
Make_simple_rules make_simple;
res = api.apply_rules( res.get(), make_simple, event_handler, options, error);
} // end switch
if (res.is_valid_interface()) {
return res.get();
error = -3;
return nullptr;
// Factory to create an instance of CUstom_distilling_target.
extern "C"
//mi::base::Plugin* mi_plugin_factory(
void* mi_plugin_factory(
mi::Sint32 index, // Index of the plugin. We only allow index 0.
void* context) // Context given to the library, distiller plugins can ignore it.
#if 1
if( index > 0)
return 0;
return new Distilling_target_plugin();
return 0;
#endif // 0
} // namespace DIST
} // namespace MI
The ILogger interface class supports logging of messages.
Definition: ilogger.h:194
Options class to hold all parameters for algorithm and rule customizations.
Definition: mdl_distiller_options.h:15
The rule engine handles the transformation of a compiled material by a rule set.
Definition: mdl_distiller_plugin_api.h:30
virtual IMaterial_instance * apply_rules(IMaterial_instance const *inst, IRule_matcher &matcher, IRule_matcher_event *event_handler, const Distiller_options *options, mi::Sint32 &error)=0
Apply rules using a strategy.
An instantiated material.
Definition: mdl_generated_dag.h:1383
An interface for reporting rule matcher events.
Definition: mdl_distiller_rules.h:139
Handle<Interface> make_handle_dup(Interface *iptr)
Converts passed-in interface pointer to a handle, without taking interface over.
Definition: handle.h:439
Interface * get() const
Access to the interface. Returns 0 for an invalid interface.
Definition: handle.h:294
bool is_valid_interface() const
Returns true if the interface is valid.
Definition: handle.h:291
Constants for possible message severities.
Definition: enums.h:31
std::basic_ostream<C, T> & error(std::basic_ostream<C, T> &ostream)
Manipulator for mi::base::Log_stream.
Definition: ilogger.h:542
virtual void message(Message_severity level, const char *module_category, const char *message)
Emits a message to the application's log.
Definition: ilogger.h:215
This is a normal operational message.
Definition: enums.h:39
Color log(const Color &c)
Returns a color with elementwise natural logarithm of the color c.
Definition: color.h:689
Logger interface class that supports message logging.

Example Source for Distiller Plugin rules

Source Code Location: examples/mdl_sdk/distilling_target/distilling_target_plugin_rules.mtldl

* Copyright 2024 NVIDIA Corporation. All rights reserved.
/// \file distilling_target_plugin_rules.mdltl
/// \brief Rule sets for the distilling custom target rules example.
// Rules to reduce an MDL expression to a 'simple' subset of
// distribution functions called 'mini_glossy'.
rules Make_simple_rules topdown { // bottomup would work too
import math;
// Alternative BSDFs for glossy interactions are replaced by a
// simple glossy BSDF.
microfacet_beckmann_smith_bsdf(ru,rv,tint,_,t,mode) -->
simple_glossy_bsdf( ru,rv,tint,color(0.0),t,mode) repeat_rules;
microfacet_beckmann_vcavities_bsdf(ru,rv,tint,_,t,mode) -->
simple_glossy_bsdf( ru,rv,tint,color(0.0),t,mode) repeat_rules;
microfacet_ggx_smith_bsdf(ru,rv,tint,_,t,mode) -->
simple_glossy_bsdf( ru,rv,tint,color(0.0),t,mode) repeat_rules;
microfacet_ggx_vcavities_bsdf(ru,rv,tint,_,t,mode) -->
simple_glossy_bsdf( ru,rv,tint,color(0.0),t,mode) repeat_rules;
ward_geisler_moroder_bsdf(ru,rv,tint,_,t) -->
simple_glossy_bsdf( ru,rv,tint,color(0.0),t) repeat_rules;
backscattering_glossy_reflection_bsdf(ru,rv,tint,_,t,handle) -->
sheen_bsdf(r,tint,tintt,_,handle) -->
// Measured BSDF nodes are replaced by an invalid bsdf()
measured_bsdf(_) --> bsdf();
// Thin-film modifier is simply removed.
thin_film(_,_,base) --> base repeat_rules;
// Directional factor BSDFs are replaced by a fresnel layer (or
// just the base BSDF), using a helper function from the
// ::nvidia::distilling_support module for the IOR.
bsdf_directional_factor(tint_n,tint_g,_,base) -->
fresnel_layer( nvidia::distilling_support::float_ior_from_refl(tint_n), 1.0,
bsdf_tint( tint_g, base), bsdf());
fresnel_factor(ior,k,base) -->
// Measured curve factor and measured factor BSDFs are removed.
measured_curve_factor(_,base) --> base repeat_rules;
measured_factor(_,base) --> base repeat_rules;
// Mixers are simplified to normalized mixers.
bsdf_clamped_mix_1(w1,df1) --> bsdf_mix_1(w1,df1);
bsdf_clamped_mix_2(w1,df1,w2,df2) --> bsdf_mix_2(w1,df1,w2,df2);
bsdf_clamped_mix_3(w1,df1,w2,df2,w3,df3) --> bsdf_mix_3(w1,df1,w2,df2,w3,df3);
bsdf_unbounded_mix_1(w1,df1) --> bsdf_mix_1(w1,df1);
bsdf_unbounded_mix_2(w1,df1,w2,df2) --> bsdf_mix_2(w1,df1,w2,df2);
bsdf_unbounded_mix_3(w1,df1,w2,df2,w3,df3) --> bsdf_mix_3(w1,df1,w2,df2,w3,df3);
bsdf_color_clamped_mix_1(w1,df1) --> bsdf_color_mix_1(w1,df1);
bsdf_color_clamped_mix_2(w1,df1,w2,df2) --> bsdf_color_mix_2(w1,df1,w2,df2);
bsdf_color_clamped_mix_3(w1,df1,w2,df2,w3,df3) --> bsdf_color_mix_3(w1,df1,w2,df2,w3,df3);
bsdf_color_unbounded_mix_1(w1,df1) --> bsdf_color_mix_1(w1,df1);
bsdf_color_unbounded_mix_2(w1,df1,w2,df2) --> bsdf_color_mix_2(w1,df1,w2,df2);
bsdf_color_unbounded_mix_3(w1,df1,w2,df2,w3,df3) --> bsdf_color_mix_3(w1,df1,w2,df2,w3,df3);
// Curve layers are also reduced to more simple constructions
// using tint BSDFs, fresnel layers and calls to helper functions.
custom_curve_layer(refl_n,refl_g,_,w,layer,base,n) -->
fresnel_layer( nvidia::distilling_support::float_ior_from_refl(refl_n), w,
bsdf_tint( color(refl_g), layer), base, n);
color_custom_curve_layer(refl_n,refl_g,_,w,layer,base,n) -->
color_fresnel_layer( nvidia::distilling_support::ior_from_refl(refl_n), w,
bsdf_tint( refl_g, layer), base, n);
measured_curve_layer(_,w,layer,base,n) -->
fresnel_layer( 1.5, w, layer, base, n);
color_measured_curve_layer(_,w,layer,base,n) -->
color_fresnel_layer( color(1.5), w, layer, base, n);
[Previous] [Up] [Next]