2014-04-01 18:13
<< Preserving Software: Emulators | The roads I take... | Integration eines Magento-2-Webshops mit FreeFinance und selbstgebautem Warenmanagement >>
Lantea Maps conversion to WebGL
I blogged about Lantea Maps 18 months ago. As its marketplace listing describes, the app's purpose is displaying maps as well as recording and displaying GPS tracks.
I wrote this app both to scratch an itch (I wanted an OpenStreetMap-based app to record GPS tracks) and to learn a bit more of JavaScript and web app development. As maps are a 2D problem and the track display requires drawing various lines and possibly other location indicators, I wrote this app based on 2D canvas. I started off with some base code on drawing bitmap tile maps to canvas, and wrote the app around that, doing quite some rewriting on the little bit of code I started from. I also ended up splitting map and track into separate canvases so I wouldn't need to redraw everything when deleting the track or when moving the indicator of the last location or similar. Over time, I did a lot of improvements in various areas of the app, from the tile cache in IndexedDB via OpenStreetMap upload of tracks to pinch zooming on touch screens.
Still, performance of the map canvas was not good - on phones (esp. the small 320x480 screens like the ZTE Open), where you only have a handful of 256x256 map tiles to draw, panning was slightly chunky, but on larger screens, like my Android tablet or even my pretty fast desktop, it ranged from bad to awful (like, noticeably waiting from any movement until you saw any drawing of a move map). Also, as it takes until images are loaded (cached from IndexedDB or out from the web) and that's all called asynchronously, the positions the images ended up being drawn often weren't completely correct any more at the time of drawing them. I tried some optimizations with actually grepping the pixels from the canvas, setting them in the new positions and only actually redrawing the images on the borders, but that only helped slightly on small screens while making large ones even worse in performance.
Given what I read and heard about how today's graphics chips and pipelines work, I figured that the problem was with the drawImage() calls to draw the tiles to the canvas as well as the getImageData()/putImageData() calls to move the pixels in the optimizations. All those copy image data between JS and graphics memory, which is slow, and doing it a lot doesn't really fit well with how graphics stacks work nowadays. The only way I heard that should improve that a lot would be to switch from 2D canvas to WebGL (or go to the image-based tile maps that many others are using, but that wouldn't be as much fun). I don't remember all sources for that, but just did get another pointer to a Mozilla Hacks post that explains some of it. And as Google also seems to being moving their Maps site to WebGL (from image-based tiles, mind you), it can't be a really wrong move.
So, I set out to try and learn the pieces of WebGL I needed for this app. You'd guess that Mozilla, who invented that API together with Khronos, would have ample docs on it, but the WebGL MDN page does only have one tutorial for an animated 3D cube and a list of external links. I meanwhile have filed a bug on a WebGL reference so may improve this further in the future, but I started off first trying with the tutorial that MDN has. I didn't get a lot to work there except some basics, and a lot of the commands in there were not very well explained, but the html5rocks tutorial helped me to get things into a better shape, and some amount of trying around and the MSDN WebGL reference helped to understand more and get things actually right.
One thing that was pretty useful there as well was separating the determination of what tiles should be visible and loading them into textures from the actual drawing of the textures to the canvas. By doing the drawing itself on requestAnimationFrame and this being the only thing done when we pan as long as I have all tiles loaded into textures, I save work and should improve performance.
2D Canvas (left) and WebGL (right) version of Lantea Maps on the ZTE Open
As you can see from the images, the 2D canvas and WebGL versions of Lantea Maps do not look different - but then, that was not intended, as the map is the map after all. Right now, you can actually test both versions, though: I have not moved the WebGL to production yet, so lantea.kairo.at still uses 2D canvas, while the staging version lantea-dev.kairo.at already is WebGL. You'll notice that panning the map is way more fluid in the new version and the tile distortions that could happen with delayed loading in the old one do not happen. I still wonder though why it sometimes happens that you have to wait very long for tiles to load, esp. after zooming. I still need to figure that out at some point, but things render after waiting, so I found it OK for now. Also, I found the WebGL app to work fine on Firefox desktop (Linux), Firefox for Android, as well as Firefox OS (1.1, 1.2, and 1.5/Nightly).
So, I'm happy I did manage the conversion and learn some WebGL, though there's still a lot to be done. And as always, the code to Lantea Maps is available in my public git as well as GitHub if you want to learn or help.
I wrote this app both to scratch an itch (I wanted an OpenStreetMap-based app to record GPS tracks) and to learn a bit more of JavaScript and web app development. As maps are a 2D problem and the track display requires drawing various lines and possibly other location indicators, I wrote this app based on 2D canvas. I started off with some base code on drawing bitmap tile maps to canvas, and wrote the app around that, doing quite some rewriting on the little bit of code I started from. I also ended up splitting map and track into separate canvases so I wouldn't need to redraw everything when deleting the track or when moving the indicator of the last location or similar. Over time, I did a lot of improvements in various areas of the app, from the tile cache in IndexedDB via OpenStreetMap upload of tracks to pinch zooming on touch screens.
Still, performance of the map canvas was not good - on phones (esp. the small 320x480 screens like the ZTE Open), where you only have a handful of 256x256 map tiles to draw, panning was slightly chunky, but on larger screens, like my Android tablet or even my pretty fast desktop, it ranged from bad to awful (like, noticeably waiting from any movement until you saw any drawing of a move map). Also, as it takes until images are loaded (cached from IndexedDB or out from the web) and that's all called asynchronously, the positions the images ended up being drawn often weren't completely correct any more at the time of drawing them. I tried some optimizations with actually grepping the pixels from the canvas, setting them in the new positions and only actually redrawing the images on the borders, but that only helped slightly on small screens while making large ones even worse in performance.
Given what I read and heard about how today's graphics chips and pipelines work, I figured that the problem was with the drawImage() calls to draw the tiles to the canvas as well as the getImageData()/putImageData() calls to move the pixels in the optimizations. All those copy image data between JS and graphics memory, which is slow, and doing it a lot doesn't really fit well with how graphics stacks work nowadays. The only way I heard that should improve that a lot would be to switch from 2D canvas to WebGL (or go to the image-based tile maps that many others are using, but that wouldn't be as much fun). I don't remember all sources for that, but just did get another pointer to a Mozilla Hacks post that explains some of it. And as Google also seems to being moving their Maps site to WebGL (from image-based tiles, mind you), it can't be a really wrong move.
So, I set out to try and learn the pieces of WebGL I needed for this app. You'd guess that Mozilla, who invented that API together with Khronos, would have ample docs on it, but the WebGL MDN page does only have one tutorial for an animated 3D cube and a list of external links. I meanwhile have filed a bug on a WebGL reference so may improve this further in the future, but I started off first trying with the tutorial that MDN has. I didn't get a lot to work there except some basics, and a lot of the commands in there were not very well explained, but the html5rocks tutorial helped me to get things into a better shape, and some amount of trying around and the MSDN WebGL reference helped to understand more and get things actually right.
One thing that was pretty useful there as well was separating the determination of what tiles should be visible and loading them into textures from the actual drawing of the textures to the canvas. By doing the drawing itself on requestAnimationFrame and this being the only thing done when we pan as long as I have all tiles loaded into textures, I save work and should improve performance.
2D Canvas (left) and WebGL (right) version of Lantea Maps on the ZTE Open
As you can see from the images, the 2D canvas and WebGL versions of Lantea Maps do not look different - but then, that was not intended, as the map is the map after all. Right now, you can actually test both versions, though: I have not moved the WebGL to production yet, so lantea.kairo.at still uses 2D canvas, while the staging version lantea-dev.kairo.at already is WebGL. You'll notice that panning the map is way more fluid in the new version and the tile distortions that could happen with delayed loading in the old one do not happen. I still wonder though why it sometimes happens that you have to wait very long for tiles to load, esp. after zooming. I still need to figure that out at some point, but things render after waiting, so I found it OK for now. Also, I found the WebGL app to work fine on Firefox desktop (Linux), Firefox for Android, as well as Firefox OS (1.1, 1.2, and 1.5/Nightly).
So, I'm happy I did manage the conversion and learn some WebGL, though there's still a lot to be done. And as always, the code to Lantea Maps is available in my public git as well as GitHub if you want to learn or help.
Entry written by KaiRo and posted on March 29th, 2014 01:02 | Tags: B2G, Firefox OS, Lantea, Mozilla, OSM, Web Apps, WebGL | 2 comments | TrackBack
Comments