summaryrefslogtreecommitdiffstats
path: root/examples/integration
diff options
context:
space:
mode:
authorLibravatar Bingus <shankern@protonmail.com>2023-07-12 12:23:18 -0700
committerLibravatar Bingus <shankern@protonmail.com>2023-07-12 12:23:18 -0700
commit633f405f3f78bc7f82d2b2061491b0e011137451 (patch)
tree5ebfc1f45d216a5c14a90492563599e6969eab4d /examples/integration
parent41836dd80d0534608e7aedfbf2319c540a23de1a (diff)
parent21bd51426d900e271206f314e0c915dd41065521 (diff)
downloadiced-633f405f3f78bc7f82d2b2061491b0e011137451.tar.gz
iced-633f405f3f78bc7f82d2b2061491b0e011137451.tar.bz2
iced-633f405f3f78bc7f82d2b2061491b0e011137451.zip
Merge remote-tracking branch 'origin/master' into feat/multi-window-support
# Conflicts: # Cargo.toml # core/src/window/icon.rs # core/src/window/id.rs # core/src/window/position.rs # core/src/window/settings.rs # examples/integration/src/main.rs # examples/integration_opengl/src/main.rs # glutin/src/application.rs # native/src/subscription.rs # native/src/window.rs # runtime/src/window/action.rs # src/lib.rs # src/window.rs # winit/Cargo.toml # winit/src/application.rs # winit/src/icon.rs # winit/src/settings.rs # winit/src/window.rs
Diffstat (limited to 'examples/integration')
-rw-r--r--examples/integration/.gitignore2
-rw-r--r--examples/integration/Cargo.toml24
-rw-r--r--examples/integration/README.md36
-rw-r--r--examples/integration/index.html21
-rw-r--r--examples/integration/src/controls.rs113
-rw-r--r--examples/integration/src/main.rs312
-rw-r--r--examples/integration/src/scene.rs102
-rw-r--r--examples/integration/src/shader/frag.wgsl4
-rw-r--r--examples/integration/src/shader/vert.wgsl6
9 files changed, 620 insertions, 0 deletions
diff --git a/examples/integration/.gitignore b/examples/integration/.gitignore
new file mode 100644
index 00000000..e188dc28
--- /dev/null
+++ b/examples/integration/.gitignore
@@ -0,0 +1,2 @@
+*.wasm
+*.js
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
new file mode 100644
index 00000000..22914742
--- /dev/null
+++ b/examples/integration/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "integration"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+iced_winit = { path = "../../winit" }
+iced_wgpu = { path = "../../wgpu" }
+iced_widget = { path = "../../widget" }
+iced_renderer = { path = "../../renderer", features = ["wgpu"] }
+env_logger = "0.10"
+
+[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"] }
diff --git a/examples/integration/README.md b/examples/integration/README.md
new file mode 100644
index 00000000..ece9ba1e
--- /dev/null
+++ b/examples/integration/README.md
@@ -0,0 +1,36 @@
+## `wgpu` integration
+
+A demonstration of how to integrate Iced in an existing [`wgpu`] application.
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/nicemediocrekodiakbear">
+ <img src="https://thumbs.gfycat.com/NiceMediocreKodiakbear-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package integration_wgpu
+```
+
+### 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
diff --git a/examples/integration/index.html b/examples/integration/index.html
new file mode 100644
index 00000000..920bc4a0
--- /dev/null
+++ b/examples/integration/index.html
@@ -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.js";
+ init('./integration_bg.wasm');
+ </script>
+ <style>
+ body {
+ width: 100%;
+ text-align: center;
+ }
+ </style>
+ </body>
+</html>
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
new file mode 100644
index 00000000..14e53ede
--- /dev/null
+++ b/examples/integration/src/controls.rs
@@ -0,0 +1,113 @@
+use iced_wgpu::Renderer;
+use iced_widget::{slider, text_input, Column, Row, Text};
+use iced_winit::core::{Alignment, Color, Element, Length};
+use iced_winit::runtime::{Command, Program};
+use iced_winit::style::Theme;
+
+pub struct Controls {
+ background_color: Color,
+ text: String,
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ BackgroundColorChanged(Color),
+ TextChanged(String),
+}
+
+impl Controls {
+ pub fn new() -> Controls {
+ Controls {
+ background_color: Color::BLACK,
+ text: Default::default(),
+ }
+ }
+
+ pub fn background_color(&self) -> Color {
+ self.background_color
+ }
+}
+
+impl Program for Controls {
+ type Renderer = Renderer<Theme>;
+ type Message = Message;
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::BackgroundColorChanged(color) => {
+ self.background_color = color;
+ }
+ Message::TextChanged(text) => {
+ self.text = text;
+ }
+ }
+
+ Command::none()
+ }
+
+ fn view(&self) -> Element<Message, Renderer<Theme>> {
+ let background_color = self.background_color;
+ let text = &self.text;
+
+ let sliders = Row::new()
+ .width(500)
+ .spacing(20)
+ .push(
+ slider(0.0..=1.0, background_color.r, move |r| {
+ Message::BackgroundColorChanged(Color {
+ r,
+ ..background_color
+ })
+ })
+ .step(0.01),
+ )
+ .push(
+ slider(0.0..=1.0, background_color.g, move |g| {
+ Message::BackgroundColorChanged(Color {
+ g,
+ ..background_color
+ })
+ })
+ .step(0.01),
+ )
+ .push(
+ slider(0.0..=1.0, background_color.b, move |b| {
+ Message::BackgroundColorChanged(Color {
+ b,
+ ..background_color
+ })
+ })
+ .step(0.01),
+ );
+
+ Row::new()
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .align_items(Alignment::End)
+ .push(
+ Column::new()
+ .width(Length::Fill)
+ .align_items(Alignment::End)
+ .push(
+ Column::new()
+ .padding(10)
+ .spacing(10)
+ .push(
+ Text::new("Background color")
+ .style(Color::WHITE),
+ )
+ .push(sliders)
+ .push(
+ Text::new(format!("{background_color:?}"))
+ .size(14)
+ .style(Color::WHITE),
+ )
+ .push(
+ text_input("Placeholder", text)
+ .on_input(Message::TextChanged),
+ ),
+ ),
+ )
+ .into()
+ }
+}
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
new file mode 100644
index 00000000..a560959a
--- /dev/null
+++ b/examples/integration/src/main.rs
@@ -0,0 +1,312 @@
+mod controls;
+mod scene;
+
+use controls::Controls;
+use scene::Scene;
+
+use iced_wgpu::graphics::Viewport;
+use iced_wgpu::{wgpu, Backend, Renderer, Settings};
+use iced_winit::core::mouse;
+use iced_winit::core::renderer;
+use iced_winit::core::{Color, Size};
+use iced_winit::runtime::program;
+use iced_winit::runtime::Debug;
+use iced_winit::style::Theme;
+use iced_winit::{conversion, futures, winit, Clipboard};
+
+use winit::{
+ event::{Event, ModifiersState, WindowEvent},
+ 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() -> Result<(), Box<dyn std::error::Error>> {
+ #[cfg(target_arch = "wasm32")]
+ let canvas_element = {
+ console_log::init_with_level(log::Level::Debug)?;
+
+ 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("Get canvas element")
+ };
+
+ #[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()
+ .with_canvas(Some(canvas_element))
+ .build(&event_loop)?;
+
+ #[cfg(not(target_arch = "wasm32"))]
+ let window = winit::window::Window::new(&event_loop)?;
+
+ let physical_size = window.inner_size();
+ let mut viewport = Viewport::with_physical_size(
+ Size::new(physical_size.width, physical_size.height),
+ window.scale_factor(),
+ );
+ let mut cursor_position = None;
+ let mut modifiers = ModifiersState::default();
+ let mut clipboard = Clipboard::connect(&window);
+
+ // Initialize wgpu
+ #[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(wgpu::InstanceDescriptor {
+ backends: backend,
+ ..Default::default()
+ });
+ let surface = unsafe { instance.create_surface(&window) }?;
+
+ let (format, (device, queue)) =
+ futures::futures::executor::block_on(async {
+ let adapter = wgpu::util::initialize_adapter_from_env_or_default(
+ &instance,
+ backend,
+ Some(&surface),
+ )
+ .await
+ .expect("Create adapter");
+
+ 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();
+
+ let capabilities = surface.get_capabilities(&adapter);
+
+ (
+ capabilities
+ .formats
+ .iter()
+ .copied()
+ .find(wgpu::TextureFormat::is_srgb)
+ .or_else(|| capabilities.formats.first().copied())
+ .expect("Get preferred format"),
+ adapter
+ .request_device(
+ &wgpu::DeviceDescriptor {
+ label: None,
+ features: adapter_features
+ & wgpu::Features::default(),
+ limits: needed_limits,
+ },
+ None,
+ )
+ .await
+ .expect("Request device"),
+ )
+ });
+
+ surface.configure(
+ &device,
+ &wgpu::SurfaceConfiguration {
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ format,
+ width: physical_size.width,
+ height: physical_size.height,
+ present_mode: wgpu::PresentMode::AutoVsync,
+ alpha_mode: wgpu::CompositeAlphaMode::Auto,
+ view_formats: vec![],
+ },
+ );
+
+ let mut resized = false;
+
+ // Initialize scene and GUI controls
+ let scene = Scene::new(&device, format);
+ let controls = Controls::new();
+
+ // Initialize iced
+ let mut debug = Debug::new();
+ let mut renderer = Renderer::new(Backend::new(
+ &device,
+ &queue,
+ Settings::default(),
+ format,
+ ));
+
+ let mut state = program::State::new(
+ controls,
+ viewport.logical_size(),
+ &mut renderer,
+ &mut debug,
+ );
+
+ // Run event loop
+ event_loop.run(move |event, _, control_flow| {
+ // You should change this if you want to render continuosly
+ *control_flow = ControlFlow::Wait;
+
+ match event {
+ Event::WindowEvent { event, .. } => {
+ match event {
+ WindowEvent::CursorMoved { position, .. } => {
+ cursor_position = Some(position);
+ }
+ WindowEvent::ModifiersChanged(new_modifiers) => {
+ modifiers = new_modifiers;
+ }
+ WindowEvent::Resized(_) => {
+ resized = true;
+ }
+ WindowEvent::CloseRequested => {
+ *control_flow = ControlFlow::Exit;
+ }
+ _ => {}
+ }
+
+ // Map window event to iced event
+ if let Some(event) = iced_winit::conversion::window_event(
+ window::Id::MAIN,
+ &event,
+ window.scale_factor(),
+ modifiers,
+ ) {
+ state.queue_event(event);
+ }
+ }
+ Event::MainEventsCleared => {
+ // If there are events pending
+ if !state.is_queue_empty() {
+ // We update iced
+ let _ = state.update(
+ viewport.logical_size(),
+ cursor_position
+ .map(|p| {
+ conversion::cursor_position(
+ p,
+ viewport.scale_factor(),
+ )
+ })
+ .map(mouse::Cursor::Available)
+ .unwrap_or(mouse::Cursor::Unavailable),
+ &mut renderer,
+ &Theme::Dark,
+ &renderer::Style {
+ text_color: Color::WHITE,
+ },
+ &mut clipboard,
+ &mut debug,
+ );
+
+ // and request a redraw
+ window.request_redraw();
+ }
+ }
+ Event::RedrawRequested(_) => {
+ if resized {
+ let size = window.inner_size();
+
+ viewport = Viewport::with_physical_size(
+ Size::new(size.width, size.height),
+ window.scale_factor(),
+ );
+
+ surface.configure(
+ &device,
+ &wgpu::SurfaceConfiguration {
+ format,
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ width: size.width,
+ height: size.height,
+ present_mode: wgpu::PresentMode::AutoVsync,
+ alpha_mode: wgpu::CompositeAlphaMode::Auto,
+ view_formats: vec![],
+ },
+ );
+
+ resized = false;
+ }
+
+ match surface.get_current_texture() {
+ Ok(frame) => {
+ let mut encoder = device.create_command_encoder(
+ &wgpu::CommandEncoderDescriptor { label: None },
+ );
+
+ let program = state.program();
+
+ let view = frame.texture.create_view(
+ &wgpu::TextureViewDescriptor::default(),
+ );
+
+ {
+ // We clear the frame
+ let mut render_pass = scene.clear(
+ &view,
+ &mut encoder,
+ program.background_color(),
+ );
+
+ // Draw the scene
+ scene.draw(&mut render_pass);
+ }
+
+ // And then iced on top
+ renderer.with_primitives(|backend, primitive| {
+ backend.present(
+ &device,
+ &queue,
+ &mut encoder,
+ None,
+ &view,
+ primitive,
+ &viewport,
+ &debug.overlay(),
+ );
+ });
+
+ // Then we submit the work
+ queue.submit(Some(encoder.finish()));
+ frame.present();
+
+ // Update the mouse cursor
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ state.mouse_interaction(),
+ ),
+ );
+ }
+ Err(error) => match error {
+ wgpu::SurfaceError::OutOfMemory => {
+ panic!(
+ "Swapchain error: {error}. \
+ Rendering cannot continue."
+ )
+ }
+ _ => {
+ // Try rendering again next frame.
+ window.request_redraw();
+ }
+ },
+ }
+ }
+ _ => {}
+ }
+ })
+}
diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs
new file mode 100644
index 00000000..90c7efbf
--- /dev/null
+++ b/examples/integration/src/scene.rs
@@ -0,0 +1,102 @@
+use iced_wgpu::wgpu;
+use iced_winit::core::Color;
+
+pub struct Scene {
+ pipeline: wgpu::RenderPipeline,
+}
+
+impl Scene {
+ pub fn new(
+ device: &wgpu::Device,
+ texture_format: wgpu::TextureFormat,
+ ) -> Scene {
+ let pipeline = build_pipeline(device, texture_format);
+
+ Scene { pipeline }
+ }
+
+ pub fn clear<'a>(
+ &self,
+ target: &'a wgpu::TextureView,
+ encoder: &'a mut wgpu::CommandEncoder,
+ background_color: Color,
+ ) -> wgpu::RenderPass<'a> {
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: None,
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear({
+ let [r, g, b, a] = background_color.into_linear();
+
+ wgpu::Color {
+ r: r as f64,
+ g: g as f64,
+ b: b as f64,
+ a: a as f64,
+ }
+ }),
+ store: true,
+ },
+ })],
+ depth_stencil_attachment: None,
+ })
+ }
+
+ pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.draw(0..3, 0..1);
+ }
+}
+
+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 {
+ label: None,
+ push_constant_ranges: &[],
+ bind_group_layouts: &[],
+ });
+
+ device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: None,
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &vs_module,
+ entry_point: "main",
+ buffers: &[],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &fs_module,
+ entry_point: "main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format: texture_format,
+ blend: Some(wgpu::BlendState {
+ color: wgpu::BlendComponent::REPLACE,
+ alpha: wgpu::BlendComponent::REPLACE,
+ }),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ front_face: wgpu::FrontFace::Ccw,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ })
+}
diff --git a/examples/integration/src/shader/frag.wgsl b/examples/integration/src/shader/frag.wgsl
new file mode 100644
index 00000000..cf27bb56
--- /dev/null
+++ b/examples/integration/src/shader/frag.wgsl
@@ -0,0 +1,4 @@
+@fragment
+fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 0.0, 0.0, 1.0);
+}
diff --git a/examples/integration/src/shader/vert.wgsl b/examples/integration/src/shader/vert.wgsl
new file mode 100644
index 00000000..e353e6ba
--- /dev/null
+++ b/examples/integration/src/shader/vert.wgsl
@@ -0,0 +1,6 @@
+@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);
+}