@ortho-earth/globe と @ortho-earth/core を 2.1 にしました。
2.0 で、やりたかった機能はひととおり揃いました。だからこの版は機能を足す版ではなく、今ある機能を磨く版です。
中身は大きく二つあります。長年気になっていた地図の文字の描き方を GPU に移したこと。それと同時に、MapLibre GL JS との互換をもう一段上げたことです。
ただ、この版でいちばん伝えたいのは、変えた量ではありません。これだけ下回りを入れ替えても、使い方は 2.0 のままで、壊れた所が一つも出なかったことです。 この地図エンジンは、思っていた以上にロバストに育っていました。
1. 地図の文字を GPU へ
地名、道路の名前、記号、道路標識。地図の上の文字は、これまでずっと Canvas2D で一字ずつ描いていました。 地球や建物は GPU で描いているのに、文字だけが CPU の 2D の絵を上から重ねている。描く順番も「地図を全部描いてから、最後に文字」と決まってしまうので、文字の上に線や塗りを重ねる MapLibre の style は、そのとおりの絵になりませんでした。文字の多い街では、文字を描くだけで重くもなります。 長いあいだ、ここがこのエンジンのいちばんの懸念でした。
2.1 では、文字を SDF(字の形を「縁からの距離」で持つ画像)の字形アトラスに焼き、WebGPU で一度に描くようにしました。 色・大きさ・縁取りはシェーダで決めるので、字は書体と字の組で一度焼けば済みます。点の注記、線に沿う注記、記号と道路標識、geojson の点の symbol 層、集約(cluster)の件数、星空の星の名前と月まで、文字は全部この同じ道を通ります。 真上から見ている間は、文字を style の層の位置に描くので、「文字の上に線」の style も MapLibre と同じ重なりになります。
- 速くなった。文字のいちばん多い場面(オランダのデルフト、ズーム 17.6、55° 傾けた街並み)で、1 フレームあたりの文字の描画は 1.18 ms から 0.94 ms へ、約 2 割減りました。測った場面はすべて 60 fps です。
- 見た目は同じ。今までの Canvas2D の絵と画素で比べて、差は 1% 未満です。
- WebGPU が無くても動く。WebGPU の環境では GPU の文字が既定です。WebGPU が使えない環境(WebGL2 で動いている時)では、自動で今までの Canvas2D の描き方に戻ります。URL に
?text=2dを付ければ、WebGPU の環境でも元の描き方を選べます。
文字だけでなく、毎フレーム 2D で上から重ね描きしていた残りの物も GPU へ移しました。集約の丸(map.gadget.cluster)、画面格子(screengrid)、CZML と GPX の軌跡、色が変わる線(line-gradient)、模様の線(line-pattern)です。
2D のまま残したのは、編集の道具と描き方を分け合っている注釈と画像の貼り付けの二つだけです。
2. MapLibre との互換
互換の物差しは、MapLibre 自身の公式例 139 本です。一本ずつ本物の MapLibre と ortho-earth で同じコードを走らせ、絵と答えが同じかを撮り比べて採点しています(採点の対象は 137 本)。結果は /maplibre で一本ずつ見られます。
| 段 | 前回の撮り比べ(10 月 14 日) | 2.1(10 月 23 日) |
|---|---|---|
| 3 同じ絵 | 66 | 79 |
| 2 同じ答え | 50 | 47 |
| 1 動く | 21 | 11 |
| 0 動かない | 0 | 0 |
「同じ絵」が 13 本増え、「動くけれど答えが違う」が半分になりました。効いたのは、文字を層の位置で描けるようになったことのほかに、次のような直しです。
- style の色・不透明度・線幅をズームの途中でも連続に変える(今までは読み込んだタイルのズームの値で止まっていた)。
setPaintPropertyやfeature-stateで地物ごとの色や線幅を変えても、タイルを組み直さずにすぐ絵が変わる。- 基図の層の
moveLayer、そして自分で足した geojson の層を基図の層の間に差し込む(「道の下に塗り」など)。 queryRenderedFeaturesが heatmap の層にも答え、線の当たりの許しも MapLibre と同じになった。起動前のgetCanvas()、プラグインの事象も MapLibre どおりに。
3. 使い方は 2.0 のまま
これだけ中を入れ替えても、公開の口は 2.0 と同じです。2.0 で書いたコードは、そのまま 2.1 で動きます。速さと絵の正しさだけが上がりました。
npm i @ortho-earth/[email protected]
map.gadget.cluster は、中で MapLibre と同じ集約の source と層を作るようになりました。そのため queryRenderedFeatures が返す層の名前が clusters/unclustered-point から gadget-cluster-clusters/gadget-cluster-unclustered に変わっています。この層名を指定して問い合わせていた場合だけ、書き換えてください。MapLibre と同じ書き方(addSource の cluster: true と addLayer)で作った集約は、自分で付けた層の名前のままです。
4. 試せるように作る:Design For Test
2.1 は、エンジンのいちばん奥にある描画の道を何本も入れ替えた版です。それでも、公式例 137 本の撮り比べで、前回より下がった例は 1 本もありませんでした。
これは運ではなく、設計の結果だと思っています。半導体の世界には DFT(Design For Test)という言葉があります。作ってから試し方を考えるのではなく、試せるように最初から設計に組み込んでおく、という考え方です。この工房のエンジンも、ずっとそうやって作ってきました。
- 試す口を、エンジンの中に作り付けておく。公式例の撮り比べは、絵を見るだけではありません。エンジンが「どの文字をどこに置いたか」を答える検定の窓を持っていて、本物の MapLibre の置き方と一つずつ突き合わせます。文字を GPU へ移す時も、配置と衝突の判定はあえて CPU の同じ場所に残しました。描き方を丸ごと入れ替えても、同じ物差しがそのまま当たるようにするためです。
- GPU が無くても試せる形に切る。WebGPU の絵はクラウドの試験機では描けません。そこで、字を並べる・字の形を距離の画像にするといった中身は純粋な関数に切り出し、GPU の無い所でも検定できるようにしました。GPU が要るのは「描く」最後の一歩だけです。
- 新しい道は、古い道の横に、切り替えの口つきで作る。GPU の文字は、まず
?text=gpuを付けた時だけ動く形で入れ、Canvas2D の道はそのまま残しました。URL 一つで新旧を切り替えられるので、同じ場面を並べて画素で比べられます。出した後も、いつでも元に戻せます。 - 関門は緑のまま通す。変更ごとに、既存の検定と公式例の門を通します。下がった例が一本でも出たら、その変更は入れません。文字の GPU 化だけで十数本、互換と合わせると 50 本ほどの変更を、それぞれ単独で入れても壊れない大きさに切って、一本ずつ通しました。
- 最後は実機の門。既定を GPU に替える前に、私の Mac の実際の GPU で、東京とデルフトの字の質と速さを見ました。この時、世界全図の国名の縁取りが少し太く霞むのが見つかり、それを直してから既定を替えました。問題は、出す前の門で止まりました。
半年かけて積み上げたのは、機能だけではなかったようです。試せるように作ってあるから、大きく変えても壊れない。その手応えを、この版で初めてはっきり感じました。