Skip to content

Mesh.textureRepeat mutates the shared per-image TextureAtlas.repeat (wrap mode is image-global) #1503

@obiot

Description

@obiot

Summary

Mesh's new textureRepeat setting (added with the glTF node-animation work) sets the wrap mode by mutating this.texture.repeat:

// packages/melonjs/src/renderable/mesh.js (constructor)
if (hasRealTexture && typeof settings.textureRepeat === "string") {
    this.texture.repeat = settings.textureRepeat;
}

this.texture comes from resolveTextureAtlas(...)game.renderer.cache.get(image, …), which returns a single TextureAtlas shared per source image. So setting .repeat changes the wrap mode image-globally — every consumer of the same image object samples with that wrap.

Surfaced in the PR-2 code review; not a live bug today, but worth pinning before it bites.

How it's silent

  • The only consumer that sets textureRepeat today is the glTF loader (GLTFScene / GLTFModel), driven by the material's sampler wrapS/wrapT.
  • glTF decodes its own HTMLImageElement objects per asset, so the atlas is effectively private to that load.
  • All primitives of a given material share one sampler → one wrap mode, so meshes sharing the image (e.g. all 6 parts of a Kenney blocky character share texture-a.png) all want the same wrap → mutating the shared atlas is consistent.
  • The GL unit cache is keyed (source, repeat) (post-WebGL: createPattern cache-key collision invalidates earlier pattern handles for the same image #1448), so distinct wrap modes still get distinct GL units — but the TextureAtlas.repeat field the batcher reads at upload time is the shared one.

The trigger

Pointing two Meshes (or a Mesh and a Sprite) at the same image object but wanting different wrap modes: last-writer-wins, and the other consumer's wrap silently flips. Niche today, but it's unguarded shared-state mutation — same class as #1489.

Options

Recommendation

Address as part of #1410 (the TextureCache/batcher renderer-agnostic refactor). Same root cause as #1489 (per-image atlas sharing); both should be resolved together there.

Cross-refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions