iOS: entire render pass silently collapses when a style exceeds 482 symbol layers (482 renders, 483 blanks everything)
Summary
On MapLibre Native iOS, a style containing more than 482 symbol layers stops rendering everything except the background — fills, lines, and circles vanish along with the symbols. The failure is completely silent: no mbgl error, no delegate callback, didFinishLoading fires normally, all network requests return 200. The threshold is exact and reproducible: 482 symbol layers render fully, 483 render nothing but the background.
We hit this shipping an S-52 (nautical chart) style, where per-class × per-scale-band portrayal legitimately produces hundreds of symbol layers. We have since reduced our style below the cliff on our side — this report is about the failure mode: a hard cap that fails silently, with no error surfaced at style load, layer add, or render time.
Environment
- Platform: iOS (
maplibre-gl-native-distribution 6.27.0 via SPM)
- Architectures: identical behavior on x86_64 (simulator) and arm64 — threshold 482/483 on both
- Measured: 2026-07-16
What we measured (bisection)
Starting style: 1,379 layers total (1 background + 803 fill/line/circle + 575 symbol), all over a single vector (MVT) source.
| Slice (background + all 804 non-symbol layers + N symbol layers) |
Result |
| N = 0 … 482 |
✅ full chart renders (fills, lines, circles, symbols) |
| N = 483 … 575 |
❌ background only — every non-symbol layer vanishes too |
| All 575 minus any single bisected layer |
❌ still blank — not a poison layer |
| 520 symbol layers with 100 fill layers removed (1,225 total) |
❌ blank — symbol-count-specific, not total layer count |
- Exceeding the cap does not degrade gracefully (e.g. dropping the excess symbol layers) — it kills the whole render pass.
- Zero errors: nothing from mbgl logging, no
MLNMapViewDelegate failure callback; didFinishLoading fires as if all is well.
Reproduction recipe
No special data needed — any working vector-source style will do:
- Take any style with one vector source, a background layer, a handful of fill/line layers, and one working symbol layer (text or icon).
- Clone the symbol layer N times with distinct ids (identical source/filter is fine).
- Load on iOS. At N ≤ 482 everything renders; at N = 483 the map shows only the background.
Expected
Either of:
- the style renders (large-but-finite symbol layer counts are legal per the style spec), or
- a diagnosable failure: an error/log naming the limit, ideally degrading by dropping excess layers rather than suppressing the entire pass.
Suspicion (unverified)
The exact, architecture-independent integer threshold suggests a fixed-size resource pool or allocation limit on the symbol path (glyph/symbol buffers?) whose exhaustion aborts render-pass composition without reporting. We have not traced it in mbgl source — happy to run instrumented builds against the repro if that helps.
Context
Found while building a native ENC (Electronic Navigational Chart) viewer against CHRT's S-52 style — chart portrayal fans symbol layers per object class × scale band, which is how a legitimate style lands in this range. GL JS renders the same 575-symbol-layer document fine, which is what let it ship unnoticed; the silent nature means native consumers see "empty map" with nothing to debug from.
iOS: entire render pass silently collapses when a style exceeds 482 symbol layers (482 renders, 483 blanks everything)
Summary
On MapLibre Native iOS, a style containing more than 482 symbol layers stops rendering everything except the background — fills, lines, and circles vanish along with the symbols. The failure is completely silent: no mbgl error, no delegate callback,
didFinishLoadingfires normally, all network requests return 200. The threshold is exact and reproducible: 482 symbol layers render fully, 483 render nothing but the background.We hit this shipping an S-52 (nautical chart) style, where per-class × per-scale-band portrayal legitimately produces hundreds of symbol layers. We have since reduced our style below the cliff on our side — this report is about the failure mode: a hard cap that fails silently, with no error surfaced at style load, layer add, or render time.
Environment
maplibre-gl-native-distribution6.27.0 via SPM)What we measured (bisection)
Starting style: 1,379 layers total (1 background + 803 fill/line/circle + 575 symbol), all over a single vector (MVT) source.
MLNMapViewDelegatefailure callback;didFinishLoadingfires as if all is well.Reproduction recipe
No special data needed — any working vector-source style will do:
Expected
Either of:
Suspicion (unverified)
The exact, architecture-independent integer threshold suggests a fixed-size resource pool or allocation limit on the symbol path (glyph/symbol buffers?) whose exhaustion aborts render-pass composition without reporting. We have not traced it in mbgl source — happy to run instrumented builds against the repro if that helps.
Context
Found while building a native ENC (Electronic Navigational Chart) viewer against CHRT's S-52 style — chart portrayal fans symbol layers per object class × scale band, which is how a legitimate style lands in this range. GL JS renders the same 575-symbol-layer document fine, which is what let it ship unnoticed; the silent nature means native consumers see "empty map" with nothing to debug from.