The foundation sequence now moves from the three conceptual prerequisites to the first repository revision. The spectral, path-tracing, and Hydra code examined separately in steps 0.a–0.c is already present here; later APIs such as IDirectLightIntegrator, typed PDFs, reservoirs, and GPU kernels do not yet exist.

Commit e6fe6bb adds the complete initial renderer in one change. Four articles use the same revision so that its plugin boundary, geometry, shading, and accumulation can be read independently. This first article follows entry from OpenUSD plugin discovery to the compiled pass list.

flowchart LR
  H["Hydra Sync"] --> S["renderer scene"]
  S --> B["BVH"]
  C["Hydra camera"] --> R["camera rays"]
  R --> B
  B --> I["hit + material"]
  I --> L["spectral path integration"]
  L --> A["accumulation"]
  A --> P["post-process / AOV"]
  P --> U["usdview"]

Scope of the first revision

The Hydra layer implements meshes, materials, lights, instancing, render buffers, the render pass, and the delegate itself. Renderer converts a Hydra render job into a RenderContext, selects a pipeline, and writes the requested AOV. The initial pipeline is already pass-oriented:

  1. generate or retrieve camera rays;
  2. intersect the scene;
  3. integrate radiance;
  4. accumulate samples;
  5. optionally denoise, upscale, and post-process;
  6. hand color to Hydra.

For comparison, the Metal tutorials show a fixed Shadow → GBuffer → Lighting pass sequence and later its GPU-driven render-pass architecture. HdRestir’s list serves the same broad purpose—separating work into ordered stages—but its passes declare named inputs and outputs and may be removed when they do not contribute to the requested AOV.

The commit also includes point, rectangle, distant, dome, and physical-sky lights; UsdPreviewSurface with a GGX microfacet model; depth of field; OIDN denoising; and a test harness. The discussion below concentrates on the end-to-end ownership and execution contract.

Browse the exact source tree at e6fe6bb.

Selected execution path through the diff

Commit contents Files discussed in this chapter renderer_plugin.cpp → hd_restir_render_pass.cpp → path_tracer_pipeline.h
Complete commit diff ↗

The commit creates more than one hundred files, but only three additions are needed to understand how a frame enters the renderer. Start at plugin registration, follow the render-pass boundary, and stop at pipeline assembly. Materials, lights, BVH traversal, and post effects are reached from that path and can be learned separately.

renderer_plugin.cpp: Hydra plugin factory

TF_REGISTRY_FUNCTION(TfType)
{
    HdRendererPluginRegistry::Define<HdRestirRendererPlugin>();
}

// ...

HdRenderDelegate*
HdRestirRendererPlugin::CreateRenderDelegate()
{
    return new HdRestirRenderDelegate();
}

// ...

void
HdRestirRendererPlugin::DeleteRenderDelegate(HdRenderDelegate *renderDelegate)
{
    delete renderDelegate;
}

TF_REGISTRY_FUNCTION runs through OpenUSD’s registry system when the plugin is discovered. Define associates the C++ plugin type with Hydra. The shown creation and destruction methods establish ownership: Hydra asks the plugin to create a delegate and later returns that same pointer for destruction. The // ... marker omits the settings-aware creation overload from the same revision. No rendering happens here; this is the dynamic-loading bridge.

hd_restir_render_pass.cpp: viewport state translation

const GfMatrix4d view{renderPassState->GetWorldToViewMatrix()};
const GfMatrix4d proj{renderPassState->GetProjectionMatrix()};

// ...

if (_viewMatrix != view || _projMatrix != proj) {
    _viewMatrix = view;
    _projMatrix = proj;
    _renderThread->StopRender();
    _renderer->SetCamera(_viewMatrix, _projMatrix);
    needStartRender = true;
}

// ...

const auto rendererAovBindings{MakeRendererAovBindings(aovBindings)};
const auto requestedOutputNames{Restir::Renderer::CollectRequestedOutputNames(rendererAovBindings)};

This is the first important lifetime rule. Accumulated samples are meaningful only for one camera. A changed matrix stops the worker before updating the camera and marks the render for restart. The AOV conversion performs a similar translation: Hydra buffer bindings become renderer-owned target interfaces and named outputs.

The order is deliberate: stop concurrent work, update the state that defines the estimator, clear or reconfigure dependent buffers, then start again.

path_tracer_pipeline.h: pipeline construction

std::vector<Detail::PipelinePassSpec> passSpecs{};
passSpecs.push_back(
    Detail::MakePassSpec<RaycastPass>(settings.OutputNames));
passSpecs.push_back(
    Detail::MakePassSpec<PathTracePass>(settings.PathTrace, settings.MaxDepth));
passSpecs.push_back(
    Detail::MakePassSpec<AccumulationPass>(
        settings.Denoiser.EnableFireflyFilter));
return Detail::CompilePipeline(
    std::move(name), settings.OutputNames, std::move(passSpecs));

Each entry is a factory plus declared inputs and outputs. CompilePipeline walks backward from the requested AOV names and retains the producers needed to reach them. The runtime then sees a simple forward list: intersection → radiance → accumulated image.

The pruning operation is implemented directly in the same header:

std::unordered_set<std::string> needed{};
for (const auto outputName : requestedOutputs) {
    needed.insert(std::string{outputName});
}

// ...

for (std::size_t idx{passSpecs.size()}; idx > 0; --idx) {
    const auto& passSpec{passSpecs[idx - 1]};
    const bool producesNeededOutput{
        std::any_of(passSpec.Outputs.begin(),
                    passSpec.Outputs.end(),
                    [&](const std::string& outputName) {
                        return ContainsName(needed, outputName);
                    })
    };
    if (!producesNeededOutput) {
        continue;
    }

    includedPassIndices.push_back(idx - 1);
    for (const auto& inputName : passSpec.Inputs) {
        needed.insert(inputName);
    }
}

Suppose the requested output is color. The initial needed set therefore contains only color. Scanning from the last pass toward the first, a pass is selected if at least one of its outputs is needed. Selecting it adds all of its inputs to needed; those names become requirements that an earlier pass must satisfy. This is backward reachability on a linear pass list. The selected indices are reversed before construction so that execution still follows producer-before-consumer order.

The algorithm is intentionally smaller than a general dependency-graph scheduler. It assumes that the declaration order is already a valid topological order and that one backward scan is sufficient. A later pass cannot provide an input to an earlier pass. Duplicate producers are resolved by the reverse traversal rather than by an explicit ambiguity check. These constraints are reasonable for the initial fixed pipelines, but they are part of the contract and should be documented before adding branches or arbitrary user-defined passes.

This path explains how usdview causes one HdRestir pipeline to execute. The next three articles descend into geometry, shading, and accumulation without mixing those subjects into the plugin discussion.

Limitations at this revision

The pass compiler assumes declaration order is a valid topological order. Duplicate producers are resolved by reverse traversal rather than an explicit ambiguity check. Scene-version invalidation also requests a restart without performing the clear in the same block, as described in step 0.c.

Record this: Check out e6fe6bb, launch usdview with the plugin path configured, select HdRestir, and request color followed by depth. Record the renderer selection and any pass/debug output that changes with the requested AOV.
Caption to keep: The plugin constructs the render delegate; the render pass translates Hydra state; the pipeline compiler retains the passes required by the requested outputs.