flame-engine / flame-engine/flame

[flame_tiled] skwasm renderer crashes ("memory access out of bounds") on tilemaps with >~2000 batched tiles — upstream Flutter bug

Open
#3,913 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
Dart
Stars
10.8k
Forks
1k
Avg merge
1d 20h
Merged PRs (30d)
21

Description

## Summary

Rendering a moderately large tilemap (≥ ~2000 visible/batched tiles in one layer) on Flutter web with the `skwasm` renderer crashes the engine with `RuntimeError: memory access out of bounds`.

Root cause is upstream in Flutter, not in `flame_tiled` itself, but reporting here so anyone hitting the crash finds the workaround quickly.

## Steps to reproduce

1. `flutter run -d chrome --wasm` with `_flutter.loader.load({config: {renderer: "skwasm"}})` in `web/flutter_bootstrap.js`.
2. Load any `TiledComponent` whose visible tile-layer batch exceeds ~2000 tiles. A 64×64 ground layer triggers it.

## Trace

```
RuntimeError: memory access out of bounds
at $malloc (skwasm.wasm)
at $paint_create (skwasm.wasm)
at $SkwasmPaint.toRawPaint (paint.dart:16)
at $SkwasmCanvas.drawAtlas (canvas.dart, inside withStackScope)
at $SpriteBatch.render (flame sprite_batch.dart:520)
at $FlameTileLayer.render (tile_layer.dart:147)
at $RenderableTiledMap.render (renderable_tile_map.dart:487)
at $TiledComponent.render (tiled_component.dart:83)
```

`FlameTileLayer.render` calls `SpriteBatch.render` which calls `Canvas.drawAtlas` once per layer with one entry per tile. Skwasm's `drawAtlas` writes the per-entry arrays into the wasm stack (`_emscripten_stack_alloc`, default 64 KiB), and ~2000+ entries overflow the stack into the heap; the next `malloc` traps.

## Upstream

- Flutter issue: flutter/flutter#185995
- Proposed fix (chunking on the skwasm side): flutter/flutter#185997

Once that fix lands, no `flame_tiled` change is needed.

## Workaround until upstream fix is released

```dart
final tiled = await TiledComponent.load(
'wedding_pilgrimage.tmx',
Vector2.all(32),
useAtlas: false, // bypasses SpriteBatch.drawAtlas → uses drawImageRect per tile
);
```

`useAtlas: false` switches `FlameTileLayer` to a per-tile `Canvas.drawImageRect` path instead of `SpriteBatch.drawAtlas`. It boots fine, but expect ~10× lower FPS in debug and a measurable drop in release because the GPU batching is gone (in our case ~100 FPS skwasm + `useAtlas: false` vs ~120 FPS canvaskit + `useAtlas: true`). Acceptable as a stop-gap; not as a permanent solution.

The other workaround is to switch the web bootstrap to `renderer: "canvaskit"`, which doesn't share this code path.

## Suggested README/docs note

A short note in the `flame_tiled` README under "Web / known issues" pointing at flutter/flutter#185995 and the `useAtlas: false` escape hatch would save future users a lot of time — the crash signature points at `paint_create` rather than `drawAtlas`, so it's easy to misdiagnose. Happy to PR the docs change if useful.

## Versions

- `flame: ^1.37.0`
- `flame_tiled: ^3.1.1`
- Flutter `3.44.0-0.3.pre`, Dart `3.12.0-327.4.beta`, engine `285ae5207`
- macOS 15 / arm64, Chrome stable

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.