1use super::*;
2
3#[derive(Default)]
6pub(crate) struct PolylineResources {
7 pub(crate) pipeline: Option<DualPipeline>,
9 pub(crate) no_clip_pipeline: Option<DualPipeline>,
11 pub(crate) bgl: Option<wgpu::BindGroupLayout>,
13 pub(crate) wireframe_pipeline: Option<DualPipeline>,
15 pub(crate) wireframe_bgl: Option<wgpu::BindGroupLayout>,
17 pub(crate) outline_mask_pipeline: Option<wgpu::RenderPipeline>,
19}
20
21impl DeviceResources {
22 pub(crate) fn ensure_polyline_pipeline(&mut self, device: &wgpu::Device) {
26 if self.polyline.pipeline.is_some() {
27 return;
28 }
29
30 let pl_bgl = crate::resources::builders::uniform_texture_sampler_bgl(
31 device,
32 "polyline_bgl",
33 wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
34 wgpu::ShaderStages::VERTEX,
35 );
36
37 let shader = crate::resources::builders::wgsl_module(
38 device,
39 "polyline_shader",
40 crate::resources::builders::wgsl_source!("polyline"),
41 );
42
43 let layout = crate::resources::builders::standard_scene_layout(
44 device,
45 "polyline_pipeline_layout",
46 &self.camera_bind_group_layout,
47 &pl_bgl,
48 );
49
50 let pl_instance_layout = wgpu::VertexBufferLayout {
66 array_stride: 112,
67 step_mode: wgpu::VertexStepMode::Instance,
68 attributes: &[
69 wgpu::VertexAttribute {
70 offset: 0,
71 shader_location: 0,
72 format: wgpu::VertexFormat::Float32x3,
73 }, wgpu::VertexAttribute {
75 offset: 12,
76 shader_location: 1,
77 format: wgpu::VertexFormat::Float32x3,
78 }, wgpu::VertexAttribute {
80 offset: 24,
81 shader_location: 2,
82 format: wgpu::VertexFormat::Float32x3,
83 }, wgpu::VertexAttribute {
85 offset: 36,
86 shader_location: 3,
87 format: wgpu::VertexFormat::Float32x3,
88 }, wgpu::VertexAttribute {
90 offset: 48,
91 shader_location: 4,
92 format: wgpu::VertexFormat::Float32,
93 }, wgpu::VertexAttribute {
95 offset: 52,
96 shader_location: 5,
97 format: wgpu::VertexFormat::Float32,
98 }, wgpu::VertexAttribute {
100 offset: 56,
101 shader_location: 6,
102 format: wgpu::VertexFormat::Uint32,
103 }, wgpu::VertexAttribute {
105 offset: 60,
106 shader_location: 7,
107 format: wgpu::VertexFormat::Uint32,
108 }, wgpu::VertexAttribute {
110 offset: 64,
111 shader_location: 8,
112 format: wgpu::VertexFormat::Float32x4,
113 }, wgpu::VertexAttribute {
115 offset: 80,
116 shader_location: 9,
117 format: wgpu::VertexFormat::Float32x4,
118 }, wgpu::VertexAttribute {
120 offset: 96,
121 shader_location: 10,
122 format: wgpu::VertexFormat::Float32,
123 }, wgpu::VertexAttribute {
125 offset: 100,
126 shader_location: 11,
127 format: wgpu::VertexFormat::Float32,
128 }, wgpu::VertexAttribute {
130 offset: 104,
131 shader_location: 12,
132 format: wgpu::VertexFormat::Uint32,
133 }, ],
135 };
136
137 self.polyline.pipeline = Some(crate::resources::builders::build_dual_pipeline(
138 device,
139 &crate::resources::builders::DualPipelineDesc {
140 label: "polyline_pipeline",
141 layout: &layout,
142 shader: &shader,
143 vertex_entry: "vs_main",
144 fragment_entry: "fs_main",
145 vertex_buffers: &[pl_instance_layout.clone()],
146 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
147 topology: wgpu::PrimitiveTopology::TriangleList,
148 cull_mode: None,
149 depth_write: true,
150 depth_compare: wgpu::CompareFunction::LessEqual,
151 sample_count: self.sample_count,
152 ldr_format: self.target_format,
153 },
154 ));
155 self.polyline.bgl = Some(pl_bgl);
156
157 self.ensure_polyline_wireframe_pipeline(device);
158 }
159
160 pub(crate) fn ensure_polyline_wireframe_pipeline(&mut self, device: &wgpu::Device) {
165 if self.polyline.wireframe_pipeline.is_some() {
166 return;
167 }
168
169 let wf_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
170 label: Some("polyline_wireframe_bgl"),
171 entries: &[wgpu::BindGroupLayoutEntry {
172 binding: 0,
173 visibility: wgpu::ShaderStages::VERTEX,
174 ty: wgpu::BindingType::Buffer {
175 ty: wgpu::BufferBindingType::Storage { read_only: true },
176 has_dynamic_offset: false,
177 min_binding_size: None,
178 },
179 count: None,
180 }],
181 });
182
183 let shader = crate::resources::builders::wgsl_module(
184 device,
185 "polyline_wireframe_shader",
186 crate::resources::builders::wgsl_source!("polyline_wireframe"),
187 );
188
189 let layout = crate::resources::builders::standard_scene_layout(
190 device,
191 "polyline_wireframe_pipeline_layout",
192 &self.camera_bind_group_layout,
193 &wf_bgl,
194 );
195
196 self.polyline.wireframe_bgl = Some(wf_bgl);
197 self.polyline.wireframe_pipeline = Some(crate::resources::builders::build_dual_pipeline(
198 device,
199 &crate::resources::builders::DualPipelineDesc {
200 label: "polyline_wireframe_pipeline",
201 layout: &layout,
202 shader: &shader,
203 vertex_entry: "vs_main",
204 fragment_entry: "fs_main",
205 vertex_buffers: &[],
206 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
207 topology: wgpu::PrimitiveTopology::LineList,
208 cull_mode: None,
209 depth_write: true,
210 depth_compare: wgpu::CompareFunction::LessEqual,
211 sample_count: self.sample_count,
212 ldr_format: self.target_format,
213 },
214 ));
215 }
216
217 pub(crate) fn upload_polyline_per_frame(
229 &mut self,
230 device: &wgpu::Device,
231 queue: &wgpu::Queue,
232 item: &crate::renderer::PolylineItem,
233 viewport_size: [f32; 2],
234 ) -> PolylineGpuData {
235 #[repr(C)]
237 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
238 struct SegInstance {
239 pos_a: [f32; 3], pos_b: [f32; 3], prev_pos: [f32; 3], next_pos: [f32; 3], scalar_a: f32, scalar_b: f32, has_prev: u32, has_next: u32, colour_a: [f32; 4], colour_b: [f32; 4], radius_a: f32, radius_b: f32, use_direct_colour: u32, _pad: u32, }
254
255 let use_direct = !item.node_colours.is_empty() || !item.edge_colours.is_empty();
257 let use_edge_scalars = item.scalars.is_empty() && !item.edge_scalars.is_empty();
258 let use_node_radii = !item.node_radii.is_empty();
259
260 let mut instances: Vec<SegInstance> = Vec::new();
261 let positions = &item.positions;
262 let npos = positions.len();
263
264 let strip_ranges: Vec<(usize, usize)> = if item.strip_lengths.is_empty() {
266 vec![(0, npos)]
267 } else {
268 let mut ranges = Vec::with_capacity(item.strip_lengths.len());
269 let mut off = 0usize;
270 for &l in &item.strip_lengths {
271 ranges.push((off, off + l as usize));
272 off += l as usize;
273 }
274 ranges
275 };
276
277 let mut seg_idx_global: usize = 0; for &(strip_start, strip_end) in &strip_ranges {
280 let end = strip_end.min(npos);
281 for i in strip_start..end.saturating_sub(1) {
282 let j = i + 1;
283 let has_prev = i > strip_start;
284 let has_next = j + 1 < end;
285
286 let (scalar_a, scalar_b) = if use_edge_scalars {
288 let s = item
289 .edge_scalars
290 .get(seg_idx_global)
291 .copied()
292 .unwrap_or(0.0);
293 (s, s)
294 } else {
295 (
296 item.scalars.get(i).copied().unwrap_or(0.0),
297 item.scalars.get(j).copied().unwrap_or(0.0),
298 )
299 };
300
301 let (colour_a, colour_b) = if !item.node_colours.is_empty() {
303 (
304 item.node_colours.get(i).copied().unwrap_or([1.0; 4]),
305 item.node_colours.get(j).copied().unwrap_or([1.0; 4]),
306 )
307 } else if !item.edge_colours.is_empty() {
308 let c = item
309 .edge_colours
310 .get(seg_idx_global)
311 .copied()
312 .unwrap_or([1.0; 4]);
313 (c, c)
314 } else {
315 ([1.0; 4], [1.0; 4])
316 };
317
318 let (radius_a, radius_b) = if use_node_radii {
320 (
321 item.node_radii.get(i).copied().unwrap_or(item.line_width),
322 item.node_radii.get(j).copied().unwrap_or(item.line_width),
323 )
324 } else {
325 (item.line_width, item.line_width)
326 };
327
328 instances.push(SegInstance {
329 pos_a: positions[i],
330 pos_b: positions[j],
331 prev_pos: if has_prev {
332 positions[i - 1]
333 } else {
334 positions[i]
335 },
336 next_pos: if has_next {
337 positions[j + 1]
338 } else {
339 positions[j]
340 },
341 scalar_a,
342 scalar_b,
343 has_prev: has_prev as u32,
344 has_next: has_next as u32,
345 colour_a,
346 colour_b,
347 radius_a,
348 radius_b,
349 use_direct_colour: use_direct as u32,
350 _pad: 0,
351 });
352
353 seg_idx_global += 1;
354 }
355 }
356
357 let seg_count = instances.len() as u32;
358
359 let seg_bytes: &[u8] = bytemuck::cast_slice(&instances);
361 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
362 label: Some("polyline_vertex_buf"),
363 size: seg_bytes.len().max(112) as u64,
364 usage: wgpu::BufferUsages::VERTEX
365 | wgpu::BufferUsages::STORAGE
366 | wgpu::BufferUsages::COPY_DST,
367 mapped_at_creation: false,
368 });
369 if !seg_bytes.is_empty() {
370 queue.write_buffer(&vertex_buffer, 0, seg_bytes);
371 }
372
373 let scalar_source: &[f32] = if !item.scalars.is_empty() {
375 &item.scalars
376 } else {
377 &item.edge_scalars
378 };
379 let (has_scalar, scalar_min, scalar_max) = if !scalar_source.is_empty() {
380 let (min, max) = item.scalar_range.unwrap_or_else(|| {
381 let mn = scalar_source.iter().cloned().fold(f32::INFINITY, f32::min);
382 let mx = scalar_source
383 .iter()
384 .cloned()
385 .fold(f32::NEG_INFINITY, f32::max);
386 (mn, mx)
387 });
388 (1u32, min, max)
389 } else {
390 (0u32, 0.0f32, 1.0f32)
391 };
392
393 #[repr(C)]
394 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
395 struct PolylineUniform {
396 model: [[f32; 4]; 4], default_colour: [f32; 4], line_width: f32, scalar_min: f32, scalar_max: f32, has_scalar: u32, viewport_width: f32, viewport_height: f32, _pad: [f32; 2], }
406 let uniform_data = PolylineUniform {
407 model: item.model,
408 default_colour: item.default_colour,
409 line_width: item.line_width,
410 scalar_min,
411 scalar_max,
412 has_scalar,
413 viewport_width: viewport_size[0].max(1.0),
414 viewport_height: viewport_size[1].max(1.0),
415 _pad: [0.0; 2],
416 };
417 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
418 label: Some("polyline_uniform_buf"),
419 size: std::mem::size_of::<PolylineUniform>() as u64,
420 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
421 mapped_at_creation: false,
422 });
423 queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
424
425 let lut_view = self
426 .content
427 .builtin_colourmap_ids
428 .and_then(|ids| {
429 let preset_id = item
430 .colourmap_id
431 .unwrap_or(ids[crate::resources::BuiltinColourmap::Viridis as usize]);
432 self.content.colourmap_views.get(preset_id.0)
433 })
434 .unwrap_or(&self.content.fallback_lut_view);
435
436 let lut_sampler = &self.lut_sampler;
437
438 let bgl = self
439 .polyline
440 .bgl
441 .as_ref()
442 .expect("ensure_polyline_pipeline not called");
443 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
444 label: Some("polyline_bind_group"),
445 layout: bgl,
446 entries: &[
447 wgpu::BindGroupEntry {
448 binding: 0,
449 resource: uniform_buf.as_entire_binding(),
450 },
451 wgpu::BindGroupEntry {
452 binding: 1,
453 resource: wgpu::BindingResource::TextureView(lut_view),
454 },
455 wgpu::BindGroupEntry {
456 binding: 2,
457 resource: wgpu::BindingResource::Sampler(lut_sampler),
458 },
459 ],
460 });
461
462 let wireframe_bind_group = self.polyline.wireframe_bgl.as_ref().map(|bgl| {
463 device.create_bind_group(&wgpu::BindGroupDescriptor {
464 label: Some("polyline_wireframe_bind_group"),
465 layout: bgl,
466 entries: &[wgpu::BindGroupEntry {
467 binding: 0,
468 resource: vertex_buffer.as_entire_binding(),
469 }],
470 })
471 });
472
473 PolylineGpuData {
474 vertex_buffer,
475 segment_count: seg_count,
476 bind_group,
477 _uniform_buf: uniform_buf,
478 skip_clip: false,
479 wireframe: false,
480 wireframe_bind_group,
481 }
482 }
483
484 pub fn upload_polyline(
496 &mut self,
497 device: &wgpu::Device,
498 queue: &wgpu::Queue,
499 item: &crate::renderer::PolylineItem,
500 ) -> crate::resources::PolylineId {
501 self.ensure_polyline_pipeline(device);
502 let gpu = self.upload_polyline_per_frame(device, queue, item, [1.0, 1.0]);
503 self.content.polyline_store.insert(gpu)
504 }
505
506 pub fn drop_polyline(&mut self, id: crate::resources::PolylineId) -> bool {
509 self.content.polyline_store.remove(id)
510 }
511
512 pub fn replace_polyline(
518 &mut self,
519 device: &wgpu::Device,
520 queue: &wgpu::Queue,
521 id: crate::resources::PolylineId,
522 item: &crate::renderer::PolylineItem,
523 ) -> bool {
524 if !self.content.polyline_store.contains(id) {
525 return false;
526 }
527 self.ensure_polyline_pipeline(device);
528 let gpu = self.upload_polyline_per_frame(device, queue, item, [1.0, 1.0]);
529 self.content.polyline_store.replace(id, gpu)
530 }
531
532 pub fn begin_upload_polyline(
542 &mut self,
543 device: &wgpu::Device,
544 queue: &wgpu::Queue,
545 item: crate::renderer::PolylineItem,
546 ) -> crate::resources::JobId {
547 let slot = crate::resources::ResultSlot::<crate::resources::PolylineId>::new();
548 let slot_for_apply = slot.clone();
549 let device_for_apply = device.clone();
550 let queue_for_apply = queue.clone();
551
552 let id = {
553 let mut runner = self.jobs.lock().expect("upload job runner poisoned");
554 runner.submit_cpu(move |progress| {
555 progress.set(0.9);
556 Ok(crate::resources::upload_jobs::JobProduct::with_apply(
557 Box::new(move |resources: &mut DeviceResources| {
558 let pid =
559 resources.upload_polyline(&device_for_apply, &queue_for_apply, &item);
560 slot_for_apply.set(pid);
561 }),
562 ))
563 })
564 };
565
566 self.job_results
567 .polyline
568 .lock()
569 .expect("polyline result map poisoned")
570 .insert(id, slot);
571 id
572 }
573
574 pub fn upload_result_polyline(
577 &mut self,
578 id: crate::resources::JobId,
579 ) -> crate::error::ViewportResult<crate::resources::PolylineId> {
580 let mut map = self
581 .job_results
582 .polyline
583 .lock()
584 .expect("polyline result map poisoned");
585 let slot = match map.get(&id) {
586 Some(s) => s.clone(),
587 None => {
588 return Err(crate::error::ViewportError::JobResultMissing {
589 reason: "unknown id or wrong upload type",
590 });
591 }
592 };
593 match slot.take() {
594 Some(pid) => {
595 map.remove(&id);
596 Ok(pid)
597 }
598 None => Err(crate::error::ViewportError::JobNotReady),
599 }
600 }
601
602 pub(crate) fn ensure_polyline_no_clip_pipeline(&mut self, device: &wgpu::Device) {
608 if self.polyline.no_clip_pipeline.is_some() {
609 return;
610 }
611 self.ensure_polyline_pipeline(device);
613
614 let shader = crate::resources::builders::wgsl_module(
615 device,
616 "polyline_no_clip_shader",
617 crate::resources::builders::wgsl_source!("polyline"),
618 );
619
620 let pl_bgl = self
621 .polyline
622 .bgl
623 .as_ref()
624 .expect("polyline_bgl must exist after ensure_polyline_pipeline");
625 let layout = crate::resources::builders::standard_scene_layout(
626 device,
627 "polyline_no_clip_pipeline_layout",
628 &self.camera_bind_group_layout,
629 pl_bgl,
630 );
631
632 let pl_instance_layout = wgpu::VertexBufferLayout {
634 array_stride: 112,
635 step_mode: wgpu::VertexStepMode::Instance,
636 attributes: &[
637 wgpu::VertexAttribute {
638 offset: 0,
639 shader_location: 0,
640 format: wgpu::VertexFormat::Float32x3,
641 },
642 wgpu::VertexAttribute {
643 offset: 12,
644 shader_location: 1,
645 format: wgpu::VertexFormat::Float32x3,
646 },
647 wgpu::VertexAttribute {
648 offset: 24,
649 shader_location: 2,
650 format: wgpu::VertexFormat::Float32x3,
651 },
652 wgpu::VertexAttribute {
653 offset: 36,
654 shader_location: 3,
655 format: wgpu::VertexFormat::Float32x3,
656 },
657 wgpu::VertexAttribute {
658 offset: 48,
659 shader_location: 4,
660 format: wgpu::VertexFormat::Float32,
661 },
662 wgpu::VertexAttribute {
663 offset: 52,
664 shader_location: 5,
665 format: wgpu::VertexFormat::Float32,
666 },
667 wgpu::VertexAttribute {
668 offset: 56,
669 shader_location: 6,
670 format: wgpu::VertexFormat::Uint32,
671 },
672 wgpu::VertexAttribute {
673 offset: 60,
674 shader_location: 7,
675 format: wgpu::VertexFormat::Uint32,
676 },
677 wgpu::VertexAttribute {
678 offset: 64,
679 shader_location: 8,
680 format: wgpu::VertexFormat::Float32x4,
681 },
682 wgpu::VertexAttribute {
683 offset: 80,
684 shader_location: 9,
685 format: wgpu::VertexFormat::Float32x4,
686 },
687 wgpu::VertexAttribute {
688 offset: 96,
689 shader_location: 10,
690 format: wgpu::VertexFormat::Float32,
691 },
692 wgpu::VertexAttribute {
693 offset: 100,
694 shader_location: 11,
695 format: wgpu::VertexFormat::Float32,
696 },
697 wgpu::VertexAttribute {
698 offset: 104,
699 shader_location: 12,
700 format: wgpu::VertexFormat::Uint32,
701 },
702 ],
703 };
704
705 self.polyline.no_clip_pipeline = Some(crate::resources::builders::build_dual_pipeline(
706 device,
707 &crate::resources::builders::DualPipelineDesc {
708 label: "polyline_no_clip_pipeline",
709 layout: &layout,
710 shader: &shader,
711 vertex_entry: "vs_main",
712 fragment_entry: "fs_main_no_clip",
713 vertex_buffers: &[pl_instance_layout.clone()],
714 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
715 topology: wgpu::PrimitiveTopology::TriangleList,
716 cull_mode: None,
717 depth_write: true,
718 depth_compare: wgpu::CompareFunction::LessEqual,
719 sample_count: self.sample_count,
720 ldr_format: self.target_format,
721 },
722 ));
723 }
724
725 pub(crate) fn ensure_polyline_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
731 if self.polyline.outline_mask_pipeline.is_some() {
732 return;
733 }
734 self.ensure_polyline_pipeline(device);
735
736 let pl_bgl = self
737 .polyline
738 .bgl
739 .as_ref()
740 .expect("polyline_bgl must exist after ensure_polyline_pipeline");
741
742 let layout = crate::resources::builders::standard_scene_layout(
743 device,
744 "polyline_outline_mask_pipeline_layout",
745 &self.camera_bind_group_layout,
746 pl_bgl,
747 );
748
749 let shader = crate::resources::builders::wgsl_module(
750 device,
751 "polyline_outline_mask_shader",
752 crate::resources::builders::wgsl_source!("polyline_outline_mask"),
753 );
754
755 let pl_instance_layout = wgpu::VertexBufferLayout {
756 array_stride: 112,
757 step_mode: wgpu::VertexStepMode::Instance,
758 attributes: &[
759 wgpu::VertexAttribute {
760 offset: 0,
761 shader_location: 0,
762 format: wgpu::VertexFormat::Float32x3,
763 },
764 wgpu::VertexAttribute {
765 offset: 12,
766 shader_location: 1,
767 format: wgpu::VertexFormat::Float32x3,
768 },
769 wgpu::VertexAttribute {
770 offset: 24,
771 shader_location: 2,
772 format: wgpu::VertexFormat::Float32x3,
773 },
774 wgpu::VertexAttribute {
775 offset: 36,
776 shader_location: 3,
777 format: wgpu::VertexFormat::Float32x3,
778 },
779 wgpu::VertexAttribute {
780 offset: 48,
781 shader_location: 4,
782 format: wgpu::VertexFormat::Float32,
783 },
784 wgpu::VertexAttribute {
785 offset: 52,
786 shader_location: 5,
787 format: wgpu::VertexFormat::Float32,
788 },
789 wgpu::VertexAttribute {
790 offset: 56,
791 shader_location: 6,
792 format: wgpu::VertexFormat::Uint32,
793 },
794 wgpu::VertexAttribute {
795 offset: 60,
796 shader_location: 7,
797 format: wgpu::VertexFormat::Uint32,
798 },
799 wgpu::VertexAttribute {
800 offset: 64,
801 shader_location: 8,
802 format: wgpu::VertexFormat::Float32x4,
803 },
804 wgpu::VertexAttribute {
805 offset: 80,
806 shader_location: 9,
807 format: wgpu::VertexFormat::Float32x4,
808 },
809 wgpu::VertexAttribute {
810 offset: 96,
811 shader_location: 10,
812 format: wgpu::VertexFormat::Float32,
813 },
814 wgpu::VertexAttribute {
815 offset: 100,
816 shader_location: 11,
817 format: wgpu::VertexFormat::Float32,
818 },
819 wgpu::VertexAttribute {
820 offset: 104,
821 shader_location: 12,
822 format: wgpu::VertexFormat::Uint32,
823 },
824 ],
825 };
826
827 self.polyline.outline_mask_pipeline =
828 Some(crate::resources::builders::build_outline_mask_pipeline(
829 device,
830 "polyline_outline_mask_pipeline",
831 &layout,
832 &shader,
833 wgpu::TextureFormat::R8Unorm,
834 &[pl_instance_layout],
835 None,
836 true,
837 wgpu::CompareFunction::LessEqual,
838 ));
839 }
840}
841
842#[cfg(test)]
843mod tests {
844 use crate::DeviceResources;
845 use crate::renderer::PolylineItem;
846 use crate::resources::UploadStatus;
847
848 fn try_make_device() -> Option<(wgpu::Device, wgpu::Queue)> {
849 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
850 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
851 power_preference: wgpu::PowerPreference::LowPower,
852 compatible_surface: None,
853 force_fallback_adapter: false,
854 }))
855 .ok()?;
856 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()
857 }
858
859 fn sample_polyline() -> PolylineItem {
860 PolylineItem {
861 positions: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
862 strip_lengths: vec![3],
863 ..Default::default()
864 }
865 }
866
867 #[test]
868 fn default_model_is_identity() {
869 let item = PolylineItem::default();
870 let expected = [
871 [1.0, 0.0, 0.0, 0.0],
872 [0.0, 1.0, 0.0, 0.0],
873 [0.0, 0.0, 1.0, 0.0],
874 [0.0, 0.0, 0.0, 1.0],
875 ];
876 assert_eq!(item.model, expected);
877 }
878
879 #[test]
880 fn upload_polyline_returns_valid_handle() {
881 let Some((device, queue)) = try_make_device() else {
882 eprintln!("skipping: no wgpu adapter available");
883 return;
884 };
885 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
886 let id = resources.upload_polyline(&device, &queue, &sample_polyline());
887 assert!(resources.content.polyline_store.contains(id));
888 assert!(resources.drop_polyline(id));
890 assert!(!resources.content.polyline_store.contains(id));
891 }
892
893 #[test]
894 fn stale_polyline_handle_does_not_alias_after_slot_reuse() {
895 let Some((device, queue)) = try_make_device() else {
898 eprintln!("skipping: no wgpu adapter available");
899 return;
900 };
901 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
902
903 let id1 = resources.upload_polyline(&device, &queue, &sample_polyline());
904 assert!(resources.content.polyline_store.get(id1).is_some());
905 assert!(resources.drop_polyline(id1));
906 assert!(
907 resources.content.polyline_store.get(id1).is_none(),
908 "a dropped handle must not resolve"
909 );
910
911 let id2 = resources.upload_polyline(&device, &queue, &sample_polyline());
912 assert_eq!(id1.index(), id2.index(), "the freed slot should be reused");
913 assert_ne!(id1, id2, "the reused slot must carry a new generation");
914 assert!(resources.content.polyline_store.get(id2).is_some());
915 assert!(
916 resources.content.polyline_store.get(id1).is_none(),
917 "the stale handle must not alias the polyline now in its slot"
918 );
919 }
920
921 #[test]
922 fn replace_polyline_keeps_handle_stable() {
923 let Some((device, queue)) = try_make_device() else {
924 eprintln!("skipping: no wgpu adapter available");
925 return;
926 };
927 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
928 let id = resources.upload_polyline(&device, &queue, &sample_polyline());
929 let mut updated = sample_polyline();
930 updated.line_width = 5.0;
931 assert!(resources.replace_polyline(&device, &queue, id, &updated));
932 assert!(resources.content.polyline_store.contains(id));
933 }
934
935 #[test]
936 fn begin_upload_polyline_drains_to_handle() {
937 let Some((device, queue)) = try_make_device() else {
938 eprintln!("skipping: no wgpu adapter available");
939 return;
940 };
941 let mut resources = DeviceResources::new(&device, wgpu::TextureFormat::Rgba8UnormSrgb, 1);
942 let job = resources.begin_upload_polyline(&device, &queue, sample_polyline());
943
944 let err = resources.upload_result_polyline(job).unwrap_err();
946 assert!(matches!(err, crate::error::ViewportError::JobNotReady));
947
948 for _ in 0..200 {
949 resources.process_uploads(&device, &queue);
950 match resources.upload_status(job) {
951 UploadStatus::Ready => break,
952 UploadStatus::Failed(e) => panic!("polyline upload failed: {e:?}"),
953 UploadStatus::Pending { .. } => {
954 std::thread::sleep(std::time::Duration::from_millis(5));
955 }
956 UploadStatus::Unknown => panic!("polyline job id disappeared"),
957 }
958 }
959
960 let id = resources.upload_result_polyline(job).expect("ready result");
961 assert!(resources.content.polyline_store.contains(id));
962
963 let err = resources.upload_result_polyline(job).unwrap_err();
965 assert!(matches!(
966 err,
967 crate::error::ViewportError::JobResultMissing { .. }
968 ));
969 }
970
971 #[test]
972 fn non_identity_model_is_carried_on_item() {
973 let m = [
976 [1.0, 0.0, 0.0, 0.0],
977 [0.0, 1.0, 0.0, 0.0],
978 [0.0, 0.0, 1.0, 0.0],
979 [3.0, 4.0, 5.0, 1.0],
980 ];
981 let item = PolylineItem {
982 model: m,
983 ..PolylineItem::default()
984 };
985 assert_eq!(item.model[3], [3.0, 4.0, 5.0, 1.0]);
986 }
987}
988
989#[derive(Clone)]
991pub struct PolylineGpuData {
992 pub(crate) vertex_buffer: wgpu::Buffer,
994 pub(crate) segment_count: u32,
996 pub(crate) bind_group: wgpu::BindGroup,
998 pub(crate) _uniform_buf: wgpu::Buffer,
1000 pub(crate) skip_clip: bool,
1003 pub(crate) wireframe: bool,
1005 pub(crate) wireframe_bind_group: Option<wgpu::BindGroup>,
1008}