dear-imgui-sdl3 0.16.0

SDL3 platform backend with optional OpenGL3, SDLRenderer3, and SDLGPU3 renderers for dear-imgui-rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
use super::*;
#[cfg(any(
    feature = "opengl3-renderer",
    feature = "sdlrenderer3-renderer",
    feature = "sdlgpu3-renderer"
))]
use dear_imgui_rs::sys;

pub(super) fn with_context<R>(imgui: &Context, caller: &str, f: impl FnOnce() -> R) -> R {
    imgui
        .binding()
        .try_with_bound_context(f)
        .unwrap_or_else(|error| panic!("{caller} could not bind its Dear ImGui context: {error}"))
}

#[cfg(any(
    feature = "opengl3-renderer",
    feature = "sdlrenderer3-renderer",
    feature = "sdlgpu3-renderer"
))]
pub(super) fn assert_current_draw_data(draw_data: &DrawData, caller: &str) {
    let expected = unsafe { sys::igGetDrawData() as *const sys::ImDrawData };
    let actual = draw_data as *const DrawData as *const sys::ImDrawData;
    assert_eq!(
        expected, actual,
        "{caller} received draw data that does not belong to the captured Dear ImGui context"
    );
}

/// FFI bindings to the C wrappers defined in `wrapper.cpp`.
pub(super) mod ffi {
    use super::*;

    #[cfg(feature = "sdlgpu3-renderer")]
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub(crate) struct ImGuiImplSdlGpu3InitInfo {
        pub device: *mut SDL_GPUDevice,
        pub color_target_format: SDL_GPUTextureFormat,
        pub msaa_samples: SDL_GPUSampleCount,
        pub swapchain_composition: SDL_GPUSwapchainComposition,
        pub present_mode: SDL_GPUPresentMode,
    }

    unsafe extern "C" {
        #[cfg(test)]
        pub fn dear_imgui_sdl3_backend_sizeof_imgui_io() -> usize;
        #[cfg(all(test, debug_assertions))]
        pub fn dear_imgui_sdl3_native_contract_self_test() -> u64;
        #[cfg(all(
            test,
            debug_assertions,
            any(
                feature = "opengl3-renderer",
                feature = "sdlrenderer3-renderer",
                feature = "sdlgpu3-renderer"
            )
        ))]
        pub fn dear_imgui_sdl3_destroy_platform_windows_for_test(
            viewport: *mut sys::ImGuiViewportP,
        );
        #[cfg(test)]
        pub fn dear_imgui_sdl3_mouse_leave_due_for_test(
            pending_frame: i32,
            current_frame: i32,
            buttons_down: i32,
        ) -> bool;
        pub fn ImGui_ImplSDL3_InitForOpenGL_Rust(
            window: *mut sdl3_sys::video::SDL_Window,
            sdl_gl_context: *mut c_void,
        ) -> bool;
        pub fn ImGui_ImplSDL3_InitForVulkan_Rust(window: *mut sdl3_sys::video::SDL_Window) -> bool;
        pub fn ImGui_ImplSDL3_InitForD3D_Rust(window: *mut sdl3_sys::video::SDL_Window) -> bool;
        pub fn ImGui_ImplSDL3_InitForMetal_Rust(window: *mut sdl3_sys::video::SDL_Window) -> bool;
        pub fn ImGui_ImplSDL3_InitForSDLRenderer_Rust(
            window: *mut sdl3_sys::video::SDL_Window,
            renderer: *mut sdl3_sys::render::SDL_Renderer,
        ) -> bool;
        pub fn ImGui_ImplSDL3_InitForSDLGPU_Rust(window: *mut sdl3_sys::video::SDL_Window) -> bool;
        pub fn ImGui_ImplSDL3_InitForOther_Rust(window: *mut sdl3_sys::video::SDL_Window) -> bool;
        pub fn ImGui_ImplSDL3_Shutdown_Rust();
        pub fn ImGui_ImplSDL3_NewFrame_Rust();
        pub fn ImGui_ImplSDL3_ProcessEvent_Rust(event: *const SDL_Event) -> bool;
        pub fn dear_imgui_sdl3_native_begin(
            phase: u32,
            expects_opengl: u32,
            swap_interval_policy: u32,
            explicit_swap_interval: i32,
            viewport: *mut dear_imgui_rs::sys::ImGuiViewport,
        ) -> u64;
        pub fn dear_imgui_sdl3_native_end(first_fault: *mut u64) -> u64;
        pub fn dear_imgui_sdl3_backend_clear_platform_monitors();
        #[cfg(any(
            feature = "opengl3-renderer",
            feature = "sdlrenderer3-renderer",
            feature = "sdlgpu3-renderer"
        ))]
        pub fn dear_imgui_sdl3_backend_set_texture_updates(
            texture: *mut sys::ImTextureData,
            updates: *const sys::ImTextureRect,
            update_count: i32,
        );

        pub fn ImGui_ImplSDL3_SetGamepadMode_AutoFirst_Rust();
        pub fn ImGui_ImplSDL3_SetGamepadMode_AutoAll_Rust();
        pub fn ImGui_ImplSDL3_SetGamepadMode_Manual_Rust(
            manual_gamepads_array: *const *mut sdl3_sys::gamepad::SDL_Gamepad,
            manual_gamepads_count: i32,
        );
        pub fn ImGui_ImplSDL3_SetMouseCaptureMode_Enabled_Rust();
        pub fn ImGui_ImplSDL3_SetMouseCaptureMode_EnabledAfterDrag_Rust();
        pub fn ImGui_ImplSDL3_SetMouseCaptureMode_Disabled_Rust();

        #[cfg(feature = "sdlrenderer3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlrenderer3_init(renderer: *mut SDL_Renderer) -> bool;
        #[cfg(feature = "sdlrenderer3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlrenderer3_shutdown();
        #[cfg(feature = "sdlrenderer3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlrenderer3_new_frame();
        #[cfg(feature = "sdlrenderer3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlrenderer3_render_draw_data(
            draw_data: *mut sys::ImDrawData,
            renderer: *mut SDL_Renderer,
        );
        #[cfg(feature = "sdlrenderer3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlrenderer3_destroy_device_objects();
        #[cfg(feature = "sdlrenderer3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlrenderer3_update_texture(
            texture: *mut sys::ImTextureData,
        );

        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_init(info: *mut ImGuiImplSdlGpu3InitInfo) -> bool;
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_shutdown();
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_new_frame();
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_prepare_draw_data(
            draw_data: *mut sys::ImDrawData,
            command_buffer: *mut SDL_GPUCommandBuffer,
        );
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_render_draw_data(
            draw_data: *mut sys::ImDrawData,
            command_buffer: *mut SDL_GPUCommandBuffer,
            render_pass: *mut SDL_GPURenderPass,
            pipeline: *mut SDL_GPUGraphicsPipeline,
        );
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_render_viewport(
            viewport: *mut sys::ImGuiViewport,
            first_fault: *mut u64,
        ) -> u64;
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_create_device_objects();
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_destroy_device_objects();
        #[cfg(feature = "sdlgpu3-renderer")]
        pub fn dear_imgui_sdl3_backend_sdlgpu3_update_texture(texture: *mut sys::ImTextureData);
    }
}

/// Swap-interval policy applied when SDL3 creates a secondary OpenGL context.
///
/// [`Immediate`](Self::Immediate) matches the upstream multi-viewport default and avoids serial
/// VSync waits when several platform windows are presented in one frame.
///
/// Drivers may reject a requested interval after creating a secondary context. That is treated as
/// a presentation fallback to the driver's default rather than a viewport-creation failure.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Sdl3OpenGlViewportSwapInterval {
    /// Disable VSync for secondary viewport contexts.
    #[default]
    Immediate,
    /// Wait for one vertical refresh when swapping each secondary viewport.
    VSync,
    /// Request adaptive VSync (`-1`) where the SDL video driver supports it.
    Adaptive,
    /// Read and copy the main OpenGL context's current swap interval at viewport creation.
    MatchMain,
}

impl Sdl3OpenGlViewportSwapInterval {
    pub(crate) fn native_policy(self) -> (u32, i32) {
        match self {
            Self::Immediate => (0, 0),
            Self::VSync => (0, 1),
            Self::Adaptive => (0, -1),
            Self::MatchMain => (1, 0),
        }
    }
}

/// Errors reported by the SDL3 platform and optional renderer runtimes.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Sdl3BackendError {
    #[error("{entry_point} returned false")]
    PlatformInitFailed { entry_point: &'static str },
    #[error("ImGui_ImplOpenGL3_Init returned false")]
    OpenGlRendererInitFailed,
    #[error("Invalid GLSL version string")]
    InvalidGlslVersion,
    #[error("ImGui_ImplSDLRenderer3_Init returned false")]
    SdlRenderer3InitFailed,
    #[error("ImGui_ImplSDLGPU3_Init returned false")]
    SdlGpu3InitFailed,
    #[error("SDL3 runtime belongs to Context {expected:?}, but received Context {actual:?}")]
    ContextMismatch {
        expected: dear_imgui_rs::ContextId,
        actual: dear_imgui_rs::ContextId,
    },
    #[error("another SDL3 platform runtime already owns the process-wide SDL session")]
    PlatformSessionOccupied,
    #[error("SDL3 text input contains an interior NUL byte")]
    TextInputContainsNul,
    #[error(transparent)]
    Attachment(#[from] dear_imgui_rs::ContextAttachmentError),
    #[error(transparent)]
    PlatformAttachmentRelease(#[from] dear_imgui_rs::ContextPlatformAttachmentReleaseError),
    #[error(transparent)]
    Context(#[from] dear_imgui_rs::ContextBindingError),
    #[error("another platform backend already owns `{callback}`")]
    PlatformCallbackOccupied { callback: &'static str },
    #[error("another platform backend already owns `{field}`")]
    PlatformStateOccupied { field: &'static str },
    #[error("another platform backend already owns SDL3-reserved capability bits {flags:#x}")]
    PlatformCapabilityOccupied { flags: i32 },
    #[error("another renderer backend already owns `{callback}`")]
    RendererCallbackOccupied { callback: &'static str },
    #[error("another renderer backend already owns `{field}`")]
    RendererStateOccupied { field: &'static str },
    #[error("another renderer backend already owns SDL3-reserved capability bits {flags:#x}")]
    RendererCapabilityOccupied { flags: i32 },
    #[error("SDL3 platform callback `{callback}` was replaced while the runtime was attached")]
    PlatformCallbackReplaced { callback: &'static str },
    #[error("SDL3-owned platform state `{field}` was replaced while the runtime was attached")]
    PlatformStateReplaced { field: &'static str },
    #[error("SDL3 renderer callback `{callback}` was replaced while the runtime was attached")]
    RendererCallbackReplaced { callback: &'static str },
    #[error("SDL3-owned renderer state `{field}` was replaced while the runtime was attached")]
    RendererStateReplaced { field: &'static str },
    #[error("SDL3 platform callback `{callback}` panicked")]
    PlatformCallbackPanicked { callback: &'static str },
    #[error("another platform backend already owns BackendPlatformUserData")]
    PlatformBackendOccupied,
    #[error("viewport PlatformUserData was replaced by another platform backend")]
    ForeignPlatformUserData,
    #[error("SDL3 failed to create a secondary viewport window")]
    ViewportCreationFailed,
    #[error("SDL3 failed to capture the OpenGL state required for viewport creation")]
    ViewportOpenGlStateCaptureFailed,
    #[error("SDL3 failed to enable OpenGL context sharing for a secondary viewport")]
    ViewportOpenGlShareConfigurationFailed,
    #[error("SDL3 failed to create or activate a distinct OpenGL context for a secondary viewport")]
    ViewportOpenGlContextFailed,
    #[error(
        "SDL3 failed to maintain the OpenGL swap-interval transaction for a secondary viewport"
    )]
    ViewportOpenGlSwapIntervalFailed,
    #[error("SDL3 failed to restore the previous OpenGL window, context, or share attribute")]
    ViewportOpenGlStateRestoreFailed,
    #[error("SDL3 failed to activate the OpenGL context required to render a secondary viewport")]
    ViewportOpenGlRenderContextFailed,
    #[error("SDL3 failed to activate or swap a secondary viewport OpenGL window")]
    ViewportOpenGlSwapFailed,
    #[error("SDL3 failed to claim a secondary viewport window for the SDL GPU device")]
    ViewportSdlGpuClaimFailed,
    #[error("SDL3 failed to configure a secondary viewport SDL GPU swapchain")]
    ViewportSdlGpuConfigureFailed,
    #[error("SDL3 failed to acquire a command buffer for a secondary viewport")]
    ViewportSdlGpuCommandBufferFailed,
    #[error("SDL3 failed to acquire a swapchain texture for a secondary viewport")]
    ViewportSdlGpuSwapchainFailed,
    #[error(
        "SDL3 failed to cancel a command buffer after secondary viewport swapchain acquisition failed"
    )]
    ViewportSdlGpuCommandBufferCancelFailed,
    #[error("SDL3 failed to begin a render pass for a secondary viewport")]
    ViewportSdlGpuRenderPassFailed,
    #[error("SDL3 failed to submit a command buffer for a secondary viewport")]
    ViewportSdlGpuSubmitFailed,
    #[error("SDLRenderer3 received a WindowCanvas other than the renderer used at initialization")]
    RendererMismatch,
    #[error("the SDL3 native callback bridge observed a reentrant or unbalanced transaction")]
    NativeBridgeProtocolFailed,
    #[error("Dear ImGui platform state is unavailable")]
    PlatformStateUnavailable,
    #[error("the SDL3 runtime is no longer attached")]
    RuntimeDetached,
    #[cfg(feature = "multi-viewport")]
    #[error("the SDL3 platform backend was not initialized with init_for_vulkan")]
    VulkanSurfaceProviderRequiresVulkan,
    #[cfg(feature = "multi-viewport")]
    #[error("the SDL3 Vulkan surface provider is already leased by a renderer")]
    VulkanSurfaceProviderAlreadyLeased,
    #[cfg(feature = "multi-viewport")]
    #[error("the SDL3 Vulkan platform runtime has no Platform_CreateVkSurface callback")]
    VulkanSurfaceCallbackUnavailable,
    #[cfg(feature = "multi-viewport")]
    #[error(
        "the SDL3 platform backend cannot shut down while its Vulkan surface provider is leased"
    )]
    VulkanSurfaceProviderActive,
    #[error("SDL3 shutdown panicked while releasing {phase}")]
    ShutdownPanicked { phase: &'static str },
    #[error("SDL3 shutdown is already releasing {phase}")]
    ShutdownInProgress { phase: &'static str },
    #[error(transparent)]
    RendererConsumer(#[from] dear_imgui_rs::render::RendererConsumerError),
    #[error(transparent)]
    FrameCapture(#[from] dear_imgui_rs::render::SnapshotError),
    #[error(transparent)]
    TextureFeedback(#[from] dear_imgui_rs::render::TextureFeedbackError),
    #[error("managed texture {texture:?} received an update before renderer creation")]
    ManagedTextureNotCreated {
        texture: dear_imgui_rs::render::SnapshotTextureId,
    },
    #[error("managed texture {texture:?} request is invalid: {reason}")]
    InvalidTextureRequest {
        texture: dear_imgui_rs::render::SnapshotTextureId,
        reason: &'static str,
    },
    #[error("managed texture {texture:?} uses unsupported format {format:?}")]
    UnsupportedTextureFormat {
        texture: dear_imgui_rs::render::SnapshotTextureId,
        format: dear_imgui_rs::TextureFormat,
    },
    #[error("official SDL3 renderer failed to {operation} managed texture {texture:?}")]
    TextureOperationFailed {
        texture: dear_imgui_rs::render::SnapshotTextureId,
        operation: &'static str,
    },
}

/// Failure to create a Vulkan surface through an SDL3 platform-owner lease.
#[cfg(feature = "multi-viewport")]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Sdl3VulkanSurfaceError {
    /// The provider no longer identifies the active SDL3 platform runtime.
    #[error("the SDL3 Vulkan surface provider no longer owns the active platform runtime")]
    OwnerUnavailable,
    /// The SDL3 runtime rejected entry or observed platform-contract drift.
    #[error(transparent)]
    Backend(#[from] Sdl3BackendError),
    /// The Vulkan platform callback is absent from the validated SDL3 callback table.
    #[error("the SDL3 platform runtime has no Platform_CreateVkSurface callback")]
    CallbackUnavailable,
    /// SDL3 failed to create a non-null surface.
    #[error("Platform_CreateVkSurface failed with code {code} and surface 0x{surface:X}")]
    CallbackFailed { code: i32, surface: u64 },
}

#[cfg(feature = "opengl3-renderer")]
pub(super) fn init_opengl3_impl(
    glsl_version: *const std::ffi::c_char,
) -> Result<(), Sdl3BackendError> {
    unsafe {
        if !opengl3_backend::dear_imgui_backend_opengl3_init(glsl_version) {
            ffi::ImGui_ImplSDL3_Shutdown_Rust();
            return Err(Sdl3BackendError::OpenGlRendererInitFailed);
        }
    }
    Ok(())
}

#[cfg(feature = "sdlgpu3-renderer")]
pub(super) fn init_sdlgpu3_impl(
    info: crate::viewport::SdlGpu3InitInfo<'_>,
) -> Result<(), Sdl3BackendError> {
    let mut init_info = ffi::ImGuiImplSdlGpu3InitInfo {
        device: info.device.raw(),
        color_target_format: SDL_GPUTextureFormat(info.color_target_format as i32),
        msaa_samples: SDL_GPUSampleCount(info.msaa_samples as i32),
        swapchain_composition: SDL_GPUSwapchainComposition(info.swapchain_composition as i32),
        present_mode: SDL_GPUPresentMode(info.present_mode as i32),
    };
    unsafe {
        if !ffi::dear_imgui_sdl3_backend_sdlgpu3_init(&mut init_info) {
            ffi::ImGui_ImplSDL3_Shutdown_Rust();
            return Err(Sdl3BackendError::SdlGpu3InitFailed);
        }
    }
    Ok(())
}

pub(super) fn shutdown_platform_impl() {
    unsafe {
        ffi::ImGui_ImplSDL3_Shutdown_Rust();
    }
}

#[cfg(feature = "opengl3-renderer")]
pub(super) fn shutdown_opengl3_renderer_impl() {
    unsafe {
        opengl3_backend::dear_imgui_backend_opengl3_shutdown();
    }
}

#[cfg(feature = "sdlgpu3-renderer")]
pub(super) fn shutdown_sdlgpu3_renderer_impl() {
    unsafe {
        ffi::dear_imgui_sdl3_backend_sdlgpu3_shutdown();
    }
}

#[cfg(feature = "sdlrenderer3-renderer")]
pub(super) fn shutdown_sdlrenderer3_renderer_impl() {
    unsafe {
        ffi::dear_imgui_sdl3_backend_sdlrenderer3_shutdown();
    }
}

#[cfg(feature = "sdlgpu3-renderer")]
pub(super) fn new_frame_sdlgpu3_impl() {
    unsafe {
        ffi::dear_imgui_sdl3_backend_sdlgpu3_new_frame();
        ffi::ImGui_ImplSDL3_NewFrame_Rust();
    }
}

#[cfg(feature = "opengl3-renderer")]
pub(super) fn new_frame_opengl3_impl() {
    unsafe {
        opengl3_backend::dear_imgui_backend_opengl3_new_frame();
        ffi::ImGui_ImplSDL3_NewFrame_Rust();
    }
}

pub(super) fn sdl3_new_frame_impl() {
    unsafe {
        ffi::ImGui_ImplSDL3_NewFrame_Rust();
    }
}

#[cfg(feature = "sdlrenderer3-renderer")]
pub(super) fn new_frame_sdlrenderer3_impl() {
    unsafe {
        ffi::dear_imgui_sdl3_backend_sdlrenderer3_new_frame();
        ffi::ImGui_ImplSDL3_NewFrame_Rust();
    }
}