Material Definition Language API nvidia_logo_transpbg.gif Up
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Example for Compilation of MDL Materials
[Previous] [Up] [Next]

This example introduces compiled materials and highlights differences between different compilation modes.

New Topics

  • Compiled materials
  • Different compilation modes
  • Target code generation

Detailed Description

Compiled materials


A compiled material is a compact, optimized representation of a material instance including all call expressions in its arguments. In this optimization step call expressions for arguments are inlined (similar to function calls in programming languages), constant expressions are folded, common subexpression elimination is taking place, etc.

Different compilation modes


Please refer to Instance-compilation and class-compilation for an introduction into instance and class compilation modes.

In the output of this example, compare the difference between instance and class compilation mode of the initially created material instance. In class compilation mode, the expression for "surface.scattering.tint" is no longer a constant, but a parameter reference.

Now compare the changes in both representations when an argument like "tint" is changed. In instance compilation mode, the constant changes its value accordingly, and consequently, the hash over all temporaries and fields changes, too. In class compilation mode, only the value of argument 0 changes. All temporaries and fields of the compiled material, and consequently the hash value, remain unchanged.

Target code generation


Compiler backends allow to generate code from the abstract representation of compiled materials. There are different backends, e.g., for LLVM IR or CUDA PTX code. These backends can be obtained from mi::neuraylib::IMdl_backend_api::get_backend().

Backends support general and backend-specific options to influence the code generation. See mi::neuraylib::IMdl_backend for details.

Note that target code is not generated for an entire compiled material, but for specific subexpressions of the compiled material, like "surface.scattering.tint".

Example Source

Source Code Location: examples/mdl_sdk/compilation/example_compilation.cpp

/******************************************************************************
* Copyright 2020 NVIDIA Corporation. All rights reserved.
*****************************************************************************/
// examples/mdl_sdk/compilation/example_compilation.cpp
//
// Introduces compiled materials and highlights differences between different compilation modes.
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
#include "example_shared.h"
// Command line options structure.
struct Options {
// Materials to use.
std::string material_name;
// Expression path to compile.
std::string expr_path;
// If true, changes the arguments of the instantiated material.
// Will be set to false if the material name or expression path is changed.
bool change_arguments;
// The constructor.
Options()
: material_name("::nvidia::sdk_examples::tutorials::example_compilation")
, expr_path("backface.scattering.tint")
, change_arguments(true)
{
}
};
// Utility function to dump the hash, arguments, temporaries, and fields of a compiled material.
void dump_compiled_material(
std::ostream& s)
{
mdl_factory->create_value_factory( transaction));
mdl_factory->create_expression_factory( transaction));
mi::base::Uuid hash = cm->get_hash();
char buffer[36];
snprintf( buffer, sizeof( buffer),
"%08x %08x %08x %08x", hash.m_id1, hash.m_id2, hash.m_id3, hash.m_id4);
s << " hash overall = " << buffer << std::endl;
snprintf( buffer, sizeof( buffer),
"%08x %08x %08x %08x", hash.m_id1, hash.m_id2, hash.m_id3, hash.m_id4);
s << " hash slot " << std::setw( 2) << i << " = " << buffer << std::endl;
}
mi::Size parameter_count = cm->get_parameter_count();
for( mi::Size i = 0; i < parameter_count; ++i) {
std::stringstream name;
name << i;
value_factory->dump( argument.get(), name.str().c_str(), 1));
s << " argument " << result->get_c_str() << std::endl;
}
mi::Size temporary_count = cm->get_temporary_count();
for( mi::Size i = 0; i < temporary_count; ++i) {
std::stringstream name;
name << i;
expression_factory->dump( temporary.get(), name.str().c_str(), 1));
s << " temporary " << result->get_c_str() << std::endl;
}
mi::base::Handle<const mi::IString> result( expression_factory->dump( body.get(), 0, 1));
s << " body " << result->get_c_str() << std::endl;
s << std::endl;
}
// Creates an instance of the given material definition and stores it in the DB.
void create_material_instance(
const char* material_name,
const char* instance_name)
{
// split module and material name
std::string module_name, material_simple_name;
if (!mi::examples::mdl::parse_cmd_argument_material_name(
material_name, module_name, material_simple_name, true))
exit_failure();
// Load the module.
mdl_impexp_api->load_module(transaction, module_name.c_str(), context);
if (!print_messages(context))
exit_failure("Loading module '%s' failed.", module_name.c_str());
// Get the database name for the module we loaded
factory->get_db_module_name(module_name.c_str()));
// attach the material name
std::string material_db_name =
std::string(module_db_name->get_c_str()) + "::" + material_simple_name;
// Get the material definition from the database
transaction->access<mi::neuraylib::IMaterial_definition>(material_db_name.c_str()));
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;
material_definition->create_material_instance(0, &result));
if (result != 0)
exit_failure("Instantiating '%s' failed.", material_db_name.c_str());
transaction->store(material_instance.get(), instance_name);
}
// Compiles the given material instance in the given compilation modes, dumps the result, and stores
// it in the DB.
void compile_material_instance(
const char* instance_name,
const char* compiled_material_name,
bool class_compilation)
{
transaction->access<mi::neuraylib::IMaterial_instance>( instance_name));
mi::Uint32 flags = class_compilation
material_instance->create_compiled_material( flags, context));
check_success( print_messages( context));
std::cout << "Dumping compiled material (" << ( class_compilation ? "class" : "instance")
<< " compilation) for \"" << instance_name << "\":" << std::endl << std::endl;
dump_compiled_material( transaction, mdl_factory, compiled_material.get(), std::cout);
std::cout << std::endl;
transaction->store( compiled_material.get(), compiled_material_name);
}
// Changes the tint parameter of the given material instance to green.
void change_arguments(
const char* instance_name)
{
mdl_factory->create_value_factory( transaction));
mdl_factory->create_expression_factory( transaction));
// Edit the instance of the material definition "compilation_material".
transaction->edit<mi::neuraylib::IMaterial_instance>( instance_name));
check_success( material_instance.is_valid_interface());
// Create the new argument for the "tint" parameter from scratch with the new value, and set it.
value_factory->create_color( 0.0f, 1.0f, 0.0f));
expression_factory->create_constant( tint_value.get()));
check_success( material_instance->set_argument( "tint", tint_expr.get()) == 0);
}
// Generates LLVM IR target code for a subexpression of a given compiled material.
void generate_llvm_ir(
const char* compiled_material_name,
const char* path,
const char* fname)
{
transaction->access<mi::neuraylib::ICompiled_material>( compiled_material_name));
check_success(compiled_material.is_valid_interface());
check_success(be_llvm_ir.is_valid_interface());
check_success(be_llvm_ir->set_option( "num_texture_spaces", "16") == 0);
check_success(be_llvm_ir->set_option( "enable_simd", "on") == 0);
be_llvm_ir->translate_material_expression(
transaction, compiled_material.get(), path, fname, context));
check_success(print_messages( context));
check_success(code_llvm_ir);
std::cout << "Dumping LLVM IR code for \"" << path << "\" of \"" << compiled_material_name
<< "\":" << std::endl << std::endl;
std::cout << code_llvm_ir->get_code() << std::endl;
}
// Generates CUDA PTX target code for a subexpression of a given compiled material.
void generate_cuda_ptx(
const char* compiled_material_name,
const char* path,
const char* fname)
{
transaction->access<mi::neuraylib::ICompiled_material>( compiled_material_name));
check_success(compiled_material.is_valid_interface());
check_success(be_cuda_ptx.is_valid_interface());
check_success(be_cuda_ptx->set_option( "num_texture_spaces", "16") == 0);
check_success(be_cuda_ptx->set_option( "sm_version", "50") == 0);
be_cuda_ptx->translate_material_expression(
transaction, compiled_material.get(), path, fname, context));
check_success( print_messages( context));
check_success( code_cuda_ptx);
std::cout << "Dumping CUDA PTX code for \"" << path << "\" of \"" << compiled_material_name
<< "\":" << std::endl << std::endl;
std::cout << code_cuda_ptx->get_code() << std::endl;
}
// Generates HLSL target code for a subexpression of a given compiled material.
void generate_hlsl(
const char* compiled_material_name,
const char* path,
const char* fname)
{
transaction->access<mi::neuraylib::ICompiled_material>( compiled_material_name));
check_success(compiled_material.is_valid_interface());
check_success(be_hlsl.is_valid_interface());
check_success(be_hlsl->set_option( "num_texture_spaces", "1") == 0);
be_hlsl->translate_material_expression(
transaction, compiled_material.get(), path, fname, context));
check_success(print_messages( context));
check_success(code_hlsl);
std::cout << "Dumping HLSL code for \"" << path << "\" of \"" << compiled_material_name
<< "\":" << std::endl << std::endl;
std::cout << code_hlsl->get_code() << std::endl;
}
#ifndef MDL_SOURCE_RELEASE
// Generates GLSL target code for a subexpression of a given compiled material.
void generate_glsl(
const char* compiled_material_name,
const char* path,
const char* fname)
{
transaction->access<mi::neuraylib::ICompiled_material>( compiled_material_name));
check_success(compiled_material.is_valid_interface());
check_success(be_glsl.is_valid_interface());
check_success(be_glsl->set_option( "glsl_version", "450") == 0);
be_glsl->translate_material_expression(
transaction, compiled_material.get(), path, fname, context));
check_success(print_messages( context));
check_success(code_glsl);
std::cout << "Dumping GLSL code for \"" << path << "\" of \"" << compiled_material_name
<< "\":" << std::endl << std::endl;
std::cout << code_glsl->get_code() << std::endl;
}
#endif // MDL_SOURCE_RELEASE
void usage( char const *prog_name)
{
std::cout
<< "Usage: " << prog_name << " [options] [<material_name>]\n"
<< "Options:\n"
<< " --mdl_path <path> mdl search path, can occur multiple times.\n"
<< " --expr_path expression path to compile, defaults to "
"\"backface.scattering.tint\"."
<< " <material_name> qualified name of materials to use, defaults to\n"
<< " \"::nvidia::sdk_examples::tutorials::example_compilation\"\n"
<< std::endl;
exit_failure();
}
int MAIN_UTF8(int argc, char* argv[])
{
// Parse command line options
Options options;
mi::examples::mdl::Configure_options configure_options;
for (int i = 1; i < argc; ++i) {
char const *opt = argv[i];
if (opt[0] == '-') {
if (strcmp(opt, "--mdl_path") == 0 && i < argc - 1) {
configure_options.additional_mdl_paths.push_back(argv[++i]);
}
else if (strcmp(opt, "--expr_path") == 0 && i < argc - 1) {
options.expr_path = argv[++i];
options.change_arguments = false;
}
else {
std::cout << "Unknown option: \"" << opt << "\"" << std::endl;
usage(argv[0]);
}
}
else {
options.material_name = opt;
options.change_arguments = false;
}
}
// 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.");
// Start the MDL SDK
mi::Sint32 ret = neuray->start();
if (ret != 0)
exit_failure("Failed to initialize the SDK. Result code: %d", ret);
{
mi::base::Handle<mi::neuraylib::IScope> scope(database->get_global_scope());
mi::base::Handle<mi::neuraylib::ITransaction> transaction(scope->create_transaction());
{
// Create an execution context for options and error message handling
mdl_factory->create_execution_context());
// Create MDL import-export API for importing MDL modules
// Load the "example" module and create a material instance
std::string instance_name = "instance of compilation_material";
create_material_instance(
mdl_factory.get(),
transaction.get(),
mdl_impexp_api.get(),
context.get(),
options.material_name.c_str(),
instance_name.c_str());
// Compile the material instance in instance compilation mode
std::string instance_compilation_name
= std::string("instance compilation of ") + instance_name;
compile_material_instance(
transaction.get(), mdl_factory.get(), context.get(), instance_name.c_str(),
instance_compilation_name.c_str(), false);
// Compile the material instance in class compilation mode
std::string class_compilation_name
= std::string("class compilation of ") + instance_name;
compile_material_instance(
transaction.get(), mdl_factory.get(), context.get(), instance_name.c_str(),
class_compilation_name.c_str(), true);
// Change some material argument and compile again in both modes. Note the differences
// in instance compilation mode, whereas only the referenced parameter itself changes in
// class compilation mode.
if (options.change_arguments) {
change_arguments(transaction.get(), mdl_factory.get(), instance_name.c_str());
compile_material_instance(
transaction.get(), mdl_factory.get(), context.get(), instance_name.c_str(),
instance_compilation_name.c_str(), false);
compile_material_instance(
transaction.get(), mdl_factory.get(), context.get(), instance_name.c_str(),
class_compilation_name.c_str(), true);
}
// Use the various backends to generate target code for some material expression.
generate_llvm_ir(
transaction.get(), mdl_backend_api.get(), context.get(),
instance_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
generate_llvm_ir(
transaction.get(), mdl_backend_api.get(), context.get(),
class_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
generate_cuda_ptx(
transaction.get(), mdl_backend_api.get(), context.get(),
instance_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
generate_cuda_ptx(
transaction.get(), mdl_backend_api.get(), context.get(),
class_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
#ifndef MDL_SOURCE_RELEASE
generate_glsl(
transaction.get(), mdl_backend_api.get(), context.get(),
instance_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
generate_glsl(
transaction.get(), mdl_backend_api.get(), context.get(),
class_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
#endif /*MDL_SOURCE_RELEASE*/
generate_hlsl(
transaction.get(), mdl_backend_api.get(), context.get(),
instance_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
generate_hlsl(
transaction.get(), mdl_backend_api.get(), context.get(),
class_compilation_name.c_str(),
options.expr_path.c_str(), "tint");
}
transaction->commit();
}
// 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.");
exit_success();
}
// Convert command line arguments to UTF8 on Windows
COMMANDLINE_TO_UTF8
[Previous] [Up] [Next]