Skip to content

iOS: entire render pass silently collapses when a style exceeds 482 symbol layers (482 renders, 483 blanks everything) #4410

Description

@anthonydimare

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:

  1. Take any style with one vector source, a background layer, a handful of fill/line layers, and one working symbol layer (text or icon).
  2. Clone the symbol layer N times with distinct ids (identical source/filter is fine).
  3. 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.

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