Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve webgl lineFeature and polygonFeature. #1016

Merged
merged 3 commits into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- When only updating point styles, don't recompute geometry transforms (#1022)
- Optimized a transform code path for pixel coordinates (#1023)
- WebGL point features automatically use the most memory-efficient primitive shape for the point sizes used based on the system's graphics capabilities (#1031)
- Less data is transfered to the GPU when only styles have changed in webgl line or polygon features (#1016)

### Changes
- Switched the default tile server to Stamen Design's toner-lite. (#1020)
Expand Down
192 changes: 104 additions & 88 deletions src/webgl/lineFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ var MAX_MITER_LIMIT = 100;

/* Flags are passed to the vertex shader in a float. Since a 32-bit float has
* 24 bits of mantissa, including the sign bit, a maximum of 23 bits of flags
* can be passed in a float without loss or complication. */
* can be passed in a float without loss or complication.
* The flags*Shift values are the bit offsets within the flag value. The
* flags*Mult values are the bit-offset values converted to a multiplier (2
* raised to the offset value). The overall flags value is composed of:
* bits 0-1: vertex (corner, near, far) used by the shader to know where in
* the geometry the vertex is used.
* 2-4: near cap/join style
* 5-7: far cap/join style
* 8-18: stroke offset as a signed value in the range [-1023,1023] which
* maps to a floating-point stroke offset of [-1,1].
*/
/* vertex flags specify which direction a vertex needs to be offset */
var flagsVertex = { // uses 2 bits
corner: 0,
Expand All @@ -25,8 +35,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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, if we add some code comment on how these flags are used, it would be be easy to remember and understand the code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aashish24 I've added more to the comment above all of the flag definitions.

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 +125,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 +144,20 @@ 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,
// webgl buffers; see _init for details
posBuf, prevBuf, nextBuf, farBuf, flagsBuf,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manthey would it be possible to add some note here on each buffer type specifically the flags buff?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

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 +176,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 +203,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 +226,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 +261,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 +274,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 +373,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 +475,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 compute bounds, so make the geo.computeBounds just
* set them to 0. */
Expand Down Expand Up @@ -495,7 +511,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
Loading