Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GPU Aggregation (5/8): GridLayer #9096

Merged
merged 8 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export type AggregationProps = {
operations: AggregationOperation[];
/** Additional options to control bin sorting, e.g. bin size */
binOptions: Record<string, number | number[]>;
/** Callback after a channel is updated */
onUpdate?: (event: {channel: number}) => void;
};

/** Descriptor of an aggregated bin */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type {Bin} from './cpu-aggregator';
import type {AggregationOperation} from '../aggregator';

type AggregationFunc = (pointIndices: number[], getValue: (index: number) => number) => number;
/** A reducer function that takes a list of data points and outputs one measurement */
export type AggregationFunc = (
Pessimistress marked this conversation as resolved.
Show resolved Hide resolved
/** Indices of the points */
pointIndices: number[],
/** Accessor to the value for each point */
getValue: (index: number) => number
) => number;

const count: AggregationFunc = pointIndices => {
return pointIndices.length;
Expand Down Expand Up @@ -44,7 +50,7 @@ const max: AggregationFunc = (pointIndices, getValue) => {
return result;
};

const AGGREGATION_FUNC: Record<AggregationOperation, AggregationFunc> = {
export const BUILT_IN_OPERATIONS: Record<AggregationOperation, AggregationFunc> = {
COUNT: count,
SUM: sum,
MEAN: mean,
Expand All @@ -67,7 +73,7 @@ export function aggregate({
/** Given the index of a data point, returns its value */
getValue: (index: number) => number;
/** Method used to reduce a list of values to one number */
operation: AggregationOperation;
operation: AggregationFunc;
/** Array to write the output into */
target?: Float32Array | null;
}): {
Expand All @@ -80,11 +86,9 @@ export function aggregate({
let min = Infinity;
let max = -Infinity;

const aggregationFunc = AGGREGATION_FUNC[operation];

for (let j = 0; j < bins.length; j++) {
const {points} = bins[j];
target[j] = aggregationFunc(points, getValue);
target[j] = operation(points, getValue);
if (target[j] < min) min = target[j];
if (target[j] > max) max = target[j];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {Aggregator, AggregationProps, AggregatedBin} from '../aggregator';
import {_deepEqual as deepEqual, BinaryAttribute} from '@deck.gl/core';
import {sortBins, packBinIds} from './sort-bins';
import {aggregate} from './aggregate';
import {aggregate, AggregationFunc, BUILT_IN_OPERATIONS} from './aggregate';
import {VertexAccessor, evaluateVertexAccessor} from './vertex-accessor';

/** Options used to construct a new CPUAggregator */
Expand All @@ -19,7 +19,10 @@
} & Partial<CPUAggregationProps>;

/** Props used to run CPU aggregation, can be changed at any time */
type CPUAggregationProps = AggregationProps & {};
type CPUAggregationProps = AggregationProps & {
/** Custom callback to aggregate points, overrides the built-in operations */
customOperations: (AggregationFunc | null | undefined)[];
};

export type Bin = {
id: number[];
Expand Down Expand Up @@ -56,6 +59,7 @@
binOptions: {},
pointCount: 0,
operations: [],
customOperations: [],
attributes: {}
};
this.needsUpdate = true;
Expand All @@ -69,7 +73,7 @@
}

/** Update aggregation props */
setProps(props: Partial<CPUAggregationProps>) {

Check warning on line 76 in modules/aggregation-layers/src/aggregation-layer-v9/cpu-aggregator/cpu-aggregator.ts

View workflow job for this annotation

GitHub Actions / test-node

Method 'setProps' has a complexity of 12. Maximum allowed is 11
const oldProps = this.props;

if (props.binOptions) {
Expand All @@ -84,6 +88,15 @@
}
}
}
if (props.customOperations) {
for (let channel = 0; channel < this.channelCount; channel++) {
if (
Boolean(props.customOperations[channel]) !== Boolean(oldProps.customOperations[channel])
) {
this.setNeedsUpdate(channel);
}
}
}
if (props.pointCount !== undefined && props.pointCount !== oldProps.pointCount) {
this.setNeedsUpdate();
}
Expand Down Expand Up @@ -129,18 +142,22 @@
}
for (let channel = 0; channel < this.channelCount; channel++) {
if (this.needsUpdate === true || this.needsUpdate[channel]) {
const operation =
this.props.customOperations[channel] ||
BUILT_IN_OPERATIONS[this.props.operations[channel]];
const {value, domain} = aggregate({
bins: this.bins,
getValue: evaluateVertexAccessor(
this.props.getValue[channel],
this.props.attributes,
undefined
),
operation: this.props.operations[channel],
operation,
// Reuse allocated typed array
target: this.results[channel]?.value
});
this.results[channel] = {value, domain, type: 'float32', size: 1};
this.props.onUpdate?.({channel});
}
}
this.needsUpdate = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,12 @@ export class WebGLAggregator implements Aggregator {
// Read to buffer and calculate domain
this.aggregationTransform.update(this.binSorter.texture, operations);

this.needsUpdate.fill(false);
for (let i = 0; i < this.channelCount; i++) {
if (this.needsUpdate[i]) {
this.needsUpdate[i] = false;
this.props.onUpdate?.({channel: i});
}
}

// Uncomment to debug
// console.log('binsFBO', new Float32Array(this.device.readPixelsToArrayWebGL(this.binSorter.texture!).buffer));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

export default /* glsl */ `#version 300 es

#define SHADER_NAME grid-cell-layer-vertex-shader

in vec3 positions;
in vec3 normals;

in vec2 instancePositions;
in float instanceElevationValues;
in float instanceColorValues;
in vec3 instancePickingColors;

// Custom uniforms
uniform float opacity;
uniform bool extruded;
uniform float coverage;
uniform vec2 cellOriginCommon;
uniform vec2 cellSizeCommon;
uniform vec2 colorDomain;
uniform sampler2D colorRange;
uniform vec2 elevationDomain;
uniform vec2 elevationRange;

// Result
out vec4 vColor;

float interp(float value, vec2 domain, vec2 range) {
float r = min(max((value - domain.x) / (domain.y - domain.x), 0.), 1.);
return mix(range.x, range.y, r);
}

vec4 interp(float value, vec2 domain, sampler2D range) {
float r = min(max((value - domain.x) / (domain.y - domain.x), 0.), 1.);
return texture(range, vec2(r, 0.5));
}

void main(void) {
geometry.pickingColor = instancePickingColors;

if (isnan(instanceColorValues)) {
gl_Position = vec4(0.);
return;
}

vec2 commonPosition = (instancePositions + (positions.xy + 1.0) / 2.0 * coverage) * cellSizeCommon + cellOriginCommon - project.commonOrigin.xy;
geometry.position = vec4(commonPosition, 0.0, 1.0);
geometry.normal = project_normal(normals);

// calculate z, if 3d not enabled set to 0
float elevation = 0.0;
if (extruded) {
elevation = interp(instanceElevationValues, elevationDomain, elevationRange);
elevation = project_size(elevation);
// cylindar gemoetry height are between -1.0 to 1.0, transform it to between 0, 1
geometry.position.z = (positions.z + 1.0) / 2.0 * elevation;
}

gl_Position = project_common_position_to_clipspace(geometry.position);
DECKGL_FILTER_GL_POSITION(gl_Position, geometry);

vColor = interp(instanceColorValues, colorDomain, colorRange);
vColor.a *= opacity;
if (extruded) {
vColor.rgb = lighting_getLightColor(vColor.rgb, project.cameraPosition, geometry.position.xyz, geometry.normal);
}
DECKGL_FILTER_COLOR(vColor, geometry);
}
`;
111 changes: 111 additions & 0 deletions modules/aggregation-layers/src/grid-layer/grid-cell-layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Texture} from '@luma.gl/core';
import {UpdateParameters, Color} from '@deck.gl/core';
import {ColumnLayer} from '@deck.gl/layers';
import {CubeGeometry} from '@luma.gl/engine';
import {colorRangeToTexture} from '../utils/color-utils';
import vs from './grid-cell-layer-vertex.glsl';

/** Proprties added by GridCellLayer. */
type GridCellLayerProps = {
cellSizeCommon: [number, number];
cellOriginCommon: [number, number];
colorDomain: () => [number, number];
colorRange?: Color[];
elevationDomain: () => [number, number];
elevationRange: [number, number];
};

export class GridCellLayer<ExtraPropsT extends {} = {}> extends ColumnLayer<
null,
ExtraPropsT & Required<GridCellLayerProps>
> {
static layerName = 'GridCellLayer';

state!: ColumnLayer['state'] & {
colorTexture: Texture;
};

getShaders() {
return {
...super.getShaders(),
vs
};
}

initializeState() {
super.initializeState();

const attributeManager = this.getAttributeManager()!;
attributeManager.remove([
'instanceElevations',
'instanceFillColors',
'instanceLineColors',
'instanceStrokeWidths'
]);
attributeManager.addInstanced({
instancePositions: {
size: 2,
type: 'float32',
accessor: 'getBin'
},
instanceColorValues: {
size: 1,
type: 'float32',
accessor: 'getColorValue'
},
instanceElevationValues: {
size: 1,
type: 'float32',
accessor: 'getElevationValue'
}
});
}

updateState(params: UpdateParameters<this>) {
super.updateState(params);

const {props, oldProps} = params;
const model = this.state.fillModel!;

if (oldProps.colorRange !== props.colorRange) {
this.state.colorTexture?.destroy();
this.state.colorTexture = colorRangeToTexture(this.context.device, props.colorRange);
model.setBindings({colorRange: this.state.colorTexture});
}
}

finalizeState(context) {
super.finalizeState(context);

this.state.colorTexture?.destroy();
}

protected _updateGeometry() {
const geometry = new CubeGeometry();
this.state.fillModel!.setGeometry(geometry);
}

draw({uniforms}) {
// Use dynamic domain from the aggregator
const colorDomain = this.props.colorDomain();
const elevationDomain = this.props.elevationDomain();
const {cellOriginCommon, cellSizeCommon, elevationRange, elevationScale, extruded, coverage} =
this.props;
const fillModel = this.state.fillModel!;
fillModel.setUniforms(uniforms);
fillModel.setUniforms({
extruded,
coverage,
colorDomain,
elevationDomain,
cellOriginCommon,
cellSizeCommon,
elevationRange: [elevationRange[0] * elevationScale, elevationRange[1] * elevationScale]
});
fillModel.draw(this.context.renderPass);
}
}
Loading
Loading