mapbox-gl-js
mapbox-gl-js copied to clipboard
WIP: Custom layer draping support (for globe & terrain)
Fixes https://github.com/mapbox/mapbox-gl-js/issues/11177
https://user-images.githubusercontent.com/549216/173838997-443d9ac5-bb61-4086-a5ec-8c50e417d135.mov
https://user-images.githubusercontent.com/549216/173840428-b10e1373-45e3-42bd-9562-c334b3951dd8.mov
CustomStyleLayer API extended to support draping with new methods:
export type CustomLayerInterface = {
...
// misusing MercatorCoordiate's xyz for tile id (x, y, z) where wrap is baked into x.
renderToTile: ?(gl: WebGLRenderingContext, tileId: MercatorCoordinate) => void,
// return true only for frame when content has changed - otherwise, all the terrain
// render cache would be invalidated and redrawn causing huge drop in performance.
shouldRerenderTiles: ?() => boolean,
}
Detailed explanation on how to use this and combine with exiting methods is provided in https://github.com/mapbox/mapbox-gl-js/pull/11996#issuecomment-1156907145.
Using WebGL Wind demo code (thanks @mourner), for simplicity of use, instead of adding submodule, copied dist code. Full code with change required to enable this demo is available at https://github.com/mapbox/webgl-wind/tree/astojilj-draping-support
Note: the demo uses only one tile z:0, x:0: y:0. Nearest sampling. Calculation provided for mapping ortho matrices of source tiles to render-to-tile tiles is explained in demo's offsetAndScaleForTileMapping.
Launch Checklist
- [ ] briefly describe the changes in this PR
- [ ] include before/after visuals or gifs if this PR includes visual changes
- [ ] write tests for all new functionality
- [ ] document any changes to public APIs
- [ ] post benchmark scores
- [ ] manually test the debug page
- [ ] tagged
@mapbox/map-design-team
@mapbox/static-apis
if this PR includes style spec API or visual changes - [ ] tagged
@mapbox/gl-native
if this PR includes shader changes or needs a native port - [ ] apply changelog label ('bug', 'feature', 'docs', etc) or use the label 'skip changelog'
- [ ] add an entry inside this element for inclusion in the
mapbox-gl-js
changelog:<changelog></changelog>
How would gl-native and gl-js clients use this?
Following methods are explained here:
export type CustomLayerInterface = {
render: CustomRenderMethod,
prerender: ?CustomRenderMethod,
_renderToTile: ?(gl: WebGLRenderingContext, tileId: MercatorCoordinate) => void,
shouldRerenderTiles: ?() => boolean,_
onAdd: ?(map: Map, gl: WebGLRenderingContext) => void,
onRemove: ?(map: Map, gl: WebGLRenderingContext) => void
}
Existing methods render, prerender, onAdd and onRemove are not changing their usage.
-
In case that client code already implements render() method, such method would remain the same - it is called when terrain or globe are not enabled.
-
gl-js has prerender() and this is something we should add to gl-native's CustomLayerHost. This method is called in offscreen pass, before binding main framebuffer, and is used to do various e.g. intermediate render to texture where textures are later used to render to main framebuffer (e.g. prepare hillshade). The same code is in case of WindDemo used to prepare wind particles textures that are later draped in renderToTile method.
gl-js client runs in single threaded environment, and can access Map object to query here if prerender is preceding draping. gl-native callback would need to provide this information, too.
- onAdd and onRemove, or gl-native counterparts initialize and deinitialize, respectively.
New methods:
-
shouldRerenderTiles: ?() => boolean,_ This method is called once per frame, to ask custom layer if there is need to re-render to textures. Terrain and globe approach is based on render to tile texture and then rendering that raster over tile mesh. Tile textures are cached in attempt to prevent unnecessary redrawing of e.g. line and fill layers when camera changes. That is the reason why we could e.g. experience better performance while panning 0 exaggeration terrain compared to panning map with no 2D terrain - all the e.g. line and fill layers are baked into raster and are not getting redrawn. Custom layer can using this method invalidate tile textures (tile textures are ofter referred to as drapes and the process of rendering to textures is called draping in the code comments) when there is change in data. In wind example, this method always returns true to - to optimize performance, wind animation could run e.g. 10 FPS instead of 120 FPS like on the video above.
-
_renderToTile: ?(gl: WebGLRenderingContext, tileId: MercatorCoordinate) => void, Called deep within draping code that is, with framebuffer color attachment set to tile texture, compositing list of drapeable layers one over other, according to order of layers specified in the style. Constraints: custom layer should not write to stencil buffer since stencil buffer is used by other layers to mask overlap and clip geometry. If this constraint is not acceptable, we could add a way for custom layer client to notify what was the change in state.
In gl-native Metal implementation, this method would not offer API access to MTLCommandBuffer and render pass descriptior that would allow custom layer to render to different target than the tile texture currently bound for rendering to.
Open: do we need additional method:
beforeRenderToTiles(listOfTilesToDrape) - or add the same information to prerender(), to inform user of render to texture tile cover.
This is so cool and looks like a really nice approach. 👏 I'm definitely going to take a closer look at this. I wonder if it could be used to accomplish something like rendering a terminator, e.g. https://github.com/mapbox/mapbox-gl-js/issues/7018#issuecomment-1104193476 cc @willymaps
Closing as a duplicate of https://github.com/mapbox/mapbox-gl-js/pull/12182