From 3d6472fd4176d867c2236ea14b6f8a0f45b421ba Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 4 Sep 2019 08:55:15 -0400 Subject: [PATCH] Speed up webgl point updates. --- CHANGELOG.md | 1 + src/webgl/pointFeature.js | 94 +++++++++++++++++++------------ src/webgl/pointFeaturePoly.frag | 8 +-- src/webgl/pointFeaturePoly.vert | 18 ++---- src/webgl/pointFeatureSprite.frag | 8 +-- src/webgl/pointFeatureSprite.vert | 16 ++---- tests/cases/pointFeature.js | 8 +++ 7 files changed, 83 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a128adffae..d2f0c1bb9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Improvements - Points with small radii or thin strokes are rendered better (#1021) +- When only updating point styles, don't recompute geometry transforms (#1022) ### Changes - Switched the default tile server to Stamen Design's toner-lite. (#1020) diff --git a/src/webgl/pointFeature.js b/src/webgl/pointFeature.js index 9b751ae316..972802b4ab 100644 --- a/src/webgl/pointFeature.js +++ b/src/webgl/pointFeature.js @@ -128,8 +128,11 @@ var webgl_pointFeature = function (arg) { /** * Create and style the data needed to render the points. + * + * @param {boolean} onlyStyle if true, use the existing geoemtry and just + * recalculate the style. */ - function createGLPoints() { + function createGLPoints(onlyStyle) { // unit and associated data is not used when drawing sprite var i, j, numPts = m_this.data().length, unit = pointPolygon(0, 0, 1, 1), @@ -158,31 +161,38 @@ var webgl_pointFeature = function (arg) { fillOpacityFunc = m_this.style.get('fillOpacity'); fillColorFunc = m_this.style.get('fillColor'); - /* It is more efficient to do a transform on a single array rather than on - * an array of arrays or an array of objects. */ - for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { - posVal = posFunc(data[i], i); - position[i3] = posVal.x; - position[i3 + 1] = posVal.y; - // ignore the z values until we support them - position[i3 + 2] = 0; // posVal.z || 0; - } - position = transform.transformCoordinates( - m_this.gcs(), m_this.layer().map().gcs(), position, 3); - m_origin = new Float32Array(m_this.style.get('origin')(position)); - if (m_origin[0] || m_origin[1] || m_origin[2]) { + if (!onlyStyle) { + /* It is more efficient to do a transform on a single array rather than on + * an array of arrays or an array of objects. */ for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { - position[i3] -= m_origin[0]; - position[i3 + 1] -= m_origin[1]; - position[i3 + 2] -= m_origin[2]; + posVal = posFunc(data[i], i); + position[i3] = posVal.x; + position[i3 + 1] = posVal.y; + // ignore the z values until we support them + position[i3 + 2] = 0; // posVal.z || 0; } - } - m_modelViewUniform.setOrigin(m_origin); + position = transform.transformCoordinates( + m_this.gcs(), m_this.layer().map().gcs(), position, 3); + m_origin = new Float32Array(m_this.style.get('origin')(position)); + if (m_origin[0] || m_origin[1] || m_origin[2]) { + for (i = i3 = 0; i < numPts; i += 1, i3 += 3) { + position[i3] -= m_origin[0]; + position[i3 + 1] -= m_origin[1]; + position[i3 + 2] -= m_origin[2]; + } + } + m_modelViewUniform.setOrigin(m_origin); - posBuf = util.getGeomBuffer(geom, 'pos', vpf * numPts * 3); + posBuf = util.getGeomBuffer(geom, 'pos', vpf * numPts * 3); - if (m_primitiveShape !== 'sprite') { - unitBuf = util.getGeomBuffer(geom, 'unit', vpf * numPts * 2); + if (m_primitiveShape !== 'sprite') { + unitBuf = util.getGeomBuffer(geom, 'unit', vpf * numPts * 2); + } + indices = geom.primitive(0).indices(); + if (!(indices instanceof Uint16Array) || indices.length !== vpf * numPts) { + indices = new Uint16Array(vpf * numPts); + geom.primitive(0).setIndices(indices); + } } radius = util.getGeomBuffer(geom, 'radius', vpf * numPts); @@ -193,17 +203,14 @@ var webgl_pointFeature = function (arg) { fill = util.getGeomBuffer(geom, 'fill', vpf * numPts); fillOpacity = util.getGeomBuffer(geom, 'fillOpacity', vpf * numPts); fillColor = util.getGeomBuffer(geom, 'fillColor', vpf * numPts * 3); - indices = geom.primitive(0).indices(); - if (!(indices instanceof Uint16Array) || indices.length !== vpf * numPts) { - indices = new Uint16Array(vpf * numPts); - geom.primitive(0).setIndices(indices); - } for (i = ivpf = ivpf3 = iunit = i3 = 0; i < numPts; i += 1, i3 += 3) { item = data[i]; - if (m_primitiveShape !== 'sprite') { - for (j = 0; j < unit.length; j += 1, iunit += 1) { - unitBuf[iunit] = unit[j]; + if (!onlyStyle) { + if (m_primitiveShape !== 'sprite') { + for (j = 0; j < unit.length; j += 1, iunit += 1) { + unitBuf[iunit] = unit[j]; + } } } /* We can ignore the indicies (they will all be zero) */ @@ -216,9 +223,11 @@ var webgl_pointFeature = function (arg) { fillOpacityVal = fillOpacityFunc(item, i); fillColorVal = fillColorFunc(item, i); for (j = 0; j < vpf; j += 1, ivpf += 1, ivpf3 += 3) { - posBuf[ivpf3] = position[i3]; - posBuf[ivpf3 + 1] = position[i3 + 1]; - posBuf[ivpf3 + 2] = position[i3 + 2]; + if (!onlyStyle) { + posBuf[ivpf3] = position[i3]; + posBuf[ivpf3 + 1] = position[i3 + 1]; + posBuf[ivpf3 + 2] = position[i3 + 2]; + } radius[ivpf] = radiusVal; stroke[ivpf] = strokeVal; strokeWidth[ivpf] = strokeWidthVal; @@ -234,9 +243,20 @@ var webgl_pointFeature = function (arg) { } } - geom.boundsDirty(true); - m_mapper.modified(); - m_mapper.boundsDirtyTimestamp().modified(); + if (!onlyStyle) { + geom.boundsDirty(true); + m_mapper.modified(); + m_mapper.boundsDirtyTimestamp().modified(); + } else { + m_mapper.updateSourceBuffer('radius'); + m_mapper.updateSourceBuffer('stroke'); + m_mapper.updateSourceBuffer('strokeWidth'); + m_mapper.updateSourceBuffer('strokeColor'); + m_mapper.updateSourceBuffer('strokeOpacity'); + m_mapper.updateSourceBuffer('fill'); + m_mapper.updateSourceBuffer('fillColor'); + m_mapper.updateSourceBuffer('fillOpacity'); + } } /** @@ -468,7 +488,7 @@ var webgl_pointFeature = function (arg) { * @returns {this} */ this._build = function () { - createGLPoints(); + createGLPoints(m_this.dataTime().timestamp() < m_this.buildTime().timestamp()); if (!m_this.renderer().contextRenderer().hasActor(m_actor)) { m_this.renderer().contextRenderer().addActor(m_actor); } diff --git a/src/webgl/pointFeaturePoly.frag b/src/webgl/pointFeaturePoly.frag index 35c06e7043..284e20445c 100644 --- a/src/webgl/pointFeaturePoly.frag +++ b/src/webgl/pointFeaturePoly.frag @@ -8,21 +8,19 @@ varying vec4 fillColorVar; varying vec4 strokeColorVar; varying float radiusVar; varying float strokeWidthVar; -varying float fillVar; -varying float strokeVar; varying vec3 unitVar; // distinct for square/triangle void main () { vec4 strokeColor, fillColor; float endStep; // No stroke or fill implies nothing to draw - if (fillVar == 0.0 && strokeVar == 0.0) + if (fillColorVar.a == 0.0 && strokeColorVar.a == 0.0) discard; float rad = length(unitVar.xy); // distinct for square/triangle if (rad > 1.0) discard; // If there is no stroke, the fill region should transition to nothing - if (strokeVar == 0.0) { + if (strokeColorVar.a == 0.0) { strokeColor = vec4(fillColorVar.rgb, 0.0); endStep = 1.0; } else { @@ -30,7 +28,7 @@ void main () { endStep = radiusVar / (radiusVar + strokeWidthVar); } // Likewise, if there is no fill, the stroke should transition to nothing - if (fillVar == 0.0) + if (fillColorVar.a == 0.0) fillColor = vec4(strokeColor.rgb, 0.0); else fillColor = fillColorVar; diff --git a/src/webgl/pointFeaturePoly.vert b/src/webgl/pointFeaturePoly.vert index 07e5a41b35..a145f59fbd 100644 --- a/src/webgl/pointFeaturePoly.vert +++ b/src/webgl/pointFeaturePoly.vert @@ -20,35 +20,29 @@ varying vec4 fillColorVar; varying vec4 strokeColorVar; varying float radiusVar; varying float strokeWidthVar; -varying float fillVar; -varying float strokeVar; attribute vec2 unit; // for non-sprite varying vec3 unitVar; // for non-sprite void main(void) { strokeWidthVar = strokeWidth; + fillColorVar = vec4(fillColor, fillOpacity); + strokeColorVar = vec4(strokeColor, strokeOpacity); // No stroke or fill implies nothing to draw if (stroke < 1.0 || strokeWidth <= 0.0 || strokeOpacity <= 0.0) { - strokeVar = 0.0; + strokeColorVar.a = 0.0; strokeWidthVar = 0.0; } - else - strokeVar = 1.0; if (fill < 1.0 || radius <= 0.0 || fillOpacity <= 0.0) - fillVar = 0.0; - else - fillVar = 1.0; + fillColorVar.a = 0.0; /* If the point has no visible pixels, skip doing computations on it. */ - if (fillVar == 0.0 && strokeVar == 0.0) { + if (fillColorVar.a == 0.0 && strokeColorVar.a == 0.0) { gl_Position = vec4(2, 2, 0, 1); return; } - fillColorVar = vec4 (fillColor, fillOpacity); - strokeColorVar = vec4 (strokeColor, strokeOpacity); radiusVar = radius; // for non-sprite - unitVar = vec3 (unit, 1.0); + unitVar = vec3(unit, 1.0); vec4 p = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw; if (p.w != 0.0) { p = p / p.w; diff --git a/src/webgl/pointFeatureSprite.frag b/src/webgl/pointFeatureSprite.frag index fcaf263797..f463897315 100644 --- a/src/webgl/pointFeatureSprite.frag +++ b/src/webgl/pointFeatureSprite.frag @@ -8,21 +8,19 @@ varying vec4 fillColorVar; varying vec4 strokeColorVar; varying float radiusVar; varying float strokeWidthVar; -varying float fillVar; -varying float strokeVar; // the square/triangle shade defines unitVar void main () { vec4 strokeColor, fillColor; float endStep; // No stroke or fill implies nothing to draw - if (fillVar == 0.0 && strokeVar == 0.0) + if (fillColorVar.a == 0.0 && strokeColorVar.a == 0.0) discard; float rad = 2.0 * length(gl_PointCoord - vec2(0.5)); // distinct for sprite if (rad > 1.0) discard; // If there is no stroke, the fill region should transition to nothing - if (strokeVar == 0.0) { + if (strokeColorVar.a == 0.0) { strokeColor = vec4(fillColorVar.rgb, 0.0); endStep = 1.0; } else { @@ -30,7 +28,7 @@ void main () { endStep = radiusVar / (radiusVar + strokeWidthVar); } // Likewise, if there is no fill, the stroke should transition to nothing - if (fillVar == 0.0) + if (fillColorVar.a == 0.0) fillColor = vec4(strokeColor.rgb, 0.0); else fillColor = fillColorVar; diff --git a/src/webgl/pointFeatureSprite.vert b/src/webgl/pointFeatureSprite.vert index 572100e042..e9069c0674 100644 --- a/src/webgl/pointFeatureSprite.vert +++ b/src/webgl/pointFeatureSprite.vert @@ -20,31 +20,25 @@ varying vec4 fillColorVar; varying vec4 strokeColorVar; varying float radiusVar; varying float strokeWidthVar; -varying float fillVar; -varying float strokeVar; // non-sprite has ither definitions. void main(void) { strokeWidthVar = strokeWidth; + fillColorVar = vec4(fillColor, fillOpacity); + strokeColorVar = vec4(strokeColor, strokeOpacity); // No stroke or fill implies nothing to draw if (stroke < 1.0 || strokeWidth <= 0.0 || strokeOpacity <= 0.0) { - strokeVar = 0.0; + strokeColorVar.a = 0.0; strokeWidthVar = 0.0; } - else - strokeVar = 1.0; if (fill < 1.0 || radius <= 0.0 || fillOpacity <= 0.0) - fillVar = 0.0; - else - fillVar = 1.0; + fillColorVar.a = 0.0; /* If the point has no visible pixels, skip doing computations on it. */ - if (fillVar == 0.0 && strokeVar == 0.0) { + if (fillColorVar.a == 0.0 && strokeColorVar.a == 0.0) { gl_Position = vec4(2, 2, 0, 1); return; } - fillColorVar = vec4 (fillColor, fillOpacity); - strokeColorVar = vec4 (strokeColor, strokeOpacity); radiusVar = radius; // for sprite gl_Position = (projectionMatrix * modelViewMatrix * vec4(pos, 1.0)).xyzw; diff --git a/tests/cases/pointFeature.js b/tests/cases/pointFeature.js index 9e5b8cd1dc..9e18484dc8 100644 --- a/tests/cases/pointFeature.js +++ b/tests/cases/pointFeature.js @@ -372,6 +372,14 @@ describe('geo.pointFeature', function () { point.updateStyleFromArray('radius', array1, true); expect(count).toBe(2); }); + it('modify and refresh', function () { + glCounts = $.extend({}, vgl.mockCounts()); + point2.modified().draw(); + expect(count).toBe(2); + }); + waitForIt('next render gl E', function () { + return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 8; + }); it('_exit', function () { expect(point.actors().length).toBe(1); layer.deleteFeature(point);