Skip to content

Evict one texture unit (LRU) instead of wiping every assignment on exhaustion #1586

Description

@obiot

Split out of #1584 as a self-contained mitigation.

Current state

packages/melonjs/src/video/texture/cache.js:78 — when no texture unit is free:

// No units available — flush the current batch and reset assignments
if (this.renderer.currentBatcher) {
    this.renderer.currentBatcher.flush();
}
this.units.clear();
this.usedUnits.clear();
...
emit(GPU_TEXTURE_CACHE_RESET);

The eviction policy is evict everything. One texture too many discards all N assignments, and the emitted event makes every batcher drop its cached bindings as well — _onTextureCacheReset() (webgl/batchers/material_batcher.js:100) zeroes boundTextures wholesale. resetUnitAssignments() (cache.js:129) is a second entry point to the same wipe.

The flush itself is unavoidable: quads already in the pending batch reference unit indices, so those vertices must be drawn before any unit is reassigned. Discarding the other N-1 assignments is not.

Proposal

Flush, then evict a single least-recently-used unit and keep the rest. The event carries which unit moved, so a batcher forgets one entry instead of its whole binding table.

now after
flushes per overflow 1 1 (unchanged)
textures re-bound per overflow N 1
few hot textures, occasional cold ones thrashes hot ones stay resident
strict round-robin over N+1 textures thrashes still flushes each time, re-binds 1

Never worse than today, and in the common case — a handful of textures carrying most draws, plus a tail of occasional ones — it turns the cliff into a slope.

LRU is defeated by strict round-robin, which evicts precisely the texture needed next, every time. That is expected and accepted here: the flush count in that case is already one per texture and this change does not raise it, while still cutting the re-bind count. Removing the round-robin case structurally is #1584's job, not this ticket's.

Scope

  • texture/cache.js — per-unit last-use tracking and the evict-one path, in both allocateTextureUnit and resetUnitAssignments
  • system/event.ts — the reset event carries a unit index (a full reset stays expressible, for context loss)
  • webgl/batchers/material_batcher.js_onTextureCacheReset(unit) drops one entry; the lit subclass override that also forgets paired normal-map units needs the same narrowing
  • webgpu/texture/store.js subscribes to the same event (:39) — its quad path uses segments rather than units, so confirm whether it needs the narrowed form or is unaffected

Testing

Victim selection and the resulting assignment map are pure logic — no GPU required. Given a sequence of texture requests, assert which unit is evicted and that the survivors keep their assignments. Plus:

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions