use crate::camera::Camera;
use crate::color::{solid_color_srgb, srgb_to_linear};
use crate::scene::{RenderScene, SolidDisplay};
use crate::style::{Emphasis, EmphasisState, FaceColorMode, RenderSettings, Rgba};
use std::collections::HashMap;
use wgpu::util::DeviceExt;
pub const SAMPLES: u32 = 4;
pub const COLOR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
const EDGE_NUDGE: f32 = 2e-4;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct Globals {
view_proj: [[f32; 4]; 4],
viewport: [f32; 4],
forward: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct StyleParams {
color: [f32; 4],
params: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct MeshVertex {
position: [f32; 3],
normal: [f32; 3],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct EdgeInstance {
p0: [f32; 3],
p1: [f32; 3],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct PointInstance {
center: [f32; 3],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct OverlayLineInstance {
p0: [f32; 3],
p1: [f32; 3],
color: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct OverlayTriVertex {
position: [f32; 3],
normal: [f32; 3],
color: [f32; 4],
}
struct StyleBuf {
buf: wgpu::Buffer,
bind: wgpu::BindGroup,
}
impl StyleBuf {
fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, label: &str) -> Self {
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: std::mem::size_of::<StyleParams>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buf.as_entire_binding(),
}],
});
Self { buf, bind }
}
fn write(&self, queue: &wgpu::Queue, color: Rgba, params: [f32; 4]) {
queue.write_buffer(&self.buf, 0, bytemuck::bytes_of(&StyleParams { color, params }));
}
}
struct GlobalStyles {
face_selected: StyleBuf,
face_hovered: StyleBuf,
edge_base: StyleBuf,
edge_selected: StyleBuf,
edge_hovered: StyleBuf,
edge_hidden: StyleBuf,
boundary: StyleBuf,
point_base: StyleBuf,
point_selected: StyleBuf,
point_hovered: StyleBuf,
axis_x: StyleBuf,
axis_y: StyleBuf,
axis_z: StyleBuf,
overlay_line: StyleBuf,
}
struct FaceRange {
first_index: u32,
index_count: u32,
}
struct EdgeRange {
first_instance: u32,
instance_count: u32,
}
struct GpuSolid {
revision: u64,
vertex_buf: wgpu::Buffer,
index_buf: wgpu::Buffer,
wire_index_buf: wgpu::Buffer,
wire_index_count: u32,
faces: Vec<FaceRange>,
edge_buf: Option<wgpu::Buffer>,
edges: Vec<EdgeRange>,
edge_instances: u32,
point_buf: Option<wgpu::Buffer>,
point_count: u32,
base_style: StyleBuf,
boundary: Option<BoundaryBuf>,
}
struct BoundaryBuf {
emphasis_generation: u64,
revision: u64,
buf: Option<wgpu::Buffer>,
count: u32,
}
#[derive(Default)]
pub struct GpuScene {
solids: HashMap<String, GpuSolid>,
order: Vec<String>,
settings_generation: u64,
axis_buf: Option<wgpu::Buffer>,
uploads: u64,
}
impl GpuScene {
pub fn upload_count(&self) -> u64 {
self.uploads
}
}
struct CachedTargets {
width: u32,
height: u32,
msaa_view: wgpu::TextureView,
depth_view: wgpu::TextureView,
}
pub struct FrameParams<'a> {
pub camera: &'a Camera,
pub width: u32,
pub height: u32,
pub dpr: f32,
pub settings: &'a RenderSettings,
pub emphasis: &'a Emphasis,
pub world_per_pixel: f64,
pub overlay: Option<&'a crate::widgets::WidgetOverlay>,
}
#[cfg(not(target_arch = "wasm32"))]
pub fn create_headless_device() -> Result<(wgpu::Device, wgpu::Queue, String), String> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let want = std::env::var("BREP_RENDER_ADAPTER").ok();
let adapter = if let Some(want) = &want {
let want_lower = want.to_lowercase();
pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()))
.into_iter()
.find(|adapter| adapter.get_info().name.to_lowercase().contains(&want_lower))
.ok_or_else(|| format!("no adapter matching BREP_RENDER_ADAPTER={want}"))?
} else {
pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
}))
.map_err(|error| format!("no wgpu adapter available: {error}"))?
};
let info = adapter.get_info();
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("brep-render"),
..Default::default()
}))
.map_err(|error| format!("wgpu device request failed: {error}"))?;
Ok((device, queue, format!("{} ({:?})", info.name, info.backend)))
}
pub struct RenderCore {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
format: wgpu::TextureFormat,
globals_buf: wgpu::Buffer,
globals_bind: wgpu::BindGroup,
style_layout: wgpu::BindGroupLayout,
mesh_pipeline: wgpu::RenderPipeline,
wire_pipeline: wgpu::RenderPipeline,
edge_visible_pipeline: wgpu::RenderPipeline,
edge_hidden_pipeline: wgpu::RenderPipeline,
point_pipeline: wgpu::RenderPipeline,
overlay_tri_pipeline: wgpu::RenderPipeline,
overlay_line_pipeline: wgpu::RenderPipeline,
vc_globals_buf: wgpu::Buffer,
vc_globals_bind: wgpu::BindGroup,
styles: GlobalStyles,
targets: Option<CachedTargets>,
}
#[cfg(not(target_arch = "wasm32"))]
fn encode_png(rgb: &[u8], width: u32, height: u32) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
{
let mut encoder = png::Encoder::new(&mut out, width, height);
encoder.set_color(png::ColorType::Rgb);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder
.write_header()
.map_err(|error| format!("png header: {error}"))?;
writer
.write_image_data(rgb)
.map_err(|error| format!("png data: {error}"))?;
}
Ok(out)
}
mod pipelines;
mod scene_sync;
mod frame;
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests;