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 hd_restir_render_pass.cpp → path_tracer_pipeline.h → raycast_pass.cpp
Complete commit diff ↗

Plugin registration and delegate creation were read in the preceding Hydra chapter. Here the diff starts where a viewport request becomes renderer state, then follows the pass compiler to the concrete G-buffer producer. Materials, lights, and BVH traversal are reached from that path in the following articles.

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 first three passes declare the following dataflow:

Pass Required input Produced output
RaycastPass none gbuffer; requested depth, albedo, and normal AOVs
PathTracePass gbuffer one-frame color radiance
AccumulationPass one-frame color accumulated color under the same name

The duplicate color name is intentional in this linear pipeline: AccumulationPass consumes the preceding value and replaces it with the running estimate. If the request is only normal, RaycastPass can satisfy it directly and the path-tracing and accumulation passes are not retained. If the request includes color, backward reachability adds color, then gbuffer, so all three core passes remain.

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.

Selecting pipelines in usdview

The final interface exposes the pipeline name as a RenderSettings variant. This makes the architectural distinction visible: the USD stage and camera remain fixed while the renderer changes the estimator and execution backend. Split screen selects a second pipeline for the right half of the same viewport; debug views and overlays reveal intermediate outputs and per-side measurements.

Selecting ordinary path tracing and ReSTIR pipelines, enabling debug views, and comparing two pipelines in one split-screen viewport. The vertical divider separates the independently selected left and right pipelines; it is not part of the scene. Recorded from an HdRestir working tree based on dbca10b with the Open Chess Set. These CPU/GPU and ReSTIR choices were added after e6fe6bb. The recording illustrates the pass-selection model introduced in this chapter, but it is not output from the first commit and is not an equal-time convergence benchmark.