Skip to main content

dear_imgui_bevy/
texture.rs

1//! Texture interop resources for the Bevy backend.
2//!
3//! This module owns the main-world texture-facing API. Renderer feedback for ImGui-managed
4//! textures is queued here and applied on the next UI-thread frame, while Bevy `Handle<Image>`
5//! registrations are converted into stable legacy [`TextureId`](dear_imgui_rs::TextureId)
6//! values that render-world code can resolve through Bevy's `RenderAssets<GpuImage>`.
7
8use bevy_ecs::resource::Resource;
9use dear_imgui_rs as imgui;
10use std::sync::{Arc, Mutex};
11
12/// Main-world queue of managed texture feedback produced by the render world.
13#[derive(Resource, Debug, Clone, Default)]
14pub struct ImguiTextureFeedbackQueue {
15    feedback: Arc<Mutex<Vec<imgui::render::snapshot::TextureFeedback>>>,
16    last_applied: usize,
17}
18
19impl ImguiTextureFeedbackQueue {
20    /// Queue one managed texture feedback item to be applied before the next frame begins.
21    pub fn push(&self, feedback: imgui::render::snapshot::TextureFeedback) {
22        self.feedback
23            .lock()
24            .expect("ImguiTextureFeedbackQueue mutex poisoned")
25            .push(feedback);
26    }
27
28    /// Number of queued feedback items waiting for UI-thread application.
29    #[must_use]
30    pub fn len(&self) -> usize {
31        self.feedback
32            .lock()
33            .expect("ImguiTextureFeedbackQueue mutex poisoned")
34            .len()
35    }
36
37    /// Whether no feedback items are waiting.
38    #[must_use]
39    pub fn is_empty(&self) -> bool {
40        self.len() == 0
41    }
42
43    /// Number of texture entries updated by the most recent drain/apply pass.
44    #[must_use]
45    pub fn last_applied(&self) -> usize {
46        self.last_applied
47    }
48
49    pub(crate) fn drain(&self) -> Vec<imgui::render::snapshot::TextureFeedback> {
50        std::mem::take(
51            &mut *self
52                .feedback
53                .lock()
54                .expect("ImguiTextureFeedbackQueue mutex poisoned"),
55        )
56    }
57
58    pub(crate) fn set_last_applied(&mut self, applied: usize) {
59        self.last_applied = applied;
60    }
61}
62
63#[cfg(feature = "render")]
64mod render {
65    use super::*;
66    use bevy_asset::{AssetId, Handle};
67    use bevy_image::Image;
68    use std::collections::HashMap;
69
70    const BEVY_IMAGE_TEXTURE_NAMESPACE: u64 = 0x8000_0000_0000_0000;
71
72    /// Main-world registry that maps Bevy `Image` handles to Dear ImGui legacy texture ids.
73    #[derive(Resource, Debug)]
74    pub struct ImguiBevyTextures {
75        by_asset: HashMap<AssetId<Image>, imgui::TextureId>,
76        by_texture: HashMap<imgui::TextureId, AssetId<Image>>,
77        next_texture_id: u64,
78    }
79
80    impl Default for ImguiBevyTextures {
81        fn default() -> Self {
82            Self {
83                by_asset: HashMap::new(),
84                by_texture: HashMap::new(),
85                next_texture_id: BEVY_IMAGE_TEXTURE_NAMESPACE,
86            }
87        }
88    }
89
90    impl ImguiBevyTextures {
91        /// Register a Bevy image handle and return the Dear ImGui texture id used by `ui.image`.
92        ///
93        /// Registering the same handle repeatedly is idempotent and returns the same texture id.
94        pub fn register(&mut self, image: &Handle<Image>) -> imgui::TextureId {
95            let asset_id = image.id();
96            if let Some(texture_id) = self.by_asset.get(&asset_id) {
97                return *texture_id;
98            }
99
100            let texture_id = self.allocate_texture_id();
101            self.by_asset.insert(asset_id, texture_id);
102            self.by_texture.insert(texture_id, asset_id);
103            texture_id
104        }
105
106        /// Remove a registered image handle.
107        pub fn unregister(&mut self, image: &Handle<Image>) -> Option<imgui::TextureId> {
108            let texture_id = self.by_asset.remove(&image.id())?;
109            self.by_texture.remove(&texture_id);
110            Some(texture_id)
111        }
112
113        /// Resolve a registered Dear ImGui texture id back to the Bevy image asset id.
114        #[must_use]
115        pub fn asset_id(&self, texture_id: imgui::TextureId) -> Option<AssetId<Image>> {
116            self.by_texture.get(&texture_id).copied()
117        }
118
119        /// Iterate registered texture id to Bevy asset id pairs.
120        pub fn iter(&self) -> impl Iterator<Item = (imgui::TextureId, AssetId<Image>)> + '_ {
121            self.by_texture
122                .iter()
123                .map(|(texture_id, asset_id)| (*texture_id, *asset_id))
124        }
125
126        /// Number of registered Bevy image textures.
127        #[must_use]
128        pub fn len(&self) -> usize {
129            self.by_texture.len()
130        }
131
132        /// Whether no Bevy image textures are registered.
133        #[must_use]
134        pub fn is_empty(&self) -> bool {
135            self.by_texture.is_empty()
136        }
137
138        fn allocate_texture_id(&mut self) -> imgui::TextureId {
139            if self.next_texture_id < BEVY_IMAGE_TEXTURE_NAMESPACE {
140                self.next_texture_id = BEVY_IMAGE_TEXTURE_NAMESPACE;
141            }
142
143            loop {
144                let texture_id = imgui::TextureId::new(self.next_texture_id);
145                self.next_texture_id = self.next_texture_id.wrapping_add(1);
146                if self.next_texture_id < BEVY_IMAGE_TEXTURE_NAMESPACE {
147                    self.next_texture_id = BEVY_IMAGE_TEXTURE_NAMESPACE;
148                }
149                if !texture_id.is_null() && !self.by_texture.contains_key(&texture_id) {
150                    return texture_id;
151                }
152            }
153        }
154    }
155}
156
157#[cfg(feature = "render")]
158pub use render::ImguiBevyTextures;