Skip to content
This repository was archived by the owner on Jun 18, 2021. It is now read-only.

Commit c504bbd

Browse files
committed
Add web backend
1 parent 02f7ac9 commit c504bbd

File tree

12 files changed

+1885
-627
lines changed

12 files changed

+1885
-627
lines changed

Cargo.toml

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ default = []
2323
# Make Vulkan backend available on platforms where it is by default not, e.g. macOS
2424
vulkan = ["wgn/vulkan-portability"]
2525

26-
[dependencies.wgn]
26+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgn]
2727
package = "wgpu-native"
2828
version = "0.4"
2929
git = "https://github.com/gfx-rs/wgpu"
3030
rev = "306554600ab7479ec3e54d0c076c71f02474237a"
3131

32-
[dependencies.wgc]
32+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgc]
3333
package = "wgpu-core"
3434
version = "0.1"
3535
git = "https://github.com/gfx-rs/wgpu"
@@ -49,21 +49,110 @@ parking_lot = "0.10"
4949

5050
[dev-dependencies]
5151
cgmath = "0.17"
52-
env_logger = "0.7"
53-
glsl-to-spirv = "0.1"
52+
#glsl-to-spirv = "0.1"
5453
log = "0.4"
5554
png = "0.15"
56-
winit = "0.22"
55+
winit = { version = "0.22", features = ["web-sys"] }
5756
rand = "0.7.2"
5857
zerocopy = "0.3"
5958
futures = "0.3"
6059

61-
#[patch."https://github.com/gfx-rs/wgpu"]
62-
#wgc = { version = "0.1.0", package = "wgpu-core", path = "../wgpu/wgpu-core" }
63-
#wgt = { version = "0.1.0", package = "wgpu-types", path = "../wgpu/wgpu-types" }
64-
#wgn = { version = "0.4.0", package = "wgpu-native", path = "../wgpu/wgpu-native" }
65-
6660
[[example]]
6761
name="hello-compute"
6862
path="examples/hello-compute/main.rs"
6963
test = true
64+
65+
[patch."https://github.com/gfx-rs/wgpu"]
66+
wgc = { version = "0.1.0", package = "wgpu-core", path = "../wgpu/wgpu-core" }
67+
wgt = { version = "0.1.0", package = "wgpu-types", path = "../wgpu/wgpu-types" }
68+
wgn = { version = "0.4.0", package = "wgpu-native", path = "../wgpu/wgpu-native" }
69+
70+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
71+
env_logger = "0.7"
72+
73+
[target.'cfg(target_arch = "wasm32")'.dependencies]
74+
wasm-bindgen = "0.2.59"
75+
web-sys = { version = "0.3.36", features = [
76+
"Document",
77+
"Navigator",
78+
"Node",
79+
"NodeList",
80+
"Gpu",
81+
"GpuAdapter",
82+
"GpuBindGroup",
83+
"GpuBindGroupBinding",
84+
"GpuBindGroupDescriptor",
85+
"GpuBindGroupLayout",
86+
"GpuBindGroupLayoutBinding",
87+
"GpuBindGroupLayoutDescriptor",
88+
"GpuBlendDescriptor",
89+
"GpuBlendFactor",
90+
"GpuBlendOperation",
91+
"GpuBindingType",
92+
"GpuBuffer",
93+
"GpuBufferBinding",
94+
"GpuBufferDescriptor",
95+
"GpuCanvasContext",
96+
"GpuColorDict",
97+
"GpuColorStateDescriptor",
98+
"GpuCommandBuffer",
99+
"GpuCommandBufferDescriptor",
100+
"GpuCommandEncoder",
101+
"GpuCommandEncoderDescriptor",
102+
"GpuCompareFunction",
103+
"GpuComputePassDescriptor",
104+
"GpuComputePassEncoder",
105+
"GpuComputePipeline",
106+
"GpuComputePipelineDescriptor",
107+
"GpuCullMode",
108+
"GpuDepthStencilStateDescriptor",
109+
"GpuDevice",
110+
"GpuDeviceDescriptor",
111+
"GpuExtent3dDict",
112+
"GpuFrontFace",
113+
"GpuIndexFormat",
114+
"GpuInputStepMode",
115+
"GpuLimits",
116+
"GpuLoadOp",
117+
"GpuPipelineLayout",
118+
"GpuPipelineLayoutDescriptor",
119+
"GpuPowerPreference",
120+
"GpuPrimitiveTopology",
121+
"GpuProgrammableStageDescriptor",
122+
"GpuQueue",
123+
"GpuRasterizationStateDescriptor",
124+
"GpuRenderPassColorAttachmentDescriptor",
125+
"GpuRenderPassDepthStencilAttachmentDescriptor",
126+
"GpuRenderPassDescriptor",
127+
"GpuRenderPassEncoder",
128+
"GpuRenderPipeline",
129+
"GpuRenderPipelineDescriptor",
130+
"GpuRequestAdapterOptions",
131+
"GpuSampler",
132+
"GpuShaderModule",
133+
"GpuShaderModuleDescriptor",
134+
"GpuStencilOperation",
135+
"GpuStencilStateFaceDescriptor",
136+
"GpuStoreOp",
137+
"GpuSwapChain",
138+
"GpuSwapChainDescriptor",
139+
"GpuTexture",
140+
"GpuTextureDescriptor",
141+
"GpuTextureDimension",
142+
"GpuTextureFormat",
143+
"GpuTextureViewDimension",
144+
"GpuTextureView",
145+
"GpuVertexAttributeDescriptor",
146+
"GpuVertexBufferLayoutDescriptor",
147+
"GpuVertexFormat",
148+
"GpuVertexStateDescriptor",
149+
"GpuVertexAttributeDescriptor",
150+
"HtmlCanvasElement",
151+
"Window",
152+
]}
153+
js-sys = "0.3.36"
154+
wasm-bindgen-futures = "0.4.9"
155+
156+
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
157+
console_error_panic_hook = "0.1.6"
158+
console_log = "0.1.2"

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ The `hello-triangle` and `hello-compute` examples show bare-bones setup without
3434
cargo run --example hello-compute 1 2 3 4
3535
```
3636

37+
#### Run Examples on the Web (`wasm32-unknown-unknown`)
38+
39+
To run examples on the `wasm32-unknown-unknown` target, first build the example as usual, then run `wasm-bindgen`:
40+
41+
```bash
42+
# Install or update wasm-bindgen-cli
43+
cargo install -f wasm-bindgen-cli
44+
# Build with the wasm target
45+
RUSTFLAGS=--cfg=web_sys_unstable_apis cargo build --example hello-triangle --target wasm32-unknown-unknown
46+
# Generate bindings in a `target/generated` directory
47+
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/hello-triangle.wasm --out-dir target/generated --web
48+
```
49+
3750
## Friends
3851

3952
Shout out to the following projects that work best with wgpu-rs:

examples/framework.rs

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use winit::event::WindowEvent;
1+
use winit::{
2+
event::{self, WindowEvent},
3+
event_loop::{ControlFlow, EventLoop},
4+
window::Window,
5+
};
6+
27

38
#[cfg_attr(rustfmt, rustfmt_skip)]
49
#[allow(unused)]
@@ -24,16 +29,6 @@ pub enum ShaderStage {
2429
Compute,
2530
}
2631

27-
pub fn load_glsl(code: &str, stage: ShaderStage) -> Vec<u32> {
28-
let ty = match stage {
29-
ShaderStage::Vertex => glsl_to_spirv::ShaderType::Vertex,
30-
ShaderStage::Fragment => glsl_to_spirv::ShaderType::Fragment,
31-
ShaderStage::Compute => glsl_to_spirv::ShaderType::Compute,
32-
};
33-
34-
wgpu::read_spirv(glsl_to_spirv::compile(&code, ty).unwrap()).unwrap()
35-
}
36-
3732
pub trait Example: 'static + Sized {
3833
fn init(
3934
sc_desc: &wgpu::SwapChainDescriptor,
@@ -52,51 +47,13 @@ pub trait Example: 'static + Sized {
5247
) -> wgpu::CommandBuffer;
5348
}
5449

55-
async fn run_async<E: Example>(title: &str) {
56-
use winit::{
57-
event,
58-
event_loop::{ControlFlow, EventLoop},
59-
};
50+
async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
51+
log::info!("Initializing the surface...");
6052

61-
env_logger::init();
62-
let event_loop = EventLoop::new();
63-
log::info!("Initializing the window...");
64-
65-
#[cfg(not(feature = "gl"))]
66-
let (window, size, surface) = {
67-
let mut builder = winit::window::WindowBuilder::new();
68-
builder = builder.with_title(title);
69-
#[cfg(windows_OFF)] //TODO
70-
{
71-
use winit::platform::windows::WindowBuilderExtWindows;
72-
builder = builder.with_no_redirection_bitmap(true);
73-
}
74-
let window = builder.build(&event_loop).unwrap();
53+
let (size, surface) = {
7554
let size = window.inner_size();
7655
let surface = wgpu::Surface::create(&window);
77-
(window, size, surface)
78-
};
79-
80-
#[cfg(feature = "gl")]
81-
let (window, instance, size, surface) = {
82-
let wb = winit::WindowBuilder::new();
83-
let cb = wgpu::glutin::ContextBuilder::new().with_vsync(true);
84-
let context = cb.build_windowed(wb, &event_loop).unwrap();
85-
context.window().set_title(title);
86-
87-
let hidpi_factor = context.window().hidpi_factor();
88-
let size = context
89-
.window()
90-
.get_inner_size()
91-
.unwrap()
92-
.to_physical(hidpi_factor);
93-
94-
let (context, window) = unsafe { context.make_current().unwrap().split() };
95-
96-
let instance = wgpu::Instance::new(context);
97-
let surface = instance.get_surface();
98-
99-
(window, instance, size, surface)
56+
(size, surface)
10057
};
10158

10259
let adapter = wgpu::Adapter::request(
@@ -118,7 +75,7 @@ async fn run_async<E: Example>(title: &str) {
11875

11976
let mut sc_desc = wgpu::SwapChainDescriptor {
12077
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
121-
format: wgpu::TextureFormat::Bgra8UnormSrgb,
78+
format: wgpu::TextureFormat::Bgra8Unorm,
12279
width: size.width,
12380
height: size.height,
12481
present_mode: wgpu::PresentMode::Mailbox,
@@ -183,7 +140,33 @@ async fn run_async<E: Example>(title: &str) {
183140
}
184141

185142
pub fn run<E: Example>(title: &str) {
186-
futures::executor::block_on(run_async::<E>(title));
143+
let event_loop = EventLoop::new();
144+
let mut builder = winit::window::WindowBuilder::new();
145+
builder = builder.with_title(title);
146+
#[cfg(windows_OFF)] //TODO
147+
{
148+
use winit::platform::windows::WindowBuilderExtWindows;
149+
builder = builder.with_no_redirection_bitmap(true);
150+
}
151+
let window = builder.build(&event_loop).unwrap();
152+
153+
#[cfg(not(target_arch = "wasm32"))]
154+
{
155+
env_logger::init();
156+
futures::executor::block_on(run_async::<E>(event_loop, window));
157+
}
158+
#[cfg(target_arch = "wasm32")]
159+
{
160+
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
161+
use winit::platform::web::WindowExtWebSys;
162+
// On wasm, append the canvas to the document body
163+
web_sys::window()
164+
.and_then(|win| win.document())
165+
.and_then(|doc| doc.body())
166+
.and_then(|body| body.append_child(&web_sys::Element::from(window.canvas())).ok())
167+
.expect("couldn't append canvas to document body");
168+
wasm_bindgen_futures::spawn_local(run_async::<E>(event_loop, window));
169+
}
187170
}
188171

189172
// This allows treating the framework as a standalone example,

examples/hello-compute/main.rs

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
use std::{convert::TryInto as _, str::FromStr};
21
use zerocopy::AsBytes as _;
2+
use std::{convert::TryInto, str::FromStr};
33

44
async fn run() {
5-
let numbers = if std::env::args().len() == 1 {
5+
let numbers = if std::env::args().len() <= 1 {
66
let default = vec![1, 2, 3, 4];
77
log::info!("No numbers were provided, defaulting to {:?}", default);
88
default
99
} else {
1010
std::env::args()
1111
.skip(1)
12-
.map(|s| u32::from_str(&s).expect("You must pass a list of positive integers!"))
12+
.map(|s| u32::from_str(&s)
13+
.expect("You must pass a list of positive integers!"))
1314
.collect()
1415
};
1516

@@ -122,53 +123,48 @@ async fn execute_gpu(numbers: Vec<u32>) -> Vec<u32> {
122123
}
123124
}
124125

126+
#[cfg(target_arch = "wasm32")]
127+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen(start))]
128+
pub fn wasm_main() {
129+
console_log::init().expect("could not initialize log");
130+
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
131+
wasm_bindgen_futures::spawn_local(run());
132+
}
133+
134+
#[cfg(target_arch = "wasm32")]
125135
fn main() {
126-
env_logger::init();
136+
}
127137

138+
#[cfg(not(target_arch = "wasm32"))]
139+
fn main() {
140+
env_logger::init();
128141
futures::executor::block_on(run());
129142
}
130143

131-
#[cfg(test)]
132-
mod tests {
133-
use super::*;
134-
135-
#[test]
136-
fn test_compute_1(){
137-
let input = vec!(1, 2, 3, 4);
138-
futures::executor::block_on(assert_execute_gpu(input, vec!(0, 1, 7, 2)));
139-
}
140-
141-
#[test]
142-
fn test_compute_2(){
143-
let input = vec!(5, 23, 10, 9);
144-
futures::executor::block_on(assert_execute_gpu(input, vec!(5, 15, 6, 19)));
145-
}
146-
147-
#[test]
148-
fn test_multithreaded_compute() {
149-
use std::sync::mpsc;
150-
use std::thread;
151-
use std::time::Duration;
152-
153-
let thread_count = 8;
154-
155-
let (tx, rx) = mpsc::channel();
156-
for _ in 0 .. thread_count {
157-
let tx = tx.clone();
158-
thread::spawn(move || {
159-
let input = vec![100, 100, 100];
160-
futures::executor::block_on(assert_execute_gpu(input, vec!(25, 25, 25)));
161-
tx.send(true).unwrap();
162-
});
163-
}
164-
165-
for _ in 0 .. thread_count {
166-
rx.recv_timeout(Duration::from_secs(10))
167-
.expect("A thread never completed.");
168-
}
169-
}
144+
#[test]
145+
fn test_multithreaded_compute() {
146+
use std::sync::mpsc;
147+
use std::thread;
148+
use std::time::Duration;
170149

171150
async fn assert_execute_gpu(input: Vec<u32>, expected: Vec<u32>) {
172151
assert_eq!(execute_gpu(input).await, expected);
152+
}
153+
154+
let thread_count = 8;
155+
156+
let (tx, rx) = mpsc::channel();
157+
for _ in 0 .. thread_count {
158+
let tx = tx.clone();
159+
thread::spawn(move || {
160+
let input = vec![100, 100, 100];
161+
futures::executor::block_on(assert_execute_gpu(input, vec!(25, 25, 25)));
162+
tx.send(true).unwrap();
163+
});
164+
}
165+
166+
for _ in 0 .. thread_count {
167+
rx.recv_timeout(Duration::from_secs(10))
168+
.expect("A thread never completed.");
173169
}
174170
}

0 commit comments

Comments
 (0)