@luma.gl/engine 9.2.6 → 9.3.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/animation-loop/animation-loop.d.ts +11 -5
  2. package/dist/animation-loop/animation-loop.d.ts.map +1 -1
  3. package/dist/animation-loop/animation-loop.js +83 -47
  4. package/dist/animation-loop/animation-loop.js.map +1 -1
  5. package/dist/animation-loop/make-animation-loop.js +7 -1
  6. package/dist/animation-loop/make-animation-loop.js.map +1 -1
  7. package/dist/animation-loop/request-animation-frame.d.ts.map +1 -1
  8. package/dist/animation-loop/request-animation-frame.js +23 -6
  9. package/dist/animation-loop/request-animation-frame.js.map +1 -1
  10. package/dist/compute/computation.d.ts +3 -7
  11. package/dist/compute/computation.d.ts.map +1 -1
  12. package/dist/compute/computation.js +16 -13
  13. package/dist/compute/computation.js.map +1 -1
  14. package/dist/compute/swap.d.ts +2 -0
  15. package/dist/compute/swap.d.ts.map +1 -1
  16. package/dist/compute/swap.js +10 -5
  17. package/dist/compute/swap.js.map +1 -1
  18. package/dist/dist.dev.js +2639 -1290
  19. package/dist/dist.min.js +325 -210
  20. package/dist/dynamic-texture/dynamic-texture.d.ts +102 -0
  21. package/dist/dynamic-texture/dynamic-texture.d.ts.map +1 -0
  22. package/dist/dynamic-texture/dynamic-texture.js +556 -0
  23. package/dist/dynamic-texture/dynamic-texture.js.map +1 -0
  24. package/dist/dynamic-texture/texture-data.d.ts +144 -0
  25. package/dist/dynamic-texture/texture-data.d.ts.map +1 -0
  26. package/dist/dynamic-texture/texture-data.js +208 -0
  27. package/dist/dynamic-texture/texture-data.js.map +1 -0
  28. package/dist/geometries/cone-geometry.d.ts +3 -1
  29. package/dist/geometries/cone-geometry.d.ts.map +1 -1
  30. package/dist/geometries/cone-geometry.js.map +1 -1
  31. package/dist/geometries/cylinder-geometry.d.ts +2 -1
  32. package/dist/geometries/cylinder-geometry.d.ts.map +1 -1
  33. package/dist/geometries/cylinder-geometry.js.map +1 -1
  34. package/dist/geometry/gpu-geometry.d.ts.map +1 -1
  35. package/dist/geometry/gpu-geometry.js +8 -3
  36. package/dist/geometry/gpu-geometry.js.map +1 -1
  37. package/dist/index.cjs +2497 -1212
  38. package/dist/index.cjs.map +4 -4
  39. package/dist/index.d.ts +20 -6
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/material/material-factory.d.ts +73 -0
  44. package/dist/material/material-factory.d.ts.map +1 -0
  45. package/dist/material/material-factory.js +111 -0
  46. package/dist/material/material-factory.js.map +1 -0
  47. package/dist/material/material.d.ts +84 -0
  48. package/dist/material/material.d.ts.map +1 -0
  49. package/dist/material/material.js +176 -0
  50. package/dist/material/material.js.map +1 -0
  51. package/dist/model/model.d.ts +47 -16
  52. package/dist/model/model.d.ts.map +1 -1
  53. package/dist/model/model.js +113 -47
  54. package/dist/model/model.js.map +1 -1
  55. package/dist/model/split-uniforms-and-bindings.d.ts +4 -3
  56. package/dist/model/split-uniforms-and-bindings.d.ts.map +1 -1
  57. package/dist/model/split-uniforms-and-bindings.js +2 -2
  58. package/dist/model/split-uniforms-and-bindings.js.map +1 -1
  59. package/dist/models/billboard-texture-model.d.ts +8 -5
  60. package/dist/models/billboard-texture-model.d.ts.map +1 -1
  61. package/dist/models/billboard-texture-model.js +77 -23
  62. package/dist/models/billboard-texture-model.js.map +1 -1
  63. package/dist/models/billboard-texture-module.d.ts +1 -1
  64. package/dist/models/billboard-texture-module.js +1 -1
  65. package/dist/models/clip-space.js +7 -7
  66. package/dist/models/directional-light-model.d.ts +7 -0
  67. package/dist/models/directional-light-model.d.ts.map +1 -0
  68. package/dist/models/directional-light-model.js +23 -0
  69. package/dist/models/directional-light-model.js.map +1 -0
  70. package/dist/models/light-model-utils.d.ts +69 -0
  71. package/dist/models/light-model-utils.d.ts.map +1 -0
  72. package/dist/models/light-model-utils.js +395 -0
  73. package/dist/models/light-model-utils.js.map +1 -0
  74. package/dist/models/point-light-model.d.ts +7 -0
  75. package/dist/models/point-light-model.d.ts.map +1 -0
  76. package/dist/models/point-light-model.js +22 -0
  77. package/dist/models/point-light-model.js.map +1 -0
  78. package/dist/models/spot-light-model.d.ts +7 -0
  79. package/dist/models/spot-light-model.d.ts.map +1 -0
  80. package/dist/models/spot-light-model.js +23 -0
  81. package/dist/models/spot-light-model.js.map +1 -0
  82. package/dist/modules/picking/color-picking.d.ts +5 -9
  83. package/dist/modules/picking/color-picking.d.ts.map +1 -1
  84. package/dist/modules/picking/color-picking.js +122 -115
  85. package/dist/modules/picking/color-picking.js.map +1 -1
  86. package/dist/modules/picking/index-picking.d.ts +4 -4
  87. package/dist/modules/picking/index-picking.d.ts.map +1 -1
  88. package/dist/modules/picking/index-picking.js +36 -16
  89. package/dist/modules/picking/index-picking.js.map +1 -1
  90. package/dist/modules/picking/legacy-color-picking.d.ts +26 -0
  91. package/dist/modules/picking/legacy-color-picking.d.ts.map +1 -0
  92. package/dist/modules/picking/legacy-color-picking.js +7 -0
  93. package/dist/modules/picking/legacy-color-picking.js.map +1 -0
  94. package/dist/modules/picking/picking-manager.d.ts +29 -3
  95. package/dist/modules/picking/picking-manager.d.ts.map +1 -1
  96. package/dist/modules/picking/picking-manager.js +188 -41
  97. package/dist/modules/picking/picking-manager.js.map +1 -1
  98. package/dist/modules/picking/picking-uniforms.d.ts +13 -12
  99. package/dist/modules/picking/picking-uniforms.d.ts.map +1 -1
  100. package/dist/modules/picking/picking-uniforms.js +27 -14
  101. package/dist/modules/picking/picking-uniforms.js.map +1 -1
  102. package/dist/modules/picking/picking.d.ts +25 -0
  103. package/dist/modules/picking/picking.d.ts.map +1 -0
  104. package/dist/modules/picking/picking.js +18 -0
  105. package/dist/modules/picking/picking.js.map +1 -0
  106. package/dist/passes/get-fragment-shader.js +12 -27
  107. package/dist/passes/get-fragment-shader.js.map +1 -1
  108. package/dist/passes/shader-pass-renderer.d.ts +5 -7
  109. package/dist/passes/shader-pass-renderer.d.ts.map +1 -1
  110. package/dist/passes/shader-pass-renderer.js +16 -42
  111. package/dist/passes/shader-pass-renderer.js.map +1 -1
  112. package/dist/scenegraph/group-node.d.ts +5 -0
  113. package/dist/scenegraph/group-node.d.ts.map +1 -1
  114. package/dist/scenegraph/group-node.js +12 -0
  115. package/dist/scenegraph/group-node.js.map +1 -1
  116. package/dist/scenegraph/model-node.d.ts +2 -2
  117. package/dist/scenegraph/model-node.d.ts.map +1 -1
  118. package/dist/scenegraph/model-node.js.map +1 -1
  119. package/dist/scenegraph/scenegraph-node.d.ts +1 -1
  120. package/dist/scenegraph/scenegraph-node.d.ts.map +1 -1
  121. package/dist/scenegraph/scenegraph-node.js +23 -15
  122. package/dist/scenegraph/scenegraph-node.js.map +1 -1
  123. package/dist/shader-inputs.d.ts +9 -7
  124. package/dist/shader-inputs.d.ts.map +1 -1
  125. package/dist/shader-inputs.js +84 -4
  126. package/dist/shader-inputs.js.map +1 -1
  127. package/dist/utils/buffer-layout-order.d.ts.map +1 -1
  128. package/dist/utils/buffer-layout-order.js +12 -2
  129. package/dist/utils/buffer-layout-order.js.map +1 -1
  130. package/dist/utils/shader-module-utils.d.ts +7 -0
  131. package/dist/utils/shader-module-utils.d.ts.map +1 -0
  132. package/dist/utils/shader-module-utils.js +46 -0
  133. package/dist/utils/shader-module-utils.js.map +1 -0
  134. package/package.json +6 -6
  135. package/src/animation-loop/animation-loop.ts +89 -50
  136. package/src/animation-loop/make-animation-loop.ts +13 -5
  137. package/src/animation-loop/request-animation-frame.ts +32 -6
  138. package/src/compute/computation.ts +32 -17
  139. package/src/compute/swap.ts +13 -7
  140. package/src/dynamic-texture/dynamic-texture.ts +732 -0
  141. package/src/dynamic-texture/texture-data.ts +336 -0
  142. package/src/geometries/cone-geometry.ts +6 -1
  143. package/src/geometries/cylinder-geometry.ts +5 -1
  144. package/src/geometry/gpu-geometry.ts +8 -3
  145. package/src/index.ts +38 -8
  146. package/src/material/material-factory.ts +157 -0
  147. package/src/material/material.ts +254 -0
  148. package/src/model/model.ts +158 -67
  149. package/src/model/split-uniforms-and-bindings.ts +8 -6
  150. package/src/models/billboard-texture-model.ts +88 -27
  151. package/src/models/billboard-texture-module.ts +1 -1
  152. package/src/models/clip-space.ts +7 -7
  153. package/src/models/directional-light-model.ts +32 -0
  154. package/src/models/light-model-utils.ts +587 -0
  155. package/src/models/point-light-model.ts +31 -0
  156. package/src/models/spot-light-model.ts +32 -0
  157. package/src/modules/picking/color-picking.ts +123 -122
  158. package/src/modules/picking/index-picking.ts +36 -16
  159. package/src/modules/picking/legacy-color-picking.ts +8 -0
  160. package/src/modules/picking/picking-manager.ts +252 -50
  161. package/src/modules/picking/picking-uniforms.ts +39 -24
  162. package/src/modules/picking/picking.ts +22 -0
  163. package/src/passes/get-fragment-shader.ts +12 -27
  164. package/src/passes/shader-pass-renderer.ts +25 -48
  165. package/src/scenegraph/group-node.ts +16 -0
  166. package/src/scenegraph/model-node.ts +2 -2
  167. package/src/scenegraph/scenegraph-node.ts +27 -16
  168. package/src/shader-inputs.ts +165 -15
  169. package/src/utils/buffer-layout-order.ts +18 -2
  170. package/src/utils/shader-module-utils.ts +65 -0
  171. package/dist/async-texture/async-texture.d.ts +0 -166
  172. package/dist/async-texture/async-texture.d.ts.map +0 -1
  173. package/dist/async-texture/async-texture.js +0 -386
  174. package/dist/async-texture/async-texture.js.map +0 -1
  175. package/dist/factories/pipeline-factory.d.ts +0 -37
  176. package/dist/factories/pipeline-factory.d.ts.map +0 -1
  177. package/dist/factories/pipeline-factory.js +0 -181
  178. package/dist/factories/pipeline-factory.js.map +0 -1
  179. package/dist/factories/shader-factory.d.ts +0 -22
  180. package/dist/factories/shader-factory.d.ts.map +0 -1
  181. package/dist/factories/shader-factory.js +0 -88
  182. package/dist/factories/shader-factory.js.map +0 -1
  183. package/src/async-texture/async-texture.ts +0 -551
  184. package/src/factories/pipeline-factory.ts +0 -224
  185. package/src/factories/shader-factory.ts +0 -103
  186. /package/src/{async-texture/texture-setters.ts.disabled → dynamic-texture/texture-data.ts.disabled} +0 -0
@@ -2,10 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {Device, Framebuffer} from '@luma.gl/core';
5
+ import {Buffer, Device, Framebuffer, Texture} from '@luma.gl/core';
6
6
  import {ShaderInputs} from '../../shader-inputs';
7
7
  import {pickingUniforms, INVALID_INDEX} from './picking-uniforms';
8
- // import {picking} from './color-picking';
8
+
9
+ const INDEX_PICKING_ATTACHMENT_INDEX = 1;
10
+ const INDEX_PICKING_CLEAR_COLOR = new Int32Array([INVALID_INDEX, INVALID_INDEX, 0, 0]);
11
+ const COLOR_PICKING_MAX_OBJECT_INDEX = 16777214;
12
+ const COLOR_PICKING_MAX_BATCH_INDEX = 254;
9
13
 
10
14
  /** Information about picked object */
11
15
  export type PickInfo = {
@@ -13,22 +17,81 @@ export type PickInfo = {
13
17
  objectIndex: number | null;
14
18
  };
15
19
 
20
+ export type PickingMode = 'auto' | 'index' | 'color';
21
+ export type ResolvedPickingMode = Exclude<PickingMode, 'auto'>;
22
+ /** @deprecated Use `PickingMode`. */
23
+ export type PickingBackend = PickingMode;
24
+ /** @deprecated Use `ResolvedPickingMode`. */
25
+ export type ResolvedPickingBackend = ResolvedPickingMode;
26
+
16
27
  export type PickingManagerProps = {
17
- /** Shader Inputs from models to pick */
28
+ /** Shader inputs from models to pick */
18
29
  shaderInputs?: ShaderInputs<{picking: typeof pickingUniforms.props}>;
19
30
  /** Callback */
20
31
  onObjectPicked?: (info: PickInfo) => void;
32
+ /** Select a picking mode. Defaults to `color`. Use `auto` to prefer `index` when supported. */
33
+ mode?: PickingMode;
34
+ /** @deprecated Use `mode`. */
35
+ backend?: PickingBackend;
21
36
  };
22
37
 
38
+ export function resolvePickingMode(
39
+ deviceType: Device['type'],
40
+ mode: PickingMode = 'color',
41
+ indexPickingSupported: boolean = deviceType === 'webgpu'
42
+ ): ResolvedPickingMode {
43
+ if (mode === 'auto') {
44
+ return indexPickingSupported ? 'index' : 'color';
45
+ }
46
+
47
+ if (mode === 'index' && !indexPickingSupported) {
48
+ throw new Error(
49
+ `Picking mode "${mode}" requires WebGPU or a WebGL device that supports renderable rg32sint textures.`
50
+ );
51
+ }
52
+
53
+ return mode;
54
+ }
55
+
56
+ export function supportsIndexPicking(device: Device): boolean {
57
+ return (
58
+ device.type === 'webgpu' ||
59
+ (device.type === 'webgl' && device.isTextureFormatRenderable('rg32sint'))
60
+ );
61
+ }
62
+
63
+ /** @deprecated Use `resolvePickingMode`. */
64
+ export const resolvePickingBackend = resolvePickingMode;
65
+
66
+ export function decodeIndexPickInfo(pixelData: Int32Array): PickInfo {
67
+ return {
68
+ objectIndex: pixelData[0] === INVALID_INDEX ? null : pixelData[0],
69
+ batchIndex: pixelData[1] === INVALID_INDEX ? null : pixelData[1]
70
+ };
71
+ }
72
+
73
+ export function decodeColorPickInfo(pixelData: Uint8Array): PickInfo {
74
+ const encodedObjectIndex = pixelData[0] + pixelData[1] * 256 + pixelData[2] * 65536;
75
+ if (encodedObjectIndex === 0) {
76
+ return {objectIndex: null, batchIndex: null};
77
+ }
78
+
79
+ const batchIndex = pixelData[3] > 0 ? pixelData[3] - 1 : 0;
80
+ return {
81
+ objectIndex: encodedObjectIndex - 1,
82
+ batchIndex
83
+ };
84
+ }
85
+
23
86
  /**
24
- * Helper class for using the new picking module
25
- * @todo Port to WebGPU
87
+ * Helper class for using object picking with backend-specific readback.
26
88
  * @todo Support multiple models
27
89
  * @todo Switching picking module
28
90
  */
29
91
  export class PickingManager {
30
92
  device: Device;
31
93
  props: Required<PickingManagerProps>;
94
+ mode: ResolvedPickingMode;
32
95
  /** Info from latest pick operation */
33
96
  pickInfo: PickInfo = {batchIndex: null, objectIndex: null};
34
97
  /** Framebuffer used for picking */
@@ -36,12 +99,22 @@ export class PickingManager {
36
99
 
37
100
  static defaultProps: Required<PickingManagerProps> = {
38
101
  shaderInputs: undefined!,
39
- onObjectPicked: () => {}
102
+ onObjectPicked: () => {},
103
+ mode: 'color',
104
+ backend: 'color'
40
105
  };
41
106
 
42
107
  constructor(device: Device, props: PickingManagerProps) {
43
108
  this.device = device;
44
109
  this.props = {...PickingManager.defaultProps, ...props};
110
+ const requestedMode = props.mode ?? props.backend ?? PickingManager.defaultProps.mode;
111
+ this.props.mode = requestedMode;
112
+ this.props.backend = requestedMode;
113
+ this.mode = resolvePickingMode(
114
+ this.device.type,
115
+ requestedMode,
116
+ supportsIndexPicking(this.device)
117
+ );
45
118
  }
46
119
 
47
120
  destroy() {
@@ -51,17 +124,15 @@ export class PickingManager {
51
124
  // TODO - Ask for a cached framebuffer? a Framebuffer factory?
52
125
  getFramebuffer() {
53
126
  if (!this.framebuffer) {
54
- this.framebuffer = this.device.createFramebuffer({
55
- colorAttachments: ['rgba8unorm', 'rg32sint'],
56
- depthStencilAttachment: 'depth24plus'
57
- });
127
+ this.framebuffer =
128
+ this.mode === 'index' ? this.createIndexFramebuffer() : this.createColorFramebuffer();
58
129
  }
59
130
  return this.framebuffer;
60
131
  }
61
132
 
62
133
  /** Clear highlighted / picked object */
63
134
  clearPickState() {
64
- this.props.shaderInputs.setProps({picking: {highlightedObjectIndex: null}});
135
+ this.setPickingProps({highlightedBatchIndex: null, highlightedObjectIndex: null});
65
136
  }
66
137
 
67
138
  /** Prepare for rendering picking colors */
@@ -69,56 +140,38 @@ export class PickingManager {
69
140
  const framebuffer = this.getFramebuffer();
70
141
  framebuffer.resize(this.device.getDefaultCanvasContext().getDevicePixelSize());
71
142
 
72
- this.props.shaderInputs?.setProps({picking: {isActive: true}});
73
-
74
- const pickingPass = this.device.beginRenderPass({
75
- framebuffer,
76
- clearColors: [new Float32Array([0, 0, 0, 0]), new Int32Array([-1, -1, 0, 0])],
77
- clearDepth: 1
78
- });
143
+ this.setPickingProps({isActive: true});
79
144
 
80
- return pickingPass;
145
+ return this.mode === 'index'
146
+ ? this.device.beginRenderPass({
147
+ framebuffer,
148
+ clearColors: [new Float32Array([0, 0, 0, 0]), INDEX_PICKING_CLEAR_COLOR],
149
+ clearDepth: 1
150
+ })
151
+ : this.device.beginRenderPass({
152
+ framebuffer,
153
+ clearColor: [0, 0, 0, 0],
154
+ clearDepth: 1
155
+ });
81
156
  }
82
157
 
83
158
  async updatePickInfo(mousePosition: [number, number]): Promise<PickInfo | null> {
84
159
  const framebuffer = this.getFramebuffer();
85
-
86
- // use the center pixel location in device pixel range
87
- const [pickX, pickY] = this.getPickPosition(mousePosition);
88
-
89
- // Read back
90
- const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
91
- sourceX: pickX,
92
- sourceY: pickY,
93
- sourceWidth: 1,
94
- sourceHeight: 1,
95
- sourceAttachment: 1
96
- });
97
- if (!pixelData) {
160
+ const pickPosition = this.getPickPosition(mousePosition);
161
+ const pickInfo = await this.readPickInfo(framebuffer, pickPosition);
162
+ if (!pickInfo) {
98
163
  return null;
99
164
  }
100
165
 
101
- const pickInfo: PickInfo = {
102
- objectIndex: pixelData[0] === INVALID_INDEX ? null : pixelData[0],
103
- batchIndex: pixelData[1] === INVALID_INDEX ? null : pixelData[1]
104
- };
105
-
106
- // Call callback if picked object has changed
107
- if (
108
- pickInfo.objectIndex !== this.pickInfo.objectIndex ||
109
- pickInfo.batchIndex !== this.pickInfo.batchIndex
110
- ) {
166
+ if (this.hasPickInfoChanged(pickInfo)) {
111
167
  this.pickInfo = pickInfo;
112
168
  this.props.onObjectPicked(pickInfo);
113
- // console.log(`Object ${pickInfo.objectIndex} in batch ${pickInfo.batchIndex} was picked`)
114
169
  }
115
170
 
116
- this.props.shaderInputs?.setProps({
117
- picking: {
118
- isActive: false,
119
- highlightedBatchIndex: pickInfo.batchIndex,
120
- highlightedObjectIndex: pickInfo.objectIndex
121
- }
171
+ this.setPickingProps({
172
+ isActive: false,
173
+ highlightedBatchIndex: pickInfo.batchIndex,
174
+ highlightedObjectIndex: pickInfo.objectIndex
122
175
  });
123
176
 
124
177
  return this.pickInfo;
@@ -129,9 +182,158 @@ export class PickingManager {
129
182
  * use the center pixel location in device pixel range
130
183
  */
131
184
  getPickPosition(mousePosition: [number, number]): [number, number] {
132
- const devicePixels = this.device.getDefaultCanvasContext().cssToDevicePixels(mousePosition);
185
+ const yInvert = this.device.type !== 'webgpu';
186
+ const devicePixels = this.device
187
+ .getDefaultCanvasContext()
188
+ .cssToDevicePixels(mousePosition, yInvert);
133
189
  const pickX = devicePixels.x + Math.floor(devicePixels.width / 2);
134
190
  const pickY = devicePixels.y + Math.floor(devicePixels.height / 2);
135
191
  return [pickX, pickY];
136
192
  }
193
+
194
+ protected createIndexFramebuffer(): Framebuffer {
195
+ const colorTexture = this.device.createTexture({
196
+ format: 'rgba8unorm',
197
+ width: 1,
198
+ height: 1,
199
+ usage: Texture.RENDER_ATTACHMENT
200
+ });
201
+ const pickingTexture = this.device.createTexture({
202
+ format: 'rg32sint',
203
+ width: 1,
204
+ height: 1,
205
+ usage: Texture.RENDER_ATTACHMENT | Texture.COPY_SRC
206
+ });
207
+
208
+ return this.device.createFramebuffer({
209
+ colorAttachments: [colorTexture, pickingTexture],
210
+ depthStencilAttachment: 'depth24plus'
211
+ });
212
+ }
213
+
214
+ protected createColorFramebuffer(): Framebuffer {
215
+ const pickingTexture = this.device.createTexture({
216
+ format: 'rgba8unorm',
217
+ width: 1,
218
+ height: 1,
219
+ usage: Texture.RENDER_ATTACHMENT | Texture.COPY_SRC
220
+ });
221
+
222
+ return this.device.createFramebuffer({
223
+ colorAttachments: [pickingTexture],
224
+ depthStencilAttachment: 'depth24plus'
225
+ });
226
+ }
227
+
228
+ protected setPickingProps(props: Partial<typeof pickingUniforms.props>): void {
229
+ this.props.shaderInputs?.setProps({picking: props});
230
+ }
231
+
232
+ protected async readPickInfo(
233
+ framebuffer: Framebuffer,
234
+ pickPosition: [number, number]
235
+ ): Promise<PickInfo | null> {
236
+ return this.mode === 'index'
237
+ ? this.readIndexPickInfo(framebuffer, pickPosition)
238
+ : this.readColorPickInfo(framebuffer, pickPosition);
239
+ }
240
+
241
+ protected async readIndexPickInfo(
242
+ framebuffer: Framebuffer,
243
+ [pickX, pickY]: [number, number]
244
+ ): Promise<PickInfo | null> {
245
+ if (this.device.type === 'webgpu') {
246
+ const pickTexture = framebuffer.colorAttachments[INDEX_PICKING_ATTACHMENT_INDEX]?.texture;
247
+ if (!pickTexture) {
248
+ return null;
249
+ }
250
+
251
+ const layout = pickTexture.computeMemoryLayout({width: 1, height: 1});
252
+ const readBuffer = this.device.createBuffer({
253
+ byteLength: layout.byteLength,
254
+ usage: Buffer.COPY_DST | Buffer.MAP_READ
255
+ });
256
+ try {
257
+ pickTexture.readBuffer(
258
+ {
259
+ x: pickX,
260
+ y: pickY,
261
+ width: 1,
262
+ height: 1
263
+ },
264
+ readBuffer
265
+ );
266
+ const pickDataView = await readBuffer.readAsync(0, layout.byteLength);
267
+ return decodeIndexPickInfo(new Int32Array(pickDataView.buffer, pickDataView.byteOffset, 2));
268
+ } finally {
269
+ readBuffer.destroy();
270
+ }
271
+ }
272
+
273
+ const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
274
+ sourceX: pickX,
275
+ sourceY: pickY,
276
+ sourceWidth: 1,
277
+ sourceHeight: 1,
278
+ sourceAttachment: INDEX_PICKING_ATTACHMENT_INDEX
279
+ });
280
+
281
+ return pixelData
282
+ ? decodeIndexPickInfo(new Int32Array(pixelData.buffer, pixelData.byteOffset, 2))
283
+ : null;
284
+ }
285
+
286
+ protected async readColorPickInfo(
287
+ framebuffer: Framebuffer,
288
+ [pickX, pickY]: [number, number]
289
+ ): Promise<PickInfo | null> {
290
+ if (this.device.type === 'webgpu') {
291
+ const pickTexture = framebuffer.colorAttachments[0]?.texture;
292
+ if (!pickTexture) {
293
+ return null;
294
+ }
295
+
296
+ const layout = pickTexture.computeMemoryLayout({width: 1, height: 1});
297
+ const readBuffer = this.device.createBuffer({
298
+ byteLength: layout.byteLength,
299
+ usage: Buffer.COPY_DST | Buffer.MAP_READ
300
+ });
301
+ try {
302
+ pickTexture.readBuffer(
303
+ {
304
+ x: pickX,
305
+ y: pickY,
306
+ width: 1,
307
+ height: 1
308
+ },
309
+ readBuffer
310
+ );
311
+ const pickDataView = await readBuffer.readAsync(0, layout.byteLength);
312
+ return decodeColorPickInfo(new Uint8Array(pickDataView.buffer, pickDataView.byteOffset, 4));
313
+ } finally {
314
+ readBuffer.destroy();
315
+ }
316
+ }
317
+
318
+ const pixelData = this.device.readPixelsToArrayWebGL(framebuffer, {
319
+ sourceX: pickX,
320
+ sourceY: pickY,
321
+ sourceWidth: 1,
322
+ sourceHeight: 1,
323
+ sourceAttachment: 0
324
+ });
325
+
326
+ return pixelData
327
+ ? decodeColorPickInfo(new Uint8Array(pixelData.buffer, pixelData.byteOffset, 4))
328
+ : null;
329
+ }
330
+
331
+ protected hasPickInfoChanged(pickInfo: PickInfo): boolean {
332
+ return (
333
+ pickInfo.objectIndex !== this.pickInfo.objectIndex ||
334
+ pickInfo.batchIndex !== this.pickInfo.batchIndex
335
+ );
336
+ }
137
337
  }
338
+
339
+ export {COLOR_PICKING_MAX_BATCH_INDEX, COLOR_PICKING_MAX_OBJECT_INDEX};
@@ -9,6 +9,7 @@ import type {ShaderModule} from '@luma.gl/shadertools';
9
9
  const DEFAULT_HIGHLIGHT_COLOR: NumberArray4 = [0, 1, 1, 1];
10
10
 
11
11
  export const INVALID_INDEX = -1;
12
+ export type PickingPayloadMode = 'instance' | 'attribute';
12
13
 
13
14
  /**
14
15
  * Props for the picking module, which depending on mode renders picking colors or highlighted item.
@@ -19,14 +20,14 @@ export const INVALID_INDEX = -1;
19
20
  export type PickingProps = {
20
21
  /** Are we picking? I.e. rendering picking colors? */
21
22
  isActive?: boolean;
22
- /** Whether to use instance_index (built-in) or a custom application supplied index (usually from an attribute) */
23
- indexMode?: 'instance' | 'custom';
24
- /** Batch index (used when rendering multiple models to identify which model was picked), defaults to 0 */
23
+ /** Whether the payload is sourced from the builtin instance index or a custom integer attribute */
24
+ indexMode?: PickingPayloadMode;
25
+ /** Identifier of the batch currently being rendered */
25
26
  batchIndex?: number;
26
27
 
27
- /** Index of the highlighted batch, defaults to 0 */
28
+ /** Identifier of the highlighted batch */
28
29
  highlightedBatchIndex?: number | null;
29
- /** Set an index to highlight that item, or `null` to explicitly clear **/
30
+ /** Set the highlighted object index, or `null` to explicitly clear **/
30
31
  highlightedObjectIndex?: number | null;
31
32
  /** Color of visual highlight of "selected" item () */
32
33
  highlightColor?: NumberArray4;
@@ -43,18 +44,18 @@ export type PickingUniforms = {
43
44
  * When false, renders normal colors, with the exception of selected object which is rendered with highlight
44
45
  */
45
46
  isActive: boolean;
46
- /** Set to true when picking an attribute value instead of object index */
47
+ /** Whether the current payload comes from instance_index or a custom integer attribute */
47
48
  indexMode: 0 | 1;
48
- /** Index of batch currently being rendered */
49
+ /** Identifier of the batch currently being rendered */
49
50
  batchIndex: number;
50
51
 
51
52
  /** Do we have a highlighted item? */
52
53
  isHighlightActive: boolean;
53
54
  /** Color of visual highlight of "selected" item. Note: RGBA components must in the range 0-1 */
54
55
  highlightColor: NumberArray4;
55
- /** Indicates which batch to visually highlight an item in (defaults to 0) */
56
+ /** Indicates which batch to visually highlight an item in */
56
57
  highlightedBatchIndex: number;
57
- /** Indicates which index in the batch to highlight an item in */
58
+ /** Indicates which object index in the batch to highlight */
58
59
  highlightedObjectIndex: number;
59
60
  };
60
61
 
@@ -77,7 +78,7 @@ export const GLSL_UNIFORMS = /* glsl */ `\
77
78
  precision highp float;
78
79
  precision highp int;
79
80
 
80
- uniform pickingUniforms {
81
+ layout(std140) uniform pickingUniforms {
81
82
  int isActive;
82
83
  int indexMode;
83
84
  int batchIndex;
@@ -91,15 +92,17 @@ uniform pickingUniforms {
91
92
 
92
93
  export const WGSL_UNIFORMS = /* wgsl */ `\
93
94
  struct pickingUniforms {
94
- isActive: int32;
95
- indexMode: int32;
96
- batchIndex: int32;
97
-
98
- isHighlightActive: int32;
99
- highlightedBatchIndex: int32;
100
- highlightedObjectIndex: int32;
101
- highlightColor: vec4<f32>;
102
- } picking;
95
+ isActive: i32,
96
+ indexMode: i32,
97
+ batchIndex: i32,
98
+
99
+ isHighlightActive: i32,
100
+ highlightedBatchIndex: i32,
101
+ highlightedObjectIndex: i32,
102
+ highlightColor: vec4<f32>,
103
+ };
104
+
105
+ @group(0) @binding(auto) var<uniform> picking: pickingUniforms;
103
106
  `;
104
107
 
105
108
  function getUniforms(props: PickingProps = {}, prevUniforms?: PickingUniforms): PickingUniforms {
@@ -114,7 +117,7 @@ function getUniforms(props: PickingProps = {}, prevUniforms?: PickingUniforms):
114
117
  case 'instance':
115
118
  uniforms.indexMode = 0;
116
119
  break;
117
- case 'custom':
120
+ case 'attribute':
118
121
  uniforms.indexMode = 1;
119
122
  break;
120
123
  case undefined:
@@ -122,9 +125,13 @@ function getUniforms(props: PickingProps = {}, prevUniforms?: PickingUniforms):
122
125
  break;
123
126
  }
124
127
 
128
+ if (typeof props.batchIndex === 'number') {
129
+ uniforms.batchIndex = props.batchIndex;
130
+ }
131
+
125
132
  switch (props.highlightedObjectIndex) {
126
133
  case undefined:
127
- // Unless highlightedObjectColor explicitly null or set, do not update state
134
+ // Unless highlighted payload explicitly null or set, do not update state
128
135
  break;
129
136
  case null:
130
137
  // Clear highlight
@@ -136,8 +143,16 @@ function getUniforms(props: PickingProps = {}, prevUniforms?: PickingUniforms):
136
143
  uniforms.highlightedObjectIndex = props.highlightedObjectIndex;
137
144
  }
138
145
 
139
- if (typeof props.highlightedBatchIndex === 'number') {
140
- uniforms.highlightedBatchIndex = props.highlightedBatchIndex;
146
+ switch (props.highlightedBatchIndex) {
147
+ case undefined:
148
+ break;
149
+ case null:
150
+ uniforms.isHighlightActive = false;
151
+ uniforms.highlightedBatchIndex = INVALID_INDEX;
152
+ break;
153
+ default:
154
+ uniforms.isHighlightActive = true;
155
+ uniforms.highlightedBatchIndex = props.highlightedBatchIndex;
141
156
  }
142
157
 
143
158
  if (props.highlightColor) {
@@ -169,7 +184,7 @@ export const pickingUniforms = {
169
184
  isActive: false,
170
185
  indexMode: 0,
171
186
  batchIndex: 0,
172
- isHighlightActive: true,
187
+ isHighlightActive: false,
173
188
  highlightedBatchIndex: INVALID_INDEX,
174
189
  highlightedObjectIndex: INVALID_INDEX,
175
190
  highlightColor: DEFAULT_HIGHLIGHT_COLOR
@@ -0,0 +1,22 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {ShaderModule} from '@luma.gl/shadertools';
6
+
7
+ import type {PickingBindings, PickingProps, PickingUniforms} from './picking-uniforms';
8
+ import {pickingUniforms} from './picking-uniforms';
9
+ import {picking as colorPicking} from './color-picking';
10
+ import {picking as indexPicking} from './index-picking';
11
+
12
+ /**
13
+ * Unified object-picking shader module.
14
+ * Uses color picking on GLSL/WebGL paths and index picking on WGSL/WebGPU paths.
15
+ */
16
+ export const picking = {
17
+ ...pickingUniforms,
18
+ name: 'picking',
19
+ source: indexPicking.source,
20
+ vs: colorPicking.vs,
21
+ fs: colorPicking.fs
22
+ } as const satisfies ShaderModule<PickingProps, PickingUniforms, PickingBindings>;
@@ -36,22 +36,16 @@ export function getFragmentShaderForRenderPass(options: {
36
36
  /** Get a filtering WGSL fragment shader */
37
37
  function getFilterShaderWGSL(func: string) {
38
38
  return /* wgsl */ `\
39
- // Binding 0:1 is reserved for shader passes
40
- // @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
41
- @group(0) @binding(1) var texture: texture_2d<f32>;
42
- @group(0) @binding(2) var sampler: sampler;
43
-
44
- struct FragmentInputs {
45
- @location(0) fragUV: vec2f,
46
- @location(1) fragPosition: vec4f,
47
- @location(2) fragCoordinate: vec4f
48
- };
39
+ @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
40
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
49
41
 
50
42
  @fragment
51
43
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
52
- let texSize = textureDimensions(texture, 0);
53
- var fragColor = textureSample(texture, sampler, fragUV);
54
- fragColor = ${func}(gl_FragColor, texSize, texCoord);
44
+ let texCoord = inputs.coordinate;
45
+ let texSize = vec2f(textureDimensions(sourceTexture));
46
+
47
+ var fragColor = textureSample(sourceTexture, sourceTextureSampler, texCoord);
48
+ fragColor = ${func}(fragColor, texSize, texCoord);
55
49
  return fragColor;
56
50
  }
57
51
  `;
@@ -60,23 +54,14 @@ fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
60
54
  /** Get a sampling WGSL fragment shader */
61
55
  function getSamplerShaderWGSL(func: string) {
62
56
  return /* wgsl */ `\
63
- // Binding 0:1 is reserved for shader passes
64
- @group(0) @binding(0) var<uniform> brightnessContrast : brightnessContrastUniforms;
65
- @group(0) @binding(1) var texture: texture_2d<f32>;
66
- @group(0) @binding(2) var sampler: sampler;
67
-
68
- struct FragmentInputs = {
69
- @location(0) fragUV: vec2f,
70
- @location(1) fragPosition: vec4f,
71
- @location(2) fragCoordinate: vec4f
72
- };
57
+ @group(0) @binding(0) var sourceTexture: texture_2d<f32>;
58
+ @group(0) @binding(2) var sourceTextureSampler: sampler;
73
59
 
74
60
  @fragment
75
61
  fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4f {
76
- let texSize = textureDimensions(texture, 0);
77
- var fragColor = textureSample(texture, sampler, fragUV);
78
- fragColor = ${func}(gl_FragColor, texSize, texCoord);
79
- return fragColor;
62
+ let texCoord = inputs.coordinate;
63
+ let texSize = vec2f(textureDimensions(sourceTexture));
64
+ return ${func}(sourceTexture, sourceTextureSampler, texSize, texCoord);
80
65
  }
81
66
  `;
82
67
  }