Documentation
    Preparing search index...

    Class Texture

    A 2D RGBA8 texture (§77) — texel data, an identity, and a version.

    const texture = new Texture({ width: 2, height: 2, data: bytes });
    texture.data![0] = 255; // in-place edit — invisible to the texture
    texture.markDirty(); // announce it: version += 1, backends re-upload
    texture.dispose(); // §83: explicit lifetime

    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 BufferGeometry offers, 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.

    Cube/array/3D uploads (the field is named — TextureSource.dimension — and non-"2d" is refused with NOT_IMPLEMENTED rather than sampled as 2D) and compressed containers. Render-target textures are implemented in render-target.ts; host bitmap/video sources are in host-texture.ts, and Renderer.prepareTexture waits for GPU completion with cache residency diagnostics. Regional markDirty calls 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 texParameteri argument 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 roles left 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 omitted colorSpace to "srgb" without moving goldens that never set role. 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.

    Hierarchy (View Summary)

    Implements

    Index
    id: string = ...

    Stable identity (§77, §83), assigned at construction from a monotonic counter and formatted texture-<n>. Unique within a process, ascending in construction order, never reused.

    • get anisotropy(): number

      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.

      Returns number

    • get byteLength(): number

      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.

      Returns number

    • get colorSpace(): ColorSpace

      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.

      Returns ColorSpace

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

      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.

      Returns Uint8Array<ArrayBufferLike> | null

    • get filter(): MaterialTextureFilter

      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.

      Returns MaterialTextureFilter

    • get height(): number

      Height in texels.

      Returns number

    • get mipmaps(): boolean

      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).

      Returns boolean

    • get version(): number

      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.

      Returns number

    • get width(): number

      Width in texels.

      Returns number

    • 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.

      Returns void

    • Changed texels since a backend version, or null when a full upload is needed.

      Parameters

      • sinceVersion: number

      Returns Rectangle2 | null

    • 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.

      Parameters

      Returns void