Skip to content

Commit

Permalink
Improve webgl lineFeature and polygonFeature.
Browse files Browse the repository at this point in the history
Reduce memory transfer to the gpu when only styles have changed in lines
or polygons.

Rename some internal variables in the line feature to make it clearer.

Use constants to avoid array lookups in a few spots.
  • Loading branch information
manthey committed Sep 5, 2019
1 parent 5e8ba34 commit b3699a4
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 93 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Improvements
- Points with small radii or thin strokes are rendered better (#1021)
- Less data is transfered to the GPU when only styles have changed in webgl line or polygon features (#1016)

## Version 0.19.6

Expand Down
179 changes: 92 additions & 87 deletions src/webgl/lineFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ var flagsLineJoin = { // uses 3 bits with flagsLineCap
round: 6,
'miter-clip': 7
};
var flagsNearLineShift = 2, flagsFarLineShift = 5;
var flagsNearOffsetShift = 8; // uses 11 bits
var flagsNearLineShift = 2, flagsNearLineMult = 1 << flagsNearLineShift;
var flagsFarLineShift = 5, flagsFarLineMult = 1 << flagsFarLineShift;
var flagsNearOffsetShift = 8, // uses 11 bits
flagsNearOffsetMult = 1 << flagsNearOffsetShift;
/* Fixed flags */
var flagsDebug = { // uses 1 bit
normal: 0,
Expand Down Expand Up @@ -113,11 +115,17 @@ var webgl_lineFeature = function (arg) {
*/
function createGLLines(onlyStyle) {
var data = m_this.data(),
d, i, j, k, v, v2, lidx,
d, i, j, k, v1, v2, lidx, maxj,
numSegments = 0, len,
lineItemList, lineItem, lineItemData,
vert = [{}, {}], v1 = vert[1],
vert = [{
strokeOffset: 0, posStrokeOffset: 0, negStrokeOffset: 0
}, {
strokeOffset: 0, posStrokeOffset: 0, negStrokeOffset: 0
}],
v = vert[1],
pos, posIdx3, firstpos, firstPosIdx3,
lineFunc = m_this.line(),
strokeWidthFunc = m_this.style.get('strokeWidth'), strokeWidthVal,
strokeColorFunc = m_this.style.get('strokeColor'), strokeColorVal,
strokeOpacityFunc = m_this.style.get('strokeOpacity'), strokeOpacityVal,
Expand All @@ -126,19 +134,19 @@ var webgl_lineFeature = function (arg) {
strokeOffsetFunc = m_this.style.get('strokeOffset'), strokeOffsetVal,
miterLimit = m_this.style.get('miterLimit')(data),
antialiasing = m_this.style.get('antialiasing')(data) || 0,
order = m_this.featureVertices(),
order = m_this.featureVertices(), orderk0, prevkey, nextkey, offkey,
orderLen = order.length,
posBuf, prevBuf, nextBuf, farBuf, flagsBuf, indicesBuf,
posBuf, prevBuf, nextBuf, farBuf, flagsBuf,
fixedFlags = (flagsDebug[m_this.style.get('debug')(data) ? 'debug' : 'normal'] || 0),
strokeWidthBuf, strokeColorBuf, strokeOpacityBuf,
dest, dest3,
geom = m_mapper.geometryData(),
closedFunc = m_this.style.get('closed'), closedVal, closed = [],
closedFunc = m_this.style.get('closed'), closedVal, closed,
updateFlags = true;

closedVal = util.isFunction(m_this.style('closed')) ? undefined : (closedFunc() || false);
lineCapVal = util.isFunction(m_this.style('lineCap')) ? undefined : (lineCapFunc() || 'butt');
lineJoinVal = util.isFunction(m_this.style('lineJoin')) ? undefined : (lineJoinFunc() || 'miter');
lineCapVal = util.isFunction(m_this.style('lineCap')) ? undefined : flagsLineCap[lineCapFunc() || 'butt'];
lineJoinVal = util.isFunction(m_this.style('lineJoin')) ? undefined : flagsLineJoin[lineJoinFunc() || 'miter'];
strokeColorVal = util.isFunction(m_this.style('strokeColor')) ? undefined : strokeColorFunc();
strokeOffsetVal = util.isFunction(m_this.style('strokeOffset')) ? undefined : (strokeOffsetFunc() || 0);
strokeOpacityVal = util.isFunction(m_this.style('strokeOpacity')) ? undefined : strokeOpacityFunc();
Expand All @@ -157,9 +165,10 @@ var webgl_lineFeature = function (arg) {
var position = [],
posFunc = m_this.position();
lineItemList = new Array(data.length);
closed = new Array(data.length);
for (i = 0; i < data.length; i += 1) {
d = data[i];
lineItem = m_this.line()(d, i);
lineItem = lineFunc(d, i);
lineItemList[i] = lineItem;
if (lineItem.length < 2) {
continue;
Expand All @@ -183,6 +192,8 @@ var webgl_lineFeature = function (arg) {
} else {
closed[i] = 1; /* first point is repeated as last point */
}
} else {
closed[i] = 0;
}
}

Expand All @@ -204,10 +215,8 @@ var webgl_lineFeature = function (arg) {
nextBuf = util.getGeomBuffer(geom, 'next', len * 3);
farBuf = util.getGeomBuffer(geom, 'far', len * 3);

indicesBuf = geom.primitive(0).indices();
if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== len) {
indicesBuf = new Uint16Array(len);
geom.primitive(0).setIndices(indicesBuf);
if (geom.primitive(0).numberOfIndices() !== len) {
geom.primitive(0).numberOfIndices = function () { return len; };
}
// save some information to be reused when we update only style
m_geometry = {
Expand Down Expand Up @@ -241,8 +250,10 @@ var webgl_lineFeature = function (arg) {
continue;
}
d = data[i];
closedVal = closed[i];
firstPosIdx3 = posIdx3;
for (j = 0; j < lineItem.length + (closed[i] === 2 ? 1 : 0); j += 1, posIdx3 += 3) {
maxj = lineItem.length + (closedVal === 2 ? 1 : 0);
for (j = 0; j < maxj; j += 1, posIdx3 += 3) {
lidx = j;
if (j === lineItem.length) {
lidx = 0;
Expand All @@ -252,101 +263,92 @@ var webgl_lineFeature = function (arg) {
/* swap entries in vert so that vert[0] is the first vertex, and
* vert[1] will be reused for the second vertex */
if (j) {
v1 = vert[0];
v = vert[0];
vert[0] = vert[1];
vert[1] = v1;
vert[1] = v;
}
if (!onlyStyle) {
v1.pos = j === lidx ? posIdx3 : firstPosIdx3;
v1.prev = lidx ? posIdx3 - 3 : (closed[i] ?
firstPosIdx3 + (lineItem.length - 3 + closed[i]) * 3 : posIdx3);
v1.next = j + 1 < lineItem.length ? posIdx3 + 3 : (closed[i] ?
(j !== lidx ? firstPosIdx3 + 3 : firstPosIdx3 + 6 - closed[i] * 3) :
v.pos = j === lidx ? posIdx3 : firstPosIdx3;
v.prev = lidx ? posIdx3 - 3 : (closedVal ?
firstPosIdx3 + (lineItem.length - 3 + closedVal) * 3 : posIdx3);
v.next = j + 1 < lineItem.length ? posIdx3 + 3 : (closedVal ?
(j !== lidx ? firstPosIdx3 + 3 : firstPosIdx3 + 6 - closedVal * 3) :
posIdx3);
}
v1.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
v1.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
v1.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
v.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
v.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
v.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
if (updateFlags) {
v1.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
if (v1.strokeOffset) {
/* we use 11 bits to store the offset, and we want to store values
* from -1 to 1, so multiply our values by 1023, and use some bit
* manipulation to ensure that it is packed properly */
v1.posStrokeOffset = Math.round(2048 + 1023 * Math.min(1, Math.max(-1, v1.strokeOffset))) & 0x7FF;
v1.negStrokeOffset = Math.round(2048 - 1023 * Math.min(1, Math.max(-1, v1.strokeOffset))) & 0x7FF;
} else {
v1.posStrokeOffset = v1.negStrokeOffset = 0;
if (strokeOffsetVal !== 0) {
v.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
if (v.strokeOffset) {
/* we use 11 bits to store the offset, and we want to store values
* from -1 to 1, so multiply our values by 1023, and use some bit
* manipulation to ensure that it is packed properly */
v.posStrokeOffset = Math.round(2048 + 1023 * Math.min(1, Math.max(-1, v.strokeOffset))) & 0x7FF;
v.negStrokeOffset = Math.round(2048 - 1023 * Math.min(1, Math.max(-1, v.strokeOffset))) & 0x7FF;
} else {
v.posStrokeOffset = v.negStrokeOffset = 0;
}
}
if (!closed[i] && (!j || j === lineItem.length - 1)) {
v1.flags = flagsLineCap[lineCapVal === undefined ? lineCapFunc(lineItemData, lidx, d, i) : lineCapVal] || flagsLineCap.butt;
if (!closedVal && (!j || j === lineItem.length - 1)) {
v.flags = lineCapVal === undefined ? flagsLineCap[lineCapFunc(lineItemData, lidx, d, i)] || flagsLineCap.butt : lineCapVal;
} else {
v1.flags = flagsLineJoin[lineJoinVal === undefined ? lineJoinFunc(lineItemData, lidx, d, i) : lineJoinVal] || flagsLineJoin.miter;
v.flags = lineJoinVal === undefined ? flagsLineJoin[lineJoinFunc(lineItemData, lidx, d, i)] || flagsLineJoin.miter : lineJoinVal;
}
}

if (j) {
/* zero out the z position. This can be changed if we handle it in
* the shader. */
for (k = 0; k < orderLen; k += 1, dest += 1, dest3 += 3) {
v = vert[order[k][0]];
v2 = vert[1 - order[k][0]];
orderk0 = order[k][0];
v1 = vert[orderk0];
v2 = vert[1 - orderk0];
if (!onlyStyle) {
posBuf[dest3] = position[v.pos];
posBuf[dest3 + 1] = position[v.pos + 1];
posBuf[dest3 + 2] = 0; // position[v.pos + 2];
posBuf[dest3] = position[v1.pos];
posBuf[dest3 + 1] = position[v1.pos + 1];
posBuf[dest3 + 2] = 0; // position[v1.pos + 2];
prevkey = !orderk0 ? 'prev' : 'next';
nextkey = !orderk0 ? 'next' : 'prev';
prevBuf[dest3] = position[v1[prevkey]];
prevBuf[dest3 + 1] = position[v1[prevkey] + 1];
prevBuf[dest3 + 2] = 0; // position[v1[prevkey] + 2];
nextBuf[dest3] = position[v1[nextkey]];
nextBuf[dest3 + 1] = position[v1[nextkey] + 1];
nextBuf[dest3 + 2] = 0; // position[v1[nextkey] + 2];
farBuf[dest3] = position[v2[nextkey]];
farBuf[dest3 + 1] = position[v2[nextkey] + 1];
farBuf[dest3 + 2] = 0; // position[v2[nextkey] + 2];
}
if (!order[k][0]) {
if (!onlyStyle) {
prevBuf[dest3] = position[v.prev];
prevBuf[dest3 + 1] = position[v.prev + 1];
prevBuf[dest3 + 2] = 0; // position[v.prev + 2];
nextBuf[dest3] = position[v.next];
nextBuf[dest3 + 1] = position[v.next + 1];
nextBuf[dest3 + 2] = 0; // position[v.next + 2];
farBuf[dest3] = position[v2.next];
farBuf[dest3 + 1] = position[v2.next + 1];
farBuf[dest3 + 2] = 0; // position[v2.next + 2];
}
if (updateFlags) {
flagsBuf[dest] = (flagsVertex[order[k][1]] |
(v.flags << flagsNearLineShift) |
(v2.flags << flagsFarLineShift) |
(v.negStrokeOffset << flagsNearOffsetShift));
}
} else {
if (!onlyStyle) {
prevBuf[dest3] = position[v.next];
prevBuf[dest3 + 1] = position[v.next + 1];
prevBuf[dest3 + 2] = 0; // position[v.next + 2];
nextBuf[dest3] = position[v.prev];
nextBuf[dest3 + 1] = position[v.prev + 1];
nextBuf[dest3 + 2] = 0; // position[v.prev + 2];
farBuf[dest3] = position[v2.prev];
farBuf[dest3 + 1] = position[v2.prev + 1];
farBuf[dest3 + 2] = 0; // position[v2.prev + 2];
}
if (updateFlags) {
flagsBuf[dest] = (flagsVertex[order[k][1]] |
(v.flags << flagsNearLineShift) |
(v2.flags << flagsFarLineShift) |
(v.posStrokeOffset << flagsNearOffsetShift));
}
if (updateFlags) {
offkey = !orderk0 ? 'negStrokeOffset' : 'posStrokeOffset';
flagsBuf[dest] = (order[k][3] +
v1.flags * flagsNearLineMult +
v2.flags * flagsFarLineMult +
v1[offkey] * flagsNearOffsetMult);
}
strokeWidthBuf[dest] = v.strokeWidth;
strokeColorBuf[dest3] = v.strokeColor.r;
strokeColorBuf[dest3 + 1] = v.strokeColor.g;
strokeColorBuf[dest3 + 2] = v.strokeColor.b;
strokeOpacityBuf[dest] = v.strokeOpacity;
strokeWidthBuf[dest] = v1.strokeWidth;
strokeColorBuf[dest3] = v1.strokeColor.r;
strokeColorBuf[dest3 + 1] = v1.strokeColor.g;
strokeColorBuf[dest3 + 2] = v1.strokeColor.b;
strokeOpacityBuf[dest] = v1.strokeOpacity;
}
}
}
}

m_mapper.modified();
if (!onlyStyle) {
m_mapper.modified();
geom.boundsDirty(true);
m_mapper.boundsDirtyTimestamp().modified();
} else {
if (updateFlags) {
m_mapper.updateSourceBuffer('flags');
}
m_mapper.updateSourceBuffer('strokeWidth');
m_mapper.updateSourceBuffer('strokeOpacity');
m_mapper.updateSourceBuffer('strokeColor');
}
}

Expand All @@ -360,8 +362,8 @@ var webgl_lineFeature = function (arg) {
*/
this.featureVertices = function () {
return [
[0, 'corner', -1], [0, 'near', 1], [1, 'far', -1],
[1, 'corner', 1], [1, 'near', -1], [0, 'far', 1]];
[0, 'corner', -1, flagsVertex.corner], [0, 'near', 1, flagsVertex.near], [1, 'far', -1, flagsVertex.far],
[1, 'corner', 1, flagsVertex.corner], [1, 'near', -1, flagsVertex.near], [0, 'far', 1, flagsVertex.far]];
};

/**
Expand Down Expand Up @@ -462,6 +464,9 @@ var webgl_lineFeature = function (arg) {
geom.addSource(strkColorData);
geom.addSource(strkOpacityData);
geom.addSource(flagsData);
/* put a very small array here. We only use the length, and we'll override
* that elsewhere. */
triangles.setIndices(new Uint16Array(1));
geom.addPrimitive(triangles);
/* We don't need vgl to comptue bounds, so make the geo.computeBounds just
* set them to 0. */
Expand Down Expand Up @@ -495,7 +500,7 @@ var webgl_lineFeature = function (arg) {
* @returns {this}
*/
this._build = function () {
createGLLines(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry);
createGLLines(!!(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry));

if (!m_this.renderer().contextRenderer().hasActor(m_actor)) {
m_this.renderer().contextRenderer().addActor(m_actor);
Expand Down
9 changes: 6 additions & 3 deletions src/webgl/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ var webgl_polygonFeature = function (arg) {
fillColor[d3] = color.r;
fillColor[d3 + 1] = color.g;
fillColor[d3 + 2] = color.b;
if (!uniform && fillOpacityVal === undefined) {
if (!uniform && fill && fillOpacityVal === undefined) {
opacity = fillOpacityFunc(original[j], j, item, itemIndex);
}
fillOpacity[d] = opacity;
Expand All @@ -241,10 +241,13 @@ var webgl_polygonFeature = function (arg) {
items[k].opacity = opacity;
}
}
m_mapper.modified();
if (!onlyStyle) {
m_mapper.modified();
geom.boundsDirty(true);
m_mapper.boundsDirtyTimestamp().modified();
} else {
m_mapper.updateSourceBuffer('fillOpacity');
m_mapper.updateSourceBuffer('fillColor');
}
}

Expand Down Expand Up @@ -315,7 +318,7 @@ var webgl_polygonFeature = function (arg) {
* Build.
*/
this._build = function () {
createGLPolygons(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry);
createGLPolygons(!!(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry));

if (!m_this.renderer().contextRenderer().hasActor(m_actor)) {
m_this.renderer().contextRenderer().addActor(m_actor);
Expand Down
7 changes: 7 additions & 0 deletions tests/cases/lineFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ describe('geo.lineFeature', function () {
waitForIt('next render gl A', function () {
return vgl.mockCounts().createProgram >= (glCounts.createProgram || 0) + 1;
});
it('style update', function () {
glCounts = $.extend({}, vgl.mockCounts());
line.style('strokeOpacity', 1).draw();
});
waitForIt('next render gl B', function () {
return vgl.mockCounts().bufferSubData === (glCounts.bufferSubData || 0) + 4;
});
it('_exit', function () {
expect(line.actors().length).toBe(1);
layer.deleteFeature(line);
Expand Down
6 changes: 3 additions & 3 deletions tests/cases/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ describe('geo.polygonFeature', function () {
polygons.draw();
});
waitForIt('next render gl B', function () {
return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 &&
return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 &&
buildTime !== polygons.buildTime().timestamp();
});
it('update the style B', function () {
Expand All @@ -424,7 +424,7 @@ describe('geo.polygonFeature', function () {
polygons.draw();
});
waitForIt('next render gl C', function () {
return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 &&
return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 &&
buildTime !== polygons.buildTime().timestamp();
});
it('update the style C', function () {
Expand All @@ -436,7 +436,7 @@ describe('geo.polygonFeature', function () {
polygons.draw();
});
waitForIt('next render gl D', function () {
return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 &&
return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 &&
buildTime !== polygons.buildTime().timestamp();
});
it('poor data', function () {
Expand Down

0 comments on commit b3699a4

Please sign in to comment.