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        // Datum/construction PLANE tris: identical to `overlay-tri` but with
278        // depth-write OFF, so a translucent plane never occludes the gizmo /
279        // dimension geometry drawn after it in the same depth-cleared pass.
280        let overlay_tri_nodepth_pipeline =
281            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
282                label: Some("overlay-tri-nodepth"),
283                layout: Some(&pipeline_layout),
284                vertex: wgpu::VertexState {
285                    module: &shader,
286                    entry_point: Some("vs_overlay_tri"),
287                    compilation_options: Default::default(),
288                    buffers: &[wgpu::VertexBufferLayout {
289                        array_stride: std::mem::size_of::<OverlayTriVertex>() as u64,
290                        step_mode: wgpu::VertexStepMode::Vertex,
291                        attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
292                    }],
293                },
294                fragment: Some(wgpu::FragmentState {
295                    module: &shader,
296                    entry_point: Some("fs_overlay_tri"),
297                    compilation_options: Default::default(),
298                    targets: &[color_target(Some(ALPHA_BLEND))],
299                }),
300                primitive: wgpu::PrimitiveState {
301                    topology: wgpu::PrimitiveTopology::TriangleList,
302                    front_face: wgpu::FrontFace::Ccw,
303                    cull_mode: None,
304                    ..Default::default()
305                },
306                depth_stencil: Some(wgpu::DepthStencilState {
307                    format: DEPTH_FORMAT,
308                    depth_write_enabled: Some(false),
309                    depth_compare: Some(wgpu::CompareFunction::LessEqual),
310                    stencil: Default::default(),
311                    bias: Default::default(),
312                }),
313                multisample,
314                multiview_mask: None,
315                cache: None,
316            });
317        let overlay_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
318            label: Some("overlay-line"),
319            layout: Some(&pipeline_layout),
320            vertex: wgpu::VertexState {
321                module: &shader,
322                entry_point: Some("vs_overlay_line"),
323                compilation_options: Default::default(),
324                buffers: &[wgpu::VertexBufferLayout {
325                    array_stride: std::mem::size_of::<OverlayLineInstance>() as u64,
326                    step_mode: wgpu::VertexStepMode::Instance,
327                    attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x4],
328                }],
329            },
330            fragment: Some(wgpu::FragmentState {
331                module: &shader,
332                entry_point: Some("fs_overlay_line"),
333                compilation_options: Default::default(),
334                targets: &[color_target(Some(ALPHA_BLEND))],
335            }),
336            primitive: wgpu::PrimitiveState {
337                topology: wgpu::PrimitiveTopology::TriangleList,
338                front_face: wgpu::FrontFace::Ccw,
339                cull_mode: None,
340                ..Default::default()
341            },
342            depth_stencil: Some(wgpu::DepthStencilState {
343                format: DEPTH_FORMAT,
344                depth_write_enabled: Some(false),
345                depth_compare: Some(wgpu::CompareFunction::LessEqual),
346                stencil: Default::default(),
347                bias: Default::default(),
348            }),
349            multisample,
350            multiview_mask: None,
351            cache: None,
352        });
353
354        // A second globals buffer/bind for the ViewCube pass (its mini-camera).
355        let vc_globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
356            label: Some("viewcube globals"),
357            size: std::mem::size_of::<Globals>() as u64,
358            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
359            mapped_at_creation: false,
360        });
361        let vc_globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
362            label: Some("viewcube globals"),
363            layout: &globals_layout,
364            entries: &[wgpu::BindGroupEntry {
365                binding: 0,
366                resource: vc_globals_buf.as_entire_binding(),
367            }],
368        });
369
370        let styles = GlobalStyles {
371            face_selected: StyleBuf::new(&device, &style_layout, "face-selected"),
372            face_hovered: StyleBuf::new(&device, &style_layout, "face-hovered"),
373            edge_base: StyleBuf::new(&device, &style_layout, "edge-base"),
374            edge_selected: StyleBuf::new(&device, &style_layout, "edge-selected"),
375            edge_hovered: StyleBuf::new(&device, &style_layout, "edge-hovered"),
376            edge_hidden: StyleBuf::new(&device, &style_layout, "edge-hidden"),
377            boundary: StyleBuf::new(&device, &style_layout, "boundary"),
378            point_base: StyleBuf::new(&device, &style_layout, "point-base"),
379            point_selected: StyleBuf::new(&device, &style_layout, "point-selected"),
380            point_hovered: StyleBuf::new(&device, &style_layout, "point-hovered"),
381            axis_x: StyleBuf::new(&device, &style_layout, "axis-x"),
382            axis_y: StyleBuf::new(&device, &style_layout, "axis-y"),
383            axis_z: StyleBuf::new(&device, &style_layout, "axis-z"),
384            overlay_line: StyleBuf::new(&device, &style_layout, "overlay-line"),
385        };
386
387        Self {
388            device,
389            queue,
390            format,
391            globals_buf,
392            globals_bind,
393            style_layout,
394            mesh_pipeline,
395            wire_pipeline,
396            edge_visible_pipeline,
397            edge_hidden_pipeline,
398            point_pipeline,
399            overlay_tri_pipeline,
400            overlay_tri_nodepth_pipeline,
401            overlay_line_pipeline,
402            vc_globals_buf,
403            vc_globals_bind,
404            styles,
405            targets: None,
406        }
407    }
408}