1use crate::camera::Camera;
19use crate::color::{solid_color_srgb, srgb_to_linear};
20use crate::scene::{RenderScene, SolidDisplay};
21use crate::style::{Emphasis, EmphasisState, FaceColorMode, RenderSettings, Rgba};
22use std::collections::{HashMap, HashSet};
23use wgpu::util::DeviceExt;
24
25pub const SAMPLES: u32 = 4;
27pub const COLOR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
30const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
31
32const EDGE_NUDGE: f32 = 2e-4;
35
36#[repr(C)]
37#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
38struct Globals {
39 view_proj: [[f32; 4]; 4],
40 viewport: [f32; 4],
41 forward: [f32; 4],
42}
43
44#[repr(C)]
47#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
48struct ModelParams {
49 model: [f32; 16],
50 normal: [f32; 16],
51}
52
53const IDENTITY4: [f32; 16] = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
54
55impl ModelParams {
56 fn of(pose: Option<&[f32; 16]>) -> Self {
57 let Some(m) = pose else { return Self { model: IDENTITY4, normal: IDENTITY4 } };
58 let a = |row: usize, col: usize| m[col * 4 + row] as f64;
61 let cof = |r0: usize, r1: usize, c0: usize, c1: usize| a(r0, c0) * a(r1, c1) - a(r0, c1) * a(r1, c0);
62 let det = a(0, 0) * cof(1, 2, 1, 2) - a(0, 1) * cof(1, 2, 0, 2) + a(0, 2) * cof(1, 2, 0, 1);
63 let mut normal = IDENTITY4;
64 if det.abs() > 1e-30 {
65 let c = [
67 [cof(1, 2, 1, 2), -cof(1, 2, 0, 2), cof(1, 2, 0, 1)],
68 [-cof(0, 2, 1, 2), cof(0, 2, 0, 2), -cof(0, 2, 0, 1)],
69 [cof(0, 1, 1, 2), -cof(0, 1, 0, 2), cof(0, 1, 0, 1)],
70 ];
71 for row in 0..3 {
72 for col in 0..3 {
73 normal[col * 4 + row] = (c[row][col] / det) as f32;
74 }
75 }
76 }
77 Self { model: *m, normal }
78 }
79}
80
81struct ModelArena {
86 buf: wgpu::Buffer,
87 bind: wgpu::BindGroup,
88 capacity: u32,
89 data: Vec<u8>,
90 free: Vec<u32>,
91 next: u32,
92 dirty: Option<(usize, usize)>,
94 fresh: bool,
96}
97
98const MODEL_STRIDE: u64 = 256;
100
101fn model_binding(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, buf: &wgpu::Buffer, label: &str) -> wgpu::BindGroup {
102 device.create_bind_group(&wgpu::BindGroupDescriptor {
103 label: Some(label),
104 layout,
105 entries: &[wgpu::BindGroupEntry {
106 binding: 0,
107 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
108 buffer: buf,
109 offset: 0,
110 size: std::num::NonZeroU64::new(std::mem::size_of::<ModelParams>() as u64),
111 }),
112 }],
113 })
114}
115
116fn model_buffer(device: &wgpu::Device, slots: u32, label: &str) -> wgpu::Buffer {
117 device.create_buffer(&wgpu::BufferDescriptor {
118 label: Some(label),
119 size: slots as u64 * MODEL_STRIDE,
120 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
121 mapped_at_creation: false,
122 })
123}
124
125impl ModelArena {
126 fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, capacity: u32) -> Self {
127 let capacity = capacity.max(16);
128 let buf = model_buffer(device, capacity, "solid poses");
129 let bind = model_binding(device, layout, &buf, "solid poses");
130 Self { buf, bind, capacity, data: vec![0; capacity as usize * MODEL_STRIDE as usize], free: Vec::new(), next: 0, dirty: None, fresh: true }
131 }
132
133 fn alloc(&mut self) -> u32 {
134 self.free.pop().unwrap_or_else(|| {
135 self.next += 1;
136 self.next - 1
137 })
138 }
139
140 fn release(&mut self, slot: u32) {
141 self.free.push(slot);
142 }
143
144 fn set(&mut self, slot: u32, pose: Option<&[f32; 16]>) {
145 let need = (slot as usize + 1) * MODEL_STRIDE as usize;
146 if self.data.len() < need {
147 self.data.resize(need.next_power_of_two(), 0);
148 }
149 let at = slot as usize * MODEL_STRIDE as usize;
150 let params = ModelParams::of(pose);
151 let bytes = bytemuck::bytes_of(¶ms);
152 self.data[at..at + bytes.len()].copy_from_slice(bytes);
153 let (lo, hi) = self.dirty.unwrap_or((at, at + bytes.len()));
154 self.dirty = Some((lo.min(at), hi.max(at + bytes.len())));
155 }
156
157 fn offset(slot: u32) -> u32 {
158 (slot as u64 * MODEL_STRIDE) as u32
159 }
160
161 fn flush(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, layout: &wgpu::BindGroupLayout) {
163 let slots = (self.data.len() / MODEL_STRIDE as usize) as u32;
164 if slots > self.capacity {
165 self.capacity = slots;
166 self.buf = model_buffer(device, slots, "solid poses");
167 self.bind = model_binding(device, layout, &self.buf, "solid poses");
168 self.fresh = true;
169 }
170 if std::mem::take(&mut self.fresh) {
171 queue.write_buffer(&self.buf, 0, &self.data[..self.capacity as usize * MODEL_STRIDE as usize]);
172 } else if let Some((lo, hi)) = self.dirty {
173 let hi = hi.next_multiple_of(4);
174 queue.write_buffer(&self.buf, lo as u64, &self.data[lo..hi]);
175 }
176 self.dirty = None;
177 }
178}
179
180struct IdentityModel {
183 _buf: wgpu::Buffer,
184 bind: wgpu::BindGroup,
185}
186
187impl IdentityModel {
188 fn new(device: &wgpu::Device, queue: &wgpu::Queue, layout: &wgpu::BindGroupLayout) -> Self {
189 let buf = model_buffer(device, 1, "identity pose");
190 queue.write_buffer(&buf, 0, bytemuck::bytes_of(&ModelParams::of(None)));
191 let bind = model_binding(device, layout, &buf, "identity pose");
192 Self { _buf: buf, bind }
193 }
194}
195
196#[repr(C)]
197#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
198struct StyleParams {
199 color: [f32; 4],
200 params: [f32; 4],
201}
202
203#[repr(C)]
204#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
205struct MeshVertex {
206 position: [f32; 3],
207 normal: [f32; 3],
208}
209
210#[repr(C)]
211#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
212struct EdgeInstance {
213 p0: [f32; 3],
214 p1: [f32; 3],
215}
216
217#[repr(C)]
218#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
219struct PointInstance {
220 center: [f32; 3],
221}
222
223#[repr(C)]
225#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
226struct OverlayLineInstance {
227 p0: [f32; 3],
228 p1: [f32; 3],
229 color: [f32; 4],
230}
231
232#[repr(C)]
234#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
235struct OverlayTriVertex {
236 position: [f32; 3],
237 normal: [f32; 3],
238 color: [f32; 4],
239}
240
241struct StyleBuf {
243 buf: wgpu::Buffer,
244 bind: wgpu::BindGroup,
245}
246
247impl StyleBuf {
248 fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, label: &str) -> Self {
249 let buf = device.create_buffer(&wgpu::BufferDescriptor {
250 label: Some(label),
251 size: std::mem::size_of::<StyleParams>() as u64,
252 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
253 mapped_at_creation: false,
254 });
255 let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
256 label: Some(label),
257 layout,
258 entries: &[wgpu::BindGroupEntry {
259 binding: 0,
260 resource: buf.as_entire_binding(),
261 }],
262 });
263 Self { buf, bind }
264 }
265
266 fn write(&self, queue: &wgpu::Queue, color: Rgba, params: [f32; 4]) {
267 queue.write_buffer(&self.buf, 0, bytemuck::bytes_of(&StyleParams { color, params }));
268 }
269}
270
271struct GlobalStyles {
273 face_selected: StyleBuf,
274 face_hovered: StyleBuf,
275 edge_base: StyleBuf,
276 edge_selected: StyleBuf,
277 edge_hovered: StyleBuf,
278 edge_hidden: StyleBuf,
279 edge_aux: StyleBuf,
281 boundary: StyleBuf,
282 point_base: StyleBuf,
283 point_selected: StyleBuf,
284 point_hovered: StyleBuf,
285 axis_x: StyleBuf,
286 axis_y: StyleBuf,
287 axis_z: StyleBuf,
288 overlay_line: StyleBuf,
290}
291
292struct FaceRange {
293 first_index: u32,
294 index_count: u32,
295 wire_first: u32,
300 wire_count: u32,
301 style: Option<u32>,
305}
306
307#[derive(Default)]
317struct EdgeHasher(u64);
318
319impl std::hash::Hasher for EdgeHasher {
320 fn write(&mut self, bytes: &[u8]) {
321 for &byte in bytes {
322 self.write_u32(u32::from(byte));
323 }
324 }
325
326 fn write_u32(&mut self, value: u32) {
327 self.0 = (self.0 ^ u64::from(value)).wrapping_mul(0x9E37_79B9_7F4A_7C15);
330 }
331
332 fn finish(&self) -> u64 {
333 self.0 ^ (self.0 >> 32)
334 }
335}
336
337type BuildEdgeHasher = std::hash::BuildHasherDefault<EdgeHasher>;
338
339struct EdgeRange {
340 first_instance: u32,
341 instance_count: u32,
342}
343
344struct GpuSolid {
345 revision: u64,
346 shape: u64,
350 vertex_count: u32,
351 vertex_buf: wgpu::Buffer,
352 index_buf: wgpu::Buffer,
353 wire_index_buf: wgpu::Buffer,
355 wire_index_count: u32,
356 faces: Vec<FaceRange>,
357 edge_buf: Option<wgpu::Buffer>,
358 edges: Vec<EdgeRange>,
359 edge_instances: u32,
360 occludable_edge_instances: u32,
365 point_buf: Option<wgpu::Buffer>,
366 point_count: u32,
367 base_style: StyleBuf,
369 face_styles: Vec<StyleBuf>,
374 boundary: Option<BoundaryBuf>,
377 model_slot: u32,
380 holds_shape: bool,
384}
385
386struct BoundaryBuf {
387 emphasis_generation: u64,
388 revision: u64,
389 buf: Option<wgpu::Buffer>,
390 count: u32,
391}
392
393pub const WORK_PART_CONTEXT_RGBA: [f32; 4] = [0.45, 0.55, 0.65, 0.22];
397
398#[derive(Default)]
401pub struct GpuScene {
402 solids: HashMap<String, GpuSolid>,
403 order: Vec<String>,
405 settings_generation: u64,
406 axis_buf: Option<wgpu::Buffer>,
407 uploads: u64,
410 reposes: u64,
412 pose_writes: u64,
414 models: Option<ModelArena>,
416}
417
418impl GpuScene {
419 pub fn upload_count(&self) -> u64 {
422 self.uploads
423 }
424
425 pub fn repose_count(&self) -> u64 {
428 self.reposes
429 }
430
431 pub fn pose_write_count(&self) -> u64 {
434 self.pose_writes
435 }
436}
437
438struct CachedTargets {
439 width: u32,
440 height: u32,
441 msaa_view: wgpu::TextureView,
442 depth_view: wgpu::TextureView,
443}
444
445pub struct FrameParams<'a> {
447 pub camera: &'a Camera,
448 pub width: u32,
450 pub height: u32,
451 pub dpr: f32,
453 pub settings: &'a RenderSettings,
454 pub emphasis: &'a Emphasis,
455 pub world_per_pixel: f64,
457 pub overlay: Option<&'a crate::widgets::WidgetOverlay>,
460}
461
462#[cfg(not(target_arch = "wasm32"))]
467pub fn create_headless_device() -> Result<(wgpu::Device, wgpu::Queue, String), String> {
468 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
469 let want = std::env::var("BREP_RENDER_ADAPTER").ok();
470 let adapter = if let Some(want) = &want {
471 let want_lower = want.to_lowercase();
472 pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()))
473 .into_iter()
474 .find(|adapter| adapter.get_info().name.to_lowercase().contains(&want_lower))
475 .ok_or_else(|| format!("no adapter matching BREP_RENDER_ADAPTER={want}"))?
476 } else {
477 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
478 power_preference: wgpu::PowerPreference::HighPerformance,
479 compatible_surface: None,
480 force_fallback_adapter: false,
481 }))
482 .map_err(|error| format!("no wgpu adapter available: {error}"))?
483 };
484 let info = adapter.get_info();
485 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
486 label: Some("brep-render"),
487 ..Default::default()
488 }))
489 .map_err(|error| format!("wgpu device request failed: {error}"))?;
490 Ok((device, queue, format!("{} ({:?})", info.name, info.backend)))
491}
492
493pub struct RenderCore {
495 pub device: wgpu::Device,
496 pub queue: wgpu::Queue,
497 format: wgpu::TextureFormat,
498 globals_buf: wgpu::Buffer,
499 globals_bind: wgpu::BindGroup,
500 style_layout: wgpu::BindGroupLayout,
501 model_layout: wgpu::BindGroupLayout,
504 identity_model: IdentityModel,
507 mesh_pipeline: wgpu::RenderPipeline,
508 context_pipeline: wgpu::RenderPipeline,
509 context_style: StyleBuf,
510 context_selected: StyleBuf,
513 context_hovered: StyleBuf,
514 wire_pipeline: wgpu::RenderPipeline,
515 edge_visible_pipeline: wgpu::RenderPipeline,
516 edge_hidden_pipeline: wgpu::RenderPipeline,
517 point_pipeline: wgpu::RenderPipeline,
518 overlay_tri_pipeline: wgpu::RenderPipeline,
521 overlay_tri_nodepth_pipeline: wgpu::RenderPipeline,
527 overlay_line_pipeline: wgpu::RenderPipeline,
528 vc_globals_buf: wgpu::Buffer,
530 vc_globals_bind: wgpu::BindGroup,
531 styles: GlobalStyles,
532 targets: Option<CachedTargets>,
533}
534
535#[cfg(not(target_arch = "wasm32"))]
537fn encode_png(rgb: &[u8], width: u32, height: u32) -> Result<Vec<u8>, String> {
538 brep_gizmos::raster::encode_png(rgb, width, height, png::ColorType::Rgb)
539}
540
541mod pipelines;
542mod scene_sync;
543mod frame;