Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
dear-imgui-sdl3
SDL3 platform backend with optional official renderer backends for the dear-imgui-rs
crate. This wraps the official Dear ImGui C++ backends:
imgui_impl_sdl3.cpp(platform layer)imgui_impl_opengl3.cpp(OpenGL3 renderer, via the shared sys shim)imgui_impl_sdlrenderer3.cpp(SDLRenderer (canvas) renderer, via this crate's SDL3 shim)imgui_impl_sdlgpu3.cpp(SDLGPU3 renderer, via this crate's SDL3 shim)
and exposes a small, Rust-friendly API that plugs into an existing
dear-imgui-rs::Context.
Typical use cases:
- Drive Dear ImGui input from an SDL3 window (keyboard/mouse/gamepad/IME).
- Render Dear ImGui via the official OpenGL3 backend.
- Render Dear ImGui via the official SDLRenderer3 or SDLGPU3 backends.
- Use SDL3 only for the platform layer together with a Rust renderer
(e.g.
dear-imgui-glowordear-imgui-wgpu).
Notes
- One SDL3 Dear ImGui platform runtime may be active per process. SDL cursor, capture, IME, and
hint state are process-wide in the official backend, so a second runtime returns
Sdl3BackendError::PlatformSessionOccupiedbefore native initialization mutates that state. After the first runtime shuts down, another Context may acquire the session. - The upstream SDL3 backend source is compiled from the Dear ImGui tree packaged by
dear-imgui-sys, while this crate keeps the SDL3-specific build logic, Rust API, and SDL3 wrapper boundary. - When
opengl3-rendereris enabled, this crate uses the shared OpenGL3 backend shim exported bydear-imgui-sysinstead of compiling a second local OpenGL3 wrapper layer. - When
sdlrenderer3-rendererorsdlgpu3-rendereris enabled, this crate compiles the matching official renderer source and local SDL3 shim. These renderer shims are not shareddear-imgui-sysfeatures.
Features
opengl3-renderer: enables the shared official OpenGL3 renderer shim fromdear-imgui-sys.sdlrenderer3-renderer: enables this crate's official SDLRenderer3 renderer shim.sdlgpu3-renderer: enables this crate's official SDLGPU3 renderer shim.multi-viewport: enables multi-viewport helpers (requiresdear-imgui-rs/multi-viewport).
Use the compatible stable requirement for the desired feature combination:
= { = "0.16", = ["opengl3-renderer"] }
Platform-only usage (SDL3 + WGPU/Glow, no official OpenGL3 renderer):
= { = "0.16", = false }
Enable the official OpenGL3 renderer:
= { = "0.16", = ["opengl3-renderer"] }
Enable the official SDLRenderer3 renderer:
= { = "0.16", = ["sdlrenderer3-renderer"] }
Enable the official SDLGPU3 renderer:
= { = "0.16", = ["sdlgpu3-renderer"] }
Compatibility
| Item | Version |
|---|---|
| Crate | 0.16.0 |
| dear-imgui-rs | 0.16.0 |
| SDL3 crate | 0.18.4 |
| sdl3-sys | 0.6 |
See also: docs/COMPATIBILITY.md for the full workspace matrix.
Quick Start
Minimal SDL3 + OpenGL3 flow (single window):
use ;
use ;
use GLProfile;
APIs of interest (see src/lib.rs for full docs):
Sdl3OpenGl3BackendandSdlRenderer3Backend: RAII renderer owners whose shared runtime retains the Context's renderer consumer through explicit or Context-owned teardown. OpenGL3's normal path consumes aFrameTokenthroughprepare(frame, restore_main_context): managed textures, capability-aware secondary viewports, OpenGL context restoration, and FIFO fault attribution are one transaction. Pass the resulting move-only capability torender_main(...); a rawReconciledFramecannot bypass that contract. OpenGL users must keep the initialized context current for renderer operations. SDLRenderer keeps its separate single-windowrender(...)path and rejects aWindowCanvasbacked by another raw renderer before texture or draw work starts.SdlGpu3RendererBackend: RAII renderer owner for SDL3 + SDLGPU3.prepare(frame)consumes aFrameToken, reconciles managed textures, and runs the secondary-window transaction before the application acquires its main swapchain.unsafe prepare_render_main(prepared, command_buffer)records main preparation commands and returns a move-onlySdlGpu3RenderPassFrame; consume it with unsaferender_main(render_pass)while the application-owned pass is active. If the main surface is minimized or unavailable, callprepared.skip_main()after the secondary work has completed. The unsafe boundary remains becausesdl3cannot prove that command buffers, render passes, and the initialized SDL GPU device share one native owner.Sdl3PlatformBackend: platform-only RAII owner for applications that provide a separate renderer. It intentionally does not claim a renderer consumer. Construct it with unsafeSdl3PlatformBackend::init_for_other,init_platform_for_opengl,init_for_vulkan,init_for_metal, Windows-onlyinit_for_d3d,init_for_sdl_gpu, orinit_for_sdl_rendereras appropriate. Every constructor is unsafe because the upstream backend retains native window and graphics pointers beyond the call; keep those owners alive until explicit shutdown succeeds or the Context finishes attachment teardown. Vulkan renderers should request the typedacquire_vulkan_surface_providercapability through their integration instead of cachingPlatform_CreateVkSurface. The exclusive provider is tied to one SDL runtime generation, validates each viewport immediately before native entry, and blocks platform shutdown until all renderer-owned Vulkan surfaces have been destroyed.shutdown(&mut self, &mut Context): an idempotent owner method that closes any open frame before reporting actionable teardown and callback-ownership errors. Dropping the owner defers native cleanup to the Context attachment, because Drop cannot safely normalize a frame without the mutable Context. Managed texture proxy state is held by that attachment as well, so uninstalled native allocations remain destroyable after the Rust owner is dropped. Official renderershutdown(...)anddestroy_device_objects(...)validate their Context-bound synchronous consumer before changing callbacks or native resources, and release that consumer only after teardown succeeds. A platform-only owner also rejects explicit shutdown while an external renderer attachment is active. This preflight runs before the current frame or native SDL state changes; shut down the renderer first, then retry platform shutdown. Context-owned teardown preserves the same ordered renderer-before-platform contract automatically.Sdl3PlatformBackend::drain_faults(): returns every deferred platform callback failure in observation order without unwinding through native code. First-party multi-viewport routes already include this ordered batch in their frame result; the method remains as the advanced escape hatch for Glow and custom renderer routes. Ordinary owner methods surface the oldest pending fault before entering SDL.process_event(&mut Context, &sdl3::event::Event): the safe event path for normalEventPumploops. Pointer-bearing SDL payloads such as text input remain owned while the official backend consumes them.Sdl3CallbackEventHandoffandprocess_callback_event(...): the safe main-callback path.push_from_callback(...)is the single unsafe enqueue boundary: it copies every transient payload consumed by the official backend, catches callback-path panics, and contains failures without unwinding through SDL's C ABI. Calldrain()on the main thread, inspect the returned batch'sfaults(), and pass each owned event directly to the backend owner. Callback events not consumed by the official Dear ImGui backend remain observable as inert application summaries without retaining transient SDL pointers. Events and failures are detached atomically, so sustained overflow cannot starve retained input or lifecycle events. The handoff is bounded; high-frequency motion, geometry, and display state is coalesced by native identity while ordered input and lifecycle events retain reserved capacity.QueueOverflowreports any event loss without creating another unbounded fault queue. Usewith_capacity(...)when the default capacity does not match the application's callback cadence. There is no unchecked drain or separate handoff-fault polling path in the normal API.unsafe process_raw_event(&mut Context, &SDL_Event): the low-level escape hatch for foreign event loops. Callback-mode applications should use the owned handoff instead of reconstructing and replaying the raw union themselves.- Runtime entry checks are scope-bound. Callback replacement detected while an operation returns
an error or unwinds remains queued for the route result or the next owner entry instead of being
skipped by an early exit.
The free renderer initialization, render, texture-update, and device-object functions were
removed. They allowed callers to bypass the Context-owned renderer epoch and write directly into
native texture state. Call the owning backend's
shutdown(...)method when shutdown errors need to be reported; otherwise retain the Context so its attachment can complete deferred cleanup.
Official renderer teardown is transactional: it first obtains
Context::prepare_renderer_texture_reset(&consumer) while its managed texture map is still
intact, releases the upstream renderer resources, then commits the permit. An outstanding frame or
a consumer mismatch rejects preparation before SDL resources or Context bindings change.
Every owning backend registers its platform role with the Context before native initialization. Composite owners also register a renderer role, so Context-first teardown always releases renderer resources before platform windows. Callback and platform-data claims are transactional: foreign replacements are preserved during shutdown and returned as typed faults instead of being silently cleared.
SDL3 & Build Requirements
The crate depends on:
sdl3andsdl3-sysfor SDL3 bindings.- A system SDL3 installation or a build-from-source configuration (OS-dependent).
Build behavior is aligned with Cargo.toml:
-
Linux / Windows
- The
sdl3dependency is configured withfeatures = ["build-from-source"]. - This means SDL3 is downloaded and built via CMake and does not require a pre-installed system SDL3 library.
- The build script can also discover SDL3 headers from
SDL3_INCLUDE_DIR, pkg-config, or vcpkg. Linking remains owned bysdl3/sdl3-sys. - You still need a working C toolchain (compiler, linker, CMake).
- The
-
macOS
- The crate expects a discoverable SDL3 install (for example via Homebrew):
brew install sdl3
- SDL3 headers are typically found under:
/opt/homebrew/include/SDL3/SDL.h(Apple Silicon)/usr/local/include/SDL3/SDL.h(Intel / custom setups)
- pkg-config and vcpkg are also supported when they provide SDL3 metadata.
- Linking parameters are handled by
sdl3-sys/sdl3; this crate only needs the headers to build the C++ backend sources.
- The crate expects a discoverable SDL3 install (for example via Homebrew):
-
iOS
- The crate depends on the safe
sdl3crate on iOS targets as well, but it does not force one SDL3 acquisition strategy. - Treat this as an app-owned integration route:
- provide SDL3 headers yourself and set
SDL3_INCLUDE_DIRwhen discovery is not enough - make the final application dependency graph enable
sdl3/build-from-source - or use an app-owned
SDL3.xcframework/sdl3/link-frameworksetup
- provide SDL3 headers yourself and set
- The consuming app still owns:
- SDL3 framework packaging
- the host
mainentry point (SDL_RunAppor ansdl3-maincallback setup) - Xcode signing and bundle layout
- A repository-owned integration shape lives in
examples-ios/dear-imgui-ios-sdl3-smoke/. - That smoke template now includes a checked-in Xcode host stub which keeps
the packaging boundary explicit: it can either consume an app-owned
SDL3.framework/SDL3.xcframework, or buildSDL3.frameworkfrom the upstream SDL source distributed throughsdl3-src.
- The crate depends on the safe
-
Android
- The crate depends on the safe
sdl3crate on Android targets as well, but it does not forcesdl3/build-from-source. - Android application / NDK / activity packaging still belongs to the consuming application.
- Treat this as a supported integration direction, not a zero-config turn-key path.
- There are two supported ways to satisfy the SDL3 headers needed by this crate:
- provide SDL3 headers yourself and set
SDL3_INCLUDE_DIRwhen discovery is not enough - make the final application dependency graph enable
sdl3/build-from-source, sosdl3-sysexports headers viaDEP_SDL3_OUT_DIR
- provide SDL3 headers yourself and set
- The crate depends on the safe
Header Search Order and SDL3_INCLUDE_DIR
build.rs locates SDL3 headers in the following order:
SDL3_INCLUDE_DIRenvironment variable (highest priority).DEP_SDL3_INCLUDE_PATH/DEP_SDL3_INCLUDE_DIRfrom an upstream SDL3 build script, when present.DEP_SDL3_OUT_DIR/includefromsdl3-sysbuild-from-source.- Cargo's target
build/sdl3-sys-*/out/includecache, used as a fallback when Cargo metadata is not directly available to the current build script. pkg-config sdl3with Cargo link metadata disabled.- vcpkg
sdl3with Cargo link metadata disabled. - A small set of common default paths (e.g.
/opt/homebrew/include,/usr/local/include,/opt/local/include).
Only header include paths are consumed from pkg-config/vcpkg here. Link flags
and SDL3 runtime selection remain the responsibility of sdl3 / sdl3-sys or
the final application.
1. Explicit SDL3_INCLUDE_DIR
Set this when SDL3 is installed in a non-standard location:
-
macOS (custom Homebrew prefix):
-
Linux (hand-built SDL3):
-
Windows (PowerShell, headers under
C:\libs\SDL3\include):$env:SDL3_INCLUDE_DIR="C:\libs\SDL3\include"
build.rs adds this directory to the C/C++ include path and expects to find
SDL3/SDL.h under it.
This is the preferred Android route when your application already owns the SDL3
integration (Gradle/NDK/Prefab/custom packaging) and just needs
dear-imgui-sdl3 to compile the official Dear ImGui SDL3 backend sources.
2. sdl3-sys build metadata
When the final dependency graph enables sdl3/build-from-source, sdl3-sys
builds SDL3 from source and exports header locations through Cargo metadata.
This crate reuses those headers automatically.
This is especially useful when the application wants Cargo to drive the SDL3 build instead of relying on a system install.
3. pkg-config sdl3
If SDL3_INCLUDE_DIR is not set, the build script tries:
On success, the reported include_paths are added to the compiler flags. This
is the preferred route for most Linux distributions and pkg-config-enabled
macOS setups.
4. vcpkg sdl3
If pkg-config is unavailable, the build script tries vcpkg's sdl3 port. On
Windows/MSVC, install the triplet that matches vcpkg-rs' selection, for example:
vcpkg install sdl3:x64-windows-static-md
If you use a dynamic vcpkg triplet such as x64-windows, set
VCPKGRS_DYNAMIC=1 and make the SDL3 DLLs available to the final executable.
5. Fallback paths
If the explicit environment, Cargo metadata, pkg-config, and vcpkg checks fail,
build.rs tries a few common include roots (such as Homebrew / MacPorts
locations) and looks for SDL3/SDL.h there.
When Headers Cannot Be Found
If the build script cannot locate SDL3 headers, it will panic with a message similar to:
dear-imgui-sdl3: could not find SDL3 headers.
Install SDL3 development files through pkg-config/vcpkg, set
SDL3_INCLUDE_DIR to the SDL3 include path, or make the final
dependency graph enablesdl3/build-from-source.
To fix this:
- Install SDL3 development packages and verify
pkg-config sdl3works, or - Set
SDL3_INCLUDE_DIRto the correct include root, or - Install vcpkg
sdl3with a triplet that matches your Rust target, or - Enable
sdl3/build-from-sourcein the final dependency graph sosdl3-sysexports SDL3 headers viaDEP_SDL3_OUT_DIR.
Android Integration Notes
Android integration in this crate should be understood as a low-friction path, not as a turn-key Android application template.
Recommended model:
- The consuming application owns SDL3 Android packaging, entry-point, and NDK toolchain decisions.
dear-imgui-sdl3owns the Dear ImGui SDL3 backend wrapper and can reuse whatever SDL3 headers the application chose to provide.- If the application wants Cargo to build SDL3 from source, it should add a
direct
sdl3dependency withfeatures = ["build-from-source"].
Example:
[]
= { = "0.16", = ["opengl3-renderer"] }
= { = "0.18", = ["build-from-source"] }
On Android, that route usually also requires the standard SDL/NDK build
toolchain environment expected by sdl3-sys, for example:
ANDROID_NDK/ANDROID_NDK_HOMEANDROID_ABI/CMAKE_ANDROID_ARCH_ABI(for examplearm64-v8a)CMAKE_TOOLCHAIN_FILECMAKE_GENERATOR=Ninja- a working
ninjaexecutable
In practice, this usually means the final application should drive the Android
build through a tool that already owns the ABI/toolchain contract
(cargo-ndk, Gradle+CMake, or an equivalent application build system) instead
of expecting dear-imgui-sdl3 alone to infer the full Android CMake setup.
Common Android Failure Modes
If you choose the sdl3/build-from-source route on Android, the most common
failures are application-toolchain issues rather than dear-imgui-sdl3
wrapper issues:
dear-imgui-sdl3: could not find SDL3 headers- Your final dependency graph did not actually enable
sdl3/build-from-source, or your application did not provideSDL3_INCLUDE_DIR.
- Your final dependency graph did not actually enable
CMake was unable to find a build program corresponding to "Ninja"sdl3-sysis trying to drive SDL3's Android CMake build, but your application environment did not makeninjaavailable to CMake.
- Android ABI mismatch during CMake compiler checks
- Example shape: CMake defaults to
armv7while Rust is buildingaarch64-linux-android. - In that case the application usually needs to set
ANDROID_ABI=arm64-v8aand/orCMAKE_ANDROID_ARCH_ABI=arm64-v8a, or use a tool such ascargo-ndk/ Gradle+CMake that manages those values.
- Example shape: CMake defaults to
The important boundary is:
dear-imgui-sdl3can reuse SDL3 once the application has made SDL3 headers and toolchain metadata availabledear-imgui-sdl3does not try to become the Android application build system
PowerShell Sketch For aarch64-linux-android
If your application owns the Android build directly from Cargo, the setup often looks roughly like this before you invoke your actual app build command:
$ndk = $env:ANDROID_NDK_HOME
$llvm = Join-Path $ndk 'toolchains/llvm/prebuilt/windows-x86_64/bin'
$env:ANDROID_NDK = $ndk
$env:ANDROID_ABI = 'arm64-v8a'
$env:CMAKE_ANDROID_ARCH_ABI = 'arm64-v8a'
$env:CMAKE_TOOLCHAIN_FILE = Join-Path $ndk 'build/cmake/android.toolchain.cmake'
$env:CMAKE_GENERATOR = 'Ninja'
$env:CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER = Join-Path $llvm 'aarch64-linux-android24-clang.cmd'
$env:CC_aarch64_linux_android = Join-Path $llvm 'aarch64-linux-android24-clang.cmd'
$env:CXX_aarch64_linux_android = Join-Path $llvm 'aarch64-linux-android24-clang++.cmd'
This is not something dear-imgui-sdl3 can infer safely on behalf of the
application. The final app build must own it.
If you do not want SDL3 at all, you can still build an Android backend manually
on top of dear-imgui-rs plus dear-imgui-sys::backend_shim::{android, opengl3}.
IME and Gamepad Configuration
The underlying SDL3 ImGui backend supports IME and gamepad input. This crate exposes a couple of small helpers to configure them.
IME UI
On platforms with heavy IME usage (e.g. Chinese/Japanese/Korean locales), it is recommended to enable the native IME UI before creating any SDL3 windows:
// Call this before creating SDL3 windows.
enable_native_ime_ui;
This is a convenience wrapper over SDL_HINT_IME_SHOW_UI, and failures are
treated as non-fatal.
Gamepad Mode
By default, the SDL3 backend opens the first available gamepad and feeds its state into Dear ImGui (the upstream default behavior).
You can switch to a mode where all detected gamepads are opened and merged:
use GamepadMode;
// After initializing an owning backend:
sdl3_backend.set_gamepad_mode?;
This is useful for local multiplayer setups or testing environments.
For advanced use cases, you can also opt into manual gamepad selection by
providing raw SDL_Gamepad* handles opened by your application:
// Safety: gamepads must be valid, opened SDL_Gamepad pointers.
unsafe
Mouse Capture Mode
Mouse capture keeps drag coordinates updating after the pointer leaves an SDL window. The official backend enables it immediately on capable desktop drivers, except on X11 where it waits until a drag starts so a debugger break is less likely to leave the desktop pointer captured. Applications can override that policy through any owning backend:
use MouseCaptureMode;
sdl3_backend.set_mouse_capture_mode?;
MouseCaptureMode::Disabled also releases an active capture. Changing this policy cannot add
global mouse or native viewport capabilities to a video driver that does not provide them.
Examples
The workspace includes several examples that use this backend:
Multi-viewport status on SDL3:
Native OS viewports depend on the active SDL video driver, not only the Cargo feature. The embedded
official backend currently sets BackendFlags::PLATFORM_HAS_VIEWPORTS for the Windows, Cocoa, X11,
DIVE, and VMAN drivers. It intentionally does not set that capability on Wayland, whose compositor
security model does not provide the global pointer position and capture behavior required by Dear
ImGui's current platform-viewport contract. On Wayland, docking and dragging continue to work
inside the main SDL window, but detached panels remain in that host window instead of becoming
independent OS windows. Applications can inspect imgui.io().backend_flags() after backend
initialization when they need to report this runtime degradation.
For OpenGL viewports the Rust-owned callback wrapper verifies that each secondary window has a
distinct current GL context and restores the previous window, context, and
SDL_GL_SHARE_WITH_CURRENT_CONTEXT attribute before returning. Secondary contexts default to
Sdl3OpenGlViewportSwapInterval::Immediate, matching the upstream behavior that avoids serial
VSync waits across several platform windows. Use
init_with_viewport_swap_interval(...) or
init_platform_for_opengl_with_viewport_swap_interval(...) to choose VSync, Adaptive, or
MatchMain. Swap-interval selection is best effort: if a driver rejects the requested timing after
the secondary context is valid, the viewport keeps the driver's default timing. Native GL context,
state-restoration, and SDL_GPU failures are deferred into the owning route result (or
drain_faults() for a custom route); a partially initialized viewport is closed instead of being
published as usable.
- SDL3 + OpenGL3: multi-viewport is provided by the upstream C++ backends and considered stable for desktop use.
- SDL3 + SDLRenderer3: this crate currently exposes the official single-window renderer only. It does not publish a native multi-viewport renderer route; use OpenGL3, Glow, WGPU, Ash, or SDLGPU3 when secondary OS windows are required.
- SDL3 + Glow: multi-viewport is experimental but functional on native targets.
- SDL3 + WGPU: multi-viewport is experimental on native targets; WebGPU/wasm is
single-window to match upstream
imgui_impl_wgpu.
The SDL3 + SDLRenderer3 entry above is an intentional single-window boundary, not a missing
feature flag. For the other native routes, the repository-wide evidence matrix distinguishes
shared tests and examples from route-level smoke coverage; SDL3 + Glow has the strongest current
smoke evidence, while SDL3 + WGPU and SDL3 + Ash remain B until dedicated native smokes are
available. See the renderer route evidence matrix.
-
SDL3 + OpenGL3, multi-viewport:
-
SDL3 + OpenGL3, multi-viewport (Glow renderer wrapper):
-
SDL3 + WGPU, single-window:
-
SDL3 + WGPU, multi-viewport (experimental, native only):
Note: WGPU multi-viewport support is experimental and only available on native targets
via dear-imgui-wgpu/multi-viewport-sdl3. WebGPU/wasm remains single-window to match
upstream imgui_impl_wgpu.