Skip to content

Commit 61d47af

Browse files
authored
Merge pull request #1016 from OpenGeoscience/webgl-line-improvements
Improve webgl lineFeature and polygonFeature.
2 parents 91876b3 + 9564c45 commit 61d47af

File tree

6 files changed

+122
-95
lines changed

6 files changed

+122
-95
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- When only updating point styles, don't recompute geometry transforms (#1022)
1111
- Optimized a transform code path for pixel coordinates (#1023)
1212
- WebGL point features automatically use the most memory-efficient primitive shape for the point sizes used based on the system's graphics capabilities (#1031)
13+
- Less data is transfered to the GPU when only styles have changed in webgl line or polygon features (#1016)
1314

1415
### Changes
1516
- Switched the default tile server to Stamen Design's toner-lite. (#1020)

src/webgl/lineFeature.js

Lines changed: 104 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@ var MAX_MITER_LIMIT = 100;
66

77
/* Flags are passed to the vertex shader in a float. Since a 32-bit float has
88
* 24 bits of mantissa, including the sign bit, a maximum of 23 bits of flags
9-
* can be passed in a float without loss or complication. */
9+
* can be passed in a float without loss or complication.
10+
* The flags*Shift values are the bit offsets within the flag value. The
11+
* flags*Mult values are the bit-offset values converted to a multiplier (2
12+
* raised to the offset value). The overall flags value is composed of:
13+
* bits 0-1: vertex (corner, near, far) used by the shader to know where in
14+
* the geometry the vertex is used.
15+
* 2-4: near cap/join style
16+
* 5-7: far cap/join style
17+
* 8-18: stroke offset as a signed value in the range [-1023,1023] which
18+
* maps to a floating-point stroke offset of [-1,1].
19+
*/
1020
/* vertex flags specify which direction a vertex needs to be offset */
1121
var flagsVertex = { // uses 2 bits
1222
corner: 0,
@@ -25,8 +35,10 @@ var flagsLineJoin = { // uses 3 bits with flagsLineCap
2535
round: 6,
2636
'miter-clip': 7
2737
};
28-
var flagsNearLineShift = 2, flagsFarLineShift = 5;
29-
var flagsNearOffsetShift = 8; // uses 11 bits
38+
var flagsNearLineShift = 2, flagsNearLineMult = 1 << flagsNearLineShift;
39+
var flagsFarLineShift = 5, flagsFarLineMult = 1 << flagsFarLineShift;
40+
var flagsNearOffsetShift = 8, // uses 11 bits
41+
flagsNearOffsetMult = 1 << flagsNearOffsetShift;
3042
/* Fixed flags */
3143
var flagsDebug = { // uses 1 bit
3244
normal: 0,
@@ -113,11 +125,17 @@ var webgl_lineFeature = function (arg) {
113125
*/
114126
function createGLLines(onlyStyle) {
115127
var data = m_this.data(),
116-
d, i, j, k, v, v2, lidx,
128+
d, i, j, k, v1, v2, lidx, maxj,
117129
numSegments = 0, len,
118130
lineItemList, lineItem, lineItemData,
119-
vert = [{}, {}], v1 = vert[1],
131+
vert = [{
132+
strokeOffset: 0, posStrokeOffset: 0, negStrokeOffset: 0
133+
}, {
134+
strokeOffset: 0, posStrokeOffset: 0, negStrokeOffset: 0
135+
}],
136+
v = vert[1],
120137
pos, posIdx3, firstpos, firstPosIdx3,
138+
lineFunc = m_this.line(),
121139
strokeWidthFunc = m_this.style.get('strokeWidth'), strokeWidthVal,
122140
strokeColorFunc = m_this.style.get('strokeColor'), strokeColorVal,
123141
strokeOpacityFunc = m_this.style.get('strokeOpacity'), strokeOpacityVal,
@@ -126,19 +144,20 @@ var webgl_lineFeature = function (arg) {
126144
strokeOffsetFunc = m_this.style.get('strokeOffset'), strokeOffsetVal,
127145
miterLimit = m_this.style.get('miterLimit')(data),
128146
antialiasing = m_this.style.get('antialiasing')(data) || 0,
129-
order = m_this.featureVertices(),
147+
order = m_this.featureVertices(), orderk0, prevkey, nextkey, offkey,
130148
orderLen = order.length,
131-
posBuf, prevBuf, nextBuf, farBuf, flagsBuf, indicesBuf,
149+
// webgl buffers; see _init for details
150+
posBuf, prevBuf, nextBuf, farBuf, flagsBuf,
132151
fixedFlags = (flagsDebug[m_this.style.get('debug')(data) ? 'debug' : 'normal'] || 0),
133152
strokeWidthBuf, strokeColorBuf, strokeOpacityBuf,
134153
dest, dest3,
135154
geom = m_mapper.geometryData(),
136-
closedFunc = m_this.style.get('closed'), closedVal, closed = [],
155+
closedFunc = m_this.style.get('closed'), closedVal, closed,
137156
updateFlags = true;
138157

139158
closedVal = util.isFunction(m_this.style('closed')) ? undefined : (closedFunc() || false);
140-
lineCapVal = util.isFunction(m_this.style('lineCap')) ? undefined : (lineCapFunc() || 'butt');
141-
lineJoinVal = util.isFunction(m_this.style('lineJoin')) ? undefined : (lineJoinFunc() || 'miter');
159+
lineCapVal = util.isFunction(m_this.style('lineCap')) ? undefined : flagsLineCap[lineCapFunc() || 'butt'];
160+
lineJoinVal = util.isFunction(m_this.style('lineJoin')) ? undefined : flagsLineJoin[lineJoinFunc() || 'miter'];
142161
strokeColorVal = util.isFunction(m_this.style('strokeColor')) ? undefined : strokeColorFunc();
143162
strokeOffsetVal = util.isFunction(m_this.style('strokeOffset')) ? undefined : (strokeOffsetFunc() || 0);
144163
strokeOpacityVal = util.isFunction(m_this.style('strokeOpacity')) ? undefined : strokeOpacityFunc();
@@ -157,9 +176,10 @@ var webgl_lineFeature = function (arg) {
157176
var position = [],
158177
posFunc = m_this.position();
159178
lineItemList = new Array(data.length);
179+
closed = new Array(data.length);
160180
for (i = 0; i < data.length; i += 1) {
161181
d = data[i];
162-
lineItem = m_this.line()(d, i);
182+
lineItem = lineFunc(d, i);
163183
lineItemList[i] = lineItem;
164184
if (lineItem.length < 2) {
165185
continue;
@@ -183,6 +203,8 @@ var webgl_lineFeature = function (arg) {
183203
} else {
184204
closed[i] = 1; /* first point is repeated as last point */
185205
}
206+
} else {
207+
closed[i] = 0;
186208
}
187209
}
188210

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

207-
indicesBuf = geom.primitive(0).indices();
208-
if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== len) {
209-
indicesBuf = new Uint16Array(len);
210-
geom.primitive(0).setIndices(indicesBuf);
229+
if (geom.primitive(0).numberOfIndices() !== len) {
230+
geom.primitive(0).numberOfIndices = function () { return len; };
211231
}
212232
// save some information to be reused when we update only style
213233
m_geometry = {
@@ -241,8 +261,10 @@ var webgl_lineFeature = function (arg) {
241261
continue;
242262
}
243263
d = data[i];
264+
closedVal = closed[i];
244265
firstPosIdx3 = posIdx3;
245-
for (j = 0; j < lineItem.length + (closed[i] === 2 ? 1 : 0); j += 1, posIdx3 += 3) {
266+
maxj = lineItem.length + (closedVal === 2 ? 1 : 0);
267+
for (j = 0; j < maxj; j += 1, posIdx3 += 3) {
246268
lidx = j;
247269
if (j === lineItem.length) {
248270
lidx = 0;
@@ -252,101 +274,92 @@ var webgl_lineFeature = function (arg) {
252274
/* swap entries in vert so that vert[0] is the first vertex, and
253275
* vert[1] will be reused for the second vertex */
254276
if (j) {
255-
v1 = vert[0];
277+
v = vert[0];
256278
vert[0] = vert[1];
257-
vert[1] = v1;
279+
vert[1] = v;
258280
}
259281
if (!onlyStyle) {
260-
v1.pos = j === lidx ? posIdx3 : firstPosIdx3;
261-
v1.prev = lidx ? posIdx3 - 3 : (closed[i] ?
262-
firstPosIdx3 + (lineItem.length - 3 + closed[i]) * 3 : posIdx3);
263-
v1.next = j + 1 < lineItem.length ? posIdx3 + 3 : (closed[i] ?
264-
(j !== lidx ? firstPosIdx3 + 3 : firstPosIdx3 + 6 - closed[i] * 3) :
282+
v.pos = j === lidx ? posIdx3 : firstPosIdx3;
283+
v.prev = lidx ? posIdx3 - 3 : (closedVal ?
284+
firstPosIdx3 + (lineItem.length - 3 + closedVal) * 3 : posIdx3);
285+
v.next = j + 1 < lineItem.length ? posIdx3 + 3 : (closedVal ?
286+
(j !== lidx ? firstPosIdx3 + 3 : firstPosIdx3 + 6 - closedVal * 3) :
265287
posIdx3);
266288
}
267-
v1.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
268-
v1.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
269-
v1.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
289+
v.strokeWidth = strokeWidthVal === undefined ? strokeWidthFunc(lineItemData, lidx, d, i) : strokeWidthVal;
290+
v.strokeColor = strokeColorVal === undefined ? strokeColorFunc(lineItemData, lidx, d, i) : strokeColorVal;
291+
v.strokeOpacity = strokeOpacityVal === undefined ? strokeOpacityFunc(lineItemData, lidx, d, i) : strokeOpacityVal;
270292
if (updateFlags) {
271-
v1.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
272-
if (v1.strokeOffset) {
273-
/* we use 11 bits to store the offset, and we want to store values
274-
* from -1 to 1, so multiply our values by 1023, and use some bit
275-
* manipulation to ensure that it is packed properly */
276-
v1.posStrokeOffset = Math.round(2048 + 1023 * Math.min(1, Math.max(-1, v1.strokeOffset))) & 0x7FF;
277-
v1.negStrokeOffset = Math.round(2048 - 1023 * Math.min(1, Math.max(-1, v1.strokeOffset))) & 0x7FF;
278-
} else {
279-
v1.posStrokeOffset = v1.negStrokeOffset = 0;
293+
if (strokeOffsetVal !== 0) {
294+
v.strokeOffset = (strokeOffsetVal === undefined ? strokeOffsetFunc(lineItemData, lidx, d, i) : strokeOffsetVal) || 0;
295+
if (v.strokeOffset) {
296+
/* we use 11 bits to store the offset, and we want to store values
297+
* from -1 to 1, so multiply our values by 1023, and use some bit
298+
* manipulation to ensure that it is packed properly */
299+
v.posStrokeOffset = Math.round(2048 + 1023 * Math.min(1, Math.max(-1, v.strokeOffset))) & 0x7FF;
300+
v.negStrokeOffset = Math.round(2048 - 1023 * Math.min(1, Math.max(-1, v.strokeOffset))) & 0x7FF;
301+
} else {
302+
v.posStrokeOffset = v.negStrokeOffset = 0;
303+
}
280304
}
281-
if (!closed[i] && (!j || j === lineItem.length - 1)) {
282-
v1.flags = flagsLineCap[lineCapVal === undefined ? lineCapFunc(lineItemData, lidx, d, i) : lineCapVal] || flagsLineCap.butt;
305+
if (!closedVal && (!j || j === lineItem.length - 1)) {
306+
v.flags = lineCapVal === undefined ? flagsLineCap[lineCapFunc(lineItemData, lidx, d, i)] || flagsLineCap.butt : lineCapVal;
283307
} else {
284-
v1.flags = flagsLineJoin[lineJoinVal === undefined ? lineJoinFunc(lineItemData, lidx, d, i) : lineJoinVal] || flagsLineJoin.miter;
308+
v.flags = lineJoinVal === undefined ? flagsLineJoin[lineJoinFunc(lineItemData, lidx, d, i)] || flagsLineJoin.miter : lineJoinVal;
285309
}
286310
}
287311

288312
if (j) {
289313
/* zero out the z position. This can be changed if we handle it in
290314
* the shader. */
291315
for (k = 0; k < orderLen; k += 1, dest += 1, dest3 += 3) {
292-
v = vert[order[k][0]];
293-
v2 = vert[1 - order[k][0]];
316+
orderk0 = order[k][0];
317+
v1 = vert[orderk0];
318+
v2 = vert[1 - orderk0];
294319
if (!onlyStyle) {
295-
posBuf[dest3] = position[v.pos];
296-
posBuf[dest3 + 1] = position[v.pos + 1];
297-
posBuf[dest3 + 2] = 0; // position[v.pos + 2];
320+
posBuf[dest3] = position[v1.pos];
321+
posBuf[dest3 + 1] = position[v1.pos + 1];
322+
posBuf[dest3 + 2] = 0; // position[v1.pos + 2];
323+
prevkey = !orderk0 ? 'prev' : 'next';
324+
nextkey = !orderk0 ? 'next' : 'prev';
325+
prevBuf[dest3] = position[v1[prevkey]];
326+
prevBuf[dest3 + 1] = position[v1[prevkey] + 1];
327+
prevBuf[dest3 + 2] = 0; // position[v1[prevkey] + 2];
328+
nextBuf[dest3] = position[v1[nextkey]];
329+
nextBuf[dest3 + 1] = position[v1[nextkey] + 1];
330+
nextBuf[dest3 + 2] = 0; // position[v1[nextkey] + 2];
331+
farBuf[dest3] = position[v2[nextkey]];
332+
farBuf[dest3 + 1] = position[v2[nextkey] + 1];
333+
farBuf[dest3 + 2] = 0; // position[v2[nextkey] + 2];
298334
}
299-
if (!order[k][0]) {
300-
if (!onlyStyle) {
301-
prevBuf[dest3] = position[v.prev];
302-
prevBuf[dest3 + 1] = position[v.prev + 1];
303-
prevBuf[dest3 + 2] = 0; // position[v.prev + 2];
304-
nextBuf[dest3] = position[v.next];
305-
nextBuf[dest3 + 1] = position[v.next + 1];
306-
nextBuf[dest3 + 2] = 0; // position[v.next + 2];
307-
farBuf[dest3] = position[v2.next];
308-
farBuf[dest3 + 1] = position[v2.next + 1];
309-
farBuf[dest3 + 2] = 0; // position[v2.next + 2];
310-
}
311-
if (updateFlags) {
312-
flagsBuf[dest] = (flagsVertex[order[k][1]] |
313-
(v.flags << flagsNearLineShift) |
314-
(v2.flags << flagsFarLineShift) |
315-
(v.negStrokeOffset << flagsNearOffsetShift));
316-
}
317-
} else {
318-
if (!onlyStyle) {
319-
prevBuf[dest3] = position[v.next];
320-
prevBuf[dest3 + 1] = position[v.next + 1];
321-
prevBuf[dest3 + 2] = 0; // position[v.next + 2];
322-
nextBuf[dest3] = position[v.prev];
323-
nextBuf[dest3 + 1] = position[v.prev + 1];
324-
nextBuf[dest3 + 2] = 0; // position[v.prev + 2];
325-
farBuf[dest3] = position[v2.prev];
326-
farBuf[dest3 + 1] = position[v2.prev + 1];
327-
farBuf[dest3 + 2] = 0; // position[v2.prev + 2];
328-
}
329-
if (updateFlags) {
330-
flagsBuf[dest] = (flagsVertex[order[k][1]] |
331-
(v.flags << flagsNearLineShift) |
332-
(v2.flags << flagsFarLineShift) |
333-
(v.posStrokeOffset << flagsNearOffsetShift));
334-
}
335+
if (updateFlags) {
336+
offkey = !orderk0 ? 'negStrokeOffset' : 'posStrokeOffset';
337+
flagsBuf[dest] = (order[k][3] +
338+
v1.flags * flagsNearLineMult +
339+
v2.flags * flagsFarLineMult +
340+
v1[offkey] * flagsNearOffsetMult);
335341
}
336-
strokeWidthBuf[dest] = v.strokeWidth;
337-
strokeColorBuf[dest3] = v.strokeColor.r;
338-
strokeColorBuf[dest3 + 1] = v.strokeColor.g;
339-
strokeColorBuf[dest3 + 2] = v.strokeColor.b;
340-
strokeOpacityBuf[dest] = v.strokeOpacity;
342+
strokeWidthBuf[dest] = v1.strokeWidth;
343+
strokeColorBuf[dest3] = v1.strokeColor.r;
344+
strokeColorBuf[dest3 + 1] = v1.strokeColor.g;
345+
strokeColorBuf[dest3 + 2] = v1.strokeColor.b;
346+
strokeOpacityBuf[dest] = v1.strokeOpacity;
341347
}
342348
}
343349
}
344350
}
345351

346-
m_mapper.modified();
347352
if (!onlyStyle) {
353+
m_mapper.modified();
348354
geom.boundsDirty(true);
349355
m_mapper.boundsDirtyTimestamp().modified();
356+
} else {
357+
if (updateFlags) {
358+
m_mapper.updateSourceBuffer('flags');
359+
}
360+
m_mapper.updateSourceBuffer('strokeWidth');
361+
m_mapper.updateSourceBuffer('strokeOpacity');
362+
m_mapper.updateSourceBuffer('strokeColor');
350363
}
351364
}
352365

@@ -360,8 +373,8 @@ var webgl_lineFeature = function (arg) {
360373
*/
361374
this.featureVertices = function () {
362375
return [
363-
[0, 'corner', -1], [0, 'near', 1], [1, 'far', -1],
364-
[1, 'corner', 1], [1, 'near', -1], [0, 'far', 1]];
376+
[0, 'corner', -1, flagsVertex.corner], [0, 'near', 1, flagsVertex.near], [1, 'far', -1, flagsVertex.far],
377+
[1, 'corner', 1, flagsVertex.corner], [1, 'near', -1, flagsVertex.near], [0, 'far', 1, flagsVertex.far]];
365378
};
366379

367380
/**
@@ -462,6 +475,9 @@ var webgl_lineFeature = function (arg) {
462475
geom.addSource(strkColorData);
463476
geom.addSource(strkOpacityData);
464477
geom.addSource(flagsData);
478+
/* put a very small array here. We only use the length, and we'll override
479+
* that elsewhere. */
480+
triangles.setIndices(new Uint16Array(1));
465481
geom.addPrimitive(triangles);
466482
/* We don't need vgl to compute bounds, so make the geo.computeBounds just
467483
* set them to 0. */
@@ -495,7 +511,7 @@ var webgl_lineFeature = function (arg) {
495511
* @returns {this}
496512
*/
497513
this._build = function () {
498-
createGLLines(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry);
514+
createGLLines(!!(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry));
499515

500516
if (!m_this.renderer().contextRenderer().hasActor(m_actor)) {
501517
m_this.renderer().contextRenderer().addActor(m_actor);

src/webgl/polygonFeature.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ var webgl_polygonFeature = function (arg) {
229229
fillColor[d3] = color.r;
230230
fillColor[d3 + 1] = color.g;
231231
fillColor[d3 + 2] = color.b;
232-
if (!uniform && fillOpacityVal === undefined) {
232+
if (!uniform && fill && fillOpacityVal === undefined) {
233233
opacity = fillOpacityFunc(original[j], j, item, itemIndex);
234234
}
235235
fillOpacity[d] = opacity;
@@ -241,10 +241,13 @@ var webgl_polygonFeature = function (arg) {
241241
items[k].opacity = opacity;
242242
}
243243
}
244-
m_mapper.modified();
245244
if (!onlyStyle) {
245+
m_mapper.modified();
246246
geom.boundsDirty(true);
247247
m_mapper.boundsDirtyTimestamp().modified();
248+
} else {
249+
m_mapper.updateSourceBuffer('fillOpacity');
250+
m_mapper.updateSourceBuffer('fillColor');
248251
}
249252
}
250253

@@ -315,7 +318,7 @@ var webgl_polygonFeature = function (arg) {
315318
* Build.
316319
*/
317320
this._build = function () {
318-
createGLPolygons(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry);
321+
createGLPolygons(!!(m_this.dataTime().timestamp() < m_this.buildTime().timestamp() && m_geometry));
319322

320323
if (!m_this.renderer().contextRenderer().hasActor(m_actor)) {
321324
m_this.renderer().contextRenderer().addActor(m_actor);

tests/cases/lineFeature.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,13 @@ describe('geo.lineFeature', function () {
401401
waitForIt('next render gl A', function () {
402402
return vgl.mockCounts().createProgram >= (glCounts.createProgram || 0) + 1;
403403
});
404+
it('style update', function () {
405+
glCounts = $.extend({}, vgl.mockCounts());
406+
line.style('strokeOpacity', 1).draw();
407+
});
408+
waitForIt('next render gl B', function () {
409+
return vgl.mockCounts().bufferSubData === (glCounts.bufferSubData || 0) + 4;
410+
});
404411
it('_exit', function () {
405412
expect(line.actors().length).toBe(1);
406413
layer.deleteFeature(line);

tests/cases/polygonFeature.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ describe('geo.polygonFeature', function () {
412412
polygons.draw();
413413
});
414414
waitForIt('next render gl B', function () {
415-
return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 &&
415+
return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 &&
416416
buildTime !== polygons.buildTime().timestamp();
417417
});
418418
it('update the style B', function () {
@@ -424,7 +424,7 @@ describe('geo.polygonFeature', function () {
424424
polygons.draw();
425425
});
426426
waitForIt('next render gl C', function () {
427-
return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 &&
427+
return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 &&
428428
buildTime !== polygons.buildTime().timestamp();
429429
});
430430
it('update the style C', function () {
@@ -436,7 +436,7 @@ describe('geo.polygonFeature', function () {
436436
polygons.draw();
437437
});
438438
waitForIt('next render gl D', function () {
439-
return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 &&
439+
return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 &&
440440
buildTime !== polygons.buildTime().timestamp();
441441
});
442442
it('poor data', function () {

0 commit comments

Comments
 (0)