-
-
Notifications
You must be signed in to change notification settings - Fork 730
/
render_to_texture.ts
179 lines (163 loc) · 8 KB
/
render_to_texture.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import Painter from './painter';
import Tile from '../source/tile';
import {Color} from '@maplibre/maplibre-gl-style-spec';
import {OverscaledTileID} from '../source/tile_id';
import {drawTerrain} from './draw_terrain';
import Style from '../style/style';
import Terrain from './terrain';
import RenderPool from '../gl/render_pool';
import Texture from './texture';
import type StyleLayer from '../style/style_layer';
// lookup table which layers should rendered to texture
const LAYERS: { [keyof in StyleLayer['type']]?: boolean } = {
background: true,
fill: true,
line: true,
raster: true,
hillshade: true
};
/**
* RenderToTexture
*/
export default class RenderToTexture {
painter: Painter;
terrain: Terrain;
pool: RenderPool;
// 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;
// a list of tiles that can potentially rendered
_renderableTiles: Array<Tile>;
// a list of tiles that should be rendered to screen in the next render-call
_rttTiles: Array<Tile>;
// a list of all layer-ids which should be rendered
_renderableLayerIds: Array<string>;
constructor(painter: Painter, terrain: Terrain) {
this.painter = painter;
this.terrain = terrain;
this.pool = new RenderPool(painter.context, 30, terrain.sourceCache.tileSize * terrain.qualityFactor);
}
destruct() {
this.pool.destruct();
}
getTexture(tile: Tile): Texture {
return this.pool.getObjectForId(tile.rtt[this._stacks.length - 1].id).texture;
}
prepareForRender(style: Style, zoom: number) {
this._stacks = [];
this._prevType = null;
this._rttTiles = [];
this._renderableTiles = this.terrain.sourceCache.getRenderableTiles();
this._renderableLayerIds = style._order.filter(id => !style._layers[id].isHidden(zoom));
this._coordsDescendingInv = {};
for (const id in style.sourceCaches) {
this._coordsDescendingInv[id] = {};
const tileIDs = style.sourceCaches[id].getVisibleCoordinates();
for (const tileID of tileIDs) {
const keys = this.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]);
}
}
}
this._coordsDescendingInvStr = {};
for (const id of style._order) {
const layer = style._layers[id], source = layer.source;
if (LAYERS[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();
}
}
}
// check tiles to render
for (const tile of this._renderableTiles) {
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.rttCoords[source]) tile.rtt = [];
}
}
}
/**
* 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 {
if (layer.isHidden(this.painter.transform.zoom)) return false;
const type = layer.type;
const painter = this.painter;
const isLastLayer = this._renderableLayerIds[this._renderableLayerIds.length - 1] === layer.id;
// remember background, fill, line & raster layer to render into a stack
if (LAYERS[type]) {
// create a new stack if previous layer was not rendered to texture (f.e. symbols)
if (!this._prevType || !LAYERS[this._prevType]) this._stacks.push([]);
// push current render-to-texture layer to render-stack
this._prevType = type;
this._stacks[this._stacks.length - 1].push(layer.id);
// 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 (LAYERS[this._prevType] || (LAYERS[type] && isLastLayer)) {
this._prevType = type;
const stack = this._stacks.length - 1, layers = this._stacks[stack] || [];
for (const tile of this._renderableTiles) {
// if render pool is full draw current tiles to screen and free pool
if (this.pool.isFull()) {
drawTerrain(this.painter, this.terrain, this._rttTiles);
this._rttTiles = [];
this.pool.freeAllObjects();
}
this._rttTiles.push(tile);
// check for cached PoolObject
if (tile.rtt[stack]) {
const obj = this.pool.getObjectForId(tile.rtt[stack].id);
if (obj.stamp === tile.rtt[stack].stamp) {
this.pool.useObject(obj);
continue;
}
}
// get free PoolObject
const obj = this.pool.getOrCreateFreeObject();
this.pool.useObject(obj);
this.pool.stampObject(obj);
tile.rtt[stack] = {id: obj.id, stamp: obj.stamp};
// prepare PoolObject for rendering
painter.context.bindFramebuffer.set(obj.fbo.framebuffer);
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.context.viewport.set([0, 0, obj.fbo.width, obj.fbo.height]);
painter._renderTileClippingMasks(layer, coords);
painter.renderLayer(painter, painter.style.sourceCaches[layer.source], layer, coords);
if (layer.source) tile.rttCoords[layer.source] = this._coordsDescendingInvStr[layer.source][tile.tileID.key];
}
}
drawTerrain(this.painter, this.terrain, this._rttTiles);
this._rttTiles = [];
this.pool.freeAllObjects();
return LAYERS[type];
}
return false;
}
}