From 740420468e03ecd7d153711cfe6fe7b396b8c7e5 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 18 Dec 2014 13:20:07 +0100 Subject: [PATCH 1/9] Add hit-detection framebuffer to context --- src/ol/webgl/context.js | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/ol/webgl/context.js b/src/ol/webgl/context.js index 6b91f6c2a7..f0b06a84b1 100644 --- a/src/ol/webgl/context.js +++ b/src/ol/webgl/context.js @@ -66,6 +66,24 @@ ol.webgl.Context = function(canvas, gl) { */ this.currentProgram_ = null; + /** + * @private + * @type {WebGLFramebuffer} + */ + this.hitDetectionFramebuffer_ = null; + + /** + * @private + * @type {WebGLTexture} + */ + this.hitDetectionTexture_ = null; + + /** + * @private + * @type {WebGLRenderbuffer} + */ + this.hitDetectionRenderbuffer_ = null; + /** * @type {boolean} */ @@ -153,6 +171,10 @@ ol.webgl.Context.prototype.disposeInternal = function() { goog.object.forEach(this.shaderCache_, function(shader) { gl.deleteShader(shader); }); + // delete objects for hit-detection + gl.deleteFramebuffer(this.hitDetectionFramebuffer_); + gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_); + gl.deleteTexture(this.hitDetectionTexture_); } }; @@ -174,6 +196,18 @@ ol.webgl.Context.prototype.getGL = function() { }; +/** + * @return {WebGLFramebuffer} The framebuffer for the hit-detection. + * @api + */ +ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() { + if (goog.isNull(this.hitDetectionFramebuffer_)) { + this.initHitDetectionFramebuffer_(); + } + return this.hitDetectionFramebuffer_; +}; + + /** * Get shader from the cache if it's in the cache. Otherwise, create * the WebGL shader, compile it, and add entry to cache. @@ -247,6 +281,7 @@ ol.webgl.Context.prototype.handleWebGLContextLost = function() { goog.object.clear(this.shaderCache_); goog.object.clear(this.programCache_); this.currentProgram_ = null; + this.hitDetectionFramebuffer_ = null; }; @@ -257,6 +292,39 @@ ol.webgl.Context.prototype.handleWebGLContextRestored = function() { }; +/** + * Creates a 1x1 pixel framebuffer for the hit-detection. + * @private + */ +ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { + var gl = this.gl_; + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri( + gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + + var renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, renderbuffer); + + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + this.hitDetectionFramebuffer_ = framebuffer; +}; + + /** * Just return false if that program is used already. Other use * that program (call `gl.useProgram`) and make it the "current From 3ef61fa1c5f89f22d99914488474ec7d4c1c8c7b Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 18 Dec 2014 13:21:20 +0100 Subject: [PATCH 2/9] Add hit-detection support for webgl --- src/ol/render/canvas/canvasreplay.js | 2 + src/ol/render/webgl/webglreplay.js | 190 ++++++++++++++++-- src/ol/renderer/webgl/webglmaprenderer.js | 85 +++++++- .../webgl/webglvectorlayerrenderer.js | 37 ++++ 4 files changed, 293 insertions(+), 21 deletions(-) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index c1459e8a44..8b8eed5689 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1951,6 +1951,8 @@ ol.render.canvas.ReplayGroup.prototype.replay = function( var zs = goog.array.map(goog.object.getKeys(this.replaysByZIndex_), Number); goog.array.sort(zs); + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning var maxExtent = this.maxExtent_; var minX = maxExtent[0]; var minY = maxExtent[1]; diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index 723e5c8164..d017868ef3 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -180,6 +180,13 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.verticesBuffer_ = null; + /** + * Start indices per feature. + * @type {Array.>} + * @private + */ + this.startIndexForFeature_ = []; + /** * @type {number|undefined} * @private @@ -377,6 +384,7 @@ ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry = */ ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry = function(multiPointGeometry, feature) { + this.startIndexForFeature_.push([this.indices_.length, feature]); var flatCoordinates = multiPointGeometry.getFlatCoordinates(); var stride = multiPointGeometry.getStride(); this.drawCoordinates_( @@ -396,6 +404,7 @@ ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry = */ ol.render.webgl.ImageReplay.prototype.drawPointGeometry = function(pointGeometry, feature) { + this.startIndexForFeature_.push([this.indices_.length, feature]); var flatCoordinates = pointGeometry.getFlatCoordinates(); var stride = pointGeometry.getStride(); this.drawCoordinates_( @@ -503,12 +512,14 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) { * @param {number} hue Global hue. * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. * @return {T|undefined} Callback result. * @template T */ ol.render.webgl.ImageReplay.prototype.replay = function(context, center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback) { var gl = context.getGL(); // bind the vertices buffer @@ -609,17 +620,12 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context, } // draw! - goog.asserts.assert(this.textures_.length == this.groupIndices_.length); - var i, ii, start; - for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) { - gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]); - var end = this.groupIndices_[i]; - var numItems = end - start; - var offsetInBytes = start * (context.hasOESElementIndexUint ? 4 : 2); - var elementType = context.hasOESElementIndexUint ? - goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; - gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); - start = end; + var result; + if (!goog.isDef(featureCallback)) { + this.drawReplay_(gl, context); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay_(gl, context, featureCallback); } // disable the vertex attrib arrays @@ -628,6 +634,79 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context, gl.disableVertexAttribArray(locations.a_texCoord); gl.disableVertexAttribArray(locations.a_opacity); gl.disableVertexAttribArray(locations.a_rotateWithView); + + return result; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.ImageReplay.prototype.drawReplay_ = + function(gl, context) { + goog.asserts.assert(this.textures_.length == this.groupIndices_.length); + var elementType = context.hasOESElementIndexUint ? + goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var i, ii, start; + for (i = 0, ii = this.textures_.length, start = 0; i < ii; ++i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]); + var end = this.groupIndices_[i]; + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); + start = end; + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = + function(gl, context, featureCallback) { + goog.asserts.assert(this.textures_.length == this.groupIndices_.length); + var elementType = context.hasOESElementIndexUint ? + goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var i, groupStart, groupEnd, numItems, featureInfo, start, end; + var featureIndex = this.startIndexForFeature_.length - 1; + for (i = this.textures_.length - 1; i >= 0; --i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]); + groupStart = (i > 0) ? this.groupIndices_[i - 1] : 0; + end = this.groupIndices_[i]; + + // draw all features for this texture group + while (featureIndex >= 0 && + this.startIndexForFeature_[featureIndex][0] >= groupStart) { + featureInfo = this.startIndexForFeature_[featureIndex]; + start = featureInfo[0]; + numItems = end - start; + var offsetInBytes = start * elementSize; + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.drawElements( + goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); + + var result = featureCallback(/** @type {ol.Feature} */ (featureInfo[1])); + if (result) { + return result; + } + + end = start; + featureIndex--; + } + } + return undefined; }; @@ -788,19 +867,53 @@ ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { * @param {number} hue Global hue. * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. - * @return {T|undefined} Callback result. - * @template T */ ol.render.webgl.ReplayGroup.prototype.replay = function(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash) { var i, ii, replay, result; for (i = 0, ii = ol.render.REPLAY_ORDER.length; i < ii; ++i) { + replay = this.replays_[ol.render.REPLAY_ORDER[i]]; + if (goog.isDef(replay)) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + undefined); + } + } +}; + + +/** + * @private + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + featureCallback) { + var i, replay, result; + for (i = ol.render.REPLAY_ORDER.length - 1; i >= 0; --i) { replay = this.replays_[ol.render.REPLAY_ORDER[i]]; if (goog.isDef(replay)) { result = replay.replay(context, center, resolution, rotation, size, pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash); + opacity, brightness, contrast, hue, saturation, + skippedFeaturesHash, featureCallback); if (result) { return result; } @@ -810,6 +923,53 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context, }; +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {ol.Coordinate} coordinate Coordinate. + * @param {function(ol.Feature): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.forEachGeometryAtPixel = function( + context, center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + coordinate, callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + return this.replayHitDetection_(context, + coordinate, resolution, rotation, [1, 1], pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + } + }); +}; + + /** * @const * @private diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index b5979505dc..1c06f8dda7 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -290,13 +290,10 @@ ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = replayGroup.finish(context); if (!replayGroup.isEmpty()) { // use default color values - var opacity = 1; - var brightness = 0; - var contrast = 1; - var hue = 0; - var saturation = 1; + var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; replayGroup.replay(context, center, resolution, rotation, size, - pixelRatio, opacity, brightness, contrast, hue, saturation, {}); + pixelRatio, d.opacity, d.brightness, d.contrast, + d.hue, d.saturation, {}); } replayGroup.getDeleteResourcesFunction(context)(); @@ -538,3 +535,79 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { this.scheduleExpireIconCache(frameState); }; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = + function(coordinate, frameState, callback, thisArg, + layerFilter, thisArg2) { + var result; + + if (this.getGL().isContextLost()) { + return false; + } + + var context = this.getContext(); + var viewState = frameState.viewState; + + // do the hit-detection for the overlays first + if (!goog.isNull(this.replayGroup)) { + /** @type {Object.} */ + var features = {}; + + // use default color values + var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; + + result = this.replayGroup.forEachGeometryAtPixel(context, + viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, + coordinate, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + goog.asserts.assert(goog.isDef(feature)); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, null); + } + }); + if (result) { + return result; + } + } + var layerStates = this.getMap().getLayerGroup().getLayerStatesArray(); + 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.forEachFeatureAtPixel( + coordinate, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @private + */ +ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_ = { + opacity: 1, + brightness: 0, + contrast: 1, + hue: 0, + saturation: 1 +}; diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index e2a685a633..5c914ef16a 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -58,6 +58,13 @@ ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { */ this.replayGroup_ = null; + /** + * The last layer state. + * @private + * @type {?ol.layer.LayerState} + */ + this.layerState_ = null; + }; goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); @@ -67,6 +74,7 @@ goog.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); */ ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { + this.layerState_ = layerState; var viewState = frameState.viewState; var replayGroup = this.replayGroup_; if (!goog.isNull(replayGroup) && !replayGroup.isEmpty()) { @@ -100,6 +108,35 @@ ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { */ ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = function(coordinate, frameState, callback, thisArg) { + if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) { + return undefined; + } else { + var mapRenderer = this.getWebGLMapRenderer(); + var context = mapRenderer.getContext(); + var viewState = frameState.viewState; + var layer = this.getLayer(); + var layerState = this.layerState_; + /** @type {Object.} */ + var features = {}; + return this.replayGroup_.forEachGeometryAtPixel(context, + viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + layerState.opacity, layerState.brightness, layerState.contrast, + layerState.hue, layerState.saturation, frameState.skippedFeatureUids, + coordinate, + /** + * @param {ol.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + goog.asserts.assert(goog.isDef(feature)); + var key = goog.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } }; From cf6dd38866fdff5a129ecc2ab8a5426cb39d32a8 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 18 Dec 2014 15:24:05 +0100 Subject: [PATCH 3/9] Use hit-detection images --- src/ol/render/webgl/webglreplay.js | 146 +++++++++++++----- src/ol/renderer/webgl/webglmaprenderer.js | 2 +- .../webgl/webglvectorlayerrenderer.js | 2 +- src/ol/webgl/context.js | 7 +- test/spec/ol/render/webglreplay.test.js | 12 ++ 5 files changed, 128 insertions(+), 41 deletions(-) diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index d017868ef3..079d315244 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -60,6 +60,12 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.groupIndices_ = []; + /** + * @type {Array.} + * @private + */ + this.hitDetectionGroupIndices_ = []; + /** * @type {number|undefined} * @private @@ -72,6 +78,12 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.images_ = []; + /** + * @type {Array.} + * @private + */ + this.hitDetectionImages_ = []; + /** * @type {number|undefined} * @private @@ -168,6 +180,12 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { */ this.textures_ = []; + /** + * @type {Array.} + * @private + */ + this.hitDetectionTextures_ = []; + /** * @type {Array.} * @private @@ -211,6 +229,7 @@ ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = var verticesBuffer = this.verticesBuffer_; var indicesBuffer = this.indicesBuffer_; var textures = this.textures_; + var hitDetectionTextures = this.hitDetectionTextures_; var gl = context.getGL(); return function() { if (!gl.isContextLost()) { @@ -218,6 +237,9 @@ ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = for (i = 0, ii = textures.length; i < ii; ++i) { gl.deleteTexture(textures[i]); } + for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) { + gl.deleteTexture(hitDetectionTextures[i]); + } } context.deleteBuffer(verticesBuffer); context.deleteBuffer(indicesBuffer); @@ -431,7 +453,10 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) { var gl = context.getGL(); this.groupIndices_.push(this.indices_.length); - goog.asserts.assert(this.images_.length == this.groupIndices_.length); + goog.asserts.assert(this.images_.length === this.groupIndices_.length); + this.hitDetectionGroupIndices_.push(this.indices_.length); + goog.asserts.assert(this.hitDetectionImages_.length === + this.hitDetectionGroupIndices_.length); // create, bind, and populate the vertices buffer this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); @@ -447,16 +472,53 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) { this.indicesBuffer_ = new ol.webgl.Buffer(indices); context.bindBuffer(goog.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - goog.asserts.assert(this.textures_.length === 0); - // create textures - var texture, image, uid; /** @type {Object.} */ var texturePerImage = {}; - var i; - var ii = this.images_.length; + + this.createTextures_(this.textures_, this.images_, texturePerImage, gl); + goog.asserts.assert(this.textures_.length === this.groupIndices_.length); + + this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, + texturePerImage, gl); + goog.asserts.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length); + + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.height_ = undefined; + this.images_ = null; + this.hitDetectionImages_ = null; + this.imageHeight_ = undefined; + this.imageWidth_ = undefined; + this.indices_ = null; + this.opacity_ = undefined; + this.originX_ = undefined; + this.originY_ = undefined; + this.rotateWithView_ = undefined; + this.rotation_ = undefined; + this.scale_ = undefined; + this.vertices_ = null; + this.width_ = undefined; +}; + + +/** + * @private + * @param {Array.} textures Textures. + * @param {Array.} images + * Images. + * @param {Object.} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. + */ +ol.render.webgl.ImageReplay.prototype.createTextures_ = + function(textures, images, texturePerImage, gl) { + goog.asserts.assert(textures.length === 0); + + var texture, image, uid, i; + var ii = images.length; for (i = 0; i < ii; ++i) { - image = this.images_[i]; + image = images[i]; uid = goog.getUid(image).toString(); if (goog.object.containsKey(texturePerImage, uid)) { @@ -476,26 +538,8 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) { goog.webgl.UNSIGNED_BYTE, image); texturePerImage[uid] = texture; } - this.textures_[i] = texture; + textures[i] = texture; } - - goog.asserts.assert(this.textures_.length == this.groupIndices_.length); - - this.anchorX_ = undefined; - this.anchorY_ = undefined; - this.height_ = undefined; - this.images_ = null; - this.imageHeight_ = undefined; - this.imageWidth_ = undefined; - this.indices_ = null; - this.opacity_ = undefined; - this.originX_ = undefined; - this.originY_ = undefined; - this.rotateWithView_ = undefined; - this.rotation_ = undefined; - this.scale_ = undefined; - this.vertices_ = null; - this.width_ = undefined; }; @@ -646,7 +690,7 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context, */ ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context) { - goog.asserts.assert(this.textures_.length == this.groupIndices_.length); + goog.asserts.assert(this.textures_.length === this.groupIndices_.length); var elementType = context.hasOESElementIndexUint ? goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; var elementSize = context.hasOESElementIndexUint ? 4 : 2; @@ -673,17 +717,18 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = */ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = function(gl, context, featureCallback) { - goog.asserts.assert(this.textures_.length == this.groupIndices_.length); + goog.asserts.assert(this.hitDetectionTextures_.length === + this.hitDetectionGroupIndices_.length); var elementType = context.hasOESElementIndexUint ? goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; var elementSize = context.hasOESElementIndexUint ? 4 : 2; var i, groupStart, groupEnd, numItems, featureInfo, start, end; var featureIndex = this.startIndexForFeature_.length - 1; - for (i = this.textures_.length - 1; i >= 0; --i) { - gl.bindTexture(goog.webgl.TEXTURE_2D, this.textures_[i]); - groupStart = (i > 0) ? this.groupIndices_[i - 1] : 0; - end = this.groupIndices_[i]; + for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); + groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; + end = this.hitDetectionGroupIndices_[i]; // draw all features for this texture group while (featureIndex >= 0 && @@ -726,6 +771,10 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { goog.asserts.assert(!goog.isNull(image)); var imageSize = imageStyle.getImageSize(); goog.asserts.assert(!goog.isNull(imageSize)); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + goog.asserts.assert(!goog.isNull(hitDetectionImage)); + var hitDetectionImageSize = imageStyle.getHitDetectionImageSize(); + goog.asserts.assert(!goog.isNull(hitDetectionImageSize)); var opacity = imageStyle.getOpacity(); goog.asserts.assert(goog.isDef(opacity)); var origin = imageStyle.getOrigin(); @@ -739,17 +788,31 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { var scale = imageStyle.getScale(); goog.asserts.assert(goog.isDef(scale)); + var currentImage; if (this.images_.length === 0) { this.images_.push(image); } else { - var currentImage = this.images_[this.images_.length - 1]; + currentImage = this.images_[this.images_.length - 1]; if (goog.getUid(currentImage) != goog.getUid(image)) { this.groupIndices_.push(this.indices_.length); - goog.asserts.assert(this.groupIndices_.length == this.images_.length); + goog.asserts.assert(this.groupIndices_.length === this.images_.length); this.images_.push(image); } } + if (this.hitDetectionImages_.length === 0) { + this.hitDetectionImages_.push(hitDetectionImage); + } else { + currentImage = + this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; + if (goog.getUid(currentImage) != goog.getUid(hitDetectionImage)) { + this.hitDetectionGroupIndices_.push(this.indices_.length); + goog.asserts.assert(this.hitDetectionGroupIndices_.length === + this.hitDetectionImages_.length); + this.hitDetectionImages_.push(hitDetectionImage); + } + } + this.anchorX_ = anchor[0]; this.anchorY_ = anchor[1]; this.height_ = size[1]; @@ -941,7 +1004,7 @@ ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, * @return {T|undefined} Callback result. * @template T */ -ol.render.webgl.ReplayGroup.prototype.forEachGeometryAtPixel = function( +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, coordinate, callback) { @@ -950,8 +1013,9 @@ ol.render.webgl.ReplayGroup.prototype.forEachGeometryAtPixel = function( gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); return this.replayHitDetection_(context, - coordinate, resolution, rotation, [1, 1], pixelRatio, - opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, + pixelRatio, opacity, brightness, contrast, hue, saturation, + skippedFeaturesHash, /** * @param {ol.Feature} feature Feature. * @return {?} Callback result. @@ -980,3 +1044,11 @@ ol.render.webgl.ReplayGroup.prototype.forEachGeometryAtPixel = function( ol.render.webgl.BATCH_CONSTRUCTORS_ = { 'Image': ol.render.webgl.ImageReplay }; + + +/** + * @const + * @private + * @type {Array.} + */ +ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1]; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 1c06f8dda7..1d6efe5372 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -560,7 +560,7 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = // use default color values var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; - result = this.replayGroup.forEachGeometryAtPixel(context, + result = this.replayGroup.forEachFeatureAtPixel(context, viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index 5c914ef16a..b53c0b6c8a 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -118,7 +118,7 @@ ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = var layerState = this.layerState_; /** @type {Object.} */ var features = {}; - return this.replayGroup_.forEachGeometryAtPixel(context, + return this.replayGroup_.forEachFeatureAtPixel(context, viewState.center, viewState.resolution, viewState.rotation, frameState.size, frameState.pixelRatio, layerState.opacity, layerState.brightness, layerState.contrast, diff --git a/src/ol/webgl/context.js b/src/ol/webgl/context.js index f0b06a84b1..0baaf6be80 100644 --- a/src/ol/webgl/context.js +++ b/src/ol/webgl/context.js @@ -282,6 +282,8 @@ ol.webgl.Context.prototype.handleWebGLContextLost = function() { goog.object.clear(this.programCache_); this.currentProgram_ = null; this.hitDetectionFramebuffer_ = null; + this.hitDetectionTexture_ = null; + this.hitDetectionRenderbuffer_ = null; }; @@ -304,8 +306,7 @@ ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri( - gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -322,6 +323,8 @@ ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { gl.bindFramebuffer(gl.FRAMEBUFFER, null); this.hitDetectionFramebuffer_ = framebuffer; + this.hitDetectionTexture_ = texture; + this.hitDetectionRenderbuffer_ = renderbuffer; }; diff --git a/test/spec/ol/render/webglreplay.test.js b/test/spec/ol/render/webglreplay.test.js index 8e600186e7..838004e6d5 100644 --- a/test/spec/ol/render/webglreplay.test.js +++ b/test/spec/ol/render/webglreplay.test.js @@ -16,9 +16,15 @@ describe('ol.render.webgl.ImageReplay', function() { imageStyle.getImage = function() { return image; }; + imageStyle.getHitDetectionImage = function() { + return image; + }; imageStyle.getImageSize = function() { return [512, 512]; }; + imageStyle.getHitDetectionImageSize = function() { + return [512, 512]; + }; imageStyle.getOrigin = function() { return [200, 200]; }; @@ -59,14 +65,20 @@ describe('ol.render.webgl.ImageReplay', function() { expect(replay.width_).to.be(256); expect(replay.images_).to.have.length(1); expect(replay.groupIndices_).to.have.length(0); + expect(replay.hitDetectionImages_).to.have.length(1); + expect(replay.hitDetectionGroupIndices_).to.have.length(0); replay.setImageStyle(imageStyle1); expect(replay.images_).to.have.length(1); expect(replay.groupIndices_).to.have.length(0); + expect(replay.hitDetectionImages_).to.have.length(1); + expect(replay.hitDetectionGroupIndices_).to.have.length(0); replay.setImageStyle(imageStyle2); expect(replay.images_).to.have.length(2); expect(replay.groupIndices_).to.have.length(1); + expect(replay.hitDetectionImages_).to.have.length(2); + expect(replay.hitDetectionGroupIndices_).to.have.length(1); }); }); From 461ec1d3bcda549b91a9d87d443d489e2e24865b Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 18 Dec 2014 16:44:41 +0100 Subject: [PATCH 4/9] Make sure hit-detection atlas is in sync ... with the atlas for the original images, so that the offsets are the same. --- src/ol/style/atlasmanager.js | 32 ++++++++++++++----------- test/spec/ol/style/atlasmanager.test.js | 17 +++++++++++-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/ol/style/atlasmanager.js b/src/ol/style/atlasmanager.js index a3cb260eca..f6e3d2c870 100644 --- a/src/ol/style/atlasmanager.js +++ b/src/ol/style/atlasmanager.js @@ -11,12 +11,10 @@ goog.require('ol'); /** * Provides information for an image inside an atlas manager. * `offsetX` and `offsetY` is the position of the image inside - * the atlas image `image`. - * `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image - * inside the hit-detection atlas image `hitImage` (only when a hit-detection - * image was created for this image). + * the atlas image `image` and the position of the hit-detection image + * inside the hit-detection atlas image `hitImage`. * @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement, - * hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}} + * hitImage: HTMLCanvasElement}} */ ol.style.AtlasManagerInfo; @@ -103,6 +101,7 @@ ol.style.AtlasManager.prototype.getInfo = function(id) { } /** @type {?ol.style.AtlasInfo} */ var hitInfo = this.getInfo_(this.hitAtlases_, id); + goog.asserts.assert(!goog.isNull(hitInfo)); return this.mergeInfos_(info, hitInfo); }; @@ -131,19 +130,19 @@ ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) { /** * @private * @param {ol.style.AtlasInfo} info The info for the real image. - * @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection + * @param {ol.style.AtlasInfo} hitInfo The info for the hit-detection * image. * @return {?ol.style.AtlasManagerInfo} The position and atlas image for the * entry, or `null` if the entry is not part of the atlases. */ ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) { + goog.asserts.assert(info.offsetX === hitInfo.offsetX); + goog.asserts.assert(info.offsetY === hitInfo.offsetY); return /** @type {ol.style.AtlasManagerInfo} */ ({ offsetX: info.offsetX, offsetY: info.offsetY, image: info.image, - hitOffsetX: goog.isNull(hitInfo) ? undefined : hitInfo.offsetX, - hitOffsetY: goog.isNull(hitInfo) ? undefined : hitInfo.offsetY, - hitImage: goog.isNull(hitInfo) ? undefined : hitInfo.image + hitImage: hitInfo.image }); }; @@ -185,12 +184,17 @@ ol.style.AtlasManager.prototype.add = return null; } + // even if no hit-detection entry is requested, we insert a fake entry into + // the hit-detection atlas, to make sure that the offset is the same for + // the original image and the hit-detection image. + var renderHitCallback = goog.isDef(opt_renderHitCallback) ? + opt_renderHitCallback : goog.functions.NULL; + /** @type {?ol.style.AtlasInfo} */ - var hitInfo = null; - if (goog.isDef(opt_renderHitCallback)) { - hitInfo = this.add_(true, - id, width, height, opt_renderHitCallback, opt_this); - } + var hitInfo = this.add_(true, + id, width, height, renderHitCallback, opt_this); + goog.asserts.assert(!goog.isNull(hitInfo)); + return this.mergeInfos_(info, hitInfo); }; diff --git a/test/spec/ol/style/atlasmanager.test.js b/test/spec/ol/style/atlasmanager.test.js index 66f5707fd1..66f1ca11bd 100644 --- a/test/spec/ol/style/atlasmanager.test.js +++ b/test/spec/ol/style/atlasmanager.test.js @@ -191,7 +191,7 @@ describe('ol.style.AtlasManager', function() { expect(info).to.eql({ offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_, - hitOffsetX: undefined, hitOffsetY: undefined, hitImage: undefined}); + hitImage: manager.hitAtlases_[0].canvas_}); expect(manager.getInfo('1')).to.eql(info); }); @@ -202,7 +202,6 @@ describe('ol.style.AtlasManager', function() { expect(info).to.eql({ offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_, - hitOffsetX: 1, hitOffsetY: 1, hitImage: manager.hitAtlases_[0].canvas_}); expect(manager.getInfo('1')).to.eql(info); @@ -258,6 +257,20 @@ describe('ol.style.AtlasManager', function() { expect(manager.add('2', 2048, 2048, defaultRender, defaultRender)) .to.eql(null); }); + + it('always has the same offset for the hit-detection', function() { + var manager = new ol.style.AtlasManager({initialSize: 128}); + // add one image without hit-detection callback + var info = manager.add('1', 32, 32, defaultRender); + // add then one with hit-detection callback + info = manager.add('2', 32, 32, defaultRender, defaultRender); + + expect(info).to.eql({ + offsetX: 34, offsetY: 1, image: manager.atlases_[0].canvas_, + hitImage: manager.hitAtlases_[0].canvas_}); + + expect(manager.getInfo('2')).to.eql(info); + }); }); describe('#getInfo', function() { From da7f38635be95c851a9d2589dbeb66f4b2804562 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 18 Dec 2014 16:45:44 +0100 Subject: [PATCH 5/9] Remove hitDetectionOrigin --- src/ol/style/circlestyle.js | 16 ---------------- src/ol/style/iconstyle.js | 8 -------- src/ol/style/imagestyle.js | 7 ------- src/ol/style/regularshapestyle.js | 16 ---------------- test/spec/ol/style/circlestyle.test.js | 4 ---- test/spec/ol/style/regularshapestyle.test.js | 4 ---- 6 files changed, 55 deletions(-) diff --git a/src/ol/style/circlestyle.js b/src/ol/style/circlestyle.js index 96632d06aa..d6b459b89f 100644 --- a/src/ol/style/circlestyle.js +++ b/src/ol/style/circlestyle.js @@ -70,12 +70,6 @@ ol.style.Circle = function(opt_options) { */ this.origin_ = [0, 0]; - /** - * @private - * @type {Array.} - */ - this.hitDetectionOrigin_ = [0, 0]; - /** * @private * @type {Array.} @@ -189,14 +183,6 @@ ol.style.Circle.prototype.getOrigin = function() { }; -/** - * @inheritDoc - */ -ol.style.Circle.prototype.getHitDetectionOrigin = function() { - return this.hitDetectionOrigin_; -}; - - /** * @return {number} Radius. * @api @@ -323,12 +309,10 @@ ol.style.Circle.prototype.render_ = function(atlasManager) { if (hasCustomHitDetectionImage) { this.hitDetectionCanvas_ = info.hitImage; - this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY]; this.hitDetectionImageSize_ = [info.hitImage.width, info.hitImage.height]; } else { this.hitDetectionCanvas_ = this.canvas_; - this.hitDetectionOrigin_ = this.origin_; this.hitDetectionImageSize_ = [imageSize, imageSize]; } } diff --git a/src/ol/style/iconstyle.js b/src/ol/style/iconstyle.js index 9913c6412a..e9130b3b06 100644 --- a/src/ol/style/iconstyle.js +++ b/src/ol/style/iconstyle.js @@ -301,14 +301,6 @@ ol.style.Icon.prototype.getOrigin = function() { }; -/** - * @inheritDoc - */ -ol.style.Icon.prototype.getHitDetectionOrigin = function() { - return this.getOrigin(); -}; - - /** * @return {string|undefined} Image src. * @api diff --git a/src/ol/style/imagestyle.js b/src/ol/style/imagestyle.js index d34d74ce3b..1cb95b241e 100644 --- a/src/ol/style/imagestyle.js +++ b/src/ol/style/imagestyle.js @@ -161,13 +161,6 @@ ol.style.Image.prototype.getHitDetectionImageSize = goog.abstractMethod; ol.style.Image.prototype.getOrigin = goog.abstractMethod; -/** - * @function - * @return {Array.} Origin for the hit-detection image. - */ -ol.style.Image.prototype.getHitDetectionOrigin = goog.abstractMethod; - - /** * @function * @return {ol.Size} Size. diff --git a/src/ol/style/regularshapestyle.js b/src/ol/style/regularshapestyle.js index cde61ff429..fb52b18600 100644 --- a/src/ol/style/regularshapestyle.js +++ b/src/ol/style/regularshapestyle.js @@ -60,12 +60,6 @@ ol.style.RegularShape = function(options) { */ this.origin_ = [0, 0]; - /** - * @private - * @type {Array.} - */ - this.hitDetectionOrigin_ = [0, 0]; - /** * @private * @type {number} @@ -219,14 +213,6 @@ ol.style.RegularShape.prototype.getOrigin = function() { }; -/** - * @inheritDoc - */ -ol.style.RegularShape.prototype.getHitDetectionOrigin = function() { - return this.hitDetectionOrigin_; -}; - - /** * @return {number} Number of points for stars and regular polygons. * @api @@ -370,12 +356,10 @@ ol.style.RegularShape.prototype.render_ = function(atlasManager) { if (hasCustomHitDetectionImage) { this.hitDetectionCanvas_ = info.hitImage; - this.hitDetectionOrigin_ = [info.hitOffsetX, info.hitOffsetY]; this.hitDetectionImageSize_ = [info.hitImage.width, info.hitImage.height]; } else { this.hitDetectionCanvas_ = this.canvas_; - this.hitDetectionOrigin_ = this.origin_; this.hitDetectionImageSize_ = [imageSize, imageSize]; } } diff --git a/test/spec/ol/style/circlestyle.test.js b/test/spec/ol/style/circlestyle.test.js index 4e5d0b985c..20b42b142d 100644 --- a/test/spec/ol/style/circlestyle.test.js +++ b/test/spec/ol/style/circlestyle.test.js @@ -16,7 +16,6 @@ describe('ol.style.Circle', function() { expect(style.getImage()).to.not.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); - expect(style.getHitDetectionOrigin()).to.eql([0, 0]); }); it('creates a canvas if no atlas is used (fill-style)', function() { @@ -35,7 +34,6 @@ describe('ol.style.Circle', function() { expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); - expect(style.getHitDetectionOrigin()).to.eql([0, 0]); }); it('adds itself to an atlas manager (no fill-style)', function() { @@ -50,7 +48,6 @@ describe('ol.style.Circle', function() { expect(style.getImage()).to.not.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([512, 512]); - expect(style.getHitDetectionOrigin()).to.eql([1, 1]); }); it('adds itself to an atlas manager (fill-style)', function() { @@ -71,7 +68,6 @@ describe('ol.style.Circle', function() { expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([512, 512]); - expect(style.getHitDetectionOrigin()).to.eql([1, 1]); }); }); diff --git a/test/spec/ol/style/regularshapestyle.test.js b/test/spec/ol/style/regularshapestyle.test.js index f972ae7fc6..8df8e55e98 100644 --- a/test/spec/ol/style/regularshapestyle.test.js +++ b/test/spec/ol/style/regularshapestyle.test.js @@ -49,7 +49,6 @@ describe('ol.style.RegularShape', function() { expect(style.getImage()).to.not.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); - expect(style.getHitDetectionOrigin()).to.eql([0, 0]); }); it('creates a canvas if no atlas is used (fill-style)', function() { @@ -68,7 +67,6 @@ describe('ol.style.RegularShape', function() { expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([21, 21]); - expect(style.getHitDetectionOrigin()).to.eql([0, 0]); }); it('adds itself to an atlas manager (no fill-style)', function() { @@ -84,7 +82,6 @@ describe('ol.style.RegularShape', function() { expect(style.getImage()).to.not.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([512, 512]); - expect(style.getHitDetectionOrigin()).to.eql([1, 1]); }); it('adds itself to an atlas manager (fill-style)', function() { @@ -105,7 +102,6 @@ describe('ol.style.RegularShape', function() { expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImageSize()).to.eql([512, 512]); - expect(style.getHitDetectionOrigin()).to.eql([1, 1]); }); }); From 6c48b2067b6c98f7d6ffb32159bb4aa1edb4fdfe Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 18 Dec 2014 17:26:02 +0100 Subject: [PATCH 6/9] Make icon example work with webgl --- examples/icon.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/icon.js b/examples/icon.js index d510cf16fa..5ffb2e1ad7 100644 --- a/examples/icon.js +++ b/examples/icon.js @@ -5,7 +5,7 @@ goog.require('ol.View'); goog.require('ol.geom.Point'); goog.require('ol.layer.Tile'); goog.require('ol.layer.Vector'); -goog.require('ol.source.TileJSON'); +goog.require('ol.source.OSM'); goog.require('ol.source.Vector'); goog.require('ol.style.Icon'); goog.require('ol.style.Style'); @@ -39,12 +39,11 @@ var vectorLayer = new ol.layer.Vector({ }); var rasterLayer = new ol.layer.Tile({ - source: new ol.source.TileJSON({ - url: 'http://api.tiles.mapbox.com/v3/mapbox.geography-class.jsonp' - }) + source: new ol.source.OSM() }); var map = new ol.Map({ + renderer: exampleNS.getRendererFromQueryString(), layers: [rasterLayer, vectorLayer], target: document.getElementById('map'), view: new ol.View({ From 060c9f3bc67105e726d7e7b05880508b706ce706 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Fri, 19 Dec 2014 15:34:32 +0100 Subject: [PATCH 7/9] Reduce number of arrays created --- src/ol/render/webgl/webglreplay.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index 079d315244..7161596b12 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -199,11 +199,18 @@ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { this.verticesBuffer_ = null; /** - * Start indices per feature. - * @type {Array.>} + * Start index per feature (the index). + * @type {Array.} * @private */ - this.startIndexForFeature_ = []; + this.startIndices_ = []; + + /** + * Start index per feature (the feature). + * @type {Array.} + * @private + */ + this.startIndicesFeature_ = []; /** * @type {number|undefined} @@ -406,7 +413,8 @@ ol.render.webgl.ImageReplay.prototype.drawMultiLineStringGeometry = */ ol.render.webgl.ImageReplay.prototype.drawMultiPointGeometry = function(multiPointGeometry, feature) { - this.startIndexForFeature_.push([this.indices_.length, feature]); + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); var flatCoordinates = multiPointGeometry.getFlatCoordinates(); var stride = multiPointGeometry.getStride(); this.drawCoordinates_( @@ -426,7 +434,8 @@ ol.render.webgl.ImageReplay.prototype.drawMultiPolygonGeometry = */ ol.render.webgl.ImageReplay.prototype.drawPointGeometry = function(pointGeometry, feature) { - this.startIndexForFeature_.push([this.indices_.length, feature]); + this.startIndices_.push(this.indices_.length); + this.startIndicesFeature_.push(feature); var flatCoordinates = pointGeometry.getFlatCoordinates(); var stride = pointGeometry.getStride(); this.drawCoordinates_( @@ -723,8 +732,8 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; var elementSize = context.hasOESElementIndexUint ? 4 : 2; - var i, groupStart, groupEnd, numItems, featureInfo, start, end; - var featureIndex = this.startIndexForFeature_.length - 1; + var i, groupStart, groupEnd, numItems, start, end; + var featureIndex = this.startIndices_.length - 1; for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; @@ -732,9 +741,8 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = // draw all features for this texture group while (featureIndex >= 0 && - this.startIndexForFeature_[featureIndex][0] >= groupStart) { - featureInfo = this.startIndexForFeature_[featureIndex]; - start = featureInfo[0]; + this.startIndices_[featureIndex] >= groupStart) { + start = this.startIndices_[featureIndex]; numItems = end - start; var offsetInBytes = start * elementSize; @@ -742,7 +750,7 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = gl.drawElements( goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); - var result = featureCallback(/** @type {ol.Feature} */ (featureInfo[1])); + var result = featureCallback(this.startIndicesFeature_[featureIndex]); if (result) { return result; } From 38322d615bb016da10d60686abf49c94b016d276 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Fri, 19 Dec 2014 16:58:45 +0100 Subject: [PATCH 8/9] Use renderBuffer for hit-detection --- src/ol/render/webgl/webglreplay.js | 62 ++++++++++++++----- .../webgl/webglvectorlayerrenderer.js | 2 +- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index 7161596b12..0c3e2f5265 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -566,13 +566,15 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. * @return {T|undefined} Callback result. * @template T */ ol.render.webgl.ImageReplay.prototype.replay = function(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - featureCallback) { + featureCallback, opt_hitExtent) { var gl = context.getGL(); // bind the vertices buffer @@ -678,7 +680,8 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context, this.drawReplay_(gl, context); } else { // draw feature by feature for the hit-detection - result = this.drawHitDetectionReplay_(gl, context, featureCallback); + result = this.drawHitDetectionReplay_(gl, context, featureCallback, + opt_hitExtent); } // disable the vertex attrib arrays @@ -721,18 +724,20 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. * @return {T|undefined} Callback result. * @template T */ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = - function(gl, context, featureCallback) { + function(gl, context, featureCallback, opt_hitExtent) { goog.asserts.assert(this.hitDetectionTextures_.length === this.hitDetectionGroupIndices_.length); var elementType = context.hasOESElementIndexUint ? goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; var elementSize = context.hasOESElementIndexUint ? 4 : 2; - var i, groupStart, groupEnd, numItems, start, end; + var i, groupStart, groupEnd, numItems, start, end, feature; var featureIndex = this.startIndices_.length - 1; for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); @@ -744,15 +749,19 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = this.startIndices_[featureIndex] >= groupStart) { start = this.startIndices_[featureIndex]; numItems = end - start; - var offsetInBytes = start * elementSize; + feature = this.startIndicesFeature_[featureIndex]; - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.drawElements( - goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); + if (!goog.isDef(opt_hitExtent) || ol.extent.intersects( + opt_hitExtent, feature.getGeometry().getExtent())) { + var offsetInBytes = start * elementSize; + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.drawElements( + goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); - var result = featureCallback(this.startIndicesFeature_[featureIndex]); - if (result) { - return result; + var result = featureCallback(feature); + if (result) { + return result; + } } end = start; @@ -848,9 +857,11 @@ ol.render.webgl.ImageReplay.prototype.setTextStyle = goog.abstractMethod; * @implements {ol.render.IReplayGroup} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. * @struct */ -ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) { +ol.render.webgl.ReplayGroup = function( + tolerance, maxExtent, opt_renderBuffer) { /** * @type {ol.Extent} @@ -864,6 +875,12 @@ ol.render.webgl.ReplayGroup = function(tolerance, maxExtent) { */ this.tolerance_ = tolerance; + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; + /** * ImageReplay only is supported at this point. * @type {Object.} @@ -970,13 +987,15 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context, * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. * @return {T|undefined} Callback result. * @template T */ ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - featureCallback) { + featureCallback, opt_hitExtent) { var i, replay, result; for (i = ol.render.REPLAY_ORDER.length - 1; i >= 0; --i) { replay = this.replays_[ol.render.REPLAY_ORDER[i]]; @@ -984,7 +1003,7 @@ ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, result = replay.replay(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, - skippedFeaturesHash, featureCallback); + skippedFeaturesHash, featureCallback, opt_hitExtent); if (result) { return result; } @@ -1020,6 +1039,19 @@ ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( gl.bindFramebuffer( gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (goog.isDef(this.renderBuffer_)) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.boundingExtent([coordinate]), + resolution * this.renderBuffer_); + } + return this.replayHitDetection_(context, coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, pixelRatio, opacity, brightness, contrast, hue, saturation, @@ -1038,7 +1070,7 @@ ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( return result; } } - }); + }, hitExtent); }; diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index b53c0b6c8a..cafc212ad4 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -203,7 +203,7 @@ ol.renderer.webgl.VectorLayer.prototype.prepareFrame = var replayGroup = new ol.render.webgl.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), - extent); + extent, vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); var renderFeature = /** From 974823dbd4585db5ab85c9643ffe38480ce02202 Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Thu, 8 Jan 2015 14:45:06 +0100 Subject: [PATCH 9/9] Refactor createTexture --- src/ol/render/webgl/webglreplay.js | 15 +--- .../renderer/webgl/webglimagelayerrenderer.js | 21 +----- src/ol/renderer/webgl/webgllayerrenderer.js | 12 +--- src/ol/webgl/context.js | 69 +++++++++++++++++-- 4 files changed, 71 insertions(+), 46 deletions(-) diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index 0c3e2f5265..36ff9183f5 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -13,6 +13,7 @@ goog.require('ol.render.webgl.imagereplay.shader.Color'); goog.require('ol.render.webgl.imagereplay.shader.Default'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); @@ -533,18 +534,8 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = if (goog.object.containsKey(texturePerImage, uid)) { texture = texturePerImage[uid]; } else { - texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_WRAP_S, goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR); - gl.texParameteri(goog.webgl.TEXTURE_2D, - goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, image); + texture = ol.webgl.Context.createTexture( + gl, image, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE); texturePerImage[uid] = texture; } textures[i] = texture; diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 61dd3f6a2c..80963b272e 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -14,6 +14,7 @@ goog.require('ol.extent'); goog.require('ol.layer.Image'); goog.require('ol.proj'); goog.require('ol.renderer.webgl.Layer'); +goog.require('ol.webgl.Context'); @@ -52,24 +53,8 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { var imageElement = image.getImage(); var gl = this.getWebGLMapRenderer().getGL(); - var texture = gl.createTexture(); - - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, - goog.webgl.RGBA, goog.webgl.UNSIGNED_BYTE, imageElement); - - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, - goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, - goog.webgl.CLAMP_TO_EDGE); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, goog.webgl.LINEAR); - gl.texParameteri( - goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, goog.webgl.LINEAR); - - return texture; + return ol.webgl.Context.createTexture( + gl, imageElement, goog.webgl.CLAMP_TO_EDGE, goog.webgl.CLAMP_TO_EDGE); }; diff --git a/src/ol/renderer/webgl/webgllayerrenderer.js b/src/ol/renderer/webgl/webgllayerrenderer.js index 4f09f7b881..26b9ea5770 100644 --- a/src/ol/renderer/webgl/webgllayerrenderer.js +++ b/src/ol/renderer/webgl/webgllayerrenderer.js @@ -11,6 +11,7 @@ goog.require('ol.renderer.Layer'); goog.require('ol.renderer.webgl.map.shader.Color'); goog.require('ol.renderer.webgl.map.shader.Default'); goog.require('ol.webgl.Buffer'); +goog.require('ol.webgl.Context'); @@ -115,15 +116,8 @@ ol.renderer.webgl.Layer.prototype.bindFramebuffer = } }, gl, this.framebuffer, this.texture)); - var texture = gl.createTexture(); - gl.bindTexture(goog.webgl.TEXTURE_2D, texture); - gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, - framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, null); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, - goog.webgl.LINEAR); - gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER, - goog.webgl.LINEAR); + var texture = ol.webgl.Context.createEmptyTexture( + gl, framebufferDimension, framebufferDimension); var framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer); diff --git a/src/ol/webgl/context.js b/src/ol/webgl/context.js index 0baaf6be80..616a2d7048 100644 --- a/src/ol/webgl/context.js +++ b/src/ol/webgl/context.js @@ -303,13 +303,7 @@ ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { var framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - var texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - + var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1); var renderbuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); @@ -353,3 +347,64 @@ ol.webgl.Context.prototype.useProgram = function(program) { * @type {goog.log.Logger} */ ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context'); + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} + * @private + */ +ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + if (goog.isDef(opt_wrapS)) { + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_S, opt_wrapS); + } + if (goog.isDef(opt_wrapT)) { + gl.texParameteri( + goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, opt_wrapT); + } + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number} width Width. + * @param {number} height Height. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} + */ +ol.webgl.Context.createEmptyTexture = function( + gl, width, height, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + + return texture; +}; + + +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} + */ +ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + + return texture; +};