Documentation
    Preparing search index...

    Class CanvasTexture

    A texture whose texels an application paints (§77a; RFC 0004) — a RasterSource read into an engine-owned buffer, published through the MaterialTexture contract every material and every backend already accept.

    const texture = new CanvasTexture(source);
    material.map = texture; // any MaterialTexture slot
    texture.invalidate(); // the surface changed
    texture.update(); // repaint + re-read, once, before rendering
    texture.dispose(); // §83: explicit lifetime

    A backend's texture cache keys on CanvasTexture.id and validates on CanvasTexture.version (@fourjs/render-webgl's TextureCache); a CanvasTexture satisfies both and uploads through the path that already exists — R-4's MaterialTexture seam paying off a second time, and the strongest single argument for this shape (RFC 0004 §2a). No new duck-typed contract is introduced.

    Nothing polls, nothing subscribes, and no per-frame hook exists — the "version, not events" stance Texture records, which also keeps this tier off the render loop's critical path. The consequence is documented rather than papered over (RFC 0004 Q6, adopted: no hook, no staleness warning): an application that forgets update() after invalidate() sees a stale texture and no diagnostic.

    RasterSource.width/RasterSource.height are re-validated on every CanvasTexture.update. A source that changes size without CanvasTexture.resize is still refused with INVALID_APPLICATION_STATE (§89) rather than silently reallocated — the mid-paint panel-resize hazard must not be read at the old size.

    CanvasTexture.resize is the sanctioned path: it re-validates the source against the construction-time §96 ceiling, reallocates the engine-owned buffer, updates §83 accounting by the byte delta, marks the surface fully dirty, and sets the stale flag. It does not bump CanvasTexture.version; the next successful CanvasTexture.update does, so a backend cache keyed on version re-uploads once, after the new pixels exist. §55 sprite frame rectangles are still validated at write time only — a frame authored against the previous size may be stale until the application writes it again.

    Implements

    Index
    id: string = ...

    Stable identity (§77, §83), canvas-texture-<n> from a monotonic counter (§33 forbids clock-derived identity). Unique within a process, ascending in construction order, never reused.

    • get anisotropy(): number

      How many anisotropic samples the backend may take (§77's "anisotropy"; R-30b, 2026-08-21). An integer ≥ 1.

      Optional, and absent means 1 — isotropic filtering, GL's own default and this contract's previous fixed behaviour.

      Anisotropic filtering is an extension in WebGL 2 (EXT_texture_filter_anisotropic), so this is a request, not a guarantee: a backend clamps it to what the device reports and ignores it entirely where the extension is absent. That is §62's capability tiering rather than §85's refusal — a value the driver merely cannot honour is not an authoring mistake, and refusing it would make the same scene unrunnable on a conformant device. A non-integer or a value below 1 is an authoring mistake, and the concrete class refuses it.

      Returns number

    • get byteLength(): number

      Bytes this surface describes (§83, §84's textureMemory) — four per texel, and 0 once disposed, exactly as Texture.byteLength answers.

      Returns number

    • get data(): Uint8Array<ArrayBufferLike> | null

      The RGBA8 bytes as last read from the source — row 0 is v = 0 whatever the source's RasterSource.origin — or null once disposed.

      Engine-owned and reused until CanvasTexture.resize or CanvasTexture.dispose; readable by anyone holding the texture (the upload path reads it), which is why §77a's display-only rule is an import rule on packages, not a readability rule on this field — see the module header.

      Returns Uint8Array<ArrayBufferLike> | null

    • get filter(): NonNullable<MaterialTextureFilter | undefined>

      How the backend samples between texel centres (§77; R-30, 2026-08-13).

      Optional, and absent means "linear" — the value this tier hard-coded before the field existed, so every texture written against an earlier build and every test double keeps issuing the identical texParameteri pair. Read it as texture.filter ?? "linear", exactly as MaterialTexture.colorSpace is read.

      "nearest" is what a bitmap glyph atlas and pixel art want: a texel drawn at or above 1:1 stays a square instead of being blurred across its neighbours.

      Returns NonNullable<MaterialTextureFilter | undefined>

    • get mipmaps(): boolean

      Whether the texture carries a full mip chain (§77's "mipmaps"; R-30b, 2026-08-21).

      Optional, and absent means false — no chain, exactly the one level this contract described before the field existed, so every texture and test double written earlier uploads the identical call sequence.

      true asks the backend to build the chain at upload time (WebGL 2's generateMipmap, which needs no power-of-two size). What the chain is for is minification: a 512² texture drawn across twenty pixels samples twenty of its half-million texels without one, which is the shimmer that appears when the camera moves.

      Returns boolean

    • Releases the pixel buffer and this texture's share of the §83 totals. Idempotent. The version is bumped so any backend cache keyed on it re-reads and — meeting a disposed texture — skips the draw, exactly as Texture.dispose arranges.

      Returns void

    • Marks the surface stale, so the next CanvasTexture.update repaints and re-reads. Cheap, idempotent, allocation-free — call it whenever the painted content should change, from any code that knows it did.

      Parameters

      Returns void

    • Reallocates this texture to the source's current size (§77a).

      Call after the source's width/height change and before the next CanvasTexture.update. The buffer, §83 totals, and dirty region are updated immediately; CanvasTexture.version waits for the next successful update() so a backend cache re-uploads only once the new pixels exist. A no-op when the recorded size already matches.

      Returns void

      FourError INVALID_APPLICATION_STATE on a disposed texture

      RangeError when the new size fails §85 or the construction-time §96 maximumBytes ceiling

    • If stale: call RasterSource.paint, read the pixels, bump CanvasTexture.version, clear the stale flag, and return true. Otherwise return false and touch nothing — which is the difference between one upload per repaint and one upload per frame.

      Application-driven, from §9's render or real time domain — never from a fixed step (§33; module header). Performs no Map/Set enumeration.

      Returns boolean

      FourError INVALID_APPLICATION_STATE on a disposed texture (§83's "disposed resource still in use", made loud), or when the source's size no longer matches the size last recorded by construction or CanvasTexture.resize