From 2ed5abed0781fd463ead89029066c5798fa52759 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 9 May 2017 14:12:48 +0200 Subject: [PATCH 01/10] Add WebGL text defaults --- src/ol/render/webgl.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ol/render/webgl.js b/src/ol/render/webgl.js index e016bd64cd..cac93aa290 100644 --- a/src/ol/render/webgl.js +++ b/src/ol/render/webgl.js @@ -5,12 +5,20 @@ goog.require('ol'); if (ol.ENABLE_WEBGL) { + /** + * @const + * @type {string} + */ + ol.render.webgl.defaultFont = '10px sans-serif'; + + /** * @const * @type {ol.Color} */ ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; + /** * @const * @type {string} @@ -51,6 +59,21 @@ if (ol.ENABLE_WEBGL) { */ ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; + + /** + * @const + * @type {string} + */ + ol.render.webgl.defaultTextAlign = 'center'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.defaultTextBaseline = 'middle'; + + /** * @const * @type {number} From da60b964451c162b8e1dd62ec45f43450cc07fb5 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Tue, 9 May 2017 18:55:50 +0200 Subject: [PATCH 02/10] Style texts and draw them on canvas --- src/ol/render/webgl/textreplay.js | 235 ++++++++++++++++++++++++++---- 1 file changed, 203 insertions(+), 32 deletions(-) diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index 429b03a923..b739394e10 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -1,47 +1,99 @@ goog.provide('ol.render.webgl.TextReplay'); goog.require('ol'); +goog.require('ol.colorlike'); +goog.require('ol.has'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.imagereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); if (ol.ENABLE_WEBGL) { /** * @constructor - * @abstract + * @extends {ol.render.webgl.Replay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @struct */ - ol.render.webgl.TextReplay = function(tolerance, maxExtent) {}; + ol.render.webgl.TextReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); - /** - * @param {ol.style.Text} textStyle Text style. - */ - ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {}; + /** + * @private + * @type {ol.render.webgl.imagereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {Array.} + */ + this.images_ = []; + + /** + * @private + * @type {Array.} + */ + this.textures_ = []; + + /** + * @private + * @type {{strokeColor: (ol.ColorLike|null), + * lineCap: (string|undefined), + * lineDash: Array., + * lineDashOffset: (number|undefined), + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined), + * fillColor: (ol.ColorLike|null), + * text: string, + * font: (string|undefined), + * textAlign: (string|undefined), + * textBaseline: (string|undefined), + * offsetX: (number|undefined), + * offsetY: (number|undefined), + * scale: (number|undefined), + * rotation: (number|undefined), + * rotateWithView: (boolean|undefined)} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined, + fillColor: null, + text: '', + font: undefined, + textAlign: undefined, + textBaseline: undefined, + offsetX: undefined, + offsetY: undefined, + scale: undefined, + rotation: undefined, + rotateWithView: undefined + }; - /** - * @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 {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ - ol.render.webgl.TextReplay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - return undefined; }; + ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.Replay); /** * @param {Array.} flatCoordinates Flat coordinates. @@ -52,7 +104,72 @@ if (ol.ENABLE_WEBGL) { * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, - end, stride, geometry, feature) {}; + end, stride, geometry, feature) { + //For now we create one texture per feature. That is, only multiparts are grouped. + //TODO: speed up rendering with SDF, or at least glyph atlases + var state = this.state_; + if (state.text && (state.fillColor || state.strokeColor)) { + this.images_.push(this.createTextImage_()); + } + }; + + /** + * @private + * @return {HTMLCanvasElement} Text image. + */ + ol.render.webgl.TextReplay.prototype.createTextImage_ = function() { + var state = this.state_; + var canvas = document.createElement('canvas'); + + var ctx = canvas.getContext('2d'); + ctx.font = state.font; + var lineHeight = Math.round(ctx.measureText('M').width * 1.5 + state.lineWidth * 2); + var lines = state.text.split('\n'); + //FIXME: use pixelRatio + var textHeight = Math.ceil(lineHeight * lines.length * state.scale); + var longestLine = lines.map(function(str) { + return ctx.measureText(str).width; + }).reduce(function(max, curr) { + return Math.max(max, curr); + }); + //FIXME: use pixelRatio + var textWidth = Math.ceil((longestLine + state.lineWidth * 2) * state.scale); + + //Parameterize the canvas + canvas.width = textWidth; + canvas.height = textHeight; + ctx.fillStyle = state.fillColor; + ctx.strokeStyle = state.strokeColor; + ctx.lineWidth = state.lineWidth; + ctx.lineCap = state.lineCap; + ctx.lineJoin = state.lineJoin; + ctx.miterLimit = state.miterLimit; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + if (ol.has.CANVAS_LINE_DASH) { + //FIXME: use pixelRatio + ctx.setLineDash(state.lineDash); + ctx.lineDashOffset = state.lineDashOffset; + } + if (state.scale !== 1) { + //FIXME: use pixelRatio + ctx.setTransform(state.scale, 0, 0, state.scale, 0, 0); + } + + //Draw the text on the canvas + var lineY = 0; + for (var i = 0, ii = lines.length; i < ii; ++i) { + if (state.strokeColor) { + ctx.strokeText(lines[i], 0, lineY); + } + if (state.fillColor) { + ctx.fillText(lines[i], 0, lineY); + } + lineY += lineHeight; + } + + return canvas; + }; /** * @abstract @@ -61,11 +178,65 @@ if (ol.ENABLE_WEBGL) { ol.render.webgl.TextReplay.prototype.finish = function(context) {}; /** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. + * @inheritDoc */ ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { - return ol.nullFunction; + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var textures = this.textures_; + var gl = context.getGL(); + return function() { + if (!gl.isContextLost()) { + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.deleteTexture(textures[i]); + } + } + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; + }; + + /** + * @param {ol.style.Text} textStyle Text style. + */ + ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) { + var state = this.state_; + if (!textStyle) { + state.text = ''; + } else { + var textFillStyle = textStyle.getFill(); + if (!textFillStyle) { + state.fillColor = null; + } else { + var textFillStyleColor = textFillStyle.getColor(); + state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ? + textFillStyleColor : ol.render.webgl.defaultFillStyle); + } + var textStrokeStyle = textStyle.getStroke(); + if (!textStrokeStyle) { + state.strokeColor = null; + } else { + var textStrokeStyleColor = textFillStyle.getColor(); + state.strokeColor = ol.colorlike.asColorLike(textStrokeStyleColor ? + textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle); + state.lineWidth = textStrokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; + state.lineCap = textStrokeStyle.getLineCap() || ol.render.webgl.defaultLineCap; + state.lineDash = textStrokeStyle.getLineDash() || ol.render.webgl.defaultLineDash; + state.lineDashOffset = textStrokeStyle.getLineDashOffset() || ol.render.webgl.defaultLineDashOffset; + state.lineJoin = textStrokeStyle.getLineJoin() || ol.render.webgl.defaultLineJoin; + state.miterLimit = textStrokeStyle.getMiterLimit() || ol.render.webgl.defaultMiterLimit; + } + state.font = textStyle.getFont() || ol.render.webgl.defaultFont; + state.offsetX = textStyle.getOffsetX() || 0; + state.offsetY = textStyle.getOffsetY() || 0; + state.rotateWithView = !!textStyle.getRotateWithView(); + state.rotation = textStyle.getRotation() || 0; + state.scale = textStyle.getScale() || 1; + state.text = textStyle.getText() || ''; + state.textAlign = textStyle.getTextAlign() || ol.render.webgl.defaultTextAlign; + state.textBaseline = textStyle.getTextBaseline() || ol.render.webgl.defaultTextBaseline; + } }; } From f82bc15013232057bc28f5ce3eea0f645fc28a66 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 10 May 2017 13:06:35 +0200 Subject: [PATCH 03/10] Minimal working text renderer --- src/ol/render/webgl/replay.js | 6 +- src/ol/render/webgl/textreplay.js | 357 ++++++++++++++++-- .../webgl/textreplay/defaultshader.glsl | 41 ++ .../render/webgl/textreplay/defaultshader.js | 148 ++++++++ 4 files changed, 523 insertions(+), 29 deletions(-) create mode 100644 src/ol/render/webgl/textreplay/defaultshader.glsl create mode 100644 src/ol/render/webgl/textreplay/defaultshader.js diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index 543d1048cc..2222ffd69c 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -142,7 +142,8 @@ if (ol.ENABLE_WEBGL) { * @return {ol.render.webgl.circlereplay.defaultshader.Locations| ol.render.webgl.imagereplay.defaultshader.Locations| ol.render.webgl.linestringreplay.defaultshader.Locations| - ol.render.webgl.polygonreplay.defaultshader.Locations} Locations. + ol.render.webgl.polygonreplay.defaultshader.Locations| + ol.render.webgl.textreplay.defaultshader.Locations} Locations. */ ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {}; @@ -154,7 +155,8 @@ if (ol.ENABLE_WEBGL) { * @param {ol.render.webgl.circlereplay.defaultshader.Locations| ol.render.webgl.imagereplay.defaultshader.Locations| ol.render.webgl.linestringreplay.defaultshader.Locations| - ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. + ol.render.webgl.polygonreplay.defaultshader.Locations| + ol.render.webgl.textreplay.defaultshader.Locations} locations Locations. */ ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index b739394e10..101a249459 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -2,11 +2,14 @@ goog.provide('ol.render.webgl.TextReplay'); goog.require('ol'); goog.require('ol.colorlike'); +goog.require('ol.dom'); +goog.require('ol.extent'); goog.require('ol.has'); goog.require('ol.render.webgl'); -goog.require('ol.render.webgl.imagereplay.defaultshader'); +goog.require('ol.render.webgl.textreplay.defaultshader'); goog.require('ol.render.webgl.Replay'); goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); @@ -24,7 +27,7 @@ if (ol.ENABLE_WEBGL) { /** * @private - * @type {ol.render.webgl.imagereplay.defaultshader.Locations} + * @type {ol.render.webgl.textreplay.defaultshader.Locations} */ this.defaultLocations_ = null; @@ -52,6 +55,12 @@ if (ol.ENABLE_WEBGL) { */ this.textures_ = []; + /** + * @private + * @type {HTMLCanvasElement} + */ + this.measureCanvas_ = ol.dom.createCanvasContext2D(0, 0).canvas; + /** * @private * @type {{strokeColor: (ol.ColorLike|null), @@ -59,7 +68,7 @@ if (ol.ENABLE_WEBGL) { * lineDash: Array., * lineDashOffset: (number|undefined), * lineJoin: (string|undefined), - * lineWidth: (number|undefined), + * lineWidth: number, * miterLimit: (number|undefined), * fillColor: (ol.ColorLike|null), * text: string, @@ -70,7 +79,9 @@ if (ol.ENABLE_WEBGL) { * offsetY: (number|undefined), * scale: (number|undefined), * rotation: (number|undefined), - * rotateWithView: (boolean|undefined)} + * rotateWithView: (boolean|undefined), + * height: (number|undefined), + * width: (number|undefined)}} */ this.state_ = { strokeColor: null, @@ -78,7 +89,7 @@ if (ol.ENABLE_WEBGL) { lineDash: null, lineDashOffset: undefined, lineJoin: undefined, - lineWidth: undefined, + lineWidth: 0, miterLimit: undefined, fillColor: null, text: '', @@ -89,19 +100,17 @@ if (ol.ENABLE_WEBGL) { offsetY: undefined, scale: undefined, rotation: undefined, - rotateWithView: undefined + rotateWithView: undefined, + height: undefined, + width: undefined }; }; ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.Replay); + /** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. - * @param {ol.Feature|ol.render.Feature} feature Feature. + * @inheritDoc */ ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) { @@ -109,35 +118,46 @@ if (ol.ENABLE_WEBGL) { //TODO: speed up rendering with SDF, or at least glyph atlases var state = this.state_; if (state.text && (state.fillColor || state.strokeColor)) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + this.images_.push(this.createTextImage_()); + this.drawCoordinates_( + flatCoordinates, offset, end, stride); } }; + /** * @private * @return {HTMLCanvasElement} Text image. */ ol.render.webgl.TextReplay.prototype.createTextImage_ = function() { var state = this.state_; - var canvas = document.createElement('canvas'); - var ctx = canvas.getContext('2d'); - ctx.font = state.font; - var lineHeight = Math.round(ctx.measureText('M').width * 1.5 + state.lineWidth * 2); + //Measure text dimensions + var mCtx = this.measureCanvas_.getContext('2d'); + mCtx.font = state.font; + var lineHeight = Math.round(mCtx.measureText('M').width * 1.2 + state.lineWidth * 2); var lines = state.text.split('\n'); //FIXME: use pixelRatio var textHeight = Math.ceil(lineHeight * lines.length * state.scale); + state.height = textHeight; var longestLine = lines.map(function(str) { - return ctx.measureText(str).width; + return mCtx.measureText(str).width; }).reduce(function(max, curr) { return Math.max(max, curr); }); //FIXME: use pixelRatio var textWidth = Math.ceil((longestLine + state.lineWidth * 2) * state.scale); + state.width = textWidth; + + //Create a canvas + var ctx = ol.dom.createCanvasContext2D(textWidth, textHeight); + var canvas = ctx.canvas; //Parameterize the canvas - canvas.width = textWidth; - canvas.height = textHeight; + ctx.font = state.font; ctx.fillStyle = state.fillColor; ctx.strokeStyle = state.strokeColor; ctx.lineWidth = state.lineWidth; @@ -146,7 +166,7 @@ if (ol.ENABLE_WEBGL) { ctx.miterLimit = state.miterLimit; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; - if (ol.has.CANVAS_LINE_DASH) { + if (ol.has.CANVAS_LINE_DASH && state.lineDash) { //FIXME: use pixelRatio ctx.setLineDash(state.lineDash); ctx.lineDashOffset = state.lineDashOffset; @@ -168,14 +188,282 @@ if (ol.ENABLE_WEBGL) { lineY += lineHeight; } - return canvas; + return /** @type {HTMLCanvasElement} */ (canvas); }; + /** - * @abstract - * @param {ol.webgl.Context} context Context. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} My end. + * @private */ - ol.render.webgl.TextReplay.prototype.finish = function(context) {}; + ol.render.webgl.TextReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + var state = this.state_; + var anchorX, anchorY; + var height = /** @type {number} */ (state.height); + var width = /** @type {number} */ (state.width); + switch (state.textAlign) { + default: + anchorX = width / 2; + break; + case 'left': case 'end': + anchorX = 0; + break; + case 'right': case 'start': + anchorX = width; + break; + } + switch (state.textBaseline) { + default: + anchorY = height / 2; + break; + case 'top': + anchorY = 0; + break; + case 'bottom': + anchorY = height; + break; + case 'hanging': + anchorY = height * 0.2; + break; + case 'alphabetic': case 'ideographic': + anchorY = height * 0.8; + break; + } + var rotateWithView = state.rotateWithView ? 1.0 : 0.0; + // this.rotation_ is anti-clockwise, but rotation is clockwise + var rotation = /** @type {number} */ (-state.rotation); + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + var numIndices = this.indices.length; + var numVertices = this.vertices.length; + var i, n, offsetX, offsetY, x, y; + for (i = offset; i < end; i += stride) { + x = flatCoordinates[i] - this.origin[0]; + y = flatCoordinates[i + 1] - this.origin[1]; + + // There are 4 vertices per [x, y] point, one for each corner of the + // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if + // WebGL supported Geometry Shaders (which can emit new vertices), but that + // is not currently the case. + // + // And each vertex includes 7 values: the x and y coordinates, the x and + // y offsets used to calculate the position of the corner, the u and + // v texture coordinates for the corner, and whether the + // the image should be rotated with the view (rotateWithView). + + n = numVertices / 7; + + // bottom-left corner + offsetX = -anchorX; + offsetY = -(height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = rotateWithView; + + // bottom-right corner + offsetX = width - anchorX; + offsetY = -(height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = rotateWithView; + + // top-right corner + offsetX = width - anchorX; + offsetY = anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = rotateWithView; + + // top-left corner + offsetX = -anchorX; + offsetY = anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = rotateWithView; + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + } + + return numVertices; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.finish = function(context) { + var gl = context.getGL(); + + this.startIndices.push(this.indices.length); + + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + // create textures + this.createTextures_(gl); + + this.images_ = []; + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 0, + miterLimit: undefined, + fillColor: null, + text: '', + font: undefined, + textAlign: undefined, + textBaseline: undefined, + offsetX: undefined, + offsetY: undefined, + scale: undefined, + rotation: undefined, + rotateWithView: undefined, + height: undefined, + width: undefined + }; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader = ol.render.webgl.textreplay.defaultshader.fragment; + var vertexShader = ol.render.webgl.textreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + locations = new ol.render.webgl.textreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + // use the program (FIXME: use the return value) + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 28, 0); + + gl.enableVertexAttribArray(locations.a_offsets); + gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, + false, 28, 8); + + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, + false, 28, 16); + + gl.enableVertexAttribArray(locations.a_rotateWithView); + gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, + false, 28, 24); + + return locations; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_rotateWithView); + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + var textures = this.textures_; + var startIndices = this.startIndices; + + var i, ii, start, end, feature, featureUid; + for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { + feature = this.startIndicesFeature[i]; + featureUid = ol.getUid(feature).toString(); + + end = startIndices[i]; + if (skippedFeaturesHash[featureUid] === undefined) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + this.drawElements(gl, context, start, end); + } + start = end; + } + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + var textures = this.textures_; + var startIndices = this.startIndices; + + var i, start, end, feature, featureUid; + for (i = textures.length - 1; i >= 0; --i) { + feature = this.startIndicesFeature[i]; + featureUid = ol.getUid(feature).toString(); + + start = (i > 0) ? startIndices[i - 1] : 0; + end = startIndices[i]; + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + if (result) { + return result; + } + } + } + return undefined; + }; + /** * @inheritDoc @@ -198,7 +486,20 @@ if (ol.ENABLE_WEBGL) { }; /** - * @param {ol.style.Text} textStyle Text style. + * @private + * @param {WebGLRenderingContext} gl Gl. + */ + ol.render.webgl.TextReplay.prototype.createTextures_ = function(gl) { + for (var i = 0, ii = this.images_.length; i < ii; ++i) { + var image = this.images_[i]; + this.textures_.push(ol.webgl.Context.createTexture( + gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE)); + } + }; + + + /** + * @inheritDoc */ ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) { var state = this.state_; @@ -216,16 +517,18 @@ if (ol.ENABLE_WEBGL) { var textStrokeStyle = textStyle.getStroke(); if (!textStrokeStyle) { state.strokeColor = null; + state.lineWidth = 0; } else { - var textStrokeStyleColor = textFillStyle.getColor(); + var textStrokeStyleColor = textStrokeStyle.getColor(); state.strokeColor = ol.colorlike.asColorLike(textStrokeStyleColor ? textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle); state.lineWidth = textStrokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; state.lineCap = textStrokeStyle.getLineCap() || ol.render.webgl.defaultLineCap; - state.lineDash = textStrokeStyle.getLineDash() || ol.render.webgl.defaultLineDash; state.lineDashOffset = textStrokeStyle.getLineDashOffset() || ol.render.webgl.defaultLineDashOffset; state.lineJoin = textStrokeStyle.getLineJoin() || ol.render.webgl.defaultLineJoin; state.miterLimit = textStrokeStyle.getMiterLimit() || ol.render.webgl.defaultMiterLimit; + var lineDash = textStrokeStyle.getLineDash(); + state.lineDash = lineDash ? lineDash.slice() : ol.render.webgl.defaultLineDash; } state.font = textStyle.getFont() || ol.render.webgl.defaultFont; state.offsetX = textStyle.getOffsetX() || 0; diff --git a/src/ol/render/webgl/textreplay/defaultshader.glsl b/src/ol/render/webgl/textreplay/defaultshader.glsl new file mode 100644 index 0000000000..7f0c1a2138 --- /dev/null +++ b/src/ol/render/webgl/textreplay/defaultshader.glsl @@ -0,0 +1,41 @@ +//! NAMESPACE=ol.render.webgl.textreplay.defaultshader +//! CLASS=ol.render.webgl.textreplay.defaultshader + + +//! COMMON +varying vec2 v_texCoord; + +//! VERTEX +attribute vec2 a_position; +attribute vec2 a_texCoord; +attribute vec2 a_offsets; +attribute float a_rotateWithView; + +uniform mat4 u_projectionMatrix; +uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; + +void main(void) { + mat4 offsetMatrix = u_offsetScaleMatrix; + if (a_rotateWithView == 1.0) { + offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; + } + vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0); + gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; + v_texCoord = a_texCoord; +} + + +//! FRAGMENT +uniform float u_opacity; +uniform sampler2D u_image; + +void main(void) { + vec4 texColor = texture2D(u_image, v_texCoord); + gl_FragColor.rgb = texColor.rgb; + float alpha = texColor.a * u_opacity; + if (alpha == 0.0) { + discard; + } + gl_FragColor.a = alpha; +} diff --git a/src/ol/render/webgl/textreplay/defaultshader.js b/src/ol/render/webgl/textreplay/defaultshader.js new file mode 100644 index 0000000000..105ca7f257 --- /dev/null +++ b/src/ol/render/webgl/textreplay/defaultshader.js @@ -0,0 +1,148 @@ +// This file is automatically generated, do not edit +/* eslint openlayers-internal/no-missing-requires: 0 */ +goog.provide('ol.render.webgl.textreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + +if (ol.ENABLE_WEBGL) { + + /** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ + ol.render.webgl.textreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.textreplay.defaultshader.Fragment.SOURCE); + }; + ol.inherits(ol.render.webgl.textreplay.defaultshader.Fragment, ol.webgl.Fragment); + + + /** + * @const + * @type {string} + */ + ol.render.webgl.textreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.textreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float i;uniform sampler2D j;void main(void){vec4 texColor=texture2D(j,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*i;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.textreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.textreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.textreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + + ol.render.webgl.textreplay.defaultshader.fragment = new ol.render.webgl.textreplay.defaultshader.Fragment(); + + + /** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ + ol.render.webgl.textreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.textreplay.defaultshader.Vertex.SOURCE); + }; + ol.inherits(ol.render.webgl.textreplay.defaultshader.Vertex, ol.webgl.Vertex); + + + /** + * @const + * @type {string} + */ + ol.render.webgl.textreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n}\n\n\n'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.textreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;attribute vec2 d;attribute float e;uniform mat4 f;uniform mat4 g;uniform mat4 h;void main(void){mat4 offsetMatrix=g;if(e==1.0){offsetMatrix=g*h;}vec4 offsets=offsetMatrix*vec4(d,0.0,0.0);gl_Position=f*vec4(b,0.0,1.0)+offsets;a=c;}'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.textreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.textreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.textreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + + ol.render.webgl.textreplay.defaultshader.vertex = new ol.render.webgl.textreplay.defaultshader.Vertex(); + + + /** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ + ol.render.webgl.textreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_image = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_image' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'h'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'g'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'f'); + + /** + * @type {number} + */ + this.a_offsets = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_offsets' : 'd'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'b'); + + /** + * @type {number} + */ + this.a_rotateWithView = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'e'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c'); + }; + +} From 26bfa7a17210f031a5712a12dc325e6923154e7f Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 7 Jun 2017 12:20:29 +0200 Subject: [PATCH 04/10] Refactor WebGL ImageReplay --- .../render/webgl/imagereplay/defaultshader.js | 154 ------ src/ol/render/webgl/replay.js | 6 +- .../webgl/textreplay/defaultshader.glsl | 41 -- .../render/webgl/textreplay/defaultshader.js | 148 ------ src/ol/render/webgl/texturereplay.js | 497 ++++++++++++++++++ .../defaultshader.glsl | 4 +- .../webgl/texturereplay/defaultshader.js | 154 ++++++ .../ol/render/webgl/texturereplay.test.js | 89 ++++ 8 files changed, 744 insertions(+), 349 deletions(-) delete mode 100644 src/ol/render/webgl/imagereplay/defaultshader.js delete mode 100644 src/ol/render/webgl/textreplay/defaultshader.glsl delete mode 100644 src/ol/render/webgl/textreplay/defaultshader.js create mode 100644 src/ol/render/webgl/texturereplay.js rename src/ol/render/webgl/{imagereplay => texturereplay}/defaultshader.glsl (89%) create mode 100644 src/ol/render/webgl/texturereplay/defaultshader.js create mode 100644 test/spec/ol/render/webgl/texturereplay.test.js diff --git a/src/ol/render/webgl/imagereplay/defaultshader.js b/src/ol/render/webgl/imagereplay/defaultshader.js deleted file mode 100644 index ee5fef2ea4..0000000000 --- a/src/ol/render/webgl/imagereplay/defaultshader.js +++ /dev/null @@ -1,154 +0,0 @@ -// This file is automatically generated, do not edit -/* eslint openlayers-internal/no-missing-requires: 0 */ -goog.provide('ol.render.webgl.imagereplay.defaultshader'); - -goog.require('ol'); -goog.require('ol.webgl.Fragment'); -goog.require('ol.webgl.Vertex'); - -if (ol.ENABLE_WEBGL) { - - /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct - */ - ol.render.webgl.imagereplay.defaultshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE); - }; - ol.inherits(ol.render.webgl.imagereplay.defaultshader.Fragment, ol.webgl.Fragment); - - - /** - * @const - * @type {string} - */ - ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; - - - ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment(); - - - /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct - */ - ol.render.webgl.imagereplay.defaultshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE); - }; - ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex); - - - /** - * @const - * @type {string} - */ - ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.0,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; - - - ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex(); - - - /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ - ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_image = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_image' : 'l'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'k'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); - - /** - * @type {number} - */ - this.a_offsets = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_offsets' : 'e'); - - /** - * @type {number} - */ - this.a_opacity = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_opacity' : 'f'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'c'); - - /** - * @type {number} - */ - this.a_rotateWithView = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'g'); - - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd'); - }; - -} diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index 2222ffd69c..a6d7ef24db 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -140,10 +140,9 @@ if (ol.ENABLE_WEBGL) { * @param {ol.Size} size Size. * @param {number} pixelRatio Pixel ratio. * @return {ol.render.webgl.circlereplay.defaultshader.Locations| - ol.render.webgl.imagereplay.defaultshader.Locations| ol.render.webgl.linestringreplay.defaultshader.Locations| ol.render.webgl.polygonreplay.defaultshader.Locations| - ol.render.webgl.textreplay.defaultshader.Locations} Locations. + ol.render.webgl.texturereplay.defaultshader.Locations} Locations. */ ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {}; @@ -153,10 +152,9 @@ if (ol.ENABLE_WEBGL) { * @protected * @param {WebGLRenderingContext} gl gl. * @param {ol.render.webgl.circlereplay.defaultshader.Locations| - ol.render.webgl.imagereplay.defaultshader.Locations| ol.render.webgl.linestringreplay.defaultshader.Locations| ol.render.webgl.polygonreplay.defaultshader.Locations| - ol.render.webgl.textreplay.defaultshader.Locations} locations Locations. + ol.render.webgl.texturereplay.defaultshader.Locations} locations Locations. */ ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; diff --git a/src/ol/render/webgl/textreplay/defaultshader.glsl b/src/ol/render/webgl/textreplay/defaultshader.glsl deleted file mode 100644 index 7f0c1a2138..0000000000 --- a/src/ol/render/webgl/textreplay/defaultshader.glsl +++ /dev/null @@ -1,41 +0,0 @@ -//! NAMESPACE=ol.render.webgl.textreplay.defaultshader -//! CLASS=ol.render.webgl.textreplay.defaultshader - - -//! COMMON -varying vec2 v_texCoord; - -//! VERTEX -attribute vec2 a_position; -attribute vec2 a_texCoord; -attribute vec2 a_offsets; -attribute float a_rotateWithView; - -uniform mat4 u_projectionMatrix; -uniform mat4 u_offsetScaleMatrix; -uniform mat4 u_offsetRotateMatrix; - -void main(void) { - mat4 offsetMatrix = u_offsetScaleMatrix; - if (a_rotateWithView == 1.0) { - offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; - } - vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0); - gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; - v_texCoord = a_texCoord; -} - - -//! FRAGMENT -uniform float u_opacity; -uniform sampler2D u_image; - -void main(void) { - vec4 texColor = texture2D(u_image, v_texCoord); - gl_FragColor.rgb = texColor.rgb; - float alpha = texColor.a * u_opacity; - if (alpha == 0.0) { - discard; - } - gl_FragColor.a = alpha; -} diff --git a/src/ol/render/webgl/textreplay/defaultshader.js b/src/ol/render/webgl/textreplay/defaultshader.js deleted file mode 100644 index 105ca7f257..0000000000 --- a/src/ol/render/webgl/textreplay/defaultshader.js +++ /dev/null @@ -1,148 +0,0 @@ -// This file is automatically generated, do not edit -/* eslint openlayers-internal/no-missing-requires: 0 */ -goog.provide('ol.render.webgl.textreplay.defaultshader'); - -goog.require('ol'); -goog.require('ol.webgl.Fragment'); -goog.require('ol.webgl.Vertex'); - -if (ol.ENABLE_WEBGL) { - - /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct - */ - ol.render.webgl.textreplay.defaultshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.render.webgl.textreplay.defaultshader.Fragment.SOURCE); - }; - ol.inherits(ol.render.webgl.textreplay.defaultshader.Fragment, ol.webgl.Fragment); - - - /** - * @const - * @type {string} - */ - ol.render.webgl.textreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.textreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float i;uniform sampler2D j;void main(void){vec4 texColor=texture2D(j,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*i;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.textreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.textreplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.textreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; - - - ol.render.webgl.textreplay.defaultshader.fragment = new ol.render.webgl.textreplay.defaultshader.Fragment(); - - - /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct - */ - ol.render.webgl.textreplay.defaultshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.render.webgl.textreplay.defaultshader.Vertex.SOURCE); - }; - ol.inherits(ol.render.webgl.textreplay.defaultshader.Vertex, ol.webgl.Vertex); - - - /** - * @const - * @type {string} - */ - ol.render.webgl.textreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n}\n\n\n'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.textreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;attribute vec2 d;attribute float e;uniform mat4 f;uniform mat4 g;uniform mat4 h;void main(void){mat4 offsetMatrix=g;if(e==1.0){offsetMatrix=g*h;}vec4 offsets=offsetMatrix*vec4(d,0.0,0.0);gl_Position=f*vec4(b,0.0,1.0)+offsets;a=c;}'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.textreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.textreplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.textreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; - - - ol.render.webgl.textreplay.defaultshader.vertex = new ol.render.webgl.textreplay.defaultshader.Vertex(); - - - /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ - ol.render.webgl.textreplay.defaultshader.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_image = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_image' : 'j'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'h'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'g'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'i'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'f'); - - /** - * @type {number} - */ - this.a_offsets = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_offsets' : 'd'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'b'); - - /** - * @type {number} - */ - this.a_rotateWithView = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'e'); - - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c'); - }; - -} diff --git a/src/ol/render/webgl/texturereplay.js b/src/ol/render/webgl/texturereplay.js new file mode 100644 index 0000000000..4c97e8b5c2 --- /dev/null +++ b/src/ol/render/webgl/texturereplay.js @@ -0,0 +1,497 @@ +goog.provide('ol.render.webgl.TextureReplay'); + +goog.require('ol'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.render.webgl.texturereplay.defaultshader'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Context'); + +if (ol.ENABLE_WEBGL) { + + /** + * @constructor + * @abstract + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ + ol.render.webgl.TextureReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @type {number|undefined} + * @protected + */ + this.anchorX = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.anchorY = undefined; + + /** + * @type {Array.} + * @protected + */ + this.groupIndices = []; + + /** + * @type {Array.} + * @protected + */ + this.hitDetectionGroupIndices = []; + + /** + * @type {number|undefined} + * @protected + */ + this.height = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.imageHeight = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.imageWidth = undefined; + + /** + * @protected + * @type {ol.render.webgl.texturereplay.defaultshader.Locations} + */ + this.defaultLocations = null; + + /** + * @protected + * @type {number|undefined} + */ + this.opacity = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.originX = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.originY = undefined; + + /** + * @protected + * @type {boolean|undefined} + */ + this.rotateWithView = undefined; + + /** + * @protected + * @type {number|undefined} + */ + this.rotation = undefined; + + /** + * @protected + * @type {number|undefined} + */ + this.scale = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.width = undefined; + }; + ol.inherits(ol.render.webgl.TextureReplay, ol.render.webgl.Replay); + + + /** + * @inheritDoc + */ + ol.render.webgl.TextureReplay.prototype.getDeleteResourcesFunction = function(context) { + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var textures = this.getTextures(true); + var gl = context.getGL(); + return function() { + if (!gl.isContextLost()) { + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.deleteTexture(textures[i]); + } + } + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; + }; + + + /** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} My end. + * @protected + */ + ol.render.webgl.TextureReplay.prototype.drawCoordinates = function(flatCoordinates, offset, end, stride) { + var anchorX = /** @type {number} */ (this.anchorX); + var anchorY = /** @type {number} */ (this.anchorY); + var height = /** @type {number} */ (this.height); + var imageHeight = /** @type {number} */ (this.imageHeight); + var imageWidth = /** @type {number} */ (this.imageWidth); + var opacity = /** @type {number} */ (this.opacity); + var originX = /** @type {number} */ (this.originX); + var originY = /** @type {number} */ (this.originY); + var rotateWithView = this.rotateWithView ? 1.0 : 0.0; + // this.rotation_ is anti-clockwise, but rotation is clockwise + var rotation = /** @type {number} */ (-this.rotation); + var scale = /** @type {number} */ (this.scale); + var width = /** @type {number} */ (this.width); + var cos = Math.cos(rotation); + var sin = Math.sin(rotation); + var numIndices = this.indices.length; + var numVertices = this.vertices.length; + var i, n, offsetX, offsetY, x, y; + for (i = offset; i < end; i += stride) { + x = flatCoordinates[i] - this.origin[0]; + y = flatCoordinates[i + 1] - this.origin[1]; + + // There are 4 vertices per [x, y] point, one for each corner of the + // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if + // WebGL supported Geometry Shaders (which can emit new vertices), but that + // is not currently the case. + // + // And each vertex includes 8 values: the x and y coordinates, the x and + // y offsets used to calculate the position of the corner, the u and + // v texture coordinates for the corner, the opacity, and whether the + // the image should be rotated with the view (rotateWithView). + + n = numVertices / 8; + + // bottom-left corner + offsetX = -scale * anchorX; + offsetY = -scale * (height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // bottom-right corner + offsetX = scale * (width - anchorX); + offsetY = -scale * (height - anchorY); + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // top-right corner + offsetX = scale * (width - anchorX); + offsetY = scale * anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + // top-left corner + offsetX = -scale * anchorX; + offsetY = scale * anchorY; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + } + + return numVertices; + }; + + + /** + * @protected + * @param {Array.} textures Textures. + * @param {Array.} images + * Images. + * @param {Object.} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. + */ + ol.render.webgl.TextureReplay.prototype.createTextures = function(textures, images, texturePerImage, gl) { + var texture, image, uid, i; + var ii = images.length; + for (i = 0; i < ii; ++i) { + image = images[i]; + + uid = ol.getUid(image).toString(); + if (uid in texturePerImage) { + texture = texturePerImage[uid]; + } else { + texture = ol.webgl.Context.createTexture( + gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); + texturePerImage[uid] = texture; + } + textures[i] = texture; + } + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextureReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader = ol.render.webgl.texturereplay.defaultshader.fragment; + var vertexShader = ol.render.webgl.texturereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + locations = new ol.render.webgl.texturereplay.defaultshader.Locations(gl, program); + this.defaultLocations = locations; + } else { + locations = this.defaultLocations; + } + + // use the program (FIXME: use the return value) + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 32, 0); + + gl.enableVertexAttribArray(locations.a_offsets); + gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, + false, 32, 8); + + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, + false, 32, 16); + + gl.enableVertexAttribArray(locations.a_opacity); + gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT, + false, 32, 24); + + gl.enableVertexAttribArray(locations.a_rotateWithView); + gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, + false, 32, 28); + + return locations; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextureReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_opacity); + gl.disableVertexAttribArray(locations.a_rotateWithView); + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextureReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + var textures = hitDetection ? this.getHitDetectionTextures() : this.getTextures(); + var groupIndices = hitDetection ? this.hitDetectionGroupIndices : this.groupIndices; + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping( + gl, context, skippedFeaturesHash, textures, groupIndices); + } else { + var i, ii, start; + for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var end = groupIndices[i]; + this.drawElements(gl, context, start, end); + start = end; + } + } + }; + + + /** + * Draw the replay while paying attention to skipped features. + * + * This functions creates groups of features that can be drawn to together, + * so that the number of `drawElements` calls is minimized. + * + * For example given the following texture groups: + * + * Group 1: A B C + * Group 2: D [E] F G + * + * If feature E should be skipped, the following `drawElements` calls will be + * made: + * + * drawElements with feature A, B and C + * drawElements with feature D + * drawElements with feature F and G + * + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.} textures Textures. + * @param {Array.} groupIndices Texture group indices. + */ + ol.render.webgl.TextureReplay.prototype.drawReplaySkipping = function(gl, context, skippedFeaturesHash, textures, + groupIndices) { + var featureIndex = 0; + + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var groupStart = (i > 0) ? groupIndices[i - 1] : 0; + var groupEnd = groupIndices[i]; + + var start = groupStart; + var end = groupStart; + while (featureIndex < this.startIndices.length && + this.startIndices[featureIndex] <= groupEnd) { + var feature = this.startIndicesFeature[featureIndex]; + + var featureUid = ol.getUid(feature).toString(); + if (skippedFeaturesHash[featureUid] !== undefined) { + // feature should be skipped + if (start !== end) { + // draw the features so far + this.drawElements(gl, context, start, end); + } + // continue with the next feature + start = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; + end = start; + } else { + // the feature is not skipped, augment the end index + end = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; + } + featureIndex++; + } + + if (start !== end) { + // draw the remaining features (in case there was no skipped feature + // in this texture group, all features of a group are drawn together) + this.drawElements(gl, context, start, end); + } + } + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextureReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + var i, groupStart, start, end, feature, featureUid; + var featureIndex = this.startIndices.length - 1; + var hitDetectionTextures = this.getHitDetectionTextures(); + for (i = hitDetectionTextures.length - 1; i >= 0; --i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, hitDetectionTextures[i]); + groupStart = (i > 0) ? this.hitDetectionGroupIndices[i - 1] : 0; + end = this.hitDetectionGroupIndices[i]; + + // draw all features for this texture group + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + if (result) { + return result; + } + } + + end = start; + featureIndex--; + } + } + return undefined; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextureReplay.prototype.finish = function(context) { + this.anchorX = undefined; + this.anchorY = undefined; + this.height = undefined; + 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; + }; + + + /** + * @abstract + * @protected + * @param {boolean=} opt_all Return hit detection textures with regular ones. + * @returns {Array.} Textures. + */ + ol.render.webgl.TextureReplay.prototype.getTextures = function(opt_all) {}; + + + /** + * @abstract + * @protected + * @returns {Array.} Textures. + */ + ol.render.webgl.TextureReplay.prototype.getHitDetectionTextures = function() {}; +} diff --git a/src/ol/render/webgl/imagereplay/defaultshader.glsl b/src/ol/render/webgl/texturereplay/defaultshader.glsl similarity index 89% rename from src/ol/render/webgl/imagereplay/defaultshader.glsl rename to src/ol/render/webgl/texturereplay/defaultshader.glsl index b1b6168fff..3bf3e86da5 100644 --- a/src/ol/render/webgl/imagereplay/defaultshader.glsl +++ b/src/ol/render/webgl/texturereplay/defaultshader.glsl @@ -1,5 +1,5 @@ -//! NAMESPACE=ol.render.webgl.imagereplay.defaultshader -//! CLASS=ol.render.webgl.imagereplay.defaultshader +//! NAMESPACE=ol.render.webgl.texturereplay.defaultshader +//! CLASS=ol.render.webgl.texturereplay.defaultshader //! COMMON diff --git a/src/ol/render/webgl/texturereplay/defaultshader.js b/src/ol/render/webgl/texturereplay/defaultshader.js new file mode 100644 index 0000000000..016a8c2979 --- /dev/null +++ b/src/ol/render/webgl/texturereplay/defaultshader.js @@ -0,0 +1,154 @@ +// This file is automatically generated, do not edit +/* eslint openlayers-internal/no-missing-requires: 0 */ +goog.provide('ol.render.webgl.texturereplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + +if (ol.ENABLE_WEBGL) { + + /** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ + ol.render.webgl.texturereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE); + }; + ol.inherits(ol.render.webgl.texturereplay.defaultshader.Fragment, ol.webgl.Fragment); + + + /** + * @const + * @type {string} + */ + ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + + ol.render.webgl.texturereplay.defaultshader.fragment = new ol.render.webgl.texturereplay.defaultshader.Fragment(); + + + /** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ + ol.render.webgl.texturereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE); + }; + ol.inherits(ol.render.webgl.texturereplay.defaultshader.Vertex, ol.webgl.Vertex); + + + /** + * @const + * @type {string} + */ + ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.0,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}'; + + + /** + * @const + * @type {string} + */ + ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + + ol.render.webgl.texturereplay.defaultshader.vertex = new ol.render.webgl.texturereplay.defaultshader.Vertex(); + + + /** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ + ol.render.webgl.texturereplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_image = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_image' : 'l'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'k'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); + + /** + * @type {number} + */ + this.a_offsets = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_offsets' : 'e'); + + /** + * @type {number} + */ + this.a_opacity = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_opacity' : 'f'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'c'); + + /** + * @type {number} + */ + this.a_rotateWithView = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'g'); + + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd'); + }; + +} diff --git a/test/spec/ol/render/webgl/texturereplay.test.js b/test/spec/ol/render/webgl/texturereplay.test.js new file mode 100644 index 0000000000..5e85440a6b --- /dev/null +++ b/test/spec/ol/render/webgl/texturereplay.test.js @@ -0,0 +1,89 @@ +goog.provide('ol.test.render.webgl.TextureReplay'); + +goog.require('ol.render.webgl.TextureReplay'); +goog.require('ol.render.webgl.texturereplay.defaultshader'); + +describe('ol.render.webgl.TextureReplay', function() { + var replay; + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.TextureReplay(tolerance, maxExtent); + }); + + describe('#setUpProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('returns the locations used by the shaders', function() { + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + expect(locations).to.be.a( + ol.render.webgl.texturereplay.defaultshader.Locations); + }); + + it('gets and compiles the shaders', function() { + sinon.spy(context, 'getProgram'); + sinon.spy(context, 'useProgram'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(context.getProgram.calledWithExactly( + ol.render.webgl.texturereplay.defaultshader.fragment, + ol.render.webgl.texturereplay.defaultshader.vertex)).to.be(true); + expect(context.useProgram.calledOnce).to.be(true); + }); + + it('initializes the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'vertexAttribPointer'); + sinon.spy(gl, 'enableVertexAttribArray'); + + replay.setUpProgram(gl, context, [2, 2], 1); + expect(gl.vertexAttribPointer.callCount).to.be(gl.getAttribLocation.callCount); + expect(gl.enableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); + + describe('#shutDownProgram', function() { + var context, gl; + beforeEach(function() { + context = { + getProgram: function() {}, + useProgram: function() {} + }; + gl = { + enableVertexAttribArray: function() {}, + disableVertexAttribArray: function() {}, + vertexAttribPointer: function() {}, + uniform1f: function() {}, + uniform2fv: function() {}, + getUniformLocation: function() {}, + getAttribLocation: function() {} + }; + }); + + it('disables the attrib pointers', function() { + sinon.spy(gl, 'getAttribLocation'); + sinon.spy(gl, 'disableVertexAttribArray'); + + var locations = replay.setUpProgram(gl, context, [2, 2], 1); + replay.shutDownProgram(gl, locations); + expect(gl.disableVertexAttribArray.callCount).to.be( + gl.getAttribLocation.callCount); + }); + }); +}); From bd87ec7c832c7e636c25d80bdcfac2826341aa7f Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 7 Jun 2017 12:57:03 +0200 Subject: [PATCH 05/10] Conform ImageReplay to the new structure --- src/ol/render/webgl/imagereplay.js | 524 ++---------------- test/spec/ol/render/webgl/imagereplay.test.js | 120 ++-- 2 files changed, 85 insertions(+), 559 deletions(-) diff --git a/src/ol/render/webgl/imagereplay.js b/src/ol/render/webgl/imagereplay.js index 16fb0be045..47f22045ef 100644 --- a/src/ol/render/webgl/imagereplay.js +++ b/src/ol/render/webgl/imagereplay.js @@ -1,123 +1,34 @@ goog.provide('ol.render.webgl.ImageReplay'); goog.require('ol'); -goog.require('ol.extent'); -goog.require('ol.obj'); -goog.require('ol.render.webgl.imagereplay.defaultshader'); -goog.require('ol.render.webgl.Replay'); -goog.require('ol.webgl'); +goog.require('ol.render.webgl.TextureReplay'); goog.require('ol.webgl.Buffer'); -goog.require('ol.webgl.Context'); if (ol.ENABLE_WEBGL) { /** * @constructor - * @extends {ol.render.webgl.Replay} + * @extends {ol.render.webgl.TextureReplay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @struct */ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @type {number|undefined} - * @private - */ - this.anchorX_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.anchorY_ = undefined; - - /** - * @type {Array.} - * @private - */ - this.groupIndices_ = []; - - /** - * @type {Array.} - * @private - */ - this.hitDetectionGroupIndices_ = []; - - /** - * @type {number|undefined} - * @private - */ - this.height_ = undefined; + ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); /** * @type {Array.} - * @private + * @protected */ this.images_ = []; /** * @type {Array.} - * @private + * @protected */ this.hitDetectionImages_ = []; - /** - * @type {number|undefined} - * @private - */ - this.imageHeight_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.imageWidth_ = undefined; - - /** - * @private - * @type {ol.render.webgl.imagereplay.defaultshader.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {number|undefined} - */ - this.opacity_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.originX_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.originY_ = undefined; - - /** - * @private - * @type {boolean|undefined} - */ - this.rotateWithView_ = undefined; - - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = undefined; - - /** - * @private - * @type {number|undefined} - */ - this.scale_ = undefined; - /** * @type {Array.} * @private @@ -130,141 +41,8 @@ if (ol.ENABLE_WEBGL) { */ this.hitDetectionTextures_ = []; - /** - * @type {number|undefined} - * @private - */ - this.width_ = undefined; - }; - ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); - - - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { - 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()) { - var i, ii; - 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); - }; - }; - - - /** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} My end. - * @private - */ - ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { - var anchorX = /** @type {number} */ (this.anchorX_); - var anchorY = /** @type {number} */ (this.anchorY_); - var height = /** @type {number} */ (this.height_); - var imageHeight = /** @type {number} */ (this.imageHeight_); - var imageWidth = /** @type {number} */ (this.imageWidth_); - var opacity = /** @type {number} */ (this.opacity_); - var originX = /** @type {number} */ (this.originX_); - var originY = /** @type {number} */ (this.originY_); - var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; - // this.rotation_ is anti-clockwise, but rotation is clockwise - var rotation = /** @type {number} */ (-this.rotation_); - var scale = /** @type {number} */ (this.scale_); - var width = /** @type {number} */ (this.width_); - var cos = Math.cos(rotation); - var sin = Math.sin(rotation); - var numIndices = this.indices.length; - var numVertices = this.vertices.length; - var i, n, offsetX, offsetY, x, y; - for (i = offset; i < end; i += stride) { - x = flatCoordinates[i] - this.origin[0]; - y = flatCoordinates[i + 1] - this.origin[1]; - - // There are 4 vertices per [x, y] point, one for each corner of the - // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if - // WebGL supported Geometry Shaders (which can emit new vertices), but that - // is not currently the case. - // - // And each vertex includes 8 values: the x and y coordinates, the x and - // y offsets used to calculate the position of the corner, the u and - // v texture coordinates for the corner, the opacity, and whether the - // the image should be rotated with the view (rotateWithView). - - n = numVertices / 8; - - // bottom-left corner - offsetX = -scale * anchorX; - offsetY = -scale * (height - anchorY); - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = originX / imageWidth; - this.vertices[numVertices++] = (originY + height) / imageHeight; - this.vertices[numVertices++] = opacity; - this.vertices[numVertices++] = rotateWithView; - - // bottom-right corner - offsetX = scale * (width - anchorX); - offsetY = -scale * (height - anchorY); - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = (originX + width) / imageWidth; - this.vertices[numVertices++] = (originY + height) / imageHeight; - this.vertices[numVertices++] = opacity; - this.vertices[numVertices++] = rotateWithView; - - // top-right corner - offsetX = scale * (width - anchorX); - offsetY = scale * anchorY; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = (originX + width) / imageWidth; - this.vertices[numVertices++] = originY / imageHeight; - this.vertices[numVertices++] = opacity; - this.vertices[numVertices++] = rotateWithView; - - // top-left corner - offsetX = -scale * anchorX; - offsetY = scale * anchorY; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = originX / imageWidth; - this.vertices[numVertices++] = originY / imageHeight; - this.vertices[numVertices++] = opacity; - this.vertices[numVertices++] = rotateWithView; - - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 1; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n + 3; - } - - return numVertices; }; + ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.TextureReplay); /** @@ -275,7 +53,7 @@ if (ol.ENABLE_WEBGL) { this.startIndicesFeature.push(feature); var flatCoordinates = multiPointGeometry.getFlatCoordinates(); var stride = multiPointGeometry.getStride(); - this.drawCoordinates_( + this.drawCoordinates( flatCoordinates, 0, flatCoordinates.length, stride); }; @@ -288,7 +66,7 @@ if (ol.ENABLE_WEBGL) { this.startIndicesFeature.push(feature); var flatCoordinates = pointGeometry.getFlatCoordinates(); var stride = pointGeometry.getStride(); - this.drawCoordinates_( + this.drawCoordinates( flatCoordinates, 0, flatCoordinates.length, stride); }; @@ -299,8 +77,8 @@ if (ol.ENABLE_WEBGL) { ol.render.webgl.ImageReplay.prototype.finish = function(context) { var gl = context.getGL(); - this.groupIndices_.push(this.indices.length); - this.hitDetectionGroupIndices_.push(this.indices.length); + this.groupIndices.push(this.indices.length); + this.hitDetectionGroupIndices.push(this.indices.length); // create, bind, and populate the vertices buffer this.verticesBuffer = new ol.webgl.Buffer(this.vertices); @@ -314,246 +92,14 @@ if (ol.ENABLE_WEBGL) { /** @type {Object.} */ var texturePerImage = {}; - this.createTextures_(this.textures_, this.images_, texturePerImage, gl); + this.createTextures(this.textures_, this.images_, texturePerImage, gl); - this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, + this.createTextures(this.hitDetectionTextures_, this.hitDetectionImages_, texturePerImage, gl); - 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) { - var texture, image, uid, i; - var ii = images.length; - for (i = 0; i < ii; ++i) { - image = images[i]; - - uid = ol.getUid(image).toString(); - if (uid in texturePerImage) { - texture = texturePerImage[uid]; - } else { - texture = ol.webgl.Context.createTexture( - gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); - texturePerImage[uid] = texture; - } - textures[i] = texture; - } - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; - var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - locations = new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - // use the program (FIXME: use the return value) - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 32, 0); - - gl.enableVertexAttribArray(locations.a_offsets); - gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, - false, 32, 8); - - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, - false, 32, 16); - - gl.enableVertexAttribArray(locations.a_opacity); - gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT, - false, 32, 24); - - gl.enableVertexAttribArray(locations.a_rotateWithView); - gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, - false, 32, 28); - - return locations; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_offsets); - gl.disableVertexAttribArray(locations.a_texCoord); - gl.disableVertexAttribArray(locations.a_opacity); - gl.disableVertexAttribArray(locations.a_rotateWithView); - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { - var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_; - var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; - - if (!ol.obj.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_( - gl, context, skippedFeaturesHash, textures, groupIndices); - } else { - var i, ii, start; - for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - var end = groupIndices[i]; - this.drawElements(gl, context, start, end); - start = end; - } - } - }; - - - /** - * Draw the replay while paying attention to skipped features. - * - * This functions creates groups of features that can be drawn to together, - * so that the number of `drawElements` calls is minimized. - * - * For example given the following texture groups: - * - * Group 1: A B C - * Group 2: D [E] F G - * - * If feature E should be skipped, the following `drawElements` calls will be - * made: - * - * drawElements with feature A, B and C - * drawElements with feature D - * drawElements with feature F and G - * - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {Array.} textures Textures. - * @param {Array.} groupIndices Texture group indices. - */ - ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures, - groupIndices) { - var featureIndex = 0; - - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - var groupStart = (i > 0) ? groupIndices[i - 1] : 0; - var groupEnd = groupIndices[i]; - - var start = groupStart; - var end = groupStart; - while (featureIndex < this.startIndices.length && - this.startIndices[featureIndex] <= groupEnd) { - var feature = this.startIndicesFeature[featureIndex]; - - var featureUid = ol.getUid(feature).toString(); - if (skippedFeaturesHash[featureUid] !== undefined) { - // feature should be skipped - if (start !== end) { - // draw the features so far - this.drawElements(gl, context, start, end); - } - // continue with the next feature - start = (featureIndex === this.startIndices.length - 1) ? - groupEnd : this.startIndices[featureIndex + 1]; - end = start; - } else { - // the feature is not skipped, augment the end index - end = (featureIndex === this.startIndices.length - 1) ? - groupEnd : this.startIndices[featureIndex + 1]; - } - featureIndex++; - } - - if (start !== end) { - // draw the remaining features (in case there was no skipped feature - // in this texture group, all features of a group are drawn together) - this.drawElements(gl, context, start, end); - } - } - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - var i, groupStart, start, end, feature, featureUid; - var featureIndex = this.startIndices.length - 1; - for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { - gl.bindTexture(ol.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 && - this.startIndices[featureIndex] >= groupStart) { - start = this.startIndices[featureIndex]; - feature = this.startIndicesFeature[featureIndex]; - featureUid = ol.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements(gl, context, start, end); - - var result = featureCallback(feature); - if (result) { - return result; - } - } - - end = start; - featureIndex--; - } - } - return undefined; + ol.render.webgl.TextureReplay.prototype.finish.call(this, context); }; @@ -578,7 +124,7 @@ if (ol.ENABLE_WEBGL) { } else { currentImage = this.images_[this.images_.length - 1]; if (ol.getUid(currentImage) != ol.getUid(image)) { - this.groupIndices_.push(this.indices.length); + this.groupIndices.push(this.indices.length); this.images_.push(image); } } @@ -589,23 +135,39 @@ if (ol.ENABLE_WEBGL) { currentImage = this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { - this.hitDetectionGroupIndices_.push(this.indices.length); + this.hitDetectionGroupIndices.push(this.indices.length); this.hitDetectionImages_.push(hitDetectionImage); } } - this.anchorX_ = anchor[0]; - this.anchorY_ = anchor[1]; - this.height_ = size[1]; - this.imageHeight_ = imageSize[1]; - this.imageWidth_ = imageSize[0]; - this.opacity_ = opacity; - this.originX_ = origin[0]; - this.originY_ = origin[1]; - this.rotation_ = rotation; - this.rotateWithView_ = rotateWithView; - this.scale_ = scale; - this.width_ = size[0]; + this.anchorX = anchor[0]; + this.anchorY = anchor[1]; + this.height = size[1]; + this.imageHeight = imageSize[1]; + this.imageWidth = imageSize[0]; + this.opacity = opacity; + this.originX = origin[0]; + this.originY = origin[1]; + this.rotation = rotation; + this.rotateWithView = rotateWithView; + this.scale = scale; + this.width = size[0]; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.getTextures = function(opt_all) { + return opt_all ? this.textures_.concat(this.hitDetectionTextures_) : this.textures_; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.getHitDetectionTextures = function() { + return this.hitDetectionTextures_; }; } diff --git a/test/spec/ol/render/webgl/imagereplay.test.js b/test/spec/ol/render/webgl/imagereplay.test.js index 7e5b1ce621..05e1571d78 100644 --- a/test/spec/ol/render/webgl/imagereplay.test.js +++ b/test/spec/ol/render/webgl/imagereplay.test.js @@ -2,7 +2,6 @@ goog.provide('ol.test.render.webgl.ImageReplay'); goog.require('ol.geom.MultiPoint'); goog.require('ol.geom.Point'); -goog.require('ol.render.webgl.imagereplay.defaultshader'); goog.require('ol.render.webgl.ImageReplay'); goog.require('ol.style.Image'); @@ -57,34 +56,34 @@ describe('ol.render.webgl.ImageReplay', function() { it('set expected states', function() { replay.setImageStyle(imageStyle1); - expect(replay.anchorX_).to.be(0.5); - expect(replay.anchorY_).to.be(1); - expect(replay.height_).to.be(256); - expect(replay.imageHeight_).to.be(512); - expect(replay.imageWidth_).to.be(512); - expect(replay.opacity_).to.be(0.1); - expect(replay.originX_).to.be(200); - expect(replay.originY_).to.be(200); - expect(replay.rotation_).to.be(1.5); - expect(replay.rotateWithView_).to.be(true); - expect(replay.scale_).to.be(2.0); - expect(replay.width_).to.be(256); + expect(replay.anchorX).to.be(0.5); + expect(replay.anchorY).to.be(1); + expect(replay.height).to.be(256); + expect(replay.imageHeight).to.be(512); + expect(replay.imageWidth).to.be(512); + expect(replay.opacity).to.be(0.1); + expect(replay.originX).to.be(200); + expect(replay.originY).to.be(200); + expect(replay.rotation).to.be(1.5); + expect(replay.rotateWithView).to.be(true); + expect(replay.scale).to.be(2.0); + expect(replay.width).to.be(256); expect(replay.images_).to.have.length(1); - expect(replay.groupIndices_).to.have.length(0); + expect(replay.groupIndices).to.have.length(0); expect(replay.hitDetectionImages_).to.have.length(1); - expect(replay.hitDetectionGroupIndices_).to.have.length(0); + 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.groupIndices).to.have.length(0); expect(replay.hitDetectionImages_).to.have.length(1); - expect(replay.hitDetectionGroupIndices_).to.have.length(0); + 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.groupIndices).to.have.length(1); expect(replay.hitDetectionImages_).to.have.length(2); - expect(replay.hitDetectionGroupIndices_).to.have.length(1); + expect(replay.hitDetectionGroupIndices).to.have.length(1); }); }); @@ -168,78 +167,43 @@ describe('ol.render.webgl.ImageReplay', function() { }); }); - describe('#setUpProgram', function() { - var context, gl; + describe('#getTextures', function() { beforeEach(function() { - context = { - getProgram: function() {}, - useProgram: function() {} - }; - gl = { - enableVertexAttribArray: function() {}, - vertexAttribPointer: function() {}, - uniform1f: function() {}, - uniform2fv: function() {}, - getUniformLocation: function() {}, - getAttribLocation: function() {} - }; + replay.textures_ = [1, 2]; + replay.hitDetectionTextures_ = [3, 4]; }); - it('returns the locations used by the shaders', function() { - var locations = replay.setUpProgram(gl, context, [2, 2], 1); - expect(locations).to.be.a( - ol.render.webgl.imagereplay.defaultshader.Locations); + it('returns the textures', function() { + var textures = replay.getTextures(); + + expect(textures).to.have.length(2); + expect(textures[0]).to.be(1); + expect(textures[1]).to.be(2); }); - it('gets and compiles the shaders', function() { - sinon.spy(context, 'getProgram'); - sinon.spy(context, 'useProgram'); + it('can additionally return the hit detection textures', function() { + var textures = replay.getTextures(true); - replay.setUpProgram(gl, context, [2, 2], 1); - expect(context.getProgram.calledWithExactly( - ol.render.webgl.imagereplay.defaultshader.fragment, - ol.render.webgl.imagereplay.defaultshader.vertex)).to.be(true); - expect(context.useProgram.calledOnce).to.be(true); - }); - - it('initializes the attrib pointers', function() { - sinon.spy(gl, 'getAttribLocation'); - sinon.spy(gl, 'vertexAttribPointer'); - sinon.spy(gl, 'enableVertexAttribArray'); - - replay.setUpProgram(gl, context, [2, 2], 1); - expect(gl.vertexAttribPointer.callCount).to.be(gl.getAttribLocation.callCount); - expect(gl.enableVertexAttribArray.callCount).to.be( - gl.getAttribLocation.callCount); + expect(textures).to.have.length(4); + expect(textures[0]).to.be(1); + expect(textures[1]).to.be(2); + expect(textures[2]).to.be(3); + expect(textures[3]).to.be(4); }); }); - describe('#shutDownProgram', function() { - var context, gl; + describe('#getHitDetectionTextures', function() { beforeEach(function() { - context = { - getProgram: function() {}, - useProgram: function() {} - }; - gl = { - enableVertexAttribArray: function() {}, - disableVertexAttribArray: function() {}, - vertexAttribPointer: function() {}, - uniform1f: function() {}, - uniform2fv: function() {}, - getUniformLocation: function() {}, - getAttribLocation: function() {} - }; + replay.textures_ = [1, 2]; + replay.hitDetectionTextures_ = [3, 4]; }); - it('disables the attrib pointers', function() { - sinon.spy(gl, 'getAttribLocation'); - sinon.spy(gl, 'disableVertexAttribArray'); + it('returns the hit detection textures', function() { + var textures = replay.getHitDetectionTextures(); - var locations = replay.setUpProgram(gl, context, [2, 2], 1); - replay.shutDownProgram(gl, locations); - expect(gl.disableVertexAttribArray.callCount).to.be( - gl.getAttribLocation.callCount); + expect(textures).to.have.length(2); + expect(textures[0]).to.be(3); + expect(textures[1]).to.be(4); }); }); }); From 7b9833fdcee169d230f9e9987b6241272f8f42c1 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 7 Jun 2017 12:58:29 +0200 Subject: [PATCH 06/10] Conform TextReplay to the new structure --- src/ol/render/webgl.js | 8 +- src/ol/render/webgl/textreplay.js | 427 +++++++----------------------- 2 files changed, 100 insertions(+), 335 deletions(-) diff --git a/src/ol/render/webgl.js b/src/ol/render/webgl.js index cac93aa290..26c10c7401 100644 --- a/src/ol/render/webgl.js +++ b/src/ol/render/webgl.js @@ -62,16 +62,16 @@ if (ol.ENABLE_WEBGL) { /** * @const - * @type {string} + * @type {number} */ - ol.render.webgl.defaultTextAlign = 'center'; + ol.render.webgl.defaultTextAlign = 0.5; /** * @const - * @type {string} + * @type {number} */ - ol.render.webgl.defaultTextBaseline = 'middle'; + ol.render.webgl.defaultTextBaseline = 0.5; /** diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index 101a249459..43f65080e8 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -3,45 +3,23 @@ goog.provide('ol.render.webgl.TextReplay'); goog.require('ol'); goog.require('ol.colorlike'); goog.require('ol.dom'); -goog.require('ol.extent'); goog.require('ol.has'); goog.require('ol.render.webgl'); -goog.require('ol.render.webgl.textreplay.defaultshader'); -goog.require('ol.render.webgl.Replay'); -goog.require('ol.webgl'); +goog.require('ol.render.webgl.TextureReplay'); goog.require('ol.webgl.Buffer'); -goog.require('ol.webgl.Context'); if (ol.ENABLE_WEBGL) { /** * @constructor - * @extends {ol.render.webgl.Replay} + * @extends {ol.render.webgl.TextureReplay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @struct */ ol.render.webgl.TextReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.textreplay.defaultshader.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; + ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); /** * @private @@ -71,17 +49,13 @@ if (ol.ENABLE_WEBGL) { * lineWidth: number, * miterLimit: (number|undefined), * fillColor: (ol.ColorLike|null), - * text: string, + * text: (string|undefined), * font: (string|undefined), - * textAlign: (string|undefined), - * textBaseline: (string|undefined), + * textAlign: (number|undefined), + * textBaseline: (number|undefined), * offsetX: (number|undefined), * offsetY: (number|undefined), - * scale: (number|undefined), - * rotation: (number|undefined), - * rotateWithView: (boolean|undefined), - * height: (number|undefined), - * width: (number|undefined)}} + * scale: (number|undefined)}} */ this.state_ = { strokeColor: null, @@ -98,15 +72,19 @@ if (ol.ENABLE_WEBGL) { textBaseline: undefined, offsetX: undefined, offsetY: undefined, - scale: undefined, - rotation: undefined, - rotateWithView: undefined, - height: undefined, - width: undefined + scale: undefined }; + this.originX = 0; + + this.originY = 0; + + this.scale = 1; + + this.opacity = 1; + }; - ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.Replay); + ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.TextureReplay); /** @@ -117,12 +95,11 @@ if (ol.ENABLE_WEBGL) { //For now we create one texture per feature. That is, only multiparts are grouped. //TODO: speed up rendering with SDF, or at least glyph atlases var state = this.state_; - if (state.text && (state.fillColor || state.strokeColor)) { + if (state.text) { this.startIndices.push(this.indices.length); this.startIndicesFeature.push(feature); - this.images_.push(this.createTextImage_()); - this.drawCoordinates_( + this.drawCoordinates( flatCoordinates, offset, end, stride); } }; @@ -142,7 +119,9 @@ if (ol.ENABLE_WEBGL) { var lines = state.text.split('\n'); //FIXME: use pixelRatio var textHeight = Math.ceil(lineHeight * lines.length * state.scale); - state.height = textHeight; + this.height = textHeight; + this.imageHeight = textHeight; + this.anchorY = Math.round(textHeight * state.textBaseline + state.offsetY); var longestLine = lines.map(function(str) { return mCtx.measureText(str).width; }).reduce(function(max, curr) { @@ -150,30 +129,33 @@ if (ol.ENABLE_WEBGL) { }); //FIXME: use pixelRatio var textWidth = Math.ceil((longestLine + state.lineWidth * 2) * state.scale); - state.width = textWidth; + this.width = textWidth; + this.imageWidth = textWidth; + this.anchorX = Math.round(textWidth * state.textAlign + state.offsetX); //Create a canvas var ctx = ol.dom.createCanvasContext2D(textWidth, textHeight); var canvas = ctx.canvas; //Parameterize the canvas - ctx.font = state.font; + ctx.font = /** @type {string} */ (state.font); ctx.fillStyle = state.fillColor; ctx.strokeStyle = state.strokeColor; ctx.lineWidth = state.lineWidth; - ctx.lineCap = state.lineCap; - ctx.lineJoin = state.lineJoin; - ctx.miterLimit = state.miterLimit; + ctx.lineCap = /*** @type {string} */ (state.lineCap); + ctx.lineJoin = /** @type {string} */ (state.lineJoin); + ctx.miterLimit = /** @type {number} */ (state.miterLimit); ctx.textAlign = 'left'; ctx.textBaseline = 'top'; if (ol.has.CANVAS_LINE_DASH && state.lineDash) { //FIXME: use pixelRatio ctx.setLineDash(state.lineDash); - ctx.lineDashOffset = state.lineDashOffset; + ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset); } if (state.scale !== 1) { //FIXME: use pixelRatio - ctx.setTransform(state.scale, 0, 0, state.scale, 0, 0); + ctx.setTransform(/** @type {number} */ (state.scale), 0, 0, + /** @type {number} */ (state.scale), 0, 0); } //Draw the text on the canvas @@ -192,134 +174,14 @@ if (ol.ENABLE_WEBGL) { }; - /** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} My end. - * @private - */ - ol.render.webgl.TextReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { - var state = this.state_; - var anchorX, anchorY; - var height = /** @type {number} */ (state.height); - var width = /** @type {number} */ (state.width); - switch (state.textAlign) { - default: - anchorX = width / 2; - break; - case 'left': case 'end': - anchorX = 0; - break; - case 'right': case 'start': - anchorX = width; - break; - } - switch (state.textBaseline) { - default: - anchorY = height / 2; - break; - case 'top': - anchorY = 0; - break; - case 'bottom': - anchorY = height; - break; - case 'hanging': - anchorY = height * 0.2; - break; - case 'alphabetic': case 'ideographic': - anchorY = height * 0.8; - break; - } - var rotateWithView = state.rotateWithView ? 1.0 : 0.0; - // this.rotation_ is anti-clockwise, but rotation is clockwise - var rotation = /** @type {number} */ (-state.rotation); - var cos = Math.cos(rotation); - var sin = Math.sin(rotation); - var numIndices = this.indices.length; - var numVertices = this.vertices.length; - var i, n, offsetX, offsetY, x, y; - for (i = offset; i < end; i += stride) { - x = flatCoordinates[i] - this.origin[0]; - y = flatCoordinates[i + 1] - this.origin[1]; - - // There are 4 vertices per [x, y] point, one for each corner of the - // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if - // WebGL supported Geometry Shaders (which can emit new vertices), but that - // is not currently the case. - // - // And each vertex includes 7 values: the x and y coordinates, the x and - // y offsets used to calculate the position of the corner, the u and - // v texture coordinates for the corner, and whether the - // the image should be rotated with the view (rotateWithView). - - n = numVertices / 7; - - // bottom-left corner - offsetX = -anchorX; - offsetY = -(height - anchorY); - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = rotateWithView; - - // bottom-right corner - offsetX = width - anchorX; - offsetY = -(height - anchorY); - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = rotateWithView; - - // top-right corner - offsetX = width - anchorX; - offsetY = anchorY; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = rotateWithView; - - // top-left corner - offsetX = -anchorX; - offsetY = anchorY; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - this.vertices[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = rotateWithView; - - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 1; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n + 3; - } - - return numVertices; - }; - - /** * @inheritDoc */ ol.render.webgl.TextReplay.prototype.finish = function(context) { var gl = context.getGL(); - this.startIndices.push(this.indices.length); + this.groupIndices.push(this.indices.length); + this.hitDetectionGroupIndices = this.groupIndices; // create, bind, and populate the vertices buffer this.verticesBuffer = new ol.webgl.Buffer(this.vertices); @@ -328,9 +190,11 @@ if (ol.ENABLE_WEBGL) { this.indicesBuffer = new ol.webgl.Buffer(this.indices); // create textures - this.createTextures_(gl); + /** @type {Object.} */ + var texturePerImage = {}; + + this.createTextures(this.textures_, this.images_, texturePerImage, gl); - this.images_ = []; this.state_ = { strokeColor: null, lineCap: undefined, @@ -346,155 +210,10 @@ if (ol.ENABLE_WEBGL) { textBaseline: undefined, offsetX: undefined, offsetY: undefined, - scale: undefined, - rotation: undefined, - rotateWithView: undefined, - height: undefined, - width: undefined + scale: undefined }; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader = ol.render.webgl.textreplay.defaultshader.fragment; - var vertexShader = ol.render.webgl.textreplay.defaultshader.vertex; - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - locations = new ol.render.webgl.textreplay.defaultshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - // use the program (FIXME: use the return value) - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 28, 0); - - gl.enableVertexAttribArray(locations.a_offsets); - gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, - false, 28, 8); - - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, - false, 28, 16); - - gl.enableVertexAttribArray(locations.a_rotateWithView); - gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, - false, 28, 24); - - return locations; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_offsets); - gl.disableVertexAttribArray(locations.a_texCoord); - gl.disableVertexAttribArray(locations.a_rotateWithView); - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { - var textures = this.textures_; - var startIndices = this.startIndices; - - var i, ii, start, end, feature, featureUid; - for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { - feature = this.startIndicesFeature[i]; - featureUid = ol.getUid(feature).toString(); - - end = startIndices[i]; - if (skippedFeaturesHash[featureUid] === undefined) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - this.drawElements(gl, context, start, end); - } - start = end; - } - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - var textures = this.textures_; - var startIndices = this.startIndices; - - var i, start, end, feature, featureUid; - for (i = textures.length - 1; i >= 0; --i) { - feature = this.startIndicesFeature[i]; - featureUid = ol.getUid(feature).toString(); - - start = (i > 0) ? startIndices[i - 1] : 0; - end = startIndices[i]; - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements(gl, context, start, end); - - var result = featureCallback(feature); - if (result) { - return result; - } - } - } - return undefined; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { - var verticesBuffer = this.verticesBuffer; - var indicesBuffer = this.indicesBuffer; - var textures = this.textures_; - var gl = context.getGL(); - return function() { - if (!gl.isContextLost()) { - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { - gl.deleteTexture(textures[i]); - } - } - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - }; - }; - - /** - * @private - * @param {WebGLRenderingContext} gl Gl. - */ - ol.render.webgl.TextReplay.prototype.createTextures_ = function(gl) { - for (var i = 0, ii = this.images_.length; i < ii; ++i) { - var image = this.images_[i]; - this.textures_.push(ol.webgl.Context.createTexture( - gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE)); - } + this.images_ = null; + ol.render.webgl.TextureReplay.prototype.finish.call(this, context); }; @@ -503,10 +222,11 @@ if (ol.ENABLE_WEBGL) { */ ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) { var state = this.state_; - if (!textStyle) { + var textFillStyle = textStyle.getFill(); + var textStrokeStyle = textStyle.getStroke(); + if (!textStyle || !textStyle.getText() || (!textFillStyle && !textStrokeStyle)) { state.text = ''; } else { - var textFillStyle = textStyle.getFill(); if (!textFillStyle) { state.fillColor = null; } else { @@ -514,7 +234,6 @@ if (ol.ENABLE_WEBGL) { state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ? textFillStyleColor : ol.render.webgl.defaultFillStyle); } - var textStrokeStyle = textStyle.getStroke(); if (!textStrokeStyle) { state.strokeColor = null; state.lineWidth = 0; @@ -531,15 +250,61 @@ if (ol.ENABLE_WEBGL) { state.lineDash = lineDash ? lineDash.slice() : ol.render.webgl.defaultLineDash; } state.font = textStyle.getFont() || ol.render.webgl.defaultFont; + state.scale = textStyle.getScale() || 1; + state.text = textStyle.getText(); + var textAlign = ol.render.webgl.TextReplay.Align_[textStyle.getTextAlign()]; + var textBaseline = ol.render.webgl.TextReplay.Align_[textStyle.getTextBaseline()]; + state.textAlign = textAlign === undefined ? + ol.render.webgl.defaultTextAlign : textAlign; + state.textBaseline = textBaseline === undefined ? + ol.render.webgl.defaultTextBaseline : textBaseline; state.offsetX = textStyle.getOffsetX() || 0; state.offsetY = textStyle.getOffsetY() || 0; - state.rotateWithView = !!textStyle.getRotateWithView(); - state.rotation = textStyle.getRotation() || 0; - state.scale = textStyle.getScale() || 1; - state.text = textStyle.getText() || ''; - state.textAlign = textStyle.getTextAlign() || ol.render.webgl.defaultTextAlign; - state.textBaseline = textStyle.getTextBaseline() || ol.render.webgl.defaultTextBaseline; + this.rotateWithView = !!textStyle.getRotateWithView(); + this.rotation = textStyle.getRotation() || 0; + + if (this.images_.length === 0) { + this.images_.push(this.createTextImage_()); + } else { + this.groupIndices.push(this.indices.length); + this.images_.push(this.createTextImage_()); + } } }; + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.getTextures = function(opt_all) { + return this.textures_; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.TextReplay.prototype.getHitDetectionTextures = function() { + return this.textures_; + }; + + + /** + * @enum {number} + * @private + */ + ol.render.webgl.TextReplay.Align_ = { + left: 0, + end: 0, + center: 0.5, + right: 1, + start: 1, + top: 0, + middle: 0.5, + hanging: 0.2, + alphabetic: 0.8, + ideographic: 0.8, + bottom: 1 + }; + } From a3a443324d129cf60405c87627084ba89e39821c Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 7 Jun 2017 16:13:11 +0200 Subject: [PATCH 07/10] Add tests for WebGL TextReplay --- test/spec/ol/render/webgl/textreplay.test.js | 208 ++++++++++++++++++ .../ol/style/expected/text-rotated-webgl.png | Bin 0 -> 3505 bytes .../spec/ol/style/expected/text-webgl.png | Bin 0 -> 2837 bytes test_rendering/spec/ol/style/text.test.js | 13 ++ 4 files changed, 221 insertions(+) create mode 100644 test/spec/ol/render/webgl/textreplay.test.js create mode 100644 test_rendering/spec/ol/style/expected/text-rotated-webgl.png create mode 100644 test_rendering/spec/ol/style/expected/text-webgl.png diff --git a/test/spec/ol/render/webgl/textreplay.test.js b/test/spec/ol/render/webgl/textreplay.test.js new file mode 100644 index 0000000000..6482820dbb --- /dev/null +++ b/test/spec/ol/render/webgl/textreplay.test.js @@ -0,0 +1,208 @@ +goog.provide('ol.test.render.webgl.TextReplay'); + +goog.require('ol.dom'); +goog.require('ol.render.webgl.TextReplay'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); +goog.require('ol.style.Text'); + +describe('ol.render.webgl.TextReplay', function() { + var replay; + + var createTextStyle = function(fillStyle, strokeStyle, text) { + var textStyle = new ol.style.Text({ + rotateWithView: true, + rotation: 1.5, + scale: 2, + textAlign: 'left', + textBaseline: 'top', + font: '12px Arial', + offsetX: 10, + offsetY: 10, + text: text, + fill: fillStyle, + stroke: strokeStyle + }); + return textStyle; + }; + + beforeEach(function() { + var tolerance = 0.1; + var maxExtent = [-10000, -20000, 10000, 20000]; + replay = new ol.render.webgl.TextReplay(tolerance, maxExtent); + }); + + describe('#setTextStyle', function() { + + var textStyle1, textStyle2, textStyle3, textStyle4; + + beforeEach(function() { + textStyle1 = createTextStyle( + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + new ol.style.Stroke({ + width: 1, + color: [0, 0, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel', + lineDash: [5, 5], + lineDashOffset: 15, + miterLimit: 2 + }), + 'someText'); + textStyle2 = createTextStyle( + new ol.style.Fill({ + color: [255, 255, 255, 1] + }), + new ol.style.Stroke({ + width: 1, + color: [255, 255, 255, 1] + }), + 'someText' + ); + textStyle3 = createTextStyle(null, null, 'someText'); + textStyle4 = createTextStyle( + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + new ol.style.Stroke({ + width: 1, + color: [0, 0, 0, 1] + }), + '' + ); + }); + + it('set expected states', function() { + var mCtx = ol.dom.createCanvasContext2D(0, 0); + mCtx.font = '12px Arial'; + var mWidth = mCtx.measureText('M').width; + var textWidth = mCtx.measureText('someText').width; + var width = Math.ceil((textWidth + 2) * 2); + var height = Math.ceil(Math.round(mWidth * 1.2 + 2) * 2); + + replay.setTextStyle(textStyle1); + expect(replay.anchorX).to.be(10); + expect(replay.anchorY).to.be(10); + expect(replay.height).to.be(height); + expect(replay.imageHeight).to.be(height); + expect(replay.width).to.be(width); + expect(replay.imageWidth).to.be(width); + expect(replay.opacity).to.be(1); + expect(replay.originX).to.be(0); + expect(replay.originY).to.be(0); + expect(replay.rotation).to.be(1.5); + expect(replay.rotateWithView).to.be(true); + expect(replay.scale).to.be(1); + expect(replay.images_).to.have.length(1); + expect(replay.groupIndices).to.have.length(0); + + expect(replay.state_.fillColor).to.be('rgba(0,0,0,1)'); + expect(replay.state_.strokeColor).to.be('rgba(0,0,0,1)'); + expect(replay.state_.scale).to.be(2); + expect(replay.state_.lineWidth).to.be(1); + expect(replay.state_.lineJoin).to.be('bevel'); + expect(replay.state_.lineCap).to.be('butt'); + expect(replay.state_.lineDash).to.eql([5, 5]); + expect(replay.state_.lineDashOffset).to.be(15); + expect(replay.state_.miterLimit).to.be(2); + expect(replay.state_.font).to.be('12px Arial'); + expect(replay.state_.text).to.be('someText'); + + replay.setTextStyle(textStyle2); + expect(replay.images_).to.have.length(2); + expect(replay.groupIndices).to.have.length(1); + }); + + it('does not create an image, if an empty text is supplied', function() { + replay.setTextStyle(textStyle4); + expect(replay.state_.text).to.be(''); + expect(replay.images_).to.have.length(0); + expect(replay.groupIndices).to.have.length(0); + }); + + it('does not create an image, if both fill and stroke styles are missing', function() { + replay.setTextStyle(textStyle3); + expect(replay.state_.text).to.be(''); + expect(replay.images_).to.have.length(0); + expect(replay.groupIndices).to.have.length(0); + }); + }); + + describe('#drawText', function() { + beforeEach(function() { + var textStyle = createTextStyle( + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); + replay.setTextStyle(textStyle); + }); + + it('sets the buffer data', function() { + var point; + + point = [1000, 2000]; + replay.drawText(point, 0, 2, 2, null, null); + expect(replay.vertices).to.have.length(32); + expect(replay.indices).to.have.length(6); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); + + point = [2000, 3000]; + replay.drawText(point, 0, 2, 2, null, null); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); + }); + + it('does not draw if text is empty', function() { + replay.state_.text = ''; + var point; + + point = [1000, 2000]; + replay.drawText(point, 0, 2, 2, null, null); + expect(replay.vertices).to.have.length(0); + expect(replay.indices).to.have.length(0); + }); + }); + + describe('#getTextures', function() { + beforeEach(function() { + replay.textures_ = [1, 2]; + }); + + it('returns the textures', function() { + var textures = replay.getTextures(); + + expect(textures).to.have.length(2); + expect(textures[0]).to.be(1); + expect(textures[1]).to.be(2); + expect(textures).to.eql(replay.getTextures(true)); + }); + }); + + describe('#getHitDetectionTextures', function() { + beforeEach(function() { + replay.textures_ = [1, 2]; + }); + + it('returns the textures', function() { + var textures = replay.getHitDetectionTextures(); + + expect(textures).to.have.length(2); + expect(textures[0]).to.be(1); + expect(textures[1]).to.be(2); + }); + }); +}); diff --git a/test_rendering/spec/ol/style/expected/text-rotated-webgl.png b/test_rendering/spec/ol/style/expected/text-rotated-webgl.png new file mode 100644 index 0000000000000000000000000000000000000000..33f5bfbc434fa6b362b7288cbb4d281a105a8f23 GIT binary patch literal 3505 zcmeHK`8V5H8~;WV(x|Fs43VH1REt)X3PDsYu~ui)60IVwl%ig-jJ8R&YK#i1Vr|f| zw(nsyx%$J{RiHk-uH*O=iK|;^SS5TXMgT<6I`5ZBoIgh z000tpwpOkn?fQGdMZvYgy^;aqQFd0i@1w4+n;?PoDTZ5+PC!d$gWvhiY%YOiKcM3bGUceePxGQZK+VV@1{~D z4yP~|p<3{pVuxShsIj3xrU0;MlnMk5sZ_xPJ188tz&$a@CN9EVj-ic;pB&3qu||H- zCquP1TgWk_l`Twx}pBTBZGG_FBk|GzS zsJLsP6LJaLIuWG99XJyr0eW+8^wXy>9$m+4RT+uIl+YVWm9B-<)3@1~4UYlXrLZI` z)tg_px6M6I$tlA^@rF73ECx2l`p>Fs$}6j@tx)FIbUaZt=flJqGc)1#5H#U;-<2H0 z+XCArLaP#!f*p7p6BM-U+|%y6tR<7Lg3nj&VltWSMXsDpM@Pq%ks#i?>FMb}PcJWL zTaC2H^xk>z2NqL&f^MX>cKVz9=Wu_YIDjV^_@a1JC&!aOFf~cX-?NN%e}X20(RuiD z-r6J@lwA**#Gu=%ui6*6vaMB9&x(nOQ4aY?QPaBpg~q0)dzC}b)b(!v4KI6pdoJnL zEli7x7Ap&1r0b$2=Vy}zDl6iB``z2h7(9h?U<3;8=j-d+x7ln;-9pYfJ8GnDyJ@Fa z8PjUVH=89(Tu&HZoP7xa(}XDFmb3OTAbg*OHmMW|C0D!mErd0Bzm4Pyf4en1Haa>3 zGR?*psh0e(tDn_(Pfbm&?fd_*{CG$L5+HW^P`-c-Q00cnV>Kf|Fbg@0h1X}|lasAf zQ|k%3SD>dQE!@zpNGbp$Q8m_%Av3@YYhtbIzedX1jP3vxfGHpbbhRYbT|GHF|57vT zT450_Xy2x@T__H6ikVYj7uu_!7DE_Z-EhYQsgg~&r#+sB$q3`5d(BD7WSX0QSBn>?`Eu&qM-MW z`shOlk)rerI{QJ7!J4m3=z?}^;hP6I1-+_oAM}__K`1{w<%SGxAw(aRL_|hTHa9kA zW@l#$7$_S@+|lv`09JN*Q1PMFv61JK&?!q`V5-hz#n_@Z1CqPS9X(hMaVd98TKs0N zZm^}z1O=JKMoC% z{Q?7*V4QG4bzEGWCU#(V_@FwYY<@T!c>%W=wB2)pzWg)M_e!2wH?g8kzIQ&XxS?U2fiIHo@z=jlxy0-1>)VS84qmDe%}1#k!Q})O{kMbq9ywYI$>QC zgkWK}5@9v1tF2dlr;xs4_cdzAW5RiYy@Pb4E&k%Im%RSC{koA{qPGGfF2cddX+o1y zS6x!VUZTy~hRo-E>gedGOH52m!(y=wbbQeWhr>~>ud8eEYkw?pB{?~n!rE+RFLwH< z;cyeed$dQE1-IiYac(77X4m=W&}ekVhyMOn6dXGGZu)haQ;BlGty_5Yv~C;Mvd@7p zI~P9Wa6!W!e4U${dkFX^tA0Dl$2(FLp!5DoNW;1$#GU>Qg`Y84@z`1Rx zjyf7|WK2tg$G0nD2V#s-M>Tf^0*1VTLQ}K*ss1e$O)b6H;KH$qCRT$8Y-A z*48E$4|4KE+^2r7VUZCV;JZdfRkF%c2aDZJmD&uX6LL+kSRG2V)T zo0cq38590xI>11%>V7Jso6qNG5Qfpq98ME4JbcS{U~?`OqPHIgGv=+7mb(N`RE3a8 zoRjJJcrX@Dj*pK|29QW2^lpEKczpXq+U%>G+}vj4yaCA^)mrOn0&e*ORO+JP);y2L zlO&VL<48C(+rH@2jBtN9#LL}1#k^T3huqp2o5?t|F&#xvmmWbO;+(YP5}O<}Hh-na z3>suE&NRh_YImVZ@2jS6UnpOI!U|N>nOHa*IQ#`3`cI$tPgnc@-ox)nT6_cNWj#;e Rf?qMf?xK@brKL~ozX4A#DMkPQ literal 0 HcmV?d00001 diff --git a/test_rendering/spec/ol/style/expected/text-webgl.png b/test_rendering/spec/ol/style/expected/text-webgl.png new file mode 100644 index 0000000000000000000000000000000000000000..7adae3ea3134e2f31228f4884a9521942f852a83 GIT binary patch literal 2837 zcmeHJ`&-iY7srhhwN%_(%j>6)6s4u@D@iFzlov2fS6WU>r8u zGqN;-Kp@))c(0SXQ}{2#^mURQ$|;9Hj7AAwxZuPHIU|yGmTb`_W(M@;_J58(9y$!o zE+Zk6f?{l6Zf8IByVsBP+*?Gf3VQ-GlIJ*Qs~t{e+<%{5tv+4DrrqnTP&aVNk4;Ai zDAVw*GHm@*L$}~1f8>Dq!4+BB%mHF&}PW#Nz2B&RrIZ!AQ|| zc)E9yk$TXPH@QJL?!J_L1bU~=&2h<6ky9+nlD)5|oE_{S!-ylx7>u=E5*1~cm->CH zY=(~b)3La__Pa7qp<$B%1_&4Kgq{cK+b9=w!N?H=&x&11<&6@Q_MHhe!HwB>vt_#f zmt#^1imj-Vq*r?9+bjc&YnB^xy?eA9FKv&5n7soBFtd1{L?l_aLMOssf%zk}w z`7XR5o3eD5?W;&YbWybrE}I7 ziR4+Eo^gAjr(am0`qYCHe13iX0uLiDMV}+o@WSM$-1;;cO&jfpVhn6RU;1>c9j5E;hQ!kXUSlF8rzw7P5tj?)4;l+Ma zY3-*W+wwF5gJO6O9Eys$gD+`49T;omGL7&nzmk4iD(gD>Y) z0$omRl(*0GrZ8e@t4zV@-~a@A8~M$%TUZX8t@Z1|UY`_79AIbiTLbl>dy&G=zzVA< zZMI4TcWp?YQ!N$;mQ?fSgbgDJV*h{u?TzT@H?_rnRiqEoqEsg*r&*iL{n%*UYx&RT zyKXZj9yydP{FWBYT2)om%y2q@KWw9jU~XIYLqB^hld3am>(_-=v(uql990j(*q#7r z`sB4dXOI{&-@Ou%L!(rNxF;@5Ea_%OD<_2uio;$-X}gHLR|$*kQiaFRaT|X`PBXR0 z*zL@*?3szp-Jk|u)%aqtFvP3eDPq&dD>&ayC3U>a&&ZXv3zUUNInFep7OY^zx(cVb z!-a#elP2+;ODhnRc5xT{#hMAD?u+CB>{w*oK@IHM&eE~Twe|arl$aw8*L^!@&*`4= zss3sH%!K#k7Wm=)pbIGVo^1E^rmwKlw-=1XFLcTie{3U|y%7C55-s`wD&67y;Tsjh zl|}9jr{^v8pnJ0#kizqw->jF>mdbS|B{WR<*>dt&>UQ>EtL`)D{zZQC*{p{F_L?%*n7>7qV$z~I_1kE@_;FrY03rdc2LO=U~kxi zU0F}Y+sbQwo2{3x9HbT Date: Fri, 16 Jun 2017 12:40:45 +0200 Subject: [PATCH 08/10] Use glyph atlases --- src/ol/render/webgl/textreplay.js | 321 ++++++++++++++----- src/ol/typedefs.js | 8 + test/spec/ol/render/webgl/textreplay.test.js | 202 +++++++++--- test_rendering/spec/ol/style/text.test.js | 4 +- 4 files changed, 400 insertions(+), 135 deletions(-) diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index 43f65080e8..48f164c36b 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -6,6 +6,7 @@ goog.require('ol.dom'); goog.require('ol.has'); goog.require('ol.render.webgl'); goog.require('ol.render.webgl.TextureReplay'); +goog.require('ol.style.AtlasManager'); goog.require('ol.webgl.Buffer'); @@ -49,12 +50,7 @@ if (ol.ENABLE_WEBGL) { * lineWidth: number, * miterLimit: (number|undefined), * fillColor: (ol.ColorLike|null), - * text: (string|undefined), * font: (string|undefined), - * textAlign: (number|undefined), - * textBaseline: (number|undefined), - * offsetX: (number|undefined), - * offsetY: (number|undefined), * scale: (number|undefined)}} */ this.state_ = { @@ -66,18 +62,51 @@ if (ol.ENABLE_WEBGL) { lineWidth: 0, miterLimit: undefined, fillColor: null, - text: '', font: undefined, - textAlign: undefined, - textBaseline: undefined, - offsetX: undefined, - offsetY: undefined, scale: undefined }; - this.originX = 0; + /** + * @private + * @type {string} + */ + this.text_ = ''; - this.originY = 0; + /** + * @private + * @type {number|undefined} + */ + this.textAlign_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.textBaseline_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.offsetX_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.offsetY_ = undefined; + + /** + * @private + * @type {Object.} + */ + this.atlases_ = {}; + + /** + * @private + * @type {ol.WebglGlyphAtlas|undefined} + */ + this.currAtlas_ = undefined; this.scale = 1; @@ -92,85 +121,152 @@ if (ol.ENABLE_WEBGL) { */ ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) { - //For now we create one texture per feature. That is, only multiparts are grouped. - //TODO: speed up rendering with SDF, or at least glyph atlases - var state = this.state_; - if (state.text) { + if (this.text_) { this.startIndices.push(this.indices.length); this.startIndicesFeature.push(feature); - this.drawCoordinates( - flatCoordinates, offset, end, stride); + var glyphAtlas = this.currAtlas_; + var lines = this.text_.split('\n'); + var textSize = this.getTextSize_(lines); + var i, ii, j, jj, currX, currY, charArr, charInfo; + var anchorX = Math.round(textSize[0] * this.textAlign_ + this.offsetX_); + var anchorY = Math.round(textSize[1] * this.textBaseline_ + this.offsetY_); + var lineWidth = (this.state_.lineWidth / 2) * this.state_.scale; + + for (i = 0, ii = lines.length; i < ii; ++i) { + currX = 0; + currY = glyphAtlas.height * i; + charArr = lines[i].split(''); + + for (j = 0, jj = charArr.length; j < jj; ++j) { + charInfo = glyphAtlas.atlas.getInfo(charArr[j]); + + if (charInfo) { + var image = charInfo.image; + + this.anchorX = anchorX - currX; + this.anchorY = anchorY - currY; + this.originX = j === 0 ? charInfo.offsetX - lineWidth : charInfo.offsetX; + this.originY = charInfo.offsetY; + this.height = glyphAtlas.height; + this.width = j === 0 || j === charArr.length - 1 ? + glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]]; + this.imageHeight = image.height; + this.imageWidth = image.width; + + var currentImage; + if (this.images_.length === 0) { + this.images_.push(image); + } else { + currentImage = this.images_[this.images_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(image)) { + this.groupIndices.push(this.indices.length); + this.images_.push(image); + } + } + + this.drawText_(flatCoordinates, offset, end, stride); + } + currX += this.width; + } + } } }; /** * @private - * @return {HTMLCanvasElement} Text image. + * @param {Array.} lines Label to draw split to lines. + * @return {Array.} Size of the label in pixels. */ - ol.render.webgl.TextReplay.prototype.createTextImage_ = function() { - var state = this.state_; - - //Measure text dimensions - var mCtx = this.measureCanvas_.getContext('2d'); - mCtx.font = state.font; - var lineHeight = Math.round(mCtx.measureText('M').width * 1.2 + state.lineWidth * 2); - var lines = state.text.split('\n'); - //FIXME: use pixelRatio - var textHeight = Math.ceil(lineHeight * lines.length * state.scale); - this.height = textHeight; - this.imageHeight = textHeight; - this.anchorY = Math.round(textHeight * state.textBaseline + state.offsetY); - var longestLine = lines.map(function(str) { - return mCtx.measureText(str).width; + ol.render.webgl.TextReplay.prototype.getTextSize_ = function(lines) { + var self = this; + var glyphAtlas = this.currAtlas_; + var textHeight = lines.length * glyphAtlas.height; + //Split every line to an array of chars, sum up their width, and select the longest. + var textWidth = lines.map(function(str) { + var sum = 0; + var i, ii; + for (i = 0, ii = str.length; i < ii; ++i) { + var curr = str[i]; + if (!glyphAtlas.width[curr]) { + self.addCharToAtlas_(curr); + } + sum += glyphAtlas.width[curr] ? glyphAtlas.width[curr] : 0; + } + return sum; }).reduce(function(max, curr) { return Math.max(max, curr); }); - //FIXME: use pixelRatio - var textWidth = Math.ceil((longestLine + state.lineWidth * 2) * state.scale); - this.width = textWidth; - this.imageWidth = textWidth; - this.anchorX = Math.round(textWidth * state.textAlign + state.offsetX); - //Create a canvas - var ctx = ol.dom.createCanvasContext2D(textWidth, textHeight); - var canvas = ctx.canvas; + return [textWidth, textHeight]; + }; - //Parameterize the canvas - ctx.font = /** @type {string} */ (state.font); - ctx.fillStyle = state.fillColor; - ctx.strokeStyle = state.strokeColor; - ctx.lineWidth = state.lineWidth; - ctx.lineCap = /*** @type {string} */ (state.lineCap); - ctx.lineJoin = /** @type {string} */ (state.lineJoin); - ctx.miterLimit = /** @type {number} */ (state.miterLimit); - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - if (ol.has.CANVAS_LINE_DASH && state.lineDash) { - //FIXME: use pixelRatio - ctx.setLineDash(state.lineDash); - ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset); - } - if (state.scale !== 1) { - //FIXME: use pixelRatio - ctx.setTransform(/** @type {number} */ (state.scale), 0, 0, - /** @type {number} */ (state.scale), 0, 0); + + /** + * @private + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ + ol.render.webgl.TextReplay.prototype.drawText_ = function(flatCoordinates, offset, + end, stride) { + var i, ii; + for (i = offset, ii = end; i < ii; i += stride) { + this.drawCoordinates(flatCoordinates, offset, end, stride); } + }; - //Draw the text on the canvas - var lineY = 0; - for (var i = 0, ii = lines.length; i < ii; ++i) { - if (state.strokeColor) { - ctx.strokeText(lines[i], 0, lineY); + + /** + * @private + * @param {string} char Character. + */ + ol.render.webgl.TextReplay.prototype.addCharToAtlas_ = function(char) { + if (char.length === 1) { + var glyphAtlas = this.currAtlas_; + var state = this.state_; + var mCtx = this.measureCanvas_.getContext('2d'); + mCtx.font = state.font; + var width = Math.ceil(mCtx.measureText(char).width * state.scale); + + var info = glyphAtlas.atlas.add(char, width, glyphAtlas.height, + function(ctx, x, y) { + //Parameterize the canvas + ctx.font = /** @type {string} */ (state.font); + ctx.fillStyle = state.fillColor; + ctx.strokeStyle = state.strokeColor; + ctx.lineWidth = state.lineWidth; + ctx.lineCap = /*** @type {string} */ (state.lineCap); + ctx.lineJoin = /** @type {string} */ (state.lineJoin); + ctx.miterLimit = /** @type {number} */ (state.miterLimit); + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + if (ol.has.CANVAS_LINE_DASH && state.lineDash) { + //FIXME: use pixelRatio + ctx.setLineDash(state.lineDash); + ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset); + } + if (state.scale !== 1) { + //FIXME: use pixelRatio + ctx.setTransform(/** @type {number} */ (state.scale), 0, 0, + /** @type {number} */ (state.scale), 0, 0); + } + + //Draw the character on the canvas + if (state.strokeColor) { + ctx.strokeText(char, x, y); + } + if (state.fillColor) { + ctx.fillText(char, x, y); + } + }); + + if (info) { + glyphAtlas.width[char] = width; } - if (state.fillColor) { - ctx.fillText(lines[i], 0, lineY); - } - lineY += lineHeight; } - - return /** @type {HTMLCanvasElement} */ (canvas); }; @@ -204,15 +300,17 @@ if (ol.ENABLE_WEBGL) { lineWidth: 0, miterLimit: undefined, fillColor: null, - text: '', font: undefined, - textAlign: undefined, - textBaseline: undefined, - offsetX: undefined, - offsetY: undefined, scale: undefined }; + this.text_ = ''; + this.textAlign_ = undefined; + this.textBaseline_ = undefined; + this.offsetX_ = undefined; + this.offsetY_ = undefined; this.images_ = null; + this.atlases_ = {}; + this.currAtlas_ = undefined; ol.render.webgl.TextureReplay.prototype.finish.call(this, context); }; @@ -225,7 +323,7 @@ if (ol.ENABLE_WEBGL) { var textFillStyle = textStyle.getFill(); var textStrokeStyle = textStyle.getStroke(); if (!textStyle || !textStyle.getText() || (!textFillStyle && !textStrokeStyle)) { - state.text = ''; + this.text_ = ''; } else { if (!textFillStyle) { state.fillColor = null; @@ -251,25 +349,72 @@ if (ol.ENABLE_WEBGL) { } state.font = textStyle.getFont() || ol.render.webgl.defaultFont; state.scale = textStyle.getScale() || 1; - state.text = textStyle.getText(); + this.text_ = /** @type {string} */ (textStyle.getText()); var textAlign = ol.render.webgl.TextReplay.Align_[textStyle.getTextAlign()]; var textBaseline = ol.render.webgl.TextReplay.Align_[textStyle.getTextBaseline()]; - state.textAlign = textAlign === undefined ? + this.textAlign_ = textAlign === undefined ? ol.render.webgl.defaultTextAlign : textAlign; - state.textBaseline = textBaseline === undefined ? + this.textBaseline_ = textBaseline === undefined ? ol.render.webgl.defaultTextBaseline : textBaseline; - state.offsetX = textStyle.getOffsetX() || 0; - state.offsetY = textStyle.getOffsetY() || 0; + this.offsetX_ = textStyle.getOffsetX() || 0; + this.offsetY_ = textStyle.getOffsetY() || 0; this.rotateWithView = !!textStyle.getRotateWithView(); this.rotation = textStyle.getRotation() || 0; - if (this.images_.length === 0) { - this.images_.push(this.createTextImage_()); - } else { - this.groupIndices.push(this.indices.length); - this.images_.push(this.createTextImage_()); + this.currAtlas_ = this.getAtlas_(state); + } + }; + + + /** + * @private + * @param {Object} state Font attributes. + * @return {ol.WebglGlyphAtlas} Glyph atlas. + */ + ol.render.webgl.TextReplay.prototype.getAtlas_ = function(state) { + var params = []; + var i; + for (i in state) { + if (state[i] || state[i] === 0) { + if (Array.isArray(state[i])) { + params = params.concat(state[i]); + } else { + params.push(state[i]); + } } } + var hash = this.calculateHash_(params); + if (!this.atlases_[hash]) { + var mCtx = this.measureCanvas_.getContext('2d'); + mCtx.font = state.font; + var height = Math.ceil((mCtx.measureText('M').width * 1.5 + + state.lineWidth / 2) * state.scale); + + this.atlases_[hash] = { + atlas: new ol.style.AtlasManager({ + space: state.lineWidth + 1 + }), + width: {}, + height: height + }; + } + return this.atlases_[hash]; + }; + + + /** + * @private + * @param {Array.} params Array of parameters. + * @return {string} Hash string. + */ + ol.render.webgl.TextReplay.prototype.calculateHash_ = function(params) { + //TODO: Create a more performant, reliable, general hash function. + var i, ii; + var hash = ''; + for (i = 0, ii = params.length; i < ii; ++i) { + hash += params[i]; + } + return hash; }; diff --git a/src/ol/typedefs.js b/src/ol/typedefs.js index 65de15bce1..8581394c33 100644 --- a/src/ol/typedefs.js +++ b/src/ol/typedefs.js @@ -723,6 +723,14 @@ ol.ViewAnimation; ol.WebglBufferCacheEntry; +/** + * @typedef {{atlas: ol.style.AtlasManager, + * width: Object., + * height: number}} + */ +ol.WebglGlyphAtlas; + + /** * @typedef {{p0: ol.WebglPolygonVertex, * p1: ol.WebglPolygonVertex}} diff --git a/test/spec/ol/render/webgl/textreplay.test.js b/test/spec/ol/render/webgl/textreplay.test.js index 6482820dbb..610e49a02b 100644 --- a/test/spec/ol/render/webgl/textreplay.test.js +++ b/test/spec/ol/render/webgl/textreplay.test.js @@ -75,28 +75,15 @@ describe('ol.render.webgl.TextReplay', function() { }); it('set expected states', function() { - var mCtx = ol.dom.createCanvasContext2D(0, 0); - mCtx.font = '12px Arial'; - var mWidth = mCtx.measureText('M').width; - var textWidth = mCtx.measureText('someText').width; - var width = Math.ceil((textWidth + 2) * 2); - var height = Math.ceil(Math.round(mWidth * 1.2 + 2) * 2); - replay.setTextStyle(textStyle1); - expect(replay.anchorX).to.be(10); - expect(replay.anchorY).to.be(10); - expect(replay.height).to.be(height); - expect(replay.imageHeight).to.be(height); - expect(replay.width).to.be(width); - expect(replay.imageWidth).to.be(width); expect(replay.opacity).to.be(1); - expect(replay.originX).to.be(0); - expect(replay.originY).to.be(0); expect(replay.rotation).to.be(1.5); expect(replay.rotateWithView).to.be(true); expect(replay.scale).to.be(1); - expect(replay.images_).to.have.length(1); - expect(replay.groupIndices).to.have.length(0); + expect(replay.offsetX_).to.be(10); + expect(replay.offsetY_).to.be(10); + expect(replay.text_).to.be('someText'); + expect(Object.keys(replay.atlases_)).to.have.length(1); expect(replay.state_.fillColor).to.be('rgba(0,0,0,1)'); expect(replay.state_.strokeColor).to.be('rgba(0,0,0,1)'); @@ -108,25 +95,21 @@ describe('ol.render.webgl.TextReplay', function() { expect(replay.state_.lineDashOffset).to.be(15); expect(replay.state_.miterLimit).to.be(2); expect(replay.state_.font).to.be('12px Arial'); - expect(replay.state_.text).to.be('someText'); replay.setTextStyle(textStyle2); - expect(replay.images_).to.have.length(2); - expect(replay.groupIndices).to.have.length(1); + expect(Object.keys(replay.atlases_)).to.have.length(2); }); - it('does not create an image, if an empty text is supplied', function() { + it('does not create an atlas, if an empty text is supplied', function() { replay.setTextStyle(textStyle4); - expect(replay.state_.text).to.be(''); - expect(replay.images_).to.have.length(0); - expect(replay.groupIndices).to.have.length(0); + expect(replay.text_).to.be(''); + expect(Object.keys(replay.atlases_)).to.have.length(0); }); - it('does not create an image, if both fill and stroke styles are missing', function() { + it('does not create an atlas, if both fill and stroke styles are missing', function() { replay.setTextStyle(textStyle3); - expect(replay.state_.text).to.be(''); - expect(replay.images_).to.have.length(0); - expect(replay.groupIndices).to.have.length(0); + expect(replay.text_).to.be(''); + expect(Object.keys(replay.atlases_)).to.have.length(0); }); }); @@ -145,29 +128,38 @@ describe('ol.render.webgl.TextReplay', function() { point = [1000, 2000]; replay.drawText(point, 0, 2, 2, null, null); - expect(replay.vertices).to.have.length(32); - expect(replay.indices).to.have.length(6); - expect(replay.indices[0]).to.be(0); - expect(replay.indices[1]).to.be(1); - expect(replay.indices[2]).to.be(2); - expect(replay.indices[3]).to.be(0); - expect(replay.indices[4]).to.be(2); - expect(replay.indices[5]).to.be(3); + expect(replay.vertices).to.have.length(256); + expect(replay.indices).to.have.length(48); point = [2000, 3000]; replay.drawText(point, 0, 2, 2, null, null); - expect(replay.vertices).to.have.length(64); - expect(replay.indices).to.have.length(12); - expect(replay.indices[6]).to.be(4); - expect(replay.indices[7]).to.be(5); - expect(replay.indices[8]).to.be(6); - expect(replay.indices[9]).to.be(4); - expect(replay.indices[10]).to.be(6); - expect(replay.indices[11]).to.be(7); + expect(replay.vertices).to.have.length(512); + expect(replay.indices).to.have.length(96); + }); + + it('sets part of its state during drawing', function() { + var point = [1000, 2000]; + replay.drawText(point, 0, 2, 2, null, null); + + var height = replay.currAtlas_.height; + var widths = replay.currAtlas_.width; + var width = widths.t; + var widthX = widths.s + widths.o + widths.m + widths.e + widths.T + + widths.e + widths.x; + var charInfo = replay.currAtlas_.atlas.getInfo('t'); + + expect(replay.height).to.be(height); + expect(replay.width).to.be(width); + expect(replay.originX).to.be(charInfo.offsetX); + expect(replay.originY).to.be(charInfo.offsetY); + expect(replay.imageHeight).to.be(charInfo.image.height); + expect(replay.imageWidth).to.be(charInfo.image.width); + expect(replay.anchorX).to.be(-widthX + 10); + expect(replay.anchorY).to.be(10); }); it('does not draw if text is empty', function() { - replay.state_.text = ''; + replay.text_ = ''; var point; point = [1000, 2000]; @@ -177,6 +169,126 @@ describe('ol.render.webgl.TextReplay', function() { }); }); + describe('#addCharToAtlas_', function() { + beforeEach(function() { + var textStyle = createTextStyle( + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); + replay.setTextStyle(textStyle); + }); + + it('adds a single character to the current atlas', function() { + var glyphAtlas = replay.currAtlas_.atlas; + var info; + + replay.addCharToAtlas_('someText'); + info = glyphAtlas.getInfo('someText'); + expect(info).to.be(null); + + replay.addCharToAtlas_('e'); + replay.addCharToAtlas_('x'); + info = glyphAtlas.getInfo('e'); + expect(info).not.to.be(null); + info = glyphAtlas.getInfo('x'); + expect(info).not.to.be(null); + }); + + it('keeps the atlas and the width dictionary synced', function() { + var glyphAtlas = replay.currAtlas_; + + replay.addCharToAtlas_('e'); + replay.addCharToAtlas_('x'); + expect(Object.keys(glyphAtlas.width)).to.have.length(2); + + replay.addCharToAtlas_('someText'); + expect(Object.keys(glyphAtlas.width)).to.have.length(2); + }); + }); + + describe('#getTextSize_', function() { + beforeEach(function() { + var textStyle = createTextStyle( + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); + textStyle.setScale(1); + replay.setTextStyle(textStyle); + }); + + it('adds missing characters to the current atlas', function() { + var glyphAtlas = replay.currAtlas_; + var info; + + expect(Object.keys(glyphAtlas.width)).to.have.length(0); + replay.getTextSize_(['someText']); + expect(Object.keys(glyphAtlas.width)).to.have.length(7); + info = glyphAtlas.atlas.getInfo('s'); + expect(info).not.to.be(null); + info = glyphAtlas.atlas.getInfo('o'); + expect(info).not.to.be(null); + info = glyphAtlas.atlas.getInfo('m'); + expect(info).not.to.be(null); + info = glyphAtlas.atlas.getInfo('e'); + expect(info).not.to.be(null); + info = glyphAtlas.atlas.getInfo('T'); + expect(info).not.to.be(null); + info = glyphAtlas.atlas.getInfo('x'); + expect(info).not.to.be(null); + info = glyphAtlas.atlas.getInfo('t'); + expect(info).not.to.be(null); + }); + + it('returns the size of the label\'s bounding box in pixels', function() { + var size; + var mCtx = ol.dom.createCanvasContext2D(0, 0); + mCtx.font = '12px Arial'; + var width = mCtx.measureText('someText').width; + var width2 = mCtx.measureText('anEvenLongerLine').width; + var height = Math.ceil(mCtx.measureText('M').width * 1.5); + + size = replay.getTextSize_(['someText']); + expect(size[0]).to.be.within(width, width + 8); + expect(size[1]).to.be(height); + + size = replay.getTextSize_(['someText', 'anEvenLongerLine']); + expect(size[0]).to.be.within(width2, width2 + 16); + expect(size[1]).to.be(height * 2); + }); + }); + + describe('#getAtlas_', function() { + beforeEach(function() { + var textStyle = createTextStyle( + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); + replay.setTextStyle(textStyle); + }); + + it('returns the appropriate atlas for the current state', function() { + var atlas = replay.currAtlas_; + var state = replay.state_; + + expect(Object.keys(replay.atlases_)).to.have.length(1); + expect(replay.getAtlas_(state)).to.be(atlas); + expect(Object.keys(replay.atlases_)).to.have.length(1); + }); + + it('creates a new atlas if it cannot find the one for the current state', function() { + var atlas = replay.currAtlas_; + var state = replay.state_; + state.lineWidth = 50; + + expect(Object.keys(replay.atlases_)).to.have.length(1); + expect(replay.getAtlas_(state)).not.to.be(atlas); + expect(Object.keys(replay.atlases_)).to.have.length(2); + }); + }); + describe('#getTextures', function() { beforeEach(function() { replay.textures_ = [1, 2]; diff --git a/test_rendering/spec/ol/style/text.test.js b/test_rendering/spec/ol/style/text.test.js index d32cb4c380..0dbf7dbf5e 100644 --- a/test_rendering/spec/ol/style/text.test.js +++ b/test_rendering/spec/ol/style/text.test.js @@ -105,14 +105,14 @@ describe('ol.rendering.style.Text', function() { it('tests the webgl renderer without rotation', function(done) { map = createMap('webgl'); createFeatures(); - expectResemble(map, 'spec/ol/style/expected/text-webgl.png', IMAGE_TOLERANCE, done); + expectResemble(map, 'spec/ol/style/expected/text-webgl.png', 1.8, done); }); it('tests the webgl renderer with rotation', function(done) { map = createMap('webgl'); createFeatures(); map.getView().setRotation(Math.PI / 7); - expectResemble(map, 'spec/ol/style/expected/text-rotated-webgl.png', 1.6, done); + expectResemble(map, 'spec/ol/style/expected/text-rotated-webgl.png', 1.8, done); }); }); From b3407b055459171412dfee06b7a32727f30772ba Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 23 Jun 2017 20:51:46 +0200 Subject: [PATCH 09/10] Add reindented texture shader --- src/ol/render/webgl/texturereplay/defaultshader.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ol/render/webgl/texturereplay/defaultshader.js b/src/ol/render/webgl/texturereplay/defaultshader.js index 016a8c2979..a2459f0339 100644 --- a/src/ol/render/webgl/texturereplay/defaultshader.js +++ b/src/ol/render/webgl/texturereplay/defaultshader.js @@ -38,8 +38,8 @@ if (ol.ENABLE_WEBGL) { * @type {string} */ ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; ol.render.webgl.texturereplay.defaultshader.fragment = new ol.render.webgl.texturereplay.defaultshader.Fragment(); @@ -75,8 +75,8 @@ if (ol.ENABLE_WEBGL) { * @type {string} */ ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; ol.render.webgl.texturereplay.defaultshader.vertex = new ol.render.webgl.texturereplay.defaultshader.Vertex(); From 619e85e7371d9c30e185b64f1ddd13b08b703432 Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Fri, 23 Jun 2017 21:12:27 +0200 Subject: [PATCH 10/10] Fix linting issues --- src/ol/render/webgl/textreplay.js | 10 +-- src/ol/render/webgl/texturereplay.js | 4 +- test/spec/ol/render/webgl/textreplay.test.js | 90 ++++++++++---------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index 48f164c36b..56f02502db 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -150,7 +150,7 @@ if (ol.ENABLE_WEBGL) { this.originY = charInfo.offsetY; this.height = glyphAtlas.height; this.width = j === 0 || j === charArr.length - 1 ? - glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]]; + glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]]; this.imageHeight = image.height; this.imageWidth = image.width; @@ -330,7 +330,7 @@ if (ol.ENABLE_WEBGL) { } else { var textFillStyleColor = textFillStyle.getColor(); state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ? - textFillStyleColor : ol.render.webgl.defaultFillStyle); + textFillStyleColor : ol.render.webgl.defaultFillStyle); } if (!textStrokeStyle) { state.strokeColor = null; @@ -338,7 +338,7 @@ if (ol.ENABLE_WEBGL) { } else { var textStrokeStyleColor = textStrokeStyle.getColor(); state.strokeColor = ol.colorlike.asColorLike(textStrokeStyleColor ? - textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle); + textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle); state.lineWidth = textStrokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; state.lineCap = textStrokeStyle.getLineCap() || ol.render.webgl.defaultLineCap; state.lineDashOffset = textStrokeStyle.getLineDashOffset() || ol.render.webgl.defaultLineDashOffset; @@ -353,9 +353,9 @@ if (ol.ENABLE_WEBGL) { var textAlign = ol.render.webgl.TextReplay.Align_[textStyle.getTextAlign()]; var textBaseline = ol.render.webgl.TextReplay.Align_[textStyle.getTextBaseline()]; this.textAlign_ = textAlign === undefined ? - ol.render.webgl.defaultTextAlign : textAlign; + ol.render.webgl.defaultTextAlign : textAlign; this.textBaseline_ = textBaseline === undefined ? - ol.render.webgl.defaultTextBaseline : textBaseline; + ol.render.webgl.defaultTextBaseline : textBaseline; this.offsetX_ = textStyle.getOffsetX() || 0; this.offsetY_ = textStyle.getOffsetY() || 0; this.rotateWithView = !!textStyle.getRotateWithView(); diff --git a/src/ol/render/webgl/texturereplay.js b/src/ol/render/webgl/texturereplay.js index 4c97e8b5c2..3b2fb7b13b 100644 --- a/src/ol/render/webgl/texturereplay.js +++ b/src/ol/render/webgl/texturereplay.js @@ -397,12 +397,12 @@ if (ol.ENABLE_WEBGL) { } // continue with the next feature start = (featureIndex === this.startIndices.length - 1) ? - groupEnd : this.startIndices[featureIndex + 1]; + groupEnd : this.startIndices[featureIndex + 1]; end = start; } else { // the feature is not skipped, augment the end index end = (featureIndex === this.startIndices.length - 1) ? - groupEnd : this.startIndices[featureIndex + 1]; + groupEnd : this.startIndices[featureIndex + 1]; } featureIndex++; } diff --git a/test/spec/ol/render/webgl/textreplay.test.js b/test/spec/ol/render/webgl/textreplay.test.js index 610e49a02b..69dcaffbf9 100644 --- a/test/spec/ol/render/webgl/textreplay.test.js +++ b/test/spec/ol/render/webgl/textreplay.test.js @@ -38,39 +38,39 @@ describe('ol.render.webgl.TextReplay', function() { beforeEach(function() { textStyle1 = createTextStyle( - new ol.style.Fill({ - color: [0, 0, 0, 1] - }), - new ol.style.Stroke({ - width: 1, - color: [0, 0, 0, 1], - lineCap: 'butt', - lineJoin: 'bevel', - lineDash: [5, 5], - lineDashOffset: 15, - miterLimit: 2 - }), - 'someText'); + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + new ol.style.Stroke({ + width: 1, + color: [0, 0, 0, 1], + lineCap: 'butt', + lineJoin: 'bevel', + lineDash: [5, 5], + lineDashOffset: 15, + miterLimit: 2 + }), + 'someText'); textStyle2 = createTextStyle( - new ol.style.Fill({ - color: [255, 255, 255, 1] - }), - new ol.style.Stroke({ - width: 1, - color: [255, 255, 255, 1] - }), - 'someText' + new ol.style.Fill({ + color: [255, 255, 255, 1] + }), + new ol.style.Stroke({ + width: 1, + color: [255, 255, 255, 1] + }), + 'someText' ); textStyle3 = createTextStyle(null, null, 'someText'); textStyle4 = createTextStyle( - new ol.style.Fill({ - color: [0, 0, 0, 1] - }), - new ol.style.Stroke({ - width: 1, - color: [0, 0, 0, 1] - }), - '' + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + new ol.style.Stroke({ + width: 1, + color: [0, 0, 0, 1] + }), + '' ); }); @@ -116,10 +116,10 @@ describe('ol.render.webgl.TextReplay', function() { describe('#drawText', function() { beforeEach(function() { var textStyle = createTextStyle( - new ol.style.Fill({ - color: [0, 0, 0, 1] - }), - null, 'someText'); + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); replay.setTextStyle(textStyle); }); @@ -172,10 +172,10 @@ describe('ol.render.webgl.TextReplay', function() { describe('#addCharToAtlas_', function() { beforeEach(function() { var textStyle = createTextStyle( - new ol.style.Fill({ - color: [0, 0, 0, 1] - }), - null, 'someText'); + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); replay.setTextStyle(textStyle); }); @@ -210,10 +210,10 @@ describe('ol.render.webgl.TextReplay', function() { describe('#getTextSize_', function() { beforeEach(function() { var textStyle = createTextStyle( - new ol.style.Fill({ - color: [0, 0, 0, 1] - }), - null, 'someText'); + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); textStyle.setScale(1); replay.setTextStyle(textStyle); }); @@ -262,10 +262,10 @@ describe('ol.render.webgl.TextReplay', function() { describe('#getAtlas_', function() { beforeEach(function() { var textStyle = createTextStyle( - new ol.style.Fill({ - color: [0, 0, 0, 1] - }), - null, 'someText'); + new ol.style.Fill({ + color: [0, 0, 0, 1] + }), + null, 'someText'); replay.setTextStyle(textStyle); });