Skip to content

Commit

Permalink
Implemented Worldspace UI
Browse files Browse the repository at this point in the history
  • Loading branch information
TheButlah committed Jan 9, 2024
1 parent 6f844f0 commit 278bae4
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 128 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ bevy = { version = "0.12", default-features = false, features = [
] }
egui = { version = "0.24.0", default-features = false, features = ["bytemuck"] }
webbrowser = { version = "0.8.2", optional = true }
wgpu-types = "0.17"

[target.'cfg(not(any(target_arch = "wasm32", target_os = "android")))'.dependencies]
arboard = { version = "3.2.0", optional = true }
Expand Down
74 changes: 74 additions & 0 deletions examples/worldspace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use bevy::{
prelude::*,
render::render_resource::{Extent3d, TextureUsages},
};
use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiRenderToTexture};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(EguiPlugin)
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 1.,
})
.add_systems(Startup, setup_worldspace)
// Systems that create Egui widgets should be run during the `CoreSet::Update` set,
// or after the `EguiSet::BeginFrame` system (which belongs to the `CoreSet::PreUpdate` set).
.add_systems(Update, (update_screenspace, update_worldspace))
.run();
}

fn setup_worldspace(
mut images: ResMut<Assets<Image>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut commands: Commands,
) {
let output_texture = images.add({
let size = Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
};
let mut output_texture = Image {
// You should use `0` so that the pixels are transparent.
data: vec![0; (size.width * size.height * 4) as usize],
..default()
};
output_texture.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT;
output_texture.texture_descriptor.size = size;
output_texture
});

commands.spawn(PbrBundle {
mesh: meshes.add(shape::Cube::default().into()),
material: materials.add(StandardMaterial {
base_color: Color::WHITE,
base_color_texture: Some(Handle::clone(&output_texture)),
// Remove this if you want it to use the world's lighting.
unlit: true,
..default()
}),
..default()
});
commands.spawn(EguiRenderToTexture(output_texture));
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::new(0., 0., 0.), Vec3::Y),
..default()
});
}

fn update_screenspace(mut contexts: EguiContexts) {
egui::Window::new("Screenspace UI").show(contexts.ctx_mut(), |ui| {
ui.label("I'm rendering to screenspace!");
});
}

fn update_worldspace(mut contexts: Query<&mut bevy_egui::EguiContext, With<EguiRenderToTexture>>) {
for mut ctx in contexts.iter_mut() {
egui::Window::new("Worldspace UI").show(ctx.get_mut(), |ui| {
ui.label("I'm rendering to a texture in worldspace!");
});
}
}
73 changes: 44 additions & 29 deletions src/egui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use crate::{
render_systems::{
EguiPipelines, EguiTextureBindGroups, EguiTextureId, EguiTransform, EguiTransforms,
},
EguiRenderOutput, EguiSettings, WindowSize,
EguiRenderOutput, EguiRenderToTexture, EguiSettings, WindowSize,
};
use bevy::{
core::cast_slice,
ecs::world::{FromWorld, World},
prelude::{Entity, Handle, Resource},
render::{
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_resource::{
BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType,
Expand Down Expand Up @@ -306,19 +307,29 @@ impl Node for EguiNode {
let pipeline_cache = world.get_resource::<PipelineCache>().unwrap();

let extracted_windows = &world.get_resource::<ExtractedWindows>().unwrap().windows;
let extracted_window =
if let Some(extracted_window) = extracted_windows.get(&self.window_entity) {
extracted_window
} else {
return Ok(()); // No window
};

let swap_chain_texture_view = if let Some(swap_chain_texture_view) =
extracted_window.swap_chain_texture_view.as_ref()
{
swap_chain_texture_view
} else {
return Ok(()); // No swapchain texture
let extracted_window = extracted_windows.get(&self.window_entity);
let extracted_render_to_texture: Option<&EguiRenderToTexture> =
world.get(self.window_entity);
let gpu_image = extracted_render_to_texture.map(|r| {
let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap();
gpu_images.get(&r.0).unwrap()
});

let swap_chain_texture_view = match (extracted_window, gpu_image) {
(Some(w), None) => {
if let Some(tv) = w.swap_chain_texture_view.as_ref() {
tv
} else {
// Nothing to do
return Ok(());
}
}
(None, Some(g)) => {
&g.texture_view
}
// Nothing to do
(None, None) => return Ok(()),
(Some(_), Some(_)) => panic!("RenderWorld should not contain entities with both Window and EguiRenderToTexture components. This restriction might be relaxed in the future"),
};

let render_queue = world.get_resource::<RenderQueue>().unwrap();
Expand Down Expand Up @@ -351,9 +362,7 @@ impl Node for EguiNode {
depth_stencil_attachment: None,
});

let Some(pipeline_id) = egui_pipelines.get(&extracted_window.entity) else {
return Ok(());
};
let pipeline_id = egui_pipelines.get(&self.window_entity).unwrap();
let Some(pipeline) = pipeline_cache.get_render_pipeline(*pipeline_id) else {
return Ok(());
};
Expand All @@ -369,10 +378,18 @@ impl Node for EguiNode {
let transform_buffer_bind_group = &egui_transforms.bind_group.as_ref().unwrap().1;
render_pass.set_bind_group(0, transform_buffer_bind_group, &[transform_buffer_offset]);

let (physical_width, physical_height) = match (extracted_window, gpu_image) {
(Some(w), None) => (w.physical_width, w.physical_height),
(None, Some(g)) => (g.size.x as u32, g.size.y as u32),
(None, None) | (Some(_), Some(_)) => {
unreachable!("At this point, we already checked that this should be impossible")
}
};

let mut vertex_offset: u32 = 0;
for draw_command in &self.draw_commands {
if draw_command.clipping_zone.0 < extracted_window.physical_width
&& draw_command.clipping_zone.1 < extracted_window.physical_height
if draw_command.clipping_zone.0 < physical_width
&& draw_command.clipping_zone.1 < physical_height
{
let texture_bind_group = match bind_groups.get(&draw_command.egui_texture) {
Some(texture_resource) => texture_resource,
Expand All @@ -387,16 +404,14 @@ impl Node for EguiNode {
render_pass.set_scissor_rect(
draw_command.clipping_zone.0,
draw_command.clipping_zone.1,
draw_command.clipping_zone.2.min(
extracted_window
.physical_width
.saturating_sub(draw_command.clipping_zone.0),
),
draw_command.clipping_zone.3.min(
extracted_window
.physical_height
.saturating_sub(draw_command.clipping_zone.1),
),
draw_command
.clipping_zone
.2
.min(physical_width.saturating_sub(draw_command.clipping_zone.0)),
draw_command
.clipping_zone
.3
.min(physical_height.saturating_sub(draw_command.clipping_zone.1)),
);

render_pass.draw_indexed(
Expand Down
Loading

0 comments on commit 278bae4

Please sign in to comment.