Optionaloptions: CanvasTextureOptionsReadonlyidStable 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.
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.
Bytes this surface describes (§83, §84's textureMemory) — four per
texel, and 0 once disposed, exactly as Texture.byteLength answers.
The colour space of the painted texels (§60a), "srgb" when the
source names none — see RasterSource.colorSpace for why this
default deliberately differs from TextureSource's.
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.
Whether the texture has been disposed (§83).
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.
Height in texels — last recorded by construction or CanvasTexture.resize.
How the backend samples when the texture is minified (§77; R-30b, 2026-08-21).
Optional, and absent means "derived": without
MaterialTexture.mipmaps it is MaterialTexture.filter, which
is what this contract always meant; with them it is that filter's
mip-chain-aware form ("linear" → "linear-mipmap-linear", i.e.
trilinear). Read it as the concrete class resolves it, or as
texture.minFilter ?? texture.filter ?? "linear" if the texture is known
to carry no chain.
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.
RFC 0009 provenance for render-graph feedback refusal.
Counter advanced by every completed CanvasTexture.update (and by
CanvasTexture.dispose). Backends cache GPU uploads against it;
compare for inequality, exactly as with Texture.version.
Width in texels — last recorded by construction or CanvasTexture.resize.
How the backend addresses texture coordinates outside [0, 1] (§77;
R-30, 2026-08-13).
Optional, and absent means "clamp-to-edge" — this tier's previous
fixed choice, for MaterialTexture.filter's reason. Read it as
texture.wrap ?? "clamp-to-edge".
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.
Regions use bottom-left texture coordinates, regardless of source origin.
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.
Optionalregion: Rectangle2Reallocates 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.
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.
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
A texture whose texels an application paints (§77a; RFC 0004) — a RasterSource read into an engine-owned buffer, published through the
MaterialTexturecontract every material and every backend already accept.No backend changes, none
A backend's texture cache keys on CanvasTexture.id and validates on CanvasTexture.version (
@fourjs/render-webgl'sTextureCache); aCanvasTexturesatisfies both and uploads through the path that already exists — R-4'sMaterialTextureseam paying off a second time, and the strongest single argument for this shape (RFC 0004 §2a). No new duck-typed contract is introduced.update()is called by the application, never by the engineNothing polls, nothing subscribes, and no per-frame hook exists — the "version, not events" stance
Texturerecords, 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 forgetsupdate()afterinvalidate()sees a stale texture and no diagnostic.In-place resize (RFC 0004, unblocked once R-30 landed)
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
framerectangles are still validated at write time only — a frame authored against the previous size may be stale until the application writes it again.