Overview

Crust Render is a Rust workspace of seven crates. Each owns one job and knows nothing about the others’ internals.

This section is for readers who want to know how Crust Render works inside: to judge whether it suits a use, to read its code, or to contribute. You don't need it to render.

The Design choices page explains why it is built this way, and Limitations lists what it doesn't do. The contributor-level map, with every module and invariant, is docs/architecture.md in the repository.

The crates

                    crust-render  (the CLI binary)
                     │         │
                     │         ▼
                     │    crust-assets ──► ptex-rs
                     │    (file decoders, texture caches)
                     ▼         │
                    crust-core ◄┘
         (USD import, integrator, materials, lights, volumes, guiding)
          │       │        │        │          │          │
          ▼       ▼        ▼        ▼          ▼          ▼
      crust-rt crust-mtlx crust-jit utils  openqmc-rs  openusd,
      (kernel) (MaterialX) (JIT)   (math)  (sampling)  opensubdiv-rs
crateownsknows nothing about
crust-rtgeometry, the BVH build, ray intersection, instancing, motion blurmaterials, lights, USD
crust-mtlxreading .mtlx documents, compiling node graphs into programs, the BSDF closure treeany Crust type
crust-jitcompiling MaterialX programs to machine code (optional, the jit feature)everything but crust-mtlx
crust-coreUSD import, the scene, the integrator, materials, lights, volumes, path guiding, statisticsdecoding images, textures or IES files
crust-assetsevery file decoder (EXR, PNG/HDR, Ptex, IES, .tx), the texture tile caches, .tx conversionthe integrator
crust-renderthe command line, logging, the progress bar, writing the EXR and PNGdecoding anything
utilsstateless math: sampling warps, MIS heuristics, luminanceeverything

Three libraries come from outside the workspace, all pure Rust:

  • openusd reads and composes USD stages.
  • openqmc-rs is a port of the Academy Software Foundation's OpenQMC quasi-Monte Carlo sampler. It produces exactly the same samples as the C++ library.
  • opensubdiv-rs and ptex-rs refine subdivision surfaces and read Ptex files.

openqmc-rs started inside this repository and was extracted once it had no Crust types in its interface. crust-rt and crust-mtlx are kept the same way, so that they could be extracted too.

A render, end to end

  1. Command line. crust-render parses its flags and builds the asset loader, which reads the CRUST_* texture settings.
  2. USD import (crust-core). A light "index" stage is opened with payloads unloaded, to read the render settings, pick the camera and list the top-level subtrees. Each subtree is then composed on its own masked stage, traversed and dropped. Prims become meshes, spheres, curves, instances, lights, volumes or the camera. Materials are resolved and cached. Every image, Ptex or IES file is decoded by crust-assets.
  3. Geometry build (crust-rt). Meshes are either baked into world space or kept as instances, then the top-level acceleration structure is built.
  4. Renderer setup. Light-selection tables are built, plus the learned light cache when crust:lightSelection is learned.
  5. Rendering. Tiles run in parallel. For each pixel and sample, a path is traced: intersect, shade the hit once, sample a light, pick the next direction, repeat. Path guiding (training passes) and adaptive sampling (stopping pixels early) wrap that same per-pixel routine.
  6. Output (crust-render). The linear EXR and the tone-mapped PNG are written, and the --stats report is printed.

--stats times each of these phases, and --profile breaks the rendering phase down further. See Command line.

Where things plug in

The crates meet at a few interfaces. Each has one contract that both sides keep:

interfacecontract
the kernel's Geometry / SceneBuilder / Scenemodelled on Embree: attach geometry, commit(), then intersect or occluded. A hit is only a geometry id and a primitive id.
AssetLoaderthe host decodes files. Returning nothing means "fall back to the constant value", never an error.
Texture2D, PtexTexturetexture values come out linear. UDIM tile addressing is the host's job.
Materialshaded once per path vertex into a ShadingPoint, which answers every later question about that hit
Lightlight sampling and BSDF sampling compute the same density for the same point on a light
ProgressCallbackthe engine reports progress and never prints

Edit this page on GitHub