浏览代码

Create example of rendering a dioxus-native app to a texture

Signed-off-by: Nico Burns <nico@nicoburns.com>
Nico Burns 1 周之前
父节点
当前提交
47f76a15f3
共有 4 个文件被更改,包括 234 次插入4 次删除
  1. 34 4
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 27 0
      examples/native-headless/Cargo.toml
  4. 172 0
      examples/native-headless/src/main.rs

+ 34 - 4
Cargo.lock

@@ -471,9 +471,9 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
 
 [[package]]
 name = "anyrender"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baf0dedb4c2bcf3050f37452b11e5f8901ec52d2b4ea9651b67109d6c463575"
+checksum = "26bfc8499b47c819e1f2fdbd04b1b85ca34a6e02eca996a58d750644cd847d0d"
 dependencies = [
  "kurbo",
  "peniko",
@@ -496,9 +496,9 @@ dependencies = [
 
 [[package]]
 name = "anyrender_vello"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8d3eb024ee5a62fd184dabc85cdbd827578941bfe050abe1d3ea3cb2ef50cdd"
+checksum = "7b39c05cc1c08be92d300e09a55cc9d32ffdd5b88a41e55677c75506f35b2180"
 dependencies = [
  "anyrender",
  "futures-intrusive",
@@ -2826,6 +2826,7 @@ dependencies = [
  "tracing",
  "url",
  "usvg",
+ "woff",
 ]
 
 [[package]]
@@ -10627,6 +10628,25 @@ dependencies = [
  "jobserver",
 ]
 
+[[package]]
+name = "native-headless"
+version = "0.0.0"
+dependencies = [
+ "anyrender_vello",
+ "blitz-dom",
+ "blitz-paint",
+ "blitz-traits",
+ "bytemuck",
+ "dioxus",
+ "dioxus-native",
+ "futures-util",
+ "pollster",
+ "rustc-hash 1.1.0",
+ "tracing-subscriber",
+ "vello",
+ "wgpu 24.0.5",
+]
+
 [[package]]
 name = "native-tls"
 version = "0.2.14"
@@ -19229,6 +19249,16 @@ dependencies = [
  "bitflags 2.9.1",
 ]
 
+[[package]]
+name = "woff"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e229335e006d138b1c318e83b99eaa3e1ab03e08c4ced4967e46705e6e9d1cf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
 [[package]]
 name = "writeable"
 version = "0.5.5"

+ 1 - 0
Cargo.toml

@@ -115,6 +115,7 @@ members = [
     "examples/fullstack-auth",
     "examples/fullstack-websockets",
     "examples/wgpu-texture",
+    "examples/native-headless",
     "examples/bevy",
 
     # Playwright tests

+ 27 - 0
examples/native-headless/Cargo.toml

@@ -0,0 +1,27 @@
+# Copyright © SixtyFPS GmbH <info@slint.dev>
+# SPDX-License-Identifier: MIT
+
+[package]
+name = "native-headless"
+version = "0.0.0"
+edition = "2021"
+license = "MIT"
+publish = false
+
+[features]
+tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]
+
+[dependencies]
+dioxus-native = { path = "../../packages/native" }
+anyrender_vello = "0.4.1"
+vello = "0.5"
+rustc-hash = "1.1.0"
+blitz-paint = "0.1.0-alpha.5"
+blitz-traits = "0.1.0-alpha.5"
+blitz-dom = "0.1.0-alpha.5"
+futures-util = "0.3.30"
+dioxus = { workspace = true }
+tracing-subscriber = { workspace = true, optional = true }
+wgpu = "24"
+pollster = "0.4"
+bytemuck = "1"

+ 172 - 0
examples/native-headless/src/main.rs

@@ -0,0 +1,172 @@
+//! A "sketch" of how to integrate a Dioxus Native app to into a wider application
+//! by rendering the UI to a texture and driving it with your own event loop
+//!
+//! (this example is not really intended to be run as-is, and requires you to fill
+//! in the missing pieces)
+use anyrender_vello::{wgpu_context::WGPUContext, VelloScenePainter};
+use blitz_dom::Document as _;
+use blitz_paint::paint_scene;
+use blitz_traits::{
+    events::{BlitzMouseButtonEvent, MouseEventButton, MouseEventButtons, UiEvent},
+    shell::{ColorScheme, Viewport},
+};
+use dioxus::prelude::*;
+use dioxus_native::{CustomPaintSource, DioxusDocument};
+use pollster::FutureExt as _;
+use rustc_hash::FxHashMap;
+use std::sync::Arc;
+use std::task::Context;
+use vello::{
+    peniko::color::AlphaColor, RenderParams, Renderer as VelloRenderer, RendererOptions, Scene,
+};
+use wgpu::TextureFormat;
+
+// Constant width, height, scale factor and color schemefor example purposes
+const SCALE_FACTOR: f32 = 1.0;
+const WIDTH: u32 = 800;
+const HEIGHT: u32 = 600;
+const COLOR_SCHEME: ColorScheme = ColorScheme::Light;
+
+// Example Dioxus app.
+fn app() -> Element {
+    rsx! {
+        div { "Hello, world!" }
+    }
+}
+
+fn main() {
+    #[cfg(feature = "tracing")]
+    tracing_subscriber::fmt::init();
+
+    // =============
+    // INITIAL SETUP
+    // =============
+
+    let waker = create_waker(Box::new(|| {
+        // This should wake up and "poll" your event loop
+    }));
+
+    // Create the dioxus virtual dom and the dioxus-native document
+    // It is important to set the width, height, and scale factor on the document as these are used for layout.
+    let vdom = VirtualDom::new(app);
+    let mut dioxus_doc = DioxusDocument::new(vdom, None);
+    dioxus_doc.set_viewport(Viewport::new(WIDTH, HEIGHT, SCALE_FACTOR, COLOR_SCHEME));
+
+    // Setup a WGPU Device and Queue
+    //
+    // There is nothing special about WGPUContext. It is just used to
+    // reduce the amount of boilerplate associated with setting up WGPU
+    let mut wgpu_context = WGPUContext::new();
+    let device_id = wgpu_context
+        .find_or_create_device(None)
+        .block_on()
+        .expect("Failed to create WGPU device");
+    let device_handle = &wgpu_context.device_pool[device_id];
+    let device = device_handle.device.clone();
+    let queue = device_handle.queue.clone();
+
+    // Create Vello renderer
+    // Note: creating a VelloRenderer is expensive, so it should be done once per Device.
+    let mut vello_renderer = VelloRenderer::new(&device, RendererOptions::default()).unwrap();
+
+    // =============
+    // CREATE TEXTURE (RECREATE ON RESIZE)
+    // =============
+
+    // Create texture and texture view
+    let texture = device.create_texture(&wgpu::TextureDescriptor {
+        label: None,
+        size: wgpu::Extent3d {
+            width: WIDTH,
+            height: HEIGHT,
+            depth_or_array_layers: 1,
+        },
+        mip_level_count: 1,
+        sample_count: 1,
+        dimension: wgpu::TextureDimension::D2,
+        usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
+        format: TextureFormat::Rgba8Unorm,
+        view_formats: &[],
+    });
+    let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+    // =============
+    // EACH FRAME
+    // =============
+
+    // Poll the vdom
+    dioxus_doc.poll(Context::from_waker(&waker));
+
+    // Create a `VelloScenePainter` to paint into
+    let mut custom_paint_sources =
+        FxHashMap::<u64, Box<dyn CustomPaintSource + 'static>>::default();
+    let mut scene_painter = VelloScenePainter {
+        inner: Scene::new(),
+        renderer: &mut vello_renderer,
+        custom_paint_sources: &mut custom_paint_sources,
+    };
+
+    // Paint the document using `blitz_paint::paint_scene`
+    //
+    // Note: the `paint_scene` will work with any implementation of `anyrender::PaintScene`
+    // so you could also write your own implementation if you want more control over rendering
+    // (i.e. to render a custom renderer instead of Vello)
+    paint_scene(
+        &mut scene_painter,
+        &dioxus_doc,
+        SCALE_FACTOR as f64,
+        WIDTH,
+        HEIGHT,
+    );
+
+    // Extract the `vello::Scene` from the `VelloScenePainter`
+    let scene = scene_painter.finish();
+
+    // Render the `vello::Scene` to the Texture using the `VelloRenderer`
+    vello_renderer
+        .render_to_texture(
+            &device,
+            &queue,
+            &scene,
+            &texture_view,
+            &RenderParams {
+                base_color: AlphaColor::TRANSPARENT,
+                width: WIDTH,
+                height: HEIGHT,
+                antialiasing_method: vello::AaConfig::Msaa16,
+            },
+        )
+        .expect("failed to render to texture");
+
+    // `texture` will now contain the rendered Scene
+
+    // =============
+    // EVENT HANDLING
+    // =============
+
+    let event = UiEvent::MouseDown(BlitzMouseButtonEvent {
+        x: 30.0,
+        y: 40.0,
+        button: MouseEventButton::Main,
+        buttons: MouseEventButtons::Primary, // keep track of all pressed buttons
+        mods: Modifiers::empty(),            // ctrl, alt, shift, etc
+    });
+    dioxus_doc.handle_event(event);
+
+    // Trigger a poll via your event loop (or wait for next frame)
+}
+
+/// Create a waker that will call an arbitrary callback
+pub fn create_waker(callback: Box<dyn Fn() + 'static + Send + Sync>) -> std::task::Waker {
+    struct DomHandle {
+        callback: Box<dyn Fn() + 'static + Send + Sync>,
+    }
+
+    impl futures_util::task::ArcWake for DomHandle {
+        fn wake_by_ref(arc_self: &Arc<Self>) {
+            (arc_self.callback)()
+        }
+    }
+
+    futures_util::task::waker(Arc::new(DomHandle { callback }))
+}