1use super::Engine;
4use crate::error::RenderError;
5use crate::passes::{
6 ENTITY_RESOURCE, SEGMENT_LABEL_RESOURCE, SEGMENT_VOLUME_RESOURCE, STRUCTURE_RESOURCE,
7};
8use molgfx_core::{
9 AtomSelection, EntityKind, GlobalPickIdentity, GpuPickToken, PickPageTicket, VolumeSegmentRef,
10};
11use molgfx_gpu::{BufferDesc, BufferUsage, CommandEncoder as _, Device, Queue as _};
12
13const READBACK_BYTES: u32 = 256;
14const PICK_FIELDS: u64 = 4;
15
16#[derive(Clone, Debug)]
18pub struct Pick {
19 pub entity: PickEntity,
21 pub selection: AtomSelection,
23}
24
25#[derive(Clone, Copy, PartialEq, Eq, Debug)]
27pub enum PickEntity {
28 Structure(GlobalPickIdentity),
30 VolumeSegment(VolumeSegmentRef),
32}
33
34#[derive(Debug)]
35pub(crate) struct Picker<D: Device> {
36 readback: D::Buffer,
37 submission: Box<[Option<PickPageTicket>]>,
38}
39
40impl<D: Device> Picker<D> {
41 pub(crate) fn new(device: &D, page_capacity: u32) -> Result<Self, RenderError> {
42 let capacity = usize::try_from(page_capacity)
43 .map_err(|_| molgfx_core::PickingError::CapacityTooLarge)?;
44 let mut submission = Vec::new();
45 submission
46 .try_reserve_exact(capacity)
47 .map_err(|_| molgfx_core::PickingError::AllocationFailed)?;
48 submission.resize(capacity, None);
49 Ok(Self {
50 readback: readback(device, "packed pick readback")?,
51 submission: submission.into_boxed_slice(),
52 })
53 }
54}
55
56impl<D: Device> Engine<D> {
57 pub async fn pick_async(&mut self, x: u32, y: u32) -> Result<Option<Pick>, RenderError> {
65 if !self.record_pick(x, y)? {
66 return Ok(None);
67 }
68 let packed = self
69 .queue
70 .read_buffer_async(
71 &self.device,
72 &self.picker.readback,
73 0,
74 u64::from(READBACK_BYTES) * PICK_FIELDS,
75 )
76 .await?;
77 self.resolve_pick(&packed)
78 }
79
80 #[cfg(not(target_arch = "wasm32"))]
88 pub fn pick(&mut self, x: u32, y: u32) -> Result<Option<Pick>, RenderError> {
89 if !self.record_pick(x, y)? {
90 return Ok(None);
91 }
92 let packed = self.queue.read_buffer_blocking(
93 &self.device,
94 &self.picker.readback,
95 0,
96 u64::from(READBACK_BYTES) * PICK_FIELDS,
97 )?;
98 self.resolve_pick(&packed)
99 }
100
101 fn record_pick(&mut self, x: u32, y: u32) -> Result<bool, RenderError> {
102 if x >= self.width || y >= self.height {
103 return Ok(false);
104 }
105 let Some(pool) = &self.pool else {
106 return Ok(false);
107 };
108 let (
109 Some(entity_texture),
110 Some(structure_texture),
111 Some(segment_volume_texture),
112 Some(segment_label_texture),
113 ) = (
114 pool.texture(ENTITY_RESOURCE),
115 pool.texture(STRUCTURE_RESOURCE),
116 pool.texture(SEGMENT_VOLUME_RESOURCE),
117 pool.texture(SEGMENT_LABEL_RESOURCE),
118 )
119 else {
120 return Ok(false);
121 };
122 self.scene_gpu
123 .capture_pick_submission(&mut self.picker.submission)?;
124 let mut encoder = self.device.create_command_encoder();
125 encoder.copy_texture_to_buffer(
126 entity_texture,
127 (x, y),
128 (1, 1),
129 READBACK_BYTES,
130 0,
131 &self.picker.readback,
132 );
133 encoder.copy_texture_to_buffer(
134 structure_texture,
135 (x, y),
136 (1, 1),
137 READBACK_BYTES,
138 u64::from(READBACK_BYTES),
139 &self.picker.readback,
140 );
141 encoder.copy_texture_to_buffer(
142 segment_volume_texture,
143 (x, y),
144 (1, 1),
145 READBACK_BYTES,
146 u64::from(READBACK_BYTES) * 2,
147 &self.picker.readback,
148 );
149 encoder.copy_texture_to_buffer(
150 segment_label_texture,
151 (x, y),
152 (1, 1),
153 READBACK_BYTES,
154 u64::from(READBACK_BYTES) * 3,
155 &self.picker.readback,
156 );
157 self.queue.submit(encoder);
158 Ok(true)
159 }
160
161 fn resolve_pick(&self, packed: &[u8]) -> Result<Option<Pick>, RenderError> {
162 let local_row = pick_field(packed, 0);
163 let resident_page = pick_field(packed, 1);
164 let segment_volume = pick_field(packed, 2);
165 let segment_label = pick_field(packed, 3);
166 if let (Some(source_id), Some(label)) = (read_u32(segment_volume), read_u32(segment_label))
167 && source_id != u32::MAX
168 && let Some(segment) = self.scene_gpu.resolve_segment(source_id, label)
169 {
170 return Ok(Some(Pick {
171 entity: PickEntity::VolumeSegment(segment),
172 selection: AtomSelection::Empty,
173 }));
174 }
175 let (Some(local_row), Some(resident_page)) = (read_u32(local_row), read_u32(resident_page))
176 else {
177 return Ok(None);
178 };
179 let token = GpuPickToken::new(resident_page, local_row);
180 if token == GpuPickToken::NONE {
181 return Ok(None);
182 }
183 let identity = self
184 .scene_gpu
185 .resolve_global_pick(token, &self.picker.submission)?;
186 let selection = match (identity.kind(), u32::try_from(identity.row().get())) {
187 (EntityKind::Atom, Ok(row)) => match row.checked_add(1) {
188 Some(end) => AtomSelection::Range(row..end),
189 None => AtomSelection::Empty,
190 },
191 _ => AtomSelection::Empty,
192 };
193 Ok(Some(Pick {
194 entity: PickEntity::Structure(identity),
195 selection,
196 }))
197 }
198
199 #[cfg(test)]
200 pub(crate) fn capture_pick_submission_for_test(&mut self) -> Result<(), RenderError> {
201 self.scene_gpu
202 .capture_pick_submission(&mut self.picker.submission)
203 }
204
205 #[cfg(test)]
206 pub(crate) fn resolve_pick_token_for_test(
207 &self,
208 token: GpuPickToken,
209 ) -> Result<GlobalPickIdentity, RenderError> {
210 self.scene_gpu
211 .resolve_global_pick(token, &self.picker.submission)
212 }
213}
214
215fn readback<D: Device>(device: &D, label: &'static str) -> Result<D::Buffer, RenderError> {
216 Ok(device.create_buffer(&BufferDesc {
217 label,
218 size: u64::from(READBACK_BYTES) * PICK_FIELDS,
219 usage: BufferUsage::COPY_DST.union(BufferUsage::MAP_READ),
220 })?)
221}
222
223fn pick_field(bytes: &[u8], index: usize) -> &[u8] {
224 let width = READBACK_BYTES as usize;
225 let start = index.saturating_mul(width);
226 let end = start.saturating_add(width).min(bytes.len());
227 match bytes.get(start..end) {
228 Some(field) => field,
229 None => &[],
230 }
231}
232
233fn read_u32(bytes: &[u8]) -> Option<u32> {
234 let slice = bytes.get(..std::mem::size_of::<u32>())?;
235 let mut array = [0; std::mem::size_of::<u32>()];
236 array.copy_from_slice(slice);
237 Some(u32::from_le_bytes(array))
238}