ReadonlyidStable identity (§77, §83), assigned at construction from a monotonic
counter and formatted texture-<n>. Unique within a process, ascending in
construction order, never reused.
How many anisotropic samples this texture asks for (§77; R-30b), 1 when
the source names none.
A request: the backend clamps it to the device maximum and ignores it
where EXT_texture_filter_anisotropic is absent — see
TextureSource.anisotropy for why that is §62's answer rather than
§85's.
Bytes this texture describes (§83, §84's textureMemory) — four per texel
at the current size, which is what an RGBA8 upload costs.
new Texture({ width: 256, height: 256 }).byteLength; // 262144
Counted from the size, not from data: a source with no CPU-side bytes
still makes the backend allocate zero-filled storage of exactly this size
(see TextureSource.data), so billing it zero would under-report the
memory the engine holds.
Mip levels count since R-30b (2026-08-21): a texture with
mipmaps: true bills the whole chain, summed level by level down to 1 × 1
— about a third more, and exactly a third more only for a square
power-of-two texture, which is why it is summed rather than multiplied by
4/3. A texture with no chain is unchanged, so no existing §84 number
moves.
new Texture({ width: 4, height: 4, mipmaps: true }).byteLength;
// 64 + 16 + 4 = 84
0 once disposed, because a disposed texture holds nothing — one rule,
so that a write into a disposed texture (already a §83 "disposed resource
still in use" mistake) cannot resurrect its bytes in the process-wide
totals.
The colour space of the texels (§60a). Resolved here rather than left
optional so the backend reads one value and never repeats the ??;
MaterialTexture.colorSpace stays optional because a test double and
every pre-R-15 texture satisfy it unchanged.
Authored colorSpace always wins. When it is omitted:
TextureSource.role "color" resolves to "srgb", and every
other case — "data", or no role at all — stays "linear", the dated
R-15 default. See TextureSource.role for the table.
The RGBA8 bytes, or null when the source carries none.
A flat accessor rather than source.data because it is the field a backend
reads on the upload path, and because SpriteMaterial's SpriteTexture
contract — the seam through which a backend actually sees a texture — is
expressed in terms of it. null rather than undefined for the same
reason: the contract is "there is no data", and a backend narrows it with
one comparison.
§77 target kind. "2d" when the source names none — the only
kind this tier uploads. See TextureSource.dimension.
Whether Texture.dispose has run.
How this texture is sampled between texel centres (§77; R-30), "linear"
when the source names none — see TextureSource.filter.
Resolved here rather than left optional for colorSpace's reason: the
backend reads one value and never repeats the ??, while
MaterialTexture.filter stays optional so every pre-R-30 texture and every
test double satisfies the contract unchanged.
Sampler state is read at upload time. Changing it on a texture the backend has already uploaded therefore needs Texture.markDirty (or a new Texture.source, which bumps the version for you) — the same announcement an in-place texel edit needs, and for the same reason.
Height in texels.
How this texture is sampled when minified (§77; R-30b), derived when the source names none: Texture.filter without a mip chain, and its chain-aware form with one — see TextureSource.minFilter.
Resolved here for Texture.filter's reason: the backend reads one
value and repeats no ??, and a texture that predates the field resolves
to exactly the value the backend wrote before it existed.
Whether this texture carries a mip chain (§77; R-30b), false when the
source says nothing — see TextureSource.mipmaps.
Like every other piece of sampler state, it is read at upload time: turning it on for a texture a backend already holds needs Texture.markDirty (or a new Texture.source).
Whether this texture is a colour map or a data map (§60a, §77; R-30c).
null when the source names none — see TextureSource.role. No
default is invented: omitting the field is how every pre-R-30c texture
keeps its dated "linear" colour space.
The texel data this texture holds.
The source object and its data array are held by reference, not
copied: a rasterizer that produced the bytes hands over ownership, and a
backend uploads straight out of them. Assigning a new source validates it
(§85) and bumps Texture.version; editing the existing array in place
is legal and cheap, but invisible here — call Texture.markDirty.
Counter incremented on every mutation (§77). Backends cache GPU uploads
against it; treat it as opaque and compare for inequality, exactly like
Transform.version. Monotonic, never wraps in a realistic session.
Width in texels.
How this texture is addressed outside [0, 1] (§77; R-30),
"clamp-to-edge" when the source names none — see
TextureSource.wrap and Texture.filter's note on when a
change takes effect.
Releases this texture's CPU-side data (§83). Idempotent.
The byte array is dropped — a 2048² atlas is 16 MB, and its owner should be able to reclaim that the moment nothing needs it — and the version is bumped so any backend cache keyed on it re-reads. A backend that then meets this texture in a render list skips the draw: Texture.disposed is the flag the §83 "disposed resource still in use" diagnostic checks, and drawing a sprite with no texels would paint undefined content rather than report the mistake.
Disposing does not notify the materials pointing at this texture; ownership is explicit and upwards (§83), so whoever created the texture decides when nothing needs it any more.
It does remove this texture and its bytes from the process-wide §83
totals (textureMemoryBytes, liveTextureCount), exactly once: the
idempotence guard above is what makes a double dispose() subtract once
rather than twice.
Changed texels since a backend version, or null when a full upload is needed.
Announces a mutation the texture could not see — an in-place write into
source.data. Bumps Texture.version by one, which invalidates every
backend upload keyed on it.
Calling it after assigning Texture.source is harmless, only wasteful: the version advances again and the texture re-uploads once more.
Optionalregion: Rectangle2
A 2D RGBA8 texture (§77) — texel data, an identity, and a version.
Version, not events
Backends cache GPU uploads per texture, keyed on Texture.id and validated by Texture.version; regional dirty updates additionally retain a bounded history for independent backend readers — the identical contract
BufferGeometryoffers, and for the identical reason: a renderer that draws a texture every frame compares a number, whereas a change event would cost a subscription per texture per backend for no extra information. Assigning a new Texture.source validates and bumps the version for you; editing the byte array in place is the fast path and must be announced with Texture.markDirty.Deferred from §77 (named, not dropped)
Cube/array/3D uploads (the field is named — TextureSource.dimension — and non-
"2d"is refused withNOT_IMPLEMENTEDrather than sampled as 2D) and compressed containers. Render-target textures are implemented inrender-target.ts; host bitmap/video sources are inhost-texture.ts, andRenderer.prepareTexturewaits for GPU completion with cache residency diagnostics. RegionalmarkDirtycalls preserve allocations where supported. Every one of them adds public state that a backend, the §79 scene format, and §76's asset manager all have to agree on.Wrap and filter modes left that list on 2026-08-13 (R-30) — see TextureSource.filter and TextureSource.wrap. They were the cheapest member of it by a wide margin, because sampler state is set on the texture object at upload time and read by nothing on the draw path: a
texParameteriargument became a variable, and no frame does one thing more than it did.Mipmaps and anisotropy left it on 2026-08-21 (R-30b) — see TextureSource.mipmaps, TextureSource.minFilter and TextureSource.anisotropy — for the same reason and at the same cost: one more upload-time GL call for the textures that ask, nothing at all for the ones that do not, and no change anywhere on the draw path.
Map rolesleft it on 2026-09-09 (R-30c, the tag) — see TextureSource.role. They are a colour/data annotation, not a sampler-type change: a colour map can default its omittedcolorSpaceto"srgb"without moving goldens that never setrole. What is left on the list are the members that are not upload-time state: a cube or array target changes the sampler type in every shader that reads it, a compressed container changes the upload call and needs a §62 format report. Video frame notification and asynchronous upload completion now have separate implemented entry points, without changing the CPU resource ownership model.