use crate::{
egui_node::{EguiNode, EguiPipeline, EguiPipelineKey},
EguiManagedTextures, EguiSettings, EguiUserTextures, WindowSize,
};
use bevy::{
ecs::system::SystemParam,
prelude::*,
render::{
extract_resource::ExtractResource,
render_asset::RenderAssets,
render_graph::{RenderGraph, RenderLabel},
render_resource::{
BindGroup, BindGroupEntry, BindingResource, BufferId, CachedRenderPipelineId,
DynamicUniformBuffer, PipelineCache, ShaderType, SpecializedRenderPipelines,
},
renderer::{RenderDevice, RenderQueue},
texture::Image,
view::ExtractedWindows,
Extract,
},
utils::HashMap,
};
#[derive(Resource, Deref, DerefMut, Default)]
pub struct ExtractedEguiSettings(pub EguiSettings);
#[derive(Debug, Resource)]
pub struct ExtractedEguiManagedTextures(pub HashMap<(Entity, u64), Handle<Image>>);
impl ExtractResource for ExtractedEguiManagedTextures {
type Source = EguiManagedTextures;
fn extract_resource(source: &Self::Source) -> Self {
Self(source.iter().map(|(k, v)| (*k, v.handle.clone())).collect())
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum EguiTextureId {
Managed(Entity, u64),
User(u64),
}
#[derive(SystemParam)]
pub struct ExtractedEguiTextures<'w> {
pub egui_textures: Res<'w, ExtractedEguiManagedTextures>,
pub user_textures: Res<'w, EguiUserTextures>,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct EguiPass {
pub window_index: u32,
pub window_generation: u32,
}
impl ExtractedEguiTextures<'_> {
pub fn handles(&self) -> impl Iterator<Item = (EguiTextureId, AssetId<Image>)> + '_ {
self.egui_textures
.0
.iter()
.map(|(&(window, texture_id), managed_tex)| {
(EguiTextureId::Managed(window, texture_id), managed_tex.id())
})
.chain(
self.user_textures
.textures
.iter()
.map(|(handle, id)| (EguiTextureId::User(*id), handle.id())),
)
}
}
pub fn setup_new_windows_render_system(
windows: Extract<Query<Entity, Added<Window>>>,
mut render_graph: ResMut<RenderGraph>,
) {
for window in windows.iter() {
let egui_pass = EguiPass {
window_index: window.index(),
window_generation: window.generation(),
};
let new_node = EguiNode::new(window);
render_graph.add_node(egui_pass.clone(), new_node);
render_graph.add_node_edge(bevy::render::graph::CameraDriverLabel, egui_pass);
}
}
#[derive(Resource, Default)]
pub struct EguiTransforms {
pub buffer: DynamicUniformBuffer<EguiTransform>,
pub offsets: HashMap<Entity, u32>,
pub bind_group: Option<(BufferId, BindGroup)>,
}
#[derive(ShaderType, Default)]
pub struct EguiTransform {
pub scale: Vec2,
pub translation: Vec2,
}
impl EguiTransform {
pub fn from_window_size(window_size: WindowSize, scale_factor: f32) -> Self {
EguiTransform {
scale: Vec2::new(
2.0 / (window_size.width() / scale_factor),
-2.0 / (window_size.height() / scale_factor),
),
translation: Vec2::new(-1.0, 1.0),
}
}
}
pub fn prepare_egui_transforms_system(
mut egui_transforms: ResMut<EguiTransforms>,
window_sizes: Query<(Entity, &WindowSize)>,
egui_settings: Res<EguiSettings>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
egui_pipeline: Res<EguiPipeline>,
) {
egui_transforms.buffer.clear();
egui_transforms.offsets.clear();
for (window, size) in window_sizes.iter() {
let offset = egui_transforms
.buffer
.push(&EguiTransform::from_window_size(
*size,
egui_settings.scale_factor,
));
egui_transforms.offsets.insert(window, offset);
}
egui_transforms
.buffer
.write_buffer(&render_device, &render_queue);
if let Some(buffer) = egui_transforms.buffer.buffer() {
match egui_transforms.bind_group {
Some((id, _)) if buffer.id() == id => {}
_ => {
let transform_bind_group = render_device.create_bind_group(
Some("egui transform bind group"),
&egui_pipeline.transform_bind_group_layout,
&[BindGroupEntry {
binding: 0,
resource: egui_transforms.buffer.binding().unwrap(),
}],
);
egui_transforms.bind_group = Some((buffer.id(), transform_bind_group));
}
};
}
}
#[derive(Resource, Deref, DerefMut, Default)]
pub struct EguiTextureBindGroups(pub HashMap<EguiTextureId, BindGroup>);
pub fn queue_bind_groups_system(
mut commands: Commands,
egui_textures: ExtractedEguiTextures,
render_device: Res<RenderDevice>,
gpu_images: Res<RenderAssets<Image>>,
egui_pipeline: Res<EguiPipeline>,
) {
let bind_groups = egui_textures
.handles()
.filter_map(|(texture, handle_id)| {
let gpu_image = gpu_images.get(&Handle::Weak(handle_id))?;
let bind_group = render_device.create_bind_group(
None,
&egui_pipeline.texture_bind_group_layout,
&[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&gpu_image.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
);
Some((texture, bind_group))
})
.collect();
commands.insert_resource(EguiTextureBindGroups(bind_groups))
}
#[derive(Resource)]
pub struct EguiPipelines(pub HashMap<Entity, CachedRenderPipelineId>);
pub fn queue_pipelines_system(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<EguiPipeline>>,
egui_pipeline: Res<EguiPipeline>,
windows: Res<ExtractedWindows>,
) {
let pipelines = windows
.iter()
.filter_map(|(window_id, window)| {
let key = EguiPipelineKey {
texture_format: window.swap_chain_texture_format?.add_srgb_suffix(),
};
let pipeline_id = pipelines.specialize(&pipeline_cache, &egui_pipeline, key);
Some((*window_id, pipeline_id))
})
.collect();
commands.insert_resource(EguiPipelines(pipelines));
}