Unresolved resources invalidated after call to IMdl_module_builder::add_function

Hi,

I’m trying to create a new material function by cloning an existing material function definition. I can generate the new function body by walking the argument trees of the source function (IFunction_definition::get_body()->get_arguments()). This appears to generate a valid set of arguments for the new function call but after calling IMdl_module_builder::add_function, any unresolved resource values (ie body textures) are getting squashed to invalid resource references. At a high level the code looks like this:

mi::base::Handle<const mi::neuraylib::IExpression> sourceBody(sourceDefinition->get_body());
mi::base::Handle<const mi::neuraylib::IExpression_direct_call> sourceBodyDC(sourceBody->get_interface<mi::neuraylib::IExpression_direct_call>());
mi::base::Handle<const mi::neuraylib::IExpression_list> sourceArgs(sourceBodyDC->get_arguments());
mi::base::Handle<mi::neuraylib::IExpression_list> newBodyArgs(CloneExpression(sourceArgs));
mi::base::Handle<mi::neuraylib::IExpression> newBodyDC(ef->create_direct_call(sourceBodyDC->get_definition(), newBodyArgs.get()));
moduleBuilder->add_function(newName, newBodyDC.get(), ...);

When accessing the newly created function I see all the body resources are now invalid references. I think it’s happening because the AST builder can’t handle unresolved resources (MDL-SDK/src/io/scene/mdl_elements/mdl_elements_ast_builder.cpp at 203d5140b1dee89de17b26e828c4333571878629 · NVIDIA/MDL-SDK · GitHub)? With that theory I tried to find a way to resolve an IValue_resource to a DB element but as far as I can see I can only get the file path. Can I get the DB name from the file path? Is there a better way to clone a material (eventually I’ll be injecting some code)?

Cheers,
Justin

Hi Justin,

where do these unresolved resources come from? Didn’t you get any errors/warnings when you loaded the module that contains the material you are trying to clone? In that case it would be best to load the module correctly, instead of working around the consequences. Various functionality will not work correctly with unresolved resources.

Best regards,
Joachim

Hi Joachim,

The unresolved resource references are coming from resource references within the body of a material. For example, the teak_natural_matte vMaterial has a material definition like this:

export material teak_natural_matte(....)
= nvidia::core_definitions::apply_clearcoat(
     base: nvidia::core_definitions::flex_material(
       base_color: nvidia::core_definitions::blend_colors(
         nvidia::core_definitions::blend_colors(
            nvidia::core_definitions::file_texture(texture_2d("../textures/teak_natural_col.png", ::tex::gamma_default), base::mono_average, 1.f, 1.f, float2(1.f / texture_scale.x * 1.20000005f, 1.f / texture_scale.y * 1.20000005f), texture_translate, texture_rotate, false, uv_space_index, false).tint,
            nvidia::core_definitions::file_texture(wood_brightness - 1.f <= 0.f ? texture_2d("../textures/teak_natural_col.png", ::tex::gamma_default) : texture_2d("../textures/teak_spec.png", ::tex::gamma_default), base::mono_average, 1.f, 1.f, float2(1.f / texture_scale.x * 1.20000005f, 1.f / texture_scale.y * 1.20000005f), texture_translate, texture_rotate, false, uv_space_index, false).tint,
            ....),
          ....),
        ....),
    ....),
);

These texture references are creating constant expressions that look like this:

constant texture_2d true_exp = (unset, owner module "", unresolved MDL file path "/textures/teak_natural_col.png"),
constant texture_2d false_exp = (unset, owner module "", unresolved MDL file path "/textures/teak_spec.png")

The material is loaded like this:

mi::base::Handle<mi::neuraylib::IMdl_impexp_api> ioAPI(GetIOAPI());
mi::base::Handle<mi::neuraylib::IMdl_execution_context> context(CreateExecutionContext());
ioAPI->load_module(transaction.get(), moduleName.c_str(), context.get());
mi::base::Handle<const mi::neuraylib::IModule> module(transaction->access<mi::neuraylib::IModule>(moduleDBName.c_str()));
mi::base::Handle<const mi::neuraylib::IFunction_definition> materialDefinition(transaction->access<mi::neuraylib::IFunction_definition>(materialDBName.c_str()));

I do not see any errors/warnings in the logs when loading the module. If I don’t clone the material and just compile it directly after loading the module the resource references work as expected:

mi::Sint32 ret = 0;
mi::base::Handle<const mi::neuraylib::IFunction_call> materiallCall(materialDefinition->create_function_call(nullptr, &ret));
mi::base::Handle<const mi::neuraylib::IMaterial_instance> materialInstance(materialCall->get_interface<mi::neuraylib::IMaterial_instance>());
mi::base::Handle<mi::neuraylib::ICompiled_material> compiledMaterial(materialInstance->create_compiled_material(flags, context.get()));
transaction->store(compiledMaterial.get(), materialDBName.c_str());

Should I be loading the material/module a different way?

Cheers,
Justin

Looks good in principle. A couple of questions:

  1. Does CreateExecutionContext() set any context options?
  2. Maybe your code does this already and it’s just not shown for brevity: does load_module() return 0 (or 1) and there are no messages in the context?
  3. How do the resources in the compiled material look like (e.g. when using the dump_compiled_material() method from the “compilation” example)?

Does CreateExecutionContext() set any context options?

Yes, we set:

MDL_CG_OPTION_FOLD_METERS_PER_SCENE_UNIT = true
MDL_CG_OPTION_METERS_PER_SCENE_UNIT = 1
MDL_CG_OPTION_INTERNAL_SPACE = "coordinate_world"

I also tried setting MDL_OPTION_RESOLVE_RESOURCES = true, which should be the default already; it didn’t make a difference.

Maybe your code does this already and it’s just not shown for brevity: does load_module() return 0 (or 1) and there are no messages in the context?

Yes we check that the return value is >= 0 and the message count it 0.

How do the resources in the compiled material look like

Compiled materials have resolved resources.

I did some spelunking in MDL code base today and I think I found the issue; resolve_resources is hard coded to false in Mdl_function_definition::get_body. The same happens in Mdl_function_definition::get_temporary. It seems like those functions should use the value of resolve_resources from the constructor? Or perhaps query the execution context?

Ok, your context options are all good. The resolve_resources option should not be set at all (and is not documented for that reason). I was now able to reproduce the issue and the two locations you found with the hard-coded value are indeed the culprit. false was meant as an optimization here (no need to re-resolve), but that has side-effects and is wrong. In your context, the flag will always be true, so you could just replace the hard-coded value as workaround.

A proper fix for this problem will be part of the upcoming 2024.1 release.

Great, thanks for your help Joachim!