/** * @module ol/renderer/webgl/Map */ import {inherits} from '../../index.js'; import _ol_array_ from '../../array.js'; import _ol_css_ from '../../css.js'; import {createCanvasContext2D} from '../../dom.js'; import _ol_events_ from '../../events.js'; import _ol_has_ from '../../has.js'; import _ol_layer_Layer_ from '../../layer/Layer.js'; import _ol_render_Event_ from '../../render/Event.js'; import _ol_render_EventType_ from '../../render/EventType.js'; import _ol_render_webgl_Immediate_ from '../../render/webgl/Immediate.js'; import _ol_renderer_Map_ from '../Map.js'; import _ol_renderer_Type_ from '../Type.js'; import _ol_source_State_ from '../../source/State.js'; import _ol_structs_LRUCache_ from '../../structs/LRUCache.js'; import _ol_structs_PriorityQueue_ from '../../structs/PriorityQueue.js'; import _ol_webgl_ from '../../webgl.js'; import _ol_webgl_Context_ from '../../webgl/Context.js'; import _ol_webgl_ContextEventType_ from '../../webgl/ContextEventType.js'; /** * @type {number} Texture cache high water mark. */ var WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024; /** * @constructor * @extends {ol.renderer.Map} * @param {Element} container Container. * @param {ol.PluggableMap} map Map. * @api */ var _ol_renderer_webgl_Map_ = function(container, map) { _ol_renderer_Map_.call(this, container, map); /** * @private * @type {HTMLCanvasElement} */ this.canvas_ = /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')); this.canvas_.style.width = '100%'; this.canvas_.style.height = '100%'; this.canvas_.style.display = 'block'; this.canvas_.className = _ol_css_.CLASS_UNSELECTABLE; container.insertBefore(this.canvas_, container.childNodes[0] || null); /** * @private * @type {number} */ this.clipTileCanvasWidth_ = 0; /** * @private * @type {number} */ this.clipTileCanvasHeight_ = 0; /** * @private * @type {CanvasRenderingContext2D} */ this.clipTileContext_ = createCanvasContext2D(); /** * @private * @type {boolean} */ this.renderedVisible_ = true; /** * @private * @type {WebGLRenderingContext} */ this.gl_ = _ol_webgl_.getContext(this.canvas_, { antialias: true, depth: true, failIfMajorPerformanceCaveat: true, preserveDrawingBuffer: false, stencil: true }); /** * @private * @type {ol.webgl.Context} */ this.context_ = new _ol_webgl_Context_(this.canvas_, this.gl_); _ol_events_.listen(this.canvas_, _ol_webgl_ContextEventType_.LOST, this.handleWebGLContextLost, this); _ol_events_.listen(this.canvas_, _ol_webgl_ContextEventType_.RESTORED, this.handleWebGLContextRestored, this); /** * @private * @type {ol.structs.LRUCache.} */ this.textureCache_ = new _ol_structs_LRUCache_(); /** * @private * @type {ol.Coordinate} */ this.focus_ = null; /** * @private * @type {ol.structs.PriorityQueue.} */ this.tileTextureQueue_ = new _ol_structs_PriorityQueue_( /** * @param {Array.<*>} element Element. * @return {number} Priority. * @this {ol.renderer.webgl.Map} */ (function(element) { var tileCenter = /** @type {ol.Coordinate} */ (element[1]); var tileResolution = /** @type {number} */ (element[2]); var deltaX = tileCenter[0] - this.focus_[0]; var deltaY = tileCenter[1] - this.focus_[1]; return 65536 * Math.log(tileResolution) + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; }).bind(this), /** * @param {Array.<*>} element Element. * @return {string} Key. */ function(element) { return /** @type {ol.Tile} */ (element[0]).getKey(); }); /** * @param {ol.PluggableMap} map Map. * @param {?olx.FrameState} frameState Frame state. * @return {boolean} false. * @this {ol.renderer.webgl.Map} */ this.loadNextTileTexture_ = function(map, frameState) { if (!this.tileTextureQueue_.isEmpty()) { this.tileTextureQueue_.reprioritize(); var element = this.tileTextureQueue_.dequeue(); var tile = /** @type {ol.Tile} */ (element[0]); var tileSize = /** @type {ol.Size} */ (element[3]); var tileGutter = /** @type {number} */ (element[4]); this.bindTileTexture( tile, tileSize, tileGutter, _ol_webgl_.LINEAR, _ol_webgl_.LINEAR); } return false; }.bind(this); /** * @private * @type {number} */ this.textureCacheFrameMarkerCount_ = 0; this.initializeGL_(); }; inherits(_ol_renderer_webgl_Map_, _ol_renderer_Map_); /** * Determine if this renderer handles the provided layer. * @param {ol.renderer.Type} type The renderer type. * @return {boolean} The renderer can render the layer. */ _ol_renderer_webgl_Map_['handles'] = function(type) { return _ol_has_.WEBGL && type === _ol_renderer_Type_.WEBGL; }; /** * Create the map renderer. * @param {Element} container Container. * @param {ol.PluggableMap} map Map. * @return {ol.renderer.webgl.Map} The map renderer. */ _ol_renderer_webgl_Map_['create'] = function(container, map) { return new _ol_renderer_webgl_Map_(container, map); }; /** * @param {ol.Tile} tile Tile. * @param {ol.Size} tileSize Tile size. * @param {number} tileGutter Tile gutter. * @param {number} magFilter Mag filter. * @param {number} minFilter Min filter. */ _ol_renderer_webgl_Map_.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) { var gl = this.getGL(); var tileKey = tile.getKey(); if (this.textureCache_.containsKey(tileKey)) { var textureCacheEntry = this.textureCache_.get(tileKey); gl.bindTexture(_ol_webgl_.TEXTURE_2D, textureCacheEntry.texture); if (textureCacheEntry.magFilter != magFilter) { gl.texParameteri( _ol_webgl_.TEXTURE_2D, _ol_webgl_.TEXTURE_MAG_FILTER, magFilter); textureCacheEntry.magFilter = magFilter; } if (textureCacheEntry.minFilter != minFilter) { gl.texParameteri( _ol_webgl_.TEXTURE_2D, _ol_webgl_.TEXTURE_MIN_FILTER, minFilter); textureCacheEntry.minFilter = minFilter; } } else { var texture = gl.createTexture(); gl.bindTexture(_ol_webgl_.TEXTURE_2D, texture); if (tileGutter > 0) { var clipTileCanvas = this.clipTileContext_.canvas; var clipTileContext = this.clipTileContext_; if (this.clipTileCanvasWidth_ !== tileSize[0] || this.clipTileCanvasHeight_ !== tileSize[1]) { clipTileCanvas.width = tileSize[0]; clipTileCanvas.height = tileSize[1]; this.clipTileCanvasWidth_ = tileSize[0]; this.clipTileCanvasHeight_ = tileSize[1]; } else { clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); } clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); gl.texImage2D(_ol_webgl_.TEXTURE_2D, 0, _ol_webgl_.RGBA, _ol_webgl_.RGBA, _ol_webgl_.UNSIGNED_BYTE, clipTileCanvas); } else { gl.texImage2D(_ol_webgl_.TEXTURE_2D, 0, _ol_webgl_.RGBA, _ol_webgl_.RGBA, _ol_webgl_.UNSIGNED_BYTE, tile.getImage()); } gl.texParameteri( _ol_webgl_.TEXTURE_2D, _ol_webgl_.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri( _ol_webgl_.TEXTURE_2D, _ol_webgl_.TEXTURE_MIN_FILTER, minFilter); gl.texParameteri(_ol_webgl_.TEXTURE_2D, _ol_webgl_.TEXTURE_WRAP_S, _ol_webgl_.CLAMP_TO_EDGE); gl.texParameteri(_ol_webgl_.TEXTURE_2D, _ol_webgl_.TEXTURE_WRAP_T, _ol_webgl_.CLAMP_TO_EDGE); this.textureCache_.set(tileKey, { texture: texture, magFilter: magFilter, minFilter: minFilter }); } }; /** * @param {ol.render.EventType} type Event type. * @param {olx.FrameState} frameState Frame state. * @private */ _ol_renderer_webgl_Map_.prototype.dispatchComposeEvent_ = function(type, frameState) { var map = this.getMap(); if (map.hasListener(type)) { var context = this.context_; var extent = frameState.extent; var size = frameState.size; var viewState = frameState.viewState; var pixelRatio = frameState.pixelRatio; var resolution = viewState.resolution; var center = viewState.center; var rotation = viewState.rotation; var vectorContext = new _ol_render_webgl_Immediate_(context, center, resolution, rotation, size, extent, pixelRatio); var composeEvent = new _ol_render_Event_(type, vectorContext, frameState, null, context); map.dispatchEvent(composeEvent); } }; /** * @inheritDoc */ _ol_renderer_webgl_Map_.prototype.disposeInternal = function() { var gl = this.getGL(); if (!gl.isContextLost()) { this.textureCache_.forEach( /** * @param {?ol.WebglTextureCacheEntry} textureCacheEntry * Texture cache entry. */ function(textureCacheEntry) { if (textureCacheEntry) { gl.deleteTexture(textureCacheEntry.texture); } }); } this.context_.dispose(); _ol_renderer_Map_.prototype.disposeInternal.call(this); }; /** * @param {ol.PluggableMap} map Map. * @param {olx.FrameState} frameState Frame state. * @private */ _ol_renderer_webgl_Map_.prototype.expireCache_ = function(map, frameState) { var gl = this.getGL(); var textureCacheEntry; while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { textureCacheEntry = this.textureCache_.peekLast(); if (!textureCacheEntry) { if (+this.textureCache_.peekLastKey() == frameState.index) { break; } else { --this.textureCacheFrameMarkerCount_; } } else { gl.deleteTexture(textureCacheEntry.texture); } this.textureCache_.pop(); } }; /** * @return {ol.webgl.Context} The context. */ _ol_renderer_webgl_Map_.prototype.getContext = function() { return this.context_; }; /** * @return {WebGLRenderingContext} GL. */ _ol_renderer_webgl_Map_.prototype.getGL = function() { return this.gl_; }; /** * @return {ol.structs.PriorityQueue.} Tile texture queue. */ _ol_renderer_webgl_Map_.prototype.getTileTextureQueue = function() { return this.tileTextureQueue_; }; /** * @inheritDoc */ _ol_renderer_webgl_Map_.prototype.getType = function() { return _ol_renderer_Type_.WEBGL; }; /** * @param {ol.events.Event} event Event. * @protected */ _ol_renderer_webgl_Map_.prototype.handleWebGLContextLost = function(event) { event.preventDefault(); this.textureCache_.clear(); this.textureCacheFrameMarkerCount_ = 0; var renderers = this.getLayerRenderers(); for (var id in renderers) { var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]); renderer.handleWebGLContextLost(); } }; /** * @protected */ _ol_renderer_webgl_Map_.prototype.handleWebGLContextRestored = function() { this.initializeGL_(); this.getMap().render(); }; /** * @private */ _ol_renderer_webgl_Map_.prototype.initializeGL_ = function() { var gl = this.gl_; gl.activeTexture(_ol_webgl_.TEXTURE0); gl.blendFuncSeparate( _ol_webgl_.SRC_ALPHA, _ol_webgl_.ONE_MINUS_SRC_ALPHA, _ol_webgl_.ONE, _ol_webgl_.ONE_MINUS_SRC_ALPHA); gl.disable(_ol_webgl_.CULL_FACE); gl.disable(_ol_webgl_.DEPTH_TEST); gl.disable(_ol_webgl_.SCISSOR_TEST); gl.disable(_ol_webgl_.STENCIL_TEST); }; /** * @param {ol.Tile} tile Tile. * @return {boolean} Is tile texture loaded. */ _ol_renderer_webgl_Map_.prototype.isTileTextureLoaded = function(tile) { return this.textureCache_.containsKey(tile.getKey()); }; /** * @inheritDoc */ _ol_renderer_webgl_Map_.prototype.renderFrame = function(frameState) { var context = this.getContext(); var gl = this.getGL(); if (gl.isContextLost()) { return false; } if (!frameState) { if (this.renderedVisible_) { this.canvas_.style.display = 'none'; this.renderedVisible_ = false; } return false; } this.focus_ = frameState.focus; this.textureCache_.set((-frameState.index).toString(), null); ++this.textureCacheFrameMarkerCount_; this.dispatchComposeEvent_(_ol_render_EventType_.PRECOMPOSE, frameState); /** @type {Array.} */ var layerStatesToDraw = []; var layerStatesArray = frameState.layerStatesArray; _ol_array_.stableSort(layerStatesArray, _ol_renderer_Map_.sortByZIndex); var viewResolution = frameState.viewState.resolution; var i, ii, layerRenderer, layerState; for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { layerState = layerStatesArray[i]; if (_ol_layer_Layer_.visibleAtResolution(layerState, viewResolution) && layerState.sourceState == _ol_source_State_.READY) { layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); if (layerRenderer.prepareFrame(frameState, layerState, context)) { layerStatesToDraw.push(layerState); } } } var width = frameState.size[0] * frameState.pixelRatio; var height = frameState.size[1] * frameState.pixelRatio; if (this.canvas_.width != width || this.canvas_.height != height) { this.canvas_.width = width; this.canvas_.height = height; } gl.bindFramebuffer(_ol_webgl_.FRAMEBUFFER, null); gl.clearColor(0, 0, 0, 0); gl.clear(_ol_webgl_.COLOR_BUFFER_BIT); gl.enable(_ol_webgl_.BLEND); gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { layerState = layerStatesToDraw[i]; layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); layerRenderer.composeFrame(frameState, layerState, context); } if (!this.renderedVisible_) { this.canvas_.style.display = ''; this.renderedVisible_ = true; } this.calculateMatrices2D(frameState); if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { frameState.postRenderFunctions.push( /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this)) ); } if (!this.tileTextureQueue_.isEmpty()) { frameState.postRenderFunctions.push(this.loadNextTileTexture_); frameState.animate = true; } this.dispatchComposeEvent_(_ol_render_EventType_.POSTCOMPOSE, frameState); this.scheduleRemoveUnusedLayerRenderers(frameState); this.scheduleExpireIconCache(frameState); }; /** * @inheritDoc */ _ol_renderer_webgl_Map_.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, layerFilter, thisArg2) { var result; if (this.getGL().isContextLost()) { return false; } var viewState = frameState.viewState; var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; for (i = numLayers - 1; i >= 0; --i) { var layerState = layerStates[i]; var layer = layerState.layer; if (_ol_layer_Layer_.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg2, layer)) { var layerRenderer = this.getLayerRenderer(layer); result = layerRenderer.forEachFeatureAtCoordinate( coordinate, frameState, hitTolerance, callback, thisArg); if (result) { return result; } } } return undefined; }; /** * @inheritDoc */ _ol_renderer_webgl_Map_.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { var hasFeature = false; if (this.getGL().isContextLost()) { return false; } var viewState = frameState.viewState; var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; for (i = numLayers - 1; i >= 0; --i) { var layerState = layerStates[i]; var layer = layerState.layer; if (_ol_layer_Layer_.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg, layer)) { var layerRenderer = this.getLayerRenderer(layer); hasFeature = layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); if (hasFeature) { return true; } } } return hasFeature; }; /** * @inheritDoc */ _ol_renderer_webgl_Map_.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, layerFilter, thisArg2) { if (this.getGL().isContextLost()) { return false; } var viewState = frameState.viewState; var result; var layerStates = frameState.layerStatesArray; var numLayers = layerStates.length; var i; for (i = numLayers - 1; i >= 0; --i) { var layerState = layerStates[i]; var layer = layerState.layer; if (_ol_layer_Layer_.visibleAtResolution(layerState, viewState.resolution) && layerFilter.call(thisArg, layer)) { var layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer)); result = layerRenderer.forEachLayerAtPixel( pixel, frameState, callback, thisArg); if (result) { return result; } } } return undefined; }; export default _ol_renderer_webgl_Map_;