Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental WebGL wgpu backend support #1096

Merged
merged 18 commits into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ jobs:
run: cargo build --package tour --target wasm32-unknown-unknown
- name: Check compilation of `todos` example
run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration_wgpu` example
run: cargo build --package integration_wgpu --target wasm32-unknown-unknown
11 changes: 5 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ categories = ["gui"]
resolver = "2"

[features]
default = ["wgpu", "default_system_font"]
default = ["wgpu"]
# Enables the `iced_wgpu` renderer
wgpu = ["iced_wgpu"]
# Enables the `Image` widget
Expand Down Expand Up @@ -58,7 +58,6 @@ members = [
"lazy",
"native",
"style",
"web",
"wgpu",
"winit",
"examples/bezier_tool",
Expand Down Expand Up @@ -94,16 +93,16 @@ members = [
[dependencies]
iced_core = { version = "0.4", path = "core" }
iced_futures = { version = "0.3", path = "futures" }
iced_winit = { version = "0.3", path = "winit" }
iced_glutin = { version = "0.2", path = "glutin", optional = true }
iced_glow = { version = "0.2", path = "glow", optional = true }
thiserror = "1.0"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
iced_winit = { version = "0.3", path = "winit" }
iced_glutin = { version = "0.2", path = "glutin", optional = true }
iced_wgpu = { version = "0.4", path = "wgpu", optional = true }
iced_glow = { version = "0.2", path = "glow", optional = true}

[target.'cfg(target_arch = "wasm32")'.dependencies]
iced_web = { version = "0.4", path = "web" }
iced_wgpu = { version = "0.4", path = "wgpu", features = ["webgl"], optional = true }

[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
Expand Down
3 changes: 3 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ bitflags = "1.2"
[dependencies.palette]
version = "0.5"
optional = true

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-timer = { version = "0.2" }
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
pub mod alignment;
pub mod keyboard;
pub mod mouse;
pub mod time;

mod background;
mod color;
Expand Down
9 changes: 9 additions & 0 deletions core/src/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Keep track of time, both in native and web platforms!

#[cfg(target_arch = "wasm32")]
pub use wasm_timer::Instant;

#[cfg(not(target_arch = "wasm32"))]
pub use std::time::Instant;

pub use std::time::Duration;
2 changes: 2 additions & 0 deletions examples/integration_wgpu/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.wasm
*.js
13 changes: 12 additions & 1 deletion examples/integration_wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,16 @@ publish = false

[dependencies]
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu", features = ["spirv"] }
iced_wgpu = { path = "../../wgpu", features = ["webgl"] }
env_logger = "0.8"

[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
console_log = "0.2.0"
log = "0.4"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] }
# This dependency a little bit quirky, it is deep in the tree and without `js` feature it
# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch
# to make it work
getrandom = { version = "0.2", features = ["js"] }
17 changes: 17 additions & 0 deletions examples/integration_wgpu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,22 @@ You can run it with `cargo run`:
cargo run --package integration
```

### How to run this example with WebGL backend
NOTE: Currently, WebGL backend is is still experimental, so expect bugs.

```sh
# 0. Install prerequisites
cargo install wasm-bindgen-cli https
# 1. cd to the current folder
# 2. Compile wasm module
cargo build -p integration_wgpu --target wasm32-unknown-unknown
# 3. Invoke wasm-bindgen
wasm-bindgen ../../target/wasm32-unknown-unknown/debug/integration_wgpu.wasm --out-dir . --target web --no-typescript
# 4. run http server
http
# 5. Open 127.0.0.1:8000 in browser
```


[`main`]: src/main.rs
[`wgpu`]: https://github.com/gfx-rs/wgpu
21 changes: 21 additions & 0 deletions examples/integration_wgpu/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>Iced - wgpu + WebGL integration</title>
</head>
<body>
<h1>integration_wgpu</h1>
<canvas id="iced_canvas"></canvas>
<script type="module">
import init from "./integration_wgpu.js";
init('./integration_wgpu_bg.wasm');
</script>
<style>
body {
width: 100%;
text-align: center;
}
</style>
</body>
</html>
19 changes: 18 additions & 1 deletion examples/integration_wgpu/src/controls.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
use iced_wgpu::Renderer;
use iced_winit::widget::slider::{self, Slider};
use iced_winit::widget::text_input::{self, TextInput};
use iced_winit::widget::{Column, Row, Text};
use iced_winit::{Alignment, Color, Command, Element, Length, Program};

pub struct Controls {
background_color: Color,
text: String,
sliders: [slider::State; 3],
text_input: text_input::State,
}

#[derive(Debug, Clone)]
pub enum Message {
BackgroundColorChanged(Color),
TextChanged(String),
}

impl Controls {
pub fn new() -> Controls {
Controls {
background_color: Color::BLACK,
text: Default::default(),
sliders: Default::default(),
text_input: Default::default(),
}
}

Expand All @@ -35,14 +41,19 @@ impl Program for Controls {
Message::BackgroundColorChanged(color) => {
self.background_color = color;
}
Message::TextChanged(text) => {
self.text = text;
}
}

Command::none()
}

fn view(&mut self) -> Element<Message, Renderer> {
let [r, g, b] = &mut self.sliders;
let t = &mut self.text_input;
let background_color = self.background_color;
let text = &self.text;

let sliders = Row::new()
.width(Length::Units(500))
Expand Down Expand Up @@ -96,7 +107,13 @@ impl Program for Controls {
Text::new(format!("{:?}", background_color))
.size(14)
.color(Color::WHITE),
),
)
.push(TextInput::new(
t,
"Placeholder",
text,
move |text| Message::TextChanged(text),
)),
),
)
.into()
Expand Down
94 changes: 68 additions & 26 deletions examples/integration_wgpu/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,39 @@ use winit::{
event_loop::{ControlFlow, EventLoop},
};

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use web_sys::HtmlCanvasElement;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowBuilderExtWebSys;

pub fn main() {
#[cfg(target_arch = "wasm32")]
let canvas_element = {
console_log::init_with_level(log::Level::Debug)
.expect("could not initialize logger");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));

web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| doc.get_element_by_id("iced_canvas"))
.and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
.expect("Canvas with id `iced_canvas` is missing")
};
#[cfg(not(target_arch = "wasm32"))]
env_logger::init();

// Initialize winit
let event_loop = EventLoop::new();

#[cfg(target_arch = "wasm32")]
let window = winit::window::WindowBuilder::new()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you can to save some cfg by doing:

let window = if cfg!(target_arch = "wasm32") {
    let canvas_element = {
        console_log::init_with_level(log::Level::Debug)
            .expect("could not initialize logger");
        std::panic::set_hook(Box::new(console_error_panic_hook::hook));

        web_sys::window()
            .and_then(|win| win.document())
            .and_then(|doc| doc.get_element_by_id("iced_canvas"))
            .and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok())
            .expect("Canvas with id `iced_canvas` is missing")
    };
    let window = winit::window::WindowBuilder::new()
        .with_canvas(Some(canvas_element))
        .build(&event_loop)
        .expect("Failed to build winit window")
} else {
    env_logger::init();
    winit::window::Window::new(&event_loop).unwrap()
};

unless the Window::new() method returns separate types

.with_canvas(Some(canvas_element))
.build(&event_loop)
.expect("Failed to build winit window");

#[cfg(not(target_arch = "wasm32"))]
let window = winit::window::Window::new(&event_loop).unwrap();

let physical_size = window.inner_size();
Expand All @@ -31,18 +59,35 @@ pub fn main() {
let mut clipboard = Clipboard::connect(&window);

// Initialize wgpu
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);

#[cfg(target_arch = "wasm32")]
let default_backend = wgpu::Backends::GL;
#[cfg(not(target_arch = "wasm32"))]
let default_backend = wgpu::Backends::PRIMARY;

let backend =
wgpu::util::backend_bits_from_env().unwrap_or(default_backend);

let instance = wgpu::Instance::new(backend);
let surface = unsafe { instance.create_surface(&window) };

let (format, (mut device, queue)) = futures::executor::block_on(async {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.expect("Request adapter");
let adapter = wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend,
Some(&surface),
)
.await
.expect("No suitable GPU adapters found on the system!");

let adapter_features = adapter.features();

#[cfg(target_arch = "wasm32")]
let needed_limits = wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits());

#[cfg(not(target_arch = "wasm32"))]
let needed_limits = wgpu::Limits::default();

(
surface
Expand All @@ -52,8 +97,8 @@ pub fn main() {
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
features: adapter_features & wgpu::Features::default(),
limits: needed_limits,
},
None,
)
Expand All @@ -62,28 +107,25 @@ pub fn main() {
)
});

{
let size = window.inner_size();

surface.configure(
&device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
},
)
};
surface.configure(
&device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: physical_size.width,
height: physical_size.height,
present_mode: wgpu::PresentMode::Mailbox,
},
);

let mut resized = false;

// Initialize staging belt and local pool
let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
let mut local_pool = futures::executor::LocalPool::new();

// Initialize scene and GUI controls
let scene = Scene::new(&mut device);
let scene = Scene::new(&mut device, format);
let controls = Controls::new();

// Initialize iced
Expand Down
23 changes: 14 additions & 9 deletions examples/integration_wgpu/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ pub struct Scene {
}

impl Scene {
pub fn new(device: &wgpu::Device) -> Scene {
let pipeline = build_pipeline(device);
pub fn new(
device: &wgpu::Device,
texture_format: wgpu::TextureFormat,
) -> Scene {
let pipeline = build_pipeline(device, texture_format);

Scene { pipeline }
}
Expand Down Expand Up @@ -47,12 +50,14 @@ impl Scene {
}
}

fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
let vs_module =
device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv"));

let fs_module =
device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv"));
fn build_pipeline(
device: &wgpu::Device,
texture_format: wgpu::TextureFormat,
) -> wgpu::RenderPipeline {
let (vs_module, fs_module) = (
device.create_shader_module(&wgpu::include_wgsl!("shader/vert.wgsl")),
device.create_shader_module(&wgpu::include_wgsl!("shader/frag.wgsl")),
);

let pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
Expand All @@ -74,7 +79,7 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
module: &fs_module,
entry_point: "main",
targets: &[wgpu::ColorTargetState {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
format: texture_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent::REPLACE,
alpha: wgpu::BlendComponent::REPLACE,
Expand Down
Binary file removed examples/integration_wgpu/src/shader/frag.spv
Binary file not shown.
4 changes: 4 additions & 0 deletions examples/integration_wgpu/src/shader/frag.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[stage(fragment)]]
fn main() -> [[location(0)]] vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}
Binary file removed examples/integration_wgpu/src/shader/vert.spv
Binary file not shown.
6 changes: 6 additions & 0 deletions examples/integration_wgpu/src/shader/vert.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[[stage(vertex)]]
fn main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> {
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5;
return vec4<f32>(x, y, 0.0, 1.0);
}
Loading