Add script which works for webgpu and webgl

This commit is contained in:
Maximilian Ammann 2021-12-03 21:29:48 +01:00
parent bf52ad1d8e
commit 4a90df93fb
12 changed files with 193 additions and 103 deletions

View File

@ -57,13 +57,34 @@ TODO
## Building
Now, to build the project:
Now, to clone the project:
```bash
git clone git@github.com/maxammann/mapr
```
and then build it for running on a desktop:
```bash
cargo build
```
### Target: WebGPU
```bash
tools/build-web
cd web
python3 -m http.server
```
### Target: WebGL
```bash
tools/build-web -g
cd web
python3 -m http.server
```
## Running on Linux
Fuzz using three clients:

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "1.56.1"
channel = "1.57"
targets = [ "wasm32-unknown-unknown", "x86_64-unknown-linux-gnu" ]

View File

@ -1,7 +1,7 @@
use log::info;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{WindowBuilder};
use winit::window::{Window, WindowBuilder};
use crate::state::State;
@ -14,43 +14,19 @@ mod state;
mod tesselation;
mod texture;
mod platform_constants;
#[cfg(target_arch = "wasm32")]
mod web;
mod platform_constants;
async fn setup() {
async fn setup(window: Window, event_loop: EventLoop<()>) {
info!("== mapr ==");
info!("Controls:");
info!(" Arrow keys: scrolling");
info!(" PgUp/PgDown: zoom in/out");
info!(" a/z: increase/decrease the stroke width");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
let canvas = window.canvas();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
body.append_child(&canvas)
.expect("Append canvas to HTML body");
}
//let mut state = pollster::block_on(State::new(&window));
let mut state = State::new(&window).await;
window.request_redraw();
event_loop.run(move |event, _, control_flow| {
match event {
Event::WindowEvent {
@ -105,5 +81,11 @@ async fn setup() {
fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
pollster::block_on(setup());
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
pollster::block_on(setup(window, event_loop));
}

View File

@ -21,7 +21,6 @@ pub fn create_map_render_pipeline_description<'a>(
clamp_depth: false,
conservative: false,
},
/*depth_stencil: None,*/
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,

View File

@ -1,7 +1,13 @@
// WebGPU
#[cfg(all(target_arch = "wasm32", not(feature = "web-webgl")))]
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8Unorm;
// WebGL
#[cfg(all(target_arch = "wasm32", feature = "web-webgl"))]
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
// Vulkan/Metal/OpenGL
#[cfg(not(target_arch = "wasm32"))]
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
// FIXME: This limit is enforced by WebGL. Actually this makes sense!
// This is usually achieved by _pad attributes in shader_ffi.rs
pub const MIN_BUFFER_SIZE: u64 = 32;

View File

@ -1,10 +1,25 @@
type Vec2f32 = [f32; 2];
type Vec4f32 = [f32; 4];
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Globals {
pub resolution: [f32; 2],
pub scroll_offset: [f32; 2],
pub resolution: Vec2f32,
pub scroll_offset: Vec2f32,
pub zoom: f32,
pub _pad: f32,
_pad1: u32, // _padX aligns it to 8 bytes = AlignOf(Vec2f32=vec2<f32>):
// https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size
}
impl Globals {
pub fn new(resolution: Vec2f32, scroll_offset: Vec2f32, zoom: f32) -> Self {
Self {
resolution,
scroll_offset,
zoom,
_pad1: Default::default(),
}
}
}
unsafe impl bytemuck::Pod for Globals {}
@ -13,37 +28,65 @@ unsafe impl bytemuck::Zeroable for Globals {}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct GpuVertex {
pub position: [f32; 2],
pub normal: [f32; 2],
pub position: Vec2f32,
pub normal: Vec2f32,
pub prim_id: u32,
_pad1: u32, // _padX aligns it to 8 bytes = AlignOf(Vec2f32=vec2<f32>):
// https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size
}
impl GpuVertex {
pub fn new(position: Vec2f32, normal: Vec2f32, prim_id: u32) -> Self {
Self {
position,
normal,
prim_id,
_pad1: Default::default(),
}
}
}
unsafe impl bytemuck::Pod for GpuVertex {}
unsafe impl bytemuck::Zeroable for GpuVertex {}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Primitive {
pub color: [f32; 4],
pub translate: [f32; 2],
pub color: Vec4f32,
pub translate: Vec2f32,
pub z_index: i32,
pub width: f32,
pub angle: f32,
pub scale: f32,
pub _pad1: i32,
pub _pad2: i32,
_pad1: u32, // _padX aligns it to 16 bytes = AlignOf(Vec4f32/vec4<f32>):
_pad2: u32, // https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size
}
impl Default for Primitive {
fn default() -> Self {
Primitive::new([0.0; 4], [0.0; 2], 0, 0.0, 0.0, 1.0)
}
}
impl Primitive {
pub const DEFAULT: Self = Primitive {
color: [0.0; 4],
translate: [0.0; 2],
z_index: 0,
width: 0.0,
angle: 0.0,
scale: 1.0,
_pad1: 0,
_pad2: 0,
};
pub fn new(
color: Vec4f32,
translate: Vec2f32,
z_index: i32,
width: f32,
angle: f32,
scale: f32,
) -> Self {
Self {
color,
translate,
z_index,
width,
angle,
scale,
_pad1: Default::default(),
_pad2: Default::default(),
}
}
}
unsafe impl bytemuck::Pod for Primitive {}
@ -54,5 +97,6 @@ unsafe impl bytemuck::Zeroable for Primitive {}
pub struct BgPoint {
pub point: [f32; 2],
}
unsafe impl bytemuck::Pod for BgPoint {}
unsafe impl bytemuck::Zeroable for BgPoint {}

View File

@ -1,3 +1,4 @@
use std::cmp;
use std::io::Cursor;
use std::ops::Range;
@ -13,7 +14,7 @@ use winit::window::Window;
use crate::fps_meter::FPSMeter;
use crate::multisampling::create_multisampled_framebuffer;
use crate::piplines::*;
use crate::platform_constants::COLOR_TEXTURE_FORMAT;
use crate::platform_constants::{COLOR_TEXTURE_FORMAT, MIN_BUFFER_SIZE};
use crate::shader::*;
use crate::shader_ffi::*;
use crate::tesselation::{RustLogo, Tesselated};
@ -104,29 +105,22 @@ impl State {
let mut cpu_primitives = Vec::with_capacity(PRIM_BUFFER_LEN);
for _ in 0..PRIM_BUFFER_LEN {
cpu_primitives.push(Primitive {
color: [1.0, 0.0, 0.0, 1.0],
z_index: 0,
width: 0.0,
translate: [0.0, 0.0],
angle: 0.0,
..Primitive::DEFAULT
});
cpu_primitives.push(Primitive::new(
[1.0, 0.0, 0.0, 1.0],
[0.0, 0.0],
0,
0.0,
0.0,
1.0,
));
}
// Stroke primitive
cpu_primitives[stroke_prim_id as usize] = Primitive {
color: [0.0, 0.0, 0.0, 1.0],
z_index: 3,
width: 1.0,
..Primitive::DEFAULT
};
cpu_primitives[stroke_prim_id as usize] =
Primitive::new([0.0, 0.0, 0.0, 1.0], [0.0, 0.0], 3, 1.0, 0.0, 1.0);
// Main fill primitive
cpu_primitives[fill_prim_id as usize] = Primitive {
color: [1.0, 1.0, 1.0, 1.0],
z_index: 1,
..Primitive::DEFAULT
};
cpu_primitives[fill_prim_id as usize] =
Primitive::new([1.0, 1.0, 1.0, 1.0], [0.0, 0.0], 1, 0.0, 0.0, 1.0);
// create an instance
let instance = wgpu::Instance::new(wgpu::Backends::all());
@ -155,7 +149,7 @@ impl State {
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::default(),
limits
limits,
},
None,
)
@ -174,8 +168,12 @@ impl State {
usage: wgpu::BufferUsages::INDEX,
});
let prim_buffer_byte_size = (PRIM_BUFFER_LEN * std::mem::size_of::<Primitive>()) as u64;
let globals_buffer_byte_size = std::mem::size_of::<Globals>() as u64;
let prim_buffer_byte_size = cmp::max(
MIN_BUFFER_SIZE,
(PRIM_BUFFER_LEN * std::mem::size_of::<Primitive>()) as u64,
);
let globals_buffer_byte_size =
cmp::max(MIN_BUFFER_SIZE, std::mem::size_of::<Globals>() as u64);
let prims_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Prims ubo"),
@ -243,7 +241,7 @@ impl State {
let vertex_module = device.create_shader_module(&create_vertex_module_descriptor());
let fragment_module = device.create_shader_module(&create_fragment_module_descriptor());
let mut render_pipeline_descriptor = create_map_render_pipeline_description(
let render_pipeline_descriptor = create_map_render_pipeline_description(
&pipeline_layout,
create_map_vertex_state(&vertex_module),
create_map_fragment_state(&fragment_module),
@ -406,12 +404,11 @@ impl State {
self.queue.write_buffer(
&self.globals_uniform_buffer,
0,
bytemuck::cast_slice(&[Globals {
resolution: [self.size.width as f32, self.size.height as f32],
zoom: scene.zoom,
scroll_offset: scene.scroll.to_array(),
_pad: 0.0,
}]),
bytemuck::cast_slice(&[Globals::new(
[self.size.width as f32, self.size.height as f32],
scene.scroll.to_array(),
scene.zoom,
)]),
);
self.queue.write_buffer(

View File

@ -25,21 +25,17 @@ pub struct WithId(pub u32);
impl FillVertexConstructor<GpuVertex> for WithId {
fn new_vertex(&mut self, vertex: tessellation::FillVertex) -> GpuVertex {
GpuVertex {
position: vertex.position().to_array(),
normal: [0.0, 0.0],
prim_id: self.0,
}
GpuVertex::new(vertex.position().to_array(), [0.0, 0.0], self.0)
}
}
impl StrokeVertexConstructor<GpuVertex> for WithId {
fn new_vertex(&mut self, vertex: tessellation::StrokeVertex) -> GpuVertex {
GpuVertex {
position: vertex.position_on_path().to_array(),
normal: vertex.normal().to_array(),
prim_id: self.0,
}
GpuVertex::new(
vertex.position_on_path().to_array(),
vertex.normal().to_array(),
self.0,
)
}
}

View File

@ -1,6 +0,0 @@
extern crate console_error_panic_hook;
use std::panic;
pub fn init_console_error_panic_hook() {
panic::set_hook(Box::new(console_error_panic_hook::hook));
}

View File

@ -1,14 +1,43 @@
use wasm_bindgen::prelude::wasm_bindgen;
use log::{Level, warn};
extern crate console_error_panic_hook;
mod console;
use std::panic;
use log::{warn, Level};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsCast;
use web_sys::Window as WebWindow;
use winit::dpi::{LogicalSize, Size};
use winit::event_loop::EventLoop;
use winit::platform::web::WindowBuilderExtWebSys;
use winit::window::{Window, WindowBuilder};
#[wasm_bindgen(start)]
pub fn run() {
console_log::init_with_level(Level::Info).expect("error initializing log");
console::init_console_error_panic_hook();
panic::set_hook(Box::new(console_error_panic_hook::hook));
wasm_bindgen_futures::spawn_local(async {
super::setup().await;
let event_loop = EventLoop::new();
let web_window: WebWindow = web_sys::window().unwrap();
let document = web_window.document().unwrap();
let body = document.body().unwrap();
let builder = WindowBuilder::new();
let canvas: web_sys::HtmlCanvasElement = document
.get_element_by_id("mapr")
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
let window: Window = builder
.with_canvas(Some(canvas))
.build(&event_loop)
.unwrap();
window.set_inner_size(LogicalSize {
width: body.client_width(),
height: body.client_height(),
});
super::setup(window, event_loop).await;
});
}

21
tools/build-web Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
webgl_flag=""
TEMP=$(getopt --long -o "g" "$@")
eval set -- "$TEMP"
while true; do
case "$1" in
-g)
webgl_flag="--features web-webgl"
shift 2
;;
*)
break
;;
esac
done
# shellcheck disable=SC2086
RUSTFLAGS=--cfg=web_sys_unstable_apis cargo build $webgl_flag --target wasm32-unknown-unknown --bin mapr
wasm-bindgen --out-dir web --web target/wasm32-unknown-unknown/debug/mapr.wasm

View File

@ -2,11 +2,12 @@
<head>
<title>mapr Demo</title>
</head>
<body>
<body style="margin: 0; padding: 0;">
<script type="module">
import init from "./mapr.js";
init();
</script>
<canvas id="mapr"></canvas>
</body>
</html>