Integrating Dear ImGui with SDL3 (Callbacks) and WebGPU-Native Интеграция C++-библиотеки Dear ImGui с SDL3 (Callbacks) и WebGPU-Native
A complete guide to configuring and building a native desktop application with Dear ImGui, the SDL3 Callbacks architecture (SDL_AppInit, SDL_AppIterate, SDL_AppEvent, SDL_AppQuit), and WebGPU-Native using CMake and Ninja.
Полное руководство по настройке и сборке нативного десктопного приложения с Dear ImGui, архитектурой колбэков SDL3 (SDL_AppInit, SDL_AppIterate, SDL_AppEvent, SDL_AppQuit) и WebGPU-Native с использованием CMake и Ninja.
- Windows (MinGW, CMake & Ninja) Setup Guide Руководство по настройке Windows (MinGW, CMake и Ninja)
- Ubuntu C/C++ Development Environment Setup Guide Руководство по настройке среды разработки C/C++ на Ubuntu
- Building SDL3 Static Library for Windows Сборка статической библиотеки SDL3 для Windows
- Building SDL3 Static Library for Ubuntu Сборка статической библиотеки SDL3 для Ubuntu
- Installing Prebuilt wgpu-native Library for Windows Установка предкомпилированной библиотеки wgpu-native для Windows
- Installing Prebuilt wgpu-native Library for Ubuntu Установка предкомпилированной библиотеки wgpu-native для Ubuntu
- Installing Dear ImGui for WebGPU-Native Установка Dear ImGui для WebGPU-Native
Step 1: Create Project Directory Шаг 1: Создание директории проекта
Create a workspace folder and a new directory for your project: Создайте рабочую папку для проектов и новую директорию для вашего проекта:
Windows (Command Prompt / PowerShell)
mkdir C:\projects\imgui-sdl3-wgpu-callbacks-test
cd C:\projects\imgui-sdl3-wgpu-callbacks-test
Ubuntu (Terminal)
mkdir -p ~/projects/imgui-sdl3-wgpu-callbacks-test
cd ~/projects/imgui-sdl3-wgpu-callbacks-test
Step 2: Create Source Files (main.cpp & CMakeLists.txt) Шаг 2: Создание исходных файлов (main.cpp и CMakeLists.txt)
Create main.cpp and CMakeLists.txt in the project directory:
Создайте файлы main.cpp и CMakeLists.txt в директории проекта:
Windows (Terminal / Notepad)
notepad main.cpp
notepad CMakeLists.txt
Ubuntu (Terminal / Nano)
nano main.cpp
nano CMakeLists.txt
1. File Contents for main.cpp 1. Содержимое файла main.cpp
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <iostream>
#include <string>
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#elif defined(__linux__)
#include <X11/Xlib.h>
#endif
#include <webgpu/webgpu.h>
#include <webgpu/wgpu.h>
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_wgpu.h"
// -----------------------------------------------------------------------------
// Application Context
// -----------------------------------------------------------------------------
struct AppContext {
SDL_Window* window = nullptr;
WGPUInstance instance = nullptr;
WGPUSurface surface = nullptr;
WGPUAdapter adapter = nullptr;
WGPUDevice device = nullptr;
WGPUQueue queue = nullptr;
WGPUTextureFormat surface_format = WGPUTextureFormat_Undefined;
WGPUSurfaceConfiguration config = {};
bool need_reconfigure = false;
int width = 1280;
int height = 720;
};
// -----------------------------------------------------------------------------
// WebGPU Native Surface Creation Helper (Windows & Linux)
// -----------------------------------------------------------------------------
static WGPUSurface CreateWGPUSurface(WGPUInstance instance, SDL_Window* window) {
#if defined(_WIN32)
SDL_PropertiesID props = SDL_GetWindowProperties(window);
HWND hwnd = (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
HINSTANCE hinstance = (HINSTANCE)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, NULL);
WGPUSurfaceSourceWindowsHWND hwndSource = {};
hwndSource.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
hwndSource.hinstance = hinstance;
hwndSource.hwnd = hwnd;
WGPUSurfaceDescriptor desc = {};
desc.nextInChain = (WGPUChainedStruct*)&hwndSource;
return wgpuInstanceCreateSurface(instance, &desc);
#elif defined(__linux__)
SDL_PropertiesID props = SDL_GetWindowProperties(window);
// Check Wayland first
void* wayland_display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
void* wayland_surface = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
if (wayland_display && wayland_surface) {
WGPUSurfaceSourceWaylandSurface waylandSource = {};
waylandSource.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
waylandSource.display = wayland_display;
waylandSource.surface = wayland_surface;
WGPUSurfaceDescriptor desc = {};
desc.nextInChain = (WGPUChainedStruct*)&waylandSource;
return wgpuInstanceCreateSurface(instance, &desc);
}
// Fall back to X11
void* x11_display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
uint64_t x11_window = SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
if (x11_display && x11_window) {
WGPUSurfaceSourceXlibWindow x11Source = {};
x11Source.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
x11Source.display = x11_display;
x11Source.window = x11_window;
WGPUSurfaceDescriptor desc = {};
desc.nextInChain = (WGPUChainedStruct*)&x11Source;
return wgpuInstanceCreateSurface(instance, &desc);
}
return nullptr;
#endif
}
// -----------------------------------------------------------------------------
// WebGPU Callbacks
// -----------------------------------------------------------------------------
static void handle_device_error(
WGPUDevice const* device,
WGPUErrorType type,
WGPUStringView message,
void* userdata1,
void* userdata2)
{
const char* msg = (message.data && message.length > 0) ? message.data : "(No message)";
int len = (message.data && message.length > 0) ? (int)message.length : 12;
std::cerr << "WebGPU Error [" << (int)type << "]: " << std::string(msg, len) << std::endl;
}
static void handle_device_request(
WGPURequestDeviceStatus status,
WGPUDevice res,
WGPUStringView message,
void* userdata1,
void* userdata2)
{
auto* app = static_cast<AppContext*>(userdata1);
if (status == WGPURequestDeviceStatus_Success) {
app->device = res;
} else {
const char* msg = (message.data && message.length > 0) ? message.data : "";
int len = (message.data && message.length > 0) ? (int)message.length : 0;
std::cerr << "Device Request Failed: " << std::string(msg, len) << std::endl;
}
}
static void handle_adapter_request(
WGPURequestAdapterStatus status,
WGPUAdapter res,
WGPUStringView message,
void* userdata1,
void* userdata2)
{
auto* app = static_cast<AppContext*>(userdata1);
if (status == WGPURequestAdapterStatus_Success) {
app->adapter = res;
WGPUUncapturedErrorCallbackInfo errorCallbackInfo = {};
errorCallbackInfo.callback = handle_device_error;
WGPUDeviceDescriptor deviceDesc = {};
deviceDesc.uncapturedErrorCallbackInfo = errorCallbackInfo;
WGPURequestDeviceCallbackInfo deviceCallbackInfo = {};
deviceCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
deviceCallbackInfo.callback = handle_device_request;
deviceCallbackInfo.userdata1 = app;
wgpuAdapterRequestDevice(app->adapter, &deviceDesc, deviceCallbackInfo);
} else {
const char* msg = (message.data && message.length > 0) ? message.data : "";
int len = (message.data && message.length > 0) ? (int)message.length : 0;
std::cerr << "Adapter Request Failed: " << std::string(msg, len) << std::endl;
}
}
// -----------------------------------------------------------------------------
// SDL3 Callbacks Pipeline
// -----------------------------------------------------------------------------
// 1. AppInit: Called once at application startup
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
auto* app = new AppContext();
*appstate = app;
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
return SDL_APP_FAILURE;
}
app->window = SDL_CreateWindow(
"Dear ImGui + SDL3 (WebGPU-Native) - Callbacks",
app->width, app->height,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY
);
if (!app->window) {
std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
return SDL_APP_FAILURE;
}
// Initialize WebGPU Instance and Surface
app->instance = wgpuCreateInstance(nullptr);
if (!app->instance) return SDL_APP_FAILURE;
app->surface = CreateWGPUSurface(app->instance, app->window);
if (!app->surface) return SDL_APP_FAILURE;
// Request Adapter
WGPURequestAdapterOptions opt = {};
opt.compatibleSurface = app->surface;
WGPURequestAdapterCallbackInfo adapterCallbackInfo = {};
adapterCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
adapterCallbackInfo.callback = handle_adapter_request;
adapterCallbackInfo.userdata1 = app;
wgpuInstanceRequestAdapter(app->instance, &opt, adapterCallbackInfo);
#ifndef __EMSCRIPTEN__
while (app->adapter == nullptr || app->device == nullptr) {
SDL_Delay(1);
}
#endif
app->queue = wgpuDeviceGetQueue(app->device);
// Retrieve surface capabilities & choose format
WGPUSurfaceCapabilities caps = {};
wgpuSurfaceGetCapabilities(app->surface, app->adapter, &caps);
app->surface_format = WGPUTextureFormat_RGBA8Unorm;
if (caps.formatCount > 0) {
app->surface_format = caps.formats[0];
for (size_t i = 0; i < caps.formatCount; ++i) {
if (caps.formats[i] == WGPUTextureFormat_RGBA8Unorm ||
caps.formats[i] == WGPUTextureFormat_BGRA8Unorm) {
app->surface_format = caps.formats[i];
break;
}
}
}
WGPUPresentMode present_mode = (caps.presentModeCount > 0) ? caps.presentModes[0] : WGPUPresentMode_Fifo;
wgpuSurfaceCapabilitiesFreeMembers(caps);
SDL_GetWindowSizeInPixels(app->window, &app->width, &app->height);
app->config.device = app->device;
app->config.format = app->surface_format;
app->config.usage = WGPUTextureUsage_RenderAttachment;
app->config.width = (uint32_t)app->width;
app->config.height = (uint32_t)app->height;
app->config.presentMode = present_mode;
wgpuSurfaceConfigure(app->surface, &app->config);
// Initialize Dear ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::StyleColorsDark();
ImGui_ImplSDL3_InitForOther(app->window);
ImGui_ImplWGPU_InitInfo init_info = {};
init_info.Device = app->device;
init_info.NumFramesInFlight = 3;
init_info.RenderTargetFormat = app->surface_format;
init_info.DepthStencilFormat = WGPUTextureFormat_Undefined;
ImGui_ImplWGPU_Init(&init_info);
std::cout << "Dear ImGui version: " << IMGUI_VERSION << std::endl;
std::cout << "SDL3 Callbacks + WebGPU-Native initialized successfully!" << std::endl;
return SDL_APP_CONTINUE;
}
// 2. AppEvent: Called on incoming SDL events
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
auto* app = static_cast<AppContext*>(appstate);
ImGui_ImplSDL3_ProcessEvent(event);
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
}
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event->window.windowID == SDL_GetWindowID(app->window)) {
return SDL_APP_SUCCESS;
}
if (event->type == SDL_EVENT_WINDOW_RESIZED) {
app->need_reconfigure = true;
}
return SDL_APP_CONTINUE;
}
// 3. AppIterate: Called every frame to render
SDL_AppResult SDL_AppIterate(void* appstate) {
auto* app = static_cast<AppContext*>(appstate);
// Reconfigure surface on resize
if (app->need_reconfigure) {
int w = 0, h = 0;
SDL_GetWindowSizeInPixels(app->window, &w, &h);
if (w > 0 && h > 0) {
app->width = w;
app->height = h;
app->config.width = (uint32_t)w;
app->config.height = (uint32_t)h;
wgpuSurfaceConfigure(app->surface, &app->config);
app->need_reconfigure = false;
}
}
// Acquire next surface texture
WGPUSurfaceTexture surface_texture = {};
wgpuSurfaceGetCurrentTexture(app->surface, &surface_texture);
if (!surface_texture.texture) {
return SDL_APP_CONTINUE;
}
WGPUTextureView target_view = wgpuTextureCreateView(surface_texture.texture, nullptr);
// Start ImGui frame
ImGui_ImplWGPU_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
// Show Demo Window
ImGui::ShowDemoWindow();
// Render ImGui
ImGui::Render();
// WebGPU Render Pass
WGPUCommandEncoderDescriptor enc_desc = {};
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(app->device, &enc_desc);
WGPURenderPassColorAttachment color_attachment = {};
color_attachment.view = target_view;
color_attachment.loadOp = WGPULoadOp_Clear;
color_attachment.storeOp = WGPUStoreOp_Store;
color_attachment.clearValue = WGPUColor{ 0.1, 0.12, 0.15, 1.0 };
#ifndef __EMSCRIPTEN__
color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
#endif
WGPURenderPassDescriptor pass_desc = {};
pass_desc.colorAttachmentCount = 1;
pass_desc.colorAttachments = &color_attachment;
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass);
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
WGPUCommandBufferDescriptor cmd_desc = {};
WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, &cmd_desc);
wgpuCommandEncoderRelease(encoder);
wgpuQueueSubmit(app->queue, 1, &cmd);
wgpuCommandBufferRelease(cmd);
wgpuSurfacePresent(app->surface);
wgpuTextureViewRelease(target_view);
wgpuTextureRelease(surface_texture.texture);
return SDL_APP_CONTINUE;
}
// 4. AppQuit: Called once at exit for cleanup
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
if (appstate) {
auto* app = static_cast<AppContext*>(appstate);
ImGui_ImplWGPU_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
if (app->surface)
wgpuSurfaceUnconfigure(app->surface);
if (app->queue)
wgpuQueueRelease(app->queue);
if (app->device)
wgpuDeviceRelease(app->device);
if (app->adapter)
wgpuAdapterRelease(app->adapter);
if (app->surface)
wgpuSurfaceRelease(app->surface);
if (app->instance)
wgpuInstanceRelease(app->instance);
SDL_DestroyWindow(app->window);
delete app;
}
SDL_Quit();
}
2. File Contents for CMakeLists.txt 2. Содержимое файла CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
project(imgui_sdl3_wgpu_callbacks_test CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# -----------------------------------------------------------------------------
# Paths
# -----------------------------------------------------------------------------
if(WIN32)
set(IMGUI_DIR "C:/libs/imgui-1.93.0")
set(SDL3_DIR "C:/libs/sdl3-3.4.18-mingw/lib/cmake/SDL3")
set(WGPU_DIR "C:/libs/wgpu-29.0.1.1-mingw")
else()
set(IMGUI_DIR "$ENV{HOME}/libs/imgui-1.93.0")
set(SDL3_DIR "$ENV{HOME}/libs/sdl3-3.4.18-ubuntu/lib/cmake/SDL3")
set(WGPU_DIR "$ENV{HOME}/libs/wgpu-native-29.0.1.1-ubuntu")
endif()
# -----------------------------------------------------------------------------
# Locate Official SDL3 CMake Package
# -----------------------------------------------------------------------------
find_package(SDL3 REQUIRED CONFIG)
# -----------------------------------------------------------------------------
# ImGui Sources (including Demo Window and SDL3/WGPU Backends)
# -----------------------------------------------------------------------------
set(IMGUI_SOURCES
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_demo.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp
${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp
)
# -----------------------------------------------------------------------------
# Executable Target
# -----------------------------------------------------------------------------
add_executable(app main.cpp ${IMGUI_SOURCES})
# -----------------------------------------------------------------------------
# Include Directories
# -----------------------------------------------------------------------------
target_include_directories(app PRIVATE
${IMGUI_DIR}
${IMGUI_DIR}/backends
${WGPU_DIR}/include
)
# -----------------------------------------------------------------------------
# Backend Selection Definition
# -----------------------------------------------------------------------------
target_compile_definitions(app PRIVATE IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
# -----------------------------------------------------------------------------
# Static C++ Runtime (Applied to BOTH Windows & Ubuntu)
# -----------------------------------------------------------------------------
target_link_options(app PRIVATE -static-libgcc -static-libstdc++)
# -----------------------------------------------------------------------------
# Optimization & Symbol Stripping (Release Builds Only)
# -----------------------------------------------------------------------------
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
target_compile_options(app PRIVATE -Os)
target_link_options(app PRIVATE -s)
endif()
# -----------------------------------------------------------------------------
# Platform Linking & Dependencies
# -----------------------------------------------------------------------------
if(WIN32)
add_library(wgpu_native STATIC IMPORTED)
set_target_properties(wgpu_native PROPERTIES
IMPORTED_LOCATION "${WGPU_DIR}/lib/libwgpu_native.a"
INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}/include"
INTERFACE_LINK_LIBRARIES "ws2_32;userenv;ntdll;bcrypt;d3d12;dxgi"
)
target_link_libraries(app PRIVATE
wgpu_native
SDL3::SDL3-static
)
# Windows static linking & subsystem selection
target_link_options(app PRIVATE -static)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_options(app PRIVATE -mconsole)
else()
target_link_options(app PRIVATE -mwindows)
endif()
else()
add_library(wgpu_native STATIC IMPORTED)
set_target_properties(wgpu_native PROPERTIES
IMPORTED_LOCATION "${WGPU_DIR}/lib/libwgpu_native.a"
INTERFACE_INCLUDE_DIRECTORIES "${WGPU_DIR}/include"
INTERFACE_LINK_LIBRARIES "dl;pthread;m"
)
target_link_libraries(app PRIVATE
wgpu_native
SDL3::SDL3-static
X11
)
endif()
Step 3: Create Build & Execution Scripts Шаг 3: Создание скриптов сборки и запуска
Windows (MinGW / Batch Scripts)
config-win.bat
cmake -G Ninja -S . -B dist/win
build-win.bat
cmake --build dist/win
run-win.bat
@echo off
.\dist\win\app.exe
Run the scripts sequentially in Command Prompt: Последовательно запустите скрипты в командной строке:
config-win.bat
build-win.bat
run-win.bat
Ubuntu (Bash Scripts)
config-linux.sh
#!/usr/bin/env bash
set -e
cmake -G Ninja -S . -B dist/linux
build-linux.sh
#!/usr/bin/env bash
set -e
cmake --build dist/linux
run-linux.sh
#!/usr/bin/env bash
set -e
./dist/linux/app
Grant execute permissions and launch in Ubuntu terminal: Предоставьте права на выполнение и запустите в терминале Ubuntu:
chmod +x config-linux.sh build-linux.sh run-linux.sh
./config-linux.sh
./build-linux.sh
./run-linux.sh
Step 4: Expected Output Шаг 4: Ожидаемый результат
Once executed, the application boots using the SDL3 callbacks pipeline, initializes the WebGPU surface, and renders Dear ImGui Demo Window. In the console, you will see: После запуска приложение стартует через конвейер колбэков SDL3, инициализирует поверхность WebGPU и выполняет рендеринг окна Dear ImGui Demo. В консоли появится сообщение:
Dear ImGui version: 1.93.0
SDL3 Callbacks + WebGPU-Native initialized successfully!
Support My Work Поддержать проект
If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.
Sberbank (Russia only) Сбербанк (только для РФ)
Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)
USDT TRC20
Support via Cryptocurrency Поддержка криптовалютой