Skip to main content

polyscope_render/engine/pipelines/
structure.rs

1//! Pipeline creation for core visualization structures.
2//!
3//! Contains pipelines for:
4//! - Point clouds (sphere impostor rendering)
5//! - Vectors (arrow rendering)
6//! - Surface meshes (triangulated mesh rendering)
7//! - Simple meshes (isosurface rendering)
8//! - Curve networks (line and tube rendering)
9
10use std::num::NonZeroU64;
11
12use super::super::RenderEngine;
13
14impl RenderEngine {
15    /// Initializes the point cloud render pipeline.
16    pub fn init_point_pipeline(&mut self) {
17        let shader_source = include_str!("../../shaders/point_sphere.wgsl");
18        let shader = self
19            .device
20            .create_shader_module(wgpu::ShaderModuleDescriptor {
21                label: Some("point sphere shader"),
22                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
23            });
24
25        let bind_group_layout =
26            self.device
27                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
28                    label: Some("point cloud bind group layout"),
29                    entries: &[
30                        // Camera uniforms
31                        wgpu::BindGroupLayoutEntry {
32                            binding: 0,
33                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
34                            ty: wgpu::BindingType::Buffer {
35                                ty: wgpu::BufferBindingType::Uniform,
36                                has_dynamic_offset: false,
37                                min_binding_size: NonZeroU64::new(272),
38                            },
39                            count: None,
40                        },
41                        // Point uniforms
42                        wgpu::BindGroupLayoutEntry {
43                            binding: 1,
44                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
45                            ty: wgpu::BindingType::Buffer {
46                                ty: wgpu::BufferBindingType::Uniform,
47                                has_dynamic_offset: false,
48                                min_binding_size: NonZeroU64::new(96),
49                            },
50                            count: None,
51                        },
52                        // Position storage buffer
53                        wgpu::BindGroupLayoutEntry {
54                            binding: 2,
55                            visibility: wgpu::ShaderStages::VERTEX,
56                            ty: wgpu::BindingType::Buffer {
57                                ty: wgpu::BufferBindingType::Storage { read_only: true },
58                                has_dynamic_offset: false,
59                                min_binding_size: None,
60                            },
61                            count: None,
62                        },
63                        // Color storage buffer
64                        wgpu::BindGroupLayoutEntry {
65                            binding: 3,
66                            visibility: wgpu::ShaderStages::VERTEX,
67                            ty: wgpu::BindingType::Buffer {
68                                ty: wgpu::BufferBindingType::Storage { read_only: true },
69                                has_dynamic_offset: false,
70                                min_binding_size: None,
71                            },
72                            count: None,
73                        },
74                    ],
75                });
76
77        let pipeline_layout = self
78            .device
79            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
80                label: Some("point pipeline layout"),
81                bind_group_layouts: &[
82                    &bind_group_layout,
83                    &self.slice_plane_bind_group_layout,
84                    &self.matcap_bind_group_layout,
85                ],
86                push_constant_ranges: &[],
87            });
88
89        let pipeline = self
90            .device
91            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
92                label: Some("point sphere pipeline"),
93                layout: Some(&pipeline_layout),
94                vertex: wgpu::VertexState {
95                    module: &shader,
96                    entry_point: Some("vs_main"),
97                    buffers: &[],
98                    compilation_options: wgpu::PipelineCompilationOptions::default(),
99                },
100                fragment: Some(wgpu::FragmentState {
101                    module: &shader,
102                    entry_point: Some("fs_main"),
103                    targets: &[Some(wgpu::ColorTargetState {
104                        format: wgpu::TextureFormat::Rgba16Float, // HDR format for scene rendering
105                        blend: Some(wgpu::BlendState::REPLACE),
106                        write_mask: wgpu::ColorWrites::ALL,
107                    })],
108                    compilation_options: wgpu::PipelineCompilationOptions::default(),
109                }),
110                primitive: wgpu::PrimitiveState {
111                    topology: wgpu::PrimitiveTopology::TriangleList,
112                    strip_index_format: None,
113                    front_face: wgpu::FrontFace::Ccw,
114                    cull_mode: None, // Don't cull billboards
115                    polygon_mode: wgpu::PolygonMode::Fill,
116                    unclipped_depth: false,
117                    conservative: false,
118                },
119                depth_stencil: Some(wgpu::DepthStencilState {
120                    format: wgpu::TextureFormat::Depth24PlusStencil8,
121                    depth_write_enabled: true,
122                    depth_compare: wgpu::CompareFunction::Less,
123                    stencil: wgpu::StencilState::default(),
124                    bias: wgpu::DepthBiasState::default(),
125                }),
126                multisample: wgpu::MultisampleState::default(),
127                multiview: None,
128                cache: None,
129            });
130
131        self.point_pipeline = Some(pipeline);
132        self.point_bind_group_layout = Some(bind_group_layout);
133    }
134
135    /// Gets the point cloud bind group layout.
136    pub fn point_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
137        self.point_bind_group_layout
138            .as_ref()
139            .expect("point pipeline not initialized")
140    }
141
142    /// Initializes the vector arrow render pipeline.
143    pub fn init_vector_pipeline(&mut self) {
144        let shader_source = include_str!("../../shaders/vector_arrow.wgsl");
145        let shader = self
146            .device
147            .create_shader_module(wgpu::ShaderModuleDescriptor {
148                label: Some("vector arrow shader"),
149                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
150            });
151
152        let bind_group_layout =
153            self.device
154                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
155                    label: Some("vector bind group layout"),
156                    entries: &[
157                        // Camera uniforms
158                        wgpu::BindGroupLayoutEntry {
159                            binding: 0,
160                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
161                            ty: wgpu::BindingType::Buffer {
162                                ty: wgpu::BufferBindingType::Uniform,
163                                has_dynamic_offset: false,
164                                min_binding_size: NonZeroU64::new(272),
165                            },
166                            count: None,
167                        },
168                        // Vector uniforms
169                        wgpu::BindGroupLayoutEntry {
170                            binding: 1,
171                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
172                            ty: wgpu::BindingType::Buffer {
173                                ty: wgpu::BufferBindingType::Uniform,
174                                has_dynamic_offset: false,
175                                min_binding_size: NonZeroU64::new(96),
176                            },
177                            count: None,
178                        },
179                        // Base positions storage buffer
180                        wgpu::BindGroupLayoutEntry {
181                            binding: 2,
182                            visibility: wgpu::ShaderStages::VERTEX,
183                            ty: wgpu::BindingType::Buffer {
184                                ty: wgpu::BufferBindingType::Storage { read_only: true },
185                                has_dynamic_offset: false,
186                                min_binding_size: None,
187                            },
188                            count: None,
189                        },
190                        // Vectors storage buffer
191                        wgpu::BindGroupLayoutEntry {
192                            binding: 3,
193                            visibility: wgpu::ShaderStages::VERTEX,
194                            ty: wgpu::BindingType::Buffer {
195                                ty: wgpu::BufferBindingType::Storage { read_only: true },
196                                has_dynamic_offset: false,
197                                min_binding_size: None,
198                            },
199                            count: None,
200                        },
201                    ],
202                });
203
204        let pipeline_layout = self
205            .device
206            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
207                label: Some("vector pipeline layout"),
208                bind_group_layouts: &[
209                    &bind_group_layout,
210                    &self.slice_plane_bind_group_layout,
211                    &self.matcap_bind_group_layout,
212                ],
213                push_constant_ranges: &[],
214            });
215
216        let pipeline = self
217            .device
218            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
219                label: Some("vector arrow pipeline"),
220                layout: Some(&pipeline_layout),
221                vertex: wgpu::VertexState {
222                    module: &shader,
223                    entry_point: Some("vs_main"),
224                    buffers: &[],
225                    compilation_options: wgpu::PipelineCompilationOptions::default(),
226                },
227                fragment: Some(wgpu::FragmentState {
228                    module: &shader,
229                    entry_point: Some("fs_main"),
230                    targets: &[Some(wgpu::ColorTargetState {
231                        format: wgpu::TextureFormat::Rgba16Float, // HDR format for scene rendering
232                        blend: Some(wgpu::BlendState::REPLACE),
233                        write_mask: wgpu::ColorWrites::ALL,
234                    })],
235                    compilation_options: wgpu::PipelineCompilationOptions::default(),
236                }),
237                primitive: wgpu::PrimitiveState {
238                    topology: wgpu::PrimitiveTopology::TriangleList,
239                    strip_index_format: None,
240                    front_face: wgpu::FrontFace::Ccw,
241                    cull_mode: Some(wgpu::Face::Back),
242                    polygon_mode: wgpu::PolygonMode::Fill,
243                    unclipped_depth: false,
244                    conservative: false,
245                },
246                depth_stencil: Some(wgpu::DepthStencilState {
247                    format: wgpu::TextureFormat::Depth24PlusStencil8,
248                    depth_write_enabled: true,
249                    depth_compare: wgpu::CompareFunction::Less,
250                    stencil: wgpu::StencilState::default(),
251                    bias: wgpu::DepthBiasState::default(),
252                }),
253                multisample: wgpu::MultisampleState::default(),
254                multiview: None,
255                cache: None,
256            });
257
258        self.vector_pipeline = Some(pipeline);
259        self.vector_bind_group_layout = Some(bind_group_layout);
260    }
261
262    /// Gets the vector bind group layout.
263    pub fn vector_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
264        self.vector_bind_group_layout
265            .as_ref()
266            .expect("vector pipeline not initialized")
267    }
268
269    /// Gets the mesh bind group layout.
270    pub fn mesh_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
271        self.mesh_bind_group_layout
272            .as_ref()
273            .expect("mesh pipeline not initialized")
274    }
275
276    /// Gets the simple mesh bind group layout (for isosurface rendering).
277    pub fn simple_mesh_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
278        self.simple_mesh_bind_group_layout
279            .as_ref()
280            .expect("simple mesh pipeline not initialized")
281    }
282
283    /// Creates the surface mesh render pipeline.
284    pub(crate) fn create_mesh_pipeline(&mut self) {
285        let shader_source = include_str!("../../shaders/surface_mesh.wgsl");
286        let shader = self
287            .device
288            .create_shader_module(wgpu::ShaderModuleDescriptor {
289                label: Some("surface mesh shader"),
290                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
291            });
292
293        let bind_group_layout =
294            self.device
295                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
296                    label: Some("mesh bind group layout"),
297                    entries: &[
298                        // Camera uniforms
299                        wgpu::BindGroupLayoutEntry {
300                            binding: 0,
301                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
302                            ty: wgpu::BindingType::Buffer {
303                                ty: wgpu::BufferBindingType::Uniform,
304                                has_dynamic_offset: false,
305                                min_binding_size: NonZeroU64::new(272),
306                            },
307                            count: None,
308                        },
309                        // Mesh uniforms
310                        wgpu::BindGroupLayoutEntry {
311                            binding: 1,
312                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
313                            ty: wgpu::BindingType::Buffer {
314                                ty: wgpu::BufferBindingType::Uniform,
315                                has_dynamic_offset: false,
316                                min_binding_size: NonZeroU64::new(160),
317                            },
318                            count: None,
319                        },
320                        // Positions storage buffer
321                        wgpu::BindGroupLayoutEntry {
322                            binding: 2,
323                            visibility: wgpu::ShaderStages::VERTEX,
324                            ty: wgpu::BindingType::Buffer {
325                                ty: wgpu::BufferBindingType::Storage { read_only: true },
326                                has_dynamic_offset: false,
327                                min_binding_size: None,
328                            },
329                            count: None,
330                        },
331                        // Normals storage buffer
332                        wgpu::BindGroupLayoutEntry {
333                            binding: 3,
334                            visibility: wgpu::ShaderStages::VERTEX,
335                            ty: wgpu::BindingType::Buffer {
336                                ty: wgpu::BufferBindingType::Storage { read_only: true },
337                                has_dynamic_offset: false,
338                                min_binding_size: None,
339                            },
340                            count: None,
341                        },
342                        // Barycentrics storage buffer
343                        wgpu::BindGroupLayoutEntry {
344                            binding: 4,
345                            visibility: wgpu::ShaderStages::VERTEX,
346                            ty: wgpu::BindingType::Buffer {
347                                ty: wgpu::BufferBindingType::Storage { read_only: true },
348                                has_dynamic_offset: false,
349                                min_binding_size: None,
350                            },
351                            count: None,
352                        },
353                        // Colors storage buffer
354                        wgpu::BindGroupLayoutEntry {
355                            binding: 5,
356                            visibility: wgpu::ShaderStages::VERTEX,
357                            ty: wgpu::BindingType::Buffer {
358                                ty: wgpu::BufferBindingType::Storage { read_only: true },
359                                has_dynamic_offset: false,
360                                min_binding_size: None,
361                            },
362                            count: None,
363                        },
364                        // Edge is real storage buffer
365                        wgpu::BindGroupLayoutEntry {
366                            binding: 6,
367                            visibility: wgpu::ShaderStages::VERTEX,
368                            ty: wgpu::BindingType::Buffer {
369                                ty: wgpu::BufferBindingType::Storage { read_only: true },
370                                has_dynamic_offset: false,
371                                min_binding_size: None,
372                            },
373                            count: None,
374                        },
375                    ],
376                });
377
378        let pipeline_layout = self
379            .device
380            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
381                label: Some("mesh pipeline layout"),
382                bind_group_layouts: &[
383                    &bind_group_layout,
384                    &self.slice_plane_bind_group_layout,
385                    &self.matcap_bind_group_layout,
386                ],
387                push_constant_ranges: &[],
388            });
389
390        let pipeline = self
391            .device
392            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
393                label: Some("surface mesh pipeline"),
394                layout: Some(&pipeline_layout),
395                vertex: wgpu::VertexState {
396                    module: &shader,
397                    entry_point: Some("vs_main"),
398                    buffers: &[],
399                    compilation_options: wgpu::PipelineCompilationOptions::default(),
400                },
401                fragment: Some(wgpu::FragmentState {
402                    module: &shader,
403                    entry_point: Some("fs_main"),
404                    targets: &[
405                        // Color output (HDR)
406                        Some(wgpu::ColorTargetState {
407                            format: wgpu::TextureFormat::Rgba16Float,
408                            blend: Some(wgpu::BlendState::ALPHA_BLENDING),
409                            write_mask: wgpu::ColorWrites::ALL,
410                        }),
411                        // Normal output (G-buffer for SSAO)
412                        Some(wgpu::ColorTargetState {
413                            format: wgpu::TextureFormat::Rgba16Float,
414                            blend: None,
415                            write_mask: wgpu::ColorWrites::ALL,
416                        }),
417                    ],
418                    compilation_options: wgpu::PipelineCompilationOptions::default(),
419                }),
420                primitive: wgpu::PrimitiveState {
421                    topology: wgpu::PrimitiveTopology::TriangleList,
422                    strip_index_format: None,
423                    front_face: wgpu::FrontFace::Ccw,
424                    cull_mode: None, // Culling handled in shader
425                    polygon_mode: wgpu::PolygonMode::Fill,
426                    unclipped_depth: false,
427                    conservative: false,
428                },
429                depth_stencil: Some(wgpu::DepthStencilState {
430                    format: wgpu::TextureFormat::Depth24PlusStencil8,
431                    depth_write_enabled: true,
432                    depth_compare: wgpu::CompareFunction::Less,
433                    stencil: wgpu::StencilState::default(),
434                    bias: wgpu::DepthBiasState::default(),
435                }),
436                multisample: wgpu::MultisampleState::default(),
437                multiview: None,
438                cache: None,
439            });
440
441        let depth_normal_pipeline =
442            self.device
443                .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
444                    label: Some("surface mesh depth/normal pipeline"),
445                    layout: Some(&pipeline_layout),
446                    vertex: wgpu::VertexState {
447                        module: &shader,
448                        entry_point: Some("vs_main"),
449                        buffers: &[],
450                        compilation_options: wgpu::PipelineCompilationOptions::default(),
451                    },
452                    fragment: Some(wgpu::FragmentState {
453                        module: &shader,
454                        entry_point: Some("fs_main"),
455                        targets: &[
456                            // Color output (HDR) - disabled, keep scene color from other passes
457                            Some(wgpu::ColorTargetState {
458                                format: wgpu::TextureFormat::Rgba16Float,
459                                blend: None,
460                                write_mask: wgpu::ColorWrites::empty(),
461                            }),
462                            // Normal output (G-buffer for SSAO)
463                            Some(wgpu::ColorTargetState {
464                                format: wgpu::TextureFormat::Rgba16Float,
465                                blend: None,
466                                write_mask: wgpu::ColorWrites::ALL,
467                            }),
468                        ],
469                        compilation_options: wgpu::PipelineCompilationOptions::default(),
470                    }),
471                    primitive: wgpu::PrimitiveState {
472                        topology: wgpu::PrimitiveTopology::TriangleList,
473                        strip_index_format: None,
474                        front_face: wgpu::FrontFace::Ccw,
475                        cull_mode: None, // Culling handled in shader
476                        polygon_mode: wgpu::PolygonMode::Fill,
477                        unclipped_depth: false,
478                        conservative: false,
479                    },
480                    depth_stencil: Some(wgpu::DepthStencilState {
481                        format: wgpu::TextureFormat::Depth24PlusStencil8,
482                        depth_write_enabled: true,
483                        depth_compare: wgpu::CompareFunction::Less,
484                        stencil: wgpu::StencilState::default(),
485                        bias: wgpu::DepthBiasState::default(),
486                    }),
487                    multisample: wgpu::MultisampleState::default(),
488                    multiview: None,
489                    cache: None,
490                });
491
492        self.mesh_pipeline = Some(pipeline);
493        self.mesh_depth_normal_pipeline = Some(depth_normal_pipeline);
494        self.mesh_bind_group_layout = Some(bind_group_layout);
495    }
496
497    /// Creates the simple mesh pipeline (used for isosurface rendering).
498    pub(crate) fn create_simple_mesh_pipeline(&mut self) {
499        let shader_source = include_str!("../../shaders/simple_mesh.wgsl");
500        let shader = self
501            .device
502            .create_shader_module(wgpu::ShaderModuleDescriptor {
503                label: Some("simple mesh shader"),
504                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
505            });
506
507        let bind_group_layout =
508            self.device
509                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
510                    label: Some("simple mesh bind group layout"),
511                    entries: &[
512                        // Camera uniforms
513                        wgpu::BindGroupLayoutEntry {
514                            binding: 0,
515                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
516                            ty: wgpu::BindingType::Buffer {
517                                ty: wgpu::BufferBindingType::Uniform,
518                                has_dynamic_offset: false,
519                                min_binding_size: NonZeroU64::new(272),
520                            },
521                            count: None,
522                        },
523                        // SimpleMeshUniforms (96 bytes)
524                        wgpu::BindGroupLayoutEntry {
525                            binding: 1,
526                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
527                            ty: wgpu::BindingType::Buffer {
528                                ty: wgpu::BufferBindingType::Uniform,
529                                has_dynamic_offset: false,
530                                min_binding_size: NonZeroU64::new(96),
531                            },
532                            count: None,
533                        },
534                        // Positions storage buffer
535                        wgpu::BindGroupLayoutEntry {
536                            binding: 2,
537                            visibility: wgpu::ShaderStages::VERTEX,
538                            ty: wgpu::BindingType::Buffer {
539                                ty: wgpu::BufferBindingType::Storage { read_only: true },
540                                has_dynamic_offset: false,
541                                min_binding_size: None,
542                            },
543                            count: None,
544                        },
545                        // Normals storage buffer
546                        wgpu::BindGroupLayoutEntry {
547                            binding: 3,
548                            visibility: wgpu::ShaderStages::VERTEX,
549                            ty: wgpu::BindingType::Buffer {
550                                ty: wgpu::BufferBindingType::Storage { read_only: true },
551                                has_dynamic_offset: false,
552                                min_binding_size: None,
553                            },
554                            count: None,
555                        },
556                    ],
557                });
558
559        let pipeline_layout = self
560            .device
561            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
562                label: Some("simple mesh pipeline layout"),
563                bind_group_layouts: &[
564                    &bind_group_layout,
565                    &self.slice_plane_bind_group_layout,
566                    &self.matcap_bind_group_layout,
567                ],
568                push_constant_ranges: &[],
569            });
570
571        let pipeline = self
572            .device
573            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
574                label: Some("simple mesh pipeline"),
575                layout: Some(&pipeline_layout),
576                vertex: wgpu::VertexState {
577                    module: &shader,
578                    entry_point: Some("vs_main"),
579                    buffers: &[],
580                    compilation_options: wgpu::PipelineCompilationOptions::default(),
581                },
582                fragment: Some(wgpu::FragmentState {
583                    module: &shader,
584                    entry_point: Some("fs_main"),
585                    targets: &[
586                        // Color output (HDR)
587                        Some(wgpu::ColorTargetState {
588                            format: wgpu::TextureFormat::Rgba16Float,
589                            blend: Some(wgpu::BlendState::ALPHA_BLENDING),
590                            write_mask: wgpu::ColorWrites::ALL,
591                        }),
592                        // Normal output (G-buffer for SSAO)
593                        Some(wgpu::ColorTargetState {
594                            format: wgpu::TextureFormat::Rgba16Float,
595                            blend: None,
596                            write_mask: wgpu::ColorWrites::ALL,
597                        }),
598                    ],
599                    compilation_options: wgpu::PipelineCompilationOptions::default(),
600                }),
601                primitive: wgpu::PrimitiveState {
602                    topology: wgpu::PrimitiveTopology::TriangleList,
603                    strip_index_format: None,
604                    front_face: wgpu::FrontFace::Ccw,
605                    cull_mode: None,
606                    polygon_mode: wgpu::PolygonMode::Fill,
607                    unclipped_depth: false,
608                    conservative: false,
609                },
610                depth_stencil: Some(wgpu::DepthStencilState {
611                    format: wgpu::TextureFormat::Depth24PlusStencil8,
612                    depth_write_enabled: true,
613                    depth_compare: wgpu::CompareFunction::Less,
614                    stencil: wgpu::StencilState::default(),
615                    bias: wgpu::DepthBiasState::default(),
616                }),
617                multisample: wgpu::MultisampleState::default(),
618                multiview: None,
619                cache: None,
620            });
621
622        self.simple_mesh_pipeline = Some(pipeline);
623        self.simple_mesh_bind_group_layout = Some(bind_group_layout);
624    }
625
626    /// Gets the curve network edge bind group layout.
627    pub fn curve_network_edge_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
628        self.curve_network_edge_bind_group_layout
629            .as_ref()
630            .expect("curve network edge pipeline not initialized")
631    }
632
633    /// Creates the curve network edge render pipeline (line rendering).
634    pub(crate) fn create_curve_network_edge_pipeline(&mut self) {
635        let shader_source = include_str!("../../shaders/curve_network_edge.wgsl");
636        let shader = self
637            .device
638            .create_shader_module(wgpu::ShaderModuleDescriptor {
639                label: Some("curve network edge shader"),
640                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
641            });
642
643        let bind_group_layout =
644            self.device
645                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
646                    label: Some("curve network edge bind group layout"),
647                    entries: &[
648                        // Camera uniforms
649                        wgpu::BindGroupLayoutEntry {
650                            binding: 0,
651                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
652                            ty: wgpu::BindingType::Buffer {
653                                ty: wgpu::BufferBindingType::Uniform,
654                                has_dynamic_offset: false,
655                                min_binding_size: NonZeroU64::new(272),
656                            },
657                            count: None,
658                        },
659                        // Curve network uniforms
660                        wgpu::BindGroupLayoutEntry {
661                            binding: 1,
662                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
663                            ty: wgpu::BindingType::Buffer {
664                                ty: wgpu::BufferBindingType::Uniform,
665                                has_dynamic_offset: false,
666                                min_binding_size: NonZeroU64::new(32),
667                            },
668                            count: None,
669                        },
670                        // Node positions storage buffer
671                        wgpu::BindGroupLayoutEntry {
672                            binding: 2,
673                            visibility: wgpu::ShaderStages::VERTEX,
674                            ty: wgpu::BindingType::Buffer {
675                                ty: wgpu::BufferBindingType::Storage { read_only: true },
676                                has_dynamic_offset: false,
677                                min_binding_size: None,
678                            },
679                            count: None,
680                        },
681                        // Node colors storage buffer
682                        wgpu::BindGroupLayoutEntry {
683                            binding: 3,
684                            visibility: wgpu::ShaderStages::VERTEX,
685                            ty: wgpu::BindingType::Buffer {
686                                ty: wgpu::BufferBindingType::Storage { read_only: true },
687                                has_dynamic_offset: false,
688                                min_binding_size: None,
689                            },
690                            count: None,
691                        },
692                        // Edge vertices storage buffer
693                        wgpu::BindGroupLayoutEntry {
694                            binding: 4,
695                            visibility: wgpu::ShaderStages::VERTEX,
696                            ty: wgpu::BindingType::Buffer {
697                                ty: wgpu::BufferBindingType::Storage { read_only: true },
698                                has_dynamic_offset: false,
699                                min_binding_size: None,
700                            },
701                            count: None,
702                        },
703                        // Edge colors storage buffer
704                        wgpu::BindGroupLayoutEntry {
705                            binding: 5,
706                            visibility: wgpu::ShaderStages::VERTEX,
707                            ty: wgpu::BindingType::Buffer {
708                                ty: wgpu::BufferBindingType::Storage { read_only: true },
709                                has_dynamic_offset: false,
710                                min_binding_size: None,
711                            },
712                            count: None,
713                        },
714                    ],
715                });
716
717        let pipeline_layout = self
718            .device
719            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
720                label: Some("curve network edge pipeline layout"),
721                bind_group_layouts: &[
722                    &bind_group_layout,
723                    &self.slice_plane_bind_group_layout,
724                    &self.matcap_bind_group_layout,
725                ],
726                push_constant_ranges: &[],
727            });
728
729        let pipeline = self
730            .device
731            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
732                label: Some("curve network edge pipeline"),
733                layout: Some(&pipeline_layout),
734                vertex: wgpu::VertexState {
735                    module: &shader,
736                    entry_point: Some("vs_main"),
737                    buffers: &[],
738                    compilation_options: wgpu::PipelineCompilationOptions::default(),
739                },
740                fragment: Some(wgpu::FragmentState {
741                    module: &shader,
742                    entry_point: Some("fs_main"),
743                    targets: &[Some(wgpu::ColorTargetState {
744                        format: wgpu::TextureFormat::Rgba16Float, // HDR format for scene rendering
745                        blend: Some(wgpu::BlendState::REPLACE),
746                        write_mask: wgpu::ColorWrites::ALL,
747                    })],
748                    compilation_options: wgpu::PipelineCompilationOptions::default(),
749                }),
750                primitive: wgpu::PrimitiveState {
751                    topology: wgpu::PrimitiveTopology::LineList,
752                    strip_index_format: None,
753                    front_face: wgpu::FrontFace::Ccw,
754                    cull_mode: None, // Lines have no front/back
755                    polygon_mode: wgpu::PolygonMode::Fill,
756                    unclipped_depth: false,
757                    conservative: false,
758                },
759                depth_stencil: Some(wgpu::DepthStencilState {
760                    format: wgpu::TextureFormat::Depth24PlusStencil8,
761                    depth_write_enabled: true,
762                    depth_compare: wgpu::CompareFunction::Less,
763                    stencil: wgpu::StencilState::default(),
764                    bias: wgpu::DepthBiasState::default(),
765                }),
766                multisample: wgpu::MultisampleState::default(),
767                multiview: None,
768                cache: None,
769            });
770
771        self.curve_network_edge_pipeline = Some(pipeline);
772        self.curve_network_edge_bind_group_layout = Some(bind_group_layout);
773    }
774
775    /// Creates the curve network tube pipelines (compute and render).
776    pub(crate) fn create_curve_network_tube_pipelines(&mut self) {
777        // Compute shader
778        let compute_shader_source = include_str!("../../shaders/curve_network_tube_compute.wgsl");
779        let compute_shader = self
780            .device
781            .create_shader_module(wgpu::ShaderModuleDescriptor {
782                label: Some("Curve Network Tube Compute Shader"),
783                source: wgpu::ShaderSource::Wgsl(compute_shader_source.into()),
784            });
785
786        // Compute bind group layout
787        let compute_bind_group_layout =
788            self.device
789                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
790                    label: Some("Curve Network Tube Compute Bind Group Layout"),
791                    entries: &[
792                        // Edge vertices (input)
793                        wgpu::BindGroupLayoutEntry {
794                            binding: 0,
795                            visibility: wgpu::ShaderStages::COMPUTE,
796                            ty: wgpu::BindingType::Buffer {
797                                ty: wgpu::BufferBindingType::Storage { read_only: true },
798                                has_dynamic_offset: false,
799                                min_binding_size: None,
800                            },
801                            count: None,
802                        },
803                        // Uniforms
804                        wgpu::BindGroupLayoutEntry {
805                            binding: 1,
806                            visibility: wgpu::ShaderStages::COMPUTE,
807                            ty: wgpu::BindingType::Buffer {
808                                ty: wgpu::BufferBindingType::Uniform,
809                                has_dynamic_offset: false,
810                                min_binding_size: NonZeroU64::new(32),
811                            },
812                            count: None,
813                        },
814                        // Output vertices
815                        wgpu::BindGroupLayoutEntry {
816                            binding: 2,
817                            visibility: wgpu::ShaderStages::COMPUTE,
818                            ty: wgpu::BindingType::Buffer {
819                                ty: wgpu::BufferBindingType::Storage { read_only: false },
820                                has_dynamic_offset: false,
821                                min_binding_size: None,
822                            },
823                            count: None,
824                        },
825                        // Num edges
826                        wgpu::BindGroupLayoutEntry {
827                            binding: 3,
828                            visibility: wgpu::ShaderStages::COMPUTE,
829                            ty: wgpu::BindingType::Buffer {
830                                ty: wgpu::BufferBindingType::Uniform,
831                                has_dynamic_offset: false,
832                                min_binding_size: NonZeroU64::new(4),
833                            },
834                            count: None,
835                        },
836                    ],
837                });
838
839        let compute_pipeline_layout =
840            self.device
841                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
842                    label: Some("Curve Network Tube Compute Pipeline Layout"),
843                    bind_group_layouts: &[&compute_bind_group_layout],
844                    push_constant_ranges: &[],
845                });
846
847        let compute_pipeline =
848            self.device
849                .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
850                    label: Some("Curve Network Tube Compute Pipeline"),
851                    layout: Some(&compute_pipeline_layout),
852                    module: &compute_shader,
853                    entry_point: Some("main"),
854                    compilation_options: wgpu::PipelineCompilationOptions::default(),
855                    cache: None,
856                });
857
858        // Render shader
859        let render_shader_source = include_str!("../../shaders/curve_network_tube.wgsl");
860        let render_shader = self
861            .device
862            .create_shader_module(wgpu::ShaderModuleDescriptor {
863                label: Some("Curve Network Tube Render Shader"),
864                source: wgpu::ShaderSource::Wgsl(render_shader_source.into()),
865            });
866
867        // Render bind group layout
868        let render_bind_group_layout =
869            self.device
870                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
871                    label: Some("Curve Network Tube Render Bind Group Layout"),
872                    entries: &[
873                        // Camera uniforms
874                        wgpu::BindGroupLayoutEntry {
875                            binding: 0,
876                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
877                            ty: wgpu::BindingType::Buffer {
878                                ty: wgpu::BufferBindingType::Uniform,
879                                has_dynamic_offset: false,
880                                min_binding_size: NonZeroU64::new(272),
881                            },
882                            count: None,
883                        },
884                        // Curve network uniforms
885                        wgpu::BindGroupLayoutEntry {
886                            binding: 1,
887                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
888                            ty: wgpu::BindingType::Buffer {
889                                ty: wgpu::BufferBindingType::Uniform,
890                                has_dynamic_offset: false,
891                                min_binding_size: NonZeroU64::new(32),
892                            },
893                            count: None,
894                        },
895                        // Edge vertices (for raycast)
896                        wgpu::BindGroupLayoutEntry {
897                            binding: 2,
898                            visibility: wgpu::ShaderStages::FRAGMENT,
899                            ty: wgpu::BindingType::Buffer {
900                                ty: wgpu::BufferBindingType::Storage { read_only: true },
901                                has_dynamic_offset: false,
902                                min_binding_size: None,
903                            },
904                            count: None,
905                        },
906                        // Edge colors
907                        wgpu::BindGroupLayoutEntry {
908                            binding: 3,
909                            visibility: wgpu::ShaderStages::FRAGMENT,
910                            ty: wgpu::BindingType::Buffer {
911                                ty: wgpu::BufferBindingType::Storage { read_only: true },
912                                has_dynamic_offset: false,
913                                min_binding_size: None,
914                            },
915                            count: None,
916                        },
917                    ],
918                });
919
920        let render_pipeline_layout =
921            self.device
922                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
923                    label: Some("Curve Network Tube Render Pipeline Layout"),
924                    bind_group_layouts: &[
925                        &render_bind_group_layout,
926                        &self.slice_plane_bind_group_layout,
927                        &self.matcap_bind_group_layout,
928                    ],
929                    push_constant_ranges: &[],
930                });
931
932        let render_pipeline = self
933            .device
934            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
935                label: Some("Curve Network Tube Render Pipeline"),
936                layout: Some(&render_pipeline_layout),
937                vertex: wgpu::VertexState {
938                    module: &render_shader,
939                    entry_point: Some("vs_main"),
940                    buffers: &[
941                        // Generated vertex buffer layout
942                        wgpu::VertexBufferLayout {
943                            array_stride: 32, // vec4<f32> position + vec4<u32> edge_id_and_vertex_id
944                            step_mode: wgpu::VertexStepMode::Vertex,
945                            attributes: &[
946                                wgpu::VertexAttribute {
947                                    format: wgpu::VertexFormat::Float32x4,
948                                    offset: 0,
949                                    shader_location: 0,
950                                },
951                                wgpu::VertexAttribute {
952                                    format: wgpu::VertexFormat::Uint32x4,
953                                    offset: 16,
954                                    shader_location: 1,
955                                },
956                            ],
957                        },
958                    ],
959                    compilation_options: wgpu::PipelineCompilationOptions::default(),
960                },
961                fragment: Some(wgpu::FragmentState {
962                    module: &render_shader,
963                    entry_point: Some("fs_main"),
964                    targets: &[Some(wgpu::ColorTargetState {
965                        format: wgpu::TextureFormat::Rgba16Float,
966                        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
967                        write_mask: wgpu::ColorWrites::ALL,
968                    })],
969                    compilation_options: wgpu::PipelineCompilationOptions::default(),
970                }),
971                primitive: wgpu::PrimitiveState {
972                    topology: wgpu::PrimitiveTopology::TriangleList,
973                    strip_index_format: None,
974                    front_face: wgpu::FrontFace::Ccw,
975                    cull_mode: None, // Don't cull - we need to see box from inside too
976                    polygon_mode: wgpu::PolygonMode::Fill,
977                    unclipped_depth: false,
978                    conservative: false,
979                },
980                depth_stencil: Some(wgpu::DepthStencilState {
981                    format: wgpu::TextureFormat::Depth24PlusStencil8,
982                    depth_write_enabled: true,
983                    depth_compare: wgpu::CompareFunction::Less,
984                    stencil: wgpu::StencilState::default(),
985                    bias: wgpu::DepthBiasState::default(),
986                }),
987                multisample: wgpu::MultisampleState::default(),
988                multiview: None,
989                cache: None,
990            });
991
992        self.curve_network_tube_pipeline = Some(render_pipeline);
993        self.curve_network_tube_compute_pipeline = Some(compute_pipeline);
994        self.curve_network_tube_bind_group_layout = Some(render_bind_group_layout);
995        self.curve_network_tube_compute_bind_group_layout = Some(compute_bind_group_layout);
996    }
997
998    /// Gets the curve network tube render bind group layout.
999    pub fn curve_network_tube_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
1000        self.curve_network_tube_bind_group_layout
1001            .as_ref()
1002            .expect("Tube bind group layout not initialized")
1003    }
1004
1005    /// Gets the curve network tube compute bind group layout.
1006    pub fn curve_network_tube_compute_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
1007        self.curve_network_tube_compute_bind_group_layout
1008            .as_ref()
1009            .expect("Tube compute bind group layout not initialized")
1010    }
1011
1012    /// Gets the curve network tube compute pipeline.
1013    pub fn curve_network_tube_compute_pipeline(&self) -> &wgpu::ComputePipeline {
1014        self.curve_network_tube_compute_pipeline
1015            .as_ref()
1016            .expect("Tube compute pipeline not initialized")
1017    }
1018}