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

+1 -1
View File
@@ -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 }
+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]
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"