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"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
tokio = {version="1.35", features=["rt-multi-thread", "macros"]}
|
||||
tokio-util = {version="0.7", features=["io"]}
|
||||
reqwest = {version="0.11", features=["json", "stream"]}
|
||||
proj = "0.27"
|
||||
geo-types = "0.7"
|
||||
clap = {version="4.4", features=["derive"]}
|
||||
anyhow = "1.0"
|
||||
futures = "0.3"
|
||||
bytes = "1.5"
|
||||
las = {version="0.8", features=["laz"]}
|
||||
image = "0.24"
|
||||
winit = { version = "0.30.3", features = ["rwh_06"] }
|
||||
log = "0.4.22"
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||
wgpu = "0.20.1"
|
||||
env_logger = "0.11.3"
|
||||
futures = { version = "0.3.30", features = ["executor"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wgpu = { version = "0.20.1", features = ["webgl"]}
|
||||
wasm-bindgen = "0.2.84"
|
||||
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 {
|
||||
clap::{Parser, Subcommand},
|
||||
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(())
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
pteropus::main();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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