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 globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
37            label: Some("globals"),
38            size: std::mem::size_of::<Globals>() as u64,
39            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
40            mapped_at_creation: false,
41        });
42        let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
43            label: Some("globals"),
44            layout: &globals_layout,
45            entries: &[wgpu::BindGroupEntry {
46                binding: 0,
47                resource: globals_buf.as_entire_binding(),
48            }],
49        });
50
51        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
52            label: Some("brep-render"),
53            bind_group_layouts: &[Some(&globals_layout), Some(&style_layout)],
54            immediate_size: 0,
55        });
56
57        const ALPHA_BLEND: wgpu::BlendState = wgpu::BlendState::ALPHA_BLENDING;
58        let color_target = |blend: Option<wgpu::BlendState>| {
59            Some(wgpu::ColorTargetState {
60                format,
61                blend,
62                write_mask: wgpu::ColorWrites::ALL,
63            })
64        };
65        let multisample = wgpu::MultisampleState {
66            count: SAMPLES,
67            ..Default::default()
68        };
69
70        let mesh_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
71            label: Some("mesh"),
72            layout: Some(&pipeline_layout),
73            vertex: wgpu::VertexState {
74                module: &shader,
75                entry_point: Some("vs_mesh"),
76                compilation_options: Default::default(),
77                buffers: &[wgpu::VertexBufferLayout {
78                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
79                    step_mode: wgpu::VertexStepMode::Vertex,
80                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
81                }],
82            },
83            fragment: Some(wgpu::FragmentState {
84                module: &shader,
85                entry_point: Some("fs_mesh"),
86                compilation_options: Default::default(),
87                targets: &[color_target(None)],
88            }),
89            primitive: wgpu::PrimitiveState {
90                topology: wgpu::PrimitiveTopology::TriangleList,
91                front_face: wgpu::FrontFace::Ccw,
92                // Double-sided, like the retired materials during picking — and
93                // the +plane-z winding convention (R13) is never "corrected".
94                cull_mode: None,
95                ..Default::default()
96            },
97            depth_stencil: Some(wgpu::DepthStencilState {
98                format: DEPTH_FORMAT,
99                depth_write_enabled: Some(true),
100                depth_compare: Some(wgpu::CompareFunction::Less),
101                stencil: Default::default(),
102                // Push faces slightly back so the edge overlay wins its
103                // z-fight against the faces it outlines.
104                bias: wgpu::DepthBiasState {
105                    constant: 4,
106                    slope_scale: 2.0,
107                    clamp: 0.0,
108                },
109            }),
110            multisample,
111            multiview_mask: None,
112            cache: None,
113        });
114
115        // Wireframe: same vertex layout as the mesh, but drawn as a line list
116        // (each triangle's 3 edges) with a flat fragment. `PolygonMode::Line`
117        // is unavailable on the WebGL/downlevel path, so we expand to lines.
118        let wire_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
119            label: Some("wireframe"),
120            layout: Some(&pipeline_layout),
121            vertex: wgpu::VertexState {
122                module: &shader,
123                entry_point: Some("vs_mesh"),
124                compilation_options: Default::default(),
125                buffers: &[wgpu::VertexBufferLayout {
126                    array_stride: std::mem::size_of::<MeshVertex>() as u64,
127                    step_mode: wgpu::VertexStepMode::Vertex,
128                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
129                }],
130            },
131            fragment: Some(wgpu::FragmentState {
132                module: &shader,
133                entry_point: Some("fs_wire"),
134                compilation_options: Default::default(),
135                targets: &[color_target(None)],
136            }),
137            primitive: wgpu::PrimitiveState {
138                topology: wgpu::PrimitiveTopology::LineList,
139                front_face: wgpu::FrontFace::Ccw,
140                cull_mode: None,
141                ..Default::default()
142            },
143            depth_stencil: Some(wgpu::DepthStencilState {
144                format: DEPTH_FORMAT,
145                depth_write_enabled: Some(true),
146                depth_compare: Some(wgpu::CompareFunction::Less),
147                stencil: Default::default(),
148                bias: Default::default(),
149            }),
150            multisample,
151            multiview_mask: None,
152            cache: None,
153        });
154
155        let edge_pipeline = |label: &str, depth_compare: wgpu::CompareFunction| {
156            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
157                label: Some(label),
158                layout: Some(&pipeline_layout),
159                vertex: wgpu::VertexState {
160                    module: &shader,
161                    entry_point: Some("vs_edge"),
162                    compilation_options: Default::default(),
163                    buffers: &[wgpu::VertexBufferLayout {
164                        array_stride: std::mem::size_of::<EdgeInstance>() as u64,
165                        step_mode: wgpu::VertexStepMode::Instance,
166                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
167                    }],
168                },
169                fragment: Some(wgpu::FragmentState {
170                    module: &shader,
171                    entry_point: Some("fs_edge"),
172                    compilation_options: Default::default(),
173                    targets: &[color_target(Some(ALPHA_BLEND))],
174                }),
175                primitive: wgpu::PrimitiveState {
176                    topology: wgpu::PrimitiveTopology::TriangleList,
177                    front_face: wgpu::FrontFace::Ccw,
178                    cull_mode: None,
179                    ..Default::default()
180                },
181                depth_stencil: Some(wgpu::DepthStencilState {
182                    format: DEPTH_FORMAT,
183                    // Edges never write depth (they must not occlude faces —
184                    // the CADmaterials depthWrite:false behavior).
185                    depth_write_enabled: Some(false),
186                    depth_compare: Some(depth_compare),
187                    stencil: Default::default(),
188                    bias: Default::default(),
189                }),
190                multisample,
191                multiview_mask: None,
192                cache: None,
193            })
194        };
195        // Visible edges pass the depth test; the hidden pass draws only where
196        // the edge is BEHIND geometry (dimmed, not dropped — R17).
197        let edge_visible_pipeline = edge_pipeline("edges", wgpu::CompareFunction::LessEqual);
198        let edge_hidden_pipeline = edge_pipeline("edges-hidden", wgpu::CompareFunction::Greater);
199
200        let point_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
201            label: Some("points"),
202            layout: Some(&pipeline_layout),
203            vertex: wgpu::VertexState {
204                module: &shader,
205                entry_point: Some("vs_point"),
206                compilation_options: Default::default(),
207                buffers: &[wgpu::VertexBufferLayout {
208                    array_stride: std::mem::size_of::<PointInstance>() as u64,
209                    step_mode: wgpu::VertexStepMode::Instance,
210                    attributes: &wgpu::vertex_attr_array![0 => Float32x3],
211                }],
212            },
213            fragment: Some(wgpu::FragmentState {
214                module: &shader,
215                entry_point: Some("fs_point"),
216                compilation_options: Default::default(),
217                targets: &[color_target(Some(ALPHA_BLEND))],
218            }),
219            primitive: wgpu::PrimitiveState {
220                topology: wgpu::PrimitiveTopology::TriangleList,
221                front_face: wgpu::FrontFace::Ccw,
222                cull_mode: None,
223                ..Default::default()
224            },
225            depth_stencil: Some(wgpu::DepthStencilState {
226                format: DEPTH_FORMAT,
227                depth_write_enabled: Some(false),
228                depth_compare: Some(wgpu::CompareFunction::LessEqual),
229                stencil: Default::default(),
230                bias: Default::default(),
231            }),
232            multisample,
233            multiview_mask: None,
234            cache: None,
235        });
236
237        // Overlay-widget pipelines. Both reuse the globals + style
238        // pipeline layout; tris carry per-vertex color+normal, lines carry
239        // per-instance p0/p1/color. They draw in a depth-cleared pass over the
240        // solids so widgets read on top.
241        let overlay_tri_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
242            label: Some("overlay-tri"),
243            layout: Some(&pipeline_layout),
244            vertex: wgpu::VertexState {
245                module: &shader,
246                entry_point: Some("vs_overlay_tri"),
247                compilation_options: Default::default(),
248                buffers: &[wgpu::VertexBufferLayout {
249                    array_stride: std::mem::size_of::<OverlayTriVertex>() as u64,
250                    step_mode: wgpu::VertexStepMode::Vertex,
251                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
252                }],
253            },
254            fragment: Some(wgpu::FragmentState {
255                module: &shader,
256                entry_point: Some("fs_overlay_tri"),
257                compilation_options: Default::default(),
258                targets: &[color_target(Some(ALPHA_BLEND))],
259            }),
260            primitive: wgpu::PrimitiveState {
261                topology: wgpu::PrimitiveTopology::TriangleList,
262                front_face: wgpu::FrontFace::Ccw,
263                cull_mode: None,
264                ..Default::default()
265            },
266            depth_stencil: Some(wgpu::DepthStencilState {
267                format: DEPTH_FORMAT,
268                depth_write_enabled: Some(true),
269                depth_compare: Some(wgpu::CompareFunction::LessEqual),
270                stencil: Default::default(),
271                bias: Default::default(),
272            }),
273            multisample,
274            multiview_mask: None,
275            cache: None,
276        });
277        let overlay_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
278            label: Some("overlay-line"),
279            layout: Some(&pipeline_layout),
280            vertex: wgpu::VertexState {
281                module: &shader,
282                entry_point: Some("vs_overlay_line"),
283                compilation_options: Default::default(),
284                buffers: &[wgpu::VertexBufferLayout {
285                    array_stride: std::mem::size_of::<OverlayLineInstance>() as u64,
286                    step_mode: wgpu::VertexStepMode::Instance,
287                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
288                }],
289            },
290            fragment: Some(wgpu::FragmentState {
291                module: &shader,
292                entry_point: Some("fs_overlay_line"),
293                compilation_options: Default::default(),
294                targets: &[color_target(Some(ALPHA_BLEND))],
295            }),
296            primitive: wgpu::PrimitiveState {
297                topology: wgpu::PrimitiveTopology::TriangleList,
298                front_face: wgpu::FrontFace::Ccw,
299                cull_mode: None,
300                ..Default::default()
301            },
302            depth_stencil: Some(wgpu::DepthStencilState {
303                format: DEPTH_FORMAT,
304                depth_write_enabled: Some(false),
305                depth_compare: Some(wgpu::CompareFunction::LessEqual),
306                stencil: Default::default(),
307                bias: Default::default(),
308            }),
309            multisample,
310            multiview_mask: None,
311            cache: None,
312        });
313
314        // A second globals buffer/bind for the ViewCube pass (its mini-camera).
315        let vc_globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
316            label: Some("viewcube globals"),
317            size: std::mem::size_of::<Globals>() as u64,
318            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
319            mapped_at_creation: false,
320        });
321        let vc_globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
322            label: Some("viewcube globals"),
323            layout: &globals_layout,
324            entries: &[wgpu::BindGroupEntry {
325                binding: 0,
326                resource: vc_globals_buf.as_entire_binding(),
327            }],
328        });
329
330        let styles = GlobalStyles {
331            face_selected: StyleBuf::new(&device, &style_layout, "face-selected"),
332            face_hovered: StyleBuf::new(&device, &style_layout, "face-hovered"),
333            edge_base: StyleBuf::new(&device, &style_layout, "edge-base"),
334            edge_selected: StyleBuf::new(&device, &style_layout, "edge-selected"),
335            edge_hovered: StyleBuf::new(&device, &style_layout, "edge-hovered"),
336            edge_hidden: StyleBuf::new(&device, &style_layout, "edge-hidden"),
337            boundary: StyleBuf::new(&device, &style_layout, "boundary"),
338            point_base: StyleBuf::new(&device, &style_layout, "point-base"),
339            point_selected: StyleBuf::new(&device, &style_layout, "point-selected"),
340            point_hovered: StyleBuf::new(&device, &style_layout, "point-hovered"),
341            axis_x: StyleBuf::new(&device, &style_layout, "axis-x"),
342            axis_y: StyleBuf::new(&device, &style_layout, "axis-y"),
343            axis_z: StyleBuf::new(&device, &style_layout, "axis-z"),
344            overlay_line: StyleBuf::new(&device, &style_layout, "overlay-line"),
345        };
346
347        Self {
348            device,
349            queue,
350            format,
351            globals_buf,
352            globals_bind,
353            style_layout,
354            mesh_pipeline,
355            wire_pipeline,
356            edge_visible_pipeline,
357            edge_hidden_pipeline,
358            point_pipeline,
359            overlay_tri_pipeline,
360            overlay_line_pipeline,
361            vc_globals_buf,
362            vc_globals_bind,
363            styles,
364            targets: None,
365        }
366    }
367}