UNPKG

ol

Version:

OpenLayers mapping library

394 lines (357 loc) • 11 kB
/** * @module ol/renderer/webgl/VectorTileLayer */ import EventType from '../../events/EventType.js'; import {getIntersection} from '../../extent.js'; import {ShaderBuilder} from '../../render/webgl/ShaderBuilder.js'; import VectorStyleRenderer, { convertStyleToShaders, } from '../../render/webgl/VectorStyleRenderer.js'; import { create as createTransform, makeInverse as makeInverseTransform, multiply as multiplyTransform, setFromArray as setFromTransform, } from '../../transform.js'; import { create as createMat4, fromTransform as mat4FromTransform, } from '../../vec/mat4.js'; import WebGLArrayBuffer from '../../webgl/Buffer.js'; import {AttributeType} from '../../webgl/Helper.js'; import WebGLRenderTarget from '../../webgl/RenderTarget.js'; import TileGeometry from '../../webgl/TileGeometry.js'; import {ELEMENT_ARRAY_BUFFER, STATIC_DRAW} from '../../webgl.js'; import WebGLBaseTileLayerRenderer, { Uniforms as BaseUniforms, } from './TileLayerBase.js'; export const Uniforms = { ...BaseUniforms, TILE_MASK_TEXTURE: 'u_depthMask', TILE_ZOOM_LEVEL: 'u_tileZoomLevel', }; export const Attributes = { POSITION: 'a_position', }; /** * @typedef {import('../../render/webgl/VectorStyleRenderer.js').StyleShaders} StyleShaders */ /** * @typedef {import('../../style/flat.js').FlatStyleLike | Array<StyleShaders> | StyleShaders} LayerStyle */ /** * @typedef {Object} Options * @property {LayerStyle} style Flat vector style; also accepts shaders * @property {import('../../style/flat.js').StyleVariables} [variables] Style variables. Each variable must hold a literal value (not * an expression). These variables can be used as {@link import("../../expr/expression.js").ExpressionValue expressions} in the styles properties * using the `['var', 'varName']` operator. * @property {boolean} [disableHitDetection=false] Setting this to true will provide a slight performance boost, but will * prevent all hit detection on the layer. * @property {number} [cacheSize=512] The vector tile cache size. */ /** * @typedef {import("../../layer/BaseTile.js").default} LayerType */ /** * @classdesc * WebGL renderer for vector tile layers. Experimental. * @extends {WebGLBaseTileLayerRenderer<LayerType>} */ class WebGLVectorTileLayerRenderer extends WebGLBaseTileLayerRenderer { /** * @param {LayerType} tileLayer Tile layer. * @param {Options} options Options. */ constructor(tileLayer, options) { super(tileLayer, { cacheSize: options.cacheSize, uniforms: { [Uniforms.PATTERN_ORIGIN]: [0, 0], [Uniforms.TILE_MASK_TEXTURE]: () => this.tileMaskTarget_.getTexture(), }, }); /** * @type {boolean} * @private */ this.hitDetectionEnabled_ = !options.disableHitDetection; /** * @type {LayerStyle} * @private */ this.style_ = null; /** * @type {import('../../style/flat.js').StyleVariables} * @private */ this.styleVariables_ = options.variables || {}; /** * @type {VectorStyleRenderer} * @private */ this.styleRenderer_ = null; /** * This transform is updated on every frame and is the composition of: * - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`) * - current world->screen transform * @type {import("../../transform.js").Transform} * @private */ this.currentFrameStateTransform_ = createTransform(); /** * @private */ this.tmpTransform_ = createTransform(); /** * @private */ this.tmpMat4_ = createMat4(); /** * @type {WebGLRenderTarget} * @private */ this.tileMaskTarget_ = null; /** * @private */ this.tileMaskIndices_ = new WebGLArrayBuffer( ELEMENT_ARRAY_BUFFER, STATIC_DRAW, ); this.tileMaskIndices_.fromArray([0, 1, 3, 1, 2, 3]); /** * @type {Array<import('../../webgl/Helper.js').AttributeDescription>} * @private */ this.tileMaskAttributes_ = [ { name: Attributes.POSITION, size: 2, type: AttributeType.FLOAT, }, ]; /** * @type {WebGLProgram} * @private */ this.tileMaskProgram_; this.applyOptions_(options); } /** * @param {Options} options Options. * @override */ reset(options) { super.reset(options); this.applyOptions_(options); if (this.helper) { this.createRenderers_(); this.initTileMask_(); } } /** * @param {Options} options Options. * @private */ applyOptions_(options) { this.style_ = options.style; } /** * @private */ createRenderers_() { function addBuilderParams(builder) { const exisitingDiscard = builder.getFragmentDiscardExpression(); const discardFromMask = `texture2D(${Uniforms.TILE_MASK_TEXTURE}, gl_FragCoord.xy / u_pixelRatio / u_viewportSizePx).r * 50. > ${Uniforms.TILE_ZOOM_LEVEL} + 0.5`; builder.setFragmentDiscardExpression( exisitingDiscard !== 'false' ? `(${exisitingDiscard}) || (${discardFromMask})` : discardFromMask, ); builder.addUniform(Uniforms.TILE_MASK_TEXTURE, 'sampler2D'); builder.addUniform(Uniforms.TILE_ZOOM_LEVEL, 'float'); } const styleShaders = convertStyleToShaders( this.style_, this.styleVariables_, ); for (const styleShader of styleShaders) { addBuilderParams(styleShader.builder); } this.styleRenderer_ = new VectorStyleRenderer( styleShaders, this.styleVariables_, this.helper, this.hitDetectionEnabled_, ); } /** * @private */ initTileMask_() { this.tileMaskTarget_ = new WebGLRenderTarget(this.helper); const builder = new ShaderBuilder() .setFillColorExpression( `vec4(${Uniforms.TILE_ZOOM_LEVEL} / 50., 0., 0., 1.)`, ) .addUniform(Uniforms.TILE_ZOOM_LEVEL, 'float'); this.tileMaskProgram_ = this.helper.getProgram( builder.getFillFragmentShader(), builder.getFillVertexShader(), ); this.helper.flushBufferData(this.tileMaskIndices_); } /** * @override */ afterHelperCreated() { this.createRenderers_(); this.initTileMask_(); } /** * @override */ createTileRepresentation(options) { const tileRep = new TileGeometry(options, this.styleRenderer_); // redraw the layer when the tile is ready const listener = () => { if (tileRep.ready) { this.getLayer().changed(); tileRep.removeEventListener(EventType.CHANGE, listener); } }; tileRep.addEventListener(EventType.CHANGE, listener); return tileRep; } /** * @override */ beforeTilesRender(frameState, tilesWithAlpha) { super.beforeTilesRender(frameState, true); // always consider that tiles need alpha blending this.helper.makeProjectionTransform( frameState, this.currentFrameStateTransform_, ); } /** * @override */ beforeTilesMaskRender(frameState) { this.helper.makeProjectionTransform( frameState, this.currentFrameStateTransform_, ); const pixelRatio = frameState.pixelRatio; const size = frameState.size; this.tileMaskTarget_.setSize([size[0] * pixelRatio, size[1] * pixelRatio]); this.helper.prepareDrawToRenderTarget( frameState, this.tileMaskTarget_, true, true, ); this.helper.useProgram(this.tileMaskProgram_, frameState); setFromTransform(this.tmpTransform_, this.currentFrameStateTransform_); this.helper.setUniformMatrixValue( Uniforms.PROJECTION_MATRIX, mat4FromTransform(this.tmpMat4_, this.tmpTransform_), ); makeInverseTransform(this.tmpTransform_, this.currentFrameStateTransform_); this.helper.setUniformMatrixValue( Uniforms.SCREEN_TO_WORLD_MATRIX, mat4FromTransform(this.tmpMat4_, this.tmpTransform_), ); return true; } /** * @override */ renderTileMask(tileRepresentation, tileZ, extent, depth) { if (!tileRepresentation.ready) { return; } this.helper.setUniformFloatValue(Uniforms.DEPTH, depth); this.helper.setUniformFloatValue(Uniforms.TILE_ZOOM_LEVEL, tileZ); this.helper.setUniformFloatVec4(Uniforms.RENDER_EXTENT, extent); this.helper.setUniformFloatValue(Uniforms.GLOBAL_ALPHA, 1); this.helper.bindBuffer( /** @type {TileGeometry} */ (tileRepresentation).maskVertices, ); this.helper.bindBuffer(this.tileMaskIndices_); this.helper.enableAttributes(this.tileMaskAttributes_); const renderCount = this.tileMaskIndices_.getSize(); this.helper.drawElements(0, renderCount); } /** * @param {number} alpha Alpha value of the tile * @param {import("../../extent.js").Extent} renderExtent Which extent to restrict drawing to * @param {import("../../transform.js").Transform} batchInvertTransform Inverse of the transformation in which tile geometries are expressed * @param {number} tileZ Tile zoom level * @param {number} depth Depth of the tile * @private */ applyUniforms_(alpha, renderExtent, batchInvertTransform, tileZ, depth) { // world to screen matrix setFromTransform(this.tmpTransform_, this.currentFrameStateTransform_); multiplyTransform(this.tmpTransform_, batchInvertTransform); this.helper.setUniformMatrixValue( Uniforms.PROJECTION_MATRIX, mat4FromTransform(this.tmpMat4_, this.tmpTransform_), ); // screen to world matrix makeInverseTransform(this.tmpTransform_, this.currentFrameStateTransform_); this.helper.setUniformMatrixValue( Uniforms.SCREEN_TO_WORLD_MATRIX, mat4FromTransform(this.tmpMat4_, this.tmpTransform_), ); this.helper.setUniformFloatValue(Uniforms.GLOBAL_ALPHA, alpha); this.helper.setUniformFloatValue(Uniforms.DEPTH, depth); this.helper.setUniformFloatValue(Uniforms.TILE_ZOOM_LEVEL, tileZ); this.helper.setUniformFloatVec4(Uniforms.RENDER_EXTENT, renderExtent); } /** * @override */ renderTile( tileRepresentation, tileTransform, frameState, renderExtent, tileResolution, tileSize, tileOrigin, tileExtent, depth, gutter, alpha, ) { const gutterExtent = getIntersection(tileExtent, renderExtent, tileExtent); const tileZ = tileRepresentation.tile.getTileCoord()[0]; const buffers = tileRepresentation.buffers; if (!buffers) { return; } this.styleRenderer_.render(buffers, frameState, () => { this.applyUniforms_( alpha, gutterExtent, buffers.invertVerticesTransform, tileZ, depth, ); }); } /** * Render declutter items for this layer * @param {import("../../Map.js").FrameState} frameState Frame state. */ renderDeclutter(frameState) {} /** * Clean up. * @override */ disposeInternal() { super.disposeInternal(); } } export default WebGLVectorTileLayerRenderer;