Initial plugin entry and render pipeline
e6fe6bb · 2026-05-21 ↗
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:
- generate or retrieve camera rays;
- intersect the scene;
- integrate radiance;
- accumulate samples;
- optionally denoise, upscale, and post-process;
- 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
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.
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.