Stub in basic wgpu framework; just draw triangle right now
Start again using WGPU with both native and WebAssembly. Just draws a triangle right now.
This commit is contained in:
parent
5e107d78f2
commit
5dfdd3e927
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
|
|
@ -3,18 +3,30 @@ name = "pteropus"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
winit = { version = "0.30.3", features = ["rwh_06"] }
|
||||||
tokio = {version="1.35", features=["rt-multi-thread", "macros"]}
|
log = "0.4.22"
|
||||||
tokio-util = {version="0.7", features=["io"]}
|
|
||||||
reqwest = {version="0.11", features=["json", "stream"]}
|
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||||
proj = "0.27"
|
wgpu = "0.20.1"
|
||||||
geo-types = "0.7"
|
env_logger = "0.11.3"
|
||||||
clap = {version="4.4", features=["derive"]}
|
futures = { version = "0.3.30", features = ["executor"] }
|
||||||
anyhow = "1.0"
|
|
||||||
futures = "0.3"
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bytes = "1.5"
|
wgpu = { version = "0.20.1", features = ["webgl"]}
|
||||||
las = {version="0.8", features=["laz"]}
|
wasm-bindgen = "0.2.84"
|
||||||
image = "0.24"
|
wasm-bindgen-futures = "0.4.42"
|
||||||
|
console_log = "1.0"
|
||||||
|
console_error_panic_hook = "0.1.7"
|
||||||
|
web-sys = { version = "0.3", features = [
|
||||||
|
"Document",
|
||||||
|
"Window",
|
||||||
|
"Element",
|
||||||
|
]}
|
||||||
|
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.3.34"
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
use crate::mvu::{Event, MvuApp, Size2i};
|
||||||
|
use {
|
||||||
|
std::borrow::Cow,
|
||||||
|
wgpu::{Device, Instance, Queue, RenderPipeline, Surface, SurfaceConfiguration},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Model {}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new() -> Model {
|
||||||
|
Model {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
config: SurfaceConfiguration,
|
||||||
|
surface: Surface<'static>,
|
||||||
|
device: Device,
|
||||||
|
render_pipeline: RenderPipeline,
|
||||||
|
queue: Queue,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct App {
|
||||||
|
context: Option<Context>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MvuApp<Model> for App {
|
||||||
|
async fn init(&mut self, instance: &Instance, surface: Surface<'static>, size: Size2i) {
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::default(),
|
||||||
|
force_fallback_adapter: false,
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to find an appropriate adapter");
|
||||||
|
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
required_features: wgpu::Features::empty(),
|
||||||
|
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
|
||||||
|
.using_resolution(adapter.limits()),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create device");
|
||||||
|
|
||||||
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let swapchain_capabilities = surface.get_capabilities(&adapter);
|
||||||
|
let swapchain_format = swapchain_capabilities.formats[0];
|
||||||
|
|
||||||
|
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
targets: &[Some(swapchain_format.into())],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let config = surface
|
||||||
|
.get_default_config(&adapter, size.width, size.height)
|
||||||
|
.unwrap();
|
||||||
|
surface.configure(&device, &config);
|
||||||
|
|
||||||
|
self.context = Some(Context {
|
||||||
|
config,
|
||||||
|
surface,
|
||||||
|
device,
|
||||||
|
render_pipeline,
|
||||||
|
queue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resize(&mut self, new_size: Size2i) {
|
||||||
|
if let Some(Context {
|
||||||
|
config,
|
||||||
|
surface,
|
||||||
|
device,
|
||||||
|
render_pipeline: _,
|
||||||
|
queue: _,
|
||||||
|
}) = &mut self.context
|
||||||
|
{
|
||||||
|
config.width = new_size.width.max(1);
|
||||||
|
config.height = new_size.height.max(1);
|
||||||
|
surface.configure(device, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(&self, model: Model, _event: Event) -> Model {
|
||||||
|
model
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn view(&mut self, _model: Model) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if let Some(Context {
|
||||||
|
config: _,
|
||||||
|
surface,
|
||||||
|
device,
|
||||||
|
render_pipeline,
|
||||||
|
queue,
|
||||||
|
}) = &self.context
|
||||||
|
{
|
||||||
|
let frame = surface
|
||||||
|
.get_current_texture()
|
||||||
|
.expect("Failed to acquire next swap chain texture");
|
||||||
|
let view = frame
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let mut encoder =
|
||||||
|
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
rpass.set_pipeline(render_pipeline);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(Some(encoder.finish()));
|
||||||
|
frame.present();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
||||||
|
let x = f32(i32(in_vertex_index) - 1);
|
||||||
|
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
||||||
|
return vec4<f32>(x, y, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main() -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
mod wasm;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub use wasm::run;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
mod native;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
pub use native::run;
|
||||||
|
|
||||||
|
mod mvu;
|
||||||
|
mod app;
|
||||||
|
use app::{App,Model};
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
|
||||||
|
pub fn main() {
|
||||||
|
run(App::new(), Model::new());
|
||||||
|
}
|
||||||
|
|
||||||
230
src/main.rs
230
src/main.rs
|
|
@ -1,229 +1,3 @@
|
||||||
use {
|
fn main() {
|
||||||
clap::{Parser, Subcommand},
|
pteropus::main();
|
||||||
geo_types::Point,
|
|
||||||
image::{ImageBuffer, Luma},
|
|
||||||
las::Read as LasRead,
|
|
||||||
proj::Proj,
|
|
||||||
std::path::PathBuf,
|
|
||||||
tokio::io::AsyncReadExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod geonb;
|
|
||||||
mod numeric_types;
|
|
||||||
use numeric_types::{Float, Integer, ToFloat};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(author, version, about, long_about=None)]
|
|
||||||
struct Args {
|
|
||||||
/// Print extra debug info when initializing graphics
|
|
||||||
#[arg(long)]
|
|
||||||
debug_init: bool,
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Command {
|
|
||||||
/// Download data from GeoNB
|
|
||||||
GeoNB {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: GeoNBCommand,
|
|
||||||
},
|
|
||||||
/// Create a grid DEM from point data
|
|
||||||
GridPoints {
|
|
||||||
#[arg(long, short = 'i')]
|
|
||||||
laz_filename: PathBuf,
|
|
||||||
|
|
||||||
#[arg(long)]
|
|
||||||
grid_cell_size: f64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum GeoNBCommand {
|
|
||||||
/// Download the LIDAR tile that includes a location
|
|
||||||
DownloadLidarTile {
|
|
||||||
/// Latitude to fetch LIDAR tile at
|
|
||||||
#[arg(long, allow_hyphen_values = true)]
|
|
||||||
latitude: f64,
|
|
||||||
|
|
||||||
/// Longitude to fetch LIDAR tile at
|
|
||||||
#[arg(long, allow_hyphen_values = true)]
|
|
||||||
longitude: f64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Grid<T> {
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
data: Vec<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Grid<T> {
|
|
||||||
fn calculate_index(&self, i: usize, j: usize) -> usize {
|
|
||||||
assert!(i < self.width);
|
|
||||||
assert!(j < self.height);
|
|
||||||
i + j * self.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default + Copy> Grid<T> {
|
|
||||||
fn new(width: usize, height: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
data: vec![T::default(); width * height],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy> Grid<T> {
|
|
||||||
fn set_cell_value(&mut self, i: usize, j: usize, value: T) {
|
|
||||||
let index = self.calculate_index(i, j);
|
|
||||||
self.data[index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cell_value(&self, i: usize, j: usize) -> T {
|
|
||||||
let index = self.calculate_index(i, j);
|
|
||||||
self.data[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_cell_value_returns_zero_after_new() {
|
|
||||||
let width = 11;
|
|
||||||
let height = 13;
|
|
||||||
let target: Grid<i32> = Grid::new(width, height);
|
|
||||||
for i in 0..width {
|
|
||||||
for j in 0..height {
|
|
||||||
assert_eq!(0, target.get_cell_value(i, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn get_cell_value_returns_values_set_by_set_cell_value() {
|
|
||||||
fn unique_value_for_cell(i: usize, j: usize) -> i32 {
|
|
||||||
let high_bits = i << 12;
|
|
||||||
let low_bits = j;
|
|
||||||
assert_eq!(0, high_bits & low_bits);
|
|
||||||
let bits = low_bits | high_bits;
|
|
||||||
assert_eq!(bits, bits & 0x7fffffff);
|
|
||||||
bits as i32
|
|
||||||
}
|
|
||||||
let width = 11;
|
|
||||||
let height = 13;
|
|
||||||
let mut target: Grid<i32> = Grid::new(width, height);
|
|
||||||
for i in 0..width {
|
|
||||||
for j in 0..height {
|
|
||||||
target.set_cell_value(i, j, unique_value_for_cell(i, j));
|
|
||||||
assert_eq!(unique_value_for_cell(i, j), target.get_cell_value(i, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i in 0..width {
|
|
||||||
for j in 0..height {
|
|
||||||
assert_eq!(unique_value_for_cell(i, j), target.get_cell_value(i, j));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn integerize<F, I>(grid_in: Grid<F>, min: F, max: F) -> Grid<I>
|
|
||||||
where
|
|
||||||
I: Integer + ToFloat<F>,
|
|
||||||
F: Float,
|
|
||||||
{
|
|
||||||
let mut grid_out = Grid::new(grid_in.width, grid_in.height);
|
|
||||||
let range = max - min;
|
|
||||||
for i in 0..(grid_in.width as usize) {
|
|
||||||
for j in 0..(grid_in.height as usize) {
|
|
||||||
grid_out.set_cell_value(
|
|
||||||
i,
|
|
||||||
j,
|
|
||||||
(((grid_in.get_cell_value(i, j) - min) / range) * (I::MAX).to_float())
|
|
||||||
.to_int_floor(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
grid_out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), anyhow::Error> {
|
|
||||||
let args = Args::parse();
|
|
||||||
|
|
||||||
match args.command {
|
|
||||||
Command::GeoNB {
|
|
||||||
command:
|
|
||||||
GeoNBCommand::DownloadLidarTile {
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
},
|
|
||||||
} => {
|
|
||||||
let location = Proj::new_known_crs("+proj=longlat +datum=WGS84", "EPSG:2953", None)
|
|
||||||
.unwrap()
|
|
||||||
.convert(Point::new(longitude, latitude))
|
|
||||||
.unwrap();
|
|
||||||
println!("{:?}", location);
|
|
||||||
let mut las_reader =
|
|
||||||
tokio::io::BufReader::new(geonb::get_lidar_tile_around_point(location).await?);
|
|
||||||
let mut las_bytes = Vec::new();
|
|
||||||
let mut buffer = [0_u8; 4096];
|
|
||||||
let mut byte_count = 0;
|
|
||||||
loop {
|
|
||||||
let num_bytes = las_reader.read(&mut buffer).await?;
|
|
||||||
if num_bytes == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
byte_count += num_bytes;
|
|
||||||
print!("{} bytes read\r", byte_count);
|
|
||||||
las_bytes.extend_from_slice(&buffer[0..num_bytes]);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
let mut las_reader = las::Reader::new(std::io::Cursor::new(las_bytes))?;
|
|
||||||
for wrapped_point in las_reader.points().take(10) {
|
|
||||||
let point = wrapped_point.unwrap();
|
|
||||||
println!("Point coordinates: ({}, {}, {})", point.x, point.y, point.z);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Command::GridPoints {
|
|
||||||
laz_filename,
|
|
||||||
grid_cell_size,
|
|
||||||
} => {
|
|
||||||
let mut las_reader = las::Reader::from_path(laz_filename).unwrap();
|
|
||||||
let bounds = las_reader.header().bounds();
|
|
||||||
let num_cells_x = ((bounds.max.x - bounds.min.x) / grid_cell_size).ceil() as usize;
|
|
||||||
let num_cells_y = ((bounds.max.y - bounds.min.y) / grid_cell_size).ceil() as usize;
|
|
||||||
let mut height_grid = Grid::new(num_cells_y, num_cells_x);
|
|
||||||
let mut weight_grid = Grid::new(num_cells_y, num_cells_x);
|
|
||||||
println!("Creating {} by {} grid.", num_cells_y, num_cells_x);
|
|
||||||
|
|
||||||
for wrapped_point in las_reader.points() {
|
|
||||||
let point = wrapped_point.unwrap();
|
|
||||||
let bin_i = ((point.x - bounds.min.x) / grid_cell_size).floor() as usize;
|
|
||||||
let bin_j = ((point.y - bounds.min.y) / grid_cell_size).floor() as usize;
|
|
||||||
let height: f64 = height_grid.get_cell_value(bin_i, bin_j);
|
|
||||||
let weight: f64 = weight_grid.get_cell_value(bin_i, bin_j);
|
|
||||||
height_grid.set_cell_value(
|
|
||||||
bin_i,
|
|
||||||
bin_j,
|
|
||||||
(height * weight + point.z) / (weight + 1.0),
|
|
||||||
);
|
|
||||||
weight_grid.set_cell_value(bin_i, bin_j, weight + 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let grid_u8: Grid<u8> = integerize(height_grid, bounds.min.z, bounds.max.z);
|
|
||||||
let image: ImageBuffer<Luma<u8>, _> =
|
|
||||||
ImageBuffer::from_raw(num_cells_x as u32, num_cells_y as u32, grid_u8.data)
|
|
||||||
.unwrap();
|
|
||||||
image.save("test.png")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
use wgpu::{Instance, Surface};
|
||||||
|
pub enum Event {
|
||||||
|
MouseButtonPressed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Size2i {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>
|
||||||
|
where
|
||||||
|
M: Copy,
|
||||||
|
{
|
||||||
|
async fn init(&mut self, instance: &Instance, surface: Surface<'static>, new_size: Size2i);
|
||||||
|
async fn resize(&mut self, size: Size2i);
|
||||||
|
async fn update(&self, model: M, event: Event) -> M;
|
||||||
|
async fn view(&mut self, model: M) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::mvu::{MvuApp, Size2i};
|
||||||
|
use {
|
||||||
|
futures::executor::block_on,
|
||||||
|
winit::{
|
||||||
|
application::ApplicationHandler,
|
||||||
|
dpi::PhysicalSize,
|
||||||
|
event::WindowEvent,
|
||||||
|
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
||||||
|
window::{Window, WindowId},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppHost<A, M> {
|
||||||
|
window: Option<Arc<Window>>,
|
||||||
|
app: A,
|
||||||
|
model: M,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, M> AppHost<A, M> {
|
||||||
|
fn new(app: A, model: M) -> Self {
|
||||||
|
Self {
|
||||||
|
window: None,
|
||||||
|
app,
|
||||||
|
model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, M> ApplicationHandler for AppHost<A, M>
|
||||||
|
where
|
||||||
|
A: MvuApp<M>,
|
||||||
|
M: Copy,
|
||||||
|
{
|
||||||
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
let window = Arc::new(
|
||||||
|
event_loop
|
||||||
|
.create_window(Window::default_attributes())
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let instance = wgpu::Instance::default();
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
|
||||||
|
match event {
|
||||||
|
WindowEvent::Resized(new_size) => block_on(self.app.resize(Size2i {
|
||||||
|
width: new_size.width,
|
||||||
|
height: new_size.height,
|
||||||
|
})),
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
println!("The close button was pressed; stopping");
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
WindowEvent::RedrawRequested => block_on(self.app.view(self.model)).unwrap(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<A, M>(app: A, model: M)
|
||||||
|
where
|
||||||
|
A: MvuApp<M>,
|
||||||
|
M: Copy,
|
||||||
|
{
|
||||||
|
env_logger::init();
|
||||||
|
let event_loop = EventLoop::new().unwrap();
|
||||||
|
event_loop.set_control_flow(ControlFlow::Poll);
|
||||||
|
let mut app_host = AppHost::new(app, model);
|
||||||
|
event_loop.run_app(&mut app_host).unwrap();
|
||||||
|
}
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
use std::ops::{Add, Div, Mul, Sub};
|
|
||||||
|
|
||||||
pub trait FromFloatFloor<F> {
|
|
||||||
fn from_float_floor(f: F) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ToFloat<F> {
|
|
||||||
fn to_float(self) -> F;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Integer:
|
|
||||||
Copy
|
|
||||||
+ Default
|
|
||||||
+ Add<Self, Output = Self>
|
|
||||||
+ Sub<Self, Output = Self>
|
|
||||||
+ Mul<Self, Output = Self>
|
|
||||||
+ Div<Self, Output = Self>
|
|
||||||
+ FromFloatFloor<f32>
|
|
||||||
+ FromFloatFloor<f64>
|
|
||||||
+ ToFloat<f32>
|
|
||||||
+ ToFloat<f64>
|
|
||||||
{
|
|
||||||
const MIN: Self;
|
|
||||||
const MAX: Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_int {
|
|
||||||
( $t:ident ) => {
|
|
||||||
impl FromFloatFloor<f32> for $t {
|
|
||||||
fn from_float_floor(f: f32) -> Self {
|
|
||||||
f as $t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromFloatFloor<f64> for $t {
|
|
||||||
fn from_float_floor(f: f64) -> Self {
|
|
||||||
f as $t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ToFloat<f32> for $t {
|
|
||||||
fn to_float(self) -> f32 {
|
|
||||||
self as f32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ToFloat<f64> for $t {
|
|
||||||
fn to_float(self) -> f64 {
|
|
||||||
self as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Integer for $t {
|
|
||||||
const MIN: $t = $t::MIN;
|
|
||||||
const MAX: $t = $t::MAX;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_int!(u8);
|
|
||||||
impl_int!(i8);
|
|
||||||
impl_int!(u16);
|
|
||||||
impl_int!(i16);
|
|
||||||
impl_int!(u32);
|
|
||||||
impl_int!(i32);
|
|
||||||
impl_int!(u64);
|
|
||||||
impl_int!(i64);
|
|
||||||
|
|
||||||
pub trait Unsigned {}
|
|
||||||
|
|
||||||
impl Unsigned for u8 {}
|
|
||||||
impl Unsigned for u16 {}
|
|
||||||
impl Unsigned for u32 {}
|
|
||||||
impl Unsigned for u64 {}
|
|
||||||
|
|
||||||
pub trait Float:
|
|
||||||
Copy
|
|
||||||
+ Default
|
|
||||||
+ Add<Self, Output = Self>
|
|
||||||
+ Sub<Self, Output = Self>
|
|
||||||
+ Mul<Self, Output = Self>
|
|
||||||
+ Div<Self, Output = Self>
|
|
||||||
{
|
|
||||||
fn to_int_floor<I: Integer>(self) -> I;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_float {
|
|
||||||
( $t:ident ) => {
|
|
||||||
impl Float for $t {
|
|
||||||
fn to_int_floor<I: Integer>(self) -> I {
|
|
||||||
I::from_float_floor(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_float!(f32);
|
|
||||||
impl_float!(f64);
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
use {
|
||||||
|
wasm_bindgen::prelude::*, wasm_bindgen_futures::spawn_local, web_sys::HtmlCanvasElement,
|
||||||
|
wgpu::SurfaceTarget,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::mvu::{MvuApp, Size2i};
|
||||||
|
|
||||||
|
async fn run_async<A, M>(mut app: A, model: M)
|
||||||
|
where
|
||||||
|
A: MvuApp<M> + 'static,
|
||||||
|
M: Copy + 'static,
|
||||||
|
{
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let canvas_element = document.get_element_by_id("pteropus-canvas").unwrap();
|
||||||
|
let html_canvas: HtmlCanvasElement = canvas_element.dyn_into().unwrap();
|
||||||
|
let size = Size2i {
|
||||||
|
width: html_canvas.width(),
|
||||||
|
height: html_canvas.height(),
|
||||||
|
};
|
||||||
|
let instance = wgpu::Instance::default();
|
||||||
|
let surface_target = SurfaceTarget::Canvas(html_canvas);
|
||||||
|
let surface = instance.create_surface(surface_target).unwrap();
|
||||||
|
|
||||||
|
app.init(&instance, surface, size).await;
|
||||||
|
app.view(model).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<A, M>(app: A, model: M)
|
||||||
|
where
|
||||||
|
A: MvuApp<M> + 'static,
|
||||||
|
M: Copy + 'static,
|
||||||
|
{
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
|
||||||
|
|
||||||
|
spawn_local(run_async(app, model));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Pteropus</title>
|
||||||
|
<style>
|
||||||
|
#pteropus-canvas {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vh;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="pteropus-canvas"></canvas>
|
||||||
|
<script type="module">
|
||||||
|
import init from '../pkg/pteropus.js';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
await init();
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue