maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
158 lines (143 loc) • 7.77 kB
text/typescript
import Painter from './painter';
import Tile from '../source/tile';
import Color from '../style-spec/util/color';
import {OverscaledTileID} from '../source/tile_id';
import {prepareTerrain, drawTerrain} from './draw_terrain';
import type StyleLayer from '../style/style_layer';
/**
* RenderToTexture
*/
export default class RenderToTexture {
painter: Painter;
// this object holds a lookup table which layers should rendered to texture
_renderToTexture: {[keyof in StyleLayer['type']]?: boolean};
// coordsDescendingInv contains a list of all tiles which should be rendered for one render-to-texture tile
// e.g. render 4 raster-tiles with size 256px to the 512px render-to-texture tile
_coordsDescendingInv: {[_: string]: {[_:string]: Array<OverscaledTileID>}} = {};
// create a string representation of all to tiles rendered to render-to-texture tiles
// this string representation is used to check if tile should be re-rendered.
_coordsDescendingInvStr: {[_: string]: {[_:string]: string}} = {};
// store for render-stacks
// a render stack is a set of layers which should be rendered into one texture
// every stylesheet can have multipe stacks. A new stack is created if layers which should
// not rendered to texture sit inbetween layers which should rendered to texture. e.g. hillshading or symbols
_stacks: Array<Array<string>>;
// remember the previous processed layer to check if a new stack is needed
_prevType: string;
// create a lookup which tiles should rendered to texture
_rerender: {[_: string]: boolean};
// a list of tiles that can potentially rendered
_renderableTiles: Array<Tile>;
constructor(painter: Painter) {
this.painter = painter;
this._renderToTexture = {background: true, fill: true, line: true, raster: true};
this._coordsDescendingInv = {};
this._coordsDescendingInvStr = {};
this._stacks = [];
this._prevType = null;
this._rerender = {};
this._renderableTiles = painter.style.terrain.sourceCache.getRenderableTiles();
this._init();
}
_init() {
const style = this.painter.style;
const terrain = style.terrain;
// fill _coordsDescendingInv
for (const id in style.sourceCaches) {
this._coordsDescendingInv[id] = {};
const tileIDs = style.sourceCaches[id].getVisibleCoordinates();
for (const tileID of tileIDs) {
const keys = terrain.sourceCache.getTerrainCoords(tileID);
for (const key in keys) {
if (!this._coordsDescendingInv[id][key]) this._coordsDescendingInv[id][key] = [];
this._coordsDescendingInv[id][key].push(keys[key]);
}
}
}
// fill _coordsDescendingInvStr
for (const id of style._order) {
const layer = style._layers[id], source = layer.source;
if (this._renderToTexture[layer.type]) {
if (!this._coordsDescendingInvStr[source]) {
this._coordsDescendingInvStr[source] = {};
for (const key in this._coordsDescendingInv[source])
this._coordsDescendingInvStr[source][key] = this._coordsDescendingInv[source][key].map(c => c.key).sort().join();
}
}
}
// remove cached textures
this._renderableTiles.forEach(tile => {
for (const source in this._coordsDescendingInvStr) {
// rerender if there are more coords to render than in the last rendering
const coords = this._coordsDescendingInvStr[source][tile.tileID.key];
if (coords && coords !== tile.textureCoords[source]) tile.clearTextures(this.painter);
// rerender if tile is marked for rerender
if (terrain.needsRerender(source, tile.tileID)) tile.clearTextures(this.painter);
}
this._rerender[tile.tileID.key] = !tile.textures.length;
});
terrain.clearRerenderCache();
terrain.sourceCache.removeOutdated(this.painter);
return this;
}
/**
* due that switching textures is relatively slow, the render
* layer-by-layer context is not practicable. To bypass this problem
* this lines of code stack all layers and later render all at once.
* Because of the stylesheet possibility to mixing render-to-texture layers
* and 'live'-layers (f.e. symbols) it is necessary to create more stacks. For example
* a symbol-layer is in between of fill-layers.
* @param {StyleLayer} layer the layer to render
* @returns {boolean} if true layer is rendered to texture, otherwise false
*/
renderLayer(layer: StyleLayer): boolean {
const type = layer.type;
const painter = this.painter;
const layerIds = painter.style._order;
const currentLayer = painter.currentLayer;
const isLastLayer = currentLayer + 1 === layerIds.length;
// remember background, fill, line & raster layer to render into a stack
if (this._renderToTexture[type]) {
if (!this._prevType || !this._renderToTexture[this._prevType]) this._stacks.push([]);
this._prevType = type;
this._stacks[this._stacks.length - 1].push(layerIds[currentLayer]);
// rendering is done later, all in once
if (!isLastLayer) return true;
}
// in case a stack is finished render all collected stack-layers into a texture
if (this._renderToTexture[this._prevType] || type === 'hillshade' || (this._renderToTexture[type] && isLastLayer)) {
this._prevType = type;
const stack = this._stacks.length - 1, layers = this._stacks[stack] || [];
for (const tile of this._renderableTiles) {
prepareTerrain(painter, painter.style.terrain, tile, stack);
if (this._rerender[tile.tileID.key]) {
painter.context.clear({color: Color.transparent});
for (let l = 0; l < layers.length; l++) {
const layer = painter.style._layers[layers[l]];
const coords = layer.source ? this._coordsDescendingInv[layer.source][tile.tileID.key] : [tile.tileID];
painter._renderTileClippingMasks(layer, coords);
painter.renderLayer(painter, painter.style.sourceCaches[layer.source], layer, coords);
if (layer.source) tile.textureCoords[layer.source] = this._coordsDescendingInvStr[layer.source][tile.tileID.key];
}
}
drawTerrain(painter, painter.style.terrain, tile);
}
// the hillshading layer is a special case because it changes on every camera-movement
// so rerender it in any case.
if (type === 'hillshade') {
this._stacks.push([layerIds[currentLayer]]);
for (const tile of this._renderableTiles) {
const coords = this._coordsDescendingInv[layer.source][tile.tileID.key];
prepareTerrain(painter, painter.style.terrain, tile, this._stacks.length - 1);
painter.context.clear({color: Color.transparent});
painter._renderTileClippingMasks(layer, coords);
painter.renderLayer(painter, painter.style.sourceCaches[layer.source], layer, coords);
drawTerrain(painter, painter.style.terrain, tile);
}
return true;
}
return this._renderToTexture[type];
}
return false;
}
}