ol
Version:
OpenLayers mapping library
420 lines (372 loc) • 11.2 kB
JavaScript
/**
* @module ol/renderer/webgl/TileLayer
*/
import TileState from '../../TileState.js';
import {
boundingExtent,
containsCoordinate,
getIntersection,
} from '../../extent.js';
import {fromUserExtent} from '../../proj.js';
import {toSize} from '../../size.js';
import {apply as applyTransform} from '../../transform.js';
import {fromTransform as mat4FromTransform} from '../../vec/mat4.js';
import WebGLArrayBuffer from '../../webgl/Buffer.js';
import {AttributeType} from '../../webgl/Helper.js';
import TileTexture from '../../webgl/TileTexture.js';
import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js';
import WebGLBaseTileLayerRenderer, {
Uniforms as BaseUniforms,
getCacheKey,
} from './TileLayerBase.js';
export const Uniforms = {
...BaseUniforms,
TILE_TEXTURE_ARRAY: 'u_tileTextures',
TEXTURE_PIXEL_WIDTH: 'u_texturePixelWidth',
TEXTURE_PIXEL_HEIGHT: 'u_texturePixelHeight',
TEXTURE_RESOLUTION: 'u_textureResolution', // map units per texture pixel
TEXTURE_ORIGIN_X: 'u_textureOriginX', // map x coordinate of left edge of texture
TEXTURE_ORIGIN_Y: 'u_textureOriginY', // map y coordinate of top edge of texture
};
export const Attributes = {
TEXTURE_COORD: 'a_textureCoord',
};
/**
* @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
*/
const attributeDescriptions = [
{
name: Attributes.TEXTURE_COORD,
size: 2,
type: AttributeType.FLOAT,
},
];
/**
* @typedef {Object} Options
* @property {string} vertexShader Vertex shader source.
* @property {string} fragmentShader Fragment shader source.
* @property {Object<string, import("../../webgl/Helper").UniformValue>} [uniforms] Additional uniforms
* made available to shaders.
* @property {Array<import("../../webgl/PaletteTexture.js").default>} [paletteTextures] Palette textures.
* @property {number} [cacheSize=512] The texture cache size.
* @property {Array<import('./Layer.js').PostProcessesOptions>} [postProcesses] Post-processes definitions.
*/
/**
* @typedef {import("../../webgl/TileTexture.js").TileType} TileTextureType
*/
/**
* @typedef {import("../../webgl/TileTexture.js").default} TileTextureRepresentation
*/
/**
* @classdesc
* WebGL renderer for tile layers.
* @template {import("../../layer/WebGLTile.js").default|import("../../layer/Flow.js").default} LayerType
* @extends {WebGLBaseTileLayerRenderer<LayerType, TileTextureType, TileTextureRepresentation>}
* @api
*/
class WebGLTileLayerRenderer extends WebGLBaseTileLayerRenderer {
/**
* @param {LayerType} tileLayer Tile layer.
* @param {Options} options Options.
*/
constructor(tileLayer, options) {
super(tileLayer, options);
/**
* @type {WebGLProgram}
* @private
*/
this.program_;
/**
* @private
*/
this.vertexShader_ = options.vertexShader;
/**
* @private
*/
this.fragmentShader_ = options.fragmentShader;
/**
* Tiles are rendered as a quad with the following structure:
*
* [P3]---------[P2]
* |` |
* | ` B |
* | ` |
* | ` |
* | A ` |
* | ` |
* [P0]---------[P1]
*
* Triangle A: P0, P1, P3
* Triangle B: P1, P2, P3
*
* @private
*/
this.indices_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW);
this.indices_.fromArray([0, 1, 3, 1, 2, 3]);
/**
* @type {Array<import("../../webgl/PaletteTexture.js").default>}
* @private
*/
this.paletteTextures_ = options.paletteTextures || [];
}
/**
* @param {Options} options Options.
* @override
*/
reset(options) {
super.reset(options);
if (this.helper) {
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
paletteTexture.delete(gl);
}
}
this.vertexShader_ = options.vertexShader;
this.fragmentShader_ = options.fragmentShader;
this.paletteTextures_ = options.paletteTextures || [];
if (this.helper) {
this.program_ = this.helper.getProgram(
this.fragmentShader_,
this.vertexShader_,
);
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
// upload the texture data
paletteTexture.getTexture(gl);
}
}
}
/**
* @override
*/
afterHelperCreated() {
super.afterHelperCreated();
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
// upload the texture data
paletteTexture.getTexture(gl);
}
this.program_ = this.helper.getProgram(
this.fragmentShader_,
this.vertexShader_,
);
this.helper.flushBufferData(this.indices_);
}
/**
* @override
*/
removeHelper() {
if (this.helper) {
const gl = this.helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
paletteTexture.delete(gl);
}
}
super.removeHelper();
}
/**
* @override
*/
createTileRepresentation(options) {
return new TileTexture(options);
}
/**
* @override
*/
beforeTilesRender(frameState, tilesWithAlpha) {
super.beforeTilesRender(frameState, tilesWithAlpha);
this.helper.useProgram(this.program_, frameState);
}
/**
* @override
*/
renderTile(
tileTexture,
tileTransform,
frameState,
renderExtent,
tileResolution,
tileSize,
tileOrigin,
tileExtent,
depth,
gutter,
alpha,
) {
const gl = this.helper.getGL();
this.helper.bindBuffer(tileTexture.coords);
this.helper.bindBuffer(this.indices_);
this.helper.enableAttributes(attributeDescriptions);
let textureSlot = 0;
while (textureSlot < tileTexture.textures.length) {
const uniformName = `${Uniforms.TILE_TEXTURE_ARRAY}[${textureSlot}]`;
this.helper.bindTexture(
tileTexture.textures[textureSlot],
textureSlot,
uniformName,
);
++textureSlot;
}
for (
let paletteIndex = 0;
paletteIndex < this.paletteTextures_.length;
++paletteIndex
) {
const paletteTexture = this.paletteTextures_[paletteIndex];
const texture = paletteTexture.getTexture(gl);
this.helper.bindTexture(texture, textureSlot, paletteTexture.name);
++textureSlot;
}
const viewState = frameState.viewState;
const tileWidthWithGutter = tileSize[0] + 2 * gutter;
const tileHeightWithGutter = tileSize[1] + 2 * gutter;
const tile = tileTexture.tile;
const tileCoord = tile.tileCoord;
const tileCenterI = tileCoord[1];
const tileCenterJ = tileCoord[2];
this.helper.setUniformMatrixValue(
Uniforms.TILE_TRANSFORM,
mat4FromTransform(this.tempMat4, tileTransform),
);
this.helper.setUniformFloatValue(Uniforms.TRANSITION_ALPHA, alpha);
this.helper.setUniformFloatValue(Uniforms.DEPTH, depth);
let gutterExtent = renderExtent;
if (gutter > 0) {
gutterExtent = tileExtent;
getIntersection(gutterExtent, renderExtent, gutterExtent);
}
this.helper.setUniformFloatVec4(Uniforms.RENDER_EXTENT, gutterExtent);
this.helper.setUniformFloatValue(Uniforms.RESOLUTION, viewState.resolution);
this.helper.setUniformFloatValue(Uniforms.ZOOM, viewState.zoom);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_PIXEL_WIDTH,
tileWidthWithGutter,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_PIXEL_HEIGHT,
tileHeightWithGutter,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_RESOLUTION,
tileResolution,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_ORIGIN_X,
tileOrigin[0] +
tileCenterI * tileSize[0] * tileResolution -
gutter * tileResolution,
);
this.helper.setUniformFloatValue(
Uniforms.TEXTURE_ORIGIN_Y,
tileOrigin[1] -
tileCenterJ * tileSize[1] * tileResolution +
gutter * tileResolution,
);
this.helper.drawElements(0, this.indices_.getSize());
}
/**
* @param {import("../../pixel.js").Pixel} pixel Pixel.
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView} Data at the pixel location.
* @override
*/
getData(pixel) {
const gl = this.helper.getGL();
if (!gl) {
return null;
}
const frameState = this.frameState;
if (!frameState) {
return null;
}
const layer = this.getLayer();
const coordinate = applyTransform(
frameState.pixelToCoordinateTransform,
pixel.slice(),
);
const viewState = frameState.viewState;
const layerExtent = layer.getExtent();
if (layerExtent) {
if (
!containsCoordinate(
fromUserExtent(layerExtent, viewState.projection),
coordinate,
)
) {
return null;
}
}
// determine last source suitable for rendering at coordinate
const sources = layer.getSources(
boundingExtent([coordinate]),
viewState.resolution,
);
let i, source, tileGrid;
for (i = sources.length - 1; i >= 0; --i) {
source = sources[i];
if (source.getState() === 'ready') {
tileGrid = source.getTileGridForProjection(viewState.projection);
if (source.getWrapX()) {
break;
}
const gridExtent = tileGrid.getExtent();
if (!gridExtent || containsCoordinate(gridExtent, coordinate)) {
break;
}
}
}
if (i < 0) {
return null;
}
const tileTextureCache = this.tileRepresentationCache;
for (
let z = tileGrid.getZForResolution(viewState.resolution);
z >= tileGrid.getMinZoom();
--z
) {
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
const cacheKey = getCacheKey(source, tileCoord);
if (!tileTextureCache.containsKey(cacheKey)) {
continue;
}
const tileTexture = tileTextureCache.get(cacheKey);
const tile = tileTexture.tile;
if (tile.getState() === TileState.EMPTY) {
return null;
}
if (!tileTexture.loaded) {
continue;
}
const tileOrigin = tileGrid.getOrigin(z);
const tileSize = toSize(tileGrid.getTileSize(z));
const tileResolution = tileGrid.getResolution(z);
const col =
(coordinate[0] - tileOrigin[0]) / tileResolution -
tileCoord[1] * tileSize[0];
const row =
(tileOrigin[1] - coordinate[1]) / tileResolution -
tileCoord[2] * tileSize[1];
return tileTexture.getPixelData(col, row);
}
return null;
}
/**
* Clean up.
* @override
*/
disposeInternal() {
const helper = this.helper;
if (helper) {
const gl = helper.getGL();
for (const paletteTexture of this.paletteTextures_) {
paletteTexture.delete(gl);
}
this.paletteTextures_.length = 0;
gl.deleteProgram(this.program_);
delete this.program_;
helper.deleteBuffer(this.indices_);
}
super.disposeInternal();
delete this.indices_;
}
}
export default WebGLTileLayerRenderer;