1use super::*;
2
3#[derive(Default)]
6pub(crate) struct GlyphResources {
7 pub(crate) pipeline: Option<DualPipeline>,
9 pub(crate) wireframe_pipeline: Option<DualPipeline>,
11 pub(crate) bgl: Option<wgpu::BindGroupLayout>,
13 pub(crate) instance_bgl: Option<wgpu::BindGroupLayout>,
15 pub(crate) arrow_mesh: Option<GlyphBaseMesh>,
17 pub(crate) sphere_mesh: Option<GlyphBaseMesh>,
19 pub(crate) cube_mesh: Option<GlyphBaseMesh>,
21 pub(crate) outline_mask_pipeline: Option<wgpu::RenderPipeline>,
23}
24
25#[derive(Default)]
27pub(crate) struct TensorGlyphResources {
28 pub(crate) pipeline: Option<DualPipeline>,
30 pub(crate) wireframe_pipeline: Option<DualPipeline>,
32 pub(crate) bgl: Option<wgpu::BindGroupLayout>,
34 pub(crate) instance_bgl: Option<wgpu::BindGroupLayout>,
36 pub(crate) outline_mask_pipeline: Option<wgpu::RenderPipeline>,
38}
39
40impl DeviceResources {
41 pub(crate) fn ensure_glyph_pipeline(&mut self, device: &wgpu::Device) {
45 if self.glyph.pipeline.is_some() {
46 return;
47 }
48
49 let glyph_bgl = crate::resources::builders::uniform_texture_sampler_bgl(
50 device,
51 "glyph_bgl",
52 wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
53 wgpu::ShaderStages::VERTEX,
54 );
55
56 let glyph_instance_bgl =
57 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
58 label: Some("glyph_instance_bgl"),
59 entries: &[wgpu::BindGroupLayoutEntry {
60 binding: 0,
61 visibility: wgpu::ShaderStages::VERTEX,
62 ty: wgpu::BindingType::Buffer {
63 ty: wgpu::BufferBindingType::Storage { read_only: true },
64 has_dynamic_offset: false,
65 min_binding_size: None,
66 },
67 count: None,
68 }],
69 });
70
71 let shader = crate::resources::builders::wgsl_module(
72 device,
73 "glyph_shader",
74 crate::resources::builders::wgsl_source!("glyph"),
75 );
76
77 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
78 label: Some("glyph_pipeline_layout"),
79 bind_group_layouts: &[
80 &self.camera_bind_group_layout,
81 &glyph_bgl,
82 &glyph_instance_bgl,
83 ],
84 push_constant_ranges: &[],
85 });
86
87 self.glyph.bgl = Some(glyph_bgl);
88 self.glyph.instance_bgl = Some(glyph_instance_bgl);
89 self.glyph.pipeline = Some(crate::resources::builders::build_dual_pipeline(
90 device,
91 &crate::resources::builders::DualPipelineDesc {
92 label: "glyph_pipeline",
93 layout: &layout,
94 shader: &shader,
95 vertex_entry: "vs_main",
96 fragment_entry: "fs_main",
97 vertex_buffers: &[Vertex::buffer_layout()],
98 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
99 topology: wgpu::PrimitiveTopology::TriangleList,
100 cull_mode: Some(wgpu::Face::Back),
101 depth_write: true,
102 depth_compare: wgpu::CompareFunction::Less,
103 sample_count: self.sample_count,
104 ldr_format: self.target_format,
105 },
106 ));
107
108 self.glyph.wireframe_pipeline = Some(crate::resources::builders::build_dual_pipeline(
110 device,
111 &crate::resources::builders::DualPipelineDesc {
112 label: "glyph_wireframe_pipeline",
113 layout: &layout,
114 shader: &shader,
115 vertex_entry: "vs_main",
116 fragment_entry: "fs_main",
117 vertex_buffers: &[Vertex::buffer_layout()],
118 blend: None,
119 topology: wgpu::PrimitiveTopology::LineList,
120 cull_mode: None,
121 depth_write: true,
122 depth_compare: wgpu::CompareFunction::Less,
123 sample_count: self.sample_count,
124 ldr_format: self.target_format,
125 },
126 ));
127 }
128
129 pub(crate) fn upload_glyph_set_per_frame(
134 &mut self,
135 device: &wgpu::Device,
136 queue: &wgpu::Queue,
137 item: &crate::renderer::GlyphItem,
138 wireframe: bool,
139 ) -> GlyphGpuData {
140 let instance_count = item.positions.len() as u32;
141
142 self.ensure_glyph_mesh(device, item.glyph_type);
143
144 let (mesh_vbuf, mesh_ibuf, mesh_idx_count, mesh_edge_ibuf, mesh_edge_count) = {
145 let mesh = match item.glyph_type {
146 crate::renderer::GlyphType::Arrow => self.glyph.arrow_mesh.as_ref(),
147 crate::renderer::GlyphType::Sphere => self.glyph.sphere_mesh.as_ref(),
148 crate::renderer::GlyphType::Cube => self.glyph.cube_mesh.as_ref(),
149 }
150 .expect("glyph mesh should have been created by ensure_glyph_mesh");
151
152 let vbuf: &'static wgpu::Buffer = unsafe { &*(&mesh.vertex_buffer as *const _) };
153 let ibuf: &'static wgpu::Buffer = unsafe { &*(&mesh.index_buffer as *const _) };
154 let eibuf: &'static wgpu::Buffer = unsafe { &*(&mesh.edge_index_buffer as *const _) };
155 (vbuf, ibuf, mesh.index_count, eibuf, mesh.edge_index_count)
156 };
157
158 let mags: Vec<f32> = item
159 .vectors
160 .iter()
161 .map(|v| glam::Vec3::from(*v).length())
162 .collect();
163
164 let (scalar_min, scalar_max) = if !item.scalars.is_empty() {
165 item.scalar_range.unwrap_or_else(|| {
166 let min = item.scalars.iter().cloned().fold(f32::INFINITY, f32::min);
167 let max = item
168 .scalars
169 .iter()
170 .cloned()
171 .fold(f32::NEG_INFINITY, f32::max);
172 (min, max)
173 })
174 } else {
175 item.scalar_range.unwrap_or_else(|| {
176 let min = mags.iter().cloned().fold(f32::INFINITY, f32::min);
177 let max = mags.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
178 (min, max)
179 })
180 };
181
182 let (mag_clamp_min, mag_clamp_max, has_mag_clamp) = item
183 .magnitude_clamp
184 .map(|(mn, mx)| (mn, mx, 1u32))
185 .unwrap_or((0.0, 1.0, 0u32));
186
187 #[repr(C)]
188 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
189 struct GlyphInstance {
190 position: [f32; 3],
191 _pad0: f32,
192 direction: [f32; 3],
193 scalar: f32,
194 }
195
196 let instances: Vec<GlyphInstance> = (0..item.positions.len())
197 .map(|i| GlyphInstance {
198 position: item.positions[i],
199 _pad0: 0.0,
200 direction: item.vectors.get(i).copied().unwrap_or([0.0, 0.0, 1.0]),
201 scalar: item
202 .scalars
203 .get(i)
204 .copied()
205 .unwrap_or(mags.get(i).copied().unwrap_or(0.0)),
206 })
207 .collect();
208
209 let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
210 label: Some("glyph_instance_buf"),
211 size: (std::mem::size_of::<GlyphInstance>() * instances.len()).max(32) as u64,
212 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
213 mapped_at_creation: false,
214 });
215 queue.write_buffer(&instance_buf, 0, bytemuck::cast_slice(&instances));
216
217 #[repr(C)]
218 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
219 struct GlyphUniform {
220 model: [[f32; 4]; 4],
221 global_scale: f32,
222 scale_by_magnitude: u32,
223 has_scalars: u32,
224 scalar_min: f32,
225 scalar_max: f32,
226 mag_clamp_min: f32,
227 mag_clamp_max: f32,
228 has_mag_clamp: u32,
229 default_colour: [f32; 4],
230 use_default_colour: u32,
231 unlit: u32,
232 opacity: f32,
233 wireframe: u32,
234 }
235 let uniform_data = GlyphUniform {
236 model: item.model,
237 global_scale: item.scale,
238 scale_by_magnitude: if item.scale_by_magnitude { 1 } else { 0 },
239 has_scalars: if !item.scalars.is_empty() { 1 } else { 0 },
240 scalar_min,
241 scalar_max,
242 mag_clamp_min,
243 mag_clamp_max,
244 has_mag_clamp,
245 default_colour: item.default_colour,
246 use_default_colour: if item.default_colour[3] > 0.0 && item.use_default_colour {
247 1
248 } else {
249 0
250 },
251 unlit: if item.settings.unlit { 1 } else { 0 },
252 opacity: item.settings.opacity,
253 wireframe: if wireframe { 1 } else { 0 },
254 };
255 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
256 label: Some("glyph_uniform_buf"),
257 size: std::mem::size_of::<GlyphUniform>() as u64,
258 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
259 mapped_at_creation: false,
260 });
261 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
262
263 let lut_view = self
264 .content
265 .builtin_colourmap_ids
266 .and_then(|ids| {
267 let preset_id = item
268 .colourmap_id
269 .unwrap_or(ids[crate::resources::BuiltinColourmap::Viridis as usize]);
270 self.content.colourmap_views.get(preset_id.0)
271 })
272 .unwrap_or(&self.content.fallback_lut_view);
273
274 let lut_sampler = &self.material_sampler;
275
276 let bgl1 = self
277 .glyph
278 .bgl
279 .as_ref()
280 .expect("ensure_glyph_pipeline not called");
281 let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
282 label: Some("glyph_uniform_bg"),
283 layout: bgl1,
284 entries: &[
285 wgpu::BindGroupEntry {
286 binding: 0,
287 resource: uniform_buf.as_entire_binding(),
288 },
289 wgpu::BindGroupEntry {
290 binding: 1,
291 resource: wgpu::BindingResource::TextureView(lut_view),
292 },
293 wgpu::BindGroupEntry {
294 binding: 2,
295 resource: wgpu::BindingResource::Sampler(lut_sampler),
296 },
297 ],
298 });
299
300 let bgl2 = self
301 .glyph
302 .instance_bgl
303 .as_ref()
304 .expect("ensure_glyph_pipeline not called");
305 let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
306 label: Some("glyph_instance_bg"),
307 layout: bgl2,
308 entries: &[wgpu::BindGroupEntry {
309 binding: 0,
310 resource: instance_buf.as_entire_binding(),
311 }],
312 });
313
314 GlyphGpuData {
315 mesh_vertex_buffer: mesh_vbuf,
316 mesh_index_buffer: mesh_ibuf,
317 mesh_index_count: mesh_idx_count,
318 mesh_edge_index_buffer: mesh_edge_ibuf,
319 mesh_edge_index_count: mesh_edge_count,
320 instance_count,
321 wireframe,
322 uniform_bind_group,
323 instance_bind_group,
324 _uniform_buf: uniform_buf,
325 _instance_buf: instance_buf,
326 }
327 }
328
329 fn ensure_glyph_mesh(&mut self, device: &wgpu::Device, glyph_type: crate::renderer::GlyphType) {
332 use crate::renderer::GlyphType;
333
334 let already_cached = match glyph_type {
335 GlyphType::Arrow => self.glyph.arrow_mesh.is_some(),
336 GlyphType::Sphere => self.glyph.sphere_mesh.is_some(),
337 GlyphType::Cube => self.glyph.cube_mesh.is_some(),
338 };
339 if already_cached {
340 return;
341 }
342
343 let (verts, indices) = match glyph_type {
344 GlyphType::Arrow => build_glyph_arrow(),
345 GlyphType::Sphere => build_glyph_sphere(),
346 GlyphType::Cube => build_unit_cube(),
347 };
348
349 let vbuf = device.create_buffer(&wgpu::BufferDescriptor {
350 label: Some("glyph_mesh_vbuf"),
351 size: (std::mem::size_of::<Vertex>() * verts.len()).max(64) as u64,
352 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
353 mapped_at_creation: true,
354 });
355 vbuf.slice(..)
356 .get_mapped_range_mut()
357 .copy_from_slice(bytemuck::cast_slice(&verts));
358 vbuf.unmap();
359
360 let ibuf = device.create_buffer(&wgpu::BufferDescriptor {
361 label: Some("glyph_mesh_ibuf"),
362 size: (std::mem::size_of::<u32>() * indices.len()).max(12) as u64,
363 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
364 mapped_at_creation: true,
365 });
366 ibuf.slice(..)
367 .get_mapped_range_mut()
368 .copy_from_slice(bytemuck::cast_slice(&indices));
369 ibuf.unmap();
370
371 let edge_indices = crate::resources::mesh::geometry::generate_edge_indices(&indices);
372 let edge_buf_size = (std::mem::size_of::<u32>() * edge_indices.len().max(2)) as u64;
373 let edge_ibuf = device.create_buffer(&wgpu::BufferDescriptor {
374 label: Some("glyph_mesh_edge_ibuf"),
375 size: edge_buf_size,
376 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
377 mapped_at_creation: true,
378 });
379 {
380 let mut mapped = edge_ibuf.slice(..).get_mapped_range_mut();
381 let bytes = bytemuck::cast_slice::<u32, u8>(&edge_indices);
382 mapped[..bytes.len()].copy_from_slice(bytes);
383 }
384 edge_ibuf.unmap();
385
386 let mesh = GlyphBaseMesh {
387 vertex_buffer: vbuf,
388 index_buffer: ibuf,
389 index_count: indices.len() as u32,
390 edge_index_buffer: edge_ibuf,
391 edge_index_count: edge_indices.len() as u32,
392 };
393
394 match glyph_type {
395 GlyphType::Arrow => self.glyph.arrow_mesh = Some(mesh),
396 GlyphType::Sphere => self.glyph.sphere_mesh = Some(mesh),
397 GlyphType::Cube => self.glyph.cube_mesh = Some(mesh),
398 }
399 }
400
401 pub(crate) fn ensure_tensor_glyph_pipeline(&mut self, device: &wgpu::Device) {
406 if self.tensor_glyph.pipeline.is_some() {
407 return;
408 }
409
410 let tg_bgl = crate::resources::builders::uniform_texture_sampler_bgl(
411 device,
412 "tensor_glyph_bgl",
413 wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
414 wgpu::ShaderStages::VERTEX,
415 );
416
417 let tg_instance_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
418 label: Some("tensor_glyph_instance_bgl"),
419 entries: &[wgpu::BindGroupLayoutEntry {
420 binding: 0,
421 visibility: wgpu::ShaderStages::VERTEX,
422 ty: wgpu::BindingType::Buffer {
423 ty: wgpu::BufferBindingType::Storage { read_only: true },
424 has_dynamic_offset: false,
425 min_binding_size: None,
426 },
427 count: None,
428 }],
429 });
430
431 let shader = crate::resources::builders::wgsl_module(
432 device,
433 "tensor_glyph_shader",
434 crate::resources::builders::wgsl_source!("tensor_glyph"),
435 );
436
437 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
438 label: Some("tensor_glyph_pipeline_layout"),
439 bind_group_layouts: &[&self.camera_bind_group_layout, &tg_bgl, &tg_instance_bgl],
440 push_constant_ranges: &[],
441 });
442
443 self.tensor_glyph.bgl = Some(tg_bgl);
444 self.tensor_glyph.instance_bgl = Some(tg_instance_bgl);
445 self.tensor_glyph.pipeline = Some(crate::resources::builders::build_dual_pipeline(
446 device,
447 &crate::resources::builders::DualPipelineDesc {
448 label: "tensor_glyph_pipeline",
449 layout: &layout,
450 shader: &shader,
451 vertex_entry: "vs_main",
452 fragment_entry: "fs_main",
453 vertex_buffers: &[Vertex::buffer_layout()],
454 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
455 topology: wgpu::PrimitiveTopology::TriangleList,
456 cull_mode: Some(wgpu::Face::Back),
457 depth_write: true,
458 depth_compare: wgpu::CompareFunction::Less,
459 sample_count: self.sample_count,
460 ldr_format: self.target_format,
461 },
462 ));
463
464 self.tensor_glyph.wireframe_pipeline =
466 Some(crate::resources::builders::build_dual_pipeline(
467 device,
468 &crate::resources::builders::DualPipelineDesc {
469 label: "tensor_glyph_wireframe_pipeline",
470 layout: &layout,
471 shader: &shader,
472 vertex_entry: "vs_main",
473 fragment_entry: "fs_main",
474 vertex_buffers: &[Vertex::buffer_layout()],
475 blend: None,
476 topology: wgpu::PrimitiveTopology::LineList,
477 cull_mode: None,
478 depth_write: true,
479 depth_compare: wgpu::CompareFunction::Less,
480 sample_count: self.sample_count,
481 ldr_format: self.target_format,
482 },
483 ));
484 }
485
486 pub(crate) fn upload_tensor_glyph_set_per_frame(
491 &mut self,
492 device: &wgpu::Device,
493 queue: &wgpu::Queue,
494 item: &crate::renderer::TensorGlyphItem,
495 wireframe: bool,
496 ) -> TensorGlyphGpuData {
497 use crate::renderer::GlyphType;
498
499 let instance_count = item.positions.len() as u32;
500
501 self.ensure_glyph_mesh(device, GlyphType::Sphere);
503 let (mesh_vbuf, mesh_ibuf, mesh_idx_count, mesh_edge_ibuf, mesh_edge_count) = {
504 let mesh = self
505 .glyph
506 .sphere_mesh
507 .as_ref()
508 .expect("sphere mesh should be present after ensure_glyph_mesh");
509 let vbuf: &'static wgpu::Buffer = unsafe { &*(&mesh.vertex_buffer as *const _) };
510 let ibuf: &'static wgpu::Buffer = unsafe { &*(&mesh.index_buffer as *const _) };
511 let eibuf: &'static wgpu::Buffer = unsafe { &*(&mesh.edge_index_buffer as *const _) };
512 (vbuf, ibuf, mesh.index_count, eibuf, mesh.edge_index_count)
513 };
514
515 #[repr(C)]
517 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
518 struct TensorInstance {
519 model_col0: [f32; 4],
520 model_col1: [f32; 4],
521 model_col2: [f32; 4],
522 model_col3: [f32; 4],
523 normal_col0: [f32; 4],
524 normal_col1: [f32; 4],
525 normal_col2: [f32; 4],
526 scalar: f32,
527 _pad: [f32; 3],
528 }
529
530 let has_scalars = item.colour_attribute.is_some();
537 let (scalar_min, scalar_max) = if let Some(ref scalars) = item.colour_attribute {
538 item.scalar_range.unwrap_or_else(|| {
539 let mn = scalars.iter().cloned().fold(f32::INFINITY, f32::min);
540 let mx = scalars.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
541 (mn, mx)
542 })
543 } else {
544 item.scalar_range.unwrap_or((-1.0, 1.0))
546 };
547
548 let instances: Vec<TensorInstance> = (0..item.positions.len())
549 .map(|i| {
550 let pos = glam::Vec3::from(item.positions[i]);
551 let ev = if i < item.eigenvalues.len() {
552 item.eigenvalues[i]
553 } else {
554 [1.0, 1.0, 1.0]
555 };
556 let vecs = if i < item.eigenvectors.len() {
557 item.eigenvectors[i]
558 } else {
559 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
560 };
561
562 let s0 = (ev[0].abs() * item.scale).max(1e-6_f32);
564 let s1 = (ev[1].abs() * item.scale).max(1e-6_f32);
565 let s2 = (ev[2].abs() * item.scale).max(1e-6_f32);
566
567 let col0 = glam::Vec3::from(vecs[0]);
569 let col1 = glam::Vec3::from(vecs[1]);
570 let col2 = glam::Vec3::from(vecs[2]);
571
572 let rs = glam::Mat3::from_cols(col0 * s0, col1 * s1, col2 * s2);
574
575 let local_model = glam::Mat4::from_mat3(rs) * glam::Mat4::IDENTITY;
577 let mut world_model = local_model;
578 world_model.w_axis = glam::Vec4::new(pos.x, pos.y, pos.z, 1.0);
579
580 let nm = glam::Mat3::from_cols(col0 / s0, col1 / s1, col2 / s2);
582
583 let scalar = if has_scalars {
585 item.colour_attribute
586 .as_ref()
587 .and_then(|sc| sc.get(i))
588 .copied()
589 .unwrap_or(0.0)
590 } else {
591 if i < item.eigenvalues.len() {
593 item.eigenvalues[i][0]
594 } else {
595 0.0
596 }
597 };
598
599 let mc = world_model.to_cols_array_2d();
600 TensorInstance {
601 model_col0: mc[0],
602 model_col1: mc[1],
603 model_col2: mc[2],
604 model_col3: mc[3],
605 normal_col0: [nm.x_axis.x, nm.x_axis.y, nm.x_axis.z, 0.0],
606 normal_col1: [nm.y_axis.x, nm.y_axis.y, nm.y_axis.z, 0.0],
607 normal_col2: [nm.z_axis.x, nm.z_axis.y, nm.z_axis.z, 0.0],
608 scalar,
609 _pad: [0.0; 3],
610 }
611 })
612 .collect();
613
614 let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
615 label: Some("tensor_glyph_instance_buf"),
616 size: (std::mem::size_of::<TensorInstance>() * instances.len()).max(128) as u64,
617 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
618 mapped_at_creation: false,
619 });
620 queue.write_buffer(&instance_buf, 0, bytemuck::cast_slice(&instances));
621
622 #[repr(C)]
623 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
624 struct TensorGlyphUniform {
625 model: [[f32; 4]; 4],
626 has_scalars: u32,
627 scalar_min: f32,
628 scalar_max: f32,
629 unlit: u32,
630 opacity: f32,
631 wireframe: u32,
632 _pad1b: f32,
633 _pad1c: f32,
634 _pad2: [[f32; 4]; 2],
635 }
636 let uniform_data = TensorGlyphUniform {
637 model: item.model,
638 has_scalars: if has_scalars { 1 } else { 0 },
639 scalar_min,
640 scalar_max,
641 unlit: item.settings.unlit as u32,
642 opacity: item.settings.opacity,
643 wireframe: if wireframe { 1 } else { 0 },
644 _pad1b: 0.0,
645 _pad1c: 0.0,
646 _pad2: [[0.0; 4]; 2],
647 };
648 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
649 label: Some("tensor_glyph_uniform_buf"),
650 size: std::mem::size_of::<TensorGlyphUniform>() as u64,
651 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
652 mapped_at_creation: false,
653 });
654 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
655
656 let lut_view = self
657 .content
658 .builtin_colourmap_ids
659 .and_then(|ids| {
660 let preset_id = item
661 .colourmap_id
662 .unwrap_or(ids[crate::resources::BuiltinColourmap::Viridis as usize]);
663 self.content.colourmap_views.get(preset_id.0)
664 })
665 .unwrap_or(&self.content.fallback_lut_view);
666
667 let lut_sampler = &self.material_sampler;
668
669 let bgl1 = self
670 .tensor_glyph
671 .bgl
672 .as_ref()
673 .expect("ensure_tensor_glyph_pipeline not called");
674 let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
675 label: Some("tensor_glyph_uniform_bg"),
676 layout: bgl1,
677 entries: &[
678 wgpu::BindGroupEntry {
679 binding: 0,
680 resource: uniform_buf.as_entire_binding(),
681 },
682 wgpu::BindGroupEntry {
683 binding: 1,
684 resource: wgpu::BindingResource::TextureView(lut_view),
685 },
686 wgpu::BindGroupEntry {
687 binding: 2,
688 resource: wgpu::BindingResource::Sampler(lut_sampler),
689 },
690 ],
691 });
692
693 let bgl2 = self
694 .tensor_glyph
695 .instance_bgl
696 .as_ref()
697 .expect("ensure_tensor_glyph_pipeline not called");
698 let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
699 label: Some("tensor_glyph_instance_bg"),
700 layout: bgl2,
701 entries: &[wgpu::BindGroupEntry {
702 binding: 0,
703 resource: instance_buf.as_entire_binding(),
704 }],
705 });
706
707 TensorGlyphGpuData {
708 mesh_vertex_buffer: mesh_vbuf,
709 mesh_index_buffer: mesh_ibuf,
710 mesh_index_count: mesh_idx_count,
711 mesh_edge_index_buffer: mesh_edge_ibuf,
712 mesh_edge_index_count: mesh_edge_count,
713 instance_count,
714 wireframe,
715 uniform_bind_group,
716 instance_bind_group,
717 _uniform_buf: uniform_buf,
718 _instance_buf: instance_buf,
719 }
720 }
721
722 pub(crate) fn ensure_glyph_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
729 if self.glyph.outline_mask_pipeline.is_some() {
730 return;
731 }
732 let glyph_bgl = self
733 .glyph
734 .bgl
735 .as_ref()
736 .expect("ensure_glyph_pipeline must be called first");
737 let glyph_instance_bgl = self
738 .glyph
739 .instance_bgl
740 .as_ref()
741 .expect("ensure_glyph_pipeline must be called first");
742
743 let shader = crate::resources::builders::wgsl_module(
744 device,
745 "glyph_outline_mask_shader",
746 crate::resources::builders::wgsl_source!("glyph_outline_mask"),
747 );
748
749 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
750 label: Some("glyph_outline_mask_pipeline_layout"),
751 bind_group_layouts: &[
752 &self.camera_bind_group_layout,
753 glyph_bgl,
754 glyph_instance_bgl,
755 ],
756 push_constant_ranges: &[],
757 });
758
759 self.glyph.outline_mask_pipeline =
760 Some(crate::resources::builders::build_outline_mask_pipeline(
761 device,
762 "glyph_outline_mask_pipeline",
763 &layout,
764 &shader,
765 wgpu::TextureFormat::R8Unorm,
766 &[Vertex::buffer_layout()],
767 Some(wgpu::Face::Back),
768 true,
769 wgpu::CompareFunction::Less,
770 ));
771 }
772
773 pub(crate) fn ensure_tensor_glyph_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
778 if self.tensor_glyph.outline_mask_pipeline.is_some() {
779 return;
780 }
781 let tg_bgl = self
782 .tensor_glyph
783 .bgl
784 .as_ref()
785 .expect("ensure_tensor_glyph_pipeline must be called first");
786 let tg_instance_bgl = self
787 .tensor_glyph
788 .instance_bgl
789 .as_ref()
790 .expect("ensure_tensor_glyph_pipeline must be called first");
791
792 let shader = crate::resources::builders::wgsl_module(
793 device,
794 "tensor_glyph_outline_mask_shader",
795 crate::resources::builders::wgsl_source!("tensor_glyph_outline_mask"),
796 );
797
798 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
799 label: Some("tensor_glyph_outline_mask_pipeline_layout"),
800 bind_group_layouts: &[&self.camera_bind_group_layout, tg_bgl, tg_instance_bgl],
801 push_constant_ranges: &[],
802 });
803
804 self.tensor_glyph.outline_mask_pipeline =
805 Some(crate::resources::builders::build_outline_mask_pipeline(
806 device,
807 "tensor_glyph_outline_mask_pipeline",
808 &layout,
809 &shader,
810 wgpu::TextureFormat::R8Unorm,
811 &[Vertex::buffer_layout()],
812 Some(wgpu::Face::Back),
813 true,
814 wgpu::CompareFunction::Less,
815 ));
816 }
817
818 pub fn upload_glyph_set(
820 &mut self,
821 device: &wgpu::Device,
822 queue: &wgpu::Queue,
823 item: &crate::renderer::GlyphItem,
824 ) -> crate::resources::GlyphSetId {
825 self.ensure_glyph_pipeline(device);
826 let gpu = self.upload_glyph_set_per_frame(device, queue, item, false);
827 self.content.glyph_set_store.insert(gpu)
828 }
829
830 pub fn drop_glyph_set(&mut self, id: crate::resources::GlyphSetId) -> bool {
832 self.content.glyph_set_store.remove(id)
833 }
834
835 pub fn replace_glyph_set(
837 &mut self,
838 device: &wgpu::Device,
839 queue: &wgpu::Queue,
840 id: crate::resources::GlyphSetId,
841 item: &crate::renderer::GlyphItem,
842 ) -> bool {
843 if !self.content.glyph_set_store.contains(id) {
844 return false;
845 }
846 self.ensure_glyph_pipeline(device);
847 let gpu = self.upload_glyph_set_per_frame(device, queue, item, false);
848 self.content.glyph_set_store.replace(id, gpu)
849 }
850
851 pub fn begin_upload_glyph_set(
853 &mut self,
854 device: &wgpu::Device,
855 queue: &wgpu::Queue,
856 item: crate::renderer::GlyphItem,
857 ) -> crate::resources::JobId {
858 let slot = crate::resources::ResultSlot::<crate::resources::GlyphSetId>::new();
859 let slot_for_apply = slot.clone();
860 let device_for_apply = device.clone();
861 let queue_for_apply = queue.clone();
862 let id = {
863 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
864 runner.submit_cpu(move |progress| {
865 progress.set(0.9);
866 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
867 Box::new(move |resources: &mut DeviceResources| {
868 let gid =
869 resources.upload_glyph_set(&device_for_apply, &queue_for_apply, &item);
870 slot_for_apply.set(gid);
871 }),
872 ))
873 })
874 };
875 self.job_results
876 .glyph_set
877 .lock()
878 .expect("glyph set result map poisoned")
879 .insert(id, slot);
880 id
881 }
882
883 pub fn upload_result_glyph_set(
886 &mut self,
887 id: crate::resources::JobId,
888 ) -> crate::error::ViewportResult<crate::resources::GlyphSetId> {
889 let mut map = self
890 .job_results
891 .glyph_set
892 .lock()
893 .expect("glyph set result map poisoned");
894 let slot = match map.get(&id) {
895 Some(s) => s.clone(),
896 None => {
897 return Err(crate::error::ViewportError::JobResultMissing {
898 reason: "unknown id or wrong upload type",
899 });
900 }
901 };
902 match slot.take() {
903 Some(gid) => {
904 map.remove(&id);
905 Ok(gid)
906 }
907 None => Err(crate::error::ViewportError::JobNotReady),
908 }
909 }
910
911 pub fn upload_tensor_glyph_set(
913 &mut self,
914 device: &wgpu::Device,
915 queue: &wgpu::Queue,
916 item: &crate::renderer::TensorGlyphItem,
917 ) -> crate::resources::TensorGlyphSetId {
918 self.ensure_tensor_glyph_pipeline(device);
919 let gpu = self.upload_tensor_glyph_set_per_frame(device, queue, item, false);
920 self.content.tensor_glyph_set_store.insert(gpu)
921 }
922
923 pub fn drop_tensor_glyph_set(&mut self, id: crate::resources::TensorGlyphSetId) -> bool {
925 self.content.tensor_glyph_set_store.remove(id)
926 }
927
928 pub fn replace_tensor_glyph_set(
930 &mut self,
931 device: &wgpu::Device,
932 queue: &wgpu::Queue,
933 id: crate::resources::TensorGlyphSetId,
934 item: &crate::renderer::TensorGlyphItem,
935 ) -> bool {
936 if !self.content.tensor_glyph_set_store.contains(id) {
937 return false;
938 }
939 self.ensure_tensor_glyph_pipeline(device);
940 let gpu = self.upload_tensor_glyph_set_per_frame(device, queue, item, false);
941 self.content.tensor_glyph_set_store.replace(id, gpu)
942 }
943
944 pub fn begin_upload_tensor_glyph_set(
946 &mut self,
947 device: &wgpu::Device,
948 queue: &wgpu::Queue,
949 item: crate::renderer::TensorGlyphItem,
950 ) -> crate::resources::JobId {
951 let slot = crate::resources::ResultSlot::<crate::resources::TensorGlyphSetId>::new();
952 let slot_for_apply = slot.clone();
953 let device_for_apply = device.clone();
954 let queue_for_apply = queue.clone();
955 let id = {
956 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
957 runner.submit_cpu(move |progress| {
958 progress.set(0.9);
959 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
960 Box::new(move |resources: &mut DeviceResources| {
961 let tid = resources.upload_tensor_glyph_set(
962 &device_for_apply,
963 &queue_for_apply,
964 &item,
965 );
966 slot_for_apply.set(tid);
967 }),
968 ))
969 })
970 };
971 self.job_results
972 .tensor_glyph_set
973 .lock()
974 .expect("tensor glyph set result map poisoned")
975 .insert(id, slot);
976 id
977 }
978
979 pub fn upload_result_tensor_glyph_set(
982 &mut self,
983 id: crate::resources::JobId,
984 ) -> crate::error::ViewportResult<crate::resources::TensorGlyphSetId> {
985 let mut map = self
986 .job_results
987 .tensor_glyph_set
988 .lock()
989 .expect("tensor glyph set result map poisoned");
990 let slot = match map.get(&id) {
991 Some(s) => s.clone(),
992 None => {
993 return Err(crate::error::ViewportError::JobResultMissing {
994 reason: "unknown id or wrong upload type",
995 });
996 }
997 };
998 match slot.take() {
999 Some(tid) => {
1000 map.remove(&id);
1001 Ok(tid)
1002 }
1003 None => Err(crate::error::ViewportError::JobNotReady),
1004 }
1005 }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 use crate::DeviceResources;
1011 use crate::renderer::{GlyphItem, TensorGlyphItem};
1012 use crate::resources::UploadStatus;
1013
1014 fn try_make_device() -> Option<(wgpu::Device, wgpu::Queue)> {
1015 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
1016 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1017 power_preference: wgpu::PowerPreference::LowPower,
1018 compatible_surface: None,
1019 force_fallback_adapter: false,
1020 }))
1021 .ok()?;
1022 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
1023 }
1024
1025 fn sample_glyph_set() -> GlyphItem {
1026 let mut item = GlyphItem::default();
1027 item.positions = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
1028 item.vectors = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1029 item
1030 }
1031
1032 fn sample_tensor_glyph_set() -> TensorGlyphItem {
1033 let mut item = TensorGlyphItem::default();
1034 item.positions = vec![[0.0, 0.0, 0.0]];
1035 item.eigenvalues = vec![[1.0, 0.5, 0.25]];
1036 item.eigenvectors = vec![[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]];
1037 item
1038 }
1039
1040 fn drive_until_ready(
1041 resources: &mut DeviceResources,
1042 device: &wgpu::Device,
1043 queue: &wgpu::Queue,
1044 id: crate::resources::JobId,
1045 label: &str,
1046 ) {
1047 for _ in 0..200 {
1048 resources.process_uploads(device, queue);
1049 match resources.upload_status(id) {
1050 UploadStatus::Ready => return,
1051 UploadStatus::Failed(e) => panic!("{label} upload failed: {e:?}"),
1052 UploadStatus::Pending { .. } => {
1053 std::thread::sleep(std::time::Duration::from_millis(5));
1054 }
1055 UploadStatus::Unknown => panic!("{label} job id disappeared"),
1056 }
1057 }
1058 panic!("{label} upload did not complete in time");
1059 }
1060
1061 #[test]
1062 fn upload_glyph_set_returns_valid_handle() {
1063 let Some((device, queue)) = try_make_device() else {
1064 eprintln!("skipping: no wgpu adapter available");
1065 return;
1066 };
1067 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
1068 let id = resources.upload_glyph_set(&device, &queue, &sample_glyph_set());
1069 assert!(resources.content.glyph_set_store.contains(id));
1070 assert!(resources.drop_glyph_set(id));
1071 }
1072
1073 #[test]
1074 fn upload_tensor_glyph_set_returns_valid_handle() {
1075 let Some((device, queue)) = try_make_device() else {
1076 eprintln!("skipping: no wgpu adapter available");
1077 return;
1078 };
1079 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
1080 let id = resources.upload_tensor_glyph_set(&device, &queue, &sample_tensor_glyph_set());
1081 assert!(resources.content.tensor_glyph_set_store.contains(id));
1082 assert!(resources.drop_tensor_glyph_set(id));
1083 }
1084
1085 #[test]
1086 fn begin_upload_glyph_set_drains_to_handle() {
1087 let Some((device, queue)) = try_make_device() else {
1088 eprintln!("skipping: no wgpu adapter available");
1089 return;
1090 };
1091 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
1092 let job = resources.begin_upload_glyph_set(&device, &queue, sample_glyph_set());
1093 drive_until_ready(&mut resources, &device, &queue, job, "glyph_set");
1094 let id = resources.upload_result_glyph_set(job).expect("ready");
1095 assert!(resources.content.glyph_set_store.contains(id));
1096 }
1097
1098 #[test]
1099 fn begin_upload_tensor_glyph_set_drains_to_handle() {
1100 let Some((device, queue)) = try_make_device() else {
1101 eprintln!("skipping: no wgpu adapter available");
1102 return;
1103 };
1104 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
1105 let job =
1106 resources.begin_upload_tensor_glyph_set(&device, &queue, sample_tensor_glyph_set());
1107 drive_until_ready(&mut resources, &device, &queue, job, "tensor_glyph_set");
1108 let id = resources
1109 .upload_result_tensor_glyph_set(job)
1110 .expect("ready");
1111 assert!(resources.content.tensor_glyph_set_store.contains(id));
1112 }
1113}
1114
1115pub(crate) struct GlyphBaseMesh {
1117 pub vertex_buffer: wgpu::Buffer,
1119 pub index_buffer: wgpu::Buffer,
1121 pub index_count: u32,
1123 pub edge_index_buffer: wgpu::Buffer,
1125 pub edge_index_count: u32,
1127}
1128#[derive(Clone)]
1130pub struct GlyphGpuData {
1131 pub(crate) mesh_vertex_buffer: &'static wgpu::Buffer,
1135 pub(crate) mesh_index_buffer: &'static wgpu::Buffer,
1137 pub(crate) mesh_index_count: u32,
1139 pub(crate) mesh_edge_index_buffer: &'static wgpu::Buffer,
1141 pub(crate) mesh_edge_index_count: u32,
1143 pub(crate) instance_count: u32,
1145 pub(crate) wireframe: bool,
1147 pub(crate) uniform_bind_group: wgpu::BindGroup,
1149 pub(crate) instance_bind_group: wgpu::BindGroup,
1151 pub(crate) _uniform_buf: wgpu::Buffer,
1153 pub(crate) _instance_buf: wgpu::Buffer,
1154}
1155
1156#[derive(Clone)]
1160pub struct TensorGlyphGpuData {
1161 pub(crate) mesh_vertex_buffer: &'static wgpu::Buffer,
1163 pub(crate) mesh_index_buffer: &'static wgpu::Buffer,
1165 pub(crate) mesh_index_count: u32,
1167 pub(crate) mesh_edge_index_buffer: &'static wgpu::Buffer,
1169 pub(crate) mesh_edge_index_count: u32,
1171 pub(crate) instance_count: u32,
1173 pub(crate) wireframe: bool,
1175 pub(crate) uniform_bind_group: wgpu::BindGroup,
1177 pub(crate) instance_bind_group: wgpu::BindGroup,
1179 pub(crate) _uniform_buf: wgpu::Buffer,
1181 pub(crate) _instance_buf: wgpu::Buffer,
1182}