Skip to content

Commit 48760d5

Browse files
feat: Row index based picking (#10302)
1 parent 82a0283 commit 48760d5

28 files changed

Lines changed: 200 additions & 192 deletions

docs/api-reference/core/deck.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ Parameters:
773773
* `y` (number) - y position in pixels
774774
* `radius` (number, optional) - radius of tolerance in pixels. Default `0`.
775775
* `layerIds` (string[], optional) - a list of layer ids to query from. If not specified, then all pickable and visible layers are queried.
776-
* `depth` - Specifies the max number of objects to return. Default `10`. For layers without explicit picking color buffers, only the default depth of 10 unique objects per layer is guaranteed; higher custom depths may return duplicate results for these layers.
776+
* `depth` - Specifies the max number of objects to return. Default `10`. For layers without explicit picking index buffers, only the default depth of 10 unique objects per layer is guaranteed; higher custom depths may return duplicate results for these layers.
777777
* `unproject3D` (boolean, optional) - if `true`, `info.coordinate` will be a 3D point by unprojecting the `x, y` screen coordinates onto the picked geometry. Default `false`.
778778
779779
Returns:
@@ -783,7 +783,7 @@ Returns:
783783
Notes:
784784
785785
* Deep picking is implemented as a sequence of simpler picking operations and can have a performance impact. Should this become a concern, you can use the `depth` parameter to limit the number of matches that can be returned, and thus the maximum number of picking operations.
786-
* Layers that provide explicit picking color buffers support buffer mutation between picking passes and are not subject to the default-depth unique-object guarantee.
786+
* Layers that provide explicit picking index buffers support buffer mutation between picking passes and are not subject to the default-depth unique-object guarantee.
787787
788788
789789
#### `pickObjects` {#pickobjects}

docs/developer-guide/custom-layers/attribute-management.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ While most apps rely on their layers to automatically generate appropriate GPU b
4747

4848
While this allows for ultimate performance and control of updates, as well as potential sharing of buffers between layers, the application will need to generate attributes in exactly the format that the layer shaders expect, creating a strong coupling between the application and the layer.
4949

50-
**Note:** The application can provide some buffers and let others be managed by the layer. Explicit picking color buffers are only needed when the logical picking id differs from the rendered instance id.
50+
**Note:** The application can provide some buffers and let others be managed by the layer. Explicit picking index buffers are only needed when the logical picking id differs from the rendered instance id.
5151

5252

5353
## More information

docs/developer-guide/custom-layers/layer-attributes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ For _variable primitive layers_ there are two options:
5555

5656
2) Copy each descriptive attribute `N` times (`N` being the number of vertices generated for that row during tesselation). This is the method that is used in deck.gl today.
5757

58-
3) Add a single `rowIndex` attribute and copy the same index `N` times as above. In this approach, descriptive values could then be read from textures where they are stored a single time. This provides flexibility at the price of performance (texture access latency) and complexity (working with data in textures).
58+
3) Add a single `rowIndexes` attribute and copy the same index `N` times as above. In this approach, descriptive values could then be read from textures where they are stored a single time. This provides flexibility at the price of performance (texture access latency) and complexity (working with data in textures).
5959

6060

6161
Remarks:

docs/developer-guide/custom-layers/picking.md

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,49 +55,44 @@ The following sections describe common ways to implement custom picking.
5555

5656
Instanced layer shaders can derive picking colors from the built-in instance id when each rendered instance maps to one picked object. In GLSL, use `picking_setPickingColorFromInstanceID()` or assign `geometry.pickingColor = picking_getPickingColorFromInstanceID()`. In WGSL, add `@builtin(instance_index)` to the vertex inputs and use `picking_getPickingColorFromIndex(instanceIndex)`.
5757

58-
Add an explicit picking color attribute only when the logical picking id within the current layer is different from the rendered instance id. For example:
58+
Add an explicit picking index attribute only when the logical picking id within the current layer is different from the rendered instance id. For example:
5959

6060
* Binary GeoJSON or MVT point sublayers may render local point instances while picking should return a global feature index.
6161

62-
* `PathLayer` tessellates one path into multiple rendered segment or joint instances, so its generated geometry needs explicit picking colors that map back to the source path index instead of each rendered segment's instance id.
62+
* `PathLayer` tessellates one path into multiple rendered segment or joint instances, so its generated geometry needs explicit picking indexes that map back to the source path index instead of each rendered segment's instance id.
6363

64-
### Creating A Picking Color Attribute
64+
### Creating A Picking Index Attribute
6565

66-
Add an attribute for each vertex using the layer's [AttributeManager](../../api-reference/core/attribute-manager.md):
66+
Add an attribute for each vertex or instance using the layer's [AttributeManager](../../api-reference/core/attribute-manager.md). Use the `rowIndexes` attribute name to let deck.gl handle deep-picking buffer mutation.
6767

6868
```js
69-
import {GL} from '@luma.gl/webgl/constants';
70-
7169
class MyLayer extends Layer {
7270
initializeState() {
7371
this.state.attributeManager.add({
74-
customPickingColors: {
75-
size: 3,
76-
type: GL.UNSIGNED_BYTE,
77-
update: this.calculatePickingColors
72+
rowIndexes: {
73+
size: 1,
74+
type: 'uint32',
75+
update: this.calculatePickingIndexes
7876
}
7977
});
8078
}
8179
}
8280
```
8381

84-
Populate the attribute by providing a different picking color for every object that you need to differentiate. The default implementation of [`layer.encodePickingColor()`](../../api-reference/core/layer.md#encodepickingcolor) and [`layer.decodePickingColor()`](../../api-reference/core/layer.md#decodepickingcolor) is likely sufficient, but you may need to implement your own pair.
82+
Populate the attribute by providing the logical object index for every rendered vertex or instance that you need to differentiate.
8583

8684
```js
8785
class MyLayer extends Layer {
8886

8987
...
9088

91-
calculatePickingColors(attribute) {
89+
calculatePickingIndexes(attribute) {
9290
const {data} = this.props;
9391
const {value} = attribute;
9492

9593
let i = 0;
9694
for (const object of data) {
97-
const pickingColor = this.encodePickingColor(i);
98-
value[i * 3] = pickingColor[0];
99-
value[i * 3 + 1] = pickingColor[1];
100-
value[i * 3 + 2] = pickingColor[2];
95+
value[i] = i;
10196
i++;
10297
}
10398
}
@@ -129,15 +124,15 @@ All core layers (including composite layers) support picking using luma.gl's `pi
129124

130125
#### Vertex Shader
131126

132-
Vertex shader should set current picking color using `picking_setPickingColor` method provided by picking shader module.
127+
Vertex shader should set current picking color using helpers provided by the picking shader module.
133128

134129
```glsl
135-
attribute vec3 customPickingColors;
130+
in float rowIndexes;
136131
137132
void main(void) {
138133
...
139134
140-
picking_setPickingColor(customPickingColors);
135+
geometry.pickingColor = picking_getPickingColorFromIndex(rowIndexes);
141136
}
142137
```
143138

docs/developer-guide/custom-layers/primitive-layers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,4 @@ By always using the following shader functions for handling projections and scal
149149
150150
If your layer is instanced (`data` prop is an array and each element is rendered as one primitive), then you may take advantage of the default implementation of the [layer picking methods](../../api-reference/core/layer.md#layer-picking-methods).
151151
152-
By default, instanced layer shaders can derive picking colors from the built-in instance id. Add an explicit picking color attribute only when the logical picking id within the current layer is different from the rendered instance id. See [Implementing Custom Picking](./picking.md#implementing-custom-picking) for custom picking details and examples.
152+
By default, instanced layer shaders can derive picking colors from the built-in instance id. Add an explicit picking index attribute only when the logical picking id within the current layer is different from the rendered instance id. See [Implementing Custom Picking](./picking.md#implementing-custom-picking) for custom picking details and examples.

docs/upgrade-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
#### `pickMultipleObjects()` pick depth limits
66

7-
For layers that use built-in shader instance ids instead of explicit picking color buffers, `pickMultipleObjects()` now only guarantees the default `depth` of 10 unique objects per layer. Applications that call `pickMultipleObjects()` with a custom `depth` above the default may receive duplicate results for these layers. Layers with explicit picking color buffers keep their previous buffer-mutation behavior.
7+
For layers that use built-in shader instance ids instead of explicit picking index buffers, `pickMultipleObjects()` now only guarantees the default `depth` of 10 unique objects per layer. Applications that call `pickMultipleObjects()` with a custom `depth` above the default may receive duplicate results for these layers. Layers with explicit picking index buffers keep their previous buffer-mutation behavior.
88

99
### `instancePickingColors` attribute is no longer automatically generated
1010

@@ -13,7 +13,7 @@ Most built-in and custom instanced layers now derive picking colors from built-i
1313
In rare cases, custom WebGL layer shaders may need an update if they explicitly read the `instancePickingColors` attribute.
1414

1515
- In such cases, use the new picking shader helper functions to derive the color from the instance id, for example `picking_setPickingColorFromInstanceID()` in GLSL or `picking_getPickingColorFromIndex(instanceIndex)` in WGSL.
16-
- However, if the logical picking id is different from the rendered instance id, layers can still continue to register and populate an explicit picking color attribute as before.
16+
- However, if the logical picking id is different from the rendered instance id, layers can register and populate an explicit `rowIndexes` attribute.
1717

1818
## Upgrading to v9.3
1919

modules/core/src/lib/layer-state.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export default class LayerState<LayerT extends Layer> extends ComponentState<Lay
3737
*/
3838
usesPickingColorCache: boolean;
3939
/**
40-
* If the layer has picking buffer (pickingColors or instancePickingColors)
40+
* If the layer has a picking buffer
41+
* (rowIndexes, pickingColors or instancePickingColors)
4142
*/
4243
hasPickingBuffer?: boolean;
4344
/**

modules/core/src/lib/layer.ts

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import memoize from '../utils/memoize';
1717
import {mergeShaders} from '../utils/shader';
1818
import {projectPosition, getWorldPosition} from '../shaderlib/project/project-functions';
1919
import typedArrayManager from '../utils/typed-array-manager';
20-
import {disablePickingIndex} from '../shaderlib/picking/picking';
20+
import {disablePickingIndex, PICKING_INVALID_INDEX} from '../shaderlib/picking/picking';
2121

2222
import Component from '../lifecycle/component';
2323
import LayerState, {ChangeFlags} from './layer-state';
@@ -59,6 +59,18 @@ const areViewportsEqual = memoize(
5959

6060
let pickingColorCache = new Uint8ClampedArray(0);
6161

62+
function getPickingAttribute(attributes: Record<string, Attribute>): Attribute | undefined {
63+
return attributes.rowIndexes || attributes.pickingColors || attributes.instancePickingColors;
64+
}
65+
66+
function getPickingIndexAttribute(attributes: Record<string, Attribute>): Attribute | undefined {
67+
return attributes.rowIndexes;
68+
}
69+
70+
function getPickingColorAttribute(attributes: Record<string, Attribute>): Attribute | undefined {
71+
return attributes.pickingColors || attributes.instancePickingColors;
72+
}
73+
6274
const defaultProps: DefaultProps<LayerProps> = {
6375
// data: Special handling for null, see below
6476
data: {type: 'data', value: EMPTY_ARRAY, async: true},
@@ -498,16 +510,17 @@ export default abstract class Layer<PropsT extends {} = {}> extends Component<
498510
// Only generate picking buffer if needed
499511
if (hasPickingBuffer !== needsPickingBuffer) {
500512
this.internalState!.hasPickingBuffer = needsPickingBuffer;
501-
const {pickingColors, instancePickingColors} = attributeManager.attributes;
502-
const pickingColorsAttribute = pickingColors || instancePickingColors;
503-
if (pickingColorsAttribute) {
504-
if (needsPickingBuffer && pickingColorsAttribute.constant) {
505-
pickingColorsAttribute.constant = false;
506-
attributeManager.invalidate(pickingColorsAttribute.id);
513+
const pickingAttribute = getPickingAttribute(attributeManager.attributes);
514+
if (pickingAttribute) {
515+
if (needsPickingBuffer && pickingAttribute.constant) {
516+
pickingAttribute.constant = false;
517+
attributeManager.invalidate(pickingAttribute.id);
507518
}
508-
if (!pickingColorsAttribute.value && !needsPickingBuffer) {
509-
pickingColorsAttribute.constant = true;
510-
pickingColorsAttribute.value = [0, 0, 0];
519+
if (!pickingAttribute.value && !needsPickingBuffer) {
520+
pickingAttribute.constant = true;
521+
pickingAttribute.value = getPickingIndexAttribute(attributeManager.attributes)
522+
? [PICKING_INVALID_INDEX]
523+
: [0, 0, 0];
511524
}
512525
}
513526
}
@@ -808,8 +821,23 @@ export default abstract class Layer<PropsT extends {} = {}> extends Component<
808821
}
809822

810823
// @ts-ignore (TS2531) this method is only called internally with attributeManager defined
811-
const {pickingColors, instancePickingColors} = this.getAttributeManager().attributes;
812-
const colors = pickingColors || instancePickingColors;
824+
const attributes = this.getAttributeManager().attributes;
825+
const indexes = getPickingIndexAttribute(attributes);
826+
const colors = getPickingColorAttribute(attributes);
827+
828+
const externalIndexAttribute =
829+
indexes && data.attributes && (data.attributes[indexes.id] as BinaryAttribute);
830+
if (externalIndexAttribute && externalIndexAttribute.value) {
831+
const values = externalIndexAttribute.value;
832+
for (let index = 0; index < data.length; index++) {
833+
const i = indexes.getVertexOffset(index);
834+
if (values[i] === objectIndex) {
835+
this._disablePickingIndex(index);
836+
}
837+
}
838+
return;
839+
}
840+
813841
const externalColorAttribute =
814842
colors && data.attributes && (data.attributes[colors.id] as BinaryAttribute);
815843
if (externalColorAttribute && externalColorAttribute.value) {
@@ -833,8 +861,18 @@ export default abstract class Layer<PropsT extends {} = {}> extends Component<
833861
// TODO - simplify subclassing interface
834862
protected _disablePickingIndex(objectIndex: number): void {
835863
// @ts-ignore (TS2531) this method is only called internally with attributeManager defined
836-
const {pickingColors, instancePickingColors} = this.getAttributeManager().attributes;
837-
const colors = pickingColors || instancePickingColors;
864+
const attributes = this.getAttributeManager().attributes;
865+
const indexes = getPickingIndexAttribute(attributes);
866+
if (indexes) {
867+
const start = indexes.getVertexOffset(objectIndex);
868+
const end = indexes.getVertexOffset(objectIndex + 1);
869+
const invalidIndexes = new Uint32Array(end - start);
870+
invalidIndexes.fill(PICKING_INVALID_INDEX);
871+
indexes.buffer.write(invalidIndexes, start * invalidIndexes.BYTES_PER_ELEMENT);
872+
return;
873+
}
874+
875+
const colors = getPickingColorAttribute(attributes);
838876
if (!colors) {
839877
if (this.internalState) {
840878
disablePickingIndex(this.internalState.disabledPickingIndices, objectIndex);
@@ -852,23 +890,25 @@ export default abstract class Layer<PropsT extends {} = {}> extends Component<
852890
/** (Internal) Re-enable all picking indices after multi-depth picking */
853891
restorePickingColors(): void {
854892
// @ts-ignore (TS2531) this method is only called internally with attributeManager defined
855-
const {pickingColors, instancePickingColors} = this.getAttributeManager().attributes;
856-
const colors = pickingColors || instancePickingColors;
857-
if (!colors) {
893+
const attributes = this.getAttributeManager().attributes;
894+
const pickingAttribute = getPickingAttribute(attributes);
895+
if (!pickingAttribute) {
858896
if (this.internalState) {
859897
this.internalState.disabledPickingIndices.length = 0;
860898
}
861899
return;
862900
}
901+
const colors = getPickingColorAttribute(attributes);
863902
// The picking color cache may have been freed and then reallocated. This ensures we read from the currently allocated cache.
864903
if (
865904
// @ts-ignore (TS2531) this method is only called internally with internalState defined
866905
this.internalState.usesPickingColorCache &&
906+
colors &&
867907
(colors.value as Uint8ClampedArray).buffer !== pickingColorCache.buffer
868908
) {
869909
colors.value = pickingColorCache.subarray(0, (colors.value as Uint8ClampedArray).length);
870910
}
871-
colors.updateSubBuffer({startOffset: 0});
911+
pickingAttribute.updateSubBuffer({startOffset: 0});
872912
}
873913

874914
/* eslint-disable max-statements */

modules/core/src/shaderlib/picking/picking.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import {picking} from '@luma.gl/shadertools';
66
import log from '../../utils/log';
77

88
export const PICKING_MAX_DISABLED_INDICES = 10;
9+
export const PICKING_INVALID_INDEX = 16777215;
910

1011
export function disablePickingIndex(disabledPickingIndices: number[], objectIndex: number): void {
1112
if (disabledPickingIndices.length === PICKING_MAX_DISABLED_INDICES) {
1213
log.warn(
13-
`pickMultipleObjects can only exclude ${PICKING_MAX_DISABLED_INDICES} previously picked objects for layers without picking color buffers`
14+
`pickMultipleObjects can only exclude ${PICKING_MAX_DISABLED_INDICES} previously picked objects for layers without picking buffers`
1415
)();
1516
} else {
1617
disabledPickingIndices.push(objectIndex);
@@ -42,7 +43,7 @@ function packDisabledPickingIndices(disabledPickingIndices: number[], startIndex
4243

4344
const pickingHelpersGLSL = /* glsl */ `\
4445
vec3 picking_getPickingColorFromIndex(float objectIndex) {
45-
if (objectIndex < 0.0 || objectIndex > 16777214.0) {
46+
if (objectIndex < 0.0 || objectIndex >= ${PICKING_INVALID_INDEX}.0) {
4647
return vec3(0.0);
4748
}
4849
@@ -67,6 +68,10 @@ vec3 picking_getPickingColorFromIndex(float objectIndex) {
6768
);
6869
}
6970
71+
vec3 picking_getPickingColorFromIndex(uint objectIndex) {
72+
return picking_getPickingColorFromIndex(float(objectIndex));
73+
}
74+
7075
vec3 picking_getPickingColorFromInstanceID() {
7176
return picking_getPickingColorFromIndex(float(gl_InstanceID));
7277
}
@@ -109,7 +114,7 @@ fn picking_isColorValid(color: vec3<f32>) -> bool {
109114
}
110115
111116
fn picking_getPickingColorFromIndex(objectIndex: u32) -> vec3<f32> {
112-
if (objectIndex > 16777214u) {
117+
if (objectIndex >= ${PICKING_INVALID_INDEX}u) {
113118
return vec3<f32>(0.0);
114119
}
115120

modules/geo-layers/src/mesh-layer/mesh-layer-vertex.glsl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ in vec3 normals;
1111
in vec3 colors;
1212
in vec2 texCoords;
1313
in vec4 uvRegions;
14-
in vec3 featureIdsPickingColors;
14+
in float rowIndexes;
1515
1616
// Instance attributes
1717
in vec4 instanceColors;
@@ -40,7 +40,7 @@ void main(void) {
4040
geometry.uv = uv;
4141
4242
if (mesh.pickFeatureIds) {
43-
geometry.pickingColor = featureIdsPickingColors;
43+
geometry.pickingColor = picking_getPickingColorFromIndex(rowIndexes);
4444
} else {
4545
geometry.pickingColor = picking_getPickingColorFromInstanceID();
4646
}

0 commit comments

Comments
 (0)