Documentation
    Preparing search index...

    Class RenderGraph

    §63's render graph at the linear-pass tier: an ordered, named, individually enableable list of passes that one RenderGraph.execute call drives through a Renderer.

    const graph = new RenderGraph();
    graph.addPass("world", { root: scene, views: [worldView], target: sceneColor });
    graph.addPass("bloom", bloomPass, { inputs: ["world"] });
    graph.addPass("ui", { root: overlay, views: [uiView] });
    graph.addPass("composite", compositePass, { inputs: ["bloom", "ui"] });

    Read the module header before reaching for this: it tabulates which of §63's seven requirements are shipped here and which are staged, and why.

    A graph owns nothing. Passes, roots, viewports, and render targets all belong to whoever created them, and the graph holds references — so it has no dispose, disposing a target does not remove its pass, and two graphs may name the same target. This is the same rule a material follows towards its texture (§83), applied one level up.

    Index
    • Appends a pass under name (§63's graph.addPass(name, pass, options)) and returns the graph, so calls chain.

      Throws a FourError carrying INVALID_APPLICATION_STATE when name is empty, when it is already taken, or when an entry of options.inputs names a pass this graph does not have. The last of those is what makes the graph acyclic by construction: an input must already be in the list, and the list is the execution order.

      An EffectRenderPass is additionally checked against §85 here, by validateEffectRenderPass — setup is the one place an effect's parameters can be rejected loudly, since a backend may not throw from inside a frame (§61). It raises a RangeError, not a FourError: a non-finite grading coefficient is a value violation, the same class RenderTarget's size check reports, where everything else this method refuses is a graph that cannot exist.

      The pass object is not copied. Mutating its views array between frames is therefore visible to the next execute, which is the same arrangement renderer.render already has with the array it is handed.

      Parameters

      Returns this

    • Renders the graph as text — §63's "debug visualization" at the tier this package can honestly reach without a backend (the module header says what the full requirement needs).

      RenderGraph: 4 passes, 3 enabled
      1. world [scene] -> render-target-1
      2. grade [effect grade] -> render-target-2 <- world
      3. ui [scene, disabled] -> screen
      4. composite [custom] -> screen <- grade, ui
      

      An effect pass prints its §70 effect beside its kind, because "which effect" is the only thing that distinguishes two otherwise identical passes in a chain. -> screen is a pass with no target — the default drawing buffer. The output is deterministic (§33: no clock, no identity hashes) and is meant to be logged or snapshotted; its exact layout is not a contract.

      Returns string

    • Draws every enabled pass in order through renderer, and returns how many ran.

      Each SceneRenderPass becomes exactly one renderer.render(root, views, interpolation, target) call — R-4's seam, with the frame's interpolation record threaded through unchanged — and each EffectRenderPass becomes exactly one renderer.renderEffect(pass) call, with the pass object forwarded unchanged (R-6). So a graph produces the same backend calls, in the same order, as the hand-written sequence it replaces. A CustomRenderPass is called instead, and whatever it does with the renderer is its own.

      Nothing here interprets a disposed or unallocatable target: R-4 already defines both as "the backend skips the frame", and re-deciding that here would give the engine two policies for one situation.

      An effect pass met by a renderer that does not implement ScreenEffectRenderer throws a FourError carrying INVALID_APPLICATION_STATE. That is a deliberate exception to "a frame never throws": the mismatch is a permanent property of the backend the application selected (§62 selects one at the edge, once), not the transient, driver-scheduled condition §61's no-throw rule protects against — so it can only ever fail on the very first frame, loudly, where the alternative is a post-processed image that silently never appears. supportsScreenEffects answers the same question without a frame.

      Allocates one context record per custom pass per call and nothing else; a graph of scene and effect passes allocates nothing at all.

      Parameters

      Returns number

    • Removes the pass added under name and returns whether there was one.

      Refused with a FourError while another pass declares it as an input: silently dropping a producer would leave its consumers sampling a surface nothing writes, and the graph knows that now rather than at RenderGraph.validate time. Drop the consumers first, or disable the producer with RenderGraph.setPassEnabled.

      Parameters

      • name: string

      Returns boolean

    • Turns a pass on or off — §63's "pass enable/disable" — and returns the graph, so calls chain.

      A disabled pass is skipped entirely by RenderGraph.execute: no render call, no clear, nothing bound. Its consumers keep running and keep sampling whatever its target last held, which RenderGraph.validate reports as "unwritten".

      Throws a FourError when the graph has no pass under name, rather than doing nothing: a typo that silently fails to disable a pass costs a frame's worth of work per frame, invisibly.

      Parameters

      • name: string
      • enabled: boolean

      Returns this

    • Checks the enabled passes against what they actually sample, and returns every finding in pass order (§63's "pass dependencies").

      An empty array means: no enabled pass samples the target it writes, none samples a target only a later pass writes, none samples a target nothing writes, and none is opaque to the check. See RenderGraphIssueCode for what each finding means and RenderGraphIssue.severity for how much it matters.

      const errors = graph.validate().filter((issue) => issue.severity === "error");
      expect(errors).toEqual([]);

      Call it at setup, not per frame. It builds a render list per scene pass to discover what that pass samples (the module header explains why the result cannot be cached), so it costs a scene traversal per pass. Every finding it reports is a static property of the graph plus the materials currently attached; re-run it when either changes.

      Returns readonly RenderGraphIssue[]