Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

uber / deck.gl / 13030

26 Aug 2019 - 19:27 coverage decreased (-2.6%) to 80.38%
13030

Pull #3490

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
integrate mapbox's near plane fix
Pull Request #3490: [MapboxLayer] integrate mapbox-gl's near plane fix

3369 of 4577 branches covered (73.61%)

Branch coverage included in aggregate %.

6877 of 8170 relevant lines covered (84.17%)

4644.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.8
/modules/aggregation-layers/src/utils/gpu-grid-aggregation/gpu-grid-aggregator.js
1
import GL from '@luma.gl/constants';
31×
2
import {
3
  Buffer,
4
  Model,
5
  Transform,
6
  FEATURES,
7
  hasFeatures,
8
  isWebGL2,
9
  readPixelsToBuffer,
10
  fp64 as fp64ShaderModule,
11
  withParameters
12
} from '@luma.gl/core';
13
import {log, project64} from '@deck.gl/core';
14
import {worldToPixels} from 'viewport-mercator-project';
15
const {fp64ifyMatrix4} = fp64ShaderModule;
1×
16

17
import {
18
  DEFAULT_CHANGE_FLAGS,
19
  DEFAULT_RUN_PARAMS,
20
  MAX_32_BIT_FLOAT,
21
  MIN_BLEND_EQUATION,
22
  MAX_BLEND_EQUATION,
23
  MAX_MIN_BLEND_EQUATION,
24
  EQUATION_MAP,
25
  ELEMENTCOUNT,
26
  DEFAULT_WEIGHT_PARAMS,
27
  IDENTITY_MATRIX,
28
  PIXEL_SIZE,
29
  WEIGHT_SIZE
30
} from './gpu-grid-aggregator-constants';
31
import {AGGREGATION_OPERATION} from '../aggregation-operation-utils';
32

33
import AGGREGATE_TO_GRID_VS from './aggregate-to-grid-vs.glsl';
34
import AGGREGATE_TO_GRID_VS_FP64 from './aggregate-to-grid-vs-64.glsl';
35
import AGGREGATE_TO_GRID_FS from './aggregate-to-grid-fs.glsl';
36
import AGGREGATE_ALL_VS_FP64 from './aggregate-all-vs-64.glsl';
37
import AGGREGATE_ALL_FS from './aggregate-all-fs.glsl';
38
import TRANSFORM_MEAN_VS from './transform-mean-vs.glsl';
39
import {getFloatTexture, getFramebuffer, getFloatArray} from './../resource-utils.js';
40

41
const BUFFER_NAMES = ['aggregationBuffer', 'maxMinBuffer', 'minBuffer', 'maxBuffer'];
1×
42
const ARRAY_BUFFER_MAP = {
1×
43
  maxData: 'maxBuffer',
44
  minData: 'minBuffer',
45
  maxMinData: 'maxMinBuffer'
46
};
47

48
export default class GPUGridAggregator {
49
  // Decode and return aggregation data of given pixel.
50
  static getAggregationData({aggregationData, maxData, minData, maxMinData, pixelIndex}) {
UNCOV
51
    const index = pixelIndex * PIXEL_SIZE;
!
UNCOV
52
    const results = {};
!
UNCOV
53
    if (aggregationData) {
Branches [[0, 0], [0, 1]] missed. !
UNCOV
54
      results.cellCount = aggregationData[index + 3];
!
UNCOV
55
      results.cellWeight = aggregationData[index];
!
56
    }
UNCOV
57
    if (maxMinData) {
Branches [[1, 0], [1, 1]] missed. !
UNCOV
58
      results.maxCellWieght = maxMinData[0];
!
UNCOV
59
      results.minCellWeight = maxMinData[3];
!
60
    } else {
UNCOV
61
      if (maxData) {
Branches [[2, 0], [2, 1]] missed. !
UNCOV
62
        results.maxCellWieght = maxData[0];
!
UNCOV
63
        results.totalCount = maxData[3];
!
64
      }
UNCOV
65
      if (minData) {
Branches [[3, 0], [3, 1]] missed. !
UNCOV
66
        results.minCellWeight = minData[0];
!
UNCOV
67
        results.totalCount = maxData[3];
!
68
      }
69
    }
UNCOV
70
    return results;
!
71
  }
72

73
  // Decodes and retuns counts and weights of all cells
74
  static getCellData({countsData, size = 1}) {
75
    const numCells = countsData.length / 4;
11×
76
    const cellWeights = new Float32Array(numCells * size);
11×
77
    const cellCounts = new Uint32Array(numCells);
11×
78
    for (let i = 0; i < numCells; i++) {
11×
79
      // weights in RGB channels
80
      for (let sizeIndex = 0; sizeIndex < size; sizeIndex++) {
6,009×
81
        cellWeights[i * size + sizeIndex] = countsData[i * 4 + sizeIndex];
6,009×
82
      }
83
      // count in Alpha channel
84
      cellCounts[i] = countsData[i * 4 + 3];
6,009×
85
    }
86
    return {cellCounts, cellWeights};
11×
87
  }
88

89
  static isSupported(gl) {
90
    return (
6×
91
      isWebGL2(gl) &&
Branches [[5, 1]] missed.
92
      hasFeatures(
93
        gl,
94
        FEATURES.BLEND_EQUATION_MINMAX,
95
        FEATURES.COLOR_ATTACHMENT_RGBA32F,
96
        FEATURES.TEXTURE_FLOAT
97
      )
98
    );
99
  }
100

101
  // DEBUG ONLY
102
  // static logData({aggregationBuffer, minBuffer, maxBuffer, maxMinBuffer, limit = 10}) {
103
  //   if (aggregationBuffer) {
104
  //     console.log('Aggregation Data:');
105
  //     const agrData = aggregationBuffer.getData();
106
  //     for (let index = 0; index < agrData.length && limit > 0; index += 4) {
107
  //       if (agrData[index + 3] > 0) {
108
  //         console.log(
109
  //           `index: ${index} weights: ${agrData[index]} ${agrData[index + 1]} ${
110
  //             agrData[index + 2]
111
  //           } count: ${agrData[index + 3]}`
112
  //         );
113
  //         limit--;
114
  //       }
115
  //     }
116
  //   }
117
  //   const obj = {minBuffer, maxBuffer, maxMinBuffer};
118
  //   for (const key in obj) {
119
  //     if (obj[key]) {
120
  //       const data = obj[key].getData();
121
  //       console.log(`${key} data : R: ${data[0]} G: ${data[1]} B: ${data[2]} A: ${data[3]}`);
122
  //     }
123
  //   }
124
  // }
125

126
  constructor(gl, opts = {}) {
Branches [[6, 0]] missed.
127
    this.id = opts.id || 'gpu-grid-aggregator';
Branches [[7, 1]] missed. 8×
128
    this.shaderCache = opts.shaderCache || null;
Branches [[8, 1]] missed. 8×
129
    this.gl = gl;
8×
130
    this.state = {
8×
131
      // cache weights and position data to process when data is not changed
132
      weights: null,
133
      gridPositions: null,
134
      positionsBuffer: null,
135
      positions64xyLowBuffer: null,
136
      vertexCount: 0,
137

138
      // flags/variables that affect the aggregation
139
      fp64: null,
140
      useGPU: null,
141
      numCol: 0,
142
      numRow: 0,
143
      windowSize: null,
144
      cellSize: null,
145

146
      // per weight GPU resources
147
      weightAttributes: {},
148
      textures: {},
149
      meanTextures: {},
150
      buffers: {},
151
      framebuffers: {},
152
      maxMinFramebuffers: {},
153
      minFramebuffers: {},
154
      maxFramebuffers: {},
155
      equations: {},
156

157
      // common resources to be deleted
158
      resources: {},
159

160
      // results
161
      results: {}
162
    };
163
    this._hasGPUSupport =
8×
164
      isWebGL2(gl) && // gl_InstanceID usage in min/max calculation shaders
Branches [[9, 1]] missed.
165
      hasFeatures(
166
        this.gl,
167
        FEATURES.BLEND_EQUATION_MINMAX, // set min/max blend modes
168
        FEATURES.COLOR_ATTACHMENT_RGBA32F, // render to float texture
169
        FEATURES.TEXTURE_FLOAT // sample from a float texture
170
      );
171
  }
172

173
  // Delete owned resources.
174
  /* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
175
  delete() {
176
    const {gridAggregationModel, allAggregationModel, meanTransform} = this;
6×
177
    const {
178
      positionsBuffer,
179
      positions64xyLowBuffer,
180
      textures,
181
      framebuffers,
182
      maxMinFramebuffers,
183
      minFramebuffers,
184
      maxFramebuffers,
185
      meanTextures,
186
      resources
187
    } = this.state;
6×
188

189
    gridAggregationModel && gridAggregationModel.delete();
Branches [[10, 1]] missed. 6×
190
    allAggregationModel && allAggregationModel.delete();
Branches [[11, 1]] missed. 6×
191
    meanTransform && meanTransform.delete();
Branches [[12, 1]] missed. 6×
192

193
    positionsBuffer && positionsBuffer.delete();
Branches [[13, 1]] missed. 6×
194
    positions64xyLowBuffer && positions64xyLowBuffer.delete();
Branches [[14, 1]] missed. 6×
195
    this.deleteResources([
6×
196
      framebuffers,
197
      textures,
198
      maxMinFramebuffers,
199
      minFramebuffers,
200
      maxFramebuffers,
201
      meanTextures,
202
      resources
203
    ]);
204
  }
205

206
  // Perform aggregation and retun the results
207
  run(opts = {}) {
Branches [[15, 0]] missed.
208
    // reset results
209
    this.setState({results: {}});
30×
210
    const aggregationParams = this.getAggregationParams(opts);
30×
211
    this.updateGridSize(aggregationParams);
30×
212
    const {useGPU} = aggregationParams;
30×
213
    if (this._hasGPUSupport && useGPU) {
Branches [[16, 0], [17, 1]] missed. 30×
UNCOV
214
      return this.runAggregationOnGPU(aggregationParams);
!
215
    }
216
    if (useGPU) {
30×
217
      log.warn('GPUGridAggregator: GPU Aggregation not supported, falling back to CPU')();
15×
218
    }
219
    return this.runAggregationOnCPU(aggregationParams);
30×
220
  }
221

222
  // Reads aggregation data into JS Array object
223
  // For WebGL1, data is available in JS Array objects already.
224
  // For WebGL2, data is read from Buffer objects and cached for subsequent queries.
225
  getData(weightId) {
UNCOV
226
    const data = {};
!
UNCOV
227
    const results = this.state.results;
!
228
    if (!results[weightId].aggregationData) {
Branches [[19, 0], [19, 1]] missed. !
229
      // cache the results if reading from the buffer (WebGL2 path)
230
      results[weightId].aggregationData = results[weightId].aggregationBuffer.getData();
!
231
    }
232
    data.aggregationData = results[weightId].aggregationData;
!
233

234
    // Check for optional results
235
    for (const arrayName in ARRAY_BUFFER_MAP) {
!
236
      const bufferName = ARRAY_BUFFER_MAP[arrayName];
!
237

238
      if (results[weightId][arrayName] || results[weightId][bufferName]) {
Branches [[20, 0], [20, 1], [21, 0], [21, 1]] missed. !
239
        // cache the result
240
        results[weightId][arrayName] =
!
241
          results[weightId][arrayName] || results[weightId][bufferName].getData();
Branches [[22, 0], [22, 1]] missed.
242
        data[arrayName] = results[weightId][arrayName];
!
243
      }
244
    }
245
    return data;
!
246
  }
247

248
  // PRIVATE
249

250
  deleteResources(resources) {
251
    resources = Array.isArray(resources) ? resources : [resources];
Branches [[23, 1]] missed. 6×
252
    resources.forEach(obj => {
6×
253
      for (const name in obj) {
42×
254
        obj[name].delete();
10×
255
      }
256
    });
257
  }
258

259
  getAggregationParams(opts) {
260
    const aggregationParams = Object.assign({}, DEFAULT_RUN_PARAMS, opts);
30×
261
    const {
262
      useGPU,
263
      gridTransformMatrix,
264
      viewport,
265
      weights,
266
      projectPoints,
267
      cellSize
268
    } = aggregationParams;
30×
269
    if (this.state.useGPU !== useGPU) {
30×
270
      // CPU/GPU resources need to reinitialized, force set the change flags.
271
      aggregationParams.changeFlags = Object.assign(
9×
272
        {},
273
        aggregationParams.changeFlags,
274
        DEFAULT_CHANGE_FLAGS
275
      );
276
    }
277
    if (
30×
278
      cellSize &&
279
      (!this.state.cellSize ||
280
        this.state.cellSize[0] !== cellSize[0] ||
281
        this.state.cellSize[1] !== cellSize[1])
282
    ) {
283
      aggregationParams.changeFlags.cellSizeChanged = true;
14×
284
      // For GridLayer aggregation, cellSize is calculated by parsing all input data as it depends
285
      // on bounding box, cache cellSize
286
      this.setState({cellSize});
14×
287
    }
288

289
    this.validateProps(aggregationParams, opts);
30×
290

291
    this.setState({useGPU});
30×
292
    aggregationParams.gridTransformMatrix =
30×
293
      (projectPoints ? viewport.viewportMatrix : gridTransformMatrix) || IDENTITY_MATRIX;
Branches [[27, 1]] missed.
294

295
    if (weights) {
30×
296
      aggregationParams.weights = this.normalizeWeightParams(weights);
28×
297

298
      // cache weights to process when only cellSize or viewport is changed.
299
      // position data is cached in Buffers for GPU case and in 'gridPositions' for CPU case.
300
      this.setState({weights: aggregationParams.weights});
28×
301
    }
302
    return aggregationParams;
30×
303
  }
304

305
  normalizeWeightParams(weights) {
306
    const result = {};
28×
307
    for (const id in weights) {
28×
308
      result[id] = Object.assign({}, DEFAULT_WEIGHT_PARAMS, weights[id]);
37×
309
    }
310
    return result;
28×
311
  }
312

313
  // Update priveate state
314
  setState(updateObject) {
315
    Object.assign(this.state, updateObject);
186×
316
  }
317

318
  shouldTransformToGrid(opts) {
319
    const {projectPoints, changeFlags} = opts;
30×
320
    if (
30×
321
      !this.state.gridPositions ||
322
      changeFlags.dataChanged ||
323
      (projectPoints && changeFlags.viewportChanged) // world space aggregation (GridLayer) doesn't change when viewport is changed.
324
    ) {
325
      return true;
24×
326
    }
327
    return false;
6×
328
  }
329

330
  updateGridSize(opts) {
331
    const {viewport, cellSize} = opts;
30×
332
    const width = opts.width || viewport.width;
30×
333
    const height = opts.height || viewport.height;
30×
334
    const numCol = Math.ceil(width / cellSize[0]);
30×
335
    const numRow = Math.ceil(height / cellSize[1]);
30×
336
    this.setState({numCol, numRow, windowSize: [width, height]});
30×
337
  }
338

339
  /* eslint-disable complexity */
340
  // validate and log.assert
341
  validateProps(aggregationParams, opts) {
342
    const {changeFlags, projectPoints, gridTransformMatrix} = aggregationParams;
30×
343
    log.assert(
30×
344
      changeFlags.dataChanged || changeFlags.viewportChanged || changeFlags.cellSizeChanged
345
    );
346

347
    // log.assert for required options
348
    log.assert(
30×
349
      !changeFlags.dataChanged ||
Branches [[35, 4]] missed.
350
        (opts.positions &&
351
          opts.weights &&
352
          (!opts.projectPositions || opts.viewport) &&
353
          opts.cellSize)
354
    );
355
    log.assert(!changeFlags.cellSizeChanged || opts.cellSize);
30×
356

357
    // viewport is needed only when performing screen space aggregation (projectPoints is true)
358
    log.assert(!(changeFlags.viewportChanged && projectPoints) || opts.viewport);
30×
359

360
    if (projectPoints && gridTransformMatrix) {
Branches [[39, 0]] missed. 30×
361
      log.warn('projectPoints is true, gridTransformMatrix is ignored')();
!
362
    }
363
  }
364
  /* eslint-enable complexity */
365

366
  // CPU Aggregation methods
367

368
  // aggregated weight value to a cell
369
  /* eslint-disable max-depth */
370
  calculateAggregationData(opts) {
371
    const {weights, results, cellIndex, posIndex} = opts;
7,603×
372
    for (const id in weights) {
7,603×
373
      const {values, size, operation} = weights[id];
7,628×
374
      const {aggregationData} = results[id];
7,628×
375

376
      // Fill RGB with weights
377
      for (let sizeIndex = 0; sizeIndex < size; sizeIndex++) {
7,628×
378
        const cellElementIndex = cellIndex + sizeIndex;
7,628×
379
        const weightComponent = values[posIndex * WEIGHT_SIZE + sizeIndex];
7,628×
380

381
        if (aggregationData[cellIndex + 3] === 0) {
7,628×
382
          // if the cell is getting update the first time, set the value directly.
383
          aggregationData[cellElementIndex] = weightComponent;
1,424×
384
        } else {
385
          switch (operation) {
Branches [[42, 2], [42, 3], [42, 4]] missed. 6,204×
386
            case AGGREGATION_OPERATION.SUM:
387
            case AGGREGATION_OPERATION.MEAN:
388
              aggregationData[cellElementIndex] += weightComponent;
6,204×
389
              // MEAN value is calculated during 'calculateMeanMaxMinData'
390
              break;
6,204×
391
            case AGGREGATION_OPERATION.MIN:
UNCOV
392
              aggregationData[cellElementIndex] = Math.min(
!
393
                aggregationData[cellElementIndex],
394
                weightComponent
395
              );
UNCOV
396
              break;
!
397
            case AGGREGATION_OPERATION.MAX:
UNCOV
398
              aggregationData[cellElementIndex] = Math.max(
!
399
                aggregationData[cellElementIndex],
400
                weightComponent
401
              );
UNCOV
402
              break;
!
403
            default:
404
              // Not a valid operation enum.
UNCOV
405
              log.assert(false);
!
UNCOV
406
              break;
!
407
          }
408
        }
409
      }
410

411
      // Track the count per grid-cell
412
      aggregationData[cellIndex + 3]++;
7,628×
413
    }
414
  }
415

416
  /* eslint-disable max-depth, complexity */
417
  calculateMeanMaxMinData(opts) {
418
    const {validCellIndices, results, weights} = opts;
30×
419

420
    // collect max/min values
421
    validCellIndices.forEach(cellIndex => {
30×
422
      for (const id in results) {
1,399×
423
        const {size, needMin, needMax, operation} = weights[id];
1,424×
424
        const {aggregationData, minData, maxData, maxMinData} = results[id];
1,424×
425
        const calculateMinMax = needMin || needMax;
1,424×
426
        const calculateMean = operation === AGGREGATION_OPERATION.MEAN;
1,424×
427
        const combineMaxMin = needMin && needMax && weights[id].combineMaxMin;
1,424×
428
        const count = aggregationData[cellIndex + ELEMENTCOUNT - 1];
1,424×
429
        for (
1,424×
430
          let sizeIndex = 0;
1,424×
431
          sizeIndex < size && (calculateMinMax || calculateMean);
432
          sizeIndex++
433
        ) {
434
          const cellElementIndex = cellIndex + sizeIndex;
56×
435
          let weight = aggregationData[cellElementIndex];
56×
436
          if (calculateMean) {
Branches [[46, 0]] missed. 56×
UNCOV
437
            aggregationData[cellElementIndex] /= count;
!
UNCOV
438
            weight = aggregationData[cellElementIndex];
!
439
          }
440
          if (combineMaxMin) {
56×
441
            // use RGB for max values for 3 weights.
442
            maxMinData[sizeIndex] = Math.max(maxMinData[sizeIndex], weight);
50×
443
          } else {
444
            if (needMin) {
Branches [[48, 0]] missed. 6×
UNCOV
445
              minData[sizeIndex] = Math.min(minData[sizeIndex], weight);
!
446
            }
447
            if (needMax) {
Branches [[49, 1]] missed. 6×
448
              maxData[sizeIndex] = Math.max(maxData[sizeIndex], weight);
6×
449
            }
450
          }
451
        }
452
        // update total aggregation values.
453
        if (combineMaxMin) {
1,424×
454
          // Use Alpha channel to store total min value for weight#0
455
          maxMinData[ELEMENTCOUNT - 1] = Math.min(
50×
456
            maxMinData[ELEMENTCOUNT - 1],
457
            aggregationData[cellIndex + 0]
458
          );
459
        } else {
460
          // Use Alpha channel to store total counts.
461
          if (needMin) {
Branches [[51, 0]] missed. 1,374×
UNCOV
462
            minData[ELEMENTCOUNT - 1] += count;
!
463
          }
464
          if (needMax) {
1,374×
465
            maxData[ELEMENTCOUNT - 1] += count;
6×
466
          }
467
        }
468
      }
469
    });
470
  }
471
  /* eslint-enable max-depth */
472

473
  initCPUResults(opts) {
474
    const weights = opts.weights || this.state.weights;
30×
475
    const {numCol, numRow} = this.state;
30×
476
    const results = {};
30×
477
    // setup results object
478
    for (const id in weights) {
30×
479
      let {aggregationData, minData, maxData, maxMinData} = weights[id];
40×
480
      const {needMin, needMax} = weights[id];
40×
481
      const combineMaxMin = needMin && needMax && weights[id].combineMaxMin;
40×
482

483
      const aggregationSize = numCol * numRow * ELEMENTCOUNT;
40×
484
      aggregationData = getFloatArray(aggregationData, aggregationSize);
40×
485
      if (combineMaxMin) {
40×
486
        maxMinData = getFloatArray(maxMinData, ELEMENTCOUNT);
20×
487
        // RGB for max value
488
        maxMinData.fill(-Infinity, 0, ELEMENTCOUNT - 1);
20×
489
        // Alpha for min value
490
        maxMinData[ELEMENTCOUNT - 1] = Infinity;
20×
491
      } else {
492
        // RGB for min/max values
493
        // Alpha for total count
494
        if (needMin) {
Branches [[56, 0]] missed. 20×
UNCOV
495
          minData = getFloatArray(minData, ELEMENTCOUNT, Infinity);
!
496
          minData[ELEMENTCOUNT - 1] = 0;
!
497
        }
498
        if (needMax) {
20×
499
          maxData = getFloatArray(maxData, ELEMENTCOUNT, -Infinity);
12×
500
          maxData[ELEMENTCOUNT - 1] = 0;
12×
501
        }
502
      }
503
      results[id] = Object.assign({}, weights[id], {
40×
504
        aggregationData,
505
        minData,
506
        maxData,
507
        maxMinData
508
      });
509
    }
510
    return results;
30×
511
  }
512

513
  /* eslint-disable max-statements */
514
  runAggregationOnCPU(opts) {
515
    const {positions, cellSize, gridTransformMatrix, viewport, projectPoints} = opts;
30×
516
    let {weights} = opts;
30×
517
    const {numCol, numRow} = this.state;
30×
518
    const results = this.initCPUResults(opts);
30×
519
    // screen space or world space projection required
520
    const gridTransformRequired = this.shouldTransformToGrid(opts);
30×
521
    let gridPositions;
522
    const pos = [0, 0, 0];
30×
523

524
    log.assert(gridTransformRequired || opts.changeFlags.cellSizeChanged);
30×
525

526
    let posCount;
527
    if (gridTransformRequired) {
30×
528
      posCount = positions.length / 2;
24×
529
      gridPositions = new Float64Array(positions.length);
24×
530
      this.setState({gridPositions});
24×
531
    } else {
532
      gridPositions = this.state.gridPositions;
6×
533
      weights = this.state.weights;
6×
534
      posCount = gridPositions.length / 2;
6×
535
    }
536

537
    const validCellIndices = new Set();
30×
538
    for (let posIndex = 0; posIndex < posCount; posIndex++) {
30×
539
      let x;
540
      let y;
541
      if (gridTransformRequired) {
22,737×
542
        pos[0] = positions[posIndex * 2];
15,168×
543
        pos[1] = positions[posIndex * 2 + 1];
15,168×
544
        if (projectPoints) {
15,168×
545
          [x, y] = viewport.project(pos);
10,092×
546
        } else {
547
          [x, y] = worldToPixels(pos, gridTransformMatrix);
5,076×
548
        }
549
        gridPositions[posIndex * 2] = x;
15,168×
550
        gridPositions[posIndex * 2 + 1] = y;
15,168×
551
      } else {
552
        x = gridPositions[posIndex * 2];
7,569×
553
        y = gridPositions[posIndex * 2 + 1];
7,569×
554
      }
555

556
      const colId = Math.floor(x / cellSize[0]);
22,737×
557
      const rowId = Math.floor(y / cellSize[1]);
22,737×
558
      if (colId >= 0 && colId < numCol && rowId >= 0 && rowId < numRow) {
22,737×
559
        const cellIndex = (colId + rowId * numCol) * ELEMENTCOUNT;
7,603×
560
        validCellIndices.add(cellIndex);
7,603×
561
        this.calculateAggregationData({weights, results, cellIndex, posIndex});
7,603×
562
      }
563
    }
564

565
    this.calculateMeanMaxMinData({validCellIndices, results, weights});
30×
566

567
    // Update buffer objects.
568
    this.updateAggregationBuffers(opts, results);
30×
569

570
    this.setState({results});
30×
571
    return results;
30×
572
  }
573
  /* eslint-disable max-statements */
574

575
  updateCPUResultBuffer({gl, bufferName, id, data, result}) {
576
    const {resources} = this.state;
72×
577
    const resourceName = `cpu-result-${id}-${bufferName}`;
72×
578
    result[bufferName] = result[bufferName] || resources[resourceName];
72×
579
    if (result[bufferName]) {
72×
580
      result[bufferName].setData({data});
57×
581
    } else {
582
      // save resource for garbage collection
583
      resources[resourceName] = new Buffer(gl, data);
15×
584
      result[bufferName] = resources[resourceName];
15×
585
    }
586
  }
587

588
  updateAggregationBuffers(opts, results) {
589
    if (!opts.createBufferObjects) {
Branches [[66, 0]] missed. 30×
UNCOV
590
      return;
!
591
    }
592
    const weights = opts.weights || this.state.weights;
30×
593
    for (const id in results) {
30×
594
      const {aggregationData, minData, maxData, maxMinData} = results[id];
40×
595
      const {needMin, needMax} = weights[id];
40×
596
      const combineMaxMin = needMin && needMax && weights[id].combineMaxMin;
40×
597
      this.updateCPUResultBuffer({
40×
598
        gl: this.gl,
599
        bufferName: 'aggregationBuffer',
600
        id,
601
        data: aggregationData,
602
        result: results[id]
603
      });
604
      if (combineMaxMin) {
40×
605
        this.updateCPUResultBuffer({
20×
606
          gl: this.gl,
607
          bufferName: 'maxMinBuffer',
608
          id,
609
          data: maxMinData,
610
          result: results[id]
611
        });
612
      } else {
613
        if (needMin) {
Branches [[70, 0]] missed. 20×
UNCOV
614
          this.updateCPUResultBuffer({
!
615
            gl: this.gl,
616
            bufferName: 'minBuffer',
617
            id,
618
            data: minData,
619
            result: results[id]
620
          });
621
        }
622
        if (needMax) {
20×
623
          this.updateCPUResultBuffer({
12×
624
            gl: this.gl,
625
            bufferName: 'maxBuffer',
626
            id,
627
            data: maxData,
628
            result: results[id]
629
          });
630
        }
631
      }
632
    }
633
  }
634

635
  // GPU Aggregation methods
636

637
  getAggregateData(opts) {
UNCOV
638
    const results = {};
!
639
    const {
640
      textures,
641
      framebuffers,
642
      maxMinFramebuffers,
643
      minFramebuffers,
644
      maxFramebuffers,
645
      weights
UNCOV
646
    } = this.state;
!
647

UNCOV
648
    for (const id in weights) {
!
UNCOV
649
      results[id] = {};
!
UNCOV
650
      const {needMin, needMax, combineMaxMin} = weights[id];
!
UNCOV
651
      results[id].aggregationTexture = textures[id];
!
UNCOV
652
      results[id].aggregationBuffer = readPixelsToBuffer(framebuffers[id], {
!
653
        target: weights[id].aggregationBuffer, // update if a buffer is provided
654
        sourceType: GL.FLOAT
655
      });
UNCOV
656
      if (needMin && needMax && combineMaxMin) {
Branches [[72, 0], [72, 1], [73, 0], [73, 1], [73, 2]] missed. !
UNCOV
657
        results[id].maxMinBuffer = readPixelsToBuffer(maxMinFramebuffers[id], {
!
658
          target: weights[id].maxMinBuffer, // update if a buffer is provided
659
          sourceType: GL.FLOAT
660
        });
661
      } else {
UNCOV
662
        if (needMin) {
Branches [[74, 0], [74, 1]] missed. !
UNCOV
663
          results[id].minBuffer = readPixelsToBuffer(minFramebuffers[id], {
!
664
            target: weights[id].minBuffer, // update if a buffer is provided
665
            sourceType: GL.FLOAT
666
          });
667
        }
UNCOV
668
        if (needMax) {
Branches [[75, 0], [75, 1]] missed. !
UNCOV
669
          results[id].maxBuffer = readPixelsToBuffer(maxFramebuffers[id], {
!
670
            target: weights[id].maxBuffer, // update if a buffer is provided
671
            sourceType: GL.FLOAT
672
          });
673
        }
674
      }
675
    }
UNCOV
676
    this.trackGPUResultBuffers(results, weights);
!
UNCOV
677
    return results;
!
678
  }
679

680
  getAggregationModel(fp64 = false) {
Branches [[76, 0]] missed.
UNCOV
681
    const {gl, shaderCache} = this;
!
UNCOV
682
    return new Model(gl, {
!
683
      id: 'Gird-Aggregation-Model',
684
      vs: fp64 ? AGGREGATE_TO_GRID_VS_FP64 : AGGREGATE_TO_GRID_VS,
Branches [[77, 0], [77, 1]] missed.
685
      fs: AGGREGATE_TO_GRID_FS,
686
      modules: fp64 ? [project64] : ['project32'],
Branches [[78, 0], [78, 1]] missed.
687
      shaderCache,
688
      vertexCount: 0,
689
      drawMode: GL.POINTS
690
    });
691
  }
692

693
  getAllAggregationModel() {
UNCOV
694
    const {gl, shaderCache} = this;
!
UNCOV
695
    const {numCol, numRow} = this.state;
!
UNCOV
696
    return new Model(gl, {
!
697
      id: 'All-Aggregation-Model',
698
      vs: AGGREGATE_ALL_VS_FP64,
699
      fs: AGGREGATE_ALL_FS,
700
      modules: [fp64ShaderModule],
701
      shaderCache,
702
      vertexCount: 1,
703
      drawMode: GL.POINTS,
704
      isInstanced: true,
705
      instanceCount: numCol * numRow,
706
      attributes: {
707
        position: [0, 0]
708
      }
709
    });
710
  }
711

712
  getMeanTransform(opts) {
UNCOV
713
    if (this.meanTransform) {
Branches [[79, 0], [79, 1]] missed. !
UNCOV
714
      this.meanTransform.update(opts);
!
715
    } else {
UNCOV
716
      this.meanTransform = new Transform(
!
717
        this.gl,
718
        Object.assign(
719
          {},
720
          {
721
            vs: TRANSFORM_MEAN_VS,
722
            _targetTextureVarying: 'meanValues'
723
          },
724
          opts
725
        )
726
      );
727
    }
UNCOV
728
    return this.meanTransform;
!
729
  }
730

731
  renderAggregateData(opts) {
UNCOV
732
    const {cellSize, viewport, gridTransformMatrix, projectPoints} = opts;
!
733
    const {
734
      numCol,
735
      numRow,
736
      windowSize,
737
      maxMinFramebuffers,
738
      minFramebuffers,
739
      maxFramebuffers,
740
      weights
UNCOV
741
    } = this.state;
!
742

UNCOV
743
    const uProjectionMatrixFP64 = fp64ifyMatrix4(gridTransformMatrix);
!
UNCOV
744
    const gridSize = [numCol, numRow];
!
UNCOV
745
    const parameters = {
!
746
      blend: true,
747
      depthTest: false,
748
      blendFunc: [GL.ONE, GL.ONE]
749
    };
UNCOV
750
    const moduleSettings = {viewport};
!
UNCOV
751
    const uniforms = {
!
752
      windowSize,
753
      cellSize,
754
      gridSize,
755
      uProjectionMatrix: gridTransformMatrix,
756
      uProjectionMatrixFP64,
757
      projectPoints
758
    };
759

UNCOV
760
    for (const id in weights) {
!
UNCOV
761
      const {needMin, needMax} = weights[id];
!
UNCOV
762
      const combineMaxMin = needMin && needMax && weights[id].combineMaxMin;
Branches [[80, 0], [80, 1], [80, 2]] missed. !
UNCOV
763
      this.renderToWeightsTexture({id, parameters, moduleSettings, uniforms, gridSize});
!
UNCOV
764
      if (combineMaxMin) {
Branches [[81, 0], [81, 1]] missed. !
UNCOV
765
        this.renderToMaxMinTexture({
!
766
          id,
767
          parameters: Object.assign({}, parameters, {blendEquation: MAX_MIN_BLEND_EQUATION}),
768
          gridSize,
769
          minOrMaxFb: maxMinFramebuffers[id],
770
          clearParams: {clearColor: [0, 0, 0, MAX_32_BIT_FLOAT]},
771
          combineMaxMin
772
        });
773
      } else {
774
        if (needMin) {
Branches [[82, 0], [82, 1]] missed. !
UNCOV
775
          this.renderToMaxMinTexture({
!
776
            id,
777
            parameters: Object.assign({}, parameters, {blendEquation: MIN_BLEND_EQUATION}),
778
            gridSize,
779
            minOrMaxFb: minFramebuffers[id],
780
            clearParams: {clearColor: [MAX_32_BIT_FLOAT, MAX_32_BIT_FLOAT, MAX_32_BIT_FLOAT, 0]},
781
            combineMaxMin
782
          });
783
        }
UNCOV
784
        if (needMax) {
Branches [[83, 0], [83, 1]] missed. !
UNCOV
785
          this.renderToMaxMinTexture({
!
786
            id,
787
            parameters: Object.assign({}, parameters, {blendEquation: MAX_BLEND_EQUATION}),
788
            gridSize,
789
            minOrMaxFb: maxFramebuffers[id],
790
            combineMaxMin
791
          });
792
        }
793
      }
794
    }
795
  }
796

797
  // render all aggregated grid-cells to generate Min, Max or MaxMin data texture
798
  renderToMaxMinTexture(opts) {
UNCOV
799
    const {id, parameters, gridSize, minOrMaxFb, combineMaxMin, clearParams = {}} = opts;
Branches [[84, 0]] missed. !
UNCOV
800
    const {framebuffers} = this.state;
!
UNCOV
801
    const {gl, allAggregationModel} = this;
!
802

UNCOV
803
    minOrMaxFb.bind();
!
UNCOV
804
    gl.viewport(0, 0, gridSize[0], gridSize[1]);
!
UNCOV
805
    withParameters(gl, clearParams, () => {
!
806
      gl.clear(gl.COLOR_BUFFER_BIT);
!
807
    });
UNCOV
808
    allAggregationModel.draw({
!
809
      parameters,
810
      uniforms: {
811
        uSampler: framebuffers[id].texture,
812
        gridSize,
813
        combineMaxMin
814
      }
815
    });
UNCOV
816
    minOrMaxFb.unbind();
!
817
  }
818

819
  // render all data points to aggregate weights
820
  renderToWeightsTexture(opts) {
821
    const {id, parameters, moduleSettings, uniforms, gridSize} = opts;
!
822
    const {framebuffers, equations, weightAttributes, weights} = this.state;
!
823
    const {gl, gridAggregationModel} = this;
!
824
    const {operation} = weights[id];
!
825

826
    framebuffers[id].bind();
!
827
    gl.viewport(0, 0, gridSize[0], gridSize[1]);
!
828
    const clearColor =
829
      operation === AGGREGATION_OPERATION.MIN
Branches [[85, 0], [85, 1]] missed. !
830
        ? [MAX_32_BIT_FLOAT, MAX_32_BIT_FLOAT, MAX_32_BIT_FLOAT, 0]
831
        : [0, 0, 0, 0];
832
    withParameters(gl, {clearColor}, () => {
!
833
      gl.clear(gl.COLOR_BUFFER_BIT);
!
834
    });
835

836
    const attributes = {weights: weightAttributes[id]};
!
837
    gridAggregationModel.draw({
!
838
      parameters: Object.assign({}, parameters, {blendEquation: equations[id]}),
839
      moduleSettings,
840
      uniforms,
841
      attributes
842
    });
843
    framebuffers[id].unbind();
!
844

845
    if (operation === AGGREGATION_OPERATION.MEAN) {
Branches [[86, 0], [86, 1]] missed. !
846
      const {meanTextures, textures} = this.state;
!
847
      const transformOptions = {
!
848
        _sourceTextures: {aggregationValues: meanTextures[id]}, // contains aggregated data
849
        _targetTexture: textures[id], // store mean values,
850
        elementCount: textures[id].width * textures[id].height
851
      };
852
      const meanTransform = this.getMeanTransform(transformOptions);
!
853
      meanTransform.run({
!
854
        parameters: {
855
          blend: false,
856
          depthTest: false
857
        }
858
      });
859

860
      // update framebuffer with mean results so readPixelsToBuffer returns mean values
861
      framebuffers[id].attach({[GL.COLOR_ATTACHMENT0]: textures[id]});
!
862
    }
863
  }
864

865
  runAggregationOnGPU(opts) {
866
    this.updateModels(opts);
!
867
    this.setupFramebuffers(opts);
!
868
    this.renderAggregateData(opts);
!
869
    const results = this.getAggregateData(opts);
!
870
    this.setState({results});
!
871
    return results;
!
872
  }
873

874
  // set up framebuffer for each weight
875
  /* eslint-disable complexity, max-depth */
876
  setupFramebuffers(opts) {
877
    const {
878
      numCol,
879
      numRow,
880
      textures,
881
      framebuffers,
882
      maxMinFramebuffers,
883
      minFramebuffers,
884
      maxFramebuffers,
885
      resources,
886
      meanTextures,
887
      equations,
888
      weights
889
    } = this.state;
!
890
    const framebufferSize = {width: numCol, height: numRow};
!
891
    for (const id in weights) {
!
892
      const {needMin, needMax, combineMaxMin, operation} = weights[id];
!
893
      textures[id] =
!
894
        weights[id].aggregationTexture ||
Branches [[87, 0], [87, 1], [87, 2]] missed.
895
        textures[id] ||
896
        getFloatTexture(this.gl, {id: `${id}-texture`, width: numCol, height: numRow});
897
      textures[id].resize(framebufferSize);
!
898
      let texture = textures[id];
!
899
      if (operation === AGGREGATION_OPERATION.MEAN) {
Branches [[88, 0], [88, 1]] missed. !
900
        // For MEAN, we first aggregatet into a temp texture
901
        meanTextures[id] =
!
902
          meanTextures[id] ||
Branches [[89, 0], [89, 1]] missed.
903
          getFloatTexture(this.gl, {id: `${id}-mean-texture`, width: numCol, height: numRow});
904
        meanTextures[id].resize(framebufferSize);
!
905
        texture = meanTextures[id];
!
906
      }
907
      if (framebuffers[id]) {
Branches [[90, 0], [90, 1]] missed. !
908
        framebuffers[id].attach({[GL.COLOR_ATTACHMENT0]: texture});
!
909
      } else {
910
        framebuffers[id] = getFramebuffer(this.gl, {
!
911
          id: `${id}-fb`,
912
          width: numCol,
913
          height: numRow,
914
          texture
915
        });
916
      }
917
      framebuffers[id].resize(framebufferSize);
!
918
      equations[id] = EQUATION_MAP[operation];
!
919
      // For min/max framebuffers will use default size 1X1
920
      if (needMin || needMax) {
Branches [[91, 0], [91, 1], [92, 0], [92, 1]] missed. !
921
        if (needMin && needMax && combineMaxMin) {
Branches [[93, 0], [93, 1], [94, 0], [94, 1], [94, 2]] missed. !
922
          if (!maxMinFramebuffers[id]) {
Branches [[95, 0], [95, 1]] missed. !
923
            resources[`${id}-maxMin`] = getFloatTexture(this.gl, {id: `${id}-maxMinTex`});
!
924
            maxMinFramebuffers[id] = getFramebuffer(this.gl, {
!
925
              id: `${id}-maxMinFb`,
926
              texture: resources[`${id}-maxMin`]
927
            });
928
          }
929
        } else {
930
          if (needMin) {
Branches [[96, 0], [96, 1]] missed. !
931
            if (!minFramebuffers[id]) {
Branches [[97, 0], [97, 1]] missed. !
932
              resources[`${id}-min`] = getFloatTexture(this.gl, {id: `${id}-minTex`});
!
933
              minFramebuffers[id] = getFramebuffer(this.gl, {
!
934
                id: `${id}-minFb`,
935
                texture: resources[`${id}-min`]
936
              });
937
            }
938
          }
939
          if (needMax) {
Branches [[98, 0], [98, 1]] missed. !
940
            if (!maxFramebuffers[id]) {
Branches [[99, 0], [99, 1]] missed. !
941
              resources[`${id}-max`] = getFloatTexture(this.gl, {id: `${id}-maxTex`});
!
942
              maxFramebuffers[id] = getFramebuffer(this.gl, {
!
943
                id: `${id}-maxFb`,
944
                texture: resources[`${id}-max`]
945
              });
946
            }
947
          }
948
        }
949
      }
950
    }
951
  }
952
  /* eslint-enable complexity, max-depth */
953

954
  setupModels(fp64 = false) {
Branches [[100, 0]] missed.
955
    if (this.gridAggregationModel) {
Branches [[101, 0], [101, 1]] missed. !
956
      this.gridAggregationModel.delete();
!
957
    }
958
    this.gridAggregationModel = this.getAggregationModel(fp64);
!
959
    if (!this.allAggregationModel) {
Branches [[102, 0], [102, 1]] missed. !
960
      // Model doesn't have to change when fp64 flag changes
961
      this.allAggregationModel = this.getAllAggregationModel();
!
962
    }
963
  }
964

965
  // set up buffers for all weights
966
  setupWeightAttributes(opts) {
967
    const {weightAttributes, vertexCount, weights, resources} = this.state;
!
968
    for (const id in weights) {
!
969
      const {values} = weights[id];
!
970
      // values can be Array, Float32Array or Buffer
971
      if (Array.isArray(values) || values.constructor === Float32Array) {
Branches [[103, 0], [103, 1], [104, 0], [104, 1]] missed. !
972
        log.assert(values.length / 3 === vertexCount);
!
973
        const typedArray = Array.isArray(values) ? new Float32Array(values) : values;
Branches [[105, 0], [105, 1]] missed. !
974
        if (weightAttributes[id] instanceof Buffer) {
Branches [[106, 0], [106, 1]] missed. !
975
          weightAttributes[id].setData(typedArray);
!
976
        } else {
977
          resources[`${id}-buffer`] = new Buffer(this.gl, typedArray);
!
978
          weightAttributes[id] = resources[`${id}-buffer`];
!
979
        }
980
      } else {
981
        // log.assert((values instanceof Attribute) || (values instanceof Buffer));
982
        log.assert(values instanceof Buffer);
!
983
        weightAttributes[id] = values;
!
984
      }
985
    }
986
  }
987

988
  // GPU Aggregation results are provided in Buffers, if new Buffer objects are created track them for later deletion.
989
  /* eslint-disable max-depth */
990
  trackGPUResultBuffers(results, weights) {
991
    const {resources} = this.state;
!
992
    for (const id in results) {
!
993
      if (results[id]) {
Branches [[107, 0], [107, 1]] missed. !
994
        for (const bufferName of BUFFER_NAMES) {
!
995
          if (results[id][bufferName] && weights[id][bufferName] !== results[id][bufferName]) {
Branches [[108, 0], [108, 1], [109, 0], [109, 1]] missed. !
996
            // No result buffer is provided in weights object, `readPixelsToBuffer` has created a new Buffer object
997
            // collect the new buffer for garabge collection
998
            const name = `gpu-result-${id}-${bufferName}`;
!
999
            if (resources[name]) {
Branches [[110, 0], [110, 1]] missed. !
1000
              resources[name].delete();
!
1001
            }
1002
            resources[name] = results[id][bufferName];
!
1003
          }
1004
        }
1005
      }
1006
    }
1007
  }
1008
  /* eslint-enable max-depth */
1009

1010
  /* eslint-disable max-statements */
1011
  updateModels(opts) {
1012
    const {gl} = this;
!
1013
    const {positions, positions64xyLow, changeFlags} = opts;
!
1014
    const {numCol, numRow} = this.state;
!
1015
    const aggregationModelAttributes = {};
!
1016
    let modelDirty = false;
!
1017

1018
    if (opts.fp64 !== this.state.fp64) {
Branches [[111, 0], [111, 1]] missed. !
1019
      this.setupModels(opts.fp64);
!
1020
      this.setState({fp64: opts.fp64});
!
1021
      modelDirty = true;
!
1022
    }
1023

1024
    if (changeFlags.dataChanged || !this.state.positionsBuffer) {
Branches [[112, 0], [112, 1], [113, 0], [113, 1]] missed. !
1025
      let {positionsBuffer, positions64xyLowBuffer} = this.state;
!
1026
      if (positionsBuffer) {
Branches [[114, 0], [114, 1]] missed. !
1027
        positionsBuffer.delete();
!
1028
      }
1029
      if (positions64xyLowBuffer) {
Branches [[115, 0], [115, 1]] missed. !
1030
        positions64xyLowBuffer.delete();
!
1031
      }
1032
      const vertexCount = positions.length / 2;
!
1033
      positionsBuffer = new Buffer(gl, new Float32Array(positions));
!
1034
      positions64xyLowBuffer = new Buffer(gl, {
!
1035
        data: new Float32Array(positions64xyLow),
1036
        accessor: {size: 2}
1037
      });
1038
      this.setState({positionsBuffer, positions64xyLowBuffer, vertexCount});
!
1039

1040
      this.setupWeightAttributes(opts);
!
1041
      modelDirty = true;
!
1042
    }
1043

1044
    if (modelDirty) {
Branches [[116, 0], [116, 1]] missed. !
1045
      const {vertexCount, positionsBuffer, positions64xyLowBuffer} = this.state;
!
1046
      aggregationModelAttributes.positions = positionsBuffer;
!
1047
      if (opts.fp64) {
Branches [[117, 0], [117, 1]] missed. !
1048
        aggregationModelAttributes.positions64xyLow = positions64xyLowBuffer;
!
1049
      }
1050
      this.gridAggregationModel.setVertexCount(vertexCount);
!
1051
      this.gridAggregationModel.setAttributes(aggregationModelAttributes);
!
1052
    }
1053

1054
    if (changeFlags.cellSizeChanged || changeFlags.viewportChanged) {
Branches [[118, 0], [118, 1], [119, 0], [119, 1]] missed. !
1055
      this.allAggregationModel.setInstanceCount(numCol * numRow);
!
1056
    }
1057
  }
1058
  /* eslint-enable max-statements */
1059
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC