Skip to main content

brep_render/render/
pipelines.rs

1use super::*;
2
3impl RenderCore {
4    pub fn new(device: wgpu::Device, queue: wgpu::Queue, format: wgpu::TextureFormat) -> Self {
5        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
6            label: Some("brep-render shaders"),
7            source: wgpu::ShaderSource::Wgsl(include_str!("../shaders.wgsl").into()),
8        });
9
10        let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
11            label: Some("globals"),
12            entries: &[wgpu::BindGroupLayoutEntry {
13                binding: 0,
14                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
15                ty: wgpu::BindingType::Buffer {
16                    ty: wgpu::BufferBindingType::Uniform,
17                    has_dynamic_offset: false,
18                    min_binding_size: None,
19                },
20                count: None,
21            }],
22        });
23        let style_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
24            label: Some("style"),
25            entries: &[wgpu::BindGroupLayoutEntry {
26                binding: 0,
27                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
28                ty: wgpu::BindingType::Buffer {
29                    ty: wgpu::BufferBindingType::Uniform,
30                    has_dynamic_offset: false,
31                    min_binding_size: None,
32                },
33                count: None,
34            }],
35        });
36        let model_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
37            label: Some("model"),
38            entries: &[wgpu::BindGroupLayoutEntry {
39                binding: 0,
40                visibility: wgpu::ShaderStages::VERTEX,
41                ty: wgpu::BindingType::Buffer {
42                    ty: wgpu::BufferBindingType::Uniform,
43                    has_dynamic_offset: true,
44                    min_binding_size: std::num::NonZeroU64::new(std::mem::size_of::<ModelParams>() as u64),
45                },
46                count: None,
47            }],
48        });
49        let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
50            label: Some("globals"),
51            size: std::mem::size_of::<Globals>() as u64,
52            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
53            mapped_at_creation: false,
54        });
55        let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
56            label: Some("globals"),
57            layout: &globals_layout,
58            entries: &[wgpu::BindGroupEntry {
59                binding: 0,
60                resource: globals_buf.as_entire_binding(),
61            }],
62        });
63
64        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
65            label: Some("brep-render"),
66            bind_group_layouts: &[Some(&globals_layout), Some(&style_layout)],
67            immediate_size: 0,
68        });
69        // The solids' pipelines draw each solid through its own pose (group 2).
70        let solid_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
71            label: Some("brep-render solids"),
72            bind_group_layouts: &[Some(&globals_layout), Some(&style_layout), Some(&model_layout)],
73            immediate_size: 0,
74        });
75
76        const ALPHA_BLEND: wgpu::BlendState = wgpu::BlendState::ALPHA_BLENDING;
77        let color_target = |blend: Option<wgpu::BlendState>| {
78            Some(wgpu::ColorTargetState {
79                format,
80                blend,
81                write_mask: wgpu::ColorWrites::ALL,
82            })
83        };
84        let multisample = wgpu::MultisampleState {
85            count: SAMPLES,
86            ..Default::default()
87        };
88
89        let mesh_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
90            label: Some("mesh"),
91            layout: Some(&solid_layout),
92            vertex: wgpu::VertexState {
93                module: &shader,
94                entry_point: Some("vs_mesh"),
95                compilation_options: Default::default(),
96                buffers: &[wgpu::VertexBufferLayout {
97                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
98                    step_mode: wgpu::VertexStepMode::Vertex,
99                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
100                }],
101            },
102            fragment: Some(wgpu::FragmentState {
103                module: &shader,
104                entry_point: Some("fs_mesh"),
105                compilation_options: Default::default(),
106                targets: &[color_target(None)],
107            }),
108            primitive: wgpu::PrimitiveState {
109                topology: wgpu::PrimitiveTopology::TriangleList,
110                front_face: wgpu::FrontFace::Ccw,
111                // Double-sided, like the retired materials during picking — and
112                // the +plane-z winding convention (R13) is never "corrected".
113                cull_mode: None,
114                ..Default::default()
115            },
116            depth_stencil: Some(wgpu::DepthStencilState {
117                format: DEPTH_FORMAT,
118                depth_write_enabled: Some(true),
119                depth_compare: Some(wgpu::CompareFunction::Less),
120                stencil: Default::default(),
121                // Push faces slightly back so the edge overlay wins its
122                // z-fight against the faces it outlines.
123                bias: wgpu::DepthBiasState {
124                    constant: 4,
125                    slope_scale: 2.0,
126                    clamp: 0.0,
127                },
128            }),
129            multisample,
130            multiview_mask: None,
131            cache: None,
132        });
133
134        // A Line art step's outlines (`vs_hull`), drawn after the faces:
135        // double-sided, depth-tested but not written, no bias (the shader
136        // pushes it back, at most to the far plane, which the camera's depth
137        // range is fitted to: LessEqual keeps what lands exactly there).
138        let hull_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
139            label: Some("line art outline"),
140            layout: Some(&solid_layout),
141            vertex: wgpu::VertexState {
142                module: &shader,
143                entry_point: Some("vs_hull"),
144                compilation_options: Default::default(),
145                buffers: &[wgpu::VertexBufferLayout {
146                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
147                    step_mode: wgpu::VertexStepMode::Vertex,
148                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
149                }],
150            },
151            fragment: Some(wgpu::FragmentState {
152                module: &shader,
153                entry_point: Some("fs_hull"),
154                compilation_options: Default::default(),
155                targets: &[color_target(None)],
156            }),
157            primitive: wgpu::PrimitiveState {
158                topology: wgpu::PrimitiveTopology::TriangleList,
159                front_face: wgpu::FrontFace::Ccw,
160                cull_mode: None,
161                ..Default::default()
162            },
163            depth_stencil: Some(wgpu::DepthStencilState {
164                format: DEPTH_FORMAT,
165                depth_write_enabled: Some(false),
166                depth_compare: Some(wgpu::CompareFunction::LessEqual),
167                stencil: Default::default(),
168                bias: Default::default(),
169            }),
170            multisample,
171            multiview_mask: None,
172            cache: None,
173        });
174
175        let context_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
176            label: Some("work part context"),
177            layout: Some(&solid_layout),
178            vertex: wgpu::VertexState {
179                module: &shader,
180                entry_point: Some("vs_mesh"),
181                compilation_options: Default::default(),
182                buffers: &[wgpu::VertexBufferLayout {
183                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
184                    step_mode: wgpu::VertexStepMode::Vertex,
185                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
186                }],
187            },
188            fragment: Some(wgpu::FragmentState {
189                module: &shader,
190                entry_point: Some("fs_context"),
191                compilation_options: Default::default(),
192                targets: &[color_target(Some(ALPHA_BLEND))],
193            }),
194            primitive: wgpu::PrimitiveState {
195                topology: wgpu::PrimitiveTopology::TriangleList,
196                front_face: wgpu::FrontFace::Ccw,
197                // Double-sided, like the retired materials during picking — and
198                // the +plane-z winding convention (R13) is never "corrected".
199                cull_mode: None,
200                ..Default::default()
201            },
202            depth_stencil: Some(wgpu::DepthStencilState {
203                format: DEPTH_FORMAT,
204                depth_write_enabled: Some(false),
205                depth_compare: Some(wgpu::CompareFunction::Less),
206                stencil: Default::default(),
207                // Push faces slightly back so the edge overlay wins its
208                // z-fight against the faces it outlines.
209                bias: wgpu::DepthBiasState {
210                    constant: 4,
211                    slope_scale: 2.0,
212                    clamp: 0.0,
213                },
214            }),
215            multisample,
216            multiview_mask: None,
217            cache: None,
218        });
219
220        // Wireframe: same vertex layout as the mesh, but drawn as a line list
221        // (each triangle's 3 edges) with a flat fragment. `PolygonMode::Line`
222        // is unavailable on the WebGL/downlevel path, so we expand to lines.
223        let wire_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
224            label: Some("wireframe"),
225            layout: Some(&solid_layout),
226            vertex: wgpu::VertexState {
227                module: &shader,
228                entry_point: Some("vs_mesh"),
229                compilation_options: Default::default(),
230                buffers: &[wgpu::VertexBufferLayout {
231                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
232                    step_mode: wgpu::VertexStepMode::Vertex,
233                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
234                }],
235            },
236            fragment: Some(wgpu::FragmentState {
237                module: &shader,
238                entry_point: Some("fs_wire"),
239                compilation_options: Default::default(),
240                targets: &[color_target(None)],
241            }),
242            primitive: wgpu::PrimitiveState {
243                topology: wgpu::PrimitiveTopology::LineList,
244                front_face: wgpu::FrontFace::Ccw,
245                cull_mode: None,
246                ..Default::default()
247            },
248            depth_stencil: Some(wgpu::DepthStencilState {
249                format: DEPTH_FORMAT,
250                depth_write_enabled: Some(true),
251                depth_compare: Some(wgpu::CompareFunction::Less),
252                stencil: Default::default(),
253                bias: Default::default(),
254            }),
255            multisample,
256            multiview_mask: None,
257            cache: None,
258        });
259
260        let edge_pipeline = |label: &str, depth_compare: wgpu::CompareFunction| {
261            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
262                label: Some(label),
263                layout: Some(&solid_layout),
264                vertex: wgpu::VertexState {
265                    module: &shader,
266                    entry_point: Some("vs_edge"),
267                    compilation_options: Default::default(),
268                    buffers: &[wgpu::VertexBufferLayout {
269                        array_stride: std::mem::size_of::<EdgeInstance>() as u64,
270                        step_mode: wgpu::VertexStepMode::Instance,
271                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
272                    }],
273                },
274                fragment: Some(wgpu::FragmentState {
275                    module: &shader,
276                    entry_point: Some("fs_edge"),
277                    compilation_options: Default::default(),
278                    targets: &[color_target(Some(ALPHA_BLEND))],
279                }),
280                primitive: wgpu::PrimitiveState {
281                    topology: wgpu::PrimitiveTopology::TriangleList,
282                    front_face: wgpu::FrontFace::Ccw,
283                    cull_mode: None,
284                    ..Default::default()
285                },
286                depth_stencil: Some(wgpu::DepthStencilState {
287                    format: DEPTH_FORMAT,
288                    // Edges never write depth (they must not occlude faces —
289                    // the CADmaterials depthWrite:false behavior).
290                    depth_write_enabled: Some(false),
291                    depth_compare: Some(depth_compare),
292                    stencil: Default::default(),
293                    bias: Default::default(),
294                }),
295                multisample,
296                multiview_mask: None,
297                cache: None,
298            })
299        };
300        // Visible edges pass the depth test; the hidden pass draws only where
301        // the edge is BEHIND geometry (dimmed, not dropped — R17).
302        let edge_visible_pipeline = edge_pipeline("edges", wgpu::CompareFunction::LessEqual);
303        let edge_hidden_pipeline = edge_pipeline("edges-hidden", wgpu::CompareFunction::Greater);
304
305        let point_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
306            label: Some("points"),
307            layout: Some(&solid_layout),
308            vertex: wgpu::VertexState {
309                module: &shader,
310                entry_point: Some("vs_point"),
311                compilation_options: Default::default(),
312                buffers: &[wgpu::VertexBufferLayout {
313                    array_stride: std::mem::size_of::<PointInstance>() as u64,
314                    step_mode: wgpu::VertexStepMode::Instance,
315                    attributes: &wgpu::vertex_attr_array![0 => Float32x3],
316                }],
317            },
318            fragment: Some(wgpu::FragmentState {
319                module: &shader,
320                entry_point: Some("fs_point"),
321                compilation_options: Default::default(),
322                targets: &[color_target(Some(ALPHA_BLEND))],
323            }),
324            primitive: wgpu::PrimitiveState {
325                topology: wgpu::PrimitiveTopology::TriangleList,
326                front_face: wgpu::FrontFace::Ccw,
327                cull_mode: None,
328                ..Default::default()
329            },
330            depth_stencil: Some(wgpu::DepthStencilState {
331                format: DEPTH_FORMAT,
332                depth_write_enabled: Some(false),
333                depth_compare: Some(wgpu::CompareFunction::LessEqual),
334                stencil: Default::default(),
335                bias: Default::default(),
336            }),
337            multisample,
338            multiview_mask: None,
339            cache: None,
340        });
341
342        // Overlay-widget pipelines. Both reuse the globals + style
343        // pipeline layout; tris carry per-vertex color+normal, lines carry
344        // per-instance p0/p1/color. They draw in a depth-cleared pass over the
345        // solids so widgets read on top.
346        let overlay_tri_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
347            label: Some("overlay-tri"),
348            layout: Some(&pipeline_layout),
349            vertex: wgpu::VertexState {
350                module: &shader,
351                entry_point: Some("vs_overlay_tri"),
352                compilation_options: Default::default(),
353                buffers: &[wgpu::VertexBufferLayout {
354                    array_stride: std::mem::size_of::<OverlayTriVertex>() as u64,
355                    step_mode: wgpu::VertexStepMode::Vertex,
356                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
357                }],
358            },
359            fragment: Some(wgpu::FragmentState {
360                module: &shader,
361                entry_point: Some("fs_overlay_tri"),
362                compilation_options: Default::default(),
363                targets: &[color_target(Some(ALPHA_BLEND))],
364            }),
365            primitive: wgpu::PrimitiveState {
366                topology: wgpu::PrimitiveTopology::TriangleList,
367                front_face: wgpu::FrontFace::Ccw,
368                cull_mode: None,
369                ..Default::default()
370            },
371            depth_stencil: Some(wgpu::DepthStencilState {
372                format: DEPTH_FORMAT,
373                depth_write_enabled: Some(true),
374                depth_compare: Some(wgpu::CompareFunction::LessEqual),
375                stencil: Default::default(),
376                bias: Default::default(),
377            }),
378            multisample,
379            multiview_mask: None,
380            cache: None,
381        });
382        // Datum/construction PLANE tris: identical to `overlay-tri` but with
383        // depth-write OFF, so a translucent plane never occludes the gizmo /
384        // dimension geometry drawn after it in the same depth-cleared pass.
385        let overlay_tri_nodepth_pipeline =
386            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
387                label: Some("overlay-tri-nodepth"),
388                layout: Some(&pipeline_layout),
389                vertex: wgpu::VertexState {
390                    module: &shader,
391                    entry_point: Some("vs_overlay_tri"),
392                    compilation_options: Default::default(),
393                    buffers: &[wgpu::VertexBufferLayout {
394                        array_stride: std::mem::size_of::<OverlayTriVertex>() as u64,
395                        step_mode: wgpu::VertexStepMode::Vertex,
396                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
397                    }],
398                },
399                fragment: Some(wgpu::FragmentState {
400                    module: &shader,
401                    entry_point: Some("fs_overlay_tri"),
402                    compilation_options: Default::default(),
403                    targets: &[color_target(Some(ALPHA_BLEND))],
404                }),
405                primitive: wgpu::PrimitiveState {
406                    topology: wgpu::PrimitiveTopology::TriangleList,
407                    front_face: wgpu::FrontFace::Ccw,
408                    cull_mode: None,
409                    ..Default::default()
410                },
411                depth_stencil: Some(wgpu::DepthStencilState {
412                    format: DEPTH_FORMAT,
413                    depth_write_enabled: Some(false),
414                    depth_compare: Some(wgpu::CompareFunction::LessEqual),
415                    stencil: Default::default(),
416                    bias: Default::default(),
417                }),
418                multisample,
419                multiview_mask: None,
420                cache: None,
421            });
422        let overlay_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
423            label: Some("overlay-line"),
424            layout: Some(&pipeline_layout),
425            vertex: wgpu::VertexState {
426                module: &shader,
427                entry_point: Some("vs_overlay_line"),
428                compilation_options: Default::default(),
429                buffers: &[wgpu::VertexBufferLayout {
430                    array_stride: std::mem::size_of::<OverlayLineInstance>() as u64,
431                    step_mode: wgpu::VertexStepMode::Instance,
432                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
433                }],
434            },
435            fragment: Some(wgpu::FragmentState {
436                module: &shader,
437                entry_point: Some("fs_overlay_line"),
438                compilation_options: Default::default(),
439                targets: &[color_target(Some(ALPHA_BLEND))],
440            }),
441            primitive: wgpu::PrimitiveState {
442                topology: wgpu::PrimitiveTopology::TriangleList,
443                front_face: wgpu::FrontFace::Ccw,
444                cull_mode: None,
445                ..Default::default()
446            },
447            depth_stencil: Some(wgpu::DepthStencilState {
448                format: DEPTH_FORMAT,
449                depth_write_enabled: Some(false),
450                depth_compare: Some(wgpu::CompareFunction::LessEqual),
451                stencil: Default::default(),
452                bias: Default::default(),
453            }),
454            multisample,
455            multiview_mask: None,
456            cache: None,
457        });
458
459        // A second globals buffer/bind for the ViewCube pass (its mini-camera).
460        let vc_globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
461            label: Some("viewcube globals"),
462            size: std::mem::size_of::<Globals>() as u64,
463            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
464            mapped_at_creation: false,
465        });
466        let vc_globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
467            label: Some("viewcube globals"),
468            layout: &globals_layout,
469            entries: &[wgpu::BindGroupEntry {
470                binding: 0,
471                resource: vc_globals_buf.as_entire_binding(),
472            }],
473        });
474
475        let styles = GlobalStyles {
476            face_selected: StyleBuf::new(&device, &style_layout, "face-selected"),
477            face_hovered: StyleBuf::new(&device, &style_layout, "face-hovered"),
478            edge_base: StyleBuf::new(&device, &style_layout, "edge-base"),
479            edge_selected: StyleBuf::new(&device, &style_layout, "edge-selected"),
480            edge_hovered: StyleBuf::new(&device, &style_layout, "edge-hovered"),
481            edge_hidden: StyleBuf::new(&device, &style_layout, "edge-hidden"),
482            edge_aux: StyleBuf::new(&device, &style_layout, "edge-aux"),
483            boundary: StyleBuf::new(&device, &style_layout, "boundary"),
484            point_base: StyleBuf::new(&device, &style_layout, "point-base"),
485            point_selected: StyleBuf::new(&device, &style_layout, "point-selected"),
486            point_hovered: StyleBuf::new(&device, &style_layout, "point-hovered"),
487            axis_x: StyleBuf::new(&device, &style_layout, "axis-x"),
488            axis_y: StyleBuf::new(&device, &style_layout, "axis-y"),
489            axis_z: StyleBuf::new(&device, &style_layout, "axis-z"),
490            overlay_line: StyleBuf::new(&device, &style_layout, "overlay-line"),
491            line_art_placed_face: StyleBuf::new(&device, &style_layout, "line-art-placed-face"),
492            line_art_adding_face: StyleBuf::new(&device, &style_layout, "line-art-adding-face"),
493            line_art_placed_edge: StyleBuf::new(&device, &style_layout, "line-art-placed-edge"),
494            line_art_adding_edge: StyleBuf::new(&device, &style_layout, "line-art-adding-edge"),
495            line_art_placed_outline: StyleBuf::new(&device, &style_layout, "line-art-placed-outline"),
496            line_art_adding_outline: StyleBuf::new(&device, &style_layout, "line-art-adding-outline"),
497        };
498
499        let identity_model = IdentityModel::new(&device, &queue, &model_layout);
500        let context_style = StyleBuf::new(&device, &style_layout, "work part context");
501        context_style.write(&queue, super::WORK_PART_CONTEXT_RGBA, [0.0; 4]);
502        let context_selected = StyleBuf::new(&device, &style_layout, "work part context selected");
503        let context_hovered = StyleBuf::new(&device, &style_layout, "work part context hovered");
504
505        Self {
506            context_pipeline,
507            context_style,
508            context_selected,
509            context_hovered,
510            device,
511            queue,
512            format,
513            globals_buf,
514            globals_bind,
515            style_layout,
516            model_layout,
517            identity_model,
518            mesh_pipeline,
519            hull_pipeline,
520            wire_pipeline,
521            edge_visible_pipeline,
522            edge_hidden_pipeline,
523            point_pipeline,
524            overlay_tri_pipeline,
525            overlay_tri_nodepth_pipeline,
526            overlay_line_pipeline,
527            vc_globals_buf,
528            vc_globals_bind,
529            styles,
530            targets: None,
531        }
532    }
533}