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'); + }; + +}