@ortho-earth/globe and @ortho-earth/core are now 2.1.
With 2.0 the features I wanted were all in place, so this release does not add features. It polishes the ones that are there.
Two things went into it. The way the map draws text, which had bothered me for a long time, moved to the GPU. And at the same time, compatibility with MapLibre GL JS went up another step.
What I most want to say about this release, though, is not how much changed. It is that after replacing this much of the machinery, the API is the same as 2.0 and not a single thing broke. This engine has grown more robust than I expected.
1. Map text on the GPU
Place names, road names, icons, road shields: until now every piece of text on the map was drawn one glyph at a time with Canvas2D. The Earth and the buildings were drawn on the GPU, while the text was a 2D picture laid on top by the CPU. That also fixed the order as "the whole map first, then the text", so a MapLibre style that puts lines or fills above its labels could not be drawn as written. In a dense city, drawing the text alone made things heavy. For a long time this was the engine's biggest worry.
In 2.1, text is baked into an SDF glyph atlas (each glyph stored as a distance from its edge) and drawn in one go with WebGPU. Colour, size and halo are decided in the shader, so each glyph is baked once per font. Point labels, labels along lines, icons and road shields, geojson symbol layers, cluster counts, and even the star names and the Moon all go through this one path. While the map is viewed from straight above, text is drawn at its style layer's position, so a style with lines above its labels stacks the same way it does in MapLibre.
- Faster. In the heaviest text scene (Delft in the Netherlands, zoom 17.6, tilted 55°) drawing the text went from 1.18 ms to 0.94 ms per frame, about 20% less. Every scene we measured runs at 60 fps.
- Looks the same. Compared pixel by pixel with the old Canvas2D picture, the difference is under 1%.
- Works without WebGPU. Where WebGPU is available, GPU text is the default. Where it is not (when the map runs on WebGL2), the engine falls back to the old Canvas2D drawing on its own. Add
?text=2dto the URL to choose the old drawing even with WebGPU.
Beyond text, the remaining things that were redrawn in 2D on top of the map every frame also moved to the GPU: cluster circles (map.gadget.cluster), the screen grid, CZML and GPX tracks, colour-graded lines (line-gradient) and patterned lines (line-pattern).
Only two stay in 2D: annotations and pasted images, which share their drawing code with the editing tools.
2. MapLibre compatibility
The yardstick is MapLibre's own 139 official examples. Each one runs the same code in real MapLibre and in ortho-earth, and the two are compared picture by picture and answer by answer (137 of them are scored). You can browse every result at /maplibre.
| Level | Previous run (Oct 14) | 2.1 (Oct 23) |
|---|---|---|
| 3 Same picture | 66 | 79 |
| 2 Same answers | 50 | 47 |
| 1 Runs | 21 | 11 |
| 0 Does not run | 0 | 0 |
Thirteen more examples now draw the same picture, and the ones that run but give different answers halved. Besides drawing text at its layer's position, these changes did the work:
- Style colours, opacities and line widths now change smoothly while zooming (before, they stayed at the value for the zoom of the loaded tile).
- Changing per-feature colours or widths with
setPaintPropertyorfeature-stateupdates the picture at once, without rebuilding tiles. moveLayerfor basemap layers, and inserting your own geojson layers between basemap layers ("a fill under the roads", for example).queryRenderedFeaturesnow answers for heatmap layers, and its tolerance for lines matches MapLibre.getCanvas()before load and plugin events also behave as in MapLibre.
3. Same API as 2.0
For all the changes inside, the public API is the same as 2.0. Code written for 2.0 runs on 2.1 as it is. Only the speed and the correctness of the picture went up.
npm i @ortho-earth/[email protected]
map.gadget.cluster now builds a cluster source and layers inside, the same way MapLibre does. As a result, the layer names that queryRenderedFeatures returns changed from clusters / unclustered-point to gadget-cluster-clusters / gadget-cluster-unclustered. Only code that queried by those layer names needs updating. Clusters made the MapLibre way (addSource with cluster: true and addLayer) keep the layer names you gave them.
4. Built to be tested: Design For Test
2.1 replaced several of the drawing paths deep inside the engine. Even so, across the 137 official examples not a single one scored lower than in the previous run.
I don't think that was luck. It comes from the design. In semiconductors there is a term, DFT (Design For Test): instead of working out how to test a chip after it is made, you build the means of testing into the design from the start. This engine has been built that way all along.
- Test ports built into the engine. The comparison with the official examples does not only look at pictures. The engine has a test window that answers "which text did you place, and where", and each placement is matched against real MapLibre. When text moved to the GPU, placement and collision were deliberately kept in the same place on the CPU, so the same yardstick would still apply after the drawing was replaced wholesale.
- Cut so it can be tested without a GPU. WebGPU pictures cannot be drawn on the cloud test machines. So the logic, such as laying out glyphs and turning glyph shapes into distance images, was cut into pure functions that can be tested with no GPU at all. Only the last step, the drawing itself, needs one.
- New paths beside the old, with a switch. GPU text first went in only behind
?text=gpu, with the Canvas2D path left untouched. One URL switch flips between old and new, so the same scene can be compared pixel by pixel, and you can always go back after release. - Keep the gates green. Every change passes the existing tests and the official examples gate. If even one example drops, the change does not go in. Moving text to the GPU took a dozen or so changes, and about fifty with the compatibility work. Each was cut small enough to go in on its own without breaking anything, and went through the gates one by one.
- Last, the real-hardware gate. Before making GPU text the default, I checked text quality and speed in Tokyo and Delft on the real GPU of my Mac. That check found country names on the world view with slightly thick, blurry halos. We fixed it first, then switched the default. The problem stopped at the gate, before release.
It seems that what six months built up was not only features. Because it is built to be tested, it can take a big change without breaking. With this release I felt that clearly for the first time.