Example of rendering a dioxus-native app to a texture (#4365)
* Create example of rendering a dioxus-native app to a texture Signed-off-by: Nico Burns <nico@nicoburns.com> * Use dioxus-native-dom crate Signed-off-by: Nico Burns <nico@nicoburns.com> * bump lock * cleanup cargo tomls * rustc hash --------- Signed-off-by: Nico Burns <nico@nicoburns.com> Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
b9b847c3f8
commit
9db593e303
7 files changed
+243
-11
No files matched your search
Generated
+30
@@ -2822,6 +2822,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"usvg",
|
||||
"woff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10632,6 +10633,25 @@ dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-headless"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyrender_vello",
|
||||
"blitz-dom",
|
||||
"blitz-paint",
|
||||
"blitz-traits",
|
||||
"bytemuck",
|
||||
"dioxus",
|
||||
"dioxus-native-dom",
|
||||
"futures-util",
|
||||
"pollster",
|
||||
"rustc-hash 1.1.0",
|
||||
"tracing-subscriber",
|
||||
"vello",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
@@ -19177,6 +19197,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"
|
||||
|
||||
+10
-1
@@ -114,6 +114,7 @@ members = [
|
||||
"examples/fullstack-auth",
|
||||
"examples/fullstack-websockets",
|
||||
"examples/wgpu-texture",
|
||||
"examples/native-headless",
|
||||
"examples/bevy",
|
||||
|
||||
# Playwright tests
|
||||
@@ -201,6 +202,14 @@ wasm-used = { path = "packages/wasm-split/wasm-used", version = "0.7.0-alpha.2"
|
||||
depinfo = { path = "packages/depinfo", version = "0.7.0-alpha.2" }
|
||||
warnings = { version = "0.2.1" }
|
||||
|
||||
# blitz
|
||||
blitz-dom = { version = "=0.1.0-alpha.5", default-features = false }
|
||||
blitz-net = { version = "=0.1.0-alpha.5" }
|
||||
blitz-paint = { version = "=0.1.0-alpha.5" }
|
||||
blitz-traits = { version = "=0.1.0-alpha.5" }
|
||||
blitz-shell = { version = "=0.1.0-alpha.5", default-features = false }
|
||||
wgpu = { version = "24.0" }
|
||||
|
||||
# a fork of pretty please for tests - let's get off of this if we can!
|
||||
prettier-please = { version = "0.3.0", features = ["verbatim"] }
|
||||
anyhow = "1.0.98"
|
||||
@@ -412,7 +421,7 @@ reqwest = { workspace = true, features = ["json"], optional = true }
|
||||
ciborium = { workspace = true, optional = true }
|
||||
base64 = { workspace = true, optional = true }
|
||||
http-range = { version = "0.1.5", optional = true }
|
||||
wgpu = { version = "24.0", optional = true }
|
||||
wgpu = { workspace = true, optional = true }
|
||||
ouroboros = { version = "*", optional = true }
|
||||
wasm-splitter = { workspace = true, package = "wasm-split" }
|
||||
|
||||
|
||||
@@ -15,6 +15,6 @@ tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]
|
||||
bevy = { version = "0.16" }
|
||||
dioxus-native = { path = "../../packages/native" }
|
||||
dioxus = { workspace = true }
|
||||
wgpu = "24"
|
||||
wgpu = { workspace = true }
|
||||
color = "0.3"
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "native-headless"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
tracing = ["dep:tracing-subscriber", "dioxus-native-dom/tracing"]
|
||||
|
||||
[dependencies]
|
||||
dioxus = { workspace = true, default-features = false, features = ["html", "hooks", "signals"] }
|
||||
dioxus-native-dom = { path = "../../packages/native-dom" }
|
||||
anyrender_vello = "0.4.1"
|
||||
vello = "0.5"
|
||||
rustc-hash = "1"
|
||||
futures-util = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
wgpu = { workspace = true }
|
||||
pollster = "0.4"
|
||||
bytemuck = "1"
|
||||
blitz-paint = { workspace = true, default-features = true }
|
||||
blitz-traits = { workspace = true, default-features = true }
|
||||
blitz-dom = { workspace = true, default-features = true }
|
||||
@@ -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, CustomPaintSource, 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_dom::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 }))
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
[package]
|
||||
name = "wgpu-texture"
|
||||
version = "0.0.0"
|
||||
@@ -17,7 +14,7 @@ tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]
|
||||
[dependencies]
|
||||
dioxus-native = { path = "../../packages/native" }
|
||||
dioxus = { workspace = true }
|
||||
wgpu = "24"
|
||||
wgpu = { workspace = true }
|
||||
winit = "0.30"
|
||||
bytemuck = "1"
|
||||
color = "0.3"
|
||||
|
||||
@@ -23,11 +23,11 @@ autofocus = []
|
||||
# Blitz dependencies
|
||||
anyrender = { version = "0.4", default-features = false }
|
||||
anyrender_vello = { version = "0.4", default-features = false }
|
||||
blitz-dom = { version = "=0.1.0-alpha.5", default-features = false }
|
||||
blitz-net = { version = "=0.1.0-alpha.5", optional = true }
|
||||
blitz-paint = { version = "=0.1.0-alpha.5", optional = true }
|
||||
blitz-traits = { version = "=0.1.0-alpha.5" }
|
||||
blitz-shell = { version = "=0.1.0-alpha.5", default-features = false }
|
||||
blitz-dom = { workspace = true, default-features = false }
|
||||
blitz-net = { workspace = true, optional = true }
|
||||
blitz-paint = { workspace = true, optional = true }
|
||||
blitz-traits = { workspace = true }
|
||||
blitz-shell = { workspace = true, default-features = false }
|
||||
|
||||
# DioxusLabs dependencies
|
||||
dioxus-core = { workspace = true }
|
||||
|
||||
Reference in new issue
Block a user