Skip to content

Commit

Permalink
Initial WebGL2 support. (#218)
Browse files Browse the repository at this point in the history
Co-authored-by: MarkAnthonyM <[email protected]>
  • Loading branch information
parasyte and MarkAnthonyM authored Nov 16, 2021
1 parent acd4249 commit b185ec3
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 51 deletions.
63 changes: 45 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,42 @@ jobs:
with:
command: check
args: --all
lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Update apt repos
run: sudo apt-get -y update
- name: Install dependencies
run: sudo apt -y install libsdl2-dev
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy, rustfmt
override: true
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Cargo doc
uses: actions-rs/cargo@v1
with:
command: doc
args: --workspace --no-deps
- name: Cargo clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all --tests -- -D warnings
tests:
name: Test
runs-on: ubuntu-latest
needs: [checks, lints]
strategy:
matrix:
rust:
Expand All @@ -59,9 +92,14 @@ jobs:
with:
command: test
args: --all
lints:
name: Lints
wasm:
name: WASM
runs-on: ubuntu-latest
needs: [checks, lints]
strategy:
matrix:
example:
- minimal-web
steps:
- name: Checkout sources
uses: actions/checkout@v2
Expand All @@ -74,20 +112,9 @@ jobs:
with:
profile: minimal
toolchain: stable
components: clippy, rustfmt
target: wasm32-unknown-unknown
override: true
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Cargo doc
uses: actions-rs/cargo@v1
with:
command: doc
args: --workspace --no-deps
- name: Cargo clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all --tests -- -D warnings
- name: Install tools
run: cargo install --locked wasm-bindgen-cli just
- name: Just build
run: just build ${{ matrix.example }}
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ include = [

[dependencies]
bytemuck = "1.7"
pollster = "0.2"
raw-window-handle = "0.3"
thiserror = "1.0"
ultraviolet = "0.8"
wgpu = "0.11"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pollster = "0.2"

[dev-dependencies]
pixels-mocks = { path = "internals/pixels-mocks" }
winit = "0.25"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The Minimum Supported Rust Version for `pixels` will always be made available in
- [Custom Shader](./examples/custom-shader)
- [Dear ImGui example with `winit`](./examples/imgui-winit)
- [Egui example with `winit`](./examples/minimal-egui)
- [Minimal example for WebGL2](./examples/minimal-web)
- [Minimal example with SDL2](./examples/minimal-sdl2)
- [Minimal example with `winit`](./examples/minimal-winit)
- [Minimal example with `fltk`](./examples/minimal-fltk)
Expand Down
30 changes: 30 additions & 0 deletions examples/minimal-web/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "minimal-web"
version = "0.1.0"
authors = ["Jay Oster <[email protected]>"]
edition = "2018"
resolver = "2"
publish = false

[features]
optimize = ["log/release_max_level_warn"]
web = ["wgpu/webgl", "winit/web-sys"]
default = ["optimize"]

[dependencies]
log = "0.4"
pixels = { path = "../.." }
wgpu = "0.11"
winit = "0.25"
winit_input_helper = "0.10"

[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1"
console_log = "0.2"
wasm-bindgen = "0.2.78"
wasm-bindgen-futures = "0.4"
web-sys = "0.3"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.9"
pollster = "0.2"
42 changes: 42 additions & 0 deletions examples/minimal-web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Hello Pixels + Web

![Hello Pixels + Web](../../img/minimal-web.png)

Minimal example for WebGL2.

## Install build dependencies

Install the WASM32 target and a few CLI tools:

```bash
rustup target add wasm32-unknown-unknown
cargo install --locked wasm-bindgen-cli just miniserve
```

## Running on the Web

Build the project and start a local server to host it:

```bash
just serve minimal-web
```

Open http://localhost:8080/ in your browser to run the example.

To build the project without serving it:

```bash
just build minimal-web
```

The build files are stored in `./target/minimal-web/`.

## Running on native targets

```bash
cargo run --release --package minimal-web
```

## About

This example is based on `minimal-winit`, demonstrating how to build your app for WebGL2 targets.
24 changes: 24 additions & 0 deletions examples/minimal-web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style type="text/css">
body {
background-color: #000;
margin: 0;
overflow: hidden;
}
</style>
<title>Hello Pixels + Web</title>
</head>
<body>
<script type="module">
import init from "./minimal-web.js";
window.addEventListener("load", () => {
init();
});
</script>
</body>
</html>
190 changes: 190 additions & 0 deletions examples/minimal-web/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#![deny(clippy::all)]
#![forbid(unsafe_code)]

use log::error;
use pixels::{Pixels, SurfaceTexture};
use std::rc::Rc;
use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use winit_input_helper::WinitInputHelper;

const WIDTH: u32 = 320;
const HEIGHT: u32 = 240;
const BOX_SIZE: i16 = 64;

/// Representation of the application state. In this example, a box will bounce around the screen.
struct World {
box_x: i16,
box_y: i16,
velocity_x: i16,
velocity_y: i16,
}

fn main() {
#[cfg(target_arch = "wasm32")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Trace).expect("error initializing logger");

wasm_bindgen_futures::spawn_local(run());
}

#[cfg(not(target_arch = "wasm32"))]
{
env_logger::init();

pollster::block_on(run());
}
}

async fn run() {
let event_loop = EventLoop::new();
let window = {
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
WindowBuilder::new()
.with_title("Hello Pixels + Web")
.with_inner_size(size)
.with_min_inner_size(size)
.build(&event_loop)
.expect("WindowBuilder error")
};

let window = Rc::new(window);

#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowExtWebSys;

// Retrieve current width and height dimensions of browser client window
let get_window_size = || {
let client_window = web_sys::window().unwrap();
LogicalSize::new(
client_window.inner_width().unwrap().as_f64().unwrap(),
client_window.inner_height().unwrap().as_f64().unwrap(),
)
};

let window = Rc::clone(&window);

// Initialize winit window with current dimensions of browser client
window.set_inner_size(get_window_size());

let client_window = web_sys::window().unwrap();

// Attach winit canvas to body element
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");

// Listen for resize event on browser client. Adjust winit window dimensions
// on event trigger
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| {
let size = get_window_size();
window.set_inner_size(size)
}) as Box<dyn FnMut(_)>);
client_window
.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
.unwrap();
closure.forget();
}

let mut input = WinitInputHelper::new();
let mut pixels = {
let window_size = window.inner_size();
let surface_texture =
SurfaceTexture::new(window_size.width, window_size.height, window.as_ref());
Pixels::new_async(WIDTH, HEIGHT, surface_texture)
.await
.expect("Pixels error")
};
let mut world = World::new();

event_loop.run(move |event, _, control_flow| {
// Draw the current frame
if let Event::RedrawRequested(_) = event {
world.draw(pixels.get_frame());
if pixels
.render()
.map_err(|e| error!("pixels.render() failed: {}", e))
.is_err()
{
*control_flow = ControlFlow::Exit;
return;
}
}

// Handle input events
if input.update(&event) {
// Close events
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
*control_flow = ControlFlow::Exit;
return;
}

// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
}

// Update internal state and request a redraw
world.update();
window.request_redraw();
}
});
}

impl World {
/// Create a new `World` instance that can draw a moving box.
fn new() -> Self {
Self {
box_x: 24,
box_y: 16,
velocity_x: 1,
velocity_y: 1,
}
}

/// Update the `World` internal state; bounce the box around the screen.
fn update(&mut self) {
if self.box_x <= 0 || self.box_x + BOX_SIZE > WIDTH as i16 {
self.velocity_x *= -1;
}
if self.box_y <= 0 || self.box_y + BOX_SIZE > HEIGHT as i16 {
self.velocity_y *= -1;
}

self.box_x += self.velocity_x;
self.box_y += self.velocity_y;
}

/// Draw the `World` state to the frame buffer.
///
/// Assumes the default texture format: `wgpu::TextureFormat::Rgba8UnormSrgb`
fn draw(&self, frame: &mut [u8]) {
for (i, pixel) in frame.chunks_exact_mut(4).enumerate() {
let x = (i % WIDTH as usize) as i16;
let y = (i / WIDTH as usize) as i16;

let inside_the_box = x >= self.box_x
&& x < self.box_x + BOX_SIZE
&& y >= self.box_y
&& y < self.box_y + BOX_SIZE;

let rgba = if inside_the_box {
[0x5e, 0x48, 0xe8, 0xff]
} else {
[0x48, 0xb2, 0xe8, 0xff]
};

pixel.copy_from_slice(&rgba);
}
}
}
Binary file added img/minimal-web.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b185ec3

Please sign in to comment.