-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add a bevy-texture example based on wgpu-texture #4360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
nicoburns
merged 11 commits into
DioxusLabs:main
from
jerome-caucat:example-bevy-texture
Jul 4, 2025
Merged
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
8e87b54
Add a bevy-texture example based on wgpu-texture
37b5450
Use git branch of Blitz
nicoburns 1b3ad3e
Update existing code to new blitz
nicoburns af3c810
Use Device from Dioxus for bevy
nicoburns b0969ce
Use ManualTextureView
nicoburns 0a22135
Fix texture size on window resize
cc52cdd
Clean code
f6ec229
Rename example to 'bevy-example' in 'bevy' directory
1ea7ec4
Upgrade to blitz 0.1.0-alpha.5 and anyrender 0.4
3f0bb70
Install alsa in CI
nicoburns 53dab3f
Add libudev to CI
nicoburns File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.