use crate::engine_state::EngineState;
use crate::render::{FrameParams, GpuScene, RenderCore};
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
fn js_err(message: String) -> JsValue {
js_sys::Error::new(&message).into()
}
#[derive(Debug)]
struct WebDisplay;
impl wgpu::rwh::HasDisplayHandle for WebDisplay {
fn display_handle(
&self,
) -> Result<wgpu::rwh::DisplayHandle<'_>, wgpu::rwh::HandleError> {
let raw = wgpu::rwh::RawDisplayHandle::Web(wgpu::rwh::WebDisplayHandle::new());
Ok(unsafe { wgpu::rwh::DisplayHandle::borrow_raw(raw) })
}
}
struct WasmRender {
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
core: RenderCore,
gpu_scene: GpuScene,
dpr: f32,
}
struct Inner {
state: EngineState,
render: Option<WasmRender>,
}
#[wasm_bindgen]
pub struct Engine {
inner: Rc<RefCell<Inner>>,
}
#[wasm_bindgen]
impl Engine {
#[wasm_bindgen(constructor)]
pub fn new() -> Engine {
console_error_panic_hook::set_once();
Engine {
inner: Rc::new(RefCell::new(Inner {
state: EngineState::new(),
render: None,
})),
}
}
pub fn attach(
&self,
canvas: web_sys::HtmlCanvasElement,
css_width: f64,
css_height: f64,
dpr: f64,
) -> js_sys::Promise {
let inner = self.inner.clone();
wasm_bindgen_futures::future_to_promise(async move {
let dpr = (dpr.max(0.5)) as f32;
let phys_w = (css_width * dpr as f64).round().max(1.0) as u32;
let phys_h = (css_height * dpr as f64).round().max(1.0) as u32;
canvas.set_width(phys_w);
canvas.set_height(phys_h);
let webgpu_instance = {
let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
descriptor.backends = wgpu::Backends::BROWSER_WEBGPU;
wgpu::Instance::new(descriptor)
};
let webgpu_ok = webgpu_instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.is_ok();
let (instance, surface, adapter) = if webgpu_ok {
let surface = webgpu_instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
.map_err(|error| js_err(format!("create_surface(webgpu): {error}")))?;
let adapter = webgpu_instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.map_err(|error| js_err(format!("request_adapter(webgpu): {error}")))?;
(webgpu_instance, surface, adapter)
} else {
drop(webgpu_instance);
let mut descriptor =
wgpu::InstanceDescriptor::new_with_display_handle(Box::new(WebDisplay));
descriptor.backends = wgpu::Backends::GL;
let instance = wgpu::Instance::new(descriptor);
let surface = instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
.map_err(|error| js_err(format!("create_surface(webgl2): {error}")))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.map_err(|error| js_err(format!("request_adapter(webgl2): {error}")))?;
(instance, surface, adapter)
};
let _ = &instance;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("brep-render-web"),
required_limits: adapter.limits(),
..Default::default()
})
.await
.map_err(|error| js_err(format!("request_device: {error}")))?;
let caps = surface.get_capabilities(&adapter);
let format = caps
.formats
.iter()
.copied()
.find(|format| !format.is_srgb())
.unwrap_or(caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: phys_w,
height: phys_h,
present_mode: wgpu::PresentMode::AutoVsync,
desired_maximum_frame_latency: 2,
alpha_mode: caps.alpha_modes[0],
view_formats: vec![],
};
surface.configure(&device, &config);
let core = RenderCore::new(device, queue, format);
let gpu_scene = GpuScene::default();
{
let mut inner = inner.borrow_mut();
inner.state.resize(css_width, css_height);
inner.render = Some(WasmRender {
surface,
config,
core,
gpu_scene,
dpr,
});
}
Ok(JsValue::UNDEFINED)
})
}
pub fn is_attached(&self) -> bool {
self.inner.borrow().render.is_some()
}
pub fn resize(&self, css_width: f64, css_height: f64, dpr: f64) {
let mut inner = self.inner.borrow_mut();
inner.state.resize(css_width, css_height);
if let Some(render) = &mut inner.render {
render.dpr = (dpr.max(0.5)) as f32;
render.config.width = (css_width * render.dpr as f64).round().max(1.0) as u32;
render.config.height = (css_height * render.dpr as f64).round().max(1.0) as u32;
render.surface.configure(&render.core.device, &render.config);
}
}
pub fn render(&self) -> bool {
let mut inner = self.inner.borrow_mut();
let Inner { state, render } = &mut *inner;
if !state.dirty {
return false;
}
let Some(render) = render else {
return false;
};
let settings_generation = state.settings_generation;
render
.core
.sync_scene(&mut render.gpu_scene, &state.scene, &state.settings, settings_generation);
let frame = match render.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame)
| wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
return false
}
_ => {
render.surface.configure(&render.core.device, &render.config);
match render.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame)
| wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
_ => return false,
}
}
};
let view = frame.texture.create_view(&Default::default());
let depth_bbox = state.depth_range_bbox();
state.camera.fit_depth_range(&depth_bbox);
let camera = state.camera.resolve();
let overlay = state.build_widget_overlay();
let params = FrameParams {
camera: &camera,
width: render.config.width,
height: render.config.height,
dpr: render.dpr,
settings: &state.settings,
emphasis: &state.emphasis,
world_per_pixel: state.camera.world_per_pixel(),
overlay: overlay.as_ref(),
};
render
.core
.render_to_view(&mut render.gpu_scene, &state.scene, ¶ms, &view);
frame.present();
state.dirty = false;
true
}
pub fn needs_render(&self) -> bool {
self.inner.borrow().state.dirty
}
pub fn run_history(&self, request_json: &str, overrides: Option<String>) -> Result<String, JsValue> {
let mut inner = self.inner.borrow_mut();
inner
.state
.run_history_json(request_json, overrides.as_deref())
.map_err(js_err)
}
pub fn zoom_to_fit(&self) {
self.inner.borrow_mut().state.zoom_to_fit();
}
pub fn pointer_down(&self, x: f64, y: f64, button: i32) -> bool {
self.inner.borrow_mut().state.pointer_down(x, y, button)
}
pub fn pointer_move(&self, x: f64, y: f64) -> bool {
self.inner.borrow_mut().state.pointer_move(x, y)
}
pub fn pointer_up(&self) -> bool {
self.inner.borrow_mut().state.pointer_up()
}
pub fn wheel(&self, delta_y: f64, cursor_x: f64, cursor_y: f64) -> bool {
let cursor = if cursor_x.is_finite() && cursor_y.is_finite() {
Some([cursor_x, cursor_y])
} else {
None
};
self.inner.borrow_mut().state.wheel(delta_y, cursor)
}
pub fn set_controls_enabled(&self, enabled: bool) {
self.inner.borrow_mut().state.set_controls_enabled(enabled);
}
pub fn toggle_projection(&self) -> String {
self.inner.borrow_mut().state.toggle_projection().to_string()
}
pub fn set_projection(&self, kind: &str) {
self.inner.borrow_mut().state.set_projection(kind);
}
pub fn standard_view(&self, name: &str) -> bool {
self.inner.borrow_mut().state.standard_view(name)
}
pub fn camera_state(&self) -> String {
self.inner.borrow().state.camera_state_json()
}
pub fn apply_camera_state(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.apply_camera_state_json(json).map_err(js_err)
}
pub fn world_per_pixel(&self) -> f64 {
self.inner.borrow().state.world_per_pixel()
}
pub fn world_to_screen(&self, points_json: &str) -> Result<String, JsValue> {
self.inner.borrow().state.world_to_screen_json(points_json).map_err(js_err)
}
pub fn camera_matrices(&self) -> String {
self.inner.borrow().state.camera_matrices_json()
}
pub fn pick(&self, x: f64, y: f64) -> String {
self.inner.borrow().state.pick_json(x, y)
}
pub fn hover(&self, x: f64, y: f64) -> String {
self.inner.borrow().state.hover_json(x, y)
}
pub fn apply_settings(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.apply_settings_json(json).map_err(js_err)
}
pub fn apply_emphasis(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.apply_emphasis_json(json).map_err(js_err)
}
pub fn set_visible(&self, name: &str, visible: bool) -> bool {
self.inner.borrow_mut().state.set_visible(name, visible)
}
pub fn set_color_override(&self, name: &str, color_hex: Option<String>) -> bool {
self.inner
.borrow_mut()
.state
.set_color_override(name, color_hex.as_deref())
}
pub fn scene_listing(&self) -> String {
self.inner.borrow().state.scene_listing_json()
}
pub fn set_datums(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.set_datums_json(json).map_err(js_err)
}
pub fn set_dimensions(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.set_dimensions_json(json).map_err(js_err)
}
pub fn set_overlay(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.set_overlay_json(json).map_err(js_err)
}
pub fn set_transform_gizmo(&self, json: &str) -> Result<(), JsValue> {
self.inner.borrow_mut().state.set_transform_json(json).map_err(js_err)
}
pub fn set_viewcube_enabled(&self, enabled: bool) {
self.inner.borrow_mut().state.set_viewcube_enabled(enabled);
}
pub fn viewcube_rect(&self) -> String {
self.inner.borrow().state.viewcube_rect_json()
}
pub fn viewcube_hover(&self, local_x: f64, local_y: f64) -> bool {
self.inner.borrow_mut().state.viewcube_hover(local_x, local_y)
}
pub fn viewcube_clear_hover(&self) -> bool {
self.inner.borrow_mut().state.viewcube_clear_hover()
}
pub fn viewcube_click(&self, local_x: f64, local_y: f64) -> bool {
self.inner.borrow_mut().state.viewcube_click(local_x, local_y)
}
pub fn datum_pick(&self, x: f64, y: f64) -> String {
self.inner.borrow().state.datum_pick(x, y)
}
pub fn transform_hover(&self, x: f64, y: f64) -> u32 {
self.inner.borrow_mut().state.transform_hover(x, y)
}
pub fn transform_pick(&self, x: f64, y: f64) -> u32 {
self.inner.borrow().state.transform_pick(x, y)
}
pub fn transform_drag(&self, handle: u32, sx: f64, sy: f64, cx: f64, cy: f64) -> String {
self.inner.borrow_mut().state.transform_drag(handle, sx, sy, cx, cy)
}
pub fn transform_drag_end(&self) {
self.inner.borrow_mut().state.transform_drag_end();
}
pub fn dimension_anchors(&self) -> String {
self.inner.borrow().state.dimension_anchors_json()
}
}
impl Default for Engine {
fn default() -> Self {
Self::new()
}
}