summaryrefslogtreecommitdiff
path: root/engine/src/ui/dear_imgui.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/ui/dear_imgui.rs')
-rw-r--r--engine/src/ui/dear_imgui.rs1084
1 files changed, 1084 insertions, 0 deletions
diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs
new file mode 100644
index 0000000..0047394
--- /dev/null
+++ b/engine/src/ui/dear_imgui.rs
@@ -0,0 +1,1084 @@
+use std::collections::HashMap;
+use std::path::{Path, PathBuf};
+use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::LazyLock;
+
+use dear_imgui_rs::{
+ DrawCmd as ImguiDrawCmd,
+ Key as ImguiKey,
+ MouseButton as ImguiMouseButton,
+};
+use ecs::component::local::Local;
+use ecs::component::Component;
+use ecs::error::Context as _;
+use ecs::event::component::{Changed, EventMatchExt};
+use ecs::pair::Pair;
+use ecs::query::term::With;
+use ecs::sole::Single;
+use ecs::system::initializable::Initializable;
+use ecs::system::observer::Observe;
+use ecs::system::Into;
+use ecs::time::Time;
+use ecs::{Component, Query, Sole};
+use intmap::IntMap;
+
+use crate::asset::{Assets, Handle as AssetHandle, Label as AssetLabel};
+use crate::data_types::dimens::Dimens;
+use crate::input::keyboard::{Key, Keyboard};
+use crate::input::mouse::{Button as MouseButton, Buttons as MouseButtons, Mouse};
+use crate::mesh::vertex_buffer::{
+ NamedVertexAttr as MeshNamedVertexAttr,
+ VertexAttrInfo as MeshVertexAttrInfo,
+ VertexBuffer as MeshVertexBuffer,
+ VertexLabel,
+};
+use crate::mesh::{Mesh, VertexAttrType as MeshVertexAttrType};
+use crate::projection::{
+ ClipVolume as ProjectionClipVolume,
+ Orthographic as OrthographicProjection,
+};
+use crate::reflection::Reflection;
+use crate::rendering::blending::{
+ Config as RenderingBlendingConfiguration,
+ Equation as RenderingBlendingEquation,
+ Factor as RenderingBlendingFactor,
+};
+use crate::rendering::object::Id as RenderingObjectId;
+use crate::rendering::shader::cursor::{
+ BindingTextureKind as ShaderBindingTextureKind,
+ BindingValue as ShaderBindingValue,
+ Cursor as ShaderCursor,
+};
+use crate::rendering::shader::{
+ Context as ShaderContext,
+ EntrypointFlags as ShaderEntrypointFlags,
+ ModuleSource as ShaderModuleSource,
+};
+use crate::rendering::{
+ AssetOrValue,
+ Command as RenderingCommand,
+ DrawMeshOptions,
+ DrawProperties,
+ DrawPropertiesUpdateFlags,
+ MeshUsage,
+ RenderPass,
+ RenderPasses,
+ ScissorBox,
+ SrgbaTextureDataType,
+ Surface,
+ SurfaceId,
+ TextureCreation,
+ TexturePixelDataFormat,
+ TextureUpdate,
+ PRE_RENDER_PHASE,
+};
+use crate::texture::Properties as TextureProperties;
+use crate::vector::Vec2;
+use crate::windowing::dpi::PhysicalSize;
+use crate::windowing::window::Window;
+
+mod reexports
+{
+ pub use dear_imgui_rs as bindings;
+}
+
+pub use reexports::*;
+
+pub static SHADER_ASSET_LABEL: LazyLock<AssetLabel> = LazyLock::new(|| AssetLabel {
+ path: Path::new("").into(),
+ name: Some("imgui_shader".into()),
+});
+
+/// Dear Imgui context
+#[derive(Sole)]
+pub struct Context
+{
+ pub enabled: bool,
+ ctx: inner_context_wrapper::InnerContextWrapper,
+ texture_lookup: IntMap<TextureLookupId, RenderingObjectId>,
+ texture_id_lookup:
+ HashMap<dear_imgui_rs::SnapshotTextureId, dear_imgui_rs::TextureId>,
+}
+
+impl Context
+{
+ pub fn frame(&mut self) -> Option<&mut bindings::Ui>
+ {
+ if !self.enabled {
+ return None;
+ }
+
+ self.ctx.get_frame()
+ }
+
+ pub fn register_texture(
+ &mut self,
+ texture_data: bindings::OwnedTextureData,
+ ) -> bindings::ManagedTextureId
+ {
+ self.ctx.register_texture(texture_data)
+ }
+}
+
+#[derive(Debug, Default)]
+#[non_exhaustive]
+pub struct Extension
+{
+ start_enabled: bool,
+ settings_ini_file_path: Option<PathBuf>,
+}
+
+impl Extension
+{
+ #[must_use]
+ pub fn with_start_enabled(mut self, start_enabled: bool) -> Self
+ {
+ self.start_enabled = start_enabled;
+ self
+ }
+
+ #[must_use]
+ pub fn with_settings_ini_file(
+ mut self,
+ settings_ini_file_path: Option<PathBuf>,
+ ) -> Self
+ {
+ self.settings_ini_file_path = settings_ini_file_path;
+ self
+ }
+}
+
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ let mut context = Context {
+ enabled: self.start_enabled,
+ ctx: inner_context_wrapper::InnerContextWrapper::new(),
+ texture_lookup: IntMap::with_capacity(8),
+ texture_id_lookup: HashMap::with_capacity(8),
+ };
+
+ if let Err(err) = context
+ .ctx
+ .set_settings_ini_file_path(self.settings_ini_file_path)
+ {
+ tracing::error!("Failed to set path of imgui settings ini file: {err}");
+ }
+
+ const {
+ assert!(
+ dear_imgui_rs::HAS_FREETYPE,
+ "Freetype font rasterizer support is not enabled"
+ );
+ }
+
+ context.ctx.font_atlas().add_font(&[unsafe {
+ dear_imgui_rs::FontSource::ttf_data_with_size(
+ include_bytes!("../../res/FiraMono-Regular.ttf"),
+ 14.0,
+ )
+ .with_config(
+ dear_imgui_rs::FontConfig::default()
+ .name("FiraMono-Regular.ttf")
+ .font_loader_flags(
+ dear_imgui_rs::FontLoaderFlags::BITMAP
+ | dear_imgui_rs::FontLoaderFlags::FORCE_AUTOHINT,
+ ),
+ )
+ }]);
+
+ collector.add_sole(context).ok();
+
+ collector.add_system(
+ *PRE_RENDER_PHASE,
+ update.into_system().initialize((State::default(),)),
+ );
+
+ collector.add_observer(handle_window_changed);
+ }
+}
+
+#[tracing::instrument(skip_all)]
+fn handle_window_changed(
+ observe: Observe<Pair<Changed, Window>>,
+ mut context: Single<Context>,
+)
+{
+ let Ok(context) = context.get_mut() else {
+ unreachable!();
+ };
+
+ let Some(event_match) = observe
+ .iter()
+ .find(|event_match| event_match.get_entity().has_component(TargetWindow::id()))
+ else {
+ return;
+ };
+
+ let window = event_match.get_ent_target_comp();
+
+ let hidpi_factor = window.scale_factor().round();
+
+ context
+ .ctx
+ .get_io_mut()
+ .set_display_framebuffer_scale([hidpi_factor as f32, hidpi_factor as f32]);
+
+ let window_size_logical = PhysicalSize {
+ width: f64::from(window.inner_size.width),
+ height: f64::from(window.inner_size.height),
+ }
+ .to_logical::<f64>(hidpi_factor);
+
+ context.ctx.get_io_mut().set_display_size([
+ window_size_logical.width as f32,
+ window_size_logical.height as f32,
+ ]);
+}
+
+fn update(
+ target_window_query: Query<(&Window, &Surface, With<TargetWindow>)>,
+ mut context: Single<Context>,
+ time: Single<Time>,
+ mut assets: Single<Assets>,
+ mut render_passes: Single<RenderPasses>,
+ shader_context: Single<ShaderContext>,
+ keyboard: Single<Keyboard>,
+ mouse: Single<Mouse>,
+ mouse_buttons: Single<MouseButtons>,
+ mut state: Local<State>,
+) -> Result<(), crate::Error>
+{
+ let Ok(context) = context.get_mut() else {
+ unreachable!();
+ };
+
+ let assets = assets.get_mut()?;
+ let render_passes = render_passes.get_mut()?;
+ let shader_context = shader_context.get()?;
+ let keyboard = keyboard.get()?;
+ let mouse = mouse.get()?;
+ let mouse_buttons = mouse_buttons.get()?;
+
+ let Some((window, window_surface)) = target_window_query.iter().next() else {
+ return Ok(());
+ };
+
+ let curr_state = std::mem::replace(&mut *state, State::Unreachable);
+
+ match curr_state {
+ State::NotInitialized => {
+ let Some((shader_asset, mesh_obj_id, mesh)) = initialize_context(
+ context,
+ &window,
+ window_surface.id,
+ assets,
+ render_passes,
+ shader_context,
+ ) else {
+ *state = State::NotInitialized;
+ return Ok(());
+ };
+
+ context.ctx.new_frame();
+
+ *state = State::Ready { shader_asset, mesh_obj_id, mesh };
+ }
+ State::Ready { shader_asset, mesh_obj_id, mut mesh } => {
+ if !context.enabled {
+ *state = State::Ready { shader_asset, mesh_obj_id, mesh };
+ return Ok(());
+ }
+
+ context
+ .ctx
+ .get_io_mut()
+ .set_delta_time(time.delta_time.as_secs_f32());
+
+ update_inputs(context, &window, keyboard, mouse, mouse_buttons);
+
+ add_drawing_render_pass(
+ context,
+ render_passes,
+ shader_context,
+ window_surface.id,
+ shader_asset.clone(),
+ mesh_obj_id,
+ &mut mesh,
+ )?;
+
+ context.ctx.new_frame();
+
+ *state = State::Ready { shader_asset, mesh_obj_id, mesh };
+ }
+ State::Unreachable => unreachable!(),
+ }
+
+ Ok(())
+}
+
+#[derive(Debug, Default, Clone, Copy, Component, Reflection)]
+pub struct TargetWindow;
+
+#[derive(Debug, Default, Component)]
+enum State
+{
+ #[default]
+ NotInitialized,
+ Ready
+ {
+ shader_asset: AssetHandle<ShaderModuleSource>,
+ mesh_obj_id: RenderingObjectId,
+ mesh: Mesh,
+ },
+ Unreachable,
+}
+
+fn initialize_context(
+ context: &mut Context,
+ window: &Window,
+ window_surface_id: SurfaceId,
+ assets: &mut Assets,
+ render_passes: &mut RenderPasses,
+ shader_context: &ShaderContext,
+) -> Option<(AssetHandle<ShaderModuleSource>, RenderingObjectId, Mesh)>
+{
+ let Some(shader_asset) =
+ assets.get_handle_to_loaded::<ShaderModuleSource>(SHADER_ASSET_LABEL.clone())
+ else {
+ assets.store_with_label(
+ SHADER_ASSET_LABEL.clone(),
+ ShaderModuleSource {
+ name: "imgui_shader.slang".into(),
+ file_path: Path::new("@engine/imgui_shader").into(),
+ source: include_str!("../../res/imgui_shader.slang").into(),
+ link_entrypoints: ShaderEntrypointFlags::VERTEX
+ | ShaderEntrypointFlags::FRAGMENT,
+ },
+ );
+
+ // We want to wait with initializing until the shader have been processed by the
+ // shader context
+ return None;
+ };
+
+ #[allow(clippy::cast_possible_truncation)]
+ let hidpi_factor = window.scale_factor().round() as f32;
+
+ context
+ .ctx
+ .get_io_mut()
+ .set_display_framebuffer_scale([hidpi_factor, hidpi_factor]);
+
+ let window_size = Dimens {
+ width: window.inner_size.width as f32,
+ height: window.inner_size.height as f32,
+ };
+
+ #[allow(clippy::cast_possible_truncation)]
+ let window_logical_size = window_size / hidpi_factor;
+
+ context
+ .ctx
+ .get_io_mut()
+ .set_display_size([window_logical_size.width, window_logical_size.height]);
+
+ let mesh_obj_id = RenderingObjectId::new_sequential();
+
+ let mesh = Mesh::builder()
+ .vertices(MeshVertexBuffer::with_capacity(
+ &[
+ MeshVertexAttrInfo {
+ label: VertexLabel::Position,
+ ty: MeshVertexAttrType::Float32Array { length: 3 },
+ },
+ MeshVertexAttrInfo {
+ label: VertexLabel::Color,
+ ty: MeshVertexAttrType::Float32Array { length: 4 },
+ },
+ MeshVertexAttrInfo {
+ label: VertexLabel::UvFromTopLeft,
+ ty: MeshVertexAttrType::Float32Array { length: 2 },
+ },
+ ],
+ 128,
+ ))
+ .indices([])
+ .build();
+
+ render_passes.passes.push_back(RenderPass {
+ commands: vec![
+ RenderingCommand::MakeCurrent(window_surface_id),
+ RenderingCommand::CreateShaderProgram(
+ RenderingObjectId::Asset(shader_asset.id()),
+ shader_context
+ .get_program(&shader_asset.id())
+ .expect("Not possible")
+ .clone(),
+ ),
+ RenderingCommand::ActivateShader(RenderingObjectId::Asset(shader_asset.id())),
+ RenderingCommand::CreateMesh {
+ obj_id: mesh_obj_id,
+ mesh: AssetOrValue::Value(mesh.clone()),
+ usage: MeshUsage::Stream,
+ },
+ ],
+ draw_properties: DrawProperties::default(),
+ });
+
+ Some((shader_asset, mesh_obj_id, mesh))
+}
+
+fn update_inputs(
+ context: &mut Context,
+ window: &Window,
+ keyboard: &Keyboard,
+ mouse: &Mouse,
+ mouse_buttons: &MouseButtons,
+)
+{
+ let io = context.ctx.get_io_mut();
+
+ for (key, key_state) in keyboard.new_key_states() {
+ if let Some(key) = key_to_imgui_key(key) {
+ io.add_key_event(key, key_state.is_pressed());
+ }
+ }
+
+ for character in keyboard.text_keys().chars() {
+ if character == '\u{7f}' {
+ continue;
+ }
+
+ io.add_input_character(character);
+ }
+
+ io.add_mouse_source_event(dear_imgui_rs::input::MouseSource::Mouse);
+
+ let mouse_pos = mouse.position.to_logical::<f64>(window.scale_factor());
+
+ #[allow(clippy::cast_possible_truncation)]
+ io.add_mouse_pos_event([mouse_pos.x as f32, mouse_pos.y as f32]);
+
+ if !mouse.curr_tick_scroll_delta.is_zero() {
+ io.add_mouse_wheel_event([
+ mouse.curr_tick_scroll_delta.hor_lines,
+ mouse.curr_tick_scroll_delta.vert_lines,
+ ]);
+ }
+
+ for (mouse_button, mouse_button_state) in mouse_buttons.all_current() {
+ if let Some(mouse_button) = mouse_button_to_imgui_mouse_button(mouse_button) {
+ io.add_mouse_button_event(mouse_button, mouse_button_state.is_pressed());
+ }
+ }
+}
+
+fn add_drawing_render_pass(
+ context: &mut Context,
+ render_passes: &mut RenderPasses,
+ shader_context: &ShaderContext,
+ window_surface_id: SurfaceId,
+ shader_asset: AssetHandle<ShaderModuleSource>,
+ mesh_obj_id: RenderingObjectId,
+ mesh: &mut Mesh,
+) -> Result<(), crate::Error>
+{
+ let render_pass = render_passes.passes.push_back_mut(RenderPass {
+ commands: Vec::new(),
+ draw_properties: DrawProperties {
+ blending_enabled: true,
+ blending_config: RenderingBlendingConfiguration {
+ equation: RenderingBlendingEquation::Add,
+ source_factor: RenderingBlendingFactor::SrcAlpha,
+ destination_factor: RenderingBlendingFactor::OneMinusSrcAlpha,
+ },
+ depth_test_enabled: false,
+ face_culling_enabled: false,
+ scissor_test_enabled: true,
+ ..Default::default()
+ },
+ });
+
+ let shader_program = shader_context
+ .get_program(&shader_asset.id())
+ .expect("Not possible");
+
+ let shader_cursor = ShaderCursor::new(
+ shader_program
+ .reflection(0)
+ .unwrap()
+ .global_params_var_layout()
+ .unwrap(),
+ );
+
+ let Context {
+ enabled: _,
+ ctx,
+ texture_lookup,
+ texture_id_lookup,
+ } = context;
+
+ let render_frame = ctx.render();
+
+ let mut tex_feedbacks = Vec::with_capacity(render_frame.texture_requests().len());
+
+ for texture_request in render_frame.texture_requests() {
+ match texture_request.operation() {
+ dear_imgui_rs::TextureOp::Create {
+ format,
+ width,
+ height,
+ row_pitch: _,
+ pixels,
+ } => {
+ if !matches!(format, dear_imgui_rs::TextureFormat::RGBA32) {
+ unimplemented!();
+ }
+
+ let texture_lookup_id = TextureLookupId::new();
+
+ tracing::trace!(
+ snapshot_tex_id = ?texture_request.texture(),
+ tex_lookup_id = ?texture_lookup_id,
+ "Performing texture operation: Create"
+ );
+
+ let texture_object_id = RenderingObjectId::new_sequential();
+
+ render_pass.commands.push(RenderingCommand::CreateTexture {
+ obj_id: texture_object_id,
+ pixel_data_format: TexturePixelDataFormat::Srgba(
+ SrgbaTextureDataType::UnsignedByte,
+ ),
+ creation: TextureCreation::Texture2D {
+ size: Dimens { width: *width, height: *height },
+ image: Some(pixels.clone().into_boxed_slice()),
+ },
+ properties: TextureProperties::default(),
+ });
+
+ texture_lookup.insert(texture_lookup_id, texture_object_id);
+
+ texture_id_lookup
+ .insert(texture_request.texture(), texture_lookup_id.into());
+
+ let texture_feedback =
+ match texture_request.uploaded(texture_lookup_id.into()) {
+ Ok(texture_feedback) => texture_feedback,
+ Err(
+ dear_imgui_rs::TextureFeedbackError::UploadForDestroy
+ | dear_imgui_rs::TextureFeedbackError::DestroyForUpload
+ | dear_imgui_rs::TextureFeedbackError::NullTextureId,
+ ) => unreachable!(),
+ };
+
+ tex_feedbacks.push(texture_feedback);
+ }
+ dear_imgui_rs::TextureOp::Update { format, width: _, height: _, rects } => {
+ if !matches!(format, dear_imgui_rs::TextureFormat::RGBA32) {
+ unimplemented!();
+ }
+
+ let texture_lookup_id =
+ TextureLookupId::from(texture_id_lookup[&texture_request.texture()]);
+
+ tracing::trace!(
+ snapshot_tex_id = ?texture_request.texture(),
+ tex_lookup_id = ?texture_lookup_id,
+ "Performing texture operation: Update"
+ );
+
+ let Some(texture_object_id) = texture_lookup.get(texture_lookup_id)
+ else {
+ tracing::error!(
+ snapshot_tex_id = ?texture_request.texture(),
+ tex_lookup_id = ?texture_lookup_id,
+ "Unknown texture. Skipping update",
+ );
+ continue;
+ };
+
+ render_pass.commands.reserve(rects.len());
+
+ for rect in rects {
+ render_pass.commands.push(RenderingCommand::UpdateTexture {
+ obj_id: *texture_object_id,
+ pixels: rect.data.clone().into_boxed_slice(),
+ pixel_data_format: TexturePixelDataFormat::Srgba(
+ SrgbaTextureDataType::UnsignedByte,
+ ),
+ update: TextureUpdate::Texture2D {
+ size: Dimens::<u32> {
+ width: rect.rect.w.into(),
+ height: rect.rect.h.into(),
+ },
+ offset: Vec2 {
+ x: u32::from(rect.rect.x),
+ y: u32::from(rect.rect.y),
+ },
+ },
+ });
+ }
+
+ let texture_feedback =
+ match texture_request.uploaded(texture_lookup_id.into()) {
+ Ok(texture_feedback) => texture_feedback,
+ Err(
+ dear_imgui_rs::TextureFeedbackError::UploadForDestroy
+ | dear_imgui_rs::TextureFeedbackError::DestroyForUpload
+ | dear_imgui_rs::TextureFeedbackError::NullTextureId,
+ ) => unreachable!(),
+ };
+
+ tex_feedbacks.push(texture_feedback);
+ }
+ dear_imgui_rs::TextureOp::Destroy => {
+ let texture_lookup_id =
+ TextureLookupId::from(texture_id_lookup[&texture_request.texture()]);
+
+ tracing::trace!(
+ snapshot_tex_id = ?texture_request.texture(),
+ tex_lookup_id = ?texture_lookup_id,
+ "Performing texture operation: Destroy"
+ );
+
+ let Some(texture_object_id) = texture_lookup.remove(texture_lookup_id)
+ else {
+ tracing::error!(
+ texture = ?texture_lookup_id,
+ "Unknown texture. Skipping destroyal",
+ );
+ continue;
+ };
+
+ render_pass
+ .commands
+ .push(RenderingCommand::RemoveTexture(texture_object_id));
+
+ let texture_feedback = match texture_request.destroyed() {
+ Ok(texture_feedback) => texture_feedback,
+ Err(
+ dear_imgui_rs::TextureFeedbackError::UploadForDestroy
+ | dear_imgui_rs::TextureFeedbackError::DestroyForUpload
+ | dear_imgui_rs::TextureFeedbackError::NullTextureId,
+ ) => unreachable!(),
+ };
+
+ tex_feedbacks.push(texture_feedback);
+ }
+ }
+ }
+
+ let frame = render_frame
+ .reconcile_texture_feedback(tex_feedbacks)
+ .with_context(|| "Failed to reconcile texture feedback")?;
+
+ let draw_data = frame.draw_data();
+
+ let [display_width, display_height] = draw_data.display_size();
+
+ // let [scale_width, scale_height] = draw_data.framebuffer_scale;
+
+ let shader_object_id = RenderingObjectId::Asset(shader_asset.id());
+
+ render_pass.commands.extend([
+ RenderingCommand::MakeCurrent(window_surface_id),
+ RenderingCommand::ActivateShader(shader_object_id),
+ RenderingCommand::SetShaderBinding(
+ shader_object_id,
+ shader_cursor
+ .field("Uniforms")
+ .field("projection")
+ .binding(
+ OrthographicProjection::builder()
+ .near(1.0)
+ .far(-1.0)
+ .viewport_origin(Vec2 { x: 0.0, y: 1.0 })
+ .size(crate::projection::OrthographicSize::FixedSize(Dimens {
+ width: display_width,
+ height: -display_height,
+ }))
+ .build()
+ .to_matrix_rh(
+ // Window size can be 0x0 since it will not be used
+ PhysicalSize::default(),
+ ProjectionClipVolume::NegOneToOne,
+ )
+ .into(),
+ )?,
+ ),
+ ]);
+
+ for draw_list in draw_data.draw_lists() {
+ mesh.vertex_buf_mut().clear();
+
+ for vertex in draw_list.vtx_buffer() {
+ mesh.vertex_buf_mut().push((
+ MeshNamedVertexAttr::<[f32; 3]> {
+ label: VertexLabel::Position,
+ value: [vertex.pos[0], vertex.pos[1], 0.0],
+ },
+ MeshNamedVertexAttr {
+ label: VertexLabel::Color,
+ value: vertex.rgba().map(|elem| f32::from(elem) / 255.0),
+ },
+ MeshNamedVertexAttr {
+ label: VertexLabel::UvFromTopLeft,
+ value: vertex.uv,
+ },
+ ));
+ }
+
+ mesh.set_indices(draw_list.idx_buffer().iter().map(|index| (*index).into()));
+
+ render_pass.commands.push(RenderingCommand::UpdateMesh {
+ obj_id: mesh_obj_id,
+ mesh: mesh.clone(),
+ usage: MeshUsage::Stream,
+ });
+
+ for command in draw_list.commands() {
+ if let ImguiDrawCmd::Elements { count, cmd_params } = command {
+ let Some(scissor_box) = calc_draw_cmd_scissor_box(draw_data, &cmd_params)
+ else {
+ continue;
+ };
+
+ let tex_id = cmd_params.texture_id;
+
+ let Some(texture_object_id) =
+ texture_lookup.get(TextureLookupId::from(tex_id))
+ else {
+ tracing::error!(
+ "Unknown texture {}. Skipping skipping draw command",
+ tex_id.id()
+ );
+ continue;
+ };
+
+ render_pass.commands.extend([
+ RenderingCommand::UpdateDrawProperties(
+ DrawProperties { scissor_box, ..Default::default() },
+ DrawPropertiesUpdateFlags::SCISSOR_BOX,
+ ),
+ RenderingCommand::SetShaderBinding(
+ shader_object_id,
+ shader_cursor.field("main_texture").binding(
+ ShaderBindingValue::Texture(
+ *texture_object_id,
+ ShaderBindingTextureKind::Texture2D,
+ ),
+ )?,
+ ),
+ RenderingCommand::DrawMesh(
+ mesh_obj_id,
+ DrawMeshOptions::builder()
+ .element_offset(cmd_params.idx_offset.try_into().unwrap())
+ .vertex_offset(cmd_params.vtx_offset.try_into().unwrap())
+ .element_cnt(count.try_into().unwrap())
+ .build(),
+ ),
+ ]);
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn calc_draw_cmd_scissor_box(
+ draw_data: &dear_imgui_rs::DrawData,
+ cmd_params: &dear_imgui_rs::DrawCmdParams,
+) -> Option<ScissorBox>
+{
+ let clip_rect = cmd_params.clip_rect;
+
+ let display_size = draw_data.display_size();
+ let display_pos = draw_data.display_pos();
+ let framebuffer_scale = draw_data.framebuffer_scale();
+
+ let clip_min_x = (clip_rect[0] - display_pos[0]) * framebuffer_scale[0];
+
+ let clip_min_y = (clip_rect[1] - display_pos[1]) * framebuffer_scale[1];
+
+ let clip_max_x = (clip_rect[2] - display_pos[0]) * framebuffer_scale[0];
+
+ let clip_max_y = (clip_rect[3] - display_pos[1]) * framebuffer_scale[1];
+
+ if clip_max_x <= clip_min_x || clip_max_y <= clip_min_y {
+ return None;
+ }
+
+ let fb_height = display_size[1] * framebuffer_scale[1];
+
+ Some(ScissorBox {
+ size: Some(Dimens {
+ width: (clip_max_x - clip_min_x) as u16,
+ height: (clip_max_y - clip_min_y) as u16,
+ }),
+ lower_left_corner_pos: Vec2 {
+ x: clip_min_x as u16,
+ y: (fb_height - clip_max_y) as u16,
+ },
+ })
+}
+
+fn key_to_imgui_key(key: Key) -> Option<ImguiKey>
+{
+ match key {
+ Key::Backquote => Some(ImguiKey::GraveAccent),
+ Key::Backslash => Some(ImguiKey::Backslash),
+ Key::BracketLeft => Some(ImguiKey::LeftBracket),
+ Key::BracketRight => Some(ImguiKey::RightBracket),
+ Key::Comma => Some(ImguiKey::Comma),
+ Key::Digit0 => Some(ImguiKey::Key0),
+ Key::Digit1 => Some(ImguiKey::Key1),
+ Key::Digit2 => Some(ImguiKey::Key2),
+ Key::Digit3 => Some(ImguiKey::Key3),
+ Key::Digit4 => Some(ImguiKey::Key4),
+ Key::Digit5 => Some(ImguiKey::Key5),
+ Key::Digit6 => Some(ImguiKey::Key6),
+ Key::Digit7 => Some(ImguiKey::Key7),
+ Key::Digit8 => Some(ImguiKey::Key8),
+ Key::Digit9 => Some(ImguiKey::Key9),
+ Key::Equal => Some(ImguiKey::Equal),
+ Key::A => Some(ImguiKey::A),
+ Key::B => Some(ImguiKey::B),
+ Key::C => Some(ImguiKey::C),
+ Key::D => Some(ImguiKey::D),
+ Key::E => Some(ImguiKey::E),
+ Key::F => Some(ImguiKey::F),
+ Key::G => Some(ImguiKey::G),
+ Key::H => Some(ImguiKey::H),
+ Key::I => Some(ImguiKey::I),
+ Key::J => Some(ImguiKey::J),
+ Key::K => Some(ImguiKey::K),
+ Key::L => Some(ImguiKey::L),
+ Key::M => Some(ImguiKey::M),
+ Key::N => Some(ImguiKey::N),
+ Key::O => Some(ImguiKey::O),
+ Key::P => Some(ImguiKey::P),
+ Key::Q => Some(ImguiKey::Q),
+ Key::R => Some(ImguiKey::R),
+ Key::S => Some(ImguiKey::S),
+ Key::T => Some(ImguiKey::T),
+ Key::U => Some(ImguiKey::U),
+ Key::V => Some(ImguiKey::V),
+ Key::W => Some(ImguiKey::W),
+ Key::X => Some(ImguiKey::X),
+ Key::Y => Some(ImguiKey::Y),
+ Key::Z => Some(ImguiKey::Z),
+ Key::Minus => Some(ImguiKey::Minus),
+ Key::Period => Some(ImguiKey::Period),
+ Key::Quote => Some(ImguiKey::Apostrophe),
+ Key::Semicolon => Some(ImguiKey::Semicolon),
+ Key::Slash => Some(ImguiKey::Slash),
+ Key::AltLeft => Some(ImguiKey::LeftAlt),
+ Key::AltRight => Some(ImguiKey::RightAlt),
+ Key::Backspace => Some(ImguiKey::Backspace),
+ Key::CapsLock => Some(ImguiKey::CapsLock),
+ Key::ControlLeft => Some(ImguiKey::LeftCtrl),
+ Key::ControlRight => Some(ImguiKey::RightCtrl),
+ Key::Enter => Some(ImguiKey::Enter),
+ Key::SuperLeft => Some(ImguiKey::LeftSuper),
+ Key::SuperRight => Some(ImguiKey::RightSuper),
+ Key::ShiftLeft => Some(ImguiKey::LeftShift),
+ Key::ShiftRight => Some(ImguiKey::RightShift),
+ Key::Space => Some(ImguiKey::Space),
+ Key::Tab => Some(ImguiKey::Tab),
+ Key::Delete => Some(ImguiKey::Delete),
+ Key::End => Some(ImguiKey::End),
+ Key::Home => Some(ImguiKey::Home),
+ Key::Insert => Some(ImguiKey::Insert),
+ Key::PageDown => Some(ImguiKey::PageDown),
+ Key::PageUp => Some(ImguiKey::PageUp),
+ Key::ArrowDown => Some(ImguiKey::DownArrow),
+ Key::ArrowLeft => Some(ImguiKey::LeftArrow),
+ Key::ArrowRight => Some(ImguiKey::RightArrow),
+ Key::ArrowUp => Some(ImguiKey::UpArrow),
+ Key::NumLock => Some(ImguiKey::NumLock),
+ Key::Numpad0 => Some(ImguiKey::Keypad0),
+ Key::Numpad1 => Some(ImguiKey::Keypad1),
+ Key::Numpad2 => Some(ImguiKey::Keypad2),
+ Key::Numpad3 => Some(ImguiKey::Keypad3),
+ Key::Numpad4 => Some(ImguiKey::Keypad4),
+ Key::Numpad5 => Some(ImguiKey::Keypad5),
+ Key::Numpad6 => Some(ImguiKey::Keypad6),
+ Key::Numpad7 => Some(ImguiKey::Keypad7),
+ Key::Numpad8 => Some(ImguiKey::Keypad8),
+ Key::Numpad9 => Some(ImguiKey::Keypad9),
+ Key::NumpadAdd => Some(ImguiKey::KeypadAdd),
+ Key::NumpadDecimal => Some(ImguiKey::KeypadDecimal),
+ Key::NumpadDivide => Some(ImguiKey::KeypadDivide),
+ Key::NumpadEnter => Some(ImguiKey::KeypadEnter),
+ Key::NumpadEqual => Some(ImguiKey::KeypadEqual),
+ Key::NumpadMultiply => Some(ImguiKey::KeypadMultiply),
+ Key::NumpadSubtract => Some(ImguiKey::KeypadSubtract),
+ Key::Escape => Some(ImguiKey::Escape),
+ Key::PrintScreen => Some(ImguiKey::PrintScreen),
+ Key::ScrollLock => Some(ImguiKey::ScrollLock),
+ Key::Pause => Some(ImguiKey::Pause),
+ Key::Meta => Some(ImguiKey::ModSuper),
+ Key::F1 => Some(ImguiKey::F1),
+ Key::F2 => Some(ImguiKey::F2),
+ Key::F3 => Some(ImguiKey::F3),
+ Key::F4 => Some(ImguiKey::F4),
+ Key::F5 => Some(ImguiKey::F5),
+ Key::F6 => Some(ImguiKey::F6),
+ Key::F7 => Some(ImguiKey::F7),
+ Key::F8 => Some(ImguiKey::F8),
+ Key::F9 => Some(ImguiKey::F9),
+ Key::F10 => Some(ImguiKey::F10),
+ Key::F11 => Some(ImguiKey::F11),
+ Key::F12 => Some(ImguiKey::F12),
+ _ => None,
+ }
+}
+
+fn mouse_button_to_imgui_mouse_button(
+ mouse_button: MouseButton,
+) -> Option<ImguiMouseButton>
+{
+ match mouse_button {
+ MouseButton::Left | MouseButton::Other(0) => Some(ImguiMouseButton::Left),
+ MouseButton::Right | MouseButton::Other(1) => Some(ImguiMouseButton::Right),
+ MouseButton::Middle | MouseButton::Other(2) => Some(ImguiMouseButton::Middle),
+ MouseButton::Other(3) => Some(ImguiMouseButton::Extra1),
+ MouseButton::Other(4) => Some(ImguiMouseButton::Extra2),
+ _ => None,
+ }
+}
+
+mod inner_context_wrapper
+{
+ use std::path::PathBuf;
+ use std::pin::Pin;
+ use std::ptr::null_mut;
+
+ pub struct InnerContextWrapper
+ {
+ ctx: Pin<Box<dear_imgui_rs::Context>>,
+ frame: *mut dear_imgui_rs::Ui,
+ renderer_consumer: dear_imgui_rs::SynchronousRendererConsumer,
+ }
+
+ impl InnerContextWrapper
+ {
+ pub fn new() -> Self
+ {
+ let mut ctx = Box::pin(dear_imgui_rs::Context::create());
+
+ let _ = ctx.set_platform_name(Some("engine-windowing"));
+ let _ = ctx.set_renderer_name(Some("engine-rendering"));
+
+ ctx.io_mut()
+ .set_backend_flags(dear_imgui_rs::BackendFlags::RENDERER_HAS_TEXTURES);
+
+ let renderer_consumer = ctx.create_synchronous_renderer_consumer().unwrap();
+
+ Self {
+ ctx,
+ frame: null_mut(),
+ renderer_consumer,
+ }
+ }
+
+ pub fn set_settings_ini_file_path(
+ &mut self,
+ path: Option<PathBuf>,
+ ) -> Result<(), dear_imgui_rs::ImGuiError>
+ {
+ self.ctx.set_ini_filename(path)
+ }
+
+ pub fn register_texture(
+ &mut self,
+ texture_data: dear_imgui_rs::texture::OwnedTextureData,
+ ) -> dear_imgui_rs::ManagedTextureId
+ {
+ self.ctx.register_texture(texture_data)
+ }
+
+ pub fn get_io_mut(&mut self) -> &mut dear_imgui_rs::Io
+ {
+ self.ctx.io_mut()
+ }
+
+ pub fn font_atlas(&self) -> &dear_imgui_rs::FontAtlas
+ {
+ self.ctx.font_atlas()
+ }
+
+ // pub fn create_renderer_consumer(&mut self) -> dear_imgui_rs::RendererConsumer
+ // {
+ // self.ctx.create_renderer_consumer().unwrap()
+ // }
+
+ pub fn render(&mut self) -> dear_imgui_rs::PendingFrame<'_>
+ {
+ self.ctx.render(&self.renderer_consumer)
+ }
+
+ pub fn new_frame(&mut self)
+ {
+ let frame = &raw mut *self.ctx.frame();
+
+ self.frame = frame;
+ }
+
+ pub fn get_frame(&mut self) -> Option<&mut dear_imgui_rs::Ui>
+ {
+ unsafe { self.frame.as_mut() }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct TextureLookupId
+{
+ inner: u64,
+}
+
+impl TextureLookupId
+{
+ fn new() -> Self
+ {
+ static NEXT: AtomicU64 = AtomicU64::new(1);
+
+ Self {
+ inner: NEXT.fetch_add(1, Ordering::Relaxed),
+ }
+ }
+}
+
+impl intmap::IntKey for TextureLookupId
+{
+ type Int = u64;
+
+ const PRIME: Self::Int = <u64 as intmap::IntKey>::PRIME;
+
+ fn into_int(self) -> Self::Int
+ {
+ self.inner
+ }
+}
+
+impl From<dear_imgui_rs::TextureId> for TextureLookupId
+{
+ fn from(texture_id: dear_imgui_rs::TextureId) -> Self
+ {
+ assert!(!texture_id.is_null(), "Bad texture ID: is null");
+
+ Self { inner: texture_id.id() }
+ }
+}
+
+impl From<TextureLookupId> for dear_imgui_rs::TextureId
+{
+ fn from(texture_id: TextureLookupId) -> Self
+ {
+ Self::new(texture_id.inner)
+ }
+}