From 6e3864ded2ca04cbe5918c6b3efc7d49d07d4afd Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 16 Jul 2026 17:33:13 +0200 Subject: feat(engine): use modern dear imgui texture system in dear imgui backend --- engine/src/ui/imgui.rs | 440 +++++++++++++++++++++++++++++++------------------ engine/src/util.rs | 5 + 2 files changed, 283 insertions(+), 162 deletions(-) (limited to 'engine') diff --git a/engine/src/ui/imgui.rs b/engine/src/ui/imgui.rs index 976d5ed..f9b4130 100644 --- a/engine/src/ui/imgui.rs +++ b/engine/src/ui/imgui.rs @@ -1,11 +1,11 @@ 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, - TextureId as ImguiTextureId, }; use ecs::component::local::Local; use ecs::component::Component; @@ -19,9 +19,14 @@ use ecs::system::Into; use ecs::{Component, Query, Sole}; use crate::asset::{Assets, Handle as AssetHandle, Label as AssetLabel}; +use crate::data_types::color::Rgba; use crate::data_types::dimens::Dimens; use crate::delta_time::DeltaTime; -use crate::image::Image; +use crate::image::{ + Image, + PixelBuffer as ImagePixelBuffer, + PixelBufferLenIncorrectForSize as ImagePixelBufferLenIncorrectForSize, +}; use crate::input::keyboard::{Key, Keyboard}; use crate::input::mouse::{Button as MouseButton, Buttons as MouseButtons, Mouse}; use crate::mesh::vertex_buffer::{ @@ -43,7 +48,7 @@ use crate::rendering::blending::{ Equation as RenderingBlendingEquation, Factor as RenderingBlendingFactor, }; -use crate::rendering::object::{Id as RenderingObjectId, Store as RenderingObjectStore}; +use crate::rendering::object::Id as RenderingObjectId; use crate::rendering::shader::cursor::{ BindingValue as ShaderBindingValue, Cursor as ShaderCursor, @@ -67,12 +72,8 @@ use crate::rendering::{ SurfaceSpec, PRE_RENDER_PHASE, }; -use crate::texture::{ - Filtering as TextureFiltering, - Properties as TextureProperties, - Texture, - Wrapping as TextureWrapping, -}; +use crate::texture::{Properties as TextureProperties, Texture}; +use crate::util::MapVec; use crate::vector::Vec2; use crate::windowing::dpi::PhysicalSize; use crate::windowing::window::Window; @@ -95,6 +96,7 @@ pub struct Context { pub enabled: bool, ctx: inner_context_wrapper::InnerContextWrapper, + texture_lookup: MapVec, } impl Context @@ -107,6 +109,11 @@ impl Context self.ctx.get_frame() } + + pub fn register_texture(&mut self, texture_data: &mut dear_imgui_rs::OwnedTextureData) + { + self.ctx.register_texture(texture_data); + } } #[derive(Debug, Default)] @@ -142,6 +149,7 @@ impl ecs::extension::Extension for Extension let mut context = Context { enabled: self.start_enabled, ctx: inner_context_wrapper::InnerContextWrapper::new(), + texture_lookup: MapVec::with_capacity(8), }; if let Err(err) = context @@ -206,7 +214,6 @@ fn update( delta_time: Single, mut assets: Single, mut render_passes: Single, - rendering_object_store: Single, shader_context: Single, keyboard: Single, mouse: Single, @@ -221,7 +228,6 @@ fn update( let delta_time = delta_time.get()?; let assets = assets.get_mut()?; let render_passes = render_passes.get_mut()?; - let rendering_object_store = rendering_object_store.get()?; let shader_context = shader_context.get()?; let keyboard = keyboard.get()?; let mouse = mouse.get()?; @@ -235,77 +241,25 @@ fn update( match curr_state { State::NotInitialized => { - let Some((font_texture_asset, shader_asset, mesh_obj_id, mesh)) = - initialize_context( - context, - &window, - window_surface_spec.id, - assets, - render_passes, - shader_context, - ) - else { + let Some((shader_asset, mesh_obj_id, mesh)) = initialize_context( + context, + &window, + window_surface_spec.id, + assets, + render_passes, + &shader_context, + ) else { *state = State::NotInitialized; return Ok(()); }; - *state = State::Initializing { - font_texture_asset, - shader_asset, - mesh_obj_id, - mesh, - }; - } - State::Initializing { - font_texture_asset, - shader_asset, - mesh_obj_id, - mesh, - } => { - let Some(font_texture_obj) = rendering_object_store - .get_obj(&RenderingObjectId::Asset(font_texture_asset.id())) - else { - *state = State::Initializing { - font_texture_asset, - shader_asset, - mesh_obj_id, - mesh, - }; - return Ok(()); - }; - - context - .ctx - .get_font_atlas_mut() - .set_texture_id(ImguiTextureId::new(font_texture_obj.as_raw().into())); - - context - .ctx - .get_io_mut() - .set_delta_time(delta_time.duration.as_secs() as f32); - context.ctx.new_frame(); - *state = State::Ready { - font_texture_asset, - shader_asset, - mesh_obj_id, - mesh, - }; + *state = State::Ready { shader_asset, mesh_obj_id, mesh }; } - State::Ready { - font_texture_asset, - shader_asset, - mesh_obj_id, - mut mesh, - } => { + State::Ready { shader_asset, mesh_obj_id, mut mesh } => { if !context.enabled { - *state = State::Ready { - font_texture_asset, - shader_asset, - mesh_obj_id, - mesh, - }; + *state = State::Ready { shader_asset, mesh_obj_id, mesh }; return Ok(()); } @@ -321,7 +275,6 @@ fn update( render_passes, shader_context, window_surface_spec.id, - font_texture_asset.clone(), shader_asset.clone(), mesh_obj_id, &mut mesh, @@ -329,12 +282,7 @@ fn update( context.ctx.new_frame(); - *state = State::Ready { - font_texture_asset, - shader_asset, - mesh_obj_id, - mesh, - }; + *state = State::Ready { shader_asset, mesh_obj_id, mesh }; } State::Unreachable => unreachable!(), } @@ -350,16 +298,8 @@ enum State { #[default] NotInitialized, - Initializing - { - font_texture_asset: AssetHandle, - shader_asset: AssetHandle, - mesh_obj_id: RenderingObjectId, - mesh: Mesh, - }, Ready { - font_texture_asset: AssetHandle, shader_asset: AssetHandle, mesh_obj_id: RenderingObjectId, mesh: Mesh, @@ -374,12 +314,7 @@ fn initialize_context( assets: &mut Assets, render_passes: &mut RenderPasses, shader_context: &ShaderContext, -) -> Option<( - AssetHandle, - AssetHandle, - RenderingObjectId, - Mesh, -)> +) -> Option<(AssetHandle, RenderingObjectId, Mesh)> { let shader_asset = if let Some(shader_asset) = assets.get_handle_to_loaded::(SHADER_ASSET_LABEL.clone()) @@ -421,27 +356,6 @@ fn initialize_context( .get_io_mut() .set_display_size([window_logical_size.width, window_logical_size.height]); - let mut fonts = context.ctx.get_font_atlas_mut(); - - if !fonts.is_built() { - fonts.build(); - } - - let Some(font_texture_data) = fonts.tex_data_mut() else { - tracing::error!("No texture data exists for font"); - return None; - }; - - let font_texture = match create_font_texture(font_texture_data) { - Ok(font_texture) => font_texture, - Err(err) => { - tracing::error!("Failed to create font texture: {err}"); - return None; - } - }; - - let font_texture_asset = assets.store_with_name("imgui_font_texture", font_texture); - let mesh_obj_id = RenderingObjectId::new_sequential(); let mesh = Mesh::builder() @@ -468,10 +382,6 @@ fn initialize_context( render_passes.passes.push_back(RenderPass { commands: vec![ RenderingCommand::MakeCurrent(window_surface_id), - RenderingCommand::CreateTexture( - RenderingObjectId::Asset(font_texture_asset.id()), - AssetOrValue::Asset(font_texture_asset.clone()), - ), RenderingCommand::CreateShaderProgram( RenderingObjectId::Asset(shader_asset.id()), shader_context @@ -489,7 +399,7 @@ fn initialize_context( draw_properties: DrawProperties::default(), }); - Some((font_texture_asset, shader_asset, mesh_obj_id, mesh)) + Some((shader_asset, mesh_obj_id, mesh)) } fn update_inputs( @@ -541,7 +451,6 @@ fn add_drawing_render_pass( render_passes: &mut RenderPasses, shader_context: &ShaderContext, window_surface_id: SurfaceId, - font_texture_asset: AssetHandle, shader_asset: AssetHandle, mesh_obj_id: RenderingObjectId, mesh: &mut Mesh, @@ -575,7 +484,132 @@ fn add_drawing_render_pass( .unwrap(), ); - let draw_data = context.ctx.render(); + let Context { enabled: _, ctx, texture_lookup } = context; + + let draw_data = ctx.render(); + + let mut textures = draw_data.textures_mut(); + + while let Some(mut texture_data) = textures.next() { + match texture_data.status() { + dear_imgui_rs::TextureStatus::WantCreate => { + let Some(texture_pixels) = texture_data.pixels() else { + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + }; + + if !matches!(texture_data.format(), dear_imgui_rs::TextureFormat::RGBA32) + { + unimplemented!(); + } + + let image = + match Image::from_pixels(ImagePixelBuffer::, _>::new( + texture_pixels, + Dimens { + width: texture_data.width(), + height: texture_data.height(), + }, + )) { + Ok(image) => image, + Err(ImagePixelBufferLenIncorrectForSize) => { + tracing::error!("Not enough texture pixels for texture size"); + continue; + } + }; + + let texture_object_id = RenderingObjectId::new_sequential(); + + render_pass.commands.push(RenderingCommand::CreateTexture( + texture_object_id, + AssetOrValue::Value(Texture { + image, + properties: TextureProperties::default(), + }), + )); + + let texture_lookup_id = TextureLookupId::new(); + + texture_data.set_tex_id(texture_lookup_id.into()); + + texture_lookup.insert(texture_lookup_id, texture_object_id); + + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + } + dear_imgui_rs::TextureStatus::WantUpdates => { + if texture_data.tex_id().is_null() { + tracing::error!(concat!( + "Update was requested for texture with a null ID. Did you forget ", + "to set the texture status to TextureStatus::WantCreate?")); + + continue; + } + + if texture_data.pixels().is_none() { + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + } + + let mut rects: Vec = + texture_data.updates().collect(); + + if rects.is_empty() { + let rect = texture_data.update_rect(); + if rect.w > 0 && rect.h > 0 { + rects.push(rect); + } + } + + if rects.is_empty() { + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + } + + let Some(texture_object_id) = + texture_lookup.get(&TextureLookupId::from(texture_data.tex_id())) + else { + tracing::error!( + "Unknown texture {}. Skipping update", + texture_data.tex_id().id() + ); + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + }; + + render_pass.commands.extend(rects.iter().filter_map(|rect| { + let Some(rect_image) = get_texture_rect_image(&texture_data, rect) + else { + return None; + }; + + Some(RenderingCommand::UpdateTexture { + obj_id: texture_object_id.clone(), + image: rect_image, + offset: Vec2 { x: rect.x as u32, y: rect.y as u32 }, + }) + })); + + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + } + dear_imgui_rs::TextureStatus::WantDestroy => { + let Some(texture_object_id) = + texture_lookup.remove(TextureLookupId::from(texture_data.tex_id())) + else { + tracing::error!( + "Unknown texture {}. Skipping destroyal", + texture_data.tex_id().id() + ); + texture_data.set_status(dear_imgui_rs::TextureStatus::OK); + continue; + }; + + render_pass + .commands + .push(RenderingCommand::RemoveTexture(texture_object_id)); + } + _ => {} + } + } let [display_width, display_height] = draw_data.display_size; @@ -605,10 +639,6 @@ fn add_drawing_render_pass( ), ), ), - RenderingCommand::SetShaderBinding( - shader_cursor.field("main_texture").into_binding_location(), - ShaderBindingValue::Texture(font_texture_asset.id().into()), - ), ]); for draw_list in draw_data.draw_lists() { @@ -641,18 +671,38 @@ fn add_drawing_render_pass( for command in draw_list.commands() { match command { - ImguiDrawCmd::Elements { count, cmd_params, raw_cmd: _ } => { + ImguiDrawCmd::Elements { count, cmd_params, raw_cmd } => { let Some(scissor_box) = calc_draw_cmd_scissor_box(draw_data, &cmd_params) else { continue; }; + let tex_id = dear_imgui_rs::TextureId::new(u64::from(unsafe { + let mut cmd_copy = *raw_cmd; + + dear_imgui_rs::sys::ImDrawCmd_GetTexID(&mut cmd_copy) + })); + + 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_cursor.field("main_texture").into_binding_location(), + ShaderBindingValue::Texture(texture_object_id.clone()), + ), RenderingCommand::DrawMesh( mesh_obj_id, DrawMeshOptions::builder() @@ -669,6 +719,59 @@ fn add_drawing_render_pass( } } +fn get_texture_rect_image( + texture_data: &dear_imgui_rs::TextureData, + rect: &dear_imgui_rs::TextureRect, +) -> Option +{ + let texture_pixels = texture_data.pixels()?; + + let tex_width = texture_data.width(); + let tex_height = texture_data.height(); + + if tex_width == 0 || tex_height == 0 { + return None; + } + + let rect_pos = Vec2:: { x: rect.x.into(), y: rect.y.into() }; + + let rect_size = Dimens:: { + width: rect.w.into(), + height: rect.h.into(), + }; + + if rect_size.width == 0 + || rect_size.height == 0 + || rect_pos.x >= tex_width + || rect_pos.y >= tex_height + { + return None; + } + + let rect_size = Dimens { + width: rect_size.width.min(tex_width.saturating_sub(rect_pos.x)), + height: rect_size.height.min(tex_height.saturating_sub(rect_pos.y)), + }; + + match texture_data.format() { + dear_imgui_rs::TextureFormat::RGBA32 => { + let Ok(texture_image) = + Image::from_pixels(ImagePixelBuffer::, _>::new( + texture_pixels, + Dimens { width: tex_width, height: tex_height }, + )) + else { + return None; + }; + + Some(texture_image.sub_view(rect_pos, rect_size).to_image()) + } + dear_imgui_rs::TextureFormat::Alpha8 => { + unimplemented!(); + } + } +} + fn calc_draw_cmd_scissor_box( draw_data: &dear_imgui_rs::DrawData, cmd_params: &dear_imgui_rs::DrawCmdParams, @@ -706,35 +809,6 @@ fn calc_draw_cmd_scissor_box( }) } -fn create_font_texture( - font_texture_data: &dear_imgui_rs::texture::TextureData, -) -> Result -{ - Ok(Texture { - image: Image::from_pixels(crate::image::PixelBuffer::< - crate::data_types::color::Rgba, - _, - >::new( - font_texture_data - .pixels() - .expect("Font texture data does not contain any pixels"), - Dimens { - width: font_texture_data.width().try_into().expect( - "Font texture width value does not fit in 32-bit unsigned int", - ), - height: font_texture_data.height().try_into().expect( - "Font texture height value does not fit in 32-bit unsigned int", - ), - }, - ))?, - properties: TextureProperties::builder() - .minifying_filter(TextureFiltering::Linear) - .magnifying_filter(TextureFiltering::Linear) - .wrap(TextureWrapping::ClampToBorder) - .build(), - }) -} - fn key_to_imgui_key(key: Key) -> Option { match key { @@ -882,6 +956,9 @@ mod inner_context_wrapper 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); + Self { ctx, frame: null_mut() } } @@ -893,17 +970,20 @@ mod inner_context_wrapper self.ctx.set_ini_filename(path) } - pub fn get_io_mut(&mut self) -> &mut dear_imgui_rs::Io + pub fn register_texture( + &mut self, + texture_data: &mut dear_imgui_rs::texture::OwnedTextureData, + ) { - self.ctx.io_mut() + self.ctx.register_user_texture(texture_data); } - pub fn get_font_atlas_mut(&mut self) -> dear_imgui_rs::fonts::FontAtlas + pub fn get_io_mut(&mut self) -> &mut dear_imgui_rs::Io { - self.ctx.font_atlas_mut() + self.ctx.io_mut() } - pub fn render(&mut self) -> &dear_imgui_rs::DrawData + pub fn render(&mut self) -> &mut dear_imgui_rs::DrawData { self.ctx.render() } @@ -921,3 +1001,39 @@ mod inner_context_wrapper } } } + +#[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 From 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 for dear_imgui_rs::TextureId +{ + fn from(texture_id: TextureLookupId) -> Self + { + Self::new(texture_id.inner) + } +} diff --git a/engine/src/util.rs b/engine/src/util.rs index 08e2227..a8ee893 100644 --- a/engine/src/util.rs +++ b/engine/src/util.rs @@ -10,6 +10,11 @@ pub struct MapVec impl MapVec { + pub fn with_capacity(capacity: usize) -> Self + { + Self { inner: Vec::with_capacity(capacity) } + } + pub fn insert(&mut self, key: Key, value: Value) { self.inner -- cgit v1.2.3-18-g5258