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        let context_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
135            label: Some("work part context"),
136            layout: Some(&solid_layout),
137            vertex: wgpu::VertexState {
138                module: &shader,
139                entry_point: Some("vs_mesh"),
140                compilation_options: Default::default(),
141                buffers: &[wgpu::VertexBufferLayout {
142                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
143                    step_mode: wgpu::VertexStepMode::Vertex,
144                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
145                }],
146            },
147            fragment: Some(wgpu::FragmentState {
148                module: &shader,
149                entry_point: Some("fs_context"),
150                compilation_options: Default::default(),
151                targets: &[color_target(Some(ALPHA_BLEND))],
152            }),
153            primitive: wgpu::PrimitiveState {
154                topology: wgpu::PrimitiveTopology::TriangleList,
155                front_face: wgpu::FrontFace::Ccw,
156                // Double-sided, like the retired materials during picking — and
157                // the +plane-z winding convention (R13) is never "corrected".
158                cull_mode: None,
159                ..Default::default()
160            },
161            depth_stencil: Some(wgpu::DepthStencilState {
162                format: DEPTH_FORMAT,
163                depth_write_enabled: Some(false),
164                depth_compare: Some(wgpu::CompareFunction::Less),
165                stencil: Default::default(),
166                // Push faces slightly back so the edge overlay wins its
167                // z-fight against the faces it outlines.
168                bias: wgpu::DepthBiasState {
169                    constant: 4,
170                    slope_scale: 2.0,
171                    clamp: 0.0,
172                },
173            }),
174            multisample,
175            multiview_mask: None,
176            cache: None,
177        });
178
179        // Wireframe: same vertex layout as the mesh, but drawn as a line list
180        // (each triangle's 3 edges) with a flat fragment. `PolygonMode::Line`
181        // is unavailable on the WebGL/downlevel path, so we expand to lines.
182        let wire_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
183            label: Some("wireframe"),
184            layout: Some(&solid_layout),
185            vertex: wgpu::VertexState {
186                module: &shader,
187                entry_point: Some("vs_mesh"),
188                compilation_options: Default::default(),
189                buffers: &[wgpu::VertexBufferLayout {
190                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
191                    step_mode: wgpu::VertexStepMode::Vertex,
192                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
193                }],
194            },
195            fragment: Some(wgpu::FragmentState {
196                module: &shader,
197                entry_point: Some("fs_wire"),
198                compilation_options: Default::default(),
199                targets: &[color_target(None)],
200            }),
201            primitive: wgpu::PrimitiveState {
202                topology: wgpu::PrimitiveTopology::LineList,
203                front_face: wgpu::FrontFace::Ccw,
204                cull_mode: None,
205                ..Default::default()
206            },
207            depth_stencil: Some(wgpu::DepthStencilState {
208                format: DEPTH_FORMAT,
209                depth_write_enabled: Some(true),
210                depth_compare: Some(wgpu::CompareFunction::Less),
211                stencil: Default::default(),
212                bias: Default::default(),
213            }),
214            multisample,
215            multiview_mask: None,
216            cache: None,
217        });
218
219        let edge_pipeline = |label: &str, depth_compare: wgpu::CompareFunction| {
220            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
221                label: Some(label),
222                layout: Some(&solid_layout),
223                vertex: wgpu::VertexState {
224                    module: &shader,
225                    entry_point: Some("vs_edge"),
226                    compilation_options: Default::default(),
227                    buffers: &[wgpu::VertexBufferLayout {
228                        array_stride: std::mem::size_of::<EdgeInstance>() as u64,
229                        step_mode: wgpu::VertexStepMode::Instance,
230                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
231                    }],
232                },
233                fragment: Some(wgpu::FragmentState {
234                    module: &shader,
235                    entry_point: Some("fs_edge"),
236                    compilation_options: Default::default(),
237                    targets: &[color_target(Some(ALPHA_BLEND))],
238                }),
239                primitive: wgpu::PrimitiveState {
240                    topology: wgpu::PrimitiveTopology::TriangleList,
241                    front_face: wgpu::FrontFace::Ccw,
242                    cull_mode: None,
243                    ..Default::default()
244                },
245                depth_stencil: Some(wgpu::DepthStencilState {
246                    format: DEPTH_FORMAT,
247                    // Edges never write depth (they must not occlude faces —
248                    // the CADmaterials depthWrite:false behavior).
249                    depth_write_enabled: Some(false),
250                    depth_compare: Some(depth_compare),
251                    stencil: Default::default(),
252                    bias: Default::default(),
253                }),
254                multisample,
255                multiview_mask: None,
256                cache: None,
257            })
258        };
259        // Visible edges pass the depth test; the hidden pass draws only where
260        // the edge is BEHIND geometry (dimmed, not dropped — R17).
261        let edge_visible_pipeline = edge_pipeline("edges", wgpu::CompareFunction::LessEqual);
262        let edge_hidden_pipeline = edge_pipeline("edges-hidden", wgpu::CompareFunction::Greater);
263
264        let point_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
265            label: Some("points"),
266            layout: Some(&solid_layout),
267            vertex: wgpu::VertexState {
268                module: &shader,
269                entry_point: Some("vs_point"),
270                compilation_options: Default::default(),
271                buffers: &[wgpu::VertexBufferLayout {
272                    array_stride: std::mem::size_of::<PointInstance>() as u64,
273                    step_mode: wgpu::VertexStepMode::Instance,
274                    attributes: &wgpu::vertex_attr_array![0 => Float32x3],
275                }],
276            },
277            fragment: Some(wgpu::FragmentState {
278                module: &shader,
279                entry_point: Some("fs_point"),
280                compilation_options: Default::default(),
281                targets: &[color_target(Some(ALPHA_BLEND))],
282            }),
283            primitive: wgpu::PrimitiveState {
284                topology: wgpu::PrimitiveTopology::TriangleList,
285                front_face: wgpu::FrontFace::Ccw,
286                cull_mode: None,
287                ..Default::default()
288            },
289            depth_stencil: Some(wgpu::DepthStencilState {
290                format: DEPTH_FORMAT,
291                depth_write_enabled: Some(false),
292                depth_compare: Some(wgpu::CompareFunction::LessEqual),
293                stencil: Default::default(),
294                bias: Default::default(),
295            }),
296            multisample,
297            multiview_mask: None,
298            cache: None,
299        });
300
301        // Overlay-widget pipelines. Both reuse the globals + style
302        // pipeline layout; tris carry per-vertex color+normal, lines carry
303        // per-instance p0/p1/color. They draw in a depth-cleared pass over the
304        // solids so widgets read on top.
305        let overlay_tri_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
306            label: Some("overlay-tri"),
307            layout: Some(&pipeline_layout),
308            vertex: wgpu::VertexState {
309                module: &shader,
310                entry_point: Some("vs_overlay_tri"),
311                compilation_options: Default::default(),
312                buffers: &[wgpu::VertexBufferLayout {
313                    array_stride: std::mem::size_of::<OverlayTriVertex>() as u64,
314                    step_mode: wgpu::VertexStepMode::Vertex,
315                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
316                }],
317            },
318            fragment: Some(wgpu::FragmentState {
319                module: &shader,
320                entry_point: Some("fs_overlay_tri"),
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(true),
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        // Datum/construction PLANE tris: identical to `overlay-tri` but with
342        // depth-write OFF, so a translucent plane never occludes the gizmo /
343        // dimension geometry drawn after it in the same depth-cleared pass.
344        let overlay_tri_nodepth_pipeline =
345            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
346                label: Some("overlay-tri-nodepth"),
347                layout: Some(&pipeline_layout),
348                vertex: wgpu::VertexState {
349                    module: &shader,
350                    entry_point: Some("vs_overlay_tri"),
351                    compilation_options: Default::default(),
352                    buffers: &[wgpu::VertexBufferLayout {
353                        array_stride: std::mem::size_of::<OverlayTriVertex>() as u64,
354                        step_mode: wgpu::VertexStepMode::Vertex,
355                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
356                    }],
357                },
358                fragment: Some(wgpu::FragmentState {
359                    module: &shader,
360                    entry_point: Some("fs_overlay_tri"),
361                    compilation_options: Default::default(),
362                    targets: &[color_target(Some(ALPHA_BLEND))],
363                }),
364                primitive: wgpu::PrimitiveState {
365                    topology: wgpu::PrimitiveTopology::TriangleList,
366                    front_face: wgpu::FrontFace::Ccw,
367                    cull_mode: None,
368                    ..Default::default()
369                },
370                depth_stencil: Some(wgpu::DepthStencilState {
371                    format: DEPTH_FORMAT,
372                    depth_write_enabled: Some(false),
373                    depth_compare: Some(wgpu::CompareFunction::LessEqual),
374                    stencil: Default::default(),
375                    bias: Default::default(),
376                }),
377                multisample,
378                multiview_mask: None,
379                cache: None,
380            });
381        let overlay_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
382            label: Some("overlay-line"),
383            layout: Some(&pipeline_layout),
384            vertex: wgpu::VertexState {
385                module: &shader,
386                entry_point: Some("vs_overlay_line"),
387                compilation_options: Default::default(),
388                buffers: &[wgpu::VertexBufferLayout {
389                    array_stride: std::mem::size_of::<OverlayLineInstance>() as u64,
390                    step_mode: wgpu::VertexStepMode::Instance,
391                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
392                }],
393            },
394            fragment: Some(wgpu::FragmentState {
395                module: &shader,
396                entry_point: Some("fs_overlay_line"),
397                compilation_options: Default::default(),
398                targets: &[color_target(Some(ALPHA_BLEND))],
399            }),
400            primitive: wgpu::PrimitiveState {
401                topology: wgpu::PrimitiveTopology::TriangleList,
402                front_face: wgpu::FrontFace::Ccw,
403                cull_mode: None,
404                ..Default::default()
405            },
406            depth_stencil: Some(wgpu::DepthStencilState {
407                format: DEPTH_FORMAT,
408                depth_write_enabled: Some(false),
409                depth_compare: Some(wgpu::CompareFunction::LessEqual),
410                stencil: Default::default(),
411                bias: Default::default(),
412            }),
413            multisample,
414            multiview_mask: None,
415            cache: None,
416        });
417
418        // A second globals buffer/bind for the ViewCube pass (its mini-camera).
419        let vc_globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
420            label: Some("viewcube globals"),
421            size: std::mem::size_of::<Globals>() as u64,
422            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
423            mapped_at_creation: false,
424        });
425        let vc_globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
426            label: Some("viewcube globals"),
427            layout: &globals_layout,
428            entries: &[wgpu::BindGroupEntry {
429                binding: 0,
430                resource: vc_globals_buf.as_entire_binding(),
431            }],
432        });
433
434        let styles = GlobalStyles {
435            face_selected: StyleBuf::new(&device, &style_layout, "face-selected"),
436            face_hovered: StyleBuf::new(&device, &style_layout, "face-hovered"),
437            edge_base: StyleBuf::new(&device, &style_layout, "edge-base"),
438            edge_selected: StyleBuf::new(&device, &style_layout, "edge-selected"),
439            edge_hovered: StyleBuf::new(&device, &style_layout, "edge-hovered"),
440            edge_hidden: StyleBuf::new(&device, &style_layout, "edge-hidden"),
441            edge_aux: StyleBuf::new(&device, &style_layout, "edge-aux"),
442            boundary: StyleBuf::new(&device, &style_layout, "boundary"),
443            point_base: StyleBuf::new(&device, &style_layout, "point-base"),
444            point_selected: StyleBuf::new(&device, &style_layout, "point-selected"),
445            point_hovered: StyleBuf::new(&device, &style_layout, "point-hovered"),
446            axis_x: StyleBuf::new(&device, &style_layout, "axis-x"),
447            axis_y: StyleBuf::new(&device, &style_layout, "axis-y"),
448            axis_z: StyleBuf::new(&device, &style_layout, "axis-z"),
449            overlay_line: StyleBuf::new(&device, &style_layout, "overlay-line"),
450        };
451
452        let identity_model = IdentityModel::new(&device, &queue, &model_layout);
453        let context_style = StyleBuf::new(&device, &style_layout, "work part context");
454        context_style.write(&queue, super::WORK_PART_CONTEXT_RGBA, [0.0; 4]);
455        let context_selected = StyleBuf::new(&device, &style_layout, "work part context selected");
456        let context_hovered = StyleBuf::new(&device, &style_layout, "work part context hovered");
457
458        Self {
459            context_pipeline,
460            context_style,
461            context_selected,
462            context_hovered,
463            device,
464            queue,
465            format,
466            globals_buf,
467            globals_bind,
468            style_layout,
469            model_layout,
470            identity_model,
471            mesh_pipeline,
472            wire_pipeline,
473            edge_visible_pipeline,
474            edge_hidden_pipeline,
475            point_pipeline,
476            overlay_tri_pipeline,
477            overlay_tri_nodepth_pipeline,
478            overlay_line_pipeline,
479            vc_globals_buf,
480            vc_globals_bind,
481            styles,
482            targets: None,
483        }
484    }
485}