diff --git a/nexrad-browser/Cargo.toml b/nexrad-browser/Cargo.toml index 9d91aa4..a4972ce 100644 --- a/nexrad-browser/Cargo.toml +++ b/nexrad-browser/Cargo.toml @@ -22,7 +22,10 @@ wasm-logger = "0.2" serde-wasm-bindgen = "0.6" chrono = "0.4" itertools = "0.11" -winit = "0.29" +winit = { version = "0.29", features = ["rwh_05"], default-features = false } +wgpu = { version = "0.18", features = ["webgl"] } +raw-window-handle = "0.5" +wasm-bindgen-futures = "0.4" [dependencies.js-sys] version = "0.3" diff --git a/nexrad-browser/src/lib.rs b/nexrad-browser/src/lib.rs index 0e95427..f9db102 100644 --- a/nexrad-browser/src/lib.rs +++ b/nexrad-browser/src/lib.rs @@ -10,9 +10,10 @@ pub mod colors; use std::io::Cursor; use std::ops::{Deref, DerefMut}; use js_sys::Uint8Array; -use log::{debug, info}; +use log::{debug, error, info}; use wasm_bindgen::prelude::*; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; +use wgpu::SurfaceError; use winit::dpi::PhysicalSize; use winit::event::{ElementState, Event, WindowEvent}; use winit::event::WindowEvent::KeyboardInput; @@ -23,7 +24,8 @@ use winit::platform::web::{WindowExtWebSys, EventLoopExtWebSys}; use crate::command::{exec, should_newline}; use crate::mode::Mode; use crate::mode::Mode::Reflectivity; -use crate::scope::{Preferences, RenderState, ScopeState}; +use crate::scope::{Preferences, ScopeState, WgpuState}; + #[wasm_bindgen] extern "C" { @@ -35,7 +37,7 @@ extern "C" { pub struct AbiScopeState(ScopeState); #[wasm_bindgen] -pub fn __nxrd_browser_init() -> AbiScopeState { +pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiScopeState { wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); utils::set_panic_hook(); @@ -44,7 +46,8 @@ pub fn __nxrd_browser_init() -> AbiScopeState { let event_loop = EventLoop::new().expect("event loop creation failed"); let window = WindowBuilder::new().build(&event_loop).unwrap(); - window.set_min_inner_size(Some(PhysicalSize::new(450, 400))); + window.set_min_inner_size(Some(PhysicalSize::new(w, h))); + web_sys::window() .and_then(|win| win.document()) @@ -57,6 +60,9 @@ pub fn __nxrd_browser_init() -> AbiScopeState { }) .expect("Couldn't append canvas to document body."); + let mut render_state = WgpuState::new(window, PhysicalSize::new(w, h)).await; + + // If you see an error here, your IDE is not compiling for webassembly event_loop.spawn(move |event: Event<_>, window: &EventLoopWindowTarget<_>| { match event { @@ -64,14 +70,29 @@ pub fn __nxrd_browser_init() -> AbiScopeState { match event { KeyboardInput { event, .. } => { debug!("{:?}", event.physical_key); - if event.physical_key == KeyCode::Escape { - window.exit(); - } }, WindowEvent::CloseRequested => { window.exit(); }, + WindowEvent::Resized(physical_size) => { + render_state.reconfigure(*physical_size); + }, + WindowEvent::RedrawRequested => { + render_state.update(); + match render_state.render() { + Ok(_) => {}, + Err(SurfaceError::Lost) => render_state.reconfigure(render_state.size), + Err(SurfaceError::OutOfMemory) => { + error!("out of memory!"); + window.exit(); + }, + Err(e) => error!("transient rendering error: {}", e), + } + }, _ => {} } }, + Event::AboutToWait => { + render_state.window().request_redraw(); + }, _ => {} } }); diff --git a/nexrad-browser/src/scope.rs b/nexrad-browser/src/scope.rs index 99bca68..48b39ac 100644 --- a/nexrad-browser/src/scope.rs +++ b/nexrad-browser/src/scope.rs @@ -1,6 +1,13 @@ +use std::iter; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlInputElement}; +use wgpu::{Backends, Color, CommandEncoderDescriptor, Device, Features, Instance, InstanceDescriptor, Limits, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration, SurfaceError, TextureUsages, TextureViewDescriptor}; +use winit::dpi::PhysicalSize; +use winit::event::WindowEvent; +use raw_window_handle::HasRawDisplayHandle; +use winit::window::Window; use nexrad2::Nexrad2Chunk; use crate::mode::Mode; +use wgpu::DeviceDescriptor; pub struct ScopeState { pub ar2: Option, @@ -16,11 +23,132 @@ pub struct ScopeState { pub show_ui: bool } -pub struct RenderState { - pub canvas: HtmlCanvasElement, - pub context: CanvasRenderingContext2d +pub struct WgpuState { + pub surface: Surface, + pub device: Device, + pub queue: Queue, + pub surface_config: SurfaceConfiguration, + pub size: PhysicalSize, + pub window: Window } +impl WgpuState { + pub async fn new(window: Window, size: PhysicalSize) -> Self { + let instance = Instance::new(InstanceDescriptor { + backends: Backends::all(), + flags: Default::default(), + dx12_shader_compiler: Default::default(), + gles_minor_version: Default::default(), + }); + let surface = unsafe { + instance.create_surface(&window).unwrap() + }; + + let adapter = instance.request_adapter( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }, + ).await.unwrap(); + + let (device, queue) = adapter.request_device( + &DeviceDescriptor { + features: Features::empty(), + limits: Limits::downlevel_webgl2_defaults(), + label: None, + }, + None, // Trace path + ).await.unwrap(); + + let surface_capabilities = surface.get_capabilities(&adapter); + + let surface_format = surface_capabilities.formats.iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_capabilities.formats[0]); + + let surface_config = SurfaceConfiguration { + usage: TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_capabilities.present_modes[0], + alpha_mode: surface_capabilities.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &surface_config); + + Self { + window, + surface, + device, + queue, + surface_config, + size + } + } + + pub fn window(&self) -> &Window { + &self.window + } + + pub fn reconfigure(&mut self, new_size: PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.surface_config.width = new_size.width; + self.surface_config.height = new_size.height; + self.surface.configure(&self.device, &self.surface_config); + } + } + + pub fn input(&mut self, event: &WindowEvent) -> bool { + false + } + + pub fn update(&mut self) { + + } + + pub fn render(&mut self) -> Result<(), SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output.texture.create_view(&TextureViewDescriptor::default()); + let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + // LOCK BLOCK + { + let render_pass = encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + } + + self.queue.submit(iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} + + pub struct Preferences { pub fcs: usize } \ No newline at end of file diff --git a/nexrad-browser/www/index.html b/nexrad-browser/www/index.html index 15a94b3..22e7d31 100644 --- a/nexrad-browser/www/index.html +++ b/nexrad-browser/www/index.html @@ -14,9 +14,6 @@ } canvas { - width: 100%; - height: 100%; - image-rendering: pixelated; z-index: 10000; } diff --git a/nexrad-browser/www/index.js b/nexrad-browser/www/index.js index c513c51..71b0035 100644 --- a/nexrad-browser/www/index.js +++ b/nexrad-browser/www/index.js @@ -1,6 +1,6 @@ import * as wasm from "./wasm/nexrad_browser.js"; await wasm.default(); -let global_context = wasm.__nxrd_browser_init(); +let global_context = wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight); /* function rescaleCanvas(canvas, ctx) { var dpr = window.devicePixelRatio || 1;