Skip to content

Commit 40b0c2b

Browse files
committed
- full refactor of shader code, DRY, optimizations etc.
- added bitmask tests while retaining the original 1D 'bounding box' test - removed SPLOM work seed - due to lightening up some of the shaders, do less work on the JS side too -> baseline case (1 range): 1/3 times the work in the vertex shader -> new use (>1 ranges): still rather fast
1 parent 9208cc3 commit 40b0c2b

File tree

9 files changed

+271
-150
lines changed

9 files changed

+271
-150
lines changed

src/traces/parcoords/constants.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ module.exports = {
1616
tickDistance: 50,
1717
canvasPixelRatio: 1,
1818
blockLineCount: 5000,
19-
scatter: false,
2019
layers: ['contextLineLayer', 'focusLineLayer', 'pickLineLayer'],
2120
axisTitleOffset: 28,
2221
axisExtentOffset: 10,

src/traces/parcoords/lines.js

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var glslify = require('glslify');
1212
var c = require('./constants');
1313
var vertexShaderSource = glslify('./shaders/vertex.glsl');
14+
var contextShaderSource = glslify('./shaders/context_vertex.glsl');
1415
var pickVertexShaderSource = glslify('./shaders/pick_vertex.glsl');
1516
var fragmentShaderSource = glslify('./shaders/fragment.glsl');
1617

@@ -19,6 +20,8 @@ var depthLimitEpsilon = 1e-6; // don't change; otherwise near/far plane lines ar
1920
var gpuDimensionCount = 64;
2021
var sectionVertexCount = 2;
2122
var vec4NumberCount = 4;
23+
var bitsPerByte = 8;
24+
var channelCount = gpuDimensionCount / bitsPerByte; // == 8 bytes needed to have 64 bits
2225

2326
var contextColor = [119, 119, 119]; // middle gray to not drawn the focus; looks good on a black or white background
2427

@@ -165,7 +168,7 @@ function valid(i, offset, panelCount) {
165168
return i + offset <= panelCount;
166169
}
167170

168-
module.exports = function(canvasGL, d, scatter) {
171+
module.exports = function(canvasGL, d) {
169172
var model = d.model,
170173
vm = d.viewModel,
171174
domain = model.domain;
@@ -263,7 +266,7 @@ module.exports = function(canvasGL, d, scatter) {
263266

264267
dither: false,
265268

266-
vert: pick ? pickVertexShaderSource : vertexShaderSource,
269+
vert: pick ? pickVertexShaderSource : context ? contextShaderSource : vertexShaderSource,
267270

268271
frag: fragmentShaderSource,
269272

@@ -291,8 +294,8 @@ module.exports = function(canvasGL, d, scatter) {
291294
loD: regl.prop('loD'),
292295
hiD: regl.prop('hiD'),
293296
palette: paletteTexture,
294-
colorClamp: regl.prop('colorClamp'),
295-
scatter: regl.prop('scatter')
297+
mask: regl.prop('maskTexture'),
298+
colorClamp: regl.prop('colorClamp')
296299
},
297300
offset: regl.prop('offset'),
298301
count: regl.prop('count')
@@ -307,7 +310,7 @@ module.exports = function(canvasGL, d, scatter) {
307310

308311
var previousAxisOrder = [];
309312

310-
function makeItem(i, ii, x, y, panelSizeX, canvasPanelSizeY, crossfilterDimensionIndex, scatter, I, leftmost, rightmost) {
313+
function makeItem(i, ii, x, y, panelSizeX, canvasPanelSizeY, crossfilterDimensionIndex, I, leftmost, rightmost) {
311314
var loHi, abcd, d, index;
312315
var leftRight = [i, ii];
313316
var filterEpsilon = c.verticalPadding / canvasPanelSizeY;
@@ -321,12 +324,16 @@ module.exports = function(canvasGL, d, scatter) {
321324
for(d = 0; d < 16; d++) {
322325
var dimP = d + 16 * abcd;
323326
dims[loHi][abcd][d] = d + 16 * abcd === index ? 1 : 0;
324-
lims[loHi][abcd][d] = (!context && valid(d, 16 * abcd, panelCount) ? initialDims[dimP === 0 ? 0 : 1 + ((dimP - 1) % (initialDims.length - 1))].brush.filter.getBounds()[loHi] : loHi) + (2 * loHi - 1) * filterEpsilon;
327+
if(!context) {
328+
lims[loHi][abcd][d] = (valid(d, 16 * abcd, panelCount) ? initialDims[dimP === 0 ? 0 : 1 + ((dimP - 1) % (initialDims.length - 1))].brush.filter.getBounds()[loHi] : loHi) + (2 * loHi - 1) * filterEpsilon;
329+
}
325330
}
326331
}
327332
}
328333

329-
return {
334+
var mask, maskTexture;
335+
336+
var vm = {
330337
key: crossfilterDimensionIndex,
331338
resolution: [canvasWidth, canvasHeight],
332339
viewBoxPosition: [x + overdrag, y],
@@ -343,17 +350,7 @@ module.exports = function(canvasGL, d, scatter) {
343350
dim2C: dims[1][2],
344351
dim2D: dims[1][3],
345352

346-
loA: lims[0][0],
347-
loB: lims[0][1],
348-
loC: lims[0][2],
349-
loD: lims[0][3],
350-
hiA: lims[1][0],
351-
hiB: lims[1][1],
352-
hiC: lims[1][2],
353-
hiD: lims[1][3],
354-
355353
colorClamp: colorClamp,
356-
scatter: scatter || 0,
357354

358355
scissorX: (I === leftmost ? 0 : x + overdrag) + (model.pad.l - overdrag) + model.layoutWidth * domain.x[0],
359356
scissorWidth: (I === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (I === leftmost ? x + overdrag : 0),
@@ -365,6 +362,54 @@ module.exports = function(canvasGL, d, scatter) {
365362
viewportWidth: canvasWidth,
366363
viewportHeight: canvasHeight
367364
};
365+
366+
if(!context) {
367+
mask = Array.apply(null, new Array(canvasHeight * channelCount)).map(function() {
368+
return 255;
369+
});
370+
for(var dimIndex = 0; dimIndex < dimensionCount; dimIndex++) {
371+
var bitIndex = dimIndex % bitsPerByte;
372+
var byteIndex = (dimIndex - bitIndex) / bitsPerByte;
373+
var bitMask = Math.pow(2, bitIndex);
374+
var dim = initialDims[dimIndex];
375+
var ranges = dim.brush.filter.get();
376+
if(ranges.length < 2) continue; // bail if the bounding box based filter is sufficient
377+
var pi, pixelRange;
378+
var boundingBox = dim.brush.filter.getBounds();
379+
pixelRange = boundingBox.map(dim.unitScaleInOrder);
380+
for(pi = Math.max(0, Math.floor(pixelRange[0])); pi <= Math.min(canvasHeight - 1, Math.ceil(pixelRange[1])); pi++) {
381+
mask[pi * channelCount + byteIndex] &= ~bitMask; // clear bits
382+
}
383+
for(var r = 0; r < ranges.length; r++) {
384+
pixelRange = ranges[r].map(dim.unitScaleInOrder);
385+
for(pi = Math.max(0, Math.floor(pixelRange[0])); pi <= Math.min(canvasHeight - 1, Math.ceil(pixelRange[1])); pi++) {
386+
mask[pi * channelCount + byteIndex] |= bitMask;
387+
}
388+
}
389+
}
390+
391+
maskTexture = regl.texture({
392+
shape: [channelCount, canvasHeight], // 8 units x 8 bits = 64 bits, just sufficient for the almost 64 dimensions we support
393+
format: 'alpha',
394+
type: 'uint8',
395+
mag: 'nearest',
396+
min: 'nearest',
397+
data: mask
398+
});
399+
400+
vm.maskTexture = maskTexture;
401+
402+
vm.loA = lims[0][0];
403+
vm.loB = lims[0][1];
404+
vm.loC = lims[0][2];
405+
vm.loD = lims[0][3];
406+
vm.hiA = lims[1][0];
407+
vm.hiB = lims[1][1];
408+
vm.hiC = lims[1][2];
409+
vm.hiD = lims[1][3];
410+
}
411+
412+
return vm;
368413
}
369414

370415
function renderGLParcoords(panels, setChanged, clearOnly) {
@@ -402,7 +447,7 @@ module.exports = function(canvasGL, d, scatter) {
402447
var xTo = x + panelSizeX;
403448
if(setChanged || !previousAxisOrder[i] || previousAxisOrder[i][0] !== x || previousAxisOrder[i][1] !== xTo) {
404449
previousAxisOrder[i] = [x, xTo];
405-
var item = makeItem(i, ii, x, y, panelSizeX, panelSizeY, dim1.crossfilterDimensionIndex, scatter || dim1.scatter ? 1 : 0, I, leftmost, rightmost);
450+
var item = makeItem(i, ii, x, y, panelSizeX, panelSizeY, dim1.crossfilterDimensionIndex, I, leftmost, rightmost);
406451
renderState.clearOnly = clearOnly;
407452
renderBlock(regl, glAes, renderState, setChanged ? lines.blockLineCount : sampleCount, sampleCount, item);
408453
}
@@ -435,6 +480,7 @@ module.exports = function(canvasGL, d, scatter) {
435480
function destroy() {
436481
canvasGL.style['pointer-events'] = 'none';
437482
paletteTexture.destroy();
483+
maskTexture.destroy();
438484
}
439485

440486
return {

src/traces/parcoords/parcoords.js

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ function viewModel(state, callbacks, model) {
223223
tickvals: dimension.tickvals,
224224
ticktext: dimension.ticktext,
225225
ordinal: !!dimension.tickvals,
226-
scatter: c.scatter || dimension.scatter,
227226
xIndex: i,
228227
crossfilterDimensionIndex: i,
229228
visibleIndex: dimension._index,
@@ -400,7 +399,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
400399
var yAxis = parcoordsControlView.selectAll('.' + c.cn.yAxis)
401400
.data(function(vm) {return vm.dimensions;}, keyFun);
402401

403-
function updatePanelLayoutParcoords(yAxis, vm) {
402+
function updatePanelLayout(yAxis, vm) {
404403
var panels = vm.panels || (vm.panels = []);
405404
var yAxes = yAxis.each(function(d) {return d;})[vm.key].map(function(e) {return e.__data__;});
406405
var panelCount = yAxes.length - 1;
@@ -421,31 +420,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
421420
}
422421
}
423422

424-
function updatePanelLayoutScatter(yAxis, vm) {
425-
var panels = vm.panels || (vm.panels = []);
426-
var yAxes = yAxis.each(function(d) {return d;})[vm.key].map(function(e) {return e.__data__;});
427-
var panelCount = yAxes.length - 1;
428-
var rowCount = panelCount;
429-
for(var row = 0; row < panelCount; row++) {
430-
for(var p = 0; p < panelCount; p++) {
431-
var panel = panels[p + row * panelCount] || (panels[p + row * panelCount] = {});
432-
var dim1 = yAxes[p];
433-
var dim2 = yAxes[p + 1];
434-
panel.dim1 = yAxes[row + 1];
435-
panel.dim2 = dim2;
436-
panel.canvasX = dim1.canvasX;
437-
panel.panelSizeX = dim2.canvasX - dim1.canvasX;
438-
panel.panelSizeY = vm.model.canvasHeight / rowCount;
439-
panel.y = row * panel.panelSizeY;
440-
panel.canvasY = vm.model.canvasHeight - panel.y - panel.panelSizeY;
441-
}
442-
}
443-
}
444-
445-
function updatePanelLayout(yAxis, vm) {
446-
return (c.scatter ? updatePanelLayoutScatter : updatePanelLayoutParcoords)(yAxis, vm);
447-
}
448-
449423
yAxis.enter()
450424
.append('g')
451425
.classed(c.cn.yAxis, true);
@@ -457,7 +431,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
457431
parcoordsLineLayer
458432
.filter(function(d) {return !!d.viewModel;})
459433
.each(function(d) {
460-
d.lineLayer = lineLayerMaker(this, d, c.scatter);
434+
d.lineLayer = lineLayerMaker(this, d);
461435
d.viewModel[d.key] = d.lineLayer;
462436
d.lineLayer.render(d.viewModel.panels, !d.context);
463437
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
precision highp float;
2+
3+
attribute vec4 p0, p1, p2, p3,
4+
p4, p5, p6, p7,
5+
p8, p9, pa, pb,
6+
pc, pd, pe;
7+
8+
attribute vec4 pf;
9+
10+
uniform mat4 dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D;
11+
12+
uniform vec2 resolution,
13+
viewBoxPosition,
14+
viewBoxSize;
15+
16+
uniform sampler2D palette;
17+
18+
uniform vec2 colorClamp;
19+
20+
varying vec4 fragColor;
21+
22+
#pragma glslify: unfilteredPosition = require("./unfiltered_position.glsl")
23+
24+
void main() {
25+
26+
float prominence = abs(pf[3]);
27+
28+
mat4 p[4];
29+
p[0] = mat4(p0, p1, p2, p3);
30+
p[1] = mat4(p4, p5, p6, p7);
31+
p[2] = mat4(p8, p9, pa, pb);
32+
p[3] = mat4(pc, pd, pe, abs(pf));
33+
34+
gl_Position = unfilteredPosition(
35+
1.0 - prominence,
36+
resolution, viewBoxPosition, viewBoxSize,
37+
p,
38+
sign(pf[3]),
39+
dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D
40+
);
41+
42+
float clampedColorIndex = clamp((prominence - colorClamp[0]) / (colorClamp[1] - colorClamp[0]), 0.0, 1.0);
43+
fragColor = texture2D(palette, vec2((clampedColorIndex * 255.0 + 0.5) / 256.0, 0.5));
44+
}

src/traces/parcoords/shaders/pick_vertex.glsl

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,69 +14,33 @@ uniform vec2 resolution,
1414
viewBoxPosition,
1515
viewBoxSize;
1616

17-
uniform sampler2D palette;
17+
uniform sampler2D mask;
1818

1919
uniform vec2 colorClamp;
2020

21-
uniform float scatter;
22-
2321
varying vec4 fragColor;
2422

25-
vec4 zero = vec4(0, 0, 0, 0);
26-
vec4 unit = vec4(1, 1, 1, 1);
27-
vec2 xyProjection = vec2(1, 1);
28-
29-
mat4 mclamp(mat4 m, mat4 lo, mat4 hi) {
30-
return mat4(clamp(m[0], lo[0], hi[0]),
31-
clamp(m[1], lo[1], hi[1]),
32-
clamp(m[2], lo[2], hi[2]),
33-
clamp(m[3], lo[3], hi[3]));
34-
}
35-
36-
bool mshow(mat4 p, mat4 lo, mat4 hi) {
37-
return mclamp(p, lo, hi) == p;
38-
}
39-
40-
float val(mat4 p, mat4 v) {
41-
return dot(matrixCompMult(p, v) * unit, unit);
42-
}
23+
#pragma glslify: position = require("./position.glsl")
4324

4425
void main() {
4526

46-
float x = 0.5 * sign(pf[3]) + 0.5;
4727
float prominence = abs(pf[3]);
48-
float depth = 1.0 - prominence;
49-
50-
mat4 pA = mat4(p0, p1, p2, p3);
51-
mat4 pB = mat4(p4, p5, p6, p7);
52-
mat4 pC = mat4(p8, p9, pa, pb);
53-
mat4 pD = mat4(pc, pd, pe, abs(pf));
54-
55-
float show = float(mshow(pA, loA, hiA) &&
56-
mshow(pB, loB, hiB) &&
57-
mshow(pC, loC, hiC) &&
58-
mshow(pD, loD, hiD));
59-
60-
vec2 yy = show * vec2(val(pA, dim2A) + val(pB, dim2B) + val(pC, dim2C) + val(pD, dim2D),
61-
val(pA, dim1A) + val(pB, dim1B) + val(pC, dim1C) + val(pD, dim1D));
62-
63-
vec2 dimensionToggle = vec2(x, 1.0 - x);
64-
65-
vec2 scatterToggle = vec2(scatter, 1.0 - scatter);
66-
67-
float y = dot(yy, dimensionToggle);
68-
mat2 xy = mat2(viewBoxSize * yy + dimensionToggle, viewBoxSize * vec2(x, y));
69-
70-
vec2 viewBoxXY = viewBoxPosition + xy * scatterToggle;
71-
72-
float depthOrHide = depth + 2.0 * (1.0 - show);
7328

74-
gl_Position = vec4(
75-
xyProjection * (2.0 * viewBoxXY / resolution - 1.0),
76-
depthOrHide,
77-
1.0
29+
mat4 p[4];
30+
p[0] = mat4(p0, p1, p2, p3);
31+
p[1] = mat4(p4, p5, p6, p7);
32+
p[2] = mat4(p8, p9, pa, pb);
33+
p[3] = mat4(pc, pd, pe, abs(pf));
34+
35+
gl_Position = position(
36+
1.0 - prominence,
37+
resolution, viewBoxPosition, viewBoxSize,
38+
p,
39+
sign(pf[3]),
40+
dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D,
41+
loA, hiA, loB, hiB, loC, hiC, loD, hiD,
42+
mask
7843
);
7944

80-
// pick coloring
8145
fragColor = vec4(pf.rgb, 1.0);
8246
}

0 commit comments

Comments
 (0)