vendored bevy_egui and removed legacy code :/
Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
507
crates/libmarathon/src/debug_ui/render/systems.rs
Normal file
507
crates/libmarathon/src/debug_ui/render/systems.rs
Normal file
@@ -0,0 +1,507 @@
|
||||
// Copyright (c) 2021 Vladyslav Batyrenko
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// This code is vendored from bevy_egui: https://github.com/vladbat00/bevy_egui
|
||||
// Original author: Vladyslav Batyrenko <vladyslav.batyrenko@gmail.com>
|
||||
|
||||
use crate::debug_ui::{
|
||||
EguiContextSettings, EguiManagedTextures, EguiRenderOutput, EguiUserTextures,
|
||||
RenderComputedScaleFactor,
|
||||
render::{
|
||||
DrawCommand, DrawPrimitive, EguiBevyPaintCallback, EguiCameraView, EguiDraw, EguiPipeline,
|
||||
EguiPipelineKey, EguiViewTarget, PaintCallbackDraw,
|
||||
},
|
||||
};
|
||||
use bevy::asset::prelude::*;
|
||||
use bevy::prelude::{Deref, DerefMut};
|
||||
use bevy::ecs::{prelude::*, system::SystemParam};
|
||||
use bevy::image::Image;
|
||||
use bevy::log;
|
||||
use bevy::math::{URect, UVec2, Vec2};
|
||||
use bevy::platform::collections::HashMap;
|
||||
use bevy::render::{
|
||||
camera::ExtractedCamera,
|
||||
extract_resource::ExtractResource,
|
||||
render_asset::RenderAssets,
|
||||
render_resource::{
|
||||
BindGroup, BindGroupEntry, BindingResource, Buffer, BufferDescriptor, BufferId,
|
||||
CachedRenderPipelineId, DynamicUniformBuffer, PipelineCache, SpecializedRenderPipelines,
|
||||
},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_world::{MainEntity, RenderEntity},
|
||||
texture::GpuImage,
|
||||
view::ExtractedView,
|
||||
};
|
||||
use bytemuck::cast_slice;
|
||||
use itertools::Itertools;
|
||||
use wgpu_types::{BufferAddress, BufferUsages};
|
||||
|
||||
/// Extracted Egui settings.
|
||||
#[derive(Resource, Deref, DerefMut, Default)]
|
||||
pub struct ExtractedEguiSettings(pub EguiContextSettings);
|
||||
|
||||
/// The extracted version of [`EguiManagedTextures`].
|
||||
#[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())
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to Egui's [`egui::TextureId`].
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EguiTextureId {
|
||||
/// Textures allocated via Egui.
|
||||
Managed(MainEntity, u64),
|
||||
/// Textures allocated via Bevy.
|
||||
User(u64),
|
||||
}
|
||||
|
||||
/// Extracted Egui textures.
|
||||
#[derive(SystemParam)]
|
||||
pub struct ExtractedEguiTextures<'w> {
|
||||
/// Maps Egui managed texture ids to Bevy image handles.
|
||||
pub egui_textures: Res<'w, ExtractedEguiManagedTextures>,
|
||||
/// Maps Bevy managed texture handles to Egui user texture ids.
|
||||
pub user_textures: Res<'w, EguiUserTextures>,
|
||||
}
|
||||
|
||||
impl ExtractedEguiTextures<'_> {
|
||||
/// Returns an iterator over all textures (both Egui and Bevy managed).
|
||||
pub fn handles(&self) -> impl Iterator<Item = (EguiTextureId, AssetId<Image>)> + '_ {
|
||||
self.egui_textures
|
||||
.0
|
||||
.iter()
|
||||
.map(|(&(window, texture_id), managed_tex)| {
|
||||
(
|
||||
EguiTextureId::Managed(MainEntity::from(window), texture_id),
|
||||
managed_tex.id(),
|
||||
)
|
||||
})
|
||||
.chain(
|
||||
self.user_textures
|
||||
.textures
|
||||
.iter()
|
||||
.map(|(handle, (_, id))| (EguiTextureId::User(*id), *handle)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the transform buffer.
|
||||
#[derive(Resource, Default)]
|
||||
pub struct EguiTransforms {
|
||||
/// Uniform buffer.
|
||||
pub buffer: DynamicUniformBuffer<EguiTransform>,
|
||||
/// The Entity is from the main world.
|
||||
pub offsets: HashMap<MainEntity, u32>,
|
||||
/// Bind group.
|
||||
pub bind_group: Option<(BufferId, BindGroup)>,
|
||||
}
|
||||
|
||||
/// Scale and translation for rendering Egui shapes. Is needed to transform Egui coordinates from
|
||||
/// the screen space with the center at (0, 0) to the normalised viewport space.
|
||||
#[derive(bevy::render::render_resource::ShaderType, Default)]
|
||||
pub struct EguiTransform {
|
||||
/// Is affected by render target size, scale factor and [`EguiContextSettings::scale_factor`].
|
||||
pub scale: Vec2,
|
||||
/// Normally equals `[-1.0, 1.0]`.
|
||||
pub translation: Vec2,
|
||||
}
|
||||
|
||||
impl EguiTransform {
|
||||
/// Calculates the transform from target size and target scale factor multiplied by [`EguiContextSettings::scale_factor`].
|
||||
pub fn new(target_size: Vec2, scale_factor: f32) -> Self {
|
||||
EguiTransform {
|
||||
scale: Vec2::new(
|
||||
2.0 / (target_size.x / scale_factor),
|
||||
-2.0 / (target_size.y / scale_factor),
|
||||
),
|
||||
translation: Vec2::new(-1.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares Egui transforms.
|
||||
pub fn prepare_egui_transforms_system(
|
||||
mut egui_transforms: ResMut<EguiTransforms>,
|
||||
views: Query<&RenderComputedScaleFactor>,
|
||||
render_targets: Query<(&ExtractedView, &ExtractedCamera, &EguiCameraView)>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
egui_pipeline: Res<EguiPipeline>,
|
||||
) -> Result {
|
||||
egui_transforms.buffer.clear();
|
||||
egui_transforms.offsets.clear();
|
||||
|
||||
for (view, camera, egui_camera_view) in render_targets.iter() {
|
||||
let Some(target_size) = camera.physical_target_size else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let &RenderComputedScaleFactor { scale_factor } = views.get(egui_camera_view.0)?;
|
||||
let transform = EguiTransform::new(target_size.as_vec2(), scale_factor);
|
||||
let offset = egui_transforms
|
||||
.buffer
|
||||
.push(&transform);
|
||||
egui_transforms
|
||||
.offsets
|
||||
.insert(view.retained_view_entity.main_entity, 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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Maps Egui textures to bind groups.
|
||||
#[derive(Resource, Deref, DerefMut, Default)]
|
||||
pub struct EguiTextureBindGroups(pub HashMap<EguiTextureId, (BindGroup, Option<u32>)>);
|
||||
|
||||
/// Queues bind groups.
|
||||
pub fn queue_bind_groups_system(
|
||||
mut commands: Commands,
|
||||
egui_textures: ExtractedEguiTextures,
|
||||
render_device: Res<RenderDevice>,
|
||||
gpu_images: Res<RenderAssets<GpuImage>>,
|
||||
egui_pipeline: Res<EguiPipeline>,
|
||||
) {
|
||||
let egui_texture_iterator = egui_textures.handles().filter_map(|(texture, handle_id)| {
|
||||
let gpu_image = gpu_images.get(handle_id)?;
|
||||
Some((texture, gpu_image))
|
||||
});
|
||||
|
||||
let bind_groups = if let Some(bindless) = egui_pipeline.bindless {
|
||||
let bindless = u32::from(bindless) as usize;
|
||||
let mut bind_groups = HashMap::new();
|
||||
|
||||
let mut texture_array = Vec::new();
|
||||
let mut sampler_array = Vec::new();
|
||||
let mut egui_texture_ids = Vec::new();
|
||||
|
||||
for textures in egui_texture_iterator.chunks(bindless).into_iter() {
|
||||
texture_array.clear();
|
||||
sampler_array.clear();
|
||||
egui_texture_ids.clear();
|
||||
|
||||
for (egui_texture_id, gpu_image) in textures {
|
||||
egui_texture_ids.push(egui_texture_id);
|
||||
// Dereference needed to convert from bevy to wgpu type
|
||||
texture_array.push(&*gpu_image.texture_view);
|
||||
sampler_array.push(&*gpu_image.sampler);
|
||||
}
|
||||
|
||||
let bind_group = render_device.create_bind_group(
|
||||
None,
|
||||
&egui_pipeline.texture_bind_group_layout,
|
||||
&[
|
||||
BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureViewArray(texture_array.as_slice()),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: BindingResource::SamplerArray(sampler_array.as_slice()),
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
// Simply assign bind group to egui texture
|
||||
// Additional code is not needed because bevy RenderPass set_bind_group
|
||||
// removes redundant switching between bind groups
|
||||
for (offset, egui_texture_id) in egui_texture_ids.drain(..).enumerate() {
|
||||
bind_groups.insert(egui_texture_id, (bind_group.clone(), Some(offset as u32)));
|
||||
}
|
||||
}
|
||||
bind_groups
|
||||
} else {
|
||||
egui_texture_iterator
|
||||
.map(|(texture, gpu_image)| {
|
||||
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),
|
||||
},
|
||||
],
|
||||
);
|
||||
(texture, (bind_group, None::<u32>))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
commands.insert_resource(EguiTextureBindGroups(bind_groups))
|
||||
}
|
||||
|
||||
/// Cached Pipeline IDs for the specialized instances of `EguiPipeline`.
|
||||
#[derive(Resource)]
|
||||
pub struct EguiPipelines(pub HashMap<MainEntity, CachedRenderPipelineId>);
|
||||
|
||||
/// Queue [`EguiPipeline`] instances.
|
||||
pub fn queue_pipelines_system(
|
||||
mut commands: Commands,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut specialized_pipelines: ResMut<SpecializedRenderPipelines<EguiPipeline>>,
|
||||
egui_pipeline: Res<EguiPipeline>,
|
||||
egui_views: Query<&EguiViewTarget, With<ExtractedView>>,
|
||||
camera_views: Query<(&MainEntity, &ExtractedCamera)>,
|
||||
) {
|
||||
let pipelines: HashMap<MainEntity, CachedRenderPipelineId> = egui_views
|
||||
.iter()
|
||||
.filter_map(|egui_camera_view| {
|
||||
let (main_entity, extracted_camera) = camera_views.get(egui_camera_view.0).ok()?;
|
||||
|
||||
let pipeline_id = specialized_pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&egui_pipeline,
|
||||
EguiPipelineKey {
|
||||
hdr: extracted_camera.hdr,
|
||||
},
|
||||
);
|
||||
Some((*main_entity, pipeline_id))
|
||||
})
|
||||
.collect();
|
||||
|
||||
commands.insert_resource(EguiPipelines(pipelines));
|
||||
}
|
||||
|
||||
/// Cached Pipeline IDs for the specialized instances of `EguiPipeline`.
|
||||
#[derive(Default, Resource)]
|
||||
pub struct EguiRenderData(pub(crate) HashMap<MainEntity, EguiRenderTargetData>);
|
||||
|
||||
pub(crate) struct EguiRenderTargetData {
|
||||
keep: bool,
|
||||
pub(crate) render_entity: RenderEntity,
|
||||
pub(crate) vertex_data: Vec<u8>,
|
||||
pub(crate) vertex_buffer_capacity: usize,
|
||||
pub(crate) vertex_buffer: Option<Buffer>,
|
||||
pub(crate) index_data: Vec<u32>,
|
||||
pub(crate) index_buffer_capacity: usize,
|
||||
pub(crate) index_buffer: Option<Buffer>,
|
||||
pub(crate) draw_commands: Vec<DrawCommand>,
|
||||
pub(crate) postponed_updates: Vec<(egui::Rect, PaintCallbackDraw)>,
|
||||
pub(crate) pixels_per_point: f32,
|
||||
pub(crate) target_size: UVec2,
|
||||
pub(crate) key: Option<EguiPipelineKey>,
|
||||
}
|
||||
|
||||
impl Default for EguiRenderTargetData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
keep: false,
|
||||
render_entity: RenderEntity::from(Entity::PLACEHOLDER),
|
||||
vertex_data: Vec::new(),
|
||||
vertex_buffer_capacity: 0,
|
||||
vertex_buffer: None,
|
||||
index_data: Vec::new(),
|
||||
index_buffer_capacity: 0,
|
||||
index_buffer: None,
|
||||
draw_commands: Vec::new(),
|
||||
postponed_updates: Vec::new(),
|
||||
pixels_per_point: 0.0,
|
||||
target_size: UVec2::ZERO,
|
||||
key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares Egui transforms.
|
||||
pub fn prepare_egui_render_target_data_system(
|
||||
mut render_data: ResMut<EguiRenderData>,
|
||||
render_targets: Query<(
|
||||
Entity,
|
||||
&ExtractedView,
|
||||
&RenderComputedScaleFactor,
|
||||
&EguiViewTarget,
|
||||
&EguiRenderOutput,
|
||||
)>,
|
||||
extracted_cameras: Query<&ExtractedCamera>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
) {
|
||||
let render_data = &mut render_data.0;
|
||||
render_data.retain(|_, data| {
|
||||
let keep = data.keep;
|
||||
data.keep = false;
|
||||
keep
|
||||
});
|
||||
|
||||
for (render_entity, view, computed_scale_factor, egui_view_target, render_output) in
|
||||
render_targets.iter()
|
||||
{
|
||||
let data = render_data
|
||||
.entry(view.retained_view_entity.main_entity)
|
||||
.or_default();
|
||||
|
||||
data.keep = true;
|
||||
data.render_entity = render_entity.into();
|
||||
|
||||
// Construct a pipeline key based on a render target.
|
||||
let Ok(extracted_camera) = extracted_cameras.get(egui_view_target.0) else {
|
||||
// This is ok when a window is minimized.
|
||||
log::trace!("ExtractedCamera entity doesn't exist for the Egui view");
|
||||
continue;
|
||||
};
|
||||
data.key = Some(EguiPipelineKey {
|
||||
hdr: extracted_camera.hdr,
|
||||
});
|
||||
|
||||
data.pixels_per_point = computed_scale_factor.scale_factor;
|
||||
if extracted_camera
|
||||
.physical_viewport_size
|
||||
.is_none_or(|size| size.x < 1 || size.y < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut index_offset = 0;
|
||||
|
||||
data.draw_commands.clear();
|
||||
data.vertex_data.clear();
|
||||
data.index_data.clear();
|
||||
data.postponed_updates.clear();
|
||||
|
||||
for egui::epaint::ClippedPrimitive {
|
||||
clip_rect,
|
||||
primitive,
|
||||
} in render_output.paint_jobs.as_slice()
|
||||
{
|
||||
let clip_rect = *clip_rect;
|
||||
|
||||
let clip_urect = URect {
|
||||
min: UVec2 {
|
||||
x: (clip_rect.min.x * data.pixels_per_point).round() as u32,
|
||||
y: (clip_rect.min.y * data.pixels_per_point).round() as u32,
|
||||
},
|
||||
max: UVec2 {
|
||||
x: (clip_rect.max.x * data.pixels_per_point).round() as u32,
|
||||
y: (clip_rect.max.y * data.pixels_per_point).round() as u32,
|
||||
},
|
||||
};
|
||||
|
||||
if clip_urect
|
||||
.intersect(URect::new(
|
||||
view.viewport.x,
|
||||
view.viewport.y,
|
||||
view.viewport.x + view.viewport.z,
|
||||
view.viewport.y + view.viewport.w,
|
||||
))
|
||||
.is_empty()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mesh = match primitive {
|
||||
egui::epaint::Primitive::Mesh(mesh) => mesh,
|
||||
egui::epaint::Primitive::Callback(paint_callback) => {
|
||||
let callback = match paint_callback
|
||||
.callback
|
||||
.clone()
|
||||
.downcast::<EguiBevyPaintCallback>()
|
||||
{
|
||||
Ok(callback) => callback,
|
||||
Err(err) => {
|
||||
log::error!("Unsupported Egui paint callback type: {err:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
data.postponed_updates.push((
|
||||
clip_rect,
|
||||
PaintCallbackDraw {
|
||||
callback: callback.clone(),
|
||||
rect: paint_callback.rect,
|
||||
},
|
||||
));
|
||||
|
||||
data.draw_commands.push(DrawCommand {
|
||||
primitive: DrawPrimitive::PaintCallback(PaintCallbackDraw {
|
||||
callback,
|
||||
rect: paint_callback.rect,
|
||||
}),
|
||||
clip_rect,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
data.vertex_data
|
||||
.extend_from_slice(cast_slice::<_, u8>(mesh.vertices.as_slice()));
|
||||
data.index_data
|
||||
.extend(mesh.indices.iter().map(|i| i + index_offset));
|
||||
index_offset += mesh.vertices.len() as u32;
|
||||
|
||||
let texture_handle = match mesh.texture_id {
|
||||
egui::TextureId::Managed(id) => {
|
||||
EguiTextureId::Managed(view.retained_view_entity.main_entity, id)
|
||||
}
|
||||
egui::TextureId::User(id) => EguiTextureId::User(id),
|
||||
};
|
||||
|
||||
data.draw_commands.push(DrawCommand {
|
||||
primitive: DrawPrimitive::Egui(EguiDraw {
|
||||
vertices_count: mesh.indices.len(),
|
||||
egui_texture: texture_handle,
|
||||
}),
|
||||
clip_rect,
|
||||
});
|
||||
}
|
||||
|
||||
if data.vertex_data.len() > data.vertex_buffer_capacity {
|
||||
data.vertex_buffer_capacity = data.vertex_data.len().next_power_of_two();
|
||||
data.vertex_buffer = Some(render_device.create_buffer(&BufferDescriptor {
|
||||
label: Some("egui vertex buffer"),
|
||||
size: data.vertex_buffer_capacity as BufferAddress,
|
||||
usage: BufferUsages::COPY_DST | BufferUsages::VERTEX,
|
||||
mapped_at_creation: false,
|
||||
}));
|
||||
}
|
||||
|
||||
let index_data_size = data.index_data.len() * std::mem::size_of::<u32>();
|
||||
if index_data_size > data.index_buffer_capacity {
|
||||
data.index_buffer_capacity = index_data_size.next_power_of_two();
|
||||
data.index_buffer = Some(render_device.create_buffer(&BufferDescriptor {
|
||||
label: Some("egui index buffer"),
|
||||
size: data.index_buffer_capacity as BufferAddress,
|
||||
usage: BufferUsages::COPY_DST | BufferUsages::INDEX,
|
||||
mapped_at_creation: false,
|
||||
}));
|
||||
}
|
||||
|
||||
let (vertex_buffer, index_buffer) = match (&data.vertex_buffer, &data.index_buffer) {
|
||||
(Some(vertex), Some(index)) => (vertex, index),
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
render_queue.write_buffer(vertex_buffer, 0, &data.vertex_data);
|
||||
render_queue.write_buffer(index_buffer, 0, cast_slice(&data.index_data));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user