Optional ReadonlyanisotropyHow 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.
Optional ReadonlyfilterHow 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).
Optional Readonlymaximum§96 ceiling on width * height * 4 — refused at construction and on
CanvasTexture.resize, and re-checked on every
CanvasTexture.update against the size last recorded. Defaults to
64 MiB (a 4096 × 4096 RGBA8 surface); Number.POSITIVE_INFINITY is
the explicit in-source opt-out. See the module header for the A-23
reasoning.
Optional ReadonlyminHow 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:
texParameteri calls;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.
Optional ReadonlymipmapsWhether 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).
Optional ReadonlywrapHow 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.
Construction options for a CanvasTexture.