Documentation
    Preparing search index...

    Class NullRenderer

    A renderer that draws nothing and records what it was asked to draw — §62's headless tier, reduced to its floor.

    Two jobs:

    1. Headless rendering. §62 lists a "headless/software extension" among the backends, and §104 makes headless simulation core from Phase 1. An application wired with a NullRenderer runs its whole frame loop — fixed steps, systems, render events, render-list construction — in Node, with no canvas and no GPU. That is what the Application integration tests (WP-3.6) and the §33 determinism suites need: proof that the simulation is unchanged by the presence or absence of a renderer.
    2. A conformance fixture. It is the interface's proof of implementability and the reference for backend authors: what Renderer requires, minus every line of GL.

    It is not the software rasterizer of §62's "software" tier — nothing is rasterized. Naming it "null" rather than "headless" keeps that distinction available for a backend that really does produce pixels off screen (decision, WP-3.4).

    const renderer = new NullRenderer();
    await renderer.initialize();
    renderer.render(scene, views);
    expect(renderer.renderCount).toBe(1);
    expect(renderer.lastRenderRoot).toBe(scene);

    Every call is counted and its arguments retained, so a test can assert that the application drove the renderer correctly without a display. The counters are plain public fields: a test double's whole purpose is inspection, and hiding them behind getters would add ceremony without adding a guarantee. lastViews retains the array the caller passed, not a copy — callers are expected to reuse a per-frame array, so read it before the next frame or copy what you need (the same rule the pooled render list follows).

    A null renderer has no context, so it never loses one and emits nothing on its own. NullRenderer.events is a fully functional emitter regardless — a test can emit contextlost through it to exercise application-side loss handling without a GPU, which is exactly the §92 integration test's cheap half.

    dispose() is idempotent and terminal, and every other method throws a FourError with INVALID_APPLICATION_STATE afterwards — the same contract Application uses (§45, §83). A silent no-op after disposal would let a test record zero calls and pass for the wrong reason (decision, WP-3.4). Note the deliberate asymmetry with a lost context, which is recoverable and must never throw: disposal is the application's own doing and cannot be undone.

    Implements

    Index
    capabilities: RendererCapabilities

    §62's headless tier: no textures at all, hence maxTextureSize: 0. Frozen — a fixture that could be reconfigured mid-test would make the capability negotiation it is meant to exercise meaningless.

    Every §62 member is answered rather than omitted (WP-R1.1), and every answer is the floor: this renderer has no device, no shader stage and no surface, so "no" and "zero" are not conservative guesses here but the literal truth. That is what makes it the conformance fixture — a backend author reading it sees the complete record with nothing left to infer.

    The §6b channel required by Renderer. Never emitted to by this class.

    initializeCount: number

    Number of completed NullRenderer.initialize calls.

    lastEffectPass: EffectRenderPass | null

    The most recent EffectRenderPass (not copied); null before the first call.

    Retained rather than cleared per call, unlike NullRenderer.lastRenderTarget, because there is no "an effect pass without an effect" call to clear it: every renderEffect has one, so a stale value is impossible.

    lastInitializeOptions: RendererOptions | null

    Options of the most recent initialize; null before the first call.

    lastInterpolation: RenderInterpolation | null

    The §43 interpolation record of the most recent render (not copied; callers reuse one per frame), or null when that call passed none — so a later non-interpolated frame clears the record rather than leaving the previous frame's standing, which would let a test pass for the wrong reason. null before the first call.

    lastRenderRoot: Node | null

    Root of the most recent render; null before the first call.

    lastRenderTarget: RenderTarget | null

    The RenderTarget of the most recent render, or null when that call passed none (R-4) — cleared per call for the same reason NullRenderer.lastInterpolation is: a later on-screen frame must not leave the previous off-screen pass's target standing, which would let a test pass for the wrong reason. null before the first call.

    Nothing is drawn into it. A null renderer has no surfaces at all, so an off-screen pass is recorded and skipped exactly as an on-screen one is — which is what lets an application's render-to-texture wiring be asserted headlessly (§92's cheap half).

    lastResize: ResizeRecord | null

    Arguments of the most recent resize; null before the first call.

    lastViews: readonly Viewport[] | null

    Views of the most recent render (not copied); null before the first call.

    renderCount: number

    Number of NullRenderer.render calls.

    renderEffectCount: number

    Number of NullRenderer.renderEffect calls (R-6).

    resizeCount: number

    Number of NullRenderer.resize calls.

    statistics: RenderStatistics | null

    §84's render counters (A-1). Declared, and never written to — this renderer submits no draw calls at all, so every frame's honest contribution to drawCalls, triangles, and instances is zero, and adding zero is writing nothing.

    Declaring it rather than omitting it is deliberate: it makes a headless application's statistics wiring assertable end to end (supportsRenderStatistics answers true, app.stats.drawCalls reads 0 rather than NaN), which is the same job every other recording field on this class does. A backend that genuinely cannot count is the case the optional member exists for; this one can, and the answer is zero.

    • get disposed(): boolean

      Whether NullRenderer.dispose has run. Disposal is terminal.

      Returns boolean

    • Drops every listener and marks the renderer disposed. Idempotent: a second call returns without doing anything. Recorded call data is left intact so a test can still assert on it after teardown.

      Returns void

    • Resolves immediately — there is no context to acquire. Records the call and its options.

      Returns an already-resolved promise rather than being async, so awaiting it costs one microtask and nothing else; the method still satisfies §61's Promise<void> signature and an application's await app.initialize().

      Parameters

      Returns Promise<void>

    • Records root, views, the §43 interpolation record, and the R-4 target, and draws nothing. No pose is computed: the null backend has nothing to draw them into, and a test that wants the interpolated matrices asks buildInterpolatedRenderList for them directly.

      Parameters

      Returns void

    • Records the §70 effect pass, and draws nothing (R-6).

      Declared rather than omitted, exactly as NullRenderer.statistics is and for the same reason: this class is the interface's conformance fixture, and an application's post-processing wiring — which passes ran, in which order, over which surfaces — is assertable headlessly only if the null backend accepts them. A backend that genuinely cannot run an effect is the case the optional member exists for; this one can record, and recording is the whole of what it does for render too.

      Parameters

      Returns void

    • Records the requested size. resolution defaults to 1.

      Parameters

      • width: number
      • height: number
      • Optionalresolution: number

      Returns void