Web version now has feature parity with native
This commit is contained in:
parent
a15eedcd1b
commit
c5930e55f9
|
|
@ -2707,7 +2707,6 @@ dependencies = [
|
|||
"thiserror 2.0.12",
|
||||
"wgpu-core-deps-apple",
|
||||
"wgpu-core-deps-emscripten",
|
||||
"wgpu-core-deps-wasm",
|
||||
"wgpu-core-deps-windows-linux-android",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
|
|
@ -2731,15 +2730,6 @@ dependencies = [
|
|||
"wgpu-hal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core-deps-wasm"
|
||||
version = "25.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eca8809ad123f6c7f2c5e01a2c7117c4fdfd02f70bd422ee2533f69dfa98756c"
|
||||
dependencies = [
|
||||
"wgpu-hal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core-deps-windows-linux-android"
|
||||
version = "25.0.0"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ env_logger = "0.11.3"
|
|||
futures = { version = "0.3.30", features = ["executor"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wgpu = { version = "25.0.0", features = ["webgl"]}
|
||||
wgpu = { version = "25.0.0"}
|
||||
wasm-bindgen = "0.2.84"
|
||||
wasm-bindgen-futures = "0.4.42"
|
||||
console_log = "1.0"
|
||||
|
|
@ -30,7 +30,8 @@ web-sys = { version = "0.3", features = [
|
|||
"Document",
|
||||
"Window",
|
||||
"Element",
|
||||
"File"
|
||||
"File",
|
||||
"Performance"
|
||||
]}
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use wgsl_shader_assembler::wgsl_module;
|
||||
|
||||
use super::raster::{Dem, DemBvh};
|
||||
use super::{
|
||||
FrameTimer,
|
||||
raster::{Dem, DemBvh},
|
||||
};
|
||||
use {
|
||||
bytemuck::{Pod, Zeroable},
|
||||
std::{borrow::Cow, num::NonZero, rc::Rc},
|
||||
|
|
@ -16,7 +19,7 @@ pub struct DemRenderer {
|
|||
index_count: usize,
|
||||
camera: Camera,
|
||||
uniforms: UniformBufferManager,
|
||||
animation_start: std::time::Instant,
|
||||
animation_phase: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -296,24 +299,33 @@ impl DemRenderer {
|
|||
vertex_buffer,
|
||||
index_buffer,
|
||||
index_count,
|
||||
animation_start: std::time::Instant::now(),
|
||||
animation_phase: 0.0,
|
||||
camera,
|
||||
uniforms,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
|
||||
pub fn render(
|
||||
&mut self,
|
||||
frame_timer: &dyn FrameTimer,
|
||||
view: &wgpu::TextureView,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
) {
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("DemRendererCommandEncoder"),
|
||||
});
|
||||
let camera_position = get_animated_camera_position(
|
||||
self.animation_start.elapsed(),
|
||||
self.get_max_dem_dimension(),
|
||||
);
|
||||
let radians_per_second = 0.5;
|
||||
self.animation_phase += frame_timer.get_frame_time_seconds() as f32 * radians_per_second;
|
||||
self.animation_phase = self.animation_phase % (2.0 * std::f32::consts::PI);
|
||||
let camera_position =
|
||||
get_animated_camera_position(self.animation_phase, self.get_max_dem_dimension());
|
||||
self.camera
|
||||
.set_position(camera_position, self.get_dem_centre());
|
||||
self.uniforms.set_camera_to_world_matrix(self.camera.get_camera_to_world_matrix());
|
||||
self.uniforms.set_world_to_ndc_matrix(self.camera.get_world_to_ndc_matrix());
|
||||
self.uniforms
|
||||
.set_camera_to_world_matrix(self.camera.get_camera_to_world_matrix());
|
||||
self.uniforms
|
||||
.set_world_to_ndc_matrix(self.camera.get_world_to_ndc_matrix());
|
||||
self.uniforms
|
||||
.set_camera_position(camera_position.extend(1.0));
|
||||
self.uniforms.update_buffer(queue);
|
||||
|
|
@ -493,8 +505,7 @@ fn create_dembvh_texture(
|
|||
texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
fn get_animated_camera_position(animation_time: std::time::Duration, dem_size: f32) -> glam::Vec3 {
|
||||
let animation_phase = 2.0 * std::f32::consts::PI * (animation_time.as_secs_f32() % 100.0) / 100.0;
|
||||
fn get_animated_camera_position(animation_phase: f32, dem_size: f32) -> glam::Vec3 {
|
||||
glam::Vec3::new(
|
||||
dem_size * f32::sin(animation_phase),
|
||||
dem_size * f32::cos(animation_phase),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::mvu::{Event, File, MvuApp, Size2i};
|
||||
use crate::mvu::{Event, File, MvuApp, Size2i, FrameTimer};
|
||||
use {
|
||||
log::info,
|
||||
std::borrow::Cow,
|
||||
|
|
@ -29,6 +29,7 @@ struct Context {
|
|||
render_pipeline: RenderPipeline,
|
||||
queue: Queue,
|
||||
scene_data: Option<DemRenderer>,
|
||||
frame_timer: Box<dyn FrameTimer>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -43,7 +44,13 @@ impl App {
|
|||
}
|
||||
|
||||
impl MvuApp<Model> for App {
|
||||
async fn init(&mut self, instance: &Instance, surface: Surface<'static>, size: Size2i) {
|
||||
async fn init(
|
||||
&mut self,
|
||||
instance: &Instance,
|
||||
surface: Surface<'static>,
|
||||
size: Size2i,
|
||||
frame_timer: Box<dyn FrameTimer>,
|
||||
) {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
|
|
@ -116,6 +123,7 @@ impl MvuApp<Model> for App {
|
|||
render_pipeline,
|
||||
queue,
|
||||
scene_data: None,
|
||||
frame_timer: frame_timer,
|
||||
});
|
||||
|
||||
info!("Initialized {}x{}.", size.width, size.height);
|
||||
|
|
@ -146,6 +154,7 @@ impl MvuApp<Model> for App {
|
|||
|
||||
async fn view(&mut self, model: Rc<Model>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(context) = &mut self.context {
|
||||
context.frame_timer.mark_frame_start();
|
||||
if context.scene_data.is_none() {
|
||||
if let Some(dem) = &model.dem {
|
||||
context.scene_data = Some(DemRenderer::new(
|
||||
|
|
@ -165,7 +174,12 @@ impl MvuApp<Model> for App {
|
|||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
if let Some(scene_data) = &mut context.scene_data {
|
||||
scene_data.render(&view, &context.device, &context.queue);
|
||||
scene_data.render(
|
||||
context.frame_timer.as_ref(),
|
||||
&view,
|
||||
&context.device,
|
||||
&context.queue,
|
||||
);
|
||||
} else {
|
||||
let mut encoder = context
|
||||
.device
|
||||
|
|
|
|||
|
|
@ -23,10 +23,21 @@ pub struct Size2i {
|
|||
pub height: u32,
|
||||
}
|
||||
|
||||
pub trait FrameTimer {
|
||||
fn mark_frame_start(&mut self);
|
||||
fn get_frame_time_seconds(&self) -> f64;
|
||||
}
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
// https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html#where-the-gaps-lie
|
||||
pub trait MvuApp<M> {
|
||||
async fn init(&mut self, instance: &Instance, surface: Surface<'static>, new_size: Size2i);
|
||||
async fn init(
|
||||
&mut self,
|
||||
instance: &Instance,
|
||||
surface: Surface<'static>,
|
||||
new_size: Size2i,
|
||||
frame_timer: Box<dyn FrameTimer>,
|
||||
);
|
||||
async fn resize(&mut self, size: Size2i);
|
||||
async fn update(&self, model: Rc<M>, event: Event) -> Rc<M>;
|
||||
async fn view(&mut self, model: Rc<M>) -> Result<(), Box<dyn std::error::Error>>;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,31 @@ use {
|
|||
},
|
||||
};
|
||||
|
||||
struct FrameTimer {
|
||||
last_frame_start: std::time::Instant,
|
||||
current_frame_start: std::time::Instant,
|
||||
}
|
||||
|
||||
impl FrameTimer {
|
||||
fn new() -> Self {
|
||||
FrameTimer {
|
||||
last_frame_start: std::time::Instant::now(),
|
||||
current_frame_start: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mvu::FrameTimer for FrameTimer {
|
||||
fn mark_frame_start(&mut self) {
|
||||
self.last_frame_start = self.current_frame_start;
|
||||
self.current_frame_start = std::time::Instant::now();
|
||||
}
|
||||
|
||||
fn get_frame_time_seconds(&self) -> f64 {
|
||||
(self.current_frame_start - self.last_frame_start).as_secs_f64()
|
||||
}
|
||||
}
|
||||
|
||||
struct AppHost<A, M> {
|
||||
window: Option<Arc<Window>>,
|
||||
app: A,
|
||||
|
|
@ -42,7 +67,12 @@ where
|
|||
let surface = instance.create_surface(Arc::clone(&window)).unwrap();
|
||||
let PhysicalSize { width, height } = window.inner_size();
|
||||
self.window = Some(window);
|
||||
block_on(self.app.init(&instance, surface, Size2i { width, height }));
|
||||
block_on(self.app.init(
|
||||
&instance,
|
||||
surface,
|
||||
Size2i { width, height },
|
||||
Box::new(FrameTimer::new()),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: The idea with these `block_on()` calls is that eventually I'll
|
||||
|
|
|
|||
|
|
@ -8,9 +8,43 @@ use {
|
|||
|
||||
use crate::{
|
||||
app::{App, Model},
|
||||
mvu,
|
||||
mvu::{Event, File, MvuApp, Size2i},
|
||||
};
|
||||
|
||||
struct FrameTimer {
|
||||
performance: web_sys::Performance,
|
||||
last_frame_start_milliseconds: f64,
|
||||
current_frame_start_milliseconds: f64,
|
||||
}
|
||||
|
||||
impl FrameTimer {
|
||||
fn new() -> Self {
|
||||
let performance = web_sys::window()
|
||||
.expect("get window")
|
||||
.performance()
|
||||
.expect("window has performance object");
|
||||
let last_frame_start_milliseconds = performance.now();
|
||||
let current_frame_start_milliseconds = performance.now();
|
||||
Self {
|
||||
performance,
|
||||
last_frame_start_milliseconds,
|
||||
current_frame_start_milliseconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mvu::FrameTimer for FrameTimer {
|
||||
fn mark_frame_start(&mut self) {
|
||||
self.last_frame_start_milliseconds = self.current_frame_start_milliseconds;
|
||||
self.current_frame_start_milliseconds = self.performance.now();
|
||||
}
|
||||
|
||||
fn get_frame_time_seconds(&self) -> f64 {
|
||||
(self.current_frame_start_milliseconds - self.last_frame_start_milliseconds) * 0.001
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct PteropusCanvas {
|
||||
|
|
@ -18,38 +52,13 @@ pub struct PteropusCanvas {
|
|||
model: Rc<Model>,
|
||||
}
|
||||
|
||||
/*struct ArrayFile {
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::io::Read for ArrayFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Seek for ArrayFile {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl File for ArrayFile {}*/
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl PteropusCanvas {
|
||||
async fn init(&self) {
|
||||
let window = web_sys::window().expect("get window");
|
||||
let document = window.document().expect("get HTML document");
|
||||
let canvas_element = document
|
||||
.get_element_by_id("pteropus-canvas")
|
||||
.expect("document contains element with id \"pteropus-canvas\"");
|
||||
let html_canvas: HtmlCanvasElement = canvas_element
|
||||
.dyn_into()
|
||||
.expect("pteropus-canvas element is a canvas");
|
||||
let html_canvas = self.get_canvas();
|
||||
let size = Size2i {
|
||||
width: html_canvas.width(),
|
||||
height: html_canvas.height(),
|
||||
width: html_canvas.client_width() as u32,
|
||||
height: html_canvas.client_height() as u32,
|
||||
};
|
||||
let instance = wgpu::Instance::default();
|
||||
let surface_target = SurfaceTarget::Canvas(html_canvas);
|
||||
|
|
@ -60,7 +69,7 @@ impl PteropusCanvas {
|
|||
self.app
|
||||
.lock()
|
||||
.expect("get app mutex")
|
||||
.init(&instance, surface, size)
|
||||
.init(&instance, surface, size, Box::new(FrameTimer::new()))
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +83,19 @@ impl PteropusCanvas {
|
|||
.expect("view succeeds");
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn on_resize(&self) {
|
||||
let html_canvas = self.get_canvas();
|
||||
self.app
|
||||
.lock()
|
||||
.expect("get app mutex")
|
||||
.resize(Size2i {
|
||||
width: html_canvas.client_width() as u32,
|
||||
height: html_canvas.client_height() as u32,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn load_file(&mut self, file: web_sys::File) {
|
||||
let data = gloo::file::futures::read_as_bytes(&file.into())
|
||||
|
|
@ -89,6 +111,17 @@ impl PteropusCanvas {
|
|||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub fn get_canvas(&self) -> HtmlCanvasElement {
|
||||
let window = web_sys::window().expect("get window");
|
||||
let document = window.document().expect("get HTML document");
|
||||
let canvas_element = document
|
||||
.get_element_by_id("pteropus-canvas")
|
||||
.expect("document contains element with id \"pteropus-canvas\"");
|
||||
canvas_element
|
||||
.dyn_into()
|
||||
.expect("pteropus-canvas element is a canvas")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(app: App, model: Rc<Model>) -> PteropusCanvas {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
|
||||
let pteropus = await init_pteropus();
|
||||
|
||||
let test_file_data = null
|
||||
let test_file_data = null;
|
||||
let needs_resize = true;
|
||||
|
||||
let mainCanvas = document.getElementById("pteropus-canvas");
|
||||
mainCanvas.addEventListener("drop", async (event) => {
|
||||
|
|
@ -32,6 +33,10 @@
|
|||
test_file_data = event.dataTransfer.files[0]
|
||||
});
|
||||
mainCanvas.addEventListener("dragover", fileDragOverHandler);
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
needs_resize = true;
|
||||
});
|
||||
resizeObserver.observe(mainCanvas)
|
||||
|
||||
while(true)
|
||||
{
|
||||
|
|
@ -43,6 +48,10 @@
|
|||
await pteropus.load_file(test_file_data);
|
||||
test_file_data = null
|
||||
}
|
||||
if(needs_resize) {
|
||||
await pteropus.on_resize();
|
||||
needs_resize = false;
|
||||
}
|
||||
await new Promise(requestAnimationFrame);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue