summaryrefslogtreecommitdiff
path: root/engine/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/ui')
-rw-r--r--engine/src/ui/imgui.rs440
1 files changed, 278 insertions, 162 deletions
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<TextureLookupId, RenderingObjectId>,
}
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<DeltaTime>,
mut assets: Single<Assets>,
mut render_passes: Single<RenderPasses>,
- rendering_object_store: Single<RenderingObjectStore>,
shader_context: Single<ShaderContext>,
keyboard: Single<Keyboard>,
mouse: Single<Mouse>,
@@ -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<Texture>,
- shader_asset: AssetHandle<ShaderModuleSource>,
- mesh_obj_id: RenderingObjectId,
- mesh: Mesh,
- },
Ready
{
- font_texture_asset: AssetHandle<Texture>,
shader_asset: AssetHandle<ShaderModuleSource>,
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<Texture>,
- AssetHandle<ShaderModuleSource>,
- RenderingObjectId,
- Mesh,
-)>
+) -> Option<(AssetHandle<ShaderModuleSource>, RenderingObjectId, Mesh)>
{
let shader_asset = if let Some(shader_asset) =
assets.get_handle_to_loaded::<ShaderModuleSource>(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<Texture>,
shader_asset: AssetHandle<ShaderModuleSource>,
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::<Rgba<u8>, _>::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<dear_imgui_rs::texture::TextureRect> =
+ 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<Image>
+{
+ 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::<u32> { x: rect.x.into(), y: rect.y.into() };
+
+ let rect_size = Dimens::<u32> {
+ 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::<Rgba<u8>, _>::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<Texture, crate::image::PixelBufferLenIncorrectForSize>
-{
- Ok(Texture {
- image: Image::from_pixels(crate::image::PixelBuffer::<
- crate::data_types::color::Rgba<u8>,
- _,
- >::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<ImguiKey>
{
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<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)
+ }
+}