1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! **The host lane for GPU picking** (feature `wgpu`) — the one entry point a widget
//! calls to turn a click into a [`PickId`], and the install-once lifecycle that makes
//! it work.
//!
//! # Why this is NOT an `egui_wgpu::CallbackTrait`
//!
//! Every other GPU lane in the tree (`graphcloud::cloud`, `flowsim::gpu::host`,
//! `facett-map`, `facett-map3d`) is a paint callback: a persistent renderer in
//! `callback_resources`, an `install_*`, a `prepare` that encodes and a `paint` that
//! blits. This module keeps **three of those four** and deliberately drops the fourth,
//! because picking is a **query, not a paint**:
//!
//! * the id target is never composited — compositing it would put raw id numbers on
//! screen as colours;
//! * the answer must come back on the *same* click, and a `CallbackTrait::prepare`
//! records into an encoder egui submits *after* `prepare` returns, so a readback
//! there is either a frame late or has to submit its own work anyway;
//! * a pick runs **once per click**, not once per frame, so hanging it off the paint
//! loop would run an entire extra pass on every frame that nothing was clicked.
//!
//! What is kept, because it is the part that carries the value:
//!
//! * **the persistent renderer** — [`PickHost`] lives in the `RenderState`'s
//! `callback_resources` via the shared
//! [`install_renderer`](super::install_renderer), so the pipeline, the shader module
//! and the id texture survive between clicks instead of being rebuilt per query;
//! * **an install fn + an installed latch** — [`install_pick_host`] /
//! [`pick_host_installed`], the same gate `draw_flow` uses;
//! * **one fail-safe entry point** — [`pick_at`] returns `Option<PickId>`, and `None`
//! means *"the GPU lane did not answer, use your CPU picker"*. It never invents a
//! miss, because a fabricated `NOTHING` is indistinguishable from a real click on
//! empty space and would silently deselect instead of falling back.
//!
//! # Cost, stated plainly
//!
//! [`pick_at`] submits an id pass and **blocks** on a one-texel readback
//! (`device.poll(wait_indefinitely)`). That is a GPU round-trip on the calling thread,
//! roughly a frame's worth of latency, once per click. It is the same trade deck.gl
//! makes, and it is why the pass is gated on a probe existing rather than run
//! speculatively every frame.
use ;
use cratePickId;
use Pos2;
use ;
/// Has a host installed the pick lane in this process? Read this before letting a
/// widget rely on GPU picking — see [`pick_at`]'s gate 1.
static INSTALLED: AtomicBool = new;
/// Id passes actually submitted to a device in this process.
static PASSES: AtomicU64 = new;
/// `true` once a host has installed the pick lane. Monotonic (installing is a startup
/// act, never undone).
/// **How many id passes have genuinely been submitted to a device**, process-wide.
///
/// Lane ATTRIBUTION, not a correctness oracle: it says *which* lane answered, which
/// the returned id alone cannot — a CPU fallback and a GPU hit can return the very
/// same `PickId`, and that is the whole point of them agreeing. Only meaningful PAIRED
/// with an id assertion; on its own it is the state round-trip LAW 2 forbids, since a
/// pass over an empty batch would still bump it.
///
/// Incremented only after a pass has been recorded and submitted against a real
/// device with a probe in range.
/// The persistent pick resources: the id target and the pipeline, kept across clicks.
///
/// Holds **both** a colour-only and a depth-capable pass, built lazily, because a
/// pipeline's depth state must match the target's attachments — and one host may serve
/// a 2-D pane (painter order) and a 3-D pane (camera occlusion) in the same frame.
/// Install a [`PickHost`] into an egui-wgpu `RenderState` — call once at host startup,
/// like `install_flow_renderer` / `facett-map`'s install. Idempotent. Also arms
/// [`pick_host_installed`], which is what unlocks [`pick_at`].
///
/// Returns `true` if a host was newly installed.
/// **THE one GPU-pick entry point a widget calls.** Resolve the click at `probe`
/// (logical points, relative to the widget's top-left) against `batch`.
///
/// `Some(id)` is the device's answer — possibly [`PickId::NOTHING`], which is a real
/// "you clicked empty space". `None` means **the GPU lane declined**, and the caller
/// must fall back to its CPU [`PickIndex`](crate::engine::pick::PickIndex). The
/// distinction is load-bearing: returning a fabricated `NOTHING` on failure would
/// silently *deselect* instead of falling back, and look exactly like a legitimate
/// click on the background.
///
/// The gates, each returning `None`:
/// 1. **no host installed** — the same trap `draw_flow` documents: opting in without
/// installing means nothing answers, and a caller that already skipped its CPU
/// picker would silently stop responding to clicks;
/// 2. **empty batch** — nothing is pickable, so there is nothing for the device to
/// say that the caller does not already know;
/// 3. **degenerate pane** (`< 1 px`);
/// 4. **probe outside the pane**, including negative logical coordinates.
/// `pane_px` is the widget's extent in **physical** pixels — the same space
/// [`PickVertex::pos_px`](super::picking::PickVertex::pos_px) is in. It is a parameter
/// rather than read off the surface for the reason `flowsim::gpu::host` records at
/// length: `egui_wgpu` works in the **widget rect**, and a lane built against the
/// screen size instead was measured **32.6 px** out of place while every pixel
/// assertion stayed green. For picking the same mistake is worse than a visual offset —
/// it is a click that silently resolves the wrong object.