Skip to main content

polyscope_render/engine/pipelines/
effects.rs

1//! Pipeline creation for visual effects.
2//!
3//! Contains pipelines for:
4//! - Shadow mapping (depth-only rendering from light's perspective)
5//! - Ground stencil (reflection masking)
6//! - Reflected structures (mesh, point cloud, curve network)
7
8use std::num::NonZeroU64;
9
10use super::super::RenderEngine;
11
12impl RenderEngine {
13    /// Creates the shadow render pipeline (depth-only, for rendering objects from light's perspective).
14    pub(crate) fn create_shadow_pipeline(&mut self) {
15        let shader_source = include_str!("../../shaders/shadow_map.wgsl");
16        let shader = self
17            .device
18            .create_shader_module(wgpu::ShaderModuleDescriptor {
19                label: Some("Shadow Map Shader"),
20                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
21            });
22
23        // Bind group layout matching shadow_map.wgsl:
24        // binding 0: light uniforms (view_proj, light_dir)
25        // binding 1: model uniforms (model matrix)
26        // binding 2: vertex positions (storage buffer)
27        let bind_group_layout =
28            self.device
29                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
30                    label: Some("Shadow Bind Group Layout"),
31                    entries: &[
32                        // Light uniforms
33                        wgpu::BindGroupLayoutEntry {
34                            binding: 0,
35                            visibility: wgpu::ShaderStages::VERTEX,
36                            ty: wgpu::BindingType::Buffer {
37                                ty: wgpu::BufferBindingType::Uniform,
38                                has_dynamic_offset: false,
39                                min_binding_size: NonZeroU64::new(80),
40                            },
41                            count: None,
42                        },
43                        // Model uniforms
44                        wgpu::BindGroupLayoutEntry {
45                            binding: 1,
46                            visibility: wgpu::ShaderStages::VERTEX,
47                            ty: wgpu::BindingType::Buffer {
48                                ty: wgpu::BufferBindingType::Uniform,
49                                has_dynamic_offset: false,
50                                min_binding_size: NonZeroU64::new(64),
51                            },
52                            count: None,
53                        },
54                        // Vertex positions (storage buffer)
55                        wgpu::BindGroupLayoutEntry {
56                            binding: 2,
57                            visibility: wgpu::ShaderStages::VERTEX,
58                            ty: wgpu::BindingType::Buffer {
59                                ty: wgpu::BufferBindingType::Storage { read_only: true },
60                                has_dynamic_offset: false,
61                                min_binding_size: None,
62                            },
63                            count: None,
64                        },
65                    ],
66                });
67
68        let pipeline_layout = self
69            .device
70            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
71                label: Some("Shadow Pipeline Layout"),
72                bind_group_layouts: &[&bind_group_layout],
73                push_constant_ranges: &[],
74            });
75
76        let pipeline = self
77            .device
78            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
79                label: Some("Shadow Render Pipeline"),
80                layout: Some(&pipeline_layout),
81                vertex: wgpu::VertexState {
82                    module: &shader,
83                    entry_point: Some("vs_main"),
84                    buffers: &[],
85                    compilation_options: wgpu::PipelineCompilationOptions::default(),
86                },
87                fragment: Some(wgpu::FragmentState {
88                    module: &shader,
89                    entry_point: Some("fs_main"),
90                    targets: &[], // Depth-only, no color attachments
91                    compilation_options: wgpu::PipelineCompilationOptions::default(),
92                }),
93                primitive: wgpu::PrimitiveState {
94                    topology: wgpu::PrimitiveTopology::TriangleList,
95                    strip_index_format: None,
96                    front_face: wgpu::FrontFace::Ccw,
97                    cull_mode: Some(wgpu::Face::Back),
98                    ..wgpu::PrimitiveState::default()
99                },
100                depth_stencil: Some(wgpu::DepthStencilState {
101                    // Shadow pipeline uses Depth32Float to match shadow map texture
102                    format: wgpu::TextureFormat::Depth32Float,
103                    depth_write_enabled: true,
104                    depth_compare: wgpu::CompareFunction::Less,
105                    stencil: wgpu::StencilState::default(),
106                    bias: wgpu::DepthBiasState {
107                        constant: 2, // Bias to prevent shadow acne
108                        slope_scale: 2.0,
109                        clamp: 0.0,
110                    },
111                }),
112                multisample: wgpu::MultisampleState::default(), // No MSAA for shadow map
113                multiview: None,
114                cache: None,
115            });
116
117        self.shadow_pipeline = Some(pipeline);
118        self.shadow_bind_group_layout = Some(bind_group_layout);
119    }
120
121    /// Gets the shadow render pipeline.
122    pub fn shadow_pipeline(&self) -> Option<&wgpu::RenderPipeline> {
123        self.shadow_pipeline.as_ref()
124    }
125
126    /// Gets the shadow bind group layout.
127    pub fn shadow_bind_group_layout(&self) -> Option<&wgpu::BindGroupLayout> {
128        self.shadow_bind_group_layout.as_ref()
129    }
130
131    /// Creates the ground stencil pipeline for reflection masking.
132    pub(crate) fn create_ground_stencil_pipeline(&mut self) {
133        let shader = self
134            .device
135            .create_shader_module(wgpu::ShaderModuleDescriptor {
136                label: Some("Ground Stencil Shader"),
137                source: wgpu::ShaderSource::Wgsl(
138                    include_str!("../../shaders/ground_stencil.wgsl").into(),
139                ),
140            });
141
142        // Use existing ground plane bind group layout (camera + ground uniforms)
143        let pipeline_layout = self
144            .device
145            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
146                label: Some("Ground Stencil Pipeline Layout"),
147                bind_group_layouts: &[&self.ground_plane_bind_group_layout],
148                push_constant_ranges: &[],
149            });
150
151        self.ground_stencil_pipeline = Some(self.device.create_render_pipeline(
152            &wgpu::RenderPipelineDescriptor {
153                label: Some("Ground Stencil Pipeline"),
154                layout: Some(&pipeline_layout),
155                vertex: wgpu::VertexState {
156                    module: &shader,
157                    entry_point: Some("vs_main"),
158                    buffers: &[],
159                    compilation_options: wgpu::PipelineCompilationOptions::default(),
160                },
161                fragment: Some(wgpu::FragmentState {
162                    module: &shader,
163                    entry_point: Some("fs_main"),
164                    targets: &[Some(wgpu::ColorTargetState {
165                        format: wgpu::TextureFormat::Rgba16Float,
166                        blend: None,
167                        write_mask: wgpu::ColorWrites::empty(), // No color writes
168                    })],
169                    compilation_options: wgpu::PipelineCompilationOptions::default(),
170                }),
171                primitive: wgpu::PrimitiveState {
172                    topology: wgpu::PrimitiveTopology::TriangleList,
173                    ..wgpu::PrimitiveState::default()
174                },
175                depth_stencil: Some(wgpu::DepthStencilState {
176                    format: wgpu::TextureFormat::Depth24PlusStencil8,
177                    depth_write_enabled: false, // Don't write depth
178                    depth_compare: wgpu::CompareFunction::Always,
179                    stencil: wgpu::StencilState {
180                        front: wgpu::StencilFaceState {
181                            compare: wgpu::CompareFunction::Always,
182                            fail_op: wgpu::StencilOperation::Keep,
183                            depth_fail_op: wgpu::StencilOperation::Keep,
184                            pass_op: wgpu::StencilOperation::Replace, // Write stencil ref
185                        },
186                        back: wgpu::StencilFaceState {
187                            compare: wgpu::CompareFunction::Always,
188                            fail_op: wgpu::StencilOperation::Keep,
189                            depth_fail_op: wgpu::StencilOperation::Keep,
190                            pass_op: wgpu::StencilOperation::Replace,
191                        },
192                        read_mask: 0xFF,
193                        write_mask: 0xFF,
194                    },
195                    bias: wgpu::DepthBiasState::default(),
196                }),
197                multisample: wgpu::MultisampleState::default(),
198                multiview: None,
199                cache: None,
200            },
201        ));
202    }
203
204    /// Creates the reflected mesh pipeline for ground reflections.
205    pub(crate) fn create_reflected_mesh_pipeline(&mut self) {
206        let shader = self
207            .device
208            .create_shader_module(wgpu::ShaderModuleDescriptor {
209                label: Some("Reflected Mesh Shader"),
210                source: wgpu::ShaderSource::Wgsl(
211                    include_str!("../../shaders/reflected_mesh.wgsl").into(),
212                ),
213            });
214
215        // Bind group 0: camera, mesh uniforms, buffers (same as surface mesh)
216        // Bind group 1: reflection uniforms
217        let Some(reflection_pass) = &self.reflection_pass else {
218            return;
219        };
220
221        // Create bind group layout for group 0 (mesh data)
222        let mesh_bind_group_layout =
223            self.device
224                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
225                    label: Some("Reflected Mesh Bind Group Layout 0"),
226                    entries: &[
227                        // Camera uniforms
228                        wgpu::BindGroupLayoutEntry {
229                            binding: 0,
230                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
231                            ty: wgpu::BindingType::Buffer {
232                                ty: wgpu::BufferBindingType::Uniform,
233                                has_dynamic_offset: false,
234                                min_binding_size: NonZeroU64::new(272),
235                            },
236                            count: None,
237                        },
238                        // Mesh uniforms
239                        wgpu::BindGroupLayoutEntry {
240                            binding: 1,
241                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
242                            ty: wgpu::BindingType::Buffer {
243                                ty: wgpu::BufferBindingType::Uniform,
244                                has_dynamic_offset: false,
245                                min_binding_size: NonZeroU64::new(160),
246                            },
247                            count: None,
248                        },
249                        // Positions
250                        wgpu::BindGroupLayoutEntry {
251                            binding: 2,
252                            visibility: wgpu::ShaderStages::VERTEX,
253                            ty: wgpu::BindingType::Buffer {
254                                ty: wgpu::BufferBindingType::Storage { read_only: true },
255                                has_dynamic_offset: false,
256                                min_binding_size: None,
257                            },
258                            count: None,
259                        },
260                        // Normals
261                        wgpu::BindGroupLayoutEntry {
262                            binding: 3,
263                            visibility: wgpu::ShaderStages::VERTEX,
264                            ty: wgpu::BindingType::Buffer {
265                                ty: wgpu::BufferBindingType::Storage { read_only: true },
266                                has_dynamic_offset: false,
267                                min_binding_size: None,
268                            },
269                            count: None,
270                        },
271                        // Barycentrics
272                        wgpu::BindGroupLayoutEntry {
273                            binding: 4,
274                            visibility: wgpu::ShaderStages::VERTEX,
275                            ty: wgpu::BindingType::Buffer {
276                                ty: wgpu::BufferBindingType::Storage { read_only: true },
277                                has_dynamic_offset: false,
278                                min_binding_size: None,
279                            },
280                            count: None,
281                        },
282                        // Colors
283                        wgpu::BindGroupLayoutEntry {
284                            binding: 5,
285                            visibility: wgpu::ShaderStages::VERTEX,
286                            ty: wgpu::BindingType::Buffer {
287                                ty: wgpu::BufferBindingType::Storage { read_only: true },
288                                has_dynamic_offset: false,
289                                min_binding_size: None,
290                            },
291                            count: None,
292                        },
293                        // Edge is real
294                        wgpu::BindGroupLayoutEntry {
295                            binding: 6,
296                            visibility: wgpu::ShaderStages::VERTEX,
297                            ty: wgpu::BindingType::Buffer {
298                                ty: wgpu::BufferBindingType::Storage { read_only: true },
299                                has_dynamic_offset: false,
300                                min_binding_size: None,
301                            },
302                            count: None,
303                        },
304                    ],
305                });
306
307        let pipeline_layout = self
308            .device
309            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
310                label: Some("Reflected Mesh Pipeline Layout"),
311                bind_group_layouts: &[
312                    &mesh_bind_group_layout,
313                    reflection_pass.bind_group_layout(),
314                    &self.matcap_bind_group_layout,
315                    &self.slice_plane_bind_group_layout,
316                ],
317                push_constant_ranges: &[],
318            });
319
320        self.reflected_mesh_pipeline = Some(self.device.create_render_pipeline(
321            &wgpu::RenderPipelineDescriptor {
322                label: Some("Reflected Mesh Pipeline"),
323                layout: Some(&pipeline_layout),
324                vertex: wgpu::VertexState {
325                    module: &shader,
326                    entry_point: Some("vs_main"),
327                    buffers: &[],
328                    compilation_options: wgpu::PipelineCompilationOptions::default(),
329                },
330                fragment: Some(wgpu::FragmentState {
331                    module: &shader,
332                    entry_point: Some("fs_main"),
333                    targets: &[Some(wgpu::ColorTargetState {
334                        format: wgpu::TextureFormat::Rgba16Float,
335                        blend: Some(wgpu::BlendState {
336                            color: wgpu::BlendComponent {
337                                src_factor: wgpu::BlendFactor::SrcAlpha,
338                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
339                                operation: wgpu::BlendOperation::Add,
340                            },
341                            alpha: wgpu::BlendComponent {
342                                src_factor: wgpu::BlendFactor::One,
343                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
344                                operation: wgpu::BlendOperation::Add,
345                            },
346                        }),
347                        write_mask: wgpu::ColorWrites::ALL,
348                    })],
349                    compilation_options: wgpu::PipelineCompilationOptions::default(),
350                }),
351                primitive: wgpu::PrimitiveState {
352                    topology: wgpu::PrimitiveTopology::TriangleList,
353                    cull_mode: None, // No culling for reflections (VolumeMesh may have inconsistent winding)
354                    ..wgpu::PrimitiveState::default()
355                },
356                depth_stencil: Some(wgpu::DepthStencilState {
357                    format: wgpu::TextureFormat::Depth24PlusStencil8,
358                    depth_write_enabled: false, // Don't write depth for reflections
359                    depth_compare: wgpu::CompareFunction::Always, // Always pass depth test, stencil does the masking
360                    stencil: wgpu::StencilState {
361                        front: wgpu::StencilFaceState {
362                            compare: wgpu::CompareFunction::Equal, // Only render where stencil == ref
363                            fail_op: wgpu::StencilOperation::Keep,
364                            depth_fail_op: wgpu::StencilOperation::Keep,
365                            pass_op: wgpu::StencilOperation::Keep,
366                        },
367                        back: wgpu::StencilFaceState {
368                            compare: wgpu::CompareFunction::Equal,
369                            fail_op: wgpu::StencilOperation::Keep,
370                            depth_fail_op: wgpu::StencilOperation::Keep,
371                            pass_op: wgpu::StencilOperation::Keep,
372                        },
373                        read_mask: 0xFF,
374                        write_mask: 0x00, // Don't modify stencil
375                    },
376                    bias: wgpu::DepthBiasState::default(),
377                }),
378                multisample: wgpu::MultisampleState::default(),
379                multiview: None,
380                cache: None,
381            },
382        ));
383
384        self.reflected_mesh_bind_group_layout = Some(mesh_bind_group_layout);
385    }
386
387    pub(crate) fn create_reflected_point_cloud_pipeline(&mut self) {
388        let shader = self
389            .device
390            .create_shader_module(wgpu::ShaderModuleDescriptor {
391                label: Some("Reflected Point Cloud Shader"),
392                source: wgpu::ShaderSource::Wgsl(
393                    include_str!("../../shaders/reflected_point_sphere.wgsl").into(),
394                ),
395            });
396
397        let Some(reflection_pass) = &self.reflection_pass else {
398            return;
399        };
400
401        // Bind group layout for group 0 (point cloud data)
402        let point_bind_group_layout =
403            self.device
404                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
405                    label: Some("Reflected Point Cloud Bind Group Layout 0"),
406                    entries: &[
407                        // Camera uniforms
408                        wgpu::BindGroupLayoutEntry {
409                            binding: 0,
410                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
411                            ty: wgpu::BindingType::Buffer {
412                                ty: wgpu::BufferBindingType::Uniform,
413                                has_dynamic_offset: false,
414                                min_binding_size: NonZeroU64::new(272),
415                            },
416                            count: None,
417                        },
418                        // Point uniforms
419                        wgpu::BindGroupLayoutEntry {
420                            binding: 1,
421                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
422                            ty: wgpu::BindingType::Buffer {
423                                ty: wgpu::BufferBindingType::Uniform,
424                                has_dynamic_offset: false,
425                                min_binding_size: NonZeroU64::new(96),
426                            },
427                            count: None,
428                        },
429                        // Point positions
430                        wgpu::BindGroupLayoutEntry {
431                            binding: 2,
432                            visibility: wgpu::ShaderStages::VERTEX,
433                            ty: wgpu::BindingType::Buffer {
434                                ty: wgpu::BufferBindingType::Storage { read_only: true },
435                                has_dynamic_offset: false,
436                                min_binding_size: None,
437                            },
438                            count: None,
439                        },
440                        // Point colors
441                        wgpu::BindGroupLayoutEntry {
442                            binding: 3,
443                            visibility: wgpu::ShaderStages::VERTEX,
444                            ty: wgpu::BindingType::Buffer {
445                                ty: wgpu::BufferBindingType::Storage { read_only: true },
446                                has_dynamic_offset: false,
447                                min_binding_size: None,
448                            },
449                            count: None,
450                        },
451                    ],
452                });
453
454        let pipeline_layout = self
455            .device
456            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
457                label: Some("Reflected Point Cloud Pipeline Layout"),
458                bind_group_layouts: &[
459                    &point_bind_group_layout,
460                    reflection_pass.bind_group_layout(),
461                    &self.matcap_bind_group_layout,
462                    &self.slice_plane_bind_group_layout,
463                ],
464                push_constant_ranges: &[],
465            });
466
467        self.reflected_point_cloud_pipeline = Some(self.device.create_render_pipeline(
468            &wgpu::RenderPipelineDescriptor {
469                label: Some("Reflected Point Cloud Pipeline"),
470                layout: Some(&pipeline_layout),
471                vertex: wgpu::VertexState {
472                    module: &shader,
473                    entry_point: Some("vs_main"),
474                    buffers: &[],
475                    compilation_options: wgpu::PipelineCompilationOptions::default(),
476                },
477                fragment: Some(wgpu::FragmentState {
478                    module: &shader,
479                    entry_point: Some("fs_main"),
480                    targets: &[Some(wgpu::ColorTargetState {
481                        format: wgpu::TextureFormat::Rgba16Float,
482                        blend: Some(wgpu::BlendState {
483                            color: wgpu::BlendComponent {
484                                src_factor: wgpu::BlendFactor::SrcAlpha,
485                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
486                                operation: wgpu::BlendOperation::Add,
487                            },
488                            alpha: wgpu::BlendComponent {
489                                src_factor: wgpu::BlendFactor::One,
490                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
491                                operation: wgpu::BlendOperation::Add,
492                            },
493                        }),
494                        write_mask: wgpu::ColorWrites::ALL,
495                    })],
496                    compilation_options: wgpu::PipelineCompilationOptions::default(),
497                }),
498                primitive: wgpu::PrimitiveState {
499                    topology: wgpu::PrimitiveTopology::TriangleList,
500                    ..wgpu::PrimitiveState::default()
501                },
502                depth_stencil: Some(wgpu::DepthStencilState {
503                    format: wgpu::TextureFormat::Depth24PlusStencil8,
504                    depth_write_enabled: false,
505                    depth_compare: wgpu::CompareFunction::Always,
506                    stencil: wgpu::StencilState {
507                        front: wgpu::StencilFaceState {
508                            compare: wgpu::CompareFunction::Equal,
509                            fail_op: wgpu::StencilOperation::Keep,
510                            depth_fail_op: wgpu::StencilOperation::Keep,
511                            pass_op: wgpu::StencilOperation::Keep,
512                        },
513                        back: wgpu::StencilFaceState {
514                            compare: wgpu::CompareFunction::Equal,
515                            fail_op: wgpu::StencilOperation::Keep,
516                            depth_fail_op: wgpu::StencilOperation::Keep,
517                            pass_op: wgpu::StencilOperation::Keep,
518                        },
519                        read_mask: 0xFF,
520                        write_mask: 0x00,
521                    },
522                    bias: wgpu::DepthBiasState::default(),
523                }),
524                multisample: wgpu::MultisampleState::default(),
525                multiview: None,
526                cache: None,
527            },
528        ));
529
530        self.reflected_point_cloud_bind_group_layout = Some(point_bind_group_layout);
531    }
532
533    pub(crate) fn create_reflected_curve_network_pipeline(&mut self) {
534        let shader = self
535            .device
536            .create_shader_module(wgpu::ShaderModuleDescriptor {
537                label: Some("Reflected Curve Network Shader"),
538                source: wgpu::ShaderSource::Wgsl(
539                    include_str!("../../shaders/reflected_curve_network_tube.wgsl").into(),
540                ),
541            });
542
543        let Some(reflection_pass) = &self.reflection_pass else {
544            return;
545        };
546
547        // Bind group layout for group 0 (curve network data)
548        let curve_bind_group_layout =
549            self.device
550                .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
551                    label: Some("Reflected Curve Network Bind Group Layout 0"),
552                    entries: &[
553                        // Camera uniforms
554                        wgpu::BindGroupLayoutEntry {
555                            binding: 0,
556                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
557                            ty: wgpu::BindingType::Buffer {
558                                ty: wgpu::BufferBindingType::Uniform,
559                                has_dynamic_offset: false,
560                                min_binding_size: NonZeroU64::new(272),
561                            },
562                            count: None,
563                        },
564                        // Curve network uniforms
565                        wgpu::BindGroupLayoutEntry {
566                            binding: 1,
567                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
568                            ty: wgpu::BindingType::Buffer {
569                                ty: wgpu::BufferBindingType::Uniform,
570                                has_dynamic_offset: false,
571                                min_binding_size: NonZeroU64::new(32),
572                            },
573                            count: None,
574                        },
575                        // Edge vertices
576                        wgpu::BindGroupLayoutEntry {
577                            binding: 2,
578                            visibility: wgpu::ShaderStages::FRAGMENT,
579                            ty: wgpu::BindingType::Buffer {
580                                ty: wgpu::BufferBindingType::Storage { read_only: true },
581                                has_dynamic_offset: false,
582                                min_binding_size: None,
583                            },
584                            count: None,
585                        },
586                        // Edge colors
587                        wgpu::BindGroupLayoutEntry {
588                            binding: 3,
589                            visibility: wgpu::ShaderStages::FRAGMENT,
590                            ty: wgpu::BindingType::Buffer {
591                                ty: wgpu::BufferBindingType::Storage { read_only: true },
592                                has_dynamic_offset: false,
593                                min_binding_size: None,
594                            },
595                            count: None,
596                        },
597                    ],
598                });
599
600        let pipeline_layout = self
601            .device
602            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
603                label: Some("Reflected Curve Network Pipeline Layout"),
604                bind_group_layouts: &[
605                    &curve_bind_group_layout,
606                    reflection_pass.bind_group_layout(),
607                    &self.matcap_bind_group_layout,
608                    &self.slice_plane_bind_group_layout,
609                ],
610                push_constant_ranges: &[],
611            });
612
613        // Vertex buffer layout for tube bounding box
614        let vertex_buffer_layout = wgpu::VertexBufferLayout {
615            array_stride: std::mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
616            step_mode: wgpu::VertexStepMode::Vertex,
617            attributes: &[
618                // position (vec4)
619                wgpu::VertexAttribute {
620                    format: wgpu::VertexFormat::Float32x4,
621                    offset: 0,
622                    shader_location: 0,
623                },
624                // edge_id_and_vertex_id (vec4<u32>)
625                wgpu::VertexAttribute {
626                    format: wgpu::VertexFormat::Uint32x4,
627                    offset: 16,
628                    shader_location: 1,
629                },
630            ],
631        };
632
633        self.reflected_curve_network_pipeline = Some(self.device.create_render_pipeline(
634            &wgpu::RenderPipelineDescriptor {
635                label: Some("Reflected Curve Network Pipeline"),
636                layout: Some(&pipeline_layout),
637                vertex: wgpu::VertexState {
638                    module: &shader,
639                    entry_point: Some("vs_main"),
640                    buffers: &[vertex_buffer_layout],
641                    compilation_options: wgpu::PipelineCompilationOptions::default(),
642                },
643                fragment: Some(wgpu::FragmentState {
644                    module: &shader,
645                    entry_point: Some("fs_main"),
646                    targets: &[Some(wgpu::ColorTargetState {
647                        format: wgpu::TextureFormat::Rgba16Float,
648                        blend: Some(wgpu::BlendState {
649                            color: wgpu::BlendComponent {
650                                src_factor: wgpu::BlendFactor::SrcAlpha,
651                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
652                                operation: wgpu::BlendOperation::Add,
653                            },
654                            alpha: wgpu::BlendComponent {
655                                src_factor: wgpu::BlendFactor::One,
656                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
657                                operation: wgpu::BlendOperation::Add,
658                            },
659                        }),
660                        write_mask: wgpu::ColorWrites::ALL,
661                    })],
662                    compilation_options: wgpu::PipelineCompilationOptions::default(),
663                }),
664                primitive: wgpu::PrimitiveState {
665                    topology: wgpu::PrimitiveTopology::TriangleList,
666                    cull_mode: None, // No culling for tubes
667                    ..wgpu::PrimitiveState::default()
668                },
669                depth_stencil: Some(wgpu::DepthStencilState {
670                    format: wgpu::TextureFormat::Depth24PlusStencil8,
671                    depth_write_enabled: false, // Don't write depth for reflections
672                    depth_compare: wgpu::CompareFunction::Always, // Always pass depth test, stencil does the masking
673                    stencil: wgpu::StencilState {
674                        front: wgpu::StencilFaceState {
675                            compare: wgpu::CompareFunction::Equal,
676                            fail_op: wgpu::StencilOperation::Keep,
677                            depth_fail_op: wgpu::StencilOperation::Keep,
678                            pass_op: wgpu::StencilOperation::Keep,
679                        },
680                        back: wgpu::StencilFaceState {
681                            compare: wgpu::CompareFunction::Equal,
682                            fail_op: wgpu::StencilOperation::Keep,
683                            depth_fail_op: wgpu::StencilOperation::Keep,
684                            pass_op: wgpu::StencilOperation::Keep,
685                        },
686                        read_mask: 0xFF,
687                        write_mask: 0x00,
688                    },
689                    bias: wgpu::DepthBiasState::default(),
690                }),
691                multisample: wgpu::MultisampleState::default(),
692                multiview: None,
693                cache: None,
694            },
695        ));
696
697        self.reflected_curve_network_bind_group_layout = Some(curve_bind_group_layout);
698    }
699}