Another project
1use wgpu::util::DeviceExt;
2
3use bone_types::ShadingModel;
4
5use crate::RenderTargets;
6use crate::ViewportRegion;
7use crate::gpu::{Gpu, PICK_FORMAT};
8use crate::lower_f32;
9use crate::scene::SolidScene;
10use crate::snapshot::Style;
11
12pub(crate) const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
13
14const LIGHT_DIR: [f32; 4] = [0.302, 0.503, 0.809, 0.0];
15const FILL_DIR: [f32; 4] = [-0.302, -0.503, -0.809, 0.4];
16const AMBIENT: f32 = 0.28;
17
18const SHADING_DEFAULT: u32 = 0;
19const SHADING_PHONG: u32 = 1;
20
21const FACE_SHADED: u32 = 0;
22const FACE_OCCLUDER: u32 = 1;
23
24const fn shading_code(model: ShadingModel) -> u32 {
25 match model {
26 ShadingModel::Phong => SHADING_PHONG,
27 ShadingModel::Flat | ShadingModel::Gouraud => SHADING_DEFAULT,
28 }
29}
30
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32pub(crate) enum FaceFill {
33 Shaded,
34 Occluder,
35}
36
37const fn fill_code(fill: FaceFill) -> u32 {
38 match fill {
39 FaceFill::Shaded => FACE_SHADED,
40 FaceFill::Occluder => FACE_OCCLUDER,
41 }
42}
43
44#[derive(Copy, Clone)]
45pub(crate) struct SolidView {
46 pub clip_from_world: [f32; 16],
47 pub eye_world: [f32; 3],
48 pub shading: ShadingModel,
49 pub fill: FaceFill,
50 pub region: ViewportRegion,
51}
52
53#[repr(C)]
54#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
55struct SolidVertex {
56 position: [f32; 3],
57 normal: [f32; 3],
58 color: [f32; 4],
59 pick: u32,
60}
61
62const VERTEX_STRIDE: u64 = core::mem::size_of::<SolidVertex>() as u64;
63
64#[repr(C, align(16))]
65#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
66struct SolidUniform {
67 clip_from_world: [f32; 16],
68 light_dir: [f32; 4],
69 fill_dir: [f32; 4],
70 base_color: [f32; 4],
71 background: [f32; 4],
72 eye_world: [f32; 4],
73 ambient: f32,
74 shading_model: u32,
75 face_mode: u32,
76 pad: f32,
77}
78
79const UNIFORM_SIZE: u64 = core::mem::size_of::<SolidUniform>() as u64;
80
81const VERTEX_ATTRS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
82 0 => Float32x3,
83 1 => Float32x3,
84 2 => Float32x4,
85 3 => Uint32,
86];
87
88pub(crate) struct SolidPipeline {
89 device: wgpu::Device,
90 queue: wgpu::Queue,
91 pipeline: wgpu::RenderPipeline,
92 uniform_buffer: wgpu::Buffer,
93 bind_group: wgpu::BindGroup,
94}
95
96impl SolidPipeline {
97 #[must_use]
98 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat) -> Self {
99 let device = gpu.device().clone();
100 let queue = gpu.queue().clone();
101 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
102 label: Some("bone-render:solid-shader"),
103 source: wgpu::ShaderSource::Wgsl(include_str!("solid.wgsl").into()),
104 });
105 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
106 label: Some("bone-render:solid-bgl"),
107 entries: &[wgpu::BindGroupLayoutEntry {
108 binding: 0,
109 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
110 ty: wgpu::BindingType::Buffer {
111 ty: wgpu::BufferBindingType::Uniform,
112 has_dynamic_offset: false,
113 min_binding_size: wgpu::BufferSize::new(UNIFORM_SIZE),
114 },
115 count: None,
116 }],
117 });
118 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
119 label: Some("bone-render:solid-layout"),
120 bind_group_layouts: &[Some(&bind_group_layout)],
121 immediate_size: 0,
122 });
123 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
124 label: Some("bone-render:solid-pipeline"),
125 layout: Some(&pipeline_layout),
126 vertex: wgpu::VertexState {
127 module: &shader,
128 entry_point: Some("vs"),
129 compilation_options: wgpu::PipelineCompilationOptions::default(),
130 buffers: &[wgpu::VertexBufferLayout {
131 array_stride: VERTEX_STRIDE,
132 step_mode: wgpu::VertexStepMode::Vertex,
133 attributes: &VERTEX_ATTRS,
134 }],
135 },
136 fragment: Some(wgpu::FragmentState {
137 module: &shader,
138 entry_point: Some("fs"),
139 compilation_options: wgpu::PipelineCompilationOptions::default(),
140 targets: &[
141 Some(wgpu::ColorTargetState {
142 format: color_format,
143 blend: None,
144 write_mask: wgpu::ColorWrites::ALL,
145 }),
146 Some(wgpu::ColorTargetState {
147 format: PICK_FORMAT,
148 blend: None,
149 write_mask: wgpu::ColorWrites::ALL,
150 }),
151 ],
152 }),
153 primitive: wgpu::PrimitiveState {
154 topology: wgpu::PrimitiveTopology::TriangleList,
155 strip_index_format: None,
156 front_face: wgpu::FrontFace::Ccw,
157 cull_mode: Some(wgpu::Face::Back),
158 polygon_mode: wgpu::PolygonMode::Fill,
159 conservative: false,
160 unclipped_depth: false,
161 },
162 depth_stencil: Some(wgpu::DepthStencilState {
163 format: DEPTH_FORMAT,
164 depth_write_enabled: Some(true),
165 depth_compare: Some(wgpu::CompareFunction::Less),
166 stencil: wgpu::StencilState::default(),
167 bias: wgpu::DepthBiasState::default(),
168 }),
169 multisample: wgpu::MultisampleState::default(),
170 multiview_mask: None,
171 cache: None,
172 });
173 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
174 label: Some("bone-render:solid-uniform"),
175 size: UNIFORM_SIZE,
176 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
177 mapped_at_creation: false,
178 });
179 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
180 label: Some("bone-render:solid-bg"),
181 layout: &bind_group_layout,
182 entries: &[wgpu::BindGroupEntry {
183 binding: 0,
184 resource: uniform_buffer.as_entire_binding(),
185 }],
186 });
187 Self {
188 device,
189 queue,
190 pipeline,
191 uniform_buffer,
192 bind_group,
193 }
194 }
195
196 pub fn draw(
197 &self,
198 encoder: &mut wgpu::CommandEncoder,
199 targets: RenderTargets<'_>,
200 depth_view: &wgpu::TextureView,
201 scene: &SolidScene,
202 view: SolidView,
203 style: &Style,
204 ) {
205 let uniform = SolidUniform {
206 clip_from_world: view.clip_from_world,
207 light_dir: LIGHT_DIR,
208 fill_dir: FILL_DIR,
209 base_color: style.solid_base_color().to_array(),
210 background: style.background().to_rgba_array(),
211 eye_world: [view.eye_world[0], view.eye_world[1], view.eye_world[2], 1.0],
212 ambient: AMBIENT,
213 shading_model: shading_code(view.shading),
214 face_mode: fill_code(view.fill),
215 pad: 0.0,
216 };
217 self.queue
218 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
219 let vertices = build_vertices(scene);
220 let indices = build_indices(scene);
221 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
222 label: Some("bone-render:solid-pass"),
223 color_attachments: &crate::solid_clear_color_attachments(targets, style),
224 depth_stencil_attachment: Some(crate::solid_clear_depth_attachment(depth_view)),
225 timestamp_writes: None,
226 occlusion_query_set: None,
227 multiview_mask: None,
228 });
229 crate::apply_viewport_region(&mut pass, view.region);
230 let Ok(count) = u32::try_from(indices.len()) else {
231 panic!("solid index count {} exceeds u32::MAX", indices.len());
232 };
233 if count == 0 {
234 return;
235 }
236 let vertex_buffer = self
237 .device
238 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
239 label: Some("bone-render:solid-vertices"),
240 contents: bytemuck::cast_slice(&vertices),
241 usage: wgpu::BufferUsages::VERTEX,
242 });
243 let index_buffer = self
244 .device
245 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
246 label: Some("bone-render:solid-indices"),
247 contents: bytemuck::cast_slice(&indices),
248 usage: wgpu::BufferUsages::INDEX,
249 });
250 pass.set_pipeline(&self.pipeline);
251 pass.set_bind_group(0, &self.bind_group, &[]);
252 pass.set_vertex_buffer(0, vertex_buffer.slice(..));
253 pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
254 pass.draw_indexed(0..count, 0, 0..1);
255 }
256}
257
258impl core::fmt::Debug for SolidPipeline {
259 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
260 f.debug_struct("SolidPipeline").finish_non_exhaustive()
261 }
262}
263
264fn build_vertices(scene: &SolidScene) -> Vec<SolidVertex> {
265 scene
266 .positions()
267 .iter()
268 .zip(scene.normals())
269 .zip(scene.colors())
270 .zip(scene.pick_ids())
271 .map(|(((point, normal), color), pick)| {
272 let (px, py, pz) = point.coords_mm();
273 let (nx, ny, nz) = normal.components();
274 SolidVertex {
275 position: [lower_f32(px), lower_f32(py), lower_f32(pz)],
276 normal: [lower_f32(nx), lower_f32(ny), lower_f32(nz)],
277 color: color.to_array(),
278 pick: pick.raw(),
279 }
280 })
281 .collect()
282}
283
284fn build_indices(scene: &SolidScene) -> Vec<u32> {
285 scene
286 .triangles()
287 .iter()
288 .flat_map(|tri| tri.iter().copied())
289 .collect()
290}