Integrating Dear ImGui with SDL3 on Android (OpenGL ES 3.0) Интеграция Dear ImGui с SDL3 на Android (OpenGL ES 3.0)
A step-by-step guide to integrating Dear ImGui source files with SDL3 and OpenGL ES 3.0 on Android, setting up touch scaling, building the APK with Gradle, and running it on a device. Пошаговое руководство по интеграции исходных файлов Dear ImGui с SDL3 и OpenGL ES 3.0 на Android, настройке масштабирования под сенсорный ввод, сборке APK с помощью Gradle и запуску на устройстве.
Ensure you have the Dear ImGui source files downloaded (e.g. at C:/libs/imgui-1.93.0):
Убедитесь, что исходные файлы Dear ImGui доступны на вашем компьютере (например, по пути C:/libs/imgui-1.93.0):
C:/libs/imgui-1.93.0
Step 1: Project Setup Шаг 1: Настройка структуры проекта
Create a new project directory named C:\projects\android-app-sdl3-imgui-opengl-cpp by copying your starter project folder from Integrating compiled libSDL3.so on Android (OpenGL ES 3.0):
Создайте директорию проекта с именем C:\projects\android-app-sdl3-imgui-opengl-cpp, скопировав папку стартового проекта из руководства Интеграция собранной библиотеки libSDL3.so на Android (OpenGL ES 3.0):
The copied starter project has the following directory structure: Скопированный стартовый проект имеет следующую структуру каталогов:
C:\projects\android-app-sdl3-imgui-opengl-cpp\
├── android-project\
│ ├── app\
│ │ ├── build.gradle
│ │ └── src\
│ │ └── main\
│ │ ├── AndroidManifest.xml
│ │ └── java\
│ │ └── com\
│ │ └── example\
│ │ └── myapp\
│ │ └── MyActivity.java
│ ├── build.gradle
│ ├── settings.gradle
│ └── gradlew.bat
├── src\
│ └── main.cpp
├── CMakeLists.txt
├── build-android.bat
└── install-android.bat
Step 2: Update CMakeLists.txt Шаг 2: Обновление CMakeLists.txt
Open CMakeLists.txt in the project root directory and update it to compile the Dear ImGui core sources and SDL3/OpenGL3 backends, set include directories, and define IMGUI_IMPL_OPENGL_ES3:
Откройте CMakeLists.txt в корневой папке проекта и обновите его: добавьте компиляцию файлов Dear ImGui и бэкендов SDL3/OpenGL3, пути к заголовочным файлам и макрос IMGUI_IMPL_OPENGL_ES3:
cmake_minimum_required(VERSION 3.21)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
endif()
project(myapp CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# -----------------------------------------------------------------------------
# Paths
# -----------------------------------------------------------------------------
set(SDL3_DIR "C:/libs/sdl3-3.4.18-android")
set(IMGUI_DIR "C:/libs/imgui-1.93.0")
# -----------------------------------------------------------------------------
# Native Shared Library Target
# -----------------------------------------------------------------------------
add_library(main SHARED
src/main.cpp
${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_opengl3.cpp
)
# -----------------------------------------------------------------------------
# Include Directories
# -----------------------------------------------------------------------------
target_include_directories(main PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${SDL3_DIR}/${ANDROID_ABI}/include
${IMGUI_DIR}
${IMGUI_DIR}/backends
)
# -----------------------------------------------------------------------------
# Compile Definitions
# -----------------------------------------------------------------------------
# Enable OpenGL ES 3.0 path in imgui_impl_opengl3
target_compile_definitions(main PRIVATE
IMGUI_IMPL_OPENGL_ES3
)
# -----------------------------------------------------------------------------
# Link SDL3 & Android System Libraries
# -----------------------------------------------------------------------------
target_link_libraries(main PRIVATE
${SDL3_DIR}/${ANDROID_ABI}/lib/libSDL3.so
GLESv3
EGL
android
log
)
Step 3: Update Source Code (src/main.cpp) Шаг 3: Обновление исходного кода (src/main.cpp)
SDL3 offers two architectural models for application entry points: the Classic Main Loop and the Modern Callbacks API. Choose the option that fits your preference: В SDL3 доступны две архитектурные модели организации точки входа: классический цикл (Classic Loop) и современный API функций обратного вызова (Callbacks API). Выберите подходящий вариант:
The traditional approach familiar from desktop development. An explicit loop polls events with SDL_PollEvent and executes frame rendering continuously:
Традиционный подход, привычный по настольной разработке. Явный цикл опрашивает события через SDL_PollEvent и выполняет отрисовку кадров:
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <GLES3/gl3.h>
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_opengl3.h"
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
// Optional: lock screen orientation to landscape
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
// Initialize SDL3 video subsystem
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD))
{
SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
return 1;
}
// Configure OpenGL ES 3.0 context attributes
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
// Create SDL window with OpenGL and High-DPI flags
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3 GLES3", 0, 0, window_flags);
if (!window)
{
SDL_Log("Failed to create SDL window: %s", SDL_GetError());
SDL_Quit();
return 1;
}
// Create OpenGL ES context
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
if (!gl_context)
{
SDL_Log("Failed to create OpenGL ES context: %s", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable V-Sync
// Initialize Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
// Apply scaling for High-DPI mobile touch screens
float scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(window));
if (scale <= 0.0f)
{
scale = 1.0f;
}
ImGui::GetStyle().ScaleAllSizes(scale);
io.FontGlobalScale = scale;
// Set dark theme style
ImGui::StyleColorsDark();
// Initialize platform and OpenGL ES 3.0 renderers
ImGui_ImplSDL3_InitForOpenGL(window, gl_context);
const char* glsl_version = "#version 300 es";
ImGui_ImplOpenGL3_Init(glsl_version);
// Application state
bool running = true;
bool show_demo_window = true;
ImVec4 clear_color = ImVec4(0.12f, 0.14f, 0.18f, 1.00f);
int click_counter = 0;
// Main loop
while (running)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL3_ProcessEvent(&event);
if (event.type == SDL_EVENT_QUIT)
{
running = false;
}
if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED &&
event.window.windowID == SDL_GetWindowID(window))
{
running = false;
}
}
// Start Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
// 1. Show the Dear ImGui demo window
if (show_demo_window)
{
ImGui::ShowDemoWindow(&show_demo_window);
}
// 2. Custom touch-friendly window
{
ImGui::Begin("Dear ImGui with SDL3 & GLES3 on Android");
ImGui::Text("Application average: %.3f ms/frame (%.1f FPS)",
1000.0f / io.Framerate, io.Framerate);
ImGui::Text("Screen scale factor: %.2f", scale);
ImGui::Separator();
ImGui::Checkbox("Show ImGui Demo Window", &show_demo_window);
// Large button easy to tap on mobile screens
if (ImGui::Button("Tap Me!", ImVec2(180 * scale, 60 * scale)))
{
click_counter++;
}
ImGui::SameLine();
ImGui::Text("Taps count: %d", click_counter);
ImGui::Separator();
ImGui::ColorEdit3("Background Color", (float*)&clear_color);
ImGui::End();
}
// Rendering
ImGui::Render();
int display_w, display_h;
SDL_GetWindowSizeInPixels(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(clear_color.x * clear_color.w,
clear_color.y * clear_color.w,
clear_color.z * clear_color.w,
clear_color.w);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
SDL_GL_DestroyContext(gl_context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
SDL3 introduces a modern callbacks architecture ideal for mobile platforms like Android and iOS. By defining SDL_MAIN_USE_CALLBACKS 1, the operating system manages the event loop, driving callbacks when events arrive or when a new frame is needed:
В SDL3 появилась архитектура функций обратного вызова, оптимально подходящая для мобильных платформ (Android, iOS). При определении макроса SDL_MAIN_USE_CALLBACKS 1 операционная система сама управляет циклом, вызывая функции приложения при поступлении событий или необходимости отрисовки кадра:
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <GLES3/gl3.h>
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_opengl3.h"
// Application state structure
struct AppState {
SDL_Window* window = nullptr;
SDL_GLContext gl_context = nullptr;
float scale = 1.0f;
bool show_demo_window = true;
ImVec4 clear_color = ImVec4(0.12f, 0.14f, 0.18f, 1.00f);
int click_counter = 0;
};
// Called once when the application starts
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
(void)argc;
(void)argv;
// Optional: lock screen orientation to landscape
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
// Initialize SDL3 video subsystem
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD))
{
SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
// Allocate application state
AppState* state = new AppState();
*appstate = state;
// Configure OpenGL ES 3.0 context attributes
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
// Create SDL window
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
state->window = SDL_CreateWindow("Dear ImGui SDL3 GLES3", 0, 0, window_flags);
if (!state->window)
{
SDL_Log("Failed to create SDL window: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
// Create OpenGL ES context
state->gl_context = SDL_GL_CreateContext(state->window);
if (!state->gl_context)
{
SDL_Log("Failed to create OpenGL ES context: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_GL_MakeCurrent(state->window, state->gl_context);
SDL_GL_SetSwapInterval(1); // Enable V-Sync
// Initialize Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
// Apply scaling for High-DPI mobile touch screens
state->scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(state->window));
if (state->scale <= 0.0f)
{
state->scale = 1.0f;
}
ImGui::GetStyle().ScaleAllSizes(state->scale);
io.FontGlobalScale = state->scale;
// Set dark theme style
ImGui::StyleColorsDark();
// Initialize platform and OpenGL ES 3.0 renderers
ImGui_ImplSDL3_InitForOpenGL(state->window, state->gl_context);
const char* glsl_version = "#version 300 es";
ImGui_ImplOpenGL3_Init(glsl_version);
return SDL_APP_CONTINUE;
}
// Called for every incoming SDL event
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
AppState* state = (AppState*)appstate;
ImGui_ImplSDL3_ProcessEvent(event);
if (event->type == SDL_EVENT_QUIT)
{
return SDL_APP_SUCCESS; // Request application to terminate cleanly
}
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED &&
event->window.windowID == SDL_GetWindowID(state->window))
{
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
}
// Called once per frame to update and render
SDL_AppResult SDL_AppIterate(void* appstate)
{
AppState* state = (AppState*)appstate;
ImGuiIO& io = ImGui::GetIO();
// Start Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
// 1. Show the Dear ImGui demo window
if (state->show_demo_window)
{
ImGui::ShowDemoWindow(&state->show_demo_window);
}
// 2. Custom touch-friendly window
{
ImGui::Begin("Dear ImGui with SDL3 & GLES3 on Android");
ImGui::Text("Application average: %.3f ms/frame (%.1f FPS)",
1000.0f / io.Framerate, io.Framerate);
ImGui::Text("Screen scale factor: %.2f", state->scale);
ImGui::Separator();
ImGui::Checkbox("Show ImGui Demo Window", &state->show_demo_window);
// Large button easy to tap on mobile screens
if (ImGui::Button("Tap Me!", ImVec2(180 * state->scale, 60 * state->scale)))
{
state->click_counter++;
}
ImGui::SameLine();
ImGui::Text("Taps count: %d", state->click_counter);
ImGui::Separator();
ImGui::ColorEdit3("Background Color", (float*)&state->clear_color);
ImGui::End();
}
// Rendering
ImGui::Render();
int display_w, display_h;
SDL_GetWindowSizeInPixels(state->window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(state->clear_color.x * state->clear_color.w,
state->clear_color.y * state->clear_color.w,
state->clear_color.z * state->clear_color.w,
state->clear_color.w);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(state->window);
return SDL_APP_CONTINUE;
}
// Called once when the application is shutting down
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
(void)result;
AppState* state = (AppState*)appstate;
if (state)
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
if (state->gl_context)
{
SDL_GL_DestroyContext(state->gl_context);
}
if (state->window)
{
SDL_DestroyWindow(state->window);
}
delete state;
}
SDL_Quit();
}
Step 4: Verify Android Configuration Шаг 4: Проверка конфигурации Android
1. Verify that android-project/app/build.gradle includes the C++ shared library runtime argument -DANDROID_STL=c++_shared:
1. Убедитесь, что android-project/app/build.gradle содержит аргумент рантайма C++ -DANDROID_STL=c++_shared:
plugins {
id 'com.android.application'
}
android {
namespace "com.example.myapp"
compileSdkVersion 35
defaultConfig {
applicationId 'com.example.myapp'
minSdkVersion 21
targetSdkVersion 35
versionCode 1
versionName '1.0'
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_shared'
abiFilters 'arm64-v8a'
}
}
}
externalNativeBuild {
cmake {
path '../../CMakeLists.txt'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
prefab false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
}
2. Confirm android-project/app/src/main/java/com/example/myapp/MyActivity.java loads c++_shared, SDL3, and main in order:
2. Проверьте, что android-project/app/src/main/java/com/example/myapp/MyActivity.java загружает по порядку c++_shared, SDL3 и main:
package com.example.myapp;
import org.libsdl.app.SDLActivity;
public class MyActivity extends SDLActivity {
@Override
protected String[] getLibraries() {
return new String[] {
"c++_shared",
"SDL3",
"main"
};
}
}
Step 5: Build and Run Scripts Шаг 5: Скрипты сборки и запуска
Your project already contains the batch scripts in the root folder: В корневой папке вашего проекта уже находятся вспомогательные bat-скрипты:
build-android.bat
@echo off
pushd "%~dp0android-project"
call gradlew.bat assembleDebug
popd
install-android.bat
@echo off
set APK_PATH=%~dp0android-project\app\build\outputs\apk\debug\app-debug.apk
echo [*] Installing APK to device...
adb install -r "%APK_PATH%"
if errorlevel 1 (
echo [!] Installation failed.
exit /b 1
)
echo [*] Launching application...
adb shell am start -n com.example.myapp/com.example.myapp.MyActivity
Step 6: Build, Deploy, and Run Шаг 6: Сборка, установка и запуск
Run the build script from your terminal: Запустите скрипт сборки из терминала:
build-android.bat
Connect your Android device with USB debugging enabled and install the APK: Подключите Android-устройство с включенной отладкой по USB и запустите установку:
install-android.bat
The application will launch on your device displaying the Dear ImGui interface and Demo Window with touch controls and DPI scaling. Приложение запустится на вашем устройстве, отображая интерфейс Dear ImGui и демонстрационное окно Demo с поддержкой сенсорного ввода и масштабированием под экран.
Complete source code for this project is available on GitHub: Полный исходный код проекта доступен на GitHub:
View on GitHub Смотреть на GitHubSupport 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 Поддержка криптовалютой