Documentation
    Preparing search index...

    Interface HostTextureOptions

    A supplied surface keeps DOM discovery, threading, and ownership in the host.

    interface HostTextureOptions {
        anisotropy?: number;
        colorSpace?: ColorSpace;
        filter?: MaterialTextureFilter;
        maximumBytes?: number;
        minFilter?: MaterialTextureMinFilter;
        mipmaps?: boolean;
        role?: TextureMapRole;
        surface: ImageReadSurface;
        wrap?: MaterialTextureWrap;
    }

    Hierarchy

    • Pick<
          TextureSource,
          | "colorSpace"
          | "role"
          | "filter"
          | "wrap"
          | "mipmaps"
          | "minFilter"
          | "anisotropy",
      >
      • HostTextureOptions
    Index
    anisotropy?: number

    How many anisotropic samples the backend may take when this texture is minified at a steep angle (§77's "anisotropy"; R-30b, 2026-08-21). An integer ≥ 1; defaults to 1, which is isotropic filtering and GL's own default.

    // A ground plane seen edge-on: mip levels alone blur it to mush.
    new Texture({ ...ground, mipmaps: true, anisotropy: 8 });

    Anisotropic filtering is an extension in WebGL 2 — EXT_texture_filter_anisotropic — so a conformant device may not have it, and every device that does has its own maximum. The policy here is §62's, not §85's: the backend clamps the request to the device's maximum, and where the extension is absent it uploads the texture with no anisotropy at all rather than failing. Presence is the capability, the stance this package takes for every optional renderer feature.

    The asymmetry with TextureSource.filter, which is refused when unknown, is deliberate and worth stating: "nearset" is a mistake in the scene, and no device will ever honour it; anisotropy: 16 is a correct request that some devices cannot fill, and a quality knob that turns a scene into an error on half the fleet is worse than one that quietly costs less. What §85 does refuse here is a value that is not an integer ≥ 1 — no device could honour that either.

    Anisotropy above 1 is only worth asking for together with mipmaps: true (it selects among mip levels per axis); the combination is not required and not refused, because a driver is free to do something useful with it and the texture is complete either way.

    colorSpace?: ColorSpace

    The colour space these texels are in — §60a's first bullet: "color textures default to sRGB-encoded and are decoded to linear on sample; data maps (normal, roughness, occlusion) default to linear" (R-15, 2026-08-08).

    // An albedo map authored in an image editor: sRGB-encoded bytes.
    new Texture({ width, height, data, colorSpace: "srgb" });

    "srgb" makes the backend allocate an sRGB internal format, so the GPU decodes each sample to linear-light before the shader sees it and §60a's "lighting and blending run in linear space" holds for textured surfaces too. "linear" uploads the bytes as the numbers they are — the right tag for a data map, a mask, or texels a program computed.

    §60a defaults colour textures to sRGB. This field defaults to "linear" when TextureSource.role is omitted, and the reason is the one this repository applies to every default: flipping the engine-wide default would silently darken every texture already authored against this engine and move every pixel golden, for scenes that never asked for colour management. Opt-in keeps §60a's behaviour available and every existing frame byte-identical. Map roles (R-30c, 2026-09-09) made the faithful default cheap per texture — role: "color" with no colorSpace resolves to "srgb" — without flipping the engine-wide default. An owner call can still change the omitted-role default later; this packet does not.

    §77a's CanvasTexture deliberately answers the same question differently — its default is "srgb" — because a class born after R-15 has no authored content and no goldens to protect, and a host 2D canvas produces sRGB-encoded bytes unambiguously (RFC 0004 Q3, adopted; the reason is recorded at both defaults).

    How this texture is sampled between texel centres (§77; R-30, 2026-08-13). Defaults to "linear".

    // A 6 × 12 bitmap font: every glyph texel stays a square.
    const atlas = new Texture({ ...buildGlyphAtlas(), filter: "nearest" });

    The default is "linear" because that is what this tier sampled with before the field existed: a texture that names no filter issues the identical texParameteri pair it always did, so no already-authored scene moves a pixel (the rule R-15's colorSpace default follows, for the same reason).

    maximumBytes?: number

    How this texture is sampled when minified (§77; R-30b, 2026-08-21) — the mip-level-aware half of TextureSource.filter, which R-30 staged and this packet lands.

    // Crisp within a level, snapped between levels — pixel art at a distance.
    new Texture({ ...art, filter: "nearest", mipmaps: true,
    minFilter: "nearest-mipmap-nearest" });

    Defaults to the derived value, never to a constant:

    • with no mip chain, to TextureSource.filter — which is what this field's absence has always meant, and why a texture that names neither still issues the identical pair of texParameteri calls;
    • with mipmaps: true, to that filter's chain-aware form — "linear" → "linear-mipmap-linear" (trilinear), "nearest" → "nearest-mipmap-nearest". Building a chain and then not sampling it would be the one combination nobody asks for on purpose.

    A mip-choosing value without mipmaps: true is refused (§85): GL calls such a texture incomplete and samples it as opaque black, which is a whole-surface visual failure with nothing anywhere saying why. Refusing is this repository's rule for exactly that class of value.

    mipmaps?: boolean

    Whether the backend builds a full mip chain for this texture at upload (§77's "mipmaps"; R-30b, 2026-08-21). Defaults to false — one level, which is what every texture in the engine had before this field existed.

    // A ground texture the camera flies away from.
    new Texture({ width: 512, height: 512, data, mipmaps: true });

    It buys minification: a 512² texture covering twenty screen pixels takes twenty samples out of a quarter-million texels without a chain, and which twenty depends on the sub-pixel position of the camera — which is the crawling shimmer that appears the moment anything moves. With a chain the hardware samples a level whose texels are about the size of a pixel, and the image holds still.

    It costs a third more texture memory (Texture.byteLength counts the chain) and one generateMipmap at upload — both paid once per upload, and nothing at all on the draw path.

    WebGL 1 could neither mipmap nor REPEAT a non-power-of-two texture. WebGL 2 is GLES 3.0 and lifts both restrictions for ordinary uncompressed formats, so a 300 × 173 texture takes a chain here with no padding and no resize. (Compressed containers keep restrictions of their own — they are still deferred, see Texture.) A backend at §62's WebGL 1 tier is the one that would have to refuse the combination, exactly as it would have to refuse TextureSource.wrap's "repeat".

    A backend whose context cannot generate mipmaps at all uploads the texture with one level and an in-level min filter — degrading rather than leaving a texture GL would treat as incomplete (see @fourjs/render-webgl).

    Whether these texels are a colour map or a data map (§60a, §77; R-30c, 2026-09-09). Optional: omitting it invents no default role.

    // An albedo PNG: sRGB-encoded bytes, decoded on sample.
    new Texture({ width, height, data, role: "color" });
    // A roughness map: bytes are the numbers they are.
    new Texture({ width, height, data, role: "data" });
    role colorSpace authored resolved colorSpace
    omitted omitted "linear" (R-15, goldens unchanged)
    omitted "srgb" / "linear" the authored tag
    "color" omitted "srgb"
    "data" omitted "linear"
    either "srgb" / "linear" the authored tag wins

    Backends still read Texture.colorSpace only. The role never changes a sampler type, and a source that never sets it is byte-identical to one built before this field existed.

    How this texture is addressed outside [0, 1] (§77; R-30, 2026-08-13). Defaults to "clamp-to-edge", this tier's previous fixed choice.

    // A tiling ground texture, with uv running 0…8 across the plane.
    new Texture({ width, height, data, wrap: "repeat" });

    Note that "repeat" and "mirrored-repeat" are legal on a non-power-of-two texture in WebGL 2 (they were not in WebGL 1), so a backend at §62's WebGL 1 tier would have to refuse the combination rather than silently sample black.