Skip to content

Add TerrainLoadMode selection; use gl-js's terrain control icon - #32

Open
acalcutt wants to merge 11 commits into
mainfrom
feat/terrain-load-mode-and-icon
Open

Add TerrainLoadMode selection; use gl-js's terrain control icon#32
acalcutt wants to merge 11 commits into
mainfrom
feat/terrain-load-mode-and-icon

Conversation

@acalcutt

Copy link
Copy Markdown
Collaborator

Two terrain-control changes that touch the same platform files.

TerrainLoadMode: maplibre-native budgets how much terrain tile-build and drape-render work a frame may do while terrain loads, but none of it was reachable — the C ABI exposed only set_terrain/remove_terrain/ is_terrain_enabled, so every map ran the Quality default (no budget) with no way to change it. Plumbed end to end following the GpsFollowZoomMode pattern: mbgl_map_{set,get}_terrain_load_mode in the C ABI, a TerrainLoadMode enum and MbglMap.TerrainLoadMode in the bindings, a bindable property on MapLibreMap wired through the handler mapper to all three controllers, and a dependency property on MlnMapImage. The controllers cache the value so one set before the map exists is still applied once it does. The sample's Terrain page gains a picker.

Icon: the on-map terrain button drew the ⛰ emoji, which renders as a different picture per platform. It now draws maplibre-gl-js's own maplibregl-ctrl-terrain.svg geometry. That SVG is two closed shapes in a 22x22 box made entirely of straight lines, so it is carried as point lists in a shared TerrainIcon class and fed to each platform's vector primitive — no path parser anywhere. Existing per-platform active/idle colours are kept, so terrain still matches the GPS control.

Builds clean on Windows, Android, iOS and WPF, plus the native lib. Not yet verified visually on a running app, and the load mode is not yet confirmed to change behaviour at runtime.

@jlKampos

jlKampos commented Aug 27, 2026

Copy link
Copy Markdown

acalcutt

The Android native library libmln-cabi.so (shipped in MapLibreNative.Maui 4.5.0)
is not aligned to 16 KB memory pages. Google Play now blocks publishing apps that
are not 16 KB compatible (error: "Your app is not compatible with 16 KB memory
page sizes").

Affected files (from the AAR):

  • base/lib/arm64-v8a/libmln-cabi.so
  • base/lib/x86_64/libmln-cabi.so

The .so needs to be rebuilt with the linker flag:
-Wl,-z,max-page-size=16384

This blocks all apps using this package from publishing to Google Play.
Could you rebuild the Android native library with 16 KB page-size alignment
and release a new version (4.5.1 or similar)?

Environment:

  • MapLibreNative.Maui 4.5.0 (net10.0-android36.0)
  • .NET MAUI / .NET 10
  • Google Play Console: "Your app is not compatible with 16 KB memory page sizes"

acalcutt and others added 10 commits August 28, 2026 08:07
Two terrain-control changes that touch the same platform files.

TerrainLoadMode: maplibre-native budgets how much terrain tile-build and
drape-render work a frame may do while terrain loads, but none of it was
reachable — the C ABI exposed only set_terrain/remove_terrain/
is_terrain_enabled, so every map ran the Quality default (no budget) with
no way to change it. Plumbed end to end following the GpsFollowZoomMode
pattern: mbgl_map_{set,get}_terrain_load_mode in the C ABI, a
TerrainLoadMode enum and MbglMap.TerrainLoadMode in the bindings, a
bindable property on MapLibreMap wired through the handler mapper to all
three controllers, and a dependency property on MlnMapImage. The
controllers cache the value so one set before the map exists is still
applied once it does. The sample's Terrain page gains a picker.

Icon: the on-map terrain button drew the ⛰ emoji, which renders as a
different picture per platform. It now draws maplibre-gl-js's own
maplibregl-ctrl-terrain.svg geometry. That SVG is two closed shapes in a
22x22 box made entirely of straight lines, so it is carried as point
lists in a shared TerrainIcon class and fed to each platform's vector
primitive — no path parser anywhere. Existing per-platform active/idle
colours are kept, so terrain still matches the GPS control.

Builds clean on Windows, Android, iOS and WPF, plus the native lib.
Not yet verified visually on a running app, and the load mode is not yet
confirmed to change behaviour at runtime.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The on-map terrain button now draws maplibre-gl-js's terrain icon, so the
sample text and comments that named the old emoji described a button that
no longer exists. The user-visible line under the map on the MAUI Terrain
page now just says "the terrain button on the map".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The C ABI's rename from mbgl to mln was only half done. Commit 1711663
("mbgl to mln name changes") renamed the files and the library --
mbgl_cabi.h -> mln_cabi.h, mbgl_cabi.cpp -> mln_cabi.cpp, mbgl-cabi ->
mln-cabi, MbglMapHost -> MlnMapHost -- but left all 182 exported
functions, handle types and enums on the old mbgl_ prefix. That commit is
not an ancestor of main; the renamed files reached main through a squash
merge of the unfinished branch, so the inconsistency shipped. The lone
mln_cabi_version export was the only symbol that had made the jump.

The exports now match the files that declare them: mbgl_* -> mln_* and
MBGL_* -> MLN_* throughout the header, the sources, and the EntryPoint
strings the bindings P/Invoke through. The MBGL_STATIC CMake option
follows as MLN_STATIC (nothing in CI passes it).

Deliberately unchanged: maplibre-native's own mbgl-core and
mbgl-vendor-* CMake target names, and the managed binding classes
(MbglMap, MbglStyle, ...), which are a separate public surface.

This breaks native ABI compatibility -- a managed assembly built against
the old names cannot resolve against a new mln-cabi.dll -- so the two
halves must ship together, which the packages already do. Flagged as
BREAKING in the changelog.

Also removes the stale pre-rename binaries left in the gitignored native
drop (mbgl-cabi.dll x2, libmbgl-cabi.a x2); on Android the equivalent
stale .so gets bundled into the APK as dead weight.

Verified: mln-cabi.dll exports 159 mln_* symbols and zero mbgl_*;
Windows, Android, iOS and WPF all build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The release job hands ncipollo/release-action the version verbatim as the
tag and name but never sets `prerelease`, which defaults to false. A
prerelease version therefore published as the latest stable release and
had to be corrected by hand afterwards -- as v5.0.0-experimental.1 was.

Derive the flag from the version string instead: anything carrying a
suffix (5.0.0-pre.2) is a prerelease, a plain 5.0.0 is not. Nothing else
about the release path changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The companion to the C ABI rename, and the last of the mbgl naming to go.
The binding types follow the files and the exports they wrap: MbglMap,
MbglStyle, MbglFrontend, MbglRunLoop, MbglCache, MbglNetwork,
MbglOfflineManager, MbglLayer, MbglSource, MbglStatus, MbglDebugOptions,
MbglLogLevel, MbglHttpError, MbglRenderBackend and the MbglOfflineRegion
family become Mln*, and the seven files declaring them are renamed to
match. The internal _MbglNativeDir MSBuild property becomes
_MlnNativeDir, updated in the bindings csproj and in the two workflows
that pass it on the pack command line. README and docs/ follow; two stale
mbgl_ ABI names left in docs/ by the previous commit are corrected too.

CHANGELOG.md is deliberately left alone below the master section: those
entries describe what shipped under the old names.

Applications using only the MAUI MapLibreMap or the WPF MlnMapImage are
unaffected -- those names never changed. Code reaching past them to the
low-level bindings needs a find-and-replace of Mbgl for Mln, which the
changelog says.

Windows, Android, iOS and WPF all build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The expanded attribution banner capped its TextBlock at a hard-coded
MaxWidth of 320px, on both the MAUI WinUI overlay and its WPF twin. On
any map wider than that the text wrapped into several lines with obvious
empty space beside it, which is what it looked like: a banner that would
not use the width available to it.

Derive the cap from the map's own width instead, less the banner's 10px
margins, its border and its padding, with a 120px floor so a very narrow
map still gets something readable. Both controls re-measure on their
existing size-changed path, so it follows the map through resizes rather
than being set once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The terrain-source record defaulted TileSize to 256, so Mapterhorn - which
serves 512px tiles - was declared at half its real size. That size is what a
source selects tiles with, so it loaded one zoom deeper than the terrain mesh
covers: z13 DEM tiles under a z12 mesh at map zoom 12.

The renderer's per-tile DEM lookup takes an exact match or an ancestor, so a
descendant never matches and every mesh tile fell back to the flat placeholder
DEM. A stale low-zoom DEM left in the cache hid it: those are ancestors and do
match, so the first enable rendered off leftover coarse tiles. Once they aged
out, toggling terrain off and on rebuilt every drawable against a cache holding
only descendants and the map rendered flat until a pan changed the cover.

512 is the style spec's default and what conversion/source.cpp already applies
to a style-JSON source, so it is the right default here too; AWS Terrarium now
declares the 256 its tiles actually are. Note that maplibre-native never reads
tileSize from TileJSON - Tileset has no such field and the converter parses only
tiles/minzoom/maxzoom/attribution/scheme/bounds/encoding - so unlike gl-js, a
DEM's declared size has to be supplied at the call.

Verified with the WPF --terraintest harness driving enable -> disable -> enable
over Innsbruck at z12 pitch 60: a 256-declared source gives 93.6% non-uniform
pixels on the first enable and 4.9% on the second; declared at 512, both are
93.2%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three lines restating what the declaration says, and the last of them is about
to be wrong: maplibre-native now honours tileSize from TileJSON, so a preset
value is a fallback rather than the only thing that sets the size.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings the two core fixes for the terrain-blanking bug, so a wrong tileSize can
no longer flatten the map:

- TileJSON's `tileSize` is honoured, as maplibre-gl-js does. It had been parsed
  and discarded, leaving the tileSize argument at the call site as the only
  thing that could set it, with no corrective when it was wrong.
- Terrain meshes at the tile size the DEM source selects tiles with rather than
  the decoded DEM's pixel dimension. When those disagreed the mesh covered a
  shallower zoom than the DEM loaded, every per-tile DEM lookup missed, and the
  terrain rendered flat off the placeholder DEM.

Also carries upstream main, including the symbol-buffer instancing rework
(#4383), which restructures symbol vertex data onto SSBO instancing on Metal
and Vulkan.

Verified: mln-cabi builds on GL and Vulkan; MauiSample builds for Windows,
Android and iOS and WpfExample for WPF. The WPF --terraintest harness renders
terrain unchanged at Innsbruck z12 pitch 60 - 93.2% non-uniform pixels,
avgRGB (154,158,147), matching the pre-bump build - on both the Mapterhorn and
AWS terrarium DEMs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
maplibre-native #4519 removed the Metal RendererBackend's state-cache methods -
updateAssumedState, assumeFramebufferBinding, assumeViewport, assumeScissorTest,
implicitFramebufferBound, setFramebufferBinding and FramebufferID - which the
ccde428 bump brought in. updateAssumedState was pure virtual, so our Apple
frontend overrode it; with the declaration gone the override no longer compiles
and the iOS native build failed on platform_frontend_apple.mm.

Upstream deleted the same override from every one of its Metal backends
(MLNMapView+Metal, the headless backend, glfw): Metal never needed the GL-style
state cache. Ours goes the same way, leaving getDefaultRenderable / activate /
deactivate, which matches what platform/default's Metal HeadlessBackend
overrides today.

Only Metal lost these. The GL backend still declares them, so the Android and
Windows frontends, which call the same methods, are untouched - which is why
only the iOS leg failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@acalcutt

Copy link
Copy Markdown
Collaborator Author

Can you see if https://github.com/TechIdiots-LLC/MaplibreNativeMAUI/releases/tag/v4.5.1 fixes that for you. Also, I enabled the "Issues" tab so in the future there is a place to report issues..

@acalcutt
acalcutt force-pushed the feat/terrain-load-mode-and-icon branch from 4cb09b7 to 2fc7b64 Compare August 28, 2026 14:02
Every map page put the map and its controls in one ScrollView, so scrolling
down to reach the pickers moved the map up and took the on-map controls with
it - they are children of the map, not of the page. The terrain button went
first, being the topmost of the stack, which reads as the control being cut
off at the top of the map rather than as the page having scrolled.

All eight map pages now put the map in row 0 of a Grid, outside the
ScrollView, with only the control strip beneath it scrolling. SizeToViewport
is unchanged: the map is still a fraction of the page height so a strip of
controls always shows below it, which is what keeps "drag the map to pan" and
"drag the controls to scroll" unambiguous.

Also drops the VerticalStackLayout wrapper that was left with a single child
on five of the pages, and rewrites the layout comments, which described the
old single-scroll arrangement.

Builds on Windows, Android, iOS and WPF. Not yet checked at runtime - the
structure guarantees the map cannot scroll, but the control strip's sizing and
scrolling want a look on a real page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jlKampos

Copy link
Copy Markdown

Can you see if https://github.com/TechIdiots-LLC/MaplibreNativeMAUI/releases/tag/v4.5.1 fixes that for you. Also, I enabled the "Issues" tab so in the future there is a place to report issues..

Fixed
thanks mate

imagem

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