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;
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 boundary: StyleBuf,
128 point_base: StyleBuf,
129 point_selected: StyleBuf,
130 point_hovered: StyleBuf,
131 axis_x: StyleBuf,
132 axis_y: StyleBuf,
133 axis_z: StyleBuf,
134 overlay_line: StyleBuf,
136}
137
138struct FaceRange {
139 first_index: u32,
140 index_count: u32,
141}
142
143struct EdgeRange {
144 first_instance: u32,
145 instance_count: u32,
146}
147
148struct GpuSolid {
149 revision: u64,
150 vertex_buf: wgpu::Buffer,
151 index_buf: wgpu::Buffer,
152 wire_index_buf: wgpu::Buffer,
154 wire_index_count: u32,
155 faces: Vec<FaceRange>,
156 edge_buf: Option<wgpu::Buffer>,
157 edges: Vec<EdgeRange>,
158 edge_instances: u32,
159 point_buf: Option<wgpu::Buffer>,
160 point_count: u32,
161 base_style: StyleBuf,
163 boundary: Option<BoundaryBuf>,
166}
167
168struct BoundaryBuf {
169 emphasis_generation: u64,
170 revision: u64,
171 buf: Option<wgpu::Buffer>,
172 count: u32,
173}
174
175#[derive(Default)]
178pub struct GpuScene {
179 solids: HashMap<String, GpuSolid>,
180 order: Vec<String>,
182 settings_generation: u64,
183 axis_buf: Option<wgpu::Buffer>,
184 uploads: u64,
187}
188
189impl GpuScene {
190 pub fn upload_count(&self) -> u64 {
193 self.uploads
194 }
195}
196
197struct CachedTargets {
198 width: u32,
199 height: u32,
200 msaa_view: wgpu::TextureView,
201 depth_view: wgpu::TextureView,
202}
203
204pub struct FrameParams<'a> {
206 pub camera: &'a Camera,
207 pub width: u32,
209 pub height: u32,
210 pub dpr: f32,
212 pub settings: &'a RenderSettings,
213 pub emphasis: &'a Emphasis,
214 pub world_per_pixel: f64,
216 pub overlay: Option<&'a crate::widgets::WidgetOverlay>,
219}
220
221#[cfg(not(target_arch = "wasm32"))]
226pub fn create_headless_device() -> Result<(wgpu::Device, wgpu::Queue, String), String> {
227 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
228 let want = std::env::var("BREP_RENDER_ADAPTER").ok();
229 let adapter = if let Some(want) = &want {
230 let want_lower = want.to_lowercase();
231 pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()))
232 .into_iter()
233 .find(|adapter| adapter.get_info().name.to_lowercase().contains(&want_lower))
234 .ok_or_else(|| format!("no adapter matching BREP_RENDER_ADAPTER={want}"))?
235 } else {
236 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
237 power_preference: wgpu::PowerPreference::HighPerformance,
238 compatible_surface: None,
239 force_fallback_adapter: false,
240 }))
241 .map_err(|error| format!("no wgpu adapter available: {error}"))?
242 };
243 let info = adapter.get_info();
244 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
245 label: Some("brep-render"),
246 ..Default::default()
247 }))
248 .map_err(|error| format!("wgpu device request failed: {error}"))?;
249 Ok((device, queue, format!("{} ({:?})", info.name, info.backend)))
250}
251
252pub struct RenderCore {
254 pub device: wgpu::Device,
255 pub queue: wgpu::Queue,
256 format: wgpu::TextureFormat,
257 globals_buf: wgpu::Buffer,
258 globals_bind: wgpu::BindGroup,
259 style_layout: wgpu::BindGroupLayout,
260 mesh_pipeline: wgpu::RenderPipeline,
261 wire_pipeline: wgpu::RenderPipeline,
262 edge_visible_pipeline: wgpu::RenderPipeline,
263 edge_hidden_pipeline: wgpu::RenderPipeline,
264 point_pipeline: wgpu::RenderPipeline,
265 overlay_tri_pipeline: wgpu::RenderPipeline,
268 overlay_tri_nodepth_pipeline: wgpu::RenderPipeline,
274 overlay_line_pipeline: wgpu::RenderPipeline,
275 vc_globals_buf: wgpu::Buffer,
277 vc_globals_bind: wgpu::BindGroup,
278 styles: GlobalStyles,
279 targets: Option<CachedTargets>,
280}
281
282#[cfg(not(target_arch = "wasm32"))]
284fn encode_png(rgb: &[u8], width: u32, height: u32) -> Result<Vec<u8>, String> {
285 let mut out = Vec::new();
286 {
287 let mut encoder = png::Encoder::new(&mut out, width, height);
288 encoder.set_color(png::ColorType::Rgb);
289 encoder.set_depth(png::BitDepth::Eight);
290 let mut writer = encoder
291 .write_header()
292 .map_err(|error| format!("png header: {error}"))?;
293 writer
294 .write_image_data(rgb)
295 .map_err(|error| format!("png data: {error}"))?;
296 }
297 Ok(out)
298}
299
300mod pipelines;
301mod scene_sync;
302mod frame;
303#[cfg(all(test, not(target_arch = "wasm32")))]
304mod tests;