Skip to content

feat(tilemap): Uint16Array layerData + drawTileRaw renderer bypass (#1401 foundation)#1445

Open
obiot wants to merge 1 commit into
masterfrom
feat/gpu-tilemap-1401
Open

feat(tilemap): Uint16Array layerData + drawTileRaw renderer bypass (#1401 foundation)#1445
obiot wants to merge 1 commit into
masterfrom
feat/gpu-tilemap-1401

Conversation

@obiot
Copy link
Copy Markdown
Member

@obiot obiot commented May 11, 2026

Summary

First commit toward #1401 — GPU-accelerated tilemap rendering. Refactors the TMX tile-layer data layout and renderer hot path so the upcoming WebGL2 shader path has a contiguous, GPU-uploadable backing store to draw from. No GPU shader code yet — that lands on top of this foundation in a follow-up PR.

What changed

  • TMXLayer.layerData is now a flat Uint16Array (GID + 3-bit flip mask per cell, row-major). Stable user-facing Tile identity is preserved via a lazy cachedTile view-cache that allocates on first cellAt/getTile call — games that never query tiles by coord keep it null for the layer's lifetime.
  • Map parsing decodes TMX layer data straight into bytes — zero Tile allocations during load.
  • Orientation renderers (Orthogonal / Oblique / Isometric / Hexagonal) read layerData directly and dispatch via new drawTileRaw methods on each renderer and on TMXTileset. The per-frame render loop never constructs a Tile.
  • TMXLayer.dataVersion counter bumps on setTile/clearTile — groundwork for GPU upload invalidation in the shader path.
  • buildFlipTransform(matrix, flipMask, w, h) helper shared between Tile.setTileTransform and the new raw render path.

Public API

UnchangedgetTile, setTile, cellAt, clearTile, getTileId, getTileById, getRenderable all work as before.

Wins

  • Memory: per-layer drops ~25× for games that don't query tiles by coord (~40 KB vs ~1 MB on a 100×100 dense layer).
  • Map load: ~500 000 Tile constructor calls saved on a dense 1000×1000 map.
  • Per-frame: zero Tile allocations during render. Modest FPS gain on Canvas (~2–5% in tile-heavy scenes); WebGL gains come with the shader path.

Tests

  • tmxlayer-data.spec.js (68 adversarial encoding tests) — all 8 flip combinations, GID range edge cases, identity stability via lazy cache, bounds validation, dataVersion monotonicity, parser-path no-allocation guard, cross-cell isolation, real-fixture round-trip.
  • tmxlayer-drawraw.spec.js (16 parity tests) — pixel-level byte-for-byte parity between legacy drawTile and new drawTileRaw for every flip combination; spy-verified zero Tile construction during render; buildFlipTransform matches Tile.setTileTransform.
  • Full suite: 3021/3021 passing.
  • CodeRabbit: 0 findings on uncommitted review (final pass).
  • Visual: platformer + isometric-rpg examples render identically to master.

Next

Phase 2 of #1401: WebGLRenderer.drawTileLayer shader path on top of this foundation — single quad per tileset, fragment-shader tile lookup via RG16UI index texture upload.

Test plan

  • Build clean (pnpm -F melonjs build)
  • Vitest suite green
  • CodeRabbit clean
  • Platformer renders identically
  • Isometric-rpg renders identically
  • CI green

🤖 Generated with Claude Code

…1401)

Foundation for the upcoming GPU tilemap shader path. Refactors TMX tile
layer storage and rendering so that:

- TMXLayer.layerData is now a flat Uint16Array (GID + flip mask per cell).
  Stable Tile identity is preserved via a lazy cachedTile view-cache that
  allocates on the first user-facing cellAt/getTile call — games that never
  query tiles by coord keep it null for the layer's lifetime.
- Map parsing decodes TMX layer data straight into bytes — zero Tile
  allocations during parse (saves ~500k constructor calls on a dense
  1000x1000 map).
- The orientation renderers (Orthogonal / Oblique / Isometric / Hexagonal)
  read layerData directly and dispatch via new drawTileRaw methods on each
  renderer and on TMXTileset. The per-frame render loop never constructs a
  Tile, never touches cachedTile.
- TMXLayer.dataVersion counter bumps on setTile/clearTile — groundwork for
  GPU upload invalidation in the shader path.
- New buildFlipTransform(matrix, flipMask, w, h) helper shared between
  Tile.setTileTransform and the new raw render path.
- Public API (getTile, setTile, cellAt, clearTile, getTileId, getTileById,
  getRenderable) is unchanged.

Memory: per-layer drops ~25x for games that don't query tiles by coord
(~40 KB vs ~1 MB on a 100x100 dense layer). Per-frame FPS: modest gain on
Canvas (~2-5% in tile-heavy scenes); the big WebGL win lands when the
shader path comes online on top of this foundation.

Tests: tmxlayer-data.spec.js (68 adversarial encoding tests including all
8 flip combinations + real-fixture snapshot) + tmxlayer-drawraw.spec.js
(16 parity tests between legacy drawTile and new drawTileRaw). 3021/3021
suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 11, 2026 09:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants