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 style: Option<u32>,
145}
146
147struct EdgeRange {
148 first_instance: u32,
149 instance_count: u32,
150}
151
152struct GpuSolid {
153 revision: u64,
154 vertex_buf: wgpu::Buffer,
155 index_buf: wgpu::Buffer,
156 wire_index_buf: wgpu::Buffer,
158 wire_index_count: u32,
159 faces: Vec<FaceRange>,
160 edge_buf: Option<wgpu::Buffer>,
161 edges: Vec<EdgeRange>,
162 edge_instances: u32,
163 point_buf: Option<wgpu::Buffer>,
164 point_count: u32,
165 base_style: StyleBuf,
167 face_styles: Vec<StyleBuf>,
172 boundary: Option<BoundaryBuf>,
175}
176
177struct BoundaryBuf {
178 emphasis_generation: u64,
179 revision: u64,
180 buf: Option<wgpu::Buffer>,
181 count: u32,
182}
183
184#[derive(Default)]
187pub struct GpuScene {
188 solids: HashMap<String, GpuSolid>,
189 order: Vec<String>,
191 settings_generation: u64,
192 axis_buf: Option<wgpu::Buffer>,
193 uploads: u64,
196}
197
198impl GpuScene {
199 pub fn upload_count(&self) -> u64 {
202 self.uploads
203 }
204}
205
206struct CachedTargets {
207 width: u32,
208 height: u32,
209 msaa_view: wgpu::TextureView,
210 depth_view: wgpu::TextureView,
211}
212
213pub struct FrameParams<'a> {
215 pub camera: &'a Camera,
216 pub width: u32,
218 pub height: u32,
219 pub dpr: f32,
221 pub settings: &'a RenderSettings,
222 pub emphasis: &'a Emphasis,
223 pub world_per_pixel: f64,
225 pub overlay: Option<&'a crate::widgets::WidgetOverlay>,
228}
229
230#[cfg(not(target_arch = "wasm32"))]
235pub fn create_headless_device() -> Result<(wgpu::Device, wgpu::Queue, String), String> {
236 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
237 let want = std::env::var("BREP_RENDER_ADAPTER").ok();
238 let adapter = if let Some(want) = &want {
239 let want_lower = want.to_lowercase();
240 pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()))
241 .into_iter()
242 .find(|adapter| adapter.get_info().name.to_lowercase().contains(&want_lower))
243 .ok_or_else(|| format!("no adapter matching BREP_RENDER_ADAPTER={want}"))?
244 } else {
245 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
246 power_preference: wgpu::PowerPreference::HighPerformance,
247 compatible_surface: None,
248 force_fallback_adapter: false,
249 }))
250 .map_err(|error| format!("no wgpu adapter available: {error}"))?
251 };
252 let info = adapter.get_info();
253 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
254 label: Some("brep-render"),
255 ..Default::default()
256 }))
257 .map_err(|error| format!("wgpu device request failed: {error}"))?;
258 Ok((device, queue, format!("{} ({:?})", info.name, info.backend)))
259}
260
261pub struct RenderCore {
263 pub device: wgpu::Device,
264 pub queue: wgpu::Queue,
265 format: wgpu::TextureFormat,
266 globals_buf: wgpu::Buffer,
267 globals_bind: wgpu::BindGroup,
268 style_layout: wgpu::BindGroupLayout,
269 mesh_pipeline: wgpu::RenderPipeline,
270 wire_pipeline: wgpu::RenderPipeline,
271 edge_visible_pipeline: wgpu::RenderPipeline,
272 edge_hidden_pipeline: wgpu::RenderPipeline,
273 point_pipeline: wgpu::RenderPipeline,
274 overlay_tri_pipeline: wgpu::RenderPipeline,
277 overlay_tri_nodepth_pipeline: wgpu::RenderPipeline,
283 overlay_line_pipeline: wgpu::RenderPipeline,
284 vc_globals_buf: wgpu::Buffer,
286 vc_globals_bind: wgpu::BindGroup,
287 styles: GlobalStyles,
288 targets: Option<CachedTargets>,
289}
290
291#[cfg(not(target_arch = "wasm32"))]
293fn encode_png(rgb: &[u8], width: u32, height: u32) -> Result<Vec<u8>, String> {
294 let mut out = Vec::new();
295 {
296 let mut encoder = png::Encoder::new(&mut out, width, height);
297 encoder.set_color(png::ColorType::Rgb);
298 encoder.set_depth(png::BitDepth::Eight);
299 let mut writer = encoder
300 .write_header()
301 .map_err(|error| format!("png header: {error}"))?;
302 writer
303 .write_image_data(rgb)
304 .map_err(|error| format!("png data: {error}"))?;
305 }
306 Ok(out)
307}
308
309mod pipelines;
310mod scene_sync;
311mod frame;
312