Skip to content

Commit

Permalink
Worldspace UI (#23)
Browse files Browse the repository at this point in the history
* WIP: Begin egui-texture skill

* Stuck on actually getting GpuImage

* Need to fix which resources are used when

* Added extract keyword to  in

* Now runs, but warnings about managed textures

* Fixed bug where I was constantly adding render nodes

* Got it working

* Rename to

* Updated docs

* Make it unlit

* Added screenshot

* nit: fix space in readme
  • Loading branch information
TheButlah authored Sep 19, 2023
1 parent 92d9ae8 commit 5d45b1d
Show file tree
Hide file tree
Showing 9 changed files with 589 additions and 1 deletion.
342 changes: 342 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = [
"skills/cube",
"skills/worldspace-ui",
"skills/ik",
"skills/manipulation-flatscreen",
"skills/openxr-6dof",
Expand All @@ -22,6 +23,12 @@ bevy_mod_picking = "0.15.0"
color-eyre = "0.6"
eyre = "0.6"
tracing = "0.1"
bevy_egui = "0.21"
egui-wgpu = "0.22"
egui = "0.22"
bevy-inspector-egui = "0.19"
wgpu = "0.16"
bevy_flycam = { git = "https://github.com/sburris0/bevy_flycam", branch = "bevy_0.11" }

# Enable a small amount of optimization in debug mode
[profile.dev]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Feel free to add to the following lists:
-[Single textured 3d cube](skills/cube)
- 📋 Flycam camera controller
- 📋 First person camera controller (for desktop users)
- 📋 In game UI ( egui, bevyui, whatever )
- [World-space UI](skills/worldspace-ui) (with egui)
- 📋 Entity inspector (`bevy_inspector_egui`)
- 📋 Custom vertex and fragment shader in WGSL + bevy
-[Render a GLTF/VRM model (standard shader)](skills/ik)
Expand Down
16 changes: 16 additions & 0 deletions skills/worldspace-ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "worldspace-ui"
version.workspace = true
license.workspace = true
repository.workspace = true
edition.workspace = true
rust-version.workspace = true

[dependencies]
bevy.workspace = true
bevy_egui.workspace = true
bevy_flycam.workspace = true
color-eyre.workspace = true
egui-wgpu.workspace = true
egui.workspace = true
wgpu.workspace = true
11 changes: 11 additions & 0 deletions skills/worldspace-ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `worldspace-ui`

A cube that renders an [egui](https://www.egui.rs/#demo) application to a texture,
and applies it to a cube.

To run the code:
```bash
cargo run -p worldspace-ui
```

![Screenshot](screenshot.png)
3 changes: 3 additions & 0 deletions skills/worldspace-ui/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 84 additions & 0 deletions skills/worldspace-ui/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
mod render_node;
mod render_systems;

use bevy::prelude::*;
use bevy_egui::egui;
use bevy_egui::EguiContexts;
use color_eyre::Result;

fn main() -> Result<()> {
color_eyre::install()?;
let mut app = App::new();
app.add_plugins(DefaultPlugins)
.add_plugins(bevy_flycam::PlayerPlugin)
.add_plugins(bevy_egui::EguiPlugin)
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.,
})
.add_systems(Startup, setup)
.add_systems(Update, screenspace_ui);

let Ok(render_app) = app.get_sub_app_mut(bevy::render::RenderApp) else {
panic!("The render plugin should have added this subapp");
};
render_app.add_systems(ExtractSchedule, crate::render_systems::add_render_node);

app.run();
Ok(())
}

#[derive(Component, Clone)]
pub struct EguiContext {
output_texture: Handle<Image>,
ctx: egui::Context,
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
) {
let egui_thing = {
let size = wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let mut output_texture = Image {
data: vec![255; (size.width * size.height * 4) as usize],
..default()
};
output_texture.texture_descriptor.usage |=
wgpu::TextureUsages::RENDER_ATTACHMENT;
output_texture.texture_descriptor.size = size;
let output_texture = images.add(output_texture);

EguiContext {
output_texture,
ctx: egui::Context::default(),
}
};

commands.spawn((
PbrBundle {
mesh: meshes.add(shape::Cube::default().into()),
material: materials.add(StandardMaterial {
base_color: Color::WHITE,
base_color_texture: Some(Handle::clone(&egui_thing.output_texture)),
// Remove this if you want it to use the world's lighting.
unlit: true,
..default()
}),
..default()
},
egui_thing,
));
}

fn screenspace_ui(mut contexts: EguiContexts) {
egui::Window::new("Screenspace Window").show(contexts.ctx_mut(), |ui| {
ui.label("I am rendering to the screen!");
});
}
88 changes: 88 additions & 0 deletions skills/worldspace-ui/src/render_node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use bevy::{prelude::*, render::render_asset::RenderAssets};
use std::sync::Mutex;

use crate::EguiContext;

pub struct EguiNode {
pub output_image: Handle<Image>,
pub egui_ctx: EguiContext,
pub renderer: Mutex<egui_wgpu::Renderer>,
}

impl bevy::render::render_graph::Node for EguiNode {
// TODO: When should I use this instead of an Extract render system?
fn update(&mut self, _world: &mut World) {}

fn run(
&self,
_graph: &mut bevy::render::render_graph::RenderGraphContext,
render_context: &mut bevy::render::renderer::RenderContext,
world: &World,
) -> Result<(), bevy::render::render_graph::NodeRunError> {
let device = render_context.render_device().clone();
let device = device.wgpu_device();
let queue = world
.get_resource::<bevy::render::renderer::RenderQueue>()
.unwrap();
let encoder = render_context.command_encoder();
let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap();
let output_gpu_image = gpu_images
.get(&self.output_image)
.expect("Should have been a `GpuImage` that corresponds to the `Image`");
let screen_descriptor = egui_wgpu::renderer::ScreenDescriptor {
pixels_per_point: 1.0,
size_in_pixels: [
output_gpu_image.texture.size().width,
output_gpu_image.texture.size().height,
],
};

let mut renderer = self.renderer.lock().unwrap();

// TODO: Eventually I'll move this to a separate user defined system.
let egui_output = self.egui_ctx.ctx.run(egui::RawInput::default(), |ctx| {
egui::Window::new("Worldspace Window")
.show(ctx, |ui| ui.label("I am rendering to a texture on a cube"));
});
// TODO: Handle textures to delete
for (tid, delta) in egui_output.textures_delta.set.iter() {
renderer.update_texture(device, queue, *tid, delta);
}
let clipped_primitives = self.egui_ctx.ctx.tessellate(egui_output.shapes);

renderer.update_buffers(
device,
queue,
encoder,
&clipped_primitives,
&screen_descriptor,
);

let mut egui_render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Egui Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &output_gpu_image.texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: true,
},
})],
depth_stencil_attachment: None,
});

renderer.render(
&mut egui_render_pass,
&clipped_primitives,
&screen_descriptor,
);

Ok(())
}
}
37 changes: 37 additions & 0 deletions skills/worldspace-ui/src/render_systems.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use bevy::render::Extract;
use bevy::{prelude::*, render::render_graph::RenderGraph};

use crate::render_node::EguiNode;
use crate::EguiContext;

pub fn add_render_node(
q: Extract<Query<(Entity, &EguiContext), Added<EguiContext>>>,
// q: Extract<Query<&EguiContext>>,
textures: Extract<Res<Assets<Image>>>,
mut render_graph: ResMut<RenderGraph>,
device: Res<bevy::render::renderer::RenderDevice>,
) {
for (entity, egui_ctx) in q.iter() {
info!("adding render node for {entity:?}");
let output_texture_format = textures
.get(&egui_ctx.output_texture)
.expect("Should have found the matching `Image`")
.texture_descriptor
.format;
let renderer = egui_wgpu::Renderer::new(
device.wgpu_device(),
output_texture_format,
None,
1,
);
let new_node = EguiNode {
output_image: Handle::clone(&egui_ctx.output_texture),
renderer: renderer.into(),
egui_ctx: egui_ctx.clone(),
};
let node_label = "egui-texture";
render_graph.add_node(node_label, new_node);
render_graph
.add_node_edge(bevy::render::main_graph::node::CAMERA_DRIVER, node_label);
}
}

0 comments on commit 5d45b1d

Please sign in to comment.