The final part of commit 4648adc compares four complete rendering pipelines: the CPU path tracer, CPU ReSTIR, GPU path tracer, and GPU ReSTIR. The comparison is repeated across scenes because an integrator does not have one scene-independent convergence rate. Light count, indirect illumination, visibility, material support, and GPU utilization all change the result.

This chapter records the protocol, commands, metrics, reference images, and graphs together. Hardware and software provenance is attached directly to each graph, so results from other platforms can be added without changing the experiment’s platform-independent description. The discussion also separates conclusions supported by the data from questions that require another experiment.

Commit contents Files discussed in this chapter Justfile · tests/perf_test.py · tests/capture_extra_scene_references.py · graphs/ · images/reference/
Complete commit diff ↗

Experimental scope

The committed curves share the following protocol:

Parameter Recorded configuration
Renderer revision 4648adc
Build capabilities GPU_ENABLED=ON, METRICS_ENABLED=ON

The stored result JSON contains scene and renderer fingerprints, pipeline, seeds, sample limits, metric, resolution level, and timestamps. It does not store the host hardware or operating-system version. That provenance is therefore stated in every graph caption rather than inferred from the result files. A future version of the benchmark should write these fields into every result.

Each curve uses:

  • sample counts doubling from 8 up to 8192;
  • one timing run per point;
  • a 60-second limit for one rendered image;
  • resolutionLevel=1;
  • split screen disabled and the firefly filter enabled;
  • benchmark seed 0;
  • reference seed 104729 when an independent reference is available.

Most captures are 960 × 700 pixels. The Cornell-box and area-light captures are 960 × 640 because their source stages use a different framing.

One run is not sufficient to estimate timing variance or construct a confidence interval. Differences between nearby curves must therefore be treated as observations from this run, not as stable performance rankings. The horizontal coordinate includes the complete capture command measured by the test process; it is not an isolated GPU-kernel duration.

Commands that generated the data

The ordinary noise curves for all repository scenes were generated with:

just perf-compare-all-scenes "" 0.01 8 8192 60 1

Independent 2048-spp path-tracer references were captured with seed 104729. Set USD_ASSETS_ROOT to the local directory containing the optional OpenUSD assets:

export USD_ASSETS_ROOT=/path/to/openusd-assets

just capture-comparison-references \
  "$USD_ASSETS_ROOT" \
  "full_assets/CarbonFrameBike/CarbonFrameBike\.usdz" \
  "full_assets/OpenChessSet/chess_set\.usda"

The reference-RMSE graphs were then produced with:

just perf-compare-reference-scenes \
  "$USD_ASSETS_ROOT" \
  "full_assets/CarbonFrameBike/CarbonFrameBike\.usdz" \
  "full_assets/OpenChessSet/chess_set\.usda"

The recipes expand to tests/perf_test.py with the four pipeline names, a target of 0.01, sample bounds 8–8192, a 60-second per-image budget, benchmark seed 0, and reference seed 104729. Compatible completed observations are reused; an interrupted curve resumes after its last stored sample count. Graphs can be redrawn without rendering through just perf-redraw-all-scenes and just perf-redraw-reference-scenes.

The following excerpt was added by the commit and is the source of the reference comparison:

def _relative_rmse_to_reference(image_path, reference_path):
    image = _read_hdr_pixels(image_path).astype(np.float64)
    reference = _read_hdr_pixels(reference_path).astype(np.float64)
    reference_rms = float(np.sqrt(np.mean(reference * reference)))
    return float(
        np.sqrt(np.mean((image - reference) ** 2))
        / max(reference_rms, 1e-12)
    )

The complete implementation also checks that both HDR images have identical dimensions before subtracting them.

What the two vertical axes mean

The graphs use two different quantities. They answer different questions and must not be combined into one ranking.

Internal noise estimate

For pixel (p), let (X_{p,1},\ldots,X_{p,n}) be its luminance samples, (\bar X_p) their mean, and

\[s_p^2 = \frac{1}{n-1} \sum_{i=1}^{n}(X_{p,i}-\bar X_p)^2\]

their unbiased sample variance when the samples are independent and identically distributed. The renderer estimates the variance of the mean as (s_p^2/n), averages this quantity over the (P) pixels, and reports

\[N_{\mathrm{rel}} = \frac{ \sqrt{\frac{1}{P}\sum_{p=1}^{P}\frac{s_p^2}{n}} }{ \frac{1}{P}\sum_{p=1}^{P}\bar X_p }.\]

This is a scale-relative estimate of stochastic fluctuation. It does not compare the image with the correct solution. The CPU accumulation pass stores the luminance sums in double precision. The GPU pass stores them in float because the backend used for this dataset does not expose shader double precision, then reduces 256 pixels per threadgroup before readback.

Temporal reuse makes consecutive estimates correlated. For correlated random variables,

\[\operatorname{Var}[\bar X] = \frac{1}{n^2} \left( \sum_i \operatorname{Var}[X_i] +2\sum_{i<j}\operatorname{Cov}[X_i,X_j] \right).\]

The (s^2/n) diagnostic omits the covariance terms. The derivation and its limitations are discussed in Renderer performance metrics.

Error against an independent HDR reference

Let (I) be a measured render and (R) a 2048-spp reference, with (K=3WH) scalar RGB values. The reference graphs use

\[E_{\mathrm{ref}}(I,R) = \frac{ \sqrt{\frac{1}{K}\sum_{k=1}^{K}(I_k-R_k)^2} }{ \max\left( \sqrt{\frac{1}{K}\sum_{k=1}^{K}R_k^2}, 10^{-12} \right) }.\]

Every pipeline uses the same denominator for a given scene. A bright outlier in a tested image therefore cannot reduce its own reported error by increasing the normalization scale.

Reference RMSE includes stochastic variance, estimator bias, systematic renderer differences, and the remaining error in the finite-sample reference. For a scalar estimator (\hat I) and exact target (I^\star),

\[\mathbb E[(\hat I-I^\star)^2] = \operatorname{Var}[\hat I] +\left(\mathbb E[\hat I]-I^\star\right)^2.\]

This identity explains why a curve can show low internal noise while retaining higher reference error: low fluctuation alone does not prove correctness.

How to read the graphs

Each marker is one complete render at a particular sample count. Sample counts increase from left to right only indirectly: the horizontal coordinate is the measured time for that image, not the sample count. Each pipeline doubles its sample count independently.

The vertical dashed line is the 60-second per-image budget. The dotted horizontal line is a readable visual guide selected from the data; it is not always the requested 0.01 stopping threshold. None of the committed curves reached 0.01 inside the 60-second budget.

For a curve that exceeds the budget, the result record retains its latest point whose individual render time is at most 60 seconds. The tables compare those retained points and report the lowest value among the four pipelines. They are summaries of this run, and their points need not occur at exactly the same time.

Scene with reference Lowest latest-in-budget reference RMSE Pipeline Time of that point
Internal scene 0.01653 GPU Path Tracer 53.705 s
Many lights 0.02520 GPU Path Tracer 53.407 s
MIS area lights 0.07358 GPU Path Tracer 45.396 s
Carbon Frame Bike 0.56355 GPU ReSTIR 47.566 s
Open Chess Set 0.50006 CPU Path Tracer 44.430 s
Scene with internal-noise graph Lowest latest-in-budget relative noise Pipeline Time of that point
Internal scene 0.03061 GPU Path Tracer 53.705 s
Many lights 0.05594 GPU ReSTIR 32.725 s
MaterialX 0.10094 GPU ReSTIR 52.319 s
MIS area lights 0.10506 GPU Path Tracer 45.396 s
Cornell-box GI 0.06639 GPU Path Tracer 40.057 s

The many-lights noise values for GPU ReSTIR and the GPU path tracer differ by about two percent. With one timing run and no uncertainty estimate, that small gap is not evidence of a reproducible winner.

Internal scene

The internal scene combines diffuse and glossy objects, direct illumination, shadows, and a small set of simple materials.

HdRestir internal test scene with four geometric objects
Path Tracer reference, 2048 spp, seed 104729. The web preview converts the linear HDR capture to sRGB; all numerical comparisons use the original HDR data.
Reference RMSE versus render time for the internal scene
Relative RMSE against the independent 2048-spp reference. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.
Internal noise versus render time for the internal scene
Internal relative-noise estimate for the same four pipelines. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.

The GPU path tracer has the lowest curve for both metrics in this scene. GPU ReSTIR improves over both CPU curves at comparable times, but remains above the ordinary GPU path tracer. This result applies to the current one-candidate temporal implementation; it does not establish that reservoir reuse is generally slower.

Many lights

This stage makes direct-light selection more important by increasing the number of emitters.

HdRestir many-lights test scene
Path Tracer reference, 2048 spp, seed 104729.
Reference RMSE versus render time for the many-lights scene
Reference error: the GPU path tracer is below GPU ReSTIR throughout the measured curve. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.
Internal noise versus render time for the many-lights scene
Internal noise: the GPU ReSTIR and GPU path-tracer curves are close near the time limit. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.

The two metrics give a useful warning. GPU ReSTIR reports slightly lower internal noise at its latest in-budget point, while the GPU path tracer has lower error against the independent reference. Temporal correlation, a systematic difference, or both can create this pattern. The plots alone do not identify which term is responsible.

MIS area lights

This stage contains several finite-area emitters and therefore exercises light/BSDF multiple importance sampling.

HdRestir area-light MIS test scene
Path Tracer reference, 2048 spp, seed 104729.
Reference RMSE versus render time for the area-light MIS scene
Reference RMSE for CPU/GPU Path Tracer and ReSTIR pipelines. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.
Internal noise versus render time for the area-light MIS scene
Internal relative noise for the same scene. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.

The GPU path tracer is lowest for both recorded quantities. In this configuration, the cost and correlation introduced by the temporal reservoir do not recover enough variance reduction to overtake ordinary GPU MIS within the budget.

MaterialX scene

The MaterialX stage checks that the comparison also passes through Hydra’s material-network translation rather than using only built-in parameters.

HdRestir MaterialX test scene with two glossy objects
Path Tracer reference preview, 2048 spp, seed 104729.
Internal noise versus render time for the MaterialX scene
Only the internal-noise graph was committed for this stage. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.

GPU ReSTIR has the lowest recorded internal-noise value, but no reference-RMSE graph accompanies this scene in the commit. The data therefore supports a statement about the renderer’s variance diagnostic only, not about absolute image accuracy.

Cornell-box global illumination

The Cornell-box stage emphasizes indirect transport. The current ReSTIR implementation resamples direct-light candidates; it does not resample entire multi-bounce paths.

Cornell-box global-illumination scene rendered by the GPU path tracer
GPU Path Tracer, 1024 spp, benchmark seed 0. A +2 EV display adjustment makes the dark linear HDR capture readable on the web. The graph uses the unmodified HDR values.
Internal noise versus render time for the Cornell-box GI scene
Internal-noise convergence; no independent reference graph was produced for this stage. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.

The ordinary GPU path tracer is lowest. CPU and GPU ReSTIR both remain above their corresponding ordinary path tracers. This is consistent with a scene whose difficult contribution is indirect transport, but the graph does not by itself prove that indirect illumination is the only cause.

External OpenUSD assets

The Carbon Frame Bike and Open Chess Set stages exercise larger scene graphs, texture and material data, and asset resolution outside the small repository examples.

Carbon Frame Bike rendered by HdRestir
Carbon Frame Bike Path Tracer reference, 2048 spp, seed 104729.
Reference RMSE versus render time for Carbon Frame Bike
Reference RMSE for the four pipelines on Carbon Frame Bike. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.
Open Chess Set rendered by HdRestir
Open Chess Set Path Tracer reference, 2048 spp, seed 104729.
Reference RMSE versus render time for Open Chess Set
Reference RMSE for the four pipelines on Open Chess Set. Benchmark platform: MacBook Pro with Apple M1 Pro (10-core CPU, 16-core GPU), 16 GB unified memory, Metal, macOS 26.5.2; commit 4648adc; one timing run per point.

The external-asset curves remain near 0.5–0.7 and flatten far above the 0.01 target. Increasing samples alone is not removing most of the difference. Possible causes include deterministic pipeline differences, material or texture support, camera or asset-resolution state, and residual error in the reference. The present experiment does not distinguish these hypotheses. These plots should therefore be treated as diagnostics that motivate a parity investigation, not as evidence that one processor is a fixed percentage faster than another.

Reproducing only the figures

Completed curves are stored below build/performance_results/. Redrawing does not execute the renderer:

just perf-redraw-all-scenes
just perf-redraw-reference-scenes \
  "$USD_ASSETS_ROOT" \
  "full_assets/CarbonFrameBike/CarbonFrameBike\.usdz" \
  "full_assets/OpenChessSet/chess_set\.usda"

The scene previews on this page are display conversions, not metric inputs. The normal conversion was:

magick input.hdr -colorspace sRGB -strip -quality 88 output.webp

The original HDR files remain the inputs to perf_test.py.

What the measurements establish

For this revision, seed policy, and 60-second budget:

  1. the ordinary GPU path tracer gives the lowest reference error on the three small scenes with complete reference comparisons;
  2. GPU ReSTIR can reduce the internal variance diagnostic without producing the lowest independent-reference error;
  3. direct-light reservoir reuse does not improve the Cornell-box GI result;
  4. the external assets expose a large error floor that needs a parity study;
  5. no pipeline reaches the requested 0.01 criterion inside the budget.

The next useful experiment is not another single timing run. It is a repeated measurement with hardware provenance embedded in the JSON, confidence intervals for wall time, an independent reference for every scene, and difference images separating localized material mismatches from stochastic noise.