diff options
| author | 2023-07-12 12:23:18 -0700 | |
|---|---|---|
| committer | 2023-07-12 12:23:18 -0700 | |
| commit | 633f405f3f78bc7f82d2b2061491b0e011137451 (patch) | |
| tree | 5ebfc1f45d216a5c14a90492563599e6969eab4d /examples/integration | |
| parent | 41836dd80d0534608e7aedfbf2319c540a23de1a (diff) | |
| parent | 21bd51426d900e271206f314e0c915dd41065521 (diff) | |
| download | iced-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/.gitignore | 2 | ||||
| -rw-r--r-- | examples/integration/Cargo.toml | 24 | ||||
| -rw-r--r-- | examples/integration/README.md | 36 | ||||
| -rw-r--r-- | examples/integration/index.html | 21 | ||||
| -rw-r--r-- | examples/integration/src/controls.rs | 113 | ||||
| -rw-r--r-- | examples/integration/src/main.rs | 312 | ||||
| -rw-r--r-- | examples/integration/src/scene.rs | 102 | ||||
| -rw-r--r-- | examples/integration/src/shader/frag.wgsl | 4 | ||||
| -rw-r--r-- | examples/integration/src/shader/vert.wgsl | 6 | 
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); +} | 
