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

Commit

Permalink
Add web backend
Browse files Browse the repository at this point in the history
  • Loading branch information
grovesNL committed Apr 4, 2020
1 parent 02f7ac9 commit c504bbd
Show file tree
Hide file tree
Showing 12 changed files with 1,885 additions and 627 deletions.
109 changes: 99 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ default = []
# Make Vulkan backend available on platforms where it is by default not, e.g. macOS
vulkan = ["wgn/vulkan-portability"]

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

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

[dev-dependencies]
cgmath = "0.17"
env_logger = "0.7"
glsl-to-spirv = "0.1"
#glsl-to-spirv = "0.1"
log = "0.4"
png = "0.15"
winit = "0.22"
winit = { version = "0.22", features = ["web-sys"] }
rand = "0.7.2"
zerocopy = "0.3"
futures = "0.3"

#[patch."https://github.com/gfx-rs/wgpu"]
#wgc = { version = "0.1.0", package = "wgpu-core", path = "../wgpu/wgpu-core" }
#wgt = { version = "0.1.0", package = "wgpu-types", path = "../wgpu/wgpu-types" }
#wgn = { version = "0.4.0", package = "wgpu-native", path = "../wgpu/wgpu-native" }

[[example]]
name="hello-compute"
path="examples/hello-compute/main.rs"
test = true

[patch."https://github.com/gfx-rs/wgpu"]
wgc = { version = "0.1.0", package = "wgpu-core", path = "../wgpu/wgpu-core" }
wgt = { version = "0.1.0", package = "wgpu-types", path = "../wgpu/wgpu-types" }
wgn = { version = "0.4.0", package = "wgpu-native", path = "../wgpu/wgpu-native" }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.7"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2.59"
web-sys = { version = "0.3.36", features = [
"Document",
"Navigator",
"Node",
"NodeList",
"Gpu",
"GpuAdapter",
"GpuBindGroup",
"GpuBindGroupBinding",
"GpuBindGroupDescriptor",
"GpuBindGroupLayout",
"GpuBindGroupLayoutBinding",
"GpuBindGroupLayoutDescriptor",
"GpuBlendDescriptor",
"GpuBlendFactor",
"GpuBlendOperation",
"GpuBindingType",
"GpuBuffer",
"GpuBufferBinding",
"GpuBufferDescriptor",
"GpuCanvasContext",
"GpuColorDict",
"GpuColorStateDescriptor",
"GpuCommandBuffer",
"GpuCommandBufferDescriptor",
"GpuCommandEncoder",
"GpuCommandEncoderDescriptor",
"GpuCompareFunction",
"GpuComputePassDescriptor",
"GpuComputePassEncoder",
"GpuComputePipeline",
"GpuComputePipelineDescriptor",
"GpuCullMode",
"GpuDepthStencilStateDescriptor",
"GpuDevice",
"GpuDeviceDescriptor",
"GpuExtent3dDict",
"GpuFrontFace",
"GpuIndexFormat",
"GpuInputStepMode",
"GpuLimits",
"GpuLoadOp",
"GpuPipelineLayout",
"GpuPipelineLayoutDescriptor",
"GpuPowerPreference",
"GpuPrimitiveTopology",
"GpuProgrammableStageDescriptor",
"GpuQueue",
"GpuRasterizationStateDescriptor",
"GpuRenderPassColorAttachmentDescriptor",
"GpuRenderPassDepthStencilAttachmentDescriptor",
"GpuRenderPassDescriptor",
"GpuRenderPassEncoder",
"GpuRenderPipeline",
"GpuRenderPipelineDescriptor",
"GpuRequestAdapterOptions",
"GpuSampler",
"GpuShaderModule",
"GpuShaderModuleDescriptor",
"GpuStencilOperation",
"GpuStencilStateFaceDescriptor",
"GpuStoreOp",
"GpuSwapChain",
"GpuSwapChainDescriptor",
"GpuTexture",
"GpuTextureDescriptor",
"GpuTextureDimension",
"GpuTextureFormat",
"GpuTextureViewDimension",
"GpuTextureView",
"GpuVertexAttributeDescriptor",
"GpuVertexBufferLayoutDescriptor",
"GpuVertexFormat",
"GpuVertexStateDescriptor",
"GpuVertexAttributeDescriptor",
"HtmlCanvasElement",
"Window",
]}
js-sys = "0.3.36"
wasm-bindgen-futures = "0.4.9"

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
console_error_panic_hook = "0.1.6"
console_log = "0.1.2"
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ The `hello-triangle` and `hello-compute` examples show bare-bones setup without
cargo run --example hello-compute 1 2 3 4
```

#### Run Examples on the Web (`wasm32-unknown-unknown`)

To run examples on the `wasm32-unknown-unknown` target, first build the example as usual, then run `wasm-bindgen`:

```bash
# Install or update wasm-bindgen-cli
cargo install -f wasm-bindgen-cli
# Build with the wasm target
RUSTFLAGS=--cfg=web_sys_unstable_apis cargo build --example hello-triangle --target wasm32-unknown-unknown
# Generate bindings in a `target/generated` directory
wasm-bindgen target/wasm32-unknown-unknown/debug/examples/hello-triangle.wasm --out-dir target/generated --web
```

## Friends

Shout out to the following projects that work best with wgpu-rs:
Expand Down
93 changes: 38 additions & 55 deletions examples/framework.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use winit::event::WindowEvent;
use winit::{
event::{self, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::Window,
};


#[cfg_attr(rustfmt, rustfmt_skip)]
#[allow(unused)]
Expand All @@ -24,16 +29,6 @@ pub enum ShaderStage {
Compute,
}

pub fn load_glsl(code: &str, stage: ShaderStage) -> Vec<u32> {
let ty = match stage {
ShaderStage::Vertex => glsl_to_spirv::ShaderType::Vertex,
ShaderStage::Fragment => glsl_to_spirv::ShaderType::Fragment,
ShaderStage::Compute => glsl_to_spirv::ShaderType::Compute,
};

wgpu::read_spirv(glsl_to_spirv::compile(&code, ty).unwrap()).unwrap()
}

pub trait Example: 'static + Sized {
fn init(
sc_desc: &wgpu::SwapChainDescriptor,
Expand All @@ -52,51 +47,13 @@ pub trait Example: 'static + Sized {
) -> wgpu::CommandBuffer;
}

async fn run_async<E: Example>(title: &str) {
use winit::{
event,
event_loop::{ControlFlow, EventLoop},
};
async fn run_async<E: Example>(event_loop: EventLoop<()>, window: Window) {
log::info!("Initializing the surface...");

env_logger::init();
let event_loop = EventLoop::new();
log::info!("Initializing the window...");

#[cfg(not(feature = "gl"))]
let (window, size, surface) = {
let mut builder = winit::window::WindowBuilder::new();
builder = builder.with_title(title);
#[cfg(windows_OFF)] //TODO
{
use winit::platform::windows::WindowBuilderExtWindows;
builder = builder.with_no_redirection_bitmap(true);
}
let window = builder.build(&event_loop).unwrap();
let (size, surface) = {
let size = window.inner_size();
let surface = wgpu::Surface::create(&window);
(window, size, surface)
};

#[cfg(feature = "gl")]
let (window, instance, size, surface) = {
let wb = winit::WindowBuilder::new();
let cb = wgpu::glutin::ContextBuilder::new().with_vsync(true);
let context = cb.build_windowed(wb, &event_loop).unwrap();
context.window().set_title(title);

let hidpi_factor = context.window().hidpi_factor();
let size = context
.window()
.get_inner_size()
.unwrap()
.to_physical(hidpi_factor);

let (context, window) = unsafe { context.make_current().unwrap().split() };

let instance = wgpu::Instance::new(context);
let surface = instance.get_surface();

(window, instance, size, surface)
(size, surface)
};

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

let mut sc_desc = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
format: wgpu::TextureFormat::Bgra8Unorm,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
Expand Down Expand Up @@ -183,7 +140,33 @@ async fn run_async<E: Example>(title: &str) {
}

pub fn run<E: Example>(title: &str) {
futures::executor::block_on(run_async::<E>(title));
let event_loop = EventLoop::new();
let mut builder = winit::window::WindowBuilder::new();
builder = builder.with_title(title);
#[cfg(windows_OFF)] //TODO
{
use winit::platform::windows::WindowBuilderExtWindows;
builder = builder.with_no_redirection_bitmap(true);
}
let window = builder.build(&event_loop).unwrap();

#[cfg(not(target_arch = "wasm32"))]
{
env_logger::init();
futures::executor::block_on(run_async::<E>(event_loop, window));
}
#[cfg(target_arch = "wasm32")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
use winit::platform::web::WindowExtWebSys;
// On wasm, append the canvas to the document body
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.body())
.and_then(|body| body.append_child(&web_sys::Element::from(window.canvas())).ok())
.expect("couldn't append canvas to document body");
wasm_bindgen_futures::spawn_local(run_async::<E>(event_loop, window));
}
}

// This allows treating the framework as a standalone example,
Expand Down
82 changes: 39 additions & 43 deletions examples/hello-compute/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use std::{convert::TryInto as _, str::FromStr};
use zerocopy::AsBytes as _;
use std::{convert::TryInto, str::FromStr};

async fn run() {
let numbers = if std::env::args().len() == 1 {
let numbers = if std::env::args().len() <= 1 {
let default = vec![1, 2, 3, 4];
log::info!("No numbers were provided, defaulting to {:?}", default);
default
} else {
std::env::args()
.skip(1)
.map(|s| u32::from_str(&s).expect("You must pass a list of positive integers!"))
.map(|s| u32::from_str(&s)
.expect("You must pass a list of positive integers!"))
.collect()
};

Expand Down Expand Up @@ -122,53 +123,48 @@ async fn execute_gpu(numbers: Vec<u32>) -> Vec<u32> {
}
}

#[cfg(target_arch = "wasm32")]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen(start))]
pub fn wasm_main() {
console_log::init().expect("could not initialize log");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
wasm_bindgen_futures::spawn_local(run());
}

#[cfg(target_arch = "wasm32")]
fn main() {
env_logger::init();
}

#[cfg(not(target_arch = "wasm32"))]
fn main() {
env_logger::init();
futures::executor::block_on(run());
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_compute_1(){
let input = vec!(1, 2, 3, 4);
futures::executor::block_on(assert_execute_gpu(input, vec!(0, 1, 7, 2)));
}

#[test]
fn test_compute_2(){
let input = vec!(5, 23, 10, 9);
futures::executor::block_on(assert_execute_gpu(input, vec!(5, 15, 6, 19)));
}

#[test]
fn test_multithreaded_compute() {
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

let thread_count = 8;

let (tx, rx) = mpsc::channel();
for _ in 0 .. thread_count {
let tx = tx.clone();
thread::spawn(move || {
let input = vec![100, 100, 100];
futures::executor::block_on(assert_execute_gpu(input, vec!(25, 25, 25)));
tx.send(true).unwrap();
});
}

for _ in 0 .. thread_count {
rx.recv_timeout(Duration::from_secs(10))
.expect("A thread never completed.");
}
}
#[test]
fn test_multithreaded_compute() {
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

async fn assert_execute_gpu(input: Vec<u32>, expected: Vec<u32>) {
assert_eq!(execute_gpu(input).await, expected);
}

let thread_count = 8;

let (tx, rx) = mpsc::channel();
for _ in 0 .. thread_count {
let tx = tx.clone();
thread::spawn(move || {
let input = vec![100, 100, 100];
futures::executor::block_on(assert_execute_gpu(input, vec!(25, 25, 25)));
tx.send(true).unwrap();
});
}

for _ in 0 .. thread_count {
rx.recv_timeout(Duration::from_secs(10))
.expect("A thread never completed.");
}
}
Loading

0 comments on commit c504bbd

Please sign in to comment.