From 94670a6f30fe0f2a9fe4c79459484a8957374bc0 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Sun, 1 Sep 2024 23:14:07 -0700 Subject: [PATCH 01/10] ScreenGridLayer --- .../screen-grid-cell-layer.ts | 24 +++++++++---------- .../screen-grid-layer-fragment.glsl.ts | 6 +---- .../screen-grid-layer-vertex.glsl.ts | 20 ++++++++++------ .../screen-grid-layer/screen-grid-layer.ts | 15 ++++++++++-- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-cell-layer.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-cell-layer.ts index dc77bfe7ef9..1fee6ecef0e 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-cell-layer.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-cell-layer.ts @@ -21,22 +21,18 @@ import {Texture} from '@luma.gl/core'; import {Model, Geometry} from '@luma.gl/engine'; import {Layer, picking, UpdateParameters, DefaultProps, Color} from '@deck.gl/core'; -import {defaultColorRange, createColorRangeTexture} from '../common/utils/color-utils'; +import {createColorRangeTexture, updateColorRangeTexture} from '../common/utils/color-utils'; import vs from './screen-grid-layer-vertex.glsl'; import fs from './screen-grid-layer-fragment.glsl'; import {ScreenGridProps, screenGridUniforms} from './screen-grid-layer-uniforms'; import {ShaderModule} from '@luma.gl/shadertools'; - -const defaultProps: DefaultProps<_ScreenGridCellLayerProps> = { - cellSizePixels: {type: 'number', value: 100, min: 1}, - cellMarginPixels: {type: 'number', value: 2, min: 0}, - colorRange: defaultColorRange -}; +import type {ScaleType} from '../common/types'; /** Proprties added by ScreenGridCellLayer. */ export type _ScreenGridCellLayerProps = { - cellSizePixels?: number; - cellMarginPixels?: number; + cellSizePixels: number; + cellMarginPixels: number; + colorScaleType: ScaleType; colorDomain: () => [number, number]; colorRange?: Color[]; }; @@ -45,7 +41,6 @@ export default class ScreenGridCellLayer extends La ExtraPropsT & Required<_ScreenGridCellLayerProps> > { static layerName = 'ScreenGridCellLayer'; - static defaultProps = defaultProps; state!: { model?: Model; @@ -81,9 +76,15 @@ export default class ScreenGridCellLayer extends La if (oldProps.colorRange !== props.colorRange) { this.state.colorTexture?.destroy(); - this.state.colorTexture = createColorRangeTexture(this.context.device, props.colorRange); + this.state.colorTexture = createColorRangeTexture( + this.context.device, + props.colorRange, + props.colorScaleType + ); const screenGridProps: Partial = {colorRange: this.state.colorTexture}; model.shaderInputs.setProps({screenGrid: screenGridProps}); + } else if (oldProps.colorScaleType !== props.colorScaleType) { + updateColorRangeTexture(this.state.colorTexture, props.colorScaleType); } if ( @@ -110,7 +111,6 @@ export default class ScreenGridCellLayer extends La } draw({uniforms}) { - // If colorDomain not specified we use dynamic domain from the aggregator const colorDomain = this.props.colorDomain(); const model = this.state.model!; diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-fragment.glsl.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-fragment.glsl.ts index 96826af59c2..39ee6e4e2dc 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-fragment.glsl.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-fragment.glsl.ts @@ -19,21 +19,17 @@ // THE SOFTWARE. /* fragment shader for the grid-layer */ -export default `\ +export default /* glsl */ `\ #version 300 es #define SHADER_NAME screen-grid-layer-fragment-shader precision highp float; in vec4 vColor; -flat in int vIsValid; out vec4 fragColor; void main(void) { - if (vIsValid == 0) { - discard; - } fragColor = vColor; DECKGL_FILTER_COLOR(fragColor, geometry); diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts index f3a387976f4..a246b1fa216 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -export default `\ +export default /* glsl */ `\ #version 300 es #define SHADER_NAME screen-grid-layer-vertex-shader #define RANGE_COUNT 6 @@ -31,20 +31,26 @@ in vec3 instancePickingColors; uniform sampler2D colorRange; out vec4 vColor; -flat out int vIsValid; + +vec4 interp(float value, vec2 domain, sampler2D range) { + float r = (value - domain.x) / (domain.y - domain.x); + return texture(range, vec2(r, 0.5)); +} void main(void) { + if (isnan(instanceWeights)) { + gl_Position = vec4(0.); + return; + } + vec2 pos = instancePositions * screenGrid.gridSizeClipspace + positions * screenGrid.cellSizeClipspace; pos.x = pos.x - 1.0; pos.y = 1.0 - pos.y; gl_Position = vec4(pos, 0., 1.); - vIsValid = isnan(instanceWeights) ? 0 : 1; - float r = min(max((instanceWeights - screenGrid.colorDomain.x) / (screenGrid.colorDomain.y - screenGrid.colorDomain.x), 0.), 1.); - vec4 rangeColor = texture(colorRange, vec2(r, 0.5)); - - vColor = vec4(rangeColor.rgb, rangeColor.a * layer.opacity); + vColor = interp(instanceWeights, screenGrid.colorDomain, colorRange); + vColor.a *= opacity; // Set color to be rendered to picking fbo (also used to check for selection highlight). picking_setPickingColor(instancePickingColors); diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts index fb2b49977b3..52ab8fbc1ce 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts @@ -35,13 +35,17 @@ import {WebGLAggregator, CPUAggregator, AggregationOperation} from '../common/ag import AggregationLayer from '../common/aggregation-layer'; import ScreenGridCellLayer from './screen-grid-cell-layer'; import {BinOptions, binOptionsUniforms} from './bin-options-uniforms'; +import {defaultColorRange} from '../common/utils/color-utils'; const defaultProps: DefaultProps = { - ...(ScreenGridCellLayer.defaultProps as DefaultProps), + cellSizePixels: {type: 'number', value: 100, min: 1}, + cellMarginPixels: {type: 'number', value: 2, min: 0}, + colorRange: defaultColorRange, + colorScaleType: 'quantize', getPosition: {type: 'accessor', value: (d: any) => d.position}, getWeight: {type: 'accessor', value: 1}, - gpuAggregation: false, // TODO(v9): Re-enable GPU aggregation. + gpuAggregation: false, aggregation: 'SUM' }; @@ -76,6 +80,13 @@ export type _ScreenGridLayerProps = { */ colorRange?: Color[]; + /** + * Scaling function used to determine the color of the grid cell. + * Supported Values are 'quantize', 'linear', 'quantile' and 'ordinal'. + * @default 'quantize' + */ + colorScaleType?: 'linear' | 'quantize'; + /** * Method called to retrieve the position of each object. * From 009a251dfa152d03ced2b4ee858a7ec55156bf98 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Sun, 1 Sep 2024 23:33:19 -0700 Subject: [PATCH 02/10] extract common code to util --- .../src/common/utils/bounds-utils.ts | 30 +++++++++++++++++++ .../src/contour-layer/contour-layer.ts | 28 ++++++++--------- .../src/grid-layer/grid-layer.ts | 28 ++++++++--------- .../src/hexagon-layer/hexagon-layer.ts | 30 +++++++------------ .../screen-grid-layer/screen-grid-layer.ts | 4 ++- 5 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 modules/aggregation-layers/src/common/utils/bounds-utils.ts diff --git a/modules/aggregation-layers/src/common/utils/bounds-utils.ts b/modules/aggregation-layers/src/common/utils/bounds-utils.ts new file mode 100644 index 00000000000..92d2673269a --- /dev/null +++ b/modules/aggregation-layers/src/common/utils/bounds-utils.ts @@ -0,0 +1,30 @@ +/** Utility to estimate binIdRange as expected by AggregatorProps */ +export function getBinIdRange({ + dataBounds, + getBinId, + padding = 0 +}: { + /** Bounds of the input data */ + dataBounds: [min: number[], max: number[]]; + /** Given a data point, returns the bin id that it belongs to */ + getBinId: (p: number[]) => number[]; + /** Add a border around the result to avoid clipping */ + padding?: number; +}): [number, number][] { + const corners = [ + dataBounds[0], + dataBounds[1], + [dataBounds[0][0], dataBounds[1][1]], + [dataBounds[1][0], dataBounds[0][1]] + ].map(p => getBinId(p)); + + const minX = Math.min(...corners.map(p => p[0])) - padding; + const minY = Math.min(...corners.map(p => p[1])) - padding; + const maxX = Math.max(...corners.map(p => p[0])) + padding + 1; + const maxY = Math.max(...corners.map(p => p[1])) + padding + 1; + + return [ + [minX, maxX], + [minY, maxY] + ]; +} diff --git a/modules/aggregation-layers/src/contour-layer/contour-layer.ts b/modules/aggregation-layers/src/contour-layer/contour-layer.ts index ce3b3ff454f..08c817de928 100644 --- a/modules/aggregation-layers/src/contour-layer/contour-layer.ts +++ b/modules/aggregation-layers/src/contour-layer/contour-layer.ts @@ -21,6 +21,7 @@ import AggregationLayer from '../common/aggregation-layer'; import {AggregationLayerProps} from '../common/aggregation-layer'; import {generateContours, Contour, ContourLine, ContourPolygon} from './contour-utils'; import {getAggregatorValueReader} from './value-reader'; +import {getBinIdRange} from '../common/utils/bounds-utils'; import {Matrix4} from '@math.gl/core'; import {BinOptions, binOptionsUniforms} from './bin-options-uniforms'; @@ -233,7 +234,7 @@ export default class GridLayer extends const bounds = this.getBounds(); const cellSizeCommon: [number, number] = [1, 1]; let cellOriginCommon: [number, number] = [0, 0]; - const binIdRange: [number, number][] = [ + let binIdRange: [number, number][] = [ [0, 1], [0, 1] ]; @@ -268,21 +269,16 @@ export default class GridLayer extends // Round to the nearest 32-bit float to match CPU and GPU results cellOriginCommon = [Math.fround(viewport.center[0]), Math.fround(viewport.center[1])]; - const corners = [ - bounds[0], - bounds[1], - [bounds[0][0], bounds[1][1]], - [bounds[1][0], bounds[0][1]] - ].map(p => viewport.projectFlat(p)); - - const minX = Math.min(...corners.map(p => p[0])); - const minY = Math.min(...corners.map(p => p[1])); - const maxX = Math.max(...corners.map(p => p[0])); - const maxY = Math.max(...corners.map(p => p[1])); - binIdRange[0][0] = Math.floor((minX - cellOriginCommon[0]) / cellSizeCommon[0]); - binIdRange[0][1] = Math.floor((maxX - cellOriginCommon[0]) / cellSizeCommon[0]) + 1; - binIdRange[1][0] = Math.floor((minY - cellOriginCommon[1]) / cellSizeCommon[1]); - binIdRange[1][1] = Math.floor((maxY - cellOriginCommon[1]) / cellSizeCommon[1]) + 1; + binIdRange = getBinIdRange({ + dataBounds: bounds, + getBinId: (p: number[]) => { + const positionCommon = viewport.projectFlat(p); + return [ + (positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0], + (positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1] + ]; + } + }); } this.setState({cellSizeCommon, cellOriginCommon, binIdRange, aggregatorViewport: viewport}); diff --git a/modules/aggregation-layers/src/grid-layer/grid-layer.ts b/modules/aggregation-layers/src/grid-layer/grid-layer.ts index b5049b7d9f3..da85fd1d987 100644 --- a/modules/aggregation-layers/src/grid-layer/grid-layer.ts +++ b/modules/aggregation-layers/src/grid-layer/grid-layer.ts @@ -24,6 +24,7 @@ import AggregationLayer from '../common/aggregation-layer'; import {AggregateAccessor} from '../common/types'; import {defaultColorRange} from '../common/utils/color-utils'; import {AttributeWithScale} from '../common/utils/scale-utils'; +import {getBinIdRange} from '../common/utils/bounds-utils'; import {GridCellLayer} from './grid-cell-layer'; import {BinOptions, binOptionsUniforms} from './bin-options-uniforms'; @@ -438,7 +439,7 @@ export default class GridLayer extends const bounds = this.getBounds(); const cellSizeCommon: [number, number] = [1, 1]; let cellOriginCommon: [number, number] = [0, 0]; - const binIdRange: [number, number][] = [ + let binIdRange: [number, number][] = [ [0, 1], [0, 1] ]; @@ -471,21 +472,16 @@ export default class GridLayer extends // Round to the nearest 32-bit float to match CPU and GPU results cellOriginCommon = [Math.fround(viewport.center[0]), Math.fround(viewport.center[1])]; - const corners = [ - bounds[0], - bounds[1], - [bounds[0][0], bounds[1][1]], - [bounds[1][0], bounds[0][1]] - ].map(p => viewport.projectFlat(p)); - - const minX = Math.min(...corners.map(p => p[0])); - const minY = Math.min(...corners.map(p => p[1])); - const maxX = Math.max(...corners.map(p => p[0])); - const maxY = Math.max(...corners.map(p => p[1])); - binIdRange[0][0] = Math.floor((minX - cellOriginCommon[0]) / cellSizeCommon[0]); - binIdRange[0][1] = Math.floor((maxX - cellOriginCommon[0]) / cellSizeCommon[0]) + 1; - binIdRange[1][0] = Math.floor((minY - cellOriginCommon[1]) / cellSizeCommon[1]); - binIdRange[1][1] = Math.floor((maxY - cellOriginCommon[1]) / cellSizeCommon[1]) + 1; + binIdRange = getBinIdRange({ + dataBounds: bounds, + getBinId: (p: number[]) => { + const positionCommon = viewport.projectFlat(p); + return [ + (positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0], + (positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1] + ]; + } + }); } this.setState({cellSizeCommon, cellOriginCommon, binIdRange, aggregatorViewport: viewport}); diff --git a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts index 640900243ba..19cc95bace2 100644 --- a/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts +++ b/modules/aggregation-layers/src/hexagon-layer/hexagon-layer.ts @@ -24,6 +24,7 @@ import AggregationLayer from '../common/aggregation-layer'; import {AggregateAccessor} from '../common/types'; import {defaultColorRange} from '../common/utils/color-utils'; import {AttributeWithScale} from '../common/utils/scale-utils'; +import {getBinIdRange} from '../common/utils/bounds-utils'; import HexagonCellLayer from './hexagon-cell-layer'; import {pointToHexbin, HexbinVertices, getHexbinCentroid, pointToHexbinGLSL} from './hexbin'; @@ -443,7 +444,7 @@ export default class HexagonLayer< const bounds = this.getBounds(); let radiusCommon = 1; let hexOriginCommon: [number, number] = [0, 0]; - const binIdRange: [number, number][] = [ + let binIdRange: [number, number][] = [ [0, 1], [0, 1] ]; @@ -470,25 +471,16 @@ export default class HexagonLayer< hexOriginCommon = [Math.fround(viewport.center[0]), Math.fround(viewport.center[1])]; - const corners = [ - bounds[0], - bounds[1], - [bounds[0][0], bounds[1][1]], - [bounds[1][0], bounds[0][1]] - ].map(p => { - const positionCommon = viewport.projectFlat(p); - positionCommon[0] -= hexOriginCommon[0]; - positionCommon[1] -= hexOriginCommon[1]; - return pointToHexbin(positionCommon, radiusCommon); + binIdRange = getBinIdRange({ + dataBounds: bounds, + getBinId: (p: number[]) => { + const positionCommon = viewport.projectFlat(p); + positionCommon[0] -= hexOriginCommon[0]; + positionCommon[1] -= hexOriginCommon[1]; + return pointToHexbin(positionCommon, radiusCommon); + }, + padding: 1 }); - - const minX = Math.min(...corners.map(p => p[0])); - const minY = Math.min(...corners.map(p => p[1])); - const maxX = Math.max(...corners.map(p => p[0])); - const maxY = Math.max(...corners.map(p => p[1])); - - binIdRange[0] = [minX - 1, maxX + 2]; // i range - binIdRange[1] = [minY - 1, maxY + 2]; // j range } this.setState({radiusCommon, hexOriginCommon, binIdRange, aggregatorViewport: viewport}); diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts index 52ab8fbc1ce..bd66bac648b 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts @@ -141,7 +141,9 @@ export default class ScreenGridLayer< static defaultProps = defaultProps; getAggregatorType(): string { - return this.props.gpuAggregation ? 'gpu' : 'cpu'; + return this.props.gpuAggregation && WebGLAggregator.isSupported(this.context.device) + ? 'gpu' + : 'cpu'; } createAggregator(type: string): WebGLAggregator | CPUAggregator { From 32c7d61acb001199f8720e4f3dff0fb6fbeb5b55 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Wed, 4 Sep 2024 10:51:58 -0700 Subject: [PATCH 03/10] merge error --- .../src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts index a246b1fa216..7039749c827 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer-vertex.glsl.ts @@ -50,7 +50,7 @@ void main(void) { gl_Position = vec4(pos, 0., 1.); vColor = interp(instanceWeights, screenGrid.colorDomain, colorRange); - vColor.a *= opacity; + vColor.a *= layer.opacity; // Set color to be rendered to picking fbo (also used to check for selection highlight). picking_setPickingColor(instancePickingColors); From 3121f0322b3d4afa2d8bf3fba4c6ed6e6e16c8cb Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Tue, 3 Sep 2024 00:49:59 -0700 Subject: [PATCH 04/10] Aggregation layer test app --- test/apps/aggregator/app.ts | 32 +++++ test/apps/aggregator/histogram-layer.ts | 172 ++++++++++++++++++++++++ test/apps/aggregator/index.html | 20 +++ test/apps/aggregator/package.json | 12 ++ 4 files changed, 236 insertions(+) create mode 100644 test/apps/aggregator/app.ts create mode 100644 test/apps/aggregator/histogram-layer.ts create mode 100644 test/apps/aggregator/index.html create mode 100644 test/apps/aggregator/package.json diff --git a/test/apps/aggregator/app.ts b/test/apps/aggregator/app.ts new file mode 100644 index 00000000000..97b928a85a9 --- /dev/null +++ b/test/apps/aggregator/app.ts @@ -0,0 +1,32 @@ +import {Deck, OrthographicView} from '@deck.gl/core'; +import {HistogramLayer} from './histogram-layer'; + +new Deck({ + views: new OrthographicView(), + initialViewState: { + target: [0, 0, 0], + zoom: 1 + }, + controller: true, + layers: [ + new HistogramLayer({ + data: generateData(10000, 0, 100), + getPosition: d => d, + gpuAggregation: true, + binSize: 1, + heightScale: 1 + }) + ] +}); + +function generateData(count: number, mean: number, stdev: number) { + const result: number[] = []; + for (let i = 0; i < count; i++) { + // Gaussian random + const u = 1 - Math.random(); + const v = Math.random(); + const z = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v ); + result.push(z * stdev + mean); + } + return result; +} diff --git a/test/apps/aggregator/histogram-layer.ts b/test/apps/aggregator/histogram-layer.ts new file mode 100644 index 00000000000..91c7818ddf3 --- /dev/null +++ b/test/apps/aggregator/histogram-layer.ts @@ -0,0 +1,172 @@ +import { + CPUAggregator, + WebGLAggregator, + AggregationOperation, + _AggregationLayer +} from '@deck.gl/aggregation-layers'; +import {GridCellLayer} from '@deck.gl/layers'; +import type { + Accessor, + Color, + DefaultProps, + UpdateParameters +} from '@deck.gl/core'; +import {Matrix4} from '@math.gl/core'; + +export type HistogramLayerProps = { + data: DataT[]; + getPosition?: Accessor; + getWeight?: Accessor; + binSize?: number; + aggregation?: AggregationOperation; + gpuAggregation?: boolean; + fillColor?: Color; + heightScale?: number; +}; + +const defaultProps: DefaultProps = { + getPosition: {type: 'accessor', value: (d: any) => d.bin}, + getWeight: {type: 'accessor', value: 1}, + binSize: 1, + aggregation: 'SUM', + gpuAggregation: false, + fillColor: {type: 'color', value: [0, 0, 0, 255]}, + heightScale: {type: 'number', min: 0, value: 1} +}; + +export class HistogramLayer extends _AggregationLayer>> { + static layerName = 'HistogramLayer'; + static defaultProps = defaultProps; + + getAggregatorType(): string { + return this.props.gpuAggregation && WebGLAggregator.isSupported(this.context.device) ? 'gpu' : 'cpu'; + } + + createAggregator(type: string) { + if (type === 'gpu') { + return new WebGLAggregator(this.context.device, { + dimensions: 1, + channelCount: 1, + bufferLayout: this.getAttributeManager()!.getBufferLayouts({isInstanced: false}), + vs: ` + uniform float binSize; + in float position; + in float weight; + + void getBin(out int binId) { + binId = int(floor(position / binSize)); + } + void getValue(out float value) { + value = weight; + } + ` + }); + } + return new CPUAggregator({ + dimensions: 1, + getBin: { + sources: ['position'], + getValue: (data: {position: number}, index: number, options: {binSize: number}) => [Math.floor(data.position / options.binSize)] + }, + getValue: [ + { + sources: ['weight'], + getValue: (data: {weight: number}) => data.weight + } + ] + }); + } + + initializeState() { + this.getAttributeManager()!.add({ + position: { + type: 'float32', + size: 1, + accessor: 'getPosition' + }, + weight: { + type: 'float32', + size: 1, + accessor: 'getWeight' + } + }); + } + + updateState(params: UpdateParameters) { + const aggregatorChanged = super.updateState(params); + + const {changeFlags, props, oldProps} = params; + if ( + aggregatorChanged || + changeFlags.dataChanged || + props.binSize !== oldProps.binSize + ) { + const {aggregator} = this.state; + + aggregator.setProps({ + // @ts-expect-error only used by GPUAggregator + binIdRange: this._getBinIdRange(), + pointCount: props.data.length, + operations: [props.aggregation], + binOptions: { + binSize: props.binSize + } + }); + } + return aggregatorChanged; + } + + onAttributeChange(id: string): void { + const {aggregator} = this.state; + switch (id) { + case 'position': + aggregator.setNeedsUpdate(); + aggregator.setProps({ + // @ts-expect-error only used by GPUAggregator + binIdRange: this._getBinIdRange(), + binOptions: { + binSize: this.props.binSize + } + }); + break; + + case 'weight': + aggregator.setNeedsUpdate(0); + break; + } + } + + _getBinIdRange() { + const bounds = this.getAttributeManager()?.getBounds(['position']) as [number[], number[]]; + if (!bounds) { + return [[0, 1]]; + } + const {binSize} = this.props; + return [[Math.floor(bounds[0][0] / binSize), Math.floor(bounds[1][0] / binSize) + 1]]; + } + + renderLayers() { + const {aggregator} = this.state; + const {heightScale, fillColor} = this.props; + const binAttribute = aggregator.getBins(); + const valueAttribute = aggregator.getResult(0); + + // Hack: provide hint to Attribute class to disable fp64 + binAttribute.value = binAttribute.value || new Float32Array(0); + + return new GridCellLayer({ + data: { + length: aggregator.binCount, + attributes: { + getPosition: binAttribute, + getElevation: valueAttribute + } + }, + modelMatrix: new Matrix4().rotateX(Math.PI / 2), + cellSize: 0.9, + extruded: true, + elevationScale: heightScale, + getFillColor: fillColor + }) + } +} diff --git a/test/apps/aggregator/index.html b/test/apps/aggregator/index.html new file mode 100644 index 00000000000..5a9b4d2561e --- /dev/null +++ b/test/apps/aggregator/index.html @@ -0,0 +1,20 @@ + + + + + deck.gl aggregator usage example + + + + + + + diff --git a/test/apps/aggregator/package.json b/test/apps/aggregator/package.json new file mode 100644 index 00000000000..a60b423e3d8 --- /dev/null +++ b/test/apps/aggregator/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "start": "vite --open", + "start-local": "vite --config ../vite.config.local.mjs" + }, + "dependencies": { + "deck.gl": "^9.0.0" + }, + "devDependencies": { + "vite": "^4.0.0" + } +} From 194e3523fcf2183d1d10907653ba1954253c7ded Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Wed, 4 Sep 2024 17:04:59 -0700 Subject: [PATCH 05/10] lint --- test/apps/aggregator/app.ts | 2 +- test/apps/aggregator/histogram-layer.ts | 28 ++++++++++++------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/test/apps/aggregator/app.ts b/test/apps/aggregator/app.ts index 97b928a85a9..4cc6d93ee4e 100644 --- a/test/apps/aggregator/app.ts +++ b/test/apps/aggregator/app.ts @@ -25,7 +25,7 @@ function generateData(count: number, mean: number, stdev: number) { // Gaussian random const u = 1 - Math.random(); const v = Math.random(); - const z = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v ); + const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); result.push(z * stdev + mean); } return result; diff --git a/test/apps/aggregator/histogram-layer.ts b/test/apps/aggregator/histogram-layer.ts index 91c7818ddf3..6978b359cad 100644 --- a/test/apps/aggregator/histogram-layer.ts +++ b/test/apps/aggregator/histogram-layer.ts @@ -5,12 +5,7 @@ import { _AggregationLayer } from '@deck.gl/aggregation-layers'; import {GridCellLayer} from '@deck.gl/layers'; -import type { - Accessor, - Color, - DefaultProps, - UpdateParameters -} from '@deck.gl/core'; +import type {Accessor, Color, DefaultProps, UpdateParameters} from '@deck.gl/core'; import {Matrix4} from '@math.gl/core'; export type HistogramLayerProps = { @@ -34,12 +29,17 @@ const defaultProps: DefaultProps = { heightScale: {type: 'number', min: 0, value: 1} }; -export class HistogramLayer extends _AggregationLayer>> { +export class HistogramLayer extends _AggregationLayer< + DataT, + Required> +> { static layerName = 'HistogramLayer'; static defaultProps = defaultProps; getAggregatorType(): string { - return this.props.gpuAggregation && WebGLAggregator.isSupported(this.context.device) ? 'gpu' : 'cpu'; + return this.props.gpuAggregation && WebGLAggregator.isSupported(this.context.device) + ? 'gpu' + : 'cpu'; } createAggregator(type: string) { @@ -66,7 +66,9 @@ export class HistogramLayer extends _AggregationLayer [Math.floor(data.position / options.binSize)] + getValue: (data: {position: number}, index: number, options: {binSize: number}) => [ + Math.floor(data.position / options.binSize) + ] }, getValue: [ { @@ -96,11 +98,7 @@ export class HistogramLayer extends _AggregationLayer extends _AggregationLayer Date: Wed, 4 Sep 2024 18:12:09 -0700 Subject: [PATCH 06/10] bug fix --- modules/aggregation-layers/src/contour-layer/contour-layer.ts | 4 ++-- modules/aggregation-layers/src/grid-layer/grid-layer.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/aggregation-layers/src/contour-layer/contour-layer.ts b/modules/aggregation-layers/src/contour-layer/contour-layer.ts index 08c817de928..e6a0de31d57 100644 --- a/modules/aggregation-layers/src/contour-layer/contour-layer.ts +++ b/modules/aggregation-layers/src/contour-layer/contour-layer.ts @@ -274,8 +274,8 @@ export default class GridLayer extends getBinId: (p: number[]) => { const positionCommon = viewport.projectFlat(p); return [ - (positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0], - (positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1] + Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]), + Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1]) ]; } }); diff --git a/modules/aggregation-layers/src/grid-layer/grid-layer.ts b/modules/aggregation-layers/src/grid-layer/grid-layer.ts index da85fd1d987..9bd822021c2 100644 --- a/modules/aggregation-layers/src/grid-layer/grid-layer.ts +++ b/modules/aggregation-layers/src/grid-layer/grid-layer.ts @@ -477,8 +477,8 @@ export default class GridLayer extends getBinId: (p: number[]) => { const positionCommon = viewport.projectFlat(p); return [ - (positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0], - (positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1] + Math.floor((positionCommon[0] - cellOriginCommon[0]) / cellSizeCommon[0]), + Math.floor((positionCommon[1] - cellOriginCommon[1]) / cellSizeCommon[1]) ]; } }); From 010b21c60941b2ecfbdfd5f24c4e8653b2d2a4d6 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Wed, 4 Sep 2024 18:28:14 -0700 Subject: [PATCH 07/10] fix test --- test/modules/aggregation-layers/screengrid-cell-layer.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/modules/aggregation-layers/screengrid-cell-layer.spec.ts b/test/modules/aggregation-layers/screengrid-cell-layer.spec.ts index 0b1df5c999a..9ed8d6bafd0 100644 --- a/test/modules/aggregation-layers/screengrid-cell-layer.spec.ts +++ b/test/modules/aggregation-layers/screengrid-cell-layer.spec.ts @@ -41,6 +41,8 @@ test('ScreenGridCellLayer#constructor', t => { instancePositions: SAMPLE_BUFFER } }, + cellSizePixels: 100, + cellMarginPixels: 2, numInstances: 1, colorDomain: () => [0, 1] } From bb51aacc95b3f6f9d80021702cd7f786409f4d02 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Wed, 4 Sep 2024 18:46:50 -0700 Subject: [PATCH 08/10] render test --- .../src/screen-grid-layer/screen-grid-layer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts index bd66bac648b..8db21411e13 100644 --- a/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts +++ b/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.ts @@ -41,7 +41,7 @@ const defaultProps: DefaultProps = { cellSizePixels: {type: 'number', value: 100, min: 1}, cellMarginPixels: {type: 'number', value: 2, min: 0}, colorRange: defaultColorRange, - colorScaleType: 'quantize', + colorScaleType: 'linear', getPosition: {type: 'accessor', value: (d: any) => d.position}, getWeight: {type: 'accessor', value: 1}, From 88bde1bd16246de36fd4bd94575218f032fad4fc Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Wed, 4 Sep 2024 18:55:00 -0700 Subject: [PATCH 09/10] update docs --- docs/api-reference/aggregation-layers/screen-grid-layer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-reference/aggregation-layers/screen-grid-layer.md b/docs/api-reference/aggregation-layers/screen-grid-layer.md index ded50266958..bdd6809b27e 100644 --- a/docs/api-reference/aggregation-layers/screen-grid-layer.md +++ b/docs/api-reference/aggregation-layers/screen-grid-layer.md @@ -229,7 +229,7 @@ Note that setting this prop does not affect how points are binned. #### `colorScaleType` (string, optional) {#colorscaletype} -* Default: `'quantize'` +* Default: `'linear'` The color scale converts from a continuous numeric stretch (`colorDomain`) into a list of colors (`colorRange`). Cells with value of `colorDomain[0]` will be rendered with the color of `colorRange[0]`, and cells with value of `colorDomain[1]` will be rendered with the color of `colorRange[colorRange.length - 1]`. From 01ff5c5663c663b170996ef29f5b9d96802ac18f Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Sat, 7 Sep 2024 23:16:37 -0700 Subject: [PATCH 10/10] Use UBO for binOptions --- test/apps/aggregator/app.ts | 28 +++++++++++++++---------- test/apps/aggregator/histogram-layer.ts | 26 +++++++++++++++++++---- test/apps/aggregator/index.html | 9 ++++++++ 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/test/apps/aggregator/app.ts b/test/apps/aggregator/app.ts index 4cc6d93ee4e..773b622a0ab 100644 --- a/test/apps/aggregator/app.ts +++ b/test/apps/aggregator/app.ts @@ -1,24 +1,30 @@ import {Deck, OrthographicView} from '@deck.gl/core'; import {HistogramLayer} from './histogram-layer'; -new Deck({ +const deckgl = new Deck({ views: new OrthographicView(), initialViewState: { target: [0, 0, 0], zoom: 1 }, - controller: true, - layers: [ - new HistogramLayer({ - data: generateData(10000, 0, 100), - getPosition: d => d, - gpuAggregation: true, - binSize: 1, - heightScale: 1 - }) - ] + controller: true }); +const slider = document.getElementById('bin-size-slider') as HTMLInputElement; +slider.oninput = updateLayer; +updateLayer(); + +function updateLayer() { + const layer = new HistogramLayer({ + data: generateData(10000, 0, 100), + getPosition: d => d, + gpuAggregation: true, + binSize: Number(slider.value), + heightScale: 1 + }); + deckgl.setProps({layers: [layer]}); +} + function generateData(count: number, mean: number, stdev: number) { const result: number[] = []; for (let i = 0; i < count; i++) { diff --git a/test/apps/aggregator/histogram-layer.ts b/test/apps/aggregator/histogram-layer.ts index 6978b359cad..24adbb987d7 100644 --- a/test/apps/aggregator/histogram-layer.ts +++ b/test/apps/aggregator/histogram-layer.ts @@ -7,6 +7,7 @@ import { import {GridCellLayer} from '@deck.gl/layers'; import type {Accessor, Color, DefaultProps, UpdateParameters} from '@deck.gl/core'; import {Matrix4} from '@math.gl/core'; +import type {ShaderModule} from '@luma.gl/shadertools'; export type HistogramLayerProps = { data: DataT[]; @@ -29,6 +30,22 @@ const defaultProps: DefaultProps = { heightScale: {type: 'number', min: 0, value: 1} }; +type BinOptions = { + binSize: number; +}; + +const binOptionsUniforms = { + name: 'binOptions', + vs: /* glsl */ `\ + uniform binOptionsUniforms { + float binSize; + } binOptions; + `, + uniformTypes: { + binSize: 'f32' + } +} as const satisfies ShaderModule; + export class HistogramLayer extends _AggregationLayer< DataT, Required> @@ -48,13 +65,14 @@ export class HistogramLayer extends _AggregationLayer< dimensions: 1, channelCount: 1, bufferLayout: this.getAttributeManager()!.getBufferLayouts({isInstanced: false}), + modules: [binOptionsUniforms], vs: ` uniform float binSize; in float position; in float weight; void getBin(out int binId) { - binId = int(floor(position / binSize)); + binId = int(floor(position / binOptions.binSize)); } void getValue(out float value) { value = weight; @@ -145,7 +163,7 @@ export class HistogramLayer extends _AggregationLayer< renderLayers() { const {aggregator} = this.state; - const {heightScale, fillColor} = this.props; + const {heightScale, fillColor, binSize} = this.props; const binAttribute = aggregator.getBins(); const valueAttribute = aggregator.getResult(0); @@ -160,8 +178,8 @@ export class HistogramLayer extends _AggregationLayer< getElevation: valueAttribute } }, - modelMatrix: new Matrix4().rotateX(Math.PI / 2), - cellSize: 0.9, + modelMatrix: new Matrix4().rotateX(Math.PI / 2).scale([binSize, 1, 1]), + cellSize: 0.9 * binSize, extruded: true, elevationScale: heightScale, getFillColor: fillColor diff --git a/test/apps/aggregator/index.html b/test/apps/aggregator/index.html index 5a9b4d2561e..d9c406434d8 100644 --- a/test/apps/aggregator/index.html +++ b/test/apps/aggregator/index.html @@ -11,10 +11,19 @@ height: 100vh; overflow: hidden; } + #controls { + position: fixed; + top: 10px; + left: 10px; + z-index: 1; + } +
+ +