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)]
45#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
46struct StyleParams {
47 color: [f32; 4],
48 params: [f32; 4],
49}
50
51#[repr(C)]
52#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
53struct MeshVertex {
54 position: [f32; 3],
55 normal: [f32; 3],
56}
57
58#[repr(C)]
59#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
60struct EdgeInstance {
61 p0: [f32; 3],
62 p1: [f32; 3],
63}
64
65#[repr(C)]
66#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
67struct PointInstance {
68 center: [f32; 3],
69}
70
71#[repr(C)]
73#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
74struct OverlayLineInstance {
75 p0: [f32; 3],
76 p1: [f32; 3],
77 color: [f32; 4],
78}
79
80#[repr(C)]
82#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
83struct OverlayTriVertex {
84 position: [f32; 3],
85 normal: [f32; 3],
86 color: [f32; 4],
87}
88
89struct StyleBuf {
91 buf: wgpu::Buffer,
92 bind: wgpu::BindGroup,
93}
94
95impl StyleBuf {
96 fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, label: &str) -> Self {
97 let buf = device.create_buffer(&wgpu::BufferDescriptor {
98 label: Some(label),
99 size: std::mem::size_of::<StyleParams>() as u64,
100 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
101 mapped_at_creation: false,
102 });
103 let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
104 label: Some(label),
105 layout,
106 entries: &[wgpu::BindGroupEntry {
107 binding: 0,
108 resource: buf.as_entire_binding(),
109 }],
110 });
111 Self { buf, bind }
112 }
113
114 fn write(&self, queue: &wgpu::Queue, color: Rgba, params: [f32; 4]) {
115 queue.write_buffer(&self.buf, 0, bytemuck::bytes_of(&StyleParams { color, params }));
116 }
117}
118
119struct GlobalStyles {
121 face_selected: StyleBuf,
122 face_hovered: StyleBuf,
123 edge_base: StyleBuf,
124 edge_selected: StyleBuf,
125 edge_hovered: StyleBuf,
126 edge_hidden: StyleBuf,
127 edge_aux: StyleBuf,
129 boundary: StyleBuf,
130 point_base: StyleBuf,
131 point_selected: StyleBuf,
132 point_hovered: StyleBuf,
133 axis_x: StyleBuf,
134 axis_y: StyleBuf,
135 axis_z: StyleBuf,
136 overlay_line: StyleBuf,
138}
139
140struct FaceRange {
141 first_index: u32,
142 index_count: u32,
143 wire_first: u32,
148 wire_count: u32,
149 style: Option<u32>,
153}
154
155#[derive(Default)]
165struct EdgeHasher(u64);
166
167impl std::hash::Hasher for EdgeHasher {
168 fn write(&mut self, bytes: &[u8]) {
169 for &byte in bytes {
170 self.write_u32(u32::from(byte));
171 }
172 }
173
174 fn write_u32(&mut self, value: u32) {
175 self.0 = (self.0 ^ u64::from(value)).wrapping_mul(0x9E37_79B9_7F4A_7C15);
178 }
179
180 fn finish(&self) -> u64 {
181 self.0 ^ (self.0 >> 32)
182 }
183}
184
185type BuildEdgeHasher = std::hash::BuildHasherDefault<EdgeHasher>;
186
187struct EdgeRange {
188 first_instance: u32,
189 instance_count: u32,
190}
191
192struct GpuSolid {
193 revision: u64,
194 vertex_buf: wgpu::Buffer,
195 index_buf: wgpu::Buffer,
196 wire_index_buf: wgpu::Buffer,
198 wire_index_count: u32,
199 faces: Vec<FaceRange>,
200 edge_buf: Option<wgpu::Buffer>,
201 edges: Vec<EdgeRange>,
202 edge_instances: u32,
203 occludable_edge_instances: u32,
208 point_buf: Option<wgpu::Buffer>,
209 point_count: u32,
210 base_style: StyleBuf,
212 face_styles: Vec<StyleBuf>,
217 boundary: Option<BoundaryBuf>,
220}
221
222struct BoundaryBuf {
223 emphasis_generation: u64,
224 revision: u64,
225 buf: Option<wgpu::Buffer>,
226 count: u32,
227}
228
229#[derive(Default)]
232pub struct GpuScene {
233 solids: HashMap<String, GpuSolid>,
234 order: Vec<String>,
236 settings_generation: u64,
237 axis_buf: Option<wgpu::Buffer>,
238 uploads: u64,
241}
242
243impl GpuScene {
244 pub fn upload_count(&self) -> u64 {
247 self.uploads
248 }
249}
250
251struct CachedTargets {
252 width: u32,
253 height: u32,
254 msaa_view: wgpu::TextureView,
255 depth_view: wgpu::TextureView,
256}
257
258pub struct FrameParams<'a> {
260 pub camera: &'a Camera,
261 pub width: u32,
263 pub height: u32,
264 pub dpr: f32,
266 pub settings: &'a RenderSettings,
267 pub emphasis: &'a Emphasis,
268 pub world_per_pixel: f64,
270 pub overlay: Option<&'a crate::widgets::WidgetOverlay>,
273}
274
275#[cfg(not(target_arch = "wasm32"))]
280pub fn create_headless_device() -> Result<(wgpu::Device, wgpu::Queue, String), String> {
281 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
282 let want = std::env::var("BREP_RENDER_ADAPTER").ok();
283 let adapter = if let Some(want) = &want {
284 let want_lower = want.to_lowercase();
285 pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()))
286 .into_iter()
287 .find(|adapter| adapter.get_info().name.to_lowercase().contains(&want_lower))
288 .ok_or_else(|| format!("no adapter matching BREP_RENDER_ADAPTER={want}"))?
289 } else {
290 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
291 power_preference: wgpu::PowerPreference::HighPerformance,
292 compatible_surface: None,
293 force_fallback_adapter: false,
294 }))
295 .map_err(|error| format!("no wgpu adapter available: {error}"))?
296 };
297 let info = adapter.get_info();
298 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
299 label: Some("brep-render"),
300 ..Default::default()
301 }))
302 .map_err(|error| format!("wgpu device request failed: {error}"))?;
303 Ok((device, queue, format!("{} ({:?})", info.name, info.backend)))
304}
305
306pub struct RenderCore {
308 pub device: wgpu::Device,
309 pub queue: wgpu::Queue,
310 format: wgpu::TextureFormat,
311 globals_buf: wgpu::Buffer,
312 globals_bind: wgpu::BindGroup,
313 style_layout: wgpu::BindGroupLayout,
314 mesh_pipeline: wgpu::RenderPipeline,
315 wire_pipeline: wgpu::RenderPipeline,
316 edge_visible_pipeline: wgpu::RenderPipeline,
317 edge_hidden_pipeline: wgpu::RenderPipeline,
318 point_pipeline: wgpu::RenderPipeline,
319 overlay_tri_pipeline: wgpu::RenderPipeline,
322 overlay_tri_nodepth_pipeline: wgpu::RenderPipeline,
328 overlay_line_pipeline: wgpu::RenderPipeline,
329 vc_globals_buf: wgpu::Buffer,
331 vc_globals_bind: wgpu::BindGroup,
332 styles: GlobalStyles,
333 targets: Option<CachedTargets>,
334}
335
336#[cfg(not(target_arch = "wasm32"))]
338fn encode_png(rgb: &[u8], width: u32, height: u32) -> Result<Vec<u8>, String> {
339 let mut out = Vec::new();
340 {
341 let mut encoder = png::Encoder::new(&mut out, width, height);
342 encoder.set_color(png::ColorType::Rgb);
343 encoder.set_depth(png::BitDepth::Eight);
344 let mut writer = encoder
345 .write_header()
346 .map_err(|error| format!("png header: {error}"))?;
347 writer
348 .write_image_data(rgb)
349 .map_err(|error| format!("png data: {error}"))?;
350 }
351 Ok(out)
352}
353
354mod pipelines;
355mod scene_sync;
356mod frame;