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 и запуску на устройстве.

Before proceeding, make sure you have configured your development environment and downloaded the necessary libraries using the following guides: Перед продолжением убедитесь, что вы настроили рабочее окружение и загрузили необходимые библиотеки, следуя руководствам:
This guide builds directly on the starter project created in: Это руководство основано на проекте, созданном в: Integrating compiled libSDL3.so on Android (OpenGL ES 3.0) Интеграция собранной библиотеки libSDL3.so на Android (OpenGL ES 3.0)

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). Выберите подходящий вариант:

Option A: Classic Main Loop (main) Вариант А: Классический цикл обработки событий (main)

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;
}
Option B: Modern SDL3 Callbacks (SDL_AppInit, SDL_AppIterate, SDL_AppEvent, SDL_AppQuit) Вариант Б: Современные функции обратного вызова SDL3

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 Смотреть на GitHub

Support My Work Поддержать проект

If these tutorials helped you, consider buying me a coffee! Если эти туториалы вам помогли, вы можете поддержать автора.

Sberbank (Russia only) Сбербанк (только для РФ)

Sberbank SBP QR Code

Direct transfer via phone number (Russia only) Перевод по номеру телефона (только для РФ)

+7 (917) 212-29-59

USDT TRC20

USDT TRC20 QR Code

Support via Cryptocurrency Поддержка криптовалютой

TMtY1YifNf6FKvgeFmqKGQR4NStKr3csGp