use crate::{
EguiContext, EguiSettings, EguiShapes, WindowSize, EGUI_PIPELINE_HANDLE,
EGUI_TEXTURE_RESOURCE_BINDING_NAME, EGUI_TRANSFORM_RESOURCE_BINDING_NAME,
};
use bevy::{
app::{Events, ManualEventReader},
asset::{AssetEvent, Assets, Handle},
core::AsBytes,
ecs::world::World,
log,
render::{
pass::{
ClearColor, LoadOp, Operations, PassDescriptor,
RenderPassDepthStencilAttachmentDescriptor, TextureAttachment,
},
pipeline::{
BindGroupDescriptor, IndexFormat, InputStepMode, PipelineCompiler, PipelineDescriptor,
PipelineLayout, PipelineSpecialization, VertexAttribute, VertexBufferLayout,
VertexFormat,
},
render_graph::{base::Msaa, CommandQueue, Node, ResourceSlotInfo, ResourceSlots},
renderer::{
BindGroup, BufferId, BufferInfo, BufferUsage, RenderContext, RenderResourceBinding,
RenderResourceBindings, RenderResourceContext, RenderResourceType, SamplerId,
TextureId,
},
shader::Shader,
texture::{Extent3d, Texture, TextureDescriptor, TextureDimension, TextureFormat},
},
utils::HashMap,
window::WindowId,
};
use egui::paint::ClippedShape;
use std::{borrow::Cow, collections::HashSet};
pub struct EguiNode {
window_id: WindowId,
pass_descriptor: PassDescriptor,
pipeline_descriptor: Option<Handle<PipelineDescriptor>>,
inputs: Vec<ResourceSlotInfo>,
color_attachment_input_indices: Vec<Option<usize>>,
color_resolve_target_indices: Vec<Option<usize>>,
depth_stencil_attachment_input_index: Option<usize>,
default_clear_color_inputs: Vec<usize>,
transform_bind_group_descriptor: Option<BindGroupDescriptor>,
transform_bind_group: Option<BindGroup>,
egui_texture: Option<Handle<Texture>>,
egui_texture_version: Option<u64>,
texture_bind_group_descriptor: Option<BindGroupDescriptor>,
texture_resources: HashMap<Handle<Texture>, TextureResource>,
vertex_buffer: Option<BufferId>,
index_buffer: Option<BufferId>,
shapes: Vec<ClippedShape>,
render_commands: CommandQueue,
}
#[derive(Debug)]
pub struct TextureResource {
descriptor: TextureDescriptor,
texture: TextureId,
sampler: SamplerId,
bind_group: BindGroup,
}
impl EguiNode {
pub fn new(msaa: &Msaa, window: WindowId) -> Self {
let color_attachments = vec![msaa.color_attachment_descriptor(
TextureAttachment::Input("color_attachment".to_string()),
TextureAttachment::Input("color_resolve_target".to_string()),
Operations {
load: LoadOp::Load,
store: true,
},
)];
let depth_stencil_attachment = RenderPassDepthStencilAttachmentDescriptor {
attachment: TextureAttachment::Input("depth".to_string()),
depth_ops: Some(Operations {
load: LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
};
let mut inputs = Vec::new();
let mut color_attachment_input_indices = Vec::new();
let mut color_resolve_target_indices = Vec::new();
for color_attachment in color_attachments.iter() {
if let TextureAttachment::Input(ref name) = color_attachment.attachment {
color_attachment_input_indices.push(Some(inputs.len()));
inputs.push(ResourceSlotInfo::new(
name.to_string(),
RenderResourceType::Texture,
));
} else {
color_attachment_input_indices.push(None);
}
if let Some(TextureAttachment::Input(ref name)) = color_attachment.resolve_target {
color_resolve_target_indices.push(Some(inputs.len()));
inputs.push(ResourceSlotInfo::new(
name.to_string(),
RenderResourceType::Texture,
));
} else {
color_resolve_target_indices.push(None);
}
}
let mut depth_stencil_attachment_input_index = None;
if let TextureAttachment::Input(ref name) = depth_stencil_attachment.attachment {
depth_stencil_attachment_input_index = Some(inputs.len());
inputs.push(ResourceSlotInfo::new(
name.to_string(),
RenderResourceType::Texture,
));
}
Self {
window_id: window,
pass_descriptor: PassDescriptor {
color_attachments,
depth_stencil_attachment: Some(depth_stencil_attachment),
sample_count: msaa.samples,
},
pipeline_descriptor: None,
default_clear_color_inputs: Vec::new(),
inputs,
depth_stencil_attachment_input_index,
color_attachment_input_indices,
transform_bind_group_descriptor: None,
transform_bind_group: None,
egui_texture: None,
egui_texture_version: None,
texture_bind_group_descriptor: None,
texture_resources: Default::default(),
vertex_buffer: None,
index_buffer: None,
color_resolve_target_indices,
shapes: Vec::new(),
render_commands: Default::default(),
}
}
}
#[derive(Debug)]
struct DrawCommand {
vertices_count: usize,
texture_handle: Option<Handle<Texture>>,
clipping_zone: (u32, u32, u32, u32), }
impl Node for EguiNode {
fn input(&self) -> &[ResourceSlotInfo] {
&self.inputs
}
fn prepare(&mut self, world: &mut World) {
self.init_pipeline(world);
let world = world.cell();
let mut texture_assets = world.get_resource_mut::<Assets<Texture>>().unwrap();
let asset_events = world.get_resource::<Events<AssetEvent<Texture>>>().unwrap();
let mut render_resource_context = world
.get_resource_mut::<Box<dyn RenderResourceContext>>()
.unwrap();
let render_resource_context = &mut **render_resource_context;
let mut egui_context = world.get_resource_mut::<EguiContext>().unwrap();
self.process_asset_events(
render_resource_context,
&mut egui_context,
&asset_events,
&mut texture_assets,
);
self.remove_unused_textures(render_resource_context, &egui_context);
self.init_textures(
render_resource_context,
&mut egui_context,
&mut texture_assets,
);
let mut egui_shapes = world
.get_resource_mut::<HashMap<WindowId, EguiShapes>>()
.unwrap();
let egui_shapes = egui_shapes.get_mut(&self.window_id).unwrap();
self.shapes = std::mem::take(&mut egui_shapes.shapes);
}
fn update(
&mut self,
world: &World,
render_context: &mut dyn RenderContext,
input: &ResourceSlots,
_output: &mut ResourceSlots,
) {
self.render_commands.execute(render_context);
self.process_attachments(input, world);
let window_size = &world
.get_resource::<HashMap<WindowId, WindowSize>>()
.unwrap()[&self.window_id];
let egui_settings = world.get_resource::<EguiSettings>().unwrap();
if window_size.physical_width == 0.0 || window_size.physical_height == 0.0 {
return;
}
let render_resource_bindings = world.get_resource::<RenderResourceBindings>().unwrap();
self.init_transform_bind_group(render_context, render_resource_bindings);
let egui_context = world.get_resource::<EguiContext>().unwrap();
let shapes = std::mem::take(&mut self.shapes);
let egui_paint_jobs = egui_context.ctx().tessellate(shapes);
let mut vertex_buffer = Vec::<u8>::new();
let mut index_buffer = Vec::new();
let mut draw_commands = Vec::new();
let mut index_offset = 0;
let scale_factor = window_size.scale_factor * egui_settings.scale_factor as f32;
for egui::ClippedMesh(rect, triangles) in &egui_paint_jobs {
let (x, y, w, h) = (
(rect.min.x * scale_factor).round() as u32,
(rect.min.y * scale_factor).round() as u32,
(rect.width() * scale_factor).round() as u32,
(rect.height() * scale_factor).round() as u32,
);
if w < 1
|| h < 1
|| x >= window_size.physical_width as u32
|| y >= window_size.physical_height as u32
{
continue;
}
let texture_handle = egui_context
.egui_textures
.get(&triangles.texture_id)
.cloned();
for vertex in &triangles.vertices {
vertex_buffer.extend_from_slice([vertex.pos.x, vertex.pos.y].as_bytes());
vertex_buffer.extend_from_slice([vertex.uv.x, vertex.uv.y].as_bytes());
vertex_buffer.extend_from_slice(
vertex
.color
.to_array()
.iter()
.map(|c| *c as f32)
.collect::<Vec<_>>()
.as_bytes(),
);
}
let indices_with_offset = triangles
.indices
.iter()
.map(|i| i + index_offset)
.collect::<Vec<_>>();
index_buffer.extend_from_slice(indices_with_offset.as_slice().as_bytes());
index_offset += triangles.vertices.len() as u32;
let x_viewport_clamp = (x + w).saturating_sub(window_size.physical_width as u32);
let y_viewport_clamp = (y + h).saturating_sub(window_size.physical_height as u32);
draw_commands.push(DrawCommand {
vertices_count: triangles.indices.len(),
texture_handle,
clipping_zone: (
x,
y,
w.saturating_sub(x_viewport_clamp).max(1),
h.saturating_sub(y_viewport_clamp).max(1),
),
});
}
self.update_buffers(render_context, &vertex_buffer, &index_buffer);
render_context.begin_pass(
&self.pass_descriptor,
render_resource_bindings,
&mut |render_pass| {
render_pass.set_pipeline(self.pipeline_descriptor.as_ref().unwrap());
render_pass.set_vertex_buffer(0, self.vertex_buffer.unwrap(), 0);
render_pass.set_index_buffer(self.index_buffer.unwrap(), 0, IndexFormat::Uint32);
render_pass.set_bind_group(
0,
self.transform_bind_group_descriptor.as_ref().unwrap().id,
self.transform_bind_group.as_ref().unwrap().id,
None,
);
for texture_resource in self.texture_resources.values() {
render_pass.set_bind_group(
1,
self.texture_bind_group_descriptor.as_ref().unwrap().id,
texture_resource.bind_group.id,
None,
);
}
let mut vertex_offset: u32 = 0;
for draw_command in &draw_commands {
let texture_resource = match draw_command
.texture_handle
.as_ref()
.and_then(|texture_handle| self.texture_resources.get(texture_handle))
{
Some(texture_resource) => texture_resource,
None => {
vertex_offset += draw_command.vertices_count as u32;
continue;
}
};
render_pass.set_bind_group(
1,
self.texture_bind_group_descriptor.as_ref().unwrap().id,
texture_resource.bind_group.id,
None,
);
render_pass.set_scissor_rect(
draw_command.clipping_zone.0,
draw_command.clipping_zone.1,
draw_command.clipping_zone.2,
draw_command.clipping_zone.3,
);
render_pass.draw_indexed(
vertex_offset..(vertex_offset + draw_command.vertices_count as u32),
0,
0..1,
);
vertex_offset += draw_command.vertices_count as u32;
}
},
);
}
}
impl EguiNode {
fn process_attachments(&mut self, input: &ResourceSlots, world: &World) {
if let Some(input_index) = self.depth_stencil_attachment_input_index {
self.pass_descriptor
.depth_stencil_attachment
.as_mut()
.unwrap()
.attachment =
TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap());
}
for (i, color_attachment) in self
.pass_descriptor
.color_attachments
.iter_mut()
.enumerate()
{
if self.default_clear_color_inputs.contains(&i) {
if let Some(default_clear_color) = world.get_resource::<ClearColor>() {
color_attachment.ops.load = LoadOp::Clear(default_clear_color.0);
}
}
if let Some(input_index) = self.color_attachment_input_indices[i] {
color_attachment.attachment =
TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap());
}
if let Some(input_index) = self.color_resolve_target_indices[i] {
color_attachment.resolve_target = Some(TextureAttachment::Id(
input.get(input_index).unwrap().get_texture().unwrap(),
));
}
}
}
fn init_pipeline(&mut self, world: &mut World) {
let world = world.cell();
let render_resource_context = world
.get_resource::<Box<dyn RenderResourceContext>>()
.unwrap();
if self.pipeline_descriptor.is_some() {
return;
}
let mut pipelines = world
.get_resource_mut::<Assets<PipelineDescriptor>>()
.unwrap();
let mut shaders = world.get_resource_mut::<Assets<Shader>>().unwrap();
let msaa = world.get_resource::<Msaa>().unwrap();
let pipeline_descriptor_handle = {
let mut pipeline_compiler = world.get_resource_mut::<PipelineCompiler>().unwrap();
let attributes = vec![
VertexAttribute {
name: Cow::from("Vertex_Position"),
offset: 0,
format: VertexFormat::Float2,
shader_location: 0,
},
VertexAttribute {
name: Cow::from("Vertex_Uv"),
offset: VertexFormat::Float2.get_size(),
format: VertexFormat::Float2,
shader_location: 1,
},
VertexAttribute {
name: Cow::from("Vertex_Color"),
offset: VertexFormat::Float2.get_size() + VertexFormat::Float2.get_size(),
format: VertexFormat::Float4,
shader_location: 2,
},
];
pipeline_compiler.compile_pipeline(
&**render_resource_context,
&mut pipelines,
&mut shaders,
&EGUI_PIPELINE_HANDLE.typed(),
&PipelineSpecialization {
vertex_buffer_layout: VertexBufferLayout {
name: Cow::from("EguiVertex"),
stride: attributes
.iter()
.fold(0, |acc, attribute| acc + attribute.format.get_size()),
step_mode: InputStepMode::Vertex,
attributes,
},
sample_count: msaa.samples,
..PipelineSpecialization::default()
},
)
};
let pipeline_descriptor = pipelines.get(pipeline_descriptor_handle.clone()).unwrap();
let layout = pipeline_descriptor.layout.as_ref().unwrap();
let transform_bind_group =
find_bind_group_by_binding_name(layout, EGUI_TRANSFORM_RESOURCE_BINDING_NAME).unwrap();
let texture_bind_group =
find_bind_group_by_binding_name(layout, EGUI_TEXTURE_RESOURCE_BINDING_NAME).unwrap();
self.pipeline_descriptor = Some(pipeline_descriptor_handle);
self.transform_bind_group_descriptor = Some(transform_bind_group);
self.texture_bind_group_descriptor = Some(texture_bind_group);
}
fn init_transform_bind_group(
&mut self,
render_context: &mut dyn RenderContext,
render_resource_bindings: &RenderResourceBindings,
) {
if self.transform_bind_group.is_none() {
let transform_bindings = render_resource_bindings
.get(EGUI_TRANSFORM_RESOURCE_BINDING_NAME)
.unwrap()
.clone();
let transform_bind_group = BindGroup::build()
.add_binding(0, transform_bindings)
.finish();
self.transform_bind_group = Some(transform_bind_group);
}
render_context.resources().create_bind_group(
self.transform_bind_group_descriptor.as_ref().unwrap().id,
self.transform_bind_group.as_ref().unwrap(),
);
}
fn process_asset_events(
&mut self,
render_resource_context: &mut dyn RenderResourceContext,
egui_context: &mut EguiContext,
asset_events: &Events<AssetEvent<Texture>>,
texture_assets: &mut Assets<Texture>,
) {
let mut changed_assets: HashMap<Handle<Texture>, &Texture> = HashMap::default();
let mut asset_event_reader = ManualEventReader::<AssetEvent<Texture>>::default();
for event in asset_event_reader.iter(asset_events) {
let handle = match event {
AssetEvent::Created { ref handle }
| AssetEvent::Modified { ref handle }
| AssetEvent::Removed { ref handle } => handle,
};
if !self.texture_resources.contains_key(handle)
|| self.egui_texture.as_ref() == Some(handle)
{
continue;
}
log::debug!("{:?}", event);
match event {
AssetEvent::Created { .. } => {
}
AssetEvent::Modified { ref handle } => {
if let Some(asset) = texture_assets.get(handle) {
changed_assets.insert(handle.clone(), asset);
}
}
AssetEvent::Removed { ref handle } => {
egui_context.remove_texture(handle);
self.remove_texture(render_resource_context, handle);
changed_assets.remove(handle);
}
}
}
for (texture_handle, texture) in changed_assets {
self.update_texture(render_resource_context, texture, texture_handle);
}
let egui_texture = egui_context.ctx_for_window(self.window_id).texture();
if self.egui_texture_version != Some(egui_texture.version) {
self.egui_texture_version = Some(egui_texture.version);
if let Some(egui_texture_handle) = self.egui_texture.clone() {
let texture_asset = texture_assets.get_mut(&egui_texture_handle).unwrap();
*texture_asset = as_bevy_texture(&egui_texture);
self.update_texture(render_resource_context, texture_asset, egui_texture_handle);
}
}
}
fn remove_unused_textures(
&mut self,
render_resource_context: &mut dyn RenderResourceContext,
egui_context: &EguiContext,
) {
let texture_handles = egui_context.egui_textures.values().collect::<HashSet<_>>();
let mut textures_to_remove = Vec::new();
for texture_handle in self.texture_resources.keys() {
if !texture_handles.contains(texture_handle)
&& self.egui_texture.as_ref().unwrap() != texture_handle
{
textures_to_remove.push(texture_handle.clone_weak());
}
}
for texture_to_remove in textures_to_remove {
self.remove_texture(render_resource_context, &texture_to_remove);
}
}
fn init_textures(
&mut self,
render_resource_context: &mut dyn RenderResourceContext,
egui_context: &mut EguiContext,
texture_assets: &mut Assets<Texture>,
) {
if self.egui_texture.is_none() {
let texture = egui_context.ctx_for_window(self.window_id).texture();
self.egui_texture = Some(texture_assets.add(as_bevy_texture(&texture)));
self.egui_texture_version = Some(texture.version);
egui_context.egui_textures.insert(
egui::TextureId::Egui,
self.egui_texture.as_ref().unwrap().clone_weak(),
);
}
for texture in egui_context.egui_textures.values() {
self.create_texture(
render_resource_context,
texture_assets,
texture.clone_weak(),
);
}
}
fn update_texture(
&mut self,
render_resource_context: &mut dyn RenderResourceContext,
texture_asset: &Texture,
texture_handle: Handle<Texture>,
) {
let texture_resource = match self.texture_resources.get(&texture_handle) {
Some(texture_resource) => texture_resource,
None => return,
};
log::debug!("Updating a texture: ${:?}", texture_handle);
let texture_descriptor: TextureDescriptor = texture_asset.into();
if texture_descriptor != texture_resource.descriptor {
log::debug!(
"Removing an updated texture for it to be re-created later: {:?}",
texture_handle
);
self.remove_texture(render_resource_context, &texture_handle);
return;
}
Self::copy_texture(
&mut self.render_commands,
render_resource_context,
texture_resource,
texture_asset,
);
}
fn create_texture(
&mut self,
render_resource_context: &mut dyn RenderResourceContext,
texture_assets: &Assets<Texture>,
texture_handle: Handle<Texture>,
) {
if let Some(texture_resource) = self.texture_resources.get(&texture_handle) {
render_resource_context.create_bind_group(
self.texture_bind_group_descriptor.as_ref().unwrap().id,
&texture_resource.bind_group,
);
return;
}
let texture_asset = match texture_assets.get(texture_handle.clone()) {
Some(texture_asset) => texture_asset,
None => return,
};
log::debug!("Creating a texture: ${:?}", texture_handle);
let texture_descriptor: TextureDescriptor = texture_asset.into();
let texture = render_resource_context.create_texture(texture_descriptor);
let sampler = render_resource_context.create_sampler(&texture_asset.sampler);
let texture_bind_group = BindGroup::build()
.add_binding(0, RenderResourceBinding::Texture(texture))
.add_binding(1, RenderResourceBinding::Sampler(sampler))
.finish();
render_resource_context.create_bind_group(
self.texture_bind_group_descriptor.as_ref().unwrap().id,
&texture_bind_group,
);
let texture_resource = TextureResource {
descriptor: texture_descriptor,
texture,
sampler,
bind_group: texture_bind_group,
};
Self::copy_texture(
&mut self.render_commands,
render_resource_context,
&texture_resource,
texture_asset,
);
log::debug!("Texture created: {:?}", texture_resource);
self.texture_resources
.insert(texture_handle, texture_resource);
}
fn remove_texture(
&mut self,
render_resource_context: &mut dyn RenderResourceContext,
texture_handle: &Handle<Texture>,
) {
let texture_resource = match self.texture_resources.remove(texture_handle) {
Some(texture_resource) => texture_resource,
None => return,
};
log::debug!("Removing a texture: ${:?}", texture_handle);
render_resource_context.remove_texture(texture_resource.texture);
render_resource_context.remove_sampler(texture_resource.sampler);
}
fn copy_texture(
render_commands: &mut CommandQueue,
render_resource_context: &mut dyn RenderResourceContext,
texture_resource: &TextureResource,
texture: &Texture,
) {
let width = texture.size.width as usize;
let aligned_width = render_resource_context.get_aligned_texture_size(width);
let format_size = texture.format.pixel_size();
let mut aligned_data = vec![
0;
format_size
* aligned_width
* texture.size.height as usize
* texture.size.depth as usize
];
texture
.data
.chunks_exact(format_size * width)
.enumerate()
.for_each(|(index, row)| {
let offset = index * aligned_width * format_size;
aligned_data[offset..(offset + width * format_size)].copy_from_slice(row);
});
let texture_buffer = render_resource_context.create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::COPY_SRC,
..Default::default()
},
&aligned_data,
);
render_commands.copy_buffer_to_texture(
texture_buffer,
0,
(format_size * aligned_width) as u32,
texture_resource.texture,
[0, 0, 0],
0,
texture_resource.descriptor.size,
);
render_commands.free_buffer(texture_buffer);
}
fn update_buffers(
&mut self,
render_context: &mut dyn RenderContext,
vertex_buffer: &[u8],
index_buffer: &[u8],
) {
if let Some(vertex_buffer) = self.vertex_buffer.take() {
render_context.resources().remove_buffer(vertex_buffer);
}
if let Some(index_buffer) = self.index_buffer.take() {
render_context.resources().remove_buffer(index_buffer);
}
self.vertex_buffer = Some(render_context.resources().create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::VERTEX,
..Default::default()
},
vertex_buffer,
));
self.index_buffer = Some(render_context.resources().create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::INDEX,
..Default::default()
},
index_buffer,
));
}
}
fn find_bind_group_by_binding_name(
pipeline_layout: &PipelineLayout,
binding_name: &str,
) -> Option<BindGroupDescriptor> {
pipeline_layout
.bind_groups
.iter()
.find(|bind_group| {
bind_group
.bindings
.iter()
.any(|binding| binding.name == binding_name)
})
.cloned()
}
fn as_bevy_texture(egui_texture: &egui::Texture) -> Texture {
let mut pixels = Vec::new();
pixels.reserve(4 * pixels.len());
for &alpha in egui_texture.pixels.iter() {
pixels.extend(
egui::color::Color32::from_white_alpha(alpha)
.to_array()
.iter(),
);
}
Texture::new(
Extent3d::new(egui_texture.width as u32, egui_texture.height as u32, 1),
TextureDimension::D2,
pixels,
TextureFormat::Rgba8UnormSrgb,
)
}