Skip to content

Safe math experiment and source code node reimplementation strategy #14

Description

@ppenenko

Overview

A layered strategy for Metashade source code node reimplementation, starting with trivial arithmetic nodes, using the safe math experiment as a forcing function, and connecting to the ASWF generic implementation gap.

Builds on the multi-environment infrastructure from #13.

The Landscape

MaterialX's stdlib has three implementation tiers, each with different Metashade opportunities:

  • 380 inline sourcecode implementations (genglsl) -- trivial expressions like {{in1}} + {{in2}}, duplicated identically across 3 targets (genglsl/genosl/genmdl) and ~16 type variants each. Only ~60 unique expression patterns.
  • 87 file-based source code nodes (listed in source_code_nodes.txt) -- dedicated .glsl/.osl files for BSDFs, noise, image sampling, compositing blends, etc. These have genuinely different implementations per target.
  • 198 nodegraph implementations -- target-independent compositions of simpler nodes. Already generic, no reimplementation needed.

Layer 1: Arithmetic Node Reimplementation (Foundation)

The 380 inline implementations collapse into ~60 unique patterns. The arithmetic subset (add, subtract, multiply, divide, modulo) accounts for 69 NodeDefs across type variants.

Why start here:

  • Identical across all targets -- {{in1}} + {{in2}} is the same in GLSL, MDL, and OSL
  • Metashade already generates these correctly via ArithmeticType._rhs_binary_operator() in metashade/targets/_clike/dtypes.py
  • The existing test_metashade_add_color3 test proves the pattern works end-to-end
  • Serves as the entry point for the safe math hook

Broader trivial set beyond pure arithmetic:

  • abs, floor, ceil, sign, clamp, min, max, pow, sqrt, exp, log -- map to intrinsic functions
  • ifgreater, ifequal, switch -- ternary expressions
  • convert_*, combine*, extract* -- type constructors / swizzles
  • constant, remap, smoothstep -- simple expressions
  • Compositing ops (plus, screen, difference, mix) -- one-liner blending math

Layer 2: Safe Math as a Forcing Function

Safe math is not just a feature -- it's the experiment that proves Metashade can do something MaterialX's sourcecode mechanism cannot: transform operator semantics globally.

The hook architecture

Python: sh.result = sh.a / sh.b
  -> __truediv__
    -> _rhs_binary_operator(op='/')
      -> Safe math hook (configurable)
        -> safe_math=False: "a / b"
        -> safe_math=True:  "safe_div(a, b)"

Injection points in Metashade

  • _rhs_binary_operator(self, rhs, op) in _clike/dtypes.py -- central dispatch for all binary ops on scalars
  • _per_element_or_scalar(self, rhs, op) in _rtsl/dtypes.py -- vector division/multiplication
  • FloatIntrinsicsMixin.pow(), .sqrt() in glsl/_intrinsics.py -- intrinsics with NaN risk

What safe math means concretely

For division:

// Normal:  result = a / b;
// Safe:    result = (abs(b) < 1e-8) ? 0.0 : a / b;
// Or:      result = a / max(abs(b), 1e-8) * sign(b);

For power/sqrt:

// Normal:  result = pow(base, exp);
// Safe:    result = pow(max(base, 0.0), exp);

Connection to multi-environment system (#13)

The safe math experiment becomes a Metashade environment:

contrib/tests/metashade_envs/
  safe_math/
    mx_divide_float_safemath_genglsl_impl.mtlx
    mx_divide_float_safemath_genglsl_impl.glsl
    ...

The divide node is interesting: it needs to replace an inline expression with a function call. The Metashade-generated .glsl file would define the safe_div function, and the .mtlx implementation would reference it. This is a promotion from inline to file-based, which the override mechanism supports since loadLibraries with the environment loaded first will find the file-based implementation before the inline one from stdlib.

Usage:

MaterialXView --library contrib/tests/metashade_envs/safe_math material.mtlx

Layer 3: Source Code Node Reimplementation (Medium-term)

After arithmetic, move to the 87 file-based source code nodes, tiered by complexity:

Tier A: Near-trivial

  • mx_premult_color4, mx_unpremult_color4 -- simple alpha math
  • mx_luminance_color3/4 -- dot product with weights
  • mx_displacement_float/vector3 -- simple scaling
  • mx_rotate_vector2/3 -- rotation matrix math

Tier B: Medium complexity, high value

  • mx_burn_*, mx_dodge_* -- compositing blend modes (division-based, safe math relevant!)
  • mx_ramplr_*, mx_ramptb_*, mx_splitlr_*, mx_splittb_* -- texture coordinate interpolation
  • mx_hsvtorgb_*, mx_rgbtohsv_* -- color space conversion
  • mx_roughness_anisotropy, mx_roughness_dual -- PBR utility

Tier C: Complex, high value

  • BSDFs -- mx_generalized_schlick_bsdf, mx_dielectric_bsdf, mx_conductor_bsdf, mx_oren_nayar_diffuse_bsdf, mx_sheen_bsdf, etc.
  • Noise -- mx_noise2d/3d_*, mx_fractal2d/3d_*, mx_worleynoise2d/3d_*, mx_cellnoise*
  • Image -- mx_image_*, mx_hextiledimage_*

Tier D: adsklib custom nodes

  • noise1d, hashnoise2d, pore_impulse, backface_util -- custom source nodes with manual GLSL/OSL duplication
  • wood3d procedural -- 1600+ line XML nodegraph
  • Legacy procedurals -- 5500+ lines of XML

Layer 4: The Generic Implementation Story (ASWF)

Current ASWF state

  • #2148 -- Templates for NodeDef verbosity (interface genericity)
  • PR #2362 -- Build-time template expansion (merged, interface-only)
  • #2549 -- Formal spec for template syntax
  • #2355 -- Functional node definitions (nodegraph-in-nodedef)
  • genmsl and genslang -- only 39 and 31 implementations respectively, mostly reusing genglsl files

The gap Metashade fills

The community solved interface genericity (templates) but explicitly punted on implementation genericity. Metashade is uniquely positioned:

  • One Python source generates correct GLSL, HLSL, OSL, MDL (and potentially WGSL, MSL, Slang)
  • Unlike ASWF templates which are syntactic macros, Metashade has semantic awareness -- it knows that color3 / float and float / float share logic but matrix44 / matrix44 needs mx_matrix_mul(mx_inverse(...))
  • The safe math hook demonstrates behavioral genericity -- same safety guarantee across all targets from one place

The data tells a compelling story

  • 380 inline implementations x 3 targets = ~1140 lines of duplicated XML for ~60 unique patterns
  • Metashade reimplements all 60 patterns from a single Python codebase
  • Safe math, half-precision, and optimization are free riders from the same infrastructure

Proposed Execution Order

  1. Now: Multi-environment infrastructure (Multi-environment Metashade overrides with --library compatibility #13 -- pink Schlick proof of concept)
  2. Next: Reimplement divide for all types with safe math hook; deploy as safe_math/ environment
  3. Then: Expand to full arithmetic set + math intrinsics (pow, sqrt, exp, log, clamp)
  4. Then: Tier A/B source code nodes (near-trivial + blend modes)
  5. Medium-term: Tier C BSDFs and procedurals; adsklib custom nodes
  6. Ongoing: Build the ASWF generic implementation narrative from accumulated evidence

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions