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:
Nico BurnsandJonathan Kelley authored and GitHub committed 2025-07-14 17:48:51 -07:00
1 parent b9b847c3f8
commit 9db593e303
7 files changed
+243 -11

No files matched your search

Generated
+30
View File
@@ -2822,6 +2822,7 @@ dependencies = [
"tracing", "tracing",
"url", "url",
"usvg", "usvg",
"woff",
] ]
[[package]] [[package]]
@@ -10632,6 +10633,25 @@ dependencies = [
"jobserver", "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]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.14" version = "0.2.14"
@@ -19177,6 +19197,16 @@ dependencies = [
"bitflags 2.9.1", "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]] [[package]]
name = "writeable" name = "writeable"
version = "0.5.5" version = "0.5.5"
+10 -1
View File
@@ -114,6 +114,7 @@ members = [
"examples/fullstack-auth", "examples/fullstack-auth",
"examples/fullstack-websockets", "examples/fullstack-websockets",
"examples/wgpu-texture", "examples/wgpu-texture",
"examples/native-headless",
"examples/bevy", "examples/bevy",
# Playwright tests # 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" } depinfo = { path = "packages/depinfo", version = "0.7.0-alpha.2" }
warnings = { version = "0.2.1" } 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! # a fork of pretty please for tests - let's get off of this if we can!
prettier-please = { version = "0.3.0", features = ["verbatim"] } prettier-please = { version = "0.3.0", features = ["verbatim"] }
anyhow = "1.0.98" anyhow = "1.0.98"
@@ -412,7 +421,7 @@ reqwest = { workspace = true, features = ["json"], optional = true }
ciborium = { workspace = true, optional = true } ciborium = { workspace = true, optional = true }
base64 = { workspace = true, optional = true } base64 = { workspace = true, optional = true }
http-range = { version = "0.1.5", 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 } ouroboros = { version = "*", optional = true }
wasm-splitter = { workspace = true, package = "wasm-split" } wasm-splitter = { workspace = true, package = "wasm-split" }
+1 -1
View File
@@ -15,6 +15,6 @@ tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]
bevy = { version = "0.16" } bevy = { version = "0.16" }
dioxus-native = { path = "../../packages/native" } dioxus-native = { path = "../../packages/native" }
dioxus = { workspace = true } dioxus = { workspace = true }
wgpu = "24" wgpu = { workspace = true }
color = "0.3" color = "0.3"
tracing-subscriber = { workspace = true, optional = true } tracing-subscriber = { workspace = true, optional = true }
+24
View File
@@ -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 }
+172
View File
@@ -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 -4
View File
@@ -1,6 +1,3 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: MIT
[package] [package]
name = "wgpu-texture" name = "wgpu-texture"
version = "0.0.0" version = "0.0.0"
@@ -17,7 +14,7 @@ tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]
[dependencies] [dependencies]
dioxus-native = { path = "../../packages/native" } dioxus-native = { path = "../../packages/native" }
dioxus = { workspace = true } dioxus = { workspace = true }
wgpu = "24" wgpu = { workspace = true }
winit = "0.30" winit = "0.30"
bytemuck = "1" bytemuck = "1"
color = "0.3" color = "0.3"
+5 -5
View File
@@ -23,11 +23,11 @@ autofocus = []
# Blitz dependencies # Blitz dependencies
anyrender = { version = "0.4", default-features = false } anyrender = { version = "0.4", default-features = false }
anyrender_vello = { 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-dom = { workspace = true, default-features = false }
blitz-net = { version = "=0.1.0-alpha.5", optional = true } blitz-net = { workspace = true, optional = true }
blitz-paint = { version = "=0.1.0-alpha.5", optional = true } blitz-paint = { workspace = true, optional = true }
blitz-traits = { version = "=0.1.0-alpha.5" } blitz-traits = { workspace = true }
blitz-shell = { version = "=0.1.0-alpha.5", default-features = false } blitz-shell = { workspace = true, default-features = false }
# DioxusLabs dependencies # DioxusLabs dependencies
dioxus-core = { workspace = true } dioxus-core = { workspace = true }