diff --git a/modules/aggregation-layers/src/gpu-grid-layer/gpu-grid-cell-layer.js b/modules/aggregation-layers/src/gpu-grid-layer/gpu-grid-cell-layer.js index ee42c270845..cb4380065b7 100644 --- a/modules/aggregation-layers/src/gpu-grid-layer/gpu-grid-cell-layer.js +++ b/modules/aggregation-layers/src/gpu-grid-layer/gpu-grid-cell-layer.js @@ -23,7 +23,7 @@ import GL from '@luma.gl/constants'; import {Model, CubeGeometry, fp64, PhongMaterial} from '@luma.gl/core'; const {fp64LowPart} = fp64; const defaultMaterial = new PhongMaterial(); -import {defaultColorRange} from '../utils/color-utils'; +import {defaultColorRange, colorRangeToFlatArray} from '../utils/color-utils'; import vs from './gpu-grid-cell-layer-vertex.glsl'; import fs from './gpu-grid-cell-layer-fragment.glsl'; @@ -102,19 +102,19 @@ export default class GPUGridCellLayer extends Layer { gridSize, gridOrigin, gridOffset, - colorRange, elevationRange } = this.props; const gridOriginLow = [fp64LowPart(gridOrigin[0]), fp64LowPart(gridOrigin[1])]; const gridOffsetLow = [fp64LowPart(gridOffset[0]), fp64LowPart(gridOffset[1])]; - const colorMaxMinBuffer = data.color.maxMinBuffer; - const elevationMaxMinBuffer = data.elevation.maxMinBuffer; - - colorMaxMinBuffer.bind({target: GL.UNIFORM_BUFFER, index: COLOR_DATA_UBO_INDEX}); - elevationMaxMinBuffer.bind({target: GL.UNIFORM_BUFFER, index: ELEVATION_DATA_UBO_INDEX}); const domainUniforms = this.getDomainUniforms(); + const uniformBuffers = { + colorMaxMinBuffer: data.color.maxMinBuffer, + elevationMaxMinBuffer: data.elevation.maxMinBuffer + }; + const colorRange = colorRangeToFlatArray(this.props.colorRange, Float32Array, 255); + this.bindUniformBuffers(uniformBuffers); this.state.model .setUniforms( Object.assign({}, uniforms, domainUniforms, { @@ -133,6 +133,15 @@ export default class GPUGridCellLayer extends Layer { }) ) .draw(); + this.unbindUniformBuffers(uniformBuffers); + } + + bindUniformBuffers({colorMaxMinBuffer, elevationMaxMinBuffer}) { + colorMaxMinBuffer.bind({target: GL.UNIFORM_BUFFER, index: COLOR_DATA_UBO_INDEX}); + elevationMaxMinBuffer.bind({target: GL.UNIFORM_BUFFER, index: ELEVATION_DATA_UBO_INDEX}); + } + + unbindUniformBuffers({colorMaxMinBuffer, elevationMaxMinBuffer}) { colorMaxMinBuffer.unbind({target: GL.UNIFORM_BUFFER, index: COLOR_DATA_UBO_INDEX}); elevationMaxMinBuffer.unbind({target: GL.UNIFORM_BUFFER, index: ELEVATION_DATA_UBO_INDEX}); } diff --git a/modules/aggregation-layers/src/utils/color-utils.js b/modules/aggregation-layers/src/utils/color-utils.js index a82dce55526..09f7e7941c5 100644 --- a/modules/aggregation-layers/src/utils/color-utils.js +++ b/modules/aggregation-layers/src/utils/color-utils.js @@ -29,6 +29,9 @@ export const defaultColorRange = [ // Converts a colorRange array to a flat array with 4 components per color export function colorRangeToFlatArray(colorRange, ArrayType, defaultValue) { + if (colorRange.constructor === ArrayType) { + return colorRange; + } const flatArray = new ArrayType(colorRange.length * 4); colorRange.forEach((color, index) => { const flatArrayIdnex = index * 4; diff --git a/package.json b/package.json index 4f049cfad8f..3c5f693dd8f 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "start": "open http://uber.github.io/deck.gl/#/documentation/getting-started/installation?section=running-the-examples", "test": "ocular-test", "test-fast": "ocular-test fast", + "test-node": "ocular-test node", "test-browser": "ocular-test browser", "bench": "ocular-test bench", "bench-browser": "ocular-test bench-browser", diff --git a/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-cell-layer.spec.js b/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-cell-layer.spec.js new file mode 100644 index 00000000000..473b06952b1 --- /dev/null +++ b/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-cell-layer.spec.js @@ -0,0 +1,82 @@ +// Copyright (c) 2015 - 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import test from 'tape-catch'; +import {Buffer} from '@luma.gl/core'; +import {testLayer, testInitializeLayer} from '@deck.gl/test-utils'; +import GPUGridCellLayer from '@deck.gl/aggregation-layers/gpu-grid-layer/gpu-grid-cell-layer'; +import {setupSpysForWebGL1, restoreSpies} from './webgl1-spies-utils'; +import {gl} from '@deck.gl/test-utils'; +const SAMPLE_BUFFER = new Buffer(gl); +const SAMPLE_PROPS = { + data: { + color: { + aggregationBuffer: SAMPLE_BUFFER, + maxMinBuffer: SAMPLE_BUFFER + }, + elevation: { + aggregationBuffer: SAMPLE_BUFFER, + maxMinBuffer: SAMPLE_BUFFER + } + } +}; + +test('GPUGridCellLayer#initializeState', t => { + const webgl1Spies = setupSpysForWebGL1(gl); + const layer = new GPUGridCellLayer(SAMPLE_PROPS); + + testInitializeLayer({layer, onError: t.notOk}); + + t.ok(layer.state.model, 'should set state.model'); + + restoreSpies(webgl1Spies); + t.end(); +}); + +test('GPUGridCellLayer#updates', t => { + const webgl1Spies = setupSpysForWebGL1(gl); + + testLayer({ + Layer: GPUGridCellLayer, + onError: t.notOk, + testCases: [ + { + props: SAMPLE_PROPS, + onAfterUpdate({layer}) { + t.ok(layer.state.model, 'should set state.model'); + } + }, + { + updateProps: { + cellSize: 123 + }, + onAfterUpdate({layer, spies}) { + t.ok( + layer.state.model.program.uniforms.cellSize === 123, + 'cellSize unifrom should get updated' + ); + } + } + ] + }); + + restoreSpies(webgl1Spies); + t.end(); +}); diff --git a/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-layer.spec.js b/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-layer.spec.js index 3296fad293e..f6c85e55ae1 100644 --- a/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-layer.spec.js +++ b/test/modules/aggregation-layers/gpu-grid-layer/gpu-grid-layer.spec.js @@ -22,8 +22,9 @@ import test from 'tape-catch'; import * as FIXTURES from 'deck.gl-test/data'; import {testLayer, generateLayerTests, testInitializeLayer} from '@deck.gl/test-utils'; import {makeSpy} from '@probe.gl/test-utils'; -import {GPUGridLayer, _GPUGridAggregator as GPUGridAggregator} from '@deck.gl/aggregation-layers'; +import {GPUGridLayer} from '@deck.gl/aggregation-layers'; import GPUGridCellLayer from '@deck.gl/aggregation-layers/gpu-grid-layer/gpu-grid-cell-layer'; +import {setupSpysForWebGL1, restoreSpies} from './webgl1-spies-utils'; import {gl} from '@deck.gl/test-utils'; const SAMPLE_PROPS = { @@ -32,11 +33,7 @@ const SAMPLE_PROPS = { }; test('GPUGridLayer', t => { - if (!GPUGridAggregator.isSupported(gl)) { - t.comment('GPUGridLayer not supported, skipping'); - t.end(); - return; - } + const webgl1Spies = setupSpysForWebGL1(gl); const testCases = generateLayerTests({ Layer: GPUGridLayer, sampleProps: SAMPLE_PROPS, @@ -49,15 +46,13 @@ test('GPUGridLayer', t => { testLayer({Layer: GPUGridLayer, testCases, onError: t.notOk}); + restoreSpies(webgl1Spies); t.end(); }); test('GPUGridLayer#renderLayers', t => { - if (!GPUGridAggregator.isSupported(gl)) { - t.comment('GPUGridLayer not supported, skipping'); - t.end(); - return; - } + const webgl1Spies = setupSpysForWebGL1(gl); + makeSpy(GPUGridLayer.prototype, 'getAggregationFlags'); makeSpy(GPUGridLayer.prototype, 'getLayerData'); @@ -76,15 +71,12 @@ test('GPUGridLayer#renderLayers', t => { GPUGridLayer.prototype.getAggregationFlags.restore(); GPUGridLayer.prototype.getLayerData.restore(); + restoreSpies(webgl1Spies); t.end(); }); test('GPUGridLayer#updates', t => { - if (!GPUGridAggregator.isSupported(gl)) { - t.comment('GPUGridLayer not supported, skipping'); - t.end(); - return; - } + const webgl1Spies = setupSpysForWebGL1(gl); testLayer({ Layer: GPUGridLayer, onError: t.notOk, @@ -155,5 +147,6 @@ test('GPUGridLayer#updates', t => { ] }); + restoreSpies(webgl1Spies); t.end(); }); diff --git a/test/modules/aggregation-layers/gpu-grid-layer/webgl1-spies-utils.js b/test/modules/aggregation-layers/gpu-grid-layer/webgl1-spies-utils.js new file mode 100644 index 00000000000..6f5e51e6e98 --- /dev/null +++ b/test/modules/aggregation-layers/gpu-grid-layer/webgl1-spies-utils.js @@ -0,0 +1,67 @@ +// Copyright (c) 2015 - 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import {isWebGL2} from '@luma.gl/core'; +import {_GPUGridAggregator as GPUGridAggregator} from '@deck.gl/aggregation-layers'; +import GPUGridCellLayer from '@deck.gl/aggregation-layers/gpu-grid-layer/gpu-grid-cell-layer'; +import {makeSpy} from '@probe.gl/test-utils'; + +const VS = 'void main() {gl_Position = vec4(0);}'; +const FS = 'void main() {gl_FragColor = vec4(0);}'; + +// Spiy methods to avoid usage of WebGL2 API when running under Node. +export function setupSpysForWebGL1(gl) { + let getShadersSpy = null; + let setupUniformBufferSpy = null; + let bindUniformBuffersSpy = null; + let unbindUniformBuffersSpy = null; + let isSupportedSpy = null; + if (!isWebGL2(gl)) { + isSupportedSpy = makeSpy(GPUGridAggregator, 'isSupported'); + getShadersSpy = makeSpy(GPUGridCellLayer.prototype, 'getShaders'); + setupUniformBufferSpy = makeSpy(GPUGridCellLayer.prototype, '_setupUniformBuffer'); + bindUniformBuffersSpy = makeSpy(GPUGridCellLayer.prototype, 'bindUniformBuffers'); + unbindUniformBuffersSpy = makeSpy(GPUGridCellLayer.prototype, 'unbindUniformBuffers'); + getShadersSpy.returns({ + vs: VS, + fs: FS, + modules: ['project32', 'gouraud-lighting', 'picking', 'fp64'] + }); + isSupportedSpy.returns(true); + setupUniformBufferSpy.returns(null); + bindUniformBuffersSpy.returns(null); + unbindUniformBuffersSpy.returns(null); + } + return { + getShadersSpy, + setupUniformBufferSpy, + bindUniformBuffersSpy, + unbindUniformBuffersSpy, + isSupportedSpy + }; +} + +export function restoreSpies(spies) { + for (const name in spies) { + if (spies[name]) { + spies[name].restore(); + } + } +} diff --git a/test/modules/aggregation-layers/index.js b/test/modules/aggregation-layers/index.js index 8fa26ad1953..470b5df021a 100644 --- a/test/modules/aggregation-layers/index.js +++ b/test/modules/aggregation-layers/index.js @@ -22,6 +22,7 @@ import './contour-layer/contour-layer.spec'; import './contour-layer/marching-squares.spec'; import './gpu-grid-layer/gpu-grid-cell-layer-vertex.spec'; import './gpu-grid-layer/gpu-grid-layer.spec'; +import './gpu-grid-layer/gpu-grid-cell-layer.spec'; import './cpu-grid-layer/cpu-grid-layer.spec'; import './grid-aggregator.spec'; import './hexagon-layer.spec'; @@ -29,3 +30,4 @@ import './hexagon-aggregator.spec'; import './grid-layer.spec'; import './screen-grid-layer.spec'; import './utils/scale-utils.spec'; +import './utils/aggregation-operation-utils.spec'; diff --git a/test/modules/aggregation-layers/utils/aggregation-operation-utils.spec.js b/test/modules/aggregation-layers/utils/aggregation-operation-utils.spec.js new file mode 100644 index 00000000000..332a640f62a --- /dev/null +++ b/test/modules/aggregation-layers/utils/aggregation-operation-utils.spec.js @@ -0,0 +1,66 @@ +// Copyright (c) 2015 - 2019 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import test from 'tape-catch'; + +import {getValueFunc} from '@deck.gl/aggregation-layers/utils/aggregation-operation-utils'; + +const data = [10, 'a', null, 14, -3, 16, 0.2]; +const accessor = x => x; +const TEST_CASES = [ + { + name: 'Min Function', + op: 'min', + data, + expected: -3 + }, + { + name: 'Max Function', + op: 'Max', + data, + expected: 16 + }, + { + name: 'Sum Function', + op: 'sUM', + data, + expected: 37.2 + }, + { + name: 'Mean Function', + op: 'MEAN', + data, + expected: 37.2 / 5 + }, + { + name: 'Invalid(should default to SUM)', + op: 'Invalid', + data, + expected: 37.2 + } +]; + +test('GridAggregationOperationUtils#getValueFunc', t => { + TEST_CASES.forEach(tc => { + const func = getValueFunc(tc.op, accessor); + t.ok(func(data) === tc.expected, `${tc.name} should return expected result`); + }); + t.end(); +}); diff --git a/test/modules/aggregation-layers/utils/grid-aggregation-utils.spec.js b/test/modules/aggregation-layers/utils/grid-aggregation-utils.spec.js index 424b855f4f6..a0d070be2d8 100644 --- a/test/modules/aggregation-layers/utils/grid-aggregation-utils.spec.js +++ b/test/modules/aggregation-layers/utils/grid-aggregation-utils.spec.js @@ -42,7 +42,7 @@ test('GridAggregationUtils#pointToDensityGridData (CPU vs GPU)', t => { const opts = { data: FIXTURES.points, getPosition, - weightParams: {weight: {needMax: 1, getWeight: x => 1}}, + weightParams: {weight: {needMax: 1, needMin: 1, getWeight: x => 1}}, gpuGridAggregator, aggregationFlags: {dataChanged: true}, fp64: true // NOTE this test fails wihtout FP64 gpu aggregation. @@ -71,6 +71,15 @@ test('GridAggregationUtils#pointToDensityGridData (CPU vs GPU)', t => { gpuMaxCountData[0], `Max data should match for cellSizeMeters:${cellSizeMeters}` ); + + const cpuMinCountsData = cpuResults.weights.weight.maxBuffer.getData(); + const gpuMinCountData = gpuResults.weights.weight.maxBuffer.getData(); + t.deepEqual( + cpuMinCountsData[0], + gpuMinCountData[0], + `Max data should match for cellSizeMeters:${cellSizeMeters}` + ); + // TODO - This is failing in headless browser test. Might be related to // https://github.com/uber/deck.gl/issues/3156 // t.deepEqual(