Skip to main content

dear_imgui_wgpu/renderer/
init.rs

1use super::{
2    WgpuRenderer,
3    callbacks::{
4        draw_callback_reset_render_state, draw_callback_set_sampler_linear,
5        draw_callback_set_sampler_nearest,
6    },
7};
8use crate::wgpu;
9use crate::{
10    GammaMode, RendererError, RendererResult, ShaderManager, WgpuBackendData, WgpuInitInfo,
11    WgpuTextureManager,
12};
13use dear_imgui_rs::{BackendFlags, Context, TextureId, sys};
14use wgpu::*;
15
16impl WgpuRenderer {
17    /// Create a new WGPU renderer with full initialization (recommended)
18    ///
19    /// This is the preferred way to create a WGPU renderer as it ensures proper
20    /// initialization order and is consistent with other backends.
21    ///
22    /// # Arguments
23    /// * `init_info` - WGPU initialization information (device, queue, format)
24    /// * `imgui_ctx` - Dear ImGui context to configure
25    ///
26    /// # Example
27    /// ```rust,no_run
28    /// use dear_imgui_rs::Context;
29    /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
30    ///
31    /// # fn main() -> Result<(), dear_imgui_wgpu::RendererError> {
32    /// # let (device, queue) = todo!("initialize a WGPU Device/Queue");
33    /// # let surface_format = wgpu::TextureFormat::Bgra8UnormSrgb;
34    /// # let mut imgui_context = Context::create();
35    /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
36    /// let mut renderer = WgpuRenderer::new(init_info, &mut imgui_context)?;
37    /// # Ok(()) }
38    /// ```
39    pub fn new(init_info: WgpuInitInfo, imgui_ctx: &mut Context) -> RendererResult<Self> {
40        // Native and wasm experimental path: fully configure context, including font atlas.
41        #[cfg(any(
42            not(target_arch = "wasm32"),
43            all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental")
44        ))]
45        {
46            let mut renderer = Self::empty();
47            renderer.init_with_context(init_info, imgui_ctx)?;
48            Ok(renderer)
49        }
50
51        // Default wasm path: skip font atlas manipulation for safety.
52        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
53        {
54            Self::new_without_font_atlas(init_info, imgui_ctx)
55        }
56    }
57
58    /// Create an empty WGPU renderer for advanced usage
59    ///
60    /// This creates an uninitialized renderer that must be initialized later
61    /// using `init_with_context()`. Most users should use `new()` instead.
62    ///
63    /// # Example
64    /// ```rust,no_run
65    /// use dear_imgui_rs::Context;
66    /// use dear_imgui_wgpu::{WgpuRenderer, WgpuInitInfo};
67    ///
68    /// # fn main() -> Result<(), dear_imgui_wgpu::RendererError> {
69    /// # let (device, queue) = todo!("initialize a WGPU Device/Queue");
70    /// # let surface_format = wgpu::TextureFormat::Bgra8UnormSrgb;
71    /// # let mut imgui_context = Context::create();
72    /// let mut renderer = WgpuRenderer::empty();
73    /// let init_info = WgpuInitInfo::new(device, queue, surface_format);
74    /// renderer.init_with_context(init_info, &mut imgui_context)?;
75    /// # Ok(()) }
76    /// ```
77    pub fn empty() -> Self {
78        Self {
79            backend_data: None,
80            shader_manager: ShaderManager::new(),
81            texture_manager: WgpuTextureManager::new(),
82            default_texture: None,
83            gamma_mode: GammaMode::Auto,
84            #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
85            viewport_clear_color: Color::BLACK,
86        }
87    }
88
89    /// Initialize the renderer
90    ///
91    /// This corresponds to ImGui_ImplWGPU_Init in the C++ implementation
92    pub fn init(&mut self, init_info: WgpuInitInfo) -> RendererResult<()> {
93        // Create backend data
94        let mut backend_data = WgpuBackendData::new(init_info);
95
96        // Preflight: ensure the render target format is render-attachable and blendable.
97        // The ImGui pipeline always uses alpha blending; non-blendable formats will
98        // fail validation later with less actionable errors.
99        let fmt = backend_data.render_target_format;
100        if let Some(adapter) = backend_data.adapter.as_ref() {
101            let fmt_features = adapter.get_texture_format_features(fmt);
102            if !fmt_features
103                .allowed_usages
104                .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
105                || !fmt_features
106                    .flags
107                    .contains(wgpu::TextureFormatFeatureFlags::BLENDABLE)
108            {
109                return Err(RendererError::InvalidRenderState(format!(
110                    "Render target format {:?} is not suitable for ImGui WGPU renderer (requires RENDER_ATTACHMENT + BLENDABLE). allowed_usages={:?} flags={:?}",
111                    fmt, fmt_features.allowed_usages, fmt_features.flags
112                )));
113            }
114        }
115
116        // Initialize render resources
117        backend_data
118            .render_resources
119            .initialize(&backend_data.device)?;
120
121        // Initialize shaders
122        self.shader_manager.initialize(&backend_data.device)?;
123
124        // Create default texture (1x1 white pixel)
125        let default_texture =
126            self.create_default_texture(&backend_data.device, &backend_data.queue)?;
127        self.default_texture = Some(default_texture);
128
129        // Create device objects (pipeline, etc.)
130        self.create_device_objects(&mut backend_data)?;
131
132        self.backend_data = Some(backend_data);
133        Ok(())
134    }
135
136    /// Initialize the renderer with ImGui context configuration (without font atlas for WASM)
137    ///
138    /// This is a variant of init_with_context that skips font atlas preparation,
139    /// useful for WASM builds where font atlas memory sharing is problematic.
140    pub fn new_without_font_atlas(
141        init_info: WgpuInitInfo,
142        imgui_ctx: &mut Context,
143    ) -> RendererResult<Self> {
144        let mut renderer = Self::empty();
145
146        // First initialize the renderer
147        renderer.init(init_info)?;
148
149        // Then configure the ImGui context with backend capabilities
150        renderer.configure_imgui_context(imgui_ctx);
151
152        // Skip font atlas preparation for WASM
153        // The default font will be used automatically by Dear ImGui
154
155        Ok(renderer)
156    }
157
158    /// Initialize the renderer with ImGui context configuration
159    ///
160    /// This is a convenience method that combines init() and configure_imgui_context()
161    /// to ensure proper initialization order, similar to the glow backend approach.
162    pub fn init_with_context(
163        &mut self,
164        init_info: WgpuInitInfo,
165        imgui_ctx: &mut Context,
166    ) -> RendererResult<()> {
167        // First initialize the renderer
168        self.init(init_info)?;
169
170        // Then configure the ImGui context with backend capabilities
171        // This must be done BEFORE preparing the font atlas
172        self.configure_imgui_context(imgui_ctx);
173
174        // Finally prepare the font atlas
175        self.prepare_font_atlas(imgui_ctx)?;
176
177        Ok(())
178    }
179
180    /// Set gamma mode
181    pub fn set_gamma_mode(&mut self, mode: GammaMode) {
182        self.gamma_mode = mode;
183    }
184
185    /// Set clear color for secondary viewports (multi-viewport mode).
186    ///
187    /// This color is used as the load/clear color when rendering ImGui-created
188    /// platform windows via `RenderPlatformWindowsDefault`. It is independent
189    /// from whatever clear color your main swapchain uses.
190    #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
191    pub fn set_viewport_clear_color(&mut self, color: Color) {
192        self.viewport_clear_color = color;
193    }
194
195    /// Get current clear color for secondary viewports.
196    #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
197    pub fn viewport_clear_color(&self) -> Color {
198        self.viewport_clear_color
199    }
200
201    /// Configure Dear ImGui context with WGPU backend capabilities
202    pub fn configure_imgui_context(&self, imgui_context: &mut Context) {
203        let should_set_name = imgui_context.io().backend_renderer_name().is_none();
204        if should_set_name {
205            let _ = imgui_context.set_renderer_name(Some(format!(
206                "dear-imgui-wgpu {}",
207                env!("CARGO_PKG_VERSION")
208            )));
209        }
210
211        let io = imgui_context.io_mut();
212        let mut flags = io.backend_flags();
213
214        // Set WGPU renderer capabilities
215        // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
216        flags.insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
217        // We can honor ImGuiPlatformIO::Textures[] requests during render.
218        flags.insert(BackendFlags::RENDERER_HAS_TEXTURES);
219
220        #[cfg(any(feature = "multi-viewport-winit", feature = "multi-viewport-sdl3"))]
221        {
222            // We can render additional platform windows
223            flags.insert(BackendFlags::RENDERER_HAS_VIEWPORTS);
224        }
225
226        io.set_backend_flags(flags);
227
228        let platform_io = imgui_context.platform_io_mut();
229        platform_io
230            .set_draw_callback_reset_render_state_raw(Some(draw_callback_reset_render_state));
231        platform_io
232            .set_draw_callback_set_sampler_linear_raw(Some(draw_callback_set_sampler_linear));
233        platform_io
234            .set_draw_callback_set_sampler_nearest_raw(Some(draw_callback_set_sampler_nearest));
235    }
236
237    /// Prepare font atlas for rendering
238    pub fn prepare_font_atlas(&mut self, imgui_ctx: &mut Context) -> RendererResult<()> {
239        if let Some(backend_data) = &self.backend_data {
240            let device = backend_data.device.clone();
241            let queue = backend_data.queue.clone();
242            self.reload_font_texture(imgui_ctx, &device, &queue)?;
243            if imgui_ctx
244                .io()
245                .backend_flags()
246                .contains(BackendFlags::RENDERER_HAS_TEXTURES)
247            {
248                // New backend texture system: font textures are produced via DrawData::textures()
249                // requests; do not assign a legacy TexID.
250                return Ok(());
251            }
252
253            // Legacy fallback: only upload when the atlas does not already resolve to a live
254            // WGPU texture. This keeps the backend idempotent without carrying a separate
255            // renderer-side font texture cache now that the managed ImTextureData path is the
256            // primary mode.
257            let mut tex_ref = imgui_ctx.font_atlas().get_tex_ref();
258            let existing_tex_id = unsafe { sys::ImTextureRef_GetTexID(&mut tex_ref) };
259            let existing_tex_id = TextureId::from(existing_tex_id);
260            let has_live_font_texture = !existing_tex_id.is_null()
261                && self.texture_manager.contains_texture(existing_tex_id);
262
263            if !has_live_font_texture
264                && let Some(tex_id) =
265                    self.try_upload_font_atlas_legacy(imgui_ctx, &device, &queue)?
266                && cfg!(debug_assertions)
267            {
268                tracing::debug!(
269                    target: "dear-imgui-wgpu",
270                    "[dear-imgui-wgpu][debug] Font atlas uploaded via legacy fallback path. tex_id={}",
271                    tex_id.id()
272                );
273            }
274        }
275        Ok(())
276    }
277
278    // create_device_objects moved to renderer/pipeline.rs
279
280    /// Create a default 1x1 white texture
281    fn create_default_texture(
282        &self,
283        device: &Device,
284        queue: &Queue,
285    ) -> RendererResult<TextureView> {
286        let texture = device.create_texture(&TextureDescriptor {
287            label: Some("Dear ImGui Default Texture"),
288            size: Extent3d {
289                width: 1,
290                height: 1,
291                depth_or_array_layers: 1,
292            },
293            mip_level_count: 1,
294            sample_count: 1,
295            dimension: TextureDimension::D2,
296            format: TextureFormat::Rgba8Unorm,
297            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
298            view_formats: &[],
299        });
300
301        // Upload white pixel
302        queue.write_texture(
303            wgpu::TexelCopyTextureInfo {
304                texture: &texture,
305                mip_level: 0,
306                origin: wgpu::Origin3d::ZERO,
307                aspect: wgpu::TextureAspect::All,
308            },
309            &[255u8, 255u8, 255u8, 255u8], // RGBA white
310            wgpu::TexelCopyBufferLayout {
311                offset: 0,
312                bytes_per_row: Some(4),
313                rows_per_image: Some(1),
314            },
315            Extent3d {
316                width: 1,
317                height: 1,
318                depth_or_array_layers: 1,
319            },
320        );
321
322        Ok(texture.create_view(&TextureViewDescriptor::default()))
323    }
324}
325
326impl Default for WgpuRenderer {
327    fn default() -> Self {
328        Self::empty()
329    }
330}