Skip to content

rtx: Add MaterialX (.mtlx) material support#348

Merged
jeffamstutz merged 13 commits into
NVIDIA:next_releasefrom
tarcila:materialx
Jul 18, 2026
Merged

rtx: Add MaterialX (.mtlx) material support#348
jeffamstutz merged 13 commits into
NVIDIA:next_releasefrom
tarcila:materialx

Conversation

@tarcila

@tarcila tarcila commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Adds a materialx ANARI material subtype to the VisRTX RTX device
(VISRTX_MATERIAL_MATERIALX extension). A .mtlx document — supplied as a
file path or inline XML — is transcoded to MDL at commit time and rendered
through the existing MDL material path, so MaterialX materials get the full
MDL shading/emission pipeline with no new runtime backend.

The MaterialX standard library is resolved from a system-installed distribution at runtime,
not embedded or shipped (ADR 0008).
TSD gains a thin authoring layer (material token, a StandardSurface preset, a menu entry).

Requires VISRTX_ENABLE_MDL_SUPPORT (MaterialX transcodes to MDL) and gated by
VISRTX_ENABLE_MATERIALX_SUPPORT.

tarcila added 13 commits July 17, 2026 17:23
Registers the materialx subtype and the VISRTX_MATERIAL_MATERIALX
extension. The MaterialX MDL support modules ride the MDL search path —
resolved from the FetchContent source tree for in-tree builds, the
installed libraries/mdl layout otherwise. Commit transcodes the document
to MDL behind a change guard and exposes a materialNames property.
TestMaterialXMaterial compiles with USE_MATERIALX so the advertised
extension flag matches the device build.
…rface builtin

Transcoder emits a clean-name -> generated-MDL-arg map so ANARI parameters
keep their MaterialX names. Bundles standard_surface.mtlx and resolves the
visrtx::standard_surface builtin against it.
materialx material token with a bare-constructor branch, a StandardSurface
preset helper, and MaterialX/StandardSurface entries in the material menu.
The transcoder splices an <image> node in front of each texture-bindable
constant input and pairs it in the paramMap (carrying origin path and type);
ANARI samplers then route through the spliced nodes by pure arg binding, and a
re-commit no longer reverts textured inputs. Only 2D image samplers (image2D /
compressedImage2D) bind — a spliced MaterialX image lowers to an MDL texture_2d
— checked locally at the bind site rather than on the Sampler base.

Also fixes MDL search-path init: the search paths are now applied from initMDL
(via syncMdlSearchPaths), not only from deviceCommitParameters. On the lazy-init
path (app never commits the device) the bundled MaterialX support-module path
was left unset, so the generated MDL failed to compile.
…a render gate

When a document has no renderable element, instantiate its file-local
surfaceshader nodedef. The file is read before stdlib import so
auto-instantiation sees file-local nodedefs.
Transcoder takes a DocumentSource (file or inline) with snapshot
provenance; the documentInline scheme survives when only source is re-set.
The implicit-input materialization (for inputs absent on the node instance, e.g.
the bundled/auto-instantiated standard_surface) looked the input up with
NodeDef::getInput, which only sees DIRECTLY declared inputs. standard_surface's
default nodedef declares just base/base_color directly and inherits the other
~40 (emission_color, metalness, ...) from ND_standard_surface_surfaceshader_100,
so getInput returned null for them, the splice was skipped, and binding a
sampler to such an input failed with 'sampler not bound (image2D textured input
required)'. base_color only worked by being one of the two direct inputs.

Use getActiveInput, which resolves inputs through nodedef inheritance. Adds a
transcoder test binding emission_color (verified to fail before the fix).
Classifies wired filename ports as texture-bindable rows, exempts them
from retranscode topology, and exposes a textureInputs discovery property.
Device-side sampler binding is not implemented yet.
A MaterialX standard_surface transcodes its emission to a coat-over-base EDF —
df::unbounded_mix([edf_component(saturate(coat), directional_factor(diffuse)),
edf_component(1-saturate(coat), diffuse)]) — so the ADR-0007 classifier rejected
it (Directional kind + unknown intensity sign) and synthesized NO Geometry Light.
The emitter then lit receivers only via the forward path, so its NEE pool was
fainter/noisier than the equivalent physicallyBased or raw-MDL emitter.

Four folds let the classifier see through the coat wrapper when coat is 0:
- model math::saturate (nonnegative, in [0,1], zero iff the argument is zero);
- prune a provably-zero-weight df::edf_component from a mix's kind union, so the
  view-dependent coat branch drops at coat=0 and only the diffuse base remains;
- stop descending into a modeled EDF leaf's operands in the mix deep-scan — the
  diffuse_edf handle string is an Opaque node that otherwise poisoned the mix to
  Unknown (now matches the evalEdf leaf path);
- fold a - 0 = a in combineSub, so the 1 - saturate(coat) layer weight resolves
  to the constant 1 and the intensity sign is ProvablyNonnegative.

Pruning keys on the arg-block value, so a genuinely coated emitter (coat>0) keeps
the view-dependent branch and stays unregistered (unfaithful), per the
miss=variance / over-register=bias policy.

Also classify the textured emitter: MaterialX's <image> node lowers to a
materialx::stdlib_*::mx_image_* wrapper (a tex::lookup with an invalid-texture
default fallback), NOT a bare tex::lookup intrinsic — so it carries no
DS_INTRINSIC_TEX_LOOKUP semantic and the emission IR saw it as an opaque call.
Recognize the mx_image_* family by name and treat it as a texture lookup whose
texture is the mxp_file argument. The DB-name match is sound here (unlike
EDF-leaf recognition, which stays semantics-only): a texture lookup only
supplies a Pick-Power mean, so a misrecognized or unbound lookup folds to zero
— variance, never bias.
Replace the compile-baked MaterialX paths with a runtime search chain:
materialxSearchPaths device param -> MATERIALX_SEARCH_PATH ->
mx::getDefaultDataSearchPath() -> compile-time last resort. The MDL
implementation modules dir derives from the winning root.

Delete the visrtx::standard_surface builtin alias and the shipped
standard_surface.mtlx: applications author the instantiation inline
(TSD preset does), the nodedef comes from the distribution.
@tarcila
tarcila requested a review from jeffamstutz July 17, 2026 22:53
@jeffamstutz
jeffamstutz merged commit a58cf33 into NVIDIA:next_release Jul 18, 2026
20 checks passed
@tarcila
tarcila deleted the materialx branch July 18, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants