Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,947 changes: 1,876 additions & 71 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ members = [
"examples/fullstack-auth",
"examples/fullstack-websockets",
"examples/wgpu-texture",
"examples/bevy-texture",

# Playwright tests
"packages/playwright-tests/liveview",
Expand Down
20 changes: 20 additions & 0 deletions examples/bevy-texture/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "bevy-texture"
version = "0.0.0"
edition = "2021"
license = "MIT"
publish = false

[features]
default = ["desktop"]
desktop = ["dioxus/desktop"]
native = ["dioxus/native"]
tracing = ["dep:tracing-subscriber", "dioxus-native/tracing"]

[dependencies]
bevy = { version = "0.16" }
dioxus-native = { path = "../../packages/native" }
dioxus = { workspace = true }
wgpu = "24"
color = "0.3"
tracing-subscriber = { workspace = true, optional = true }
186 changes: 186 additions & 0 deletions examples/bevy-texture/src/bevy_renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use std::sync::Arc;

use crate::bevy_scene_plugin::BevyScenePlugin;
use bevy::{
prelude::*,
render::{
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget},
render_resource::TextureFormat,
renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
WgpuWrapper,
},
settings::{RenderCreation, RenderResources},
RenderPlugin,
},
};
use dioxus_native::{CustomPaintCtx, DeviceHandle, TextureHandle};
use wgpu::Instance;

#[derive(Resource, Default)]
pub struct UIData {
pub width: u32,
pub height: u32,
pub color: [f32; 3],
}

pub struct BevyRenderer {
app: App,
wgpu_device: wgpu::Device,
wgpu_queue: wgpu::Queue,
last_texture_size: (u32, u32),
wgpu_texture: Option<wgpu::Texture>,
texture_handle: Option<TextureHandle>,
manual_texture_view_handle: Option<ManualTextureViewHandle>,
}

impl BevyRenderer {
pub fn new(instance: &Instance, device_handle: &DeviceHandle) -> Self {
// Create a headless Bevy App
let mut app = App::new();
app.add_plugins(
DefaultPlugins
.set(RenderPlugin {
render_creation: RenderCreation::Manual(RenderResources(
RenderDevice::new(WgpuWrapper::new(device_handle.device.clone())),
RenderQueue(Arc::new(WgpuWrapper::new(device_handle.queue.clone()))),
RenderAdapterInfo(WgpuWrapper::new(device_handle.adapter.get_info())),
RenderAdapter(Arc::new(WgpuWrapper::new(device_handle.adapter.clone()))),
RenderInstance(Arc::new(WgpuWrapper::new(instance.clone()))),
)),
synchronous_pipeline_compilation: true,
..default()
})
.set(WindowPlugin {
primary_window: None,
exit_condition: bevy::window::ExitCondition::DontExit,
close_when_requested: false,
})
.disable::<bevy::winit::WinitPlugin>(),
);

// Add data from the UI
app.insert_resource(UIData::default());

// Setup the rendering to texture
app.insert_resource(ManualTextureViews::default());

// Add the scene
app.add_plugins(BevyScenePlugin {});

// Initialize the app to set up render world properly
app.finish();
app.cleanup();

Self {
app,
wgpu_device: device_handle.device.clone(),
wgpu_queue: device_handle.queue.clone(),
last_texture_size: (0, 0),
wgpu_texture: None,
texture_handle: None,
manual_texture_view_handle: None,
}
}

pub fn render(
&mut self,
ctx: CustomPaintCtx<'_>,
color: [f32; 3],
width: u32,
height: u32,
_start_time: &std::time::Instant,
) -> Option<TextureHandle> {
// Update the UI data
if let Some(mut ui) = self.app.world_mut().get_resource_mut::<UIData>() {
ui.width = width;
ui.height = height;
ui.color = color;
}

self.create_texture(ctx, width, height);

// Run one frame of the Bevy app to render the 3D scene, and update the texture.
self.app.update();

self.texture_handle
}

fn create_texture(&mut self, mut ctx: CustomPaintCtx<'_>, width: u32, height: u32) {
let current_size = (width, height);
if self.texture_handle.is_some() || self.last_texture_size == current_size {
return;
}

if self
.app
.world_mut()
.query::<&Camera>()
.single(self.app.world_mut())
.ok()
.is_none()
{
return;
}

if let Some(mut manual_texture_views) = self
.app
.world_mut()
.get_resource_mut::<ManualTextureViews>()
{
if let Some(texture_handle) = self.texture_handle {
ctx.unregister_texture(texture_handle);
self.wgpu_texture = None;
self.texture_handle = None;
}

if let Some(old_handle) = self.manual_texture_view_handle {
manual_texture_views.remove(&old_handle);
}

let format = TextureFormat::Rgba8UnormSrgb;
let wgpu_texture = self.wgpu_device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
self.texture_handle = Some(ctx.register_texture(wgpu_texture.clone()));
self.wgpu_texture = Some(wgpu_texture);

let wgpu_texture = self.wgpu_texture.as_ref().unwrap();
let wgpu_texture_view =
wgpu_texture.create_view(&wgpu::TextureViewDescriptor::default());
let manual_texture_view = ManualTextureView {
texture_view: wgpu_texture_view.into(),
size: bevy::math::UVec2::new(width, height),
format,
};
let manual_texture_view_handle = ManualTextureViewHandle(1235078584);
manual_texture_views.insert(manual_texture_view_handle, manual_texture_view);

if let Some(mut camera_query) = self
.app
.world_mut()
.query::<&mut Camera>()
.single_mut(self.app.world_mut())
.ok()
{
camera_query.target = RenderTarget::TextureView(manual_texture_view_handle);
}

self.last_texture_size = current_size;
self.manual_texture_view_handle = Some(manual_texture_view_handle);
}
}
}
77 changes: 77 additions & 0 deletions examples/bevy-texture/src/bevy_scene_plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::bevy_renderer::UIData;
use bevy::prelude::*;

#[derive(Component)]
pub struct DynamicColoredCube;

pub struct BevyScenePlugin {}

impl Plugin for BevyScenePlugin {
fn build(&self, app: &mut App) {
app.insert_resource(ClearColor(bevy::color::Color::srgba(0.0, 0.0, 0.0, 0.0)));
app.add_systems(Startup, setup);
app.add_systems(Update, (animate, update_cube_color));
}
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: bevy::color::Color::srgb(1.0, 0.0, 0.0),
metallic: 0.0,
perceptual_roughness: 0.5,
..default()
})),
Transform::from_xyz(0.0, 0.0, 0.0),
DynamicColoredCube,
));

commands.spawn((
DirectionalLight {
color: bevy::color::Color::WHITE,
illuminance: 10000.0,
shadows_enabled: false,
..default()
},
Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
));

commands.insert_resource(AmbientLight {
color: bevy::color::Color::WHITE,
brightness: 100.0,
affects_lightmapped_meshes: true,
});

commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
Name::new("MainCamera"),
));
}

fn animate(time: Res<Time>, mut cube_query: Query<&mut Transform, With<DynamicColoredCube>>) {
for mut transform in cube_query.iter_mut() {
transform.rotation = Quat::from_rotation_y(time.elapsed_secs());
transform.translation.x = (time.elapsed_secs() * 2.0).sin() * 0.5;
}
}

fn update_cube_color(
ui: Res<UIData>,
cube_query: Query<&MeshMaterial3d<StandardMaterial>, With<DynamicColoredCube>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
if ui.is_changed() {
for mesh_material in cube_query.iter() {
if let Some(material) = materials.get_mut(&mesh_material.0) {
let [r, g, b] = ui.color;
material.base_color = bevy::color::Color::srgb(r, g, b);
}
}
}
}
93 changes: 93 additions & 0 deletions examples/bevy-texture/src/demo_renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::bevy_renderer::BevyRenderer;
use crate::Color;
use dioxus_native::{CustomPaintCtx, CustomPaintSource, DeviceHandle, TextureHandle};
use std::sync::mpsc::{channel, Receiver, Sender};
use wgpu::Instance;

pub enum DemoMessage {
// Color in RGB format
SetColor(Color),
}

enum DemoRendererState {
Active(Box<BevyRenderer>),
Suspended,
}

pub struct DemoPaintSource {
state: DemoRendererState,
start_time: std::time::Instant,
tx: Sender<DemoMessage>,
rx: Receiver<DemoMessage>,
color: Color,
}

impl DemoPaintSource {
pub fn new() -> Self {
let (tx, rx) = channel();
Self::with_channel(tx, rx)
}

pub fn with_channel(tx: Sender<DemoMessage>, rx: Receiver<DemoMessage>) -> Self {
Self {
state: DemoRendererState::Suspended,
start_time: std::time::Instant::now(),
tx,
rx,
color: Color::WHITE,
}
}

pub fn sender(&self) -> Sender<DemoMessage> {
self.tx.clone()
}

fn process_messages(&mut self) {
loop {
match self.rx.try_recv() {
Err(_) => return,
Ok(msg) => match msg {
DemoMessage::SetColor(color) => self.color = color,
},
}
}
}

fn render(
&mut self,
ctx: CustomPaintCtx<'_>,
width: u32,
height: u32,
) -> Option<TextureHandle> {
if width == 0 || height == 0 {
return None;
}
let DemoRendererState::Active(state) = &mut self.state else {
return None;
};

state.render(ctx, self.color.components, width, height, &self.start_time)
}
}

impl CustomPaintSource for DemoPaintSource {
fn resume(&mut self, instance: &Instance, device_handle: &DeviceHandle) {
let active_state = BevyRenderer::new(instance, device_handle);
self.state = DemoRendererState::Active(Box::new(active_state));
}

fn suspend(&mut self) {
self.state = DemoRendererState::Suspended;
}

fn render(
&mut self,
ctx: CustomPaintCtx<'_>,
width: u32,
height: u32,
_scale: f64,
) -> Option<TextureHandle> {
self.process_messages();
self.render(ctx, width, height)
}
}
Loading
Loading