MDL SDK API nvidia_logo_transpbg.gif Up
Example for building of new MDL Modules
[Previous] [Up] [Next]

This example builds a new MDL module from scratch

New Topics

  • Creation of a simple diffuse material
  • Creation of a material variant
  • Using the uniform analysis
  • Creation and usage of user-defined enum and struct types

Detailed Description

Creation of a simple diffuse material


To define a new material, one needs to specify at least the parameters and the body of the material. Since the body typically references the parameters, it is recommended to start with the parameters, and then to create the body bottom-up in a post-order graph traversal. Here we create a single color parameter with name "tint".

The body of the material consists of three chained function calls: df::diffuse_reflection_bsdf(), material_surface(), and material(). Note how the argument for df::diffuse_reflection_bsdf() references the "tint" parameter of the material. Each of the first two function call is used as argument to the following function call.

Finally, the intended name of the new material, as well as its parameters and the body, are passed to mi::neuraylib::IMdl_module_builder::add_function().

Creation of a material variant


To define a variant, one needs to specify the prototype and the (new) defaults. Since the material in the first step does not have any default, the new default is required for this variant here. In addition, we define an optional annotation for the variant itself.

Finally, the intended name of the new variant, as well as the prototype, the defaults, and the annotations are passed to mi::neuraylib::IMdl_module_builder::add_variant().

Using the uniform analysis


The graph structure of the body of a function or material might require certain parameters to be uniform in order to obtain valid MDL module. Since these constraints might not be obvious, the uniform analysis can be used to obtain such information.

Here, the "across_materials" parameter is created without modifiers first, and the information from the uniform analysis is used to add the uniform modifier before the material is actually added.

Creation and usage of user-defined enum and struct types


First, a new enum type with two enumerators and a new struct type with two int fields is created.

Afterwards, we create a function that makes use of these user-defined types. Note the conversion from the enum type to int since there are no comparison operators for user-defined types. Also note the accesses to the field values of the struct type. Next, the expressions for the condition, for the true expression, and for the false expression of the ternary operator are created. Finally, these expressions are passed as arguments to the ternary operator which represents the root of the function body.

Example Source

Source Code Location: examples/mdl_sdk/create_module/example_create_module.cpp

/******************************************************************************
* Copyright 2024 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/create_module/example_create_module.cpp
//
// Create a new MDL module
#include <iostream>
#include <string>
#include "example_shared.h"
const char* description)
{
ef->create_constant( arg_value.get()));
check_success( 0 == args->add_expression( "description", arg_expr.get()));
ef->create_annotation( "::anno::description(string)", args.get()));
annotation_block->add_annotation( annotation.get());
annotation_block->retain();
return annotation_block.get();
}
void create_module(
{
mdl_factory->create_type_factory( transaction));
mdl_factory->create_value_factory( transaction));
mdl_factory->create_expression_factory( transaction));
mdl_factory->create_execution_context());
// Load modules.
check_success( mdl_impexp_api->load_module(
transaction, "::df", context.get()) >= 0);
// Create the module builder.
//
// We start building a module "::new_module" that does not yet exist in the DB.
mdl_factory->create_module_builder(
transaction,
"mdl::new_module",
context.get()));
{
// (1) Create a simple diffuse material.
//
// export material diffuse_material(color tint)
// = material(
// surface: material_surface(scattering: df::diffuse_reflection_bsdf(tint: tint))
// );
// Create parameters.
mi::base::Handle<mi::neuraylib::IType_list> parameters( tf->create_type_list());
parameters->add_type( "tint", tint_type.get());
// Create body.
ef->create_parameter( tint_type.get(), 0));
drb_args->add_expression( "tint", drb_tint.get());
"mdl::df::diffuse_reflection_bsdf(color,float,string)", drb_args.get()));
surface_args->add_expression( "scattering", drb.get());
"mdl::material_surface(bsdf,material_emission)", surface_args.get()));
body_args->add_expression( "surface", surface.get());
"mdl::material(bool,material_surface,material_surface,color,material_volume,"
"material_geometry,hair_bsdf)", body_args.get()));
// Add the material to the module.
mi::Sint32 result = module_builder->add_function(
"diffuse_material",
body.get(),
/*temporaries*/ nullptr,
parameters.get(),
/*defaults*/ nullptr,
/*parameter_annotations*/ nullptr,
/*annotations*/ nullptr,
/*return_annotations*/ nullptr,
/*is_exported*/ true,
/*is_declarative*/ true,
/*frequency_qualifier*/ mi::neuraylib::IType::MK_NONE,
context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
}
{
// (2) Create a simple red material as a variant of "diffuse_material".
//
// export material red_material(*) [[ anno::description("A diffuse red material") ]]
// = diffuse_material(tint: color(1.f, 0.f, 0.f));
// Create defaults.
vf->create_color( 1.0f, 0.0f, 0.0f));
ef->create_constant( tint_value.get()));
defaults->add_expression( "tint", tint_expr.get());
// Create annotations.
create_annotations( vf.get(), ef.get(), "A diffuse red material"));
// Add the variant to the module.
mi::Sint32 result = module_builder->add_variant(
"red_material",
"mdl::new_module::diffuse_material(color)",
defaults.get(),
annotations.get(),
/*return_annotations*/ nullptr,
/*is_exported*/ true,
/*is_declarative*/ true,
context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
}
// Re-create the module builder.
//
// We could continue to use the existing module builder, but for demonstration purposes we
// ignore how "::new_module" was created and show now how to use the module builder to edit a
// module that exists already in the DB.
module_builder = mdl_factory->create_module_builder(
transaction,
"mdl::new_module",
context.get());
{
// (3) Use the uniform analysis to figure out required uniform modifiers of parameters.
//
// export material uniform_parameter(uniform bool across_materials)
// = diffuse_material(
// tint: state::rounded_corner_normal(0.f, across_materials, 1.f)
// );
// Create parameters (without uniform modifier at first).
mi::base::Handle<const mi::neuraylib::IType> across_materials_type( tf->create_bool());
mi::base::Handle<mi::neuraylib::IType_list> parameters( tf->create_type_list());
parameters->add_type( "across_materials", across_materials_type.get());
// Create body.
ef->create_parameter( across_materials_type.get(), 0));
rcn_args->add_expression( "across_materials", across_materials_expr.get());
ef->create_direct_call( "mdl::state::rounded_corner_normal$1.2(float,bool)",
rcn_args.get()));
color_args->add_expression( "rgb", rcn.get());
ef->create_direct_call( "mdl::color(float3)", color_args.get()));
body_args->add_expression( "tint", color.get());
ef->create_direct_call( "mdl::new_module::diffuse_material(color)", body_args.get()));
// Run uniform analysis.
mi::base::Handle<const mi::IArray> uniform( module_builder->analyze_uniform(
body.get(), /*root_uniform*/ false, context.get()));
print_messages( context.get());
check_success( context->get_error_messages_count() == 0);
// Adapt parameter types based on uniform analysis.
mi::base::Handle<mi::neuraylib::IType_list> fixed_parameters( tf->create_type_list());
for( mi::Size i = 0, n = uniform->get_length(); i < n; ++i) {
mi::base::Handle<const mi::neuraylib::IType> parameter( parameters->get_type( i));
mi::base::Handle<const mi::IBoolean> element( uniform->get_element<mi::IBoolean>( i));
if( element->get_value<bool>()) {
check_success( i == 0);
parameter = tf->create_alias(
parameter.get(), mi::neuraylib::IType::MK_UNIFORM, /*symbol*/ nullptr);
} else {
check_success( i != 0);
}
const char* name = parameters->get_name( i);
fixed_parameters->add_type( name, parameter.get());
}
parameters = fixed_parameters;
// Add the material to the module.
mi::Sint32 result = module_builder->add_function(
"uniform_parameter",
body.get(),
/*temporaries*/ nullptr,
parameters.get(),
/*defaults*/ nullptr,
/*parameter_annotations*/ nullptr,
/*annotations*/ nullptr,
/*return_annotations*/ nullptr,
/*is_exported*/ true,
/*is_declarative*/ true,
/*frequency_qualifier*/ mi::neuraylib::IType::MK_NONE,
context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
}
{
// (4a) Create two new types.
//
// export enum Enum { Add = 0, Sub = 1 };
// export struct Struct { int x; int y; };
ef->create_constant( add_value.get()));
ef->create_constant( sub_value.get()));
enumerators->add_expression( "Add", add_expr.get());
enumerators->add_expression( "Sub", sub_expr.get());
mi::Sint32 result = module_builder->add_enum_type(
"Enum",
enumerators.get(),
/*enumerator_annotations*/ nullptr,
/*annotations*/ nullptr,
/*is_exported*/ true,
context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
mi::base::Handle<mi::neuraylib::IType_list> fields( tf->create_type_list());
fields->add_type( "x", x.get());
fields->add_type( "y", y.get());
result = module_builder->add_struct_type(
"Struct",
fields.get(),
/*field_defaults*/ nullptr,
/*field_annotations*/ nullptr,
/*annotations*/ nullptr,
/*is_exported*/ true,
/*is_declarative*/ false,
/*struct_category*/ nullptr,
context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
// (4b) Create a function that operates on these types.
//
// export int types(Enum e, Struct s) { return e == ADD ? s.x + s.y : s.x - s.y; }
//
// Since there are no comparison operators for user-defined types, we actually compute
// "int(e) == 0" instead of "e == ADD".
// Create parameters
tf->create_enum( "::new_module::Enum"));
tf->create_struct( "::new_module::Struct"));
mi::base::Handle<mi::neuraylib::IType_list> parameters( tf->create_type_list());
parameters->add_type( "e", e_type.get());
parameters->add_type( "s", s_type.get());
// Create body.
ef->create_parameter( e_type.get(), 0));
ef->create_parameter( s_type.get(), 1));
e_int_args->add_expression( "x", parameter_e.get());
ef->create_direct_call( "mdl::new_module::int(::new_module::Enum)", e_int_args.get()));
struct_selector_args->add_expression( "s", parameter_s.get());
"mdl::new_module::Struct.x(::new_module::Struct)", struct_selector_args.get()));
"mdl::new_module::Struct.y(::new_module::Struct)", struct_selector_args.get()));
ef->create_constant( equal_y_value.get()));
equal_args->add_expression( "x", e_int.get());
equal_args->add_expression( "y", equal_y_expr.get());
ef->create_direct_call( "mdl::operator==(int,int)", equal_args.get()));
add_args->add_expression( "x", s_x.get());
add_args->add_expression( "y", s_y.get());
ef->create_direct_call( "mdl::operator+(int,int)", add_args.get()));
sub_args->add_expression( "x", s_x.get());
sub_args->add_expression( "y", s_y.get());
ef->create_direct_call( "mdl::operator-(int,int)", sub_args.get()));
body_args->add_expression( "cond", equal.get());
body_args->add_expression( "true_exp", add.get());
body_args->add_expression( "false_exp", sub.get());
ef->create_direct_call( "mdl::operator%3F(bool,%3C0%3E,%3C0%3E)", body_args.get()));
// Add the function to the module.
result = module_builder->add_function(
"types",
body.get(),
/*temporaries*/ nullptr,
parameters.get(),
/*defaults*/ nullptr,
/*parameter_annotations*/ nullptr,
/*annotations*/ nullptr,
/*return_annotations*/ nullptr,
/*is_exported*/ true,
/*is_declarative*/ false,
/*frequency_qualifier*/ mi::neuraylib::IType::MK_NONE,
context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
}
// Print the exported MDL source code to the console.
mi::base::Handle<mi::IString> module_source( transaction->create<mi::IString>( "String"));
mi::Sint32 result = mdl_impexp_api->export_module_to_string(
transaction, "mdl::new_module", module_source.get(), context.get());
print_messages( context.get());
check_success( result == 0);
check_success( context->get_error_messages_count() == 0);
std::cerr << module_source->get_c_str();
}
int MAIN_UTF8( int argc, char* argv[])
{
// 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(), /*mdl_paths=*/{}))
exit_failure( "Failed to initialize the SDK.");
{
// Start the MDL SDK
mi::Sint32 result = neuray->start();
if( result != 0)
exit_failure( "Failed to initialize the SDK. Result code: %d", result);
neuray->get_api_component<mi::neuraylib::IMdl_factory>());
neuray->get_api_component<mi::neuraylib::IMdl_impexp_api>());
// Access the database and create a transaction.
neuray->get_api_component<mi::neuraylib::IDatabase>());
mi::base::Handle<mi::neuraylib::IScope> scope( database->get_global_scope());
mi::base::Handle<mi::neuraylib::ITransaction> transaction( scope->create_transaction());
create_module( transaction.get(), mdl_impexp_api.get(), mdl_factory.get());
transaction->commit();
}
// Shut down the MDL SDK
check_success( neuray->shutdown() == 0);
neuray = 0;
// Unload the MDL SDK
neuray = nullptr;
if( !mi::examples::mdl::unload())
exit_failure( "Failed to unload the SDK.");
exit_success();
}
// Convert command line arguments to UTF8 on Windows
COMMANDLINE_TO_UTF8
This interface represents bool.
Definition: inumber.h:122
A simple string class.
Definition: istring.h:22
Handle class template for interfaces, automatizing the lifetime control via reference counting.
Definition: handle.h:113
An annotation block is an array of annotations.
Definition: iexpression.h:575
This interface is used to interact with the distributed database.
Definition: idatabase.h:289
The interface for creating expressions.
Definition: iexpression.h:650
virtual IExpression_parameter * create_parameter(const IType *type, Size index) const =0
Creates a parameter reference.
virtual IExpression_list * create_expression_list() const =0
Creates a new expression list.
virtual IAnnotation * create_annotation(const char *name, const IExpression_list *arguments) const =0
Creates a new annotation.
virtual IAnnotation_block * create_annotation_block() const =0
Creates a new annotation block.
virtual IExpression_direct_call * create_direct_call(const char *name, IExpression_list *arguments, Sint32 *errors=0) const =0
Creates a direct call.
virtual IExpression_constant * create_constant(IValue *value) const =0
Creates a constant (mutable).
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 IMdl_module_builder * create_module_builder(ITransaction *transaction, const char *module_name, Mdl_version min_module_version, Mdl_version max_module_version, IMdl_execution_context *context)=0
Creates a module builder for a given module.
virtual IType_factory * create_type_factory(ITransaction *transaction)=0
Returns an MDL type factory for the given transaction.
virtual IExpression_factory * create_expression_factory(ITransaction *transaction)=0
Returns an MDL expression factory for the given transaction.
virtual IValue_factory * create_value_factory(ITransaction *transaction)=0
Returns an MDL value factory for the given transaction.
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.
virtual Sint32 export_module_to_string(ITransaction *transaction, const char *module_name, IString *exported_module, IMdl_execution_context *context=0)=0
Exports an MDL module from the database to string.
A transaction provides a consistent view on the database.
Definition: itransaction.h:84
virtual base::IInterface * create(const char *type_name, Uint32 argc=0, const base::IInterface *argv[]=0)=0
Creates an object of the type type_name.
virtual Sint32 commit()=0
Commits the transaction.
@ MK_UNIFORM
A uniform type.
Definition: itype.h:200
@ MK_NONE
No type modifier (mutable, auto-typed).
Definition: itype.h:199
The interface for creating values.
Definition: ivalue.h:660
virtual IValue_string * create_string(const char *value="") const =0
Creates a new value of type string.
virtual IValue_int * create_int(Sint32 value=0) const =0
Creates a new value of type integer.
virtual IValue_color * create_color(Float32 red=0.0f, Float32 green=0.0f, Float32 blue=0.0f) const =0
Creates a new value of type color.
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
@ MDL_VERSION_1_0
MDL version 1.0.
Definition: iexpression.h:29
@ MDL_VERSION_LATEST
Latest MDL version.
Definition: iexpression.h:40
[Previous] [Up] [Next]