misc/cxx_module: add CMake build and tests for the C++20 modules

Module support is easy to break from the header side, e.g. by a new
system include, a forward-declared platform type, or an export block
that misses a declaration. This adds a CMake project that builds every
module whose platform library is found, alongside the header build of
the same code.

Linking alone doesn't show that the module behaves like the headers,
so the main check is differential. 'DIFF' tests are compiled once with
'import imgui;' and once with '#include "imgui.h"'. Both builds run
the same scripted frames and must print the same hash of the draw data
and texture uploads (test_diff.h, compare_output.cmake). Backends that
need a GPU or a display are only linked, and wgpu is only compiled.

The core module and the null backend are also built for each
compile-time configuration that CI builds example_null with, because
IMGUI_DISABLE_* and friends change what gets exported. Presets use
Clang, Ninja and 'import std'. Toolchains for cross builds belong in
CMakeUserPresets.json, which is ignored along with build/.
This commit is contained in:
Sharadh Rajaraman 2026-09-28 16:30:53 +01:00
parent eaed70b584
commit a92dba93d9
16 changed files with 1003 additions and 0 deletions

2
.gitignore vendored
View File

@ -85,3 +85,5 @@ examples/example_sdl3_opengl3/example_sdl3_opengl3
examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3
examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3
examples/example_sdl3_vulkan/example_sdl3_vulkan
misc/cxx_module/build/
misc/cxx_module/CMakeUserPresets.json

View File

@ -0,0 +1,231 @@
# dear imgui: build and test the C++20 named modules 'imgui' (imgui.cppm) and 'imgui.impl.<backend>' (backends/imgui_impl_<backend>.cppm).
# Requires CMake with non-experimental 'import std' support (4.5, or a 4.4 development build), a Ninja generator, and Clang.
# Tested with CMake 4.4-dev + Clang 21 on Linux (libstdc++ 15), Android (NDK r29, libc++), Windows (MinGW-w64 libstdc++ 15, clang-cl + MSVC STL 14.44).
# Usage (see CMakePresets.json):
# cmake --workflow --preset clang
# Backends are enabled when their platform library is found, e.g. on Debian/Ubuntu:
# apt install libopengl-dev libglut-dev libsdl2-dev libsdl3-dev libglfw3-dev libvulkan-dev liballegro5-dev
# WebGPU has no system package: pass its headers with -DIMGUI_WEBGPU_INCLUDE_DIRS=<...> (compile-only test).
# Tests:
# - Every test is built against the module ('import imgui;') and, where relevant, against the headers ('#include "imgui.h"'):
# 'DIFF' tests run both builds with the same scripted input and check that they render the same draw data and textures.
# - The core library, the null backend and their tests are built for each compile-time configuration (imgui_add_config() below).
cmake_minimum_required(VERSION 3.30)
project(imgui_cxx_module LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Dear ImGui root directory")
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set(IMGUI_WARNINGS /W4 /WX -Wno-reserved-module-identifier # clang-cl: /W4 = -Wall -Wextra (its -Wall = -Weverything)
-Wno-include-angled-in-module-purview # MSVC STL's std.ixx includes its headers after 'export module std;'
-Wno-unused-command-line-argument # CMake passes both -TP and '-x c++-module' for module units
/D_CRT_SECURE_NO_WARNINGS) # imgui sources #define it before CRT headers, but modules get those from 'std'
else()
set(IMGUI_WARNINGS -Wall -Wextra -Werror -Wno-reserved-module-identifier)
endif() # the synthesized 'std' targets inherit these flags
if(ANDROID)
# bionic defines some C library functions 'static inline' (ctype.h, FORTIFY wrappers), which libc++'s 'std' module can't export.
# (options, not definitions: the synthesized 'std' targets only inherit options)
add_compile_options(-D__BIONIC_CTYPE_INLINE=inline -U_FORTIFY_SOURCE)
endif()
enable_testing()
#-----------------------------------------------------------------------------
# Helpers
#-----------------------------------------------------------------------------
# Targets are suffixed with ${IMGUI_SFX}: empty for the default configuration, '_<config>' otherwise (see imgui_add_config()).
# imgui_add_backend(<name> <libs...> [OPTIONS <compile options...>])
# - imgui_impl_<name>: module 'imgui.impl.<name>'
# - imgui_header_impl_<name>: regular (header) build of the backend, to check it's unaffected by the module support
function(imgui_add_backend name)
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "OPTIONS")
set(module imgui_impl_${name}${IMGUI_SFX})
add_library(${module} STATIC)
target_sources(${module} PUBLIC FILE_SET CXX_MODULES BASE_DIRS "${IMGUI_DIR}" FILES "${IMGUI_DIR}/backends/imgui_impl_${name}.cppm")
target_link_libraries(${module} PUBLIC imgui${IMGUI_SFX} ${arg_UNPARSED_ARGUMENTS})
target_compile_options(${module} PRIVATE ${IMGUI_WARNINGS} ${arg_OPTIONS})
set(header imgui_header_impl_${name}${IMGUI_SFX})
add_library(${header} STATIC "${IMGUI_DIR}/backends/imgui_impl_${name}.cpp")
target_include_directories(${header} PUBLIC "${IMGUI_DIR}/backends")
target_link_libraries(${header} PUBLIC imgui_header${IMGUI_SFX} ${arg_UNPARSED_ARGUMENTS})
target_compile_options(${header} PRIVATE ${IMGUI_WARNINGS} ${arg_OPTIONS})
set(IMGUI_BACKENDS ${IMGUI_BACKENDS} ${name} PARENT_SCOPE)
endfunction()
# imgui_add_test(<name> RUN|DIFF|LINK|COMPILE <sources...> LIBS <targets...>)
# - RUN: compile, link, and run (natively or with CMAKE_CROSSCOMPILING_EMULATOR); LINK: compile and link; COMPILE: compile only.
# - DIFF: like RUN, and also build <sources> in header mode (IMGUI_TEST_HEADER defined, linking the 'imgui_header*' equivalent
# of each LIBS target), run both, and check that their outputs match.
# - Skipped if any of the LIBS targets doesn't exist (backend not found).
function(imgui_add_test name kind)
cmake_parse_arguments(PARSE_ARGV 2 arg "" "" "LIBS")
foreach(lib IN LISTS arg_LIBS)
if(NOT TARGET ${lib})
return()
endif()
endforeach()
set(name ${name}${IMGUI_SFX})
if(kind STREQUAL "COMPILE")
add_library(${name} OBJECT ${arg_UNPARSED_ARGUMENTS})
else()
add_executable(${name} ${arg_UNPARSED_ARGUMENTS})
endif()
target_link_libraries(${name} PRIVATE ${arg_LIBS})
target_compile_options(${name} PRIVATE ${IMGUI_WARNINGS})
if(kind STREQUAL "DIFF")
list(TRANSFORM arg_LIBS REPLACE "^imgui" "imgui_header")
add_executable(${name}_header ${arg_UNPARSED_ARGUMENTS})
target_link_libraries(${name}_header PRIVATE ${arg_LIBS})
target_compile_definitions(${name}_header PRIVATE IMGUI_TEST_HEADER)
target_compile_options(${name}_header PRIVATE ${IMGUI_WARNINGS})
endif()
if(NOT CMAKE_CROSSCOMPILING OR CMAKE_CROSSCOMPILING_EMULATOR)
if(kind STREQUAL "RUN")
add_test(NAME ${name} COMMAND ${name})
elseif(kind STREQUAL "DIFF")
add_test(NAME ${name} COMMAND "${CMAKE_COMMAND}" "-DEMULATOR=${CMAKE_CROSSCOMPILING_EMULATOR}"
"-DEXPECTED=$<TARGET_FILE:${name}_header>" "-DACTUAL=$<TARGET_FILE:${name}>" -P "${CMAKE_CURRENT_SOURCE_DIR}/compare_output.cmake")
endif()
endif()
endfunction()
# imgui_add_config(<name> [LIBRARIES_ONLY] <compile definitions...>)
# Builds the 'imgui' module, its header-mode equivalent, the null backend, and (unless LIBRARIES_ONLY) the core tests
# with a compile-time configuration. Definitions are PUBLIC: importers must use the same configuration (see imgui_macros.h).
function(imgui_add_config cfg)
cmake_parse_arguments(PARSE_ARGV 1 arg "LIBRARIES_ONLY" "" "")
if(NOT cfg STREQUAL "default")
set(IMGUI_SFX _${cfg})
endif()
# Module 'imgui'
add_library(imgui${IMGUI_SFX} STATIC)
target_sources(imgui${IMGUI_SFX} PUBLIC FILE_SET CXX_MODULES BASE_DIRS "${IMGUI_DIR}" FILES "${IMGUI_DIR}/imgui.cppm")
target_include_directories(imgui${IMGUI_SFX} PUBLIC "${IMGUI_DIR}") # imgui_macros.h, imconfig.h
target_compile_definitions(imgui${IMGUI_SFX} PUBLIC ${arg_UNPARSED_ARGUMENTS})
target_compile_options(imgui${IMGUI_SFX} PRIVATE ${IMGUI_WARNINGS})
# Regular (header) build of the same sources, to check it's unaffected by the module support
add_library(imgui_header${IMGUI_SFX} STATIC
"${IMGUI_DIR}/imgui.cpp" "${IMGUI_DIR}/imgui_demo.cpp" "${IMGUI_DIR}/imgui_draw.cpp" "${IMGUI_DIR}/imgui_tables.cpp" "${IMGUI_DIR}/imgui_widgets.cpp")
target_include_directories(imgui_header${IMGUI_SFX} PUBLIC "${IMGUI_DIR}")
target_compile_definitions(imgui_header${IMGUI_SFX} PUBLIC ${arg_UNPARSED_ARGUMENTS})
target_compile_options(imgui_header${IMGUI_SFX} PRIVATE ${IMGUI_WARNINGS})
imgui_add_backend(null)
if(arg_LIBRARIES_ONLY)
return()
endif()
imgui_add_test(test_module RUN test_module.cpp test_module_macros.cpp LIBS imgui${IMGUI_SFX})
imgui_add_test(test_header_internal RUN test_header_internal.cpp LIBS imgui_header${IMGUI_SFX})
imgui_add_test(test_backend_null DIFF test_backend_null.cpp LIBS imgui_impl_null${IMGUI_SFX})
endfunction()
#-----------------------------------------------------------------------------
# Compile-time configurations (same as the example_null builds of .github/workflows/build.yml)
#-----------------------------------------------------------------------------
imgui_add_config(default)
imgui_add_config(no_obsolete IMGUI_DISABLE_OBSOLETE_FUNCTIONS)
imgui_add_config(wchar32 IMGUI_USE_WCHAR32)
imgui_add_config(large_idx "ImDrawIdx=unsigned int" "ImTextureID=void*")
imgui_add_config(no_demo_debug IMGUI_DISABLE_DEMO_WINDOWS IMGUI_DISABLE_DEBUG_TOOLS)
imgui_add_config(no_file IMGUI_DISABLE_FILE_FUNCTIONS)
imgui_add_config(bgra IMGUI_USE_BGRA_PACKED_COLOR)
imgui_add_config(math_operators IMGUI_DEFINE_MATH_OPERATORS)
imgui_add_config(disable LIBRARIES_ONLY IMGUI_DISABLE)
#-----------------------------------------------------------------------------
# Modules 'imgui.impl.<backend>' (default configuration)
#-----------------------------------------------------------------------------
if(WIN32)
imgui_add_backend(win32 gdi32 dwmapi)
imgui_add_backend(dx9 d3d9)
imgui_add_backend(dx10 d3d10 d3dcompiler)
imgui_add_backend(dx11 d3d11 d3dcompiler)
imgui_add_backend(dx12 d3d12 dxgi d3dcompiler OPTIONS -Wno-unused-but-set-variable) # upstream warning with NDEBUG (header build too): 'hr' only used by IM_ASSERT()
endif()
if(ANDROID)
imgui_add_backend(android android log OPTIONS -Wno-unused-variable) # upstream warning (header build too): unused 'g_LogTag'
imgui_add_backend(opengl3 GLESv3) # OpenGL ES 3 is selected automatically on Android
else()
find_package(OpenGL)
if(TARGET OpenGL::GL)
imgui_add_backend(opengl2 OpenGL::GL)
if(WIN32)
imgui_add_backend(opengl3 OpenGL::GL)
else()
imgui_add_backend(opengl3 ${CMAKE_DL_LIBS}) # uses its own loader (imgui_impl_opengl3_loader.h)
endif()
endif()
find_package(GLUT)
if(TARGET GLUT::GLUT AND TARGET OpenGL::GL)
imgui_add_backend(glut GLUT::GLUT OpenGL::GL)
endif()
endif()
find_package(SDL2 CONFIG)
if(TARGET SDL2::SDL2)
imgui_add_backend(sdl2 SDL2::SDL2)
imgui_add_backend(sdlrenderer2 SDL2::SDL2)
endif()
find_package(SDL3 CONFIG)
if(TARGET SDL3::SDL3)
imgui_add_backend(sdl3 SDL3::SDL3)
imgui_add_backend(sdlrenderer3 SDL3::SDL3)
imgui_add_backend(sdlgpu3 SDL3::SDL3)
endif()
find_package(glfw3 CONFIG)
if(TARGET glfw)
imgui_add_backend(glfw glfw)
endif()
find_package(Vulkan)
if(TARGET Vulkan::Vulkan)
imgui_add_backend(vulkan Vulkan::Vulkan)
endif()
if(NOT CMAKE_CROSSCOMPILING)
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(ALLEGRO5 IMPORTED_TARGET allegro-5 allegro_primitives-5)
endif()
if(TARGET PkgConfig::ALLEGRO5)
imgui_add_backend(allegro5 PkgConfig::ALLEGRO5 OPTIONS -Wno-unused-variable) # upstream warning (header build too): unused 'platform_io'
endif()
endif()
set(IMGUI_WEBGPU_INCLUDE_DIRS "" CACHE STRING "WebGPU include directories providing <webgpu/webgpu.h> (enables the compile-only 'wgpu' backend)")
set(IMGUI_WEBGPU_BACKEND "DAWN" CACHE STRING "WebGPU implementation: DAWN or WGPU")
if(IMGUI_WEBGPU_INCLUDE_DIRS)
add_library(imgui_webgpu_headers INTERFACE)
target_include_directories(imgui_webgpu_headers SYSTEM INTERFACE ${IMGUI_WEBGPU_INCLUDE_DIRS})
target_compile_definitions(imgui_webgpu_headers INTERFACE IMGUI_IMPL_WEBGPU_BACKEND_${IMGUI_WEBGPU_BACKEND})
imgui_add_backend(wgpu imgui_webgpu_headers)
endif()
message(STATUS "imgui backend modules: ${IMGUI_BACKENDS}")
#-----------------------------------------------------------------------------
# Backend tests (default configuration)
#-----------------------------------------------------------------------------
imgui_add_test(test_backend_sdl2 DIFF test_backend_sdl2.cpp LIBS imgui_impl_sdl2 imgui_impl_sdlrenderer2)
imgui_add_test(test_backend_sdl3 DIFF test_backend_sdl3.cpp LIBS imgui_impl_sdl3 imgui_impl_sdlrenderer3 imgui_impl_sdlgpu3)
# Backends that need a GPU/display to run
imgui_add_test(test_backend_gpu LINK test_backend_gpu.cpp LIBS imgui_impl_opengl2 imgui_impl_opengl3 imgui_impl_glut imgui_impl_glfw imgui_impl_vulkan)
imgui_add_test(test_backend_allegro5 LINK test_backend_allegro5.cpp LIBS imgui_impl_allegro5)
imgui_add_test(test_backend_windows LINK test_backend_windows.cpp LIBS imgui_impl_win32 imgui_impl_dx9 imgui_impl_dx10 imgui_impl_dx11 imgui_impl_dx12 imgui_impl_opengl2 imgui_impl_opengl3)
imgui_add_test(test_backend_android LINK test_backend_android.cpp LIBS imgui_impl_android imgui_impl_opengl3 imgui_impl_vulkan)
imgui_add_test(test_backend_wgpu COMPILE test_backend_wgpu.cpp LIBS imgui_impl_wgpu) # headers only, nothing to link

View File

@ -0,0 +1,38 @@
{
"version": 6,
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "clang",
"displayName": "Clang + Ninja + import std",
"inherits": "base",
"cacheVariables": { "CMAKE_CXX_COMPILER": "clang++" }
}
],
"buildPresets": [
{ "name": "clang", "configurePreset": "clang" }
],
"testPresets": [
{ "name": "base", "hidden": true, "output": { "outputOnFailure": true }, "execution": { "noTestsAction": "error" } },
{ "name": "clang", "inherits": "base", "configurePreset": "clang" }
],
"workflowPresets": [
{
"name": "clang",
"steps": [
{ "type": "configure", "name": "clang" },
{ "type": "build", "name": "clang" },
{ "type": "test", "name": "clang" }
]
}
]
}

View File

@ -0,0 +1,13 @@
# dear imgui: run two test executables (header and module builds of the same source) and check that their outputs match.
# Usage: cmake [-DEMULATOR=<emulator>] -DEXPECTED=<header build> -DACTUAL=<module build> -P compare_output.cmake
foreach(exe IN ITEMS EXPECTED ACTUAL)
execute_process(COMMAND ${EMULATOR} "${${exe}}" OUTPUT_VARIABLE output_${exe} RESULT_VARIABLE result ERROR_VARIABLE error)
if(NOT result EQUAL 0)
message(FATAL_ERROR "${${exe}} failed (${result}):\n${output_${exe}}${error}")
endif()
endforeach()
message("${output_ACTUAL}")
if(NOT output_EXPECTED STREQUAL output_ACTUAL)
message(FATAL_ERROR "Output differs from the header build:\n${output_EXPECTED}")
endif()

View File

@ -0,0 +1,23 @@
// dear imgui: compile/link test for the C++20 backend module 'imgui.impl.allegro5' (needs a display to run).
#include <allegro5/allegro.h> // application-side platform headers
import std;
import imgui;
import imgui.impl.allegro5;
static_assert(std::is_same_v<decltype(&ImGui_ImplAllegro5_Init), bool (*)(ALLEGRO_DISPLAY*)>);
void RenderFrameAllegro5(ALLEGRO_DISPLAY* display, ALLEGRO_EVENT* event)
{
ImGui_ImplAllegro5_Init(display);
ImGui_ImplAllegro5_ProcessEvent(event);
ImGui_ImplAllegro5_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplAllegro5_RenderDrawData(ImGui::GetDrawData());
}
int main()
{
return 0;
}

View File

@ -0,0 +1,49 @@
// dear imgui: compile/link test for the C++20 backend modules for Android:
// 'imgui.impl.android', 'imgui.impl.opengl3' (OpenGL ES 3), 'imgui.impl.vulkan'.
// Checks that their API is exported, usable from an importer, and links (not run).
#include <android/native_window.h> // application-side platform headers
#include <android/input.h>
#include <vulkan/vulkan.h>
import std;
import imgui;
import imgui.impl.android;
import imgui.impl.opengl3;
import imgui.impl.vulkan;
static_assert(std::is_same_v<decltype(&ImGui_ImplAndroid_HandleInputEvent), std::int32_t (*)(const AInputEvent*)>);
static_assert(sizeof(ImGui_ImplVulkan_InitInfo) > 0);
void RenderFrameOpenGL3(ANativeWindow* window, const AInputEvent* input_event)
{
ImGui_ImplAndroid_Init(window);
ImGui_ImplOpenGL3_Init("#version 300 es");
ImGui_ImplAndroid_HandleInputEvent(input_event);
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplAndroid_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
void RenderFrameVulkan(VkInstance instance, VkDevice device, VkQueue queue, VkCommandBuffer command_buffer)
{
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.ApiVersion = VK_API_VERSION_1_1;
init_info.Instance = instance;
init_info.PhysicalDevice = ImGui_ImplVulkanH_SelectPhysicalDevice(instance);
init_info.Device = device;
init_info.Queue = queue;
init_info.MinImageCount = 2;
init_info.ImageCount = 2;
ImGui_ImplVulkan_Init(&init_info);
ImGui_ImplVulkan_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), command_buffer);
}
int main()
{
return 0;
}

View File

@ -0,0 +1,63 @@
// dear imgui: compile/link test for the C++20 backend modules that need a GPU/display to run:
// 'imgui.impl.opengl2', 'imgui.impl.opengl3', 'imgui.impl.glut', 'imgui.impl.glfw', 'imgui.impl.vulkan'.
// Checks that their API is exported, usable from an importer, and links (not run).
#include <GLFW/glfw3.h> // application-side platform headers
#include <vulkan/vulkan.h>
import std;
import imgui;
import imgui.impl.opengl2;
import imgui.impl.opengl3;
import imgui.impl.glut;
import imgui.impl.glfw;
import imgui.impl.vulkan;
// Exported structs are complete types
static_assert(sizeof(ImGui_ImplVulkan_InitInfo) > 0);
static_assert(sizeof(ImGui_ImplVulkanH_Window) > 0);
static_assert(sizeof(ImGui_ImplOpenGL3_RenderState) > 0);
static_assert(std::is_same_v<decltype(&ImGui_ImplGlfw_InitForOpenGL), bool (*)(GLFWwindow*, bool)>);
void RenderFrameOpenGL3(GLFWwindow* window)
{
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130");
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
void RenderFrameOpenGL2()
{
ImGui_ImplGLUT_Init();
ImGui_ImplOpenGL2_Init();
ImGui_ImplOpenGL2_NewFrame();
ImGui_ImplGLUT_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
}
void RenderFrameVulkan(VkInstance instance, VkDevice device, VkQueue queue, VkCommandBuffer command_buffer)
{
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.ApiVersion = VK_API_VERSION_1_3;
init_info.Instance = instance;
init_info.PhysicalDevice = ImGui_ImplVulkanH_SelectPhysicalDevice(instance);
init_info.Device = device;
init_info.Queue = queue;
init_info.MinImageCount = 2;
init_info.ImageCount = 2;
ImGui_ImplVulkan_Init(&init_info);
ImGui_ImplVulkan_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), command_buffer);
}
int main()
{
return 0;
}

View File

@ -0,0 +1,38 @@
// dear imgui: run test for the C++20 backend module 'imgui.impl.null' (see backends/imgui_impl_null.cppm).
// Differential test (see test_diff.h): the header build (IMGUI_TEST_HEADER) and the module build must print the same output.
#ifdef IMGUI_TEST_HEADER
#include "imgui.h"
#include "imgui_impl_null.h"
#include <stdio.h>
#else
import std.compat;
import imgui;
import imgui.impl.null;
#endif
#include "test_diff.h"
int main()
{
ImGui::CreateContext();
TestDiff_Init();
if (!ImGui_ImplNull_Init())
return 1;
TestHash hash;
for (int frame = 0; frame < TestDiff_FrameCount; frame++)
{
ImGui_ImplNull_NewFrame();
TestDiff_NewFrame(frame);
ImGui::NewFrame();
TestDiff_ShowWindows();
ImGui::Render();
TestDiff_HashDrawData(hash, ImGui::GetDrawData());
ImGui_ImplNullRender_RenderDrawData(ImGui::GetDrawData());
}
ImGui_ImplNull_Shutdown();
ImGui::DestroyContext();
hash.Print("imgui.impl.null");
return hash.VtxCount > 0 ? 0 : 1;
}

View File

@ -0,0 +1,74 @@
// dear imgui: run test for the C++20 backend modules 'imgui.impl.sdl2' + 'imgui.impl.sdlrenderer2'.
// Runs headless using SDL's "dummy" video driver and the software renderer.
// Differential test (see test_diff.h): the header build (IMGUI_TEST_HEADER) and the module build must print the same output.
#include <SDL.h> // SDL API + macros for the application (modules don't export SDL)
#ifdef IMGUI_TEST_HEADER
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_sdlrenderer2.h"
#include <stdio.h>
#else
import std.compat;
import imgui;
import imgui.impl.sdl2;
import imgui.impl.sdlrenderer2;
#endif
#include "test_diff.h"
int main(int, char**)
{
SDL_setenv("SDL_VIDEODRIVER", "dummy", 1);
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
printf("SDL_Init() failed: %s\n", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("imgui module test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, 0);
SDL_Renderer* renderer = window ? SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE) : nullptr;
if (renderer == nullptr)
{
printf("SDL_CreateWindow()/SDL_CreateRenderer() failed: %s\n", SDL_GetError());
return 1;
}
ImGui::CreateContext();
TestDiff_Init();
ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
ImGui_ImplSDLRenderer2_Init(renderer);
ImGui_ImplSDL2_SetGamepadMode(ImGui_ImplSDL2_GamepadMode_AutoFirst);
TestHash hash;
for (int frame = 0; frame < TestDiff_FrameCount; frame++)
{
SDL_Event event;
while (SDL_PollEvent(&event))
ImGui_ImplSDL2_ProcessEvent(&event);
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
TestDiff_NewFrame(frame);
ImGui::NewFrame();
TestDiff_ShowWindows();
ImGui::Render();
TestDiff_HashDrawData(hash, ImGui::GetDrawData());
SDL_RenderClear(renderer);
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer);
}
// Rendered image of the last frame
int w = 0, h = 0;
SDL_GetRendererOutputSize(renderer, &w, &h);
ImVector<unsigned char> pixels;
pixels.resize(w * h * 4);
SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_RGBA32, pixels.Data, w * 4);
hash.Add(pixels.Data, (size_t)pixels.Size);
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
hash.Print("imgui.impl.sdl2 + imgui.impl.sdlrenderer2");
return hash.VtxCount > 0 ? 0 : 1;
}

View File

@ -0,0 +1,92 @@
// dear imgui: run test for the C++20 backend modules 'imgui.impl.sdl3' + 'imgui.impl.sdlrenderer3' + 'imgui.impl.sdlgpu3'.
// Runs sdl3 + sdlrenderer3 headless using SDL's "dummy" video driver and the software renderer. sdlgpu3 is compiled/linked only.
// Differential test (see test_diff.h): the header build (IMGUI_TEST_HEADER) and the module build must print the same output.
#include <SDL3/SDL.h> // SDL API + macros for the application (modules don't export SDL)
#ifdef IMGUI_TEST_HEADER
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_sdlrenderer3.h"
#include "imgui_impl_sdlgpu3.h"
#include <stdio.h>
#else
import std.compat;
import imgui;
import imgui.impl.sdl3;
import imgui.impl.sdlrenderer3;
import imgui.impl.sdlgpu3;
#endif
#include "test_diff.h"
static_assert(sizeof(ImGui_ImplSDLGPU3_InitInfo) > 0);
void RenderFrameSDLGPU3(SDL_Window* window, SDL_GPUDevice* device, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass)
{
ImGui_ImplSDL3_InitForSDLGPU(window);
ImGui_ImplSDLGPU3_InitInfo init_info;
init_info.Device = device;
init_info.ColorTargetFormat = SDL_GetGPUSwapchainTextureFormat(device, window);
ImGui_ImplSDLGPU3_Init(&init_info);
ImGui_ImplSDLGPU3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplSDLGPU3_PrepareDrawData(ImGui::GetDrawData(), command_buffer);
ImGui_ImplSDLGPU3_RenderDrawData(ImGui::GetDrawData(), command_buffer, render_pass);
}
int main(int, char**)
{
SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "dummy");
if (!SDL_Init(SDL_INIT_VIDEO))
{
printf("SDL_Init() failed: %s\n", SDL_GetError());
return 1;
}
SDL_Window* window = SDL_CreateWindow("imgui module test", 1280, 720, 0);
SDL_Renderer* renderer = window ? SDL_CreateRenderer(window, SDL_SOFTWARE_RENDERER) : nullptr;
if (renderer == nullptr)
{
printf("SDL_CreateWindow()/SDL_CreateRenderer() failed: %s\n", SDL_GetError());
return 1;
}
ImGui::CreateContext();
TestDiff_Init();
ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
ImGui_ImplSDLRenderer3_Init(renderer);
ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode_AutoFirst);
TestHash hash;
for (int frame = 0; frame < TestDiff_FrameCount; frame++)
{
SDL_Event event;
while (SDL_PollEvent(&event))
ImGui_ImplSDL3_ProcessEvent(&event);
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
TestDiff_NewFrame(frame);
ImGui::NewFrame();
TestDiff_ShowWindows();
ImGui::Render();
TestDiff_HashDrawData(hash, ImGui::GetDrawData());
SDL_RenderClear(renderer);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
}
// Rendered image of the last frame
if (SDL_Surface* surface = SDL_RenderReadPixels(renderer, nullptr))
{
hash.Add(surface->pixels, (size_t)(surface->pitch * surface->h));
SDL_DestroySurface(surface);
}
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
hash.Print("imgui.impl.sdl3 + imgui.impl.sdlrenderer3");
return hash.VtxCount > 0 ? 0 : 1;
}

View File

@ -0,0 +1,21 @@
// dear imgui: compile-only test for the C++20 backend module 'imgui.impl.wgpu' (Dawn or wgpu-native headers, no library needed).
#include <webgpu/webgpu.h> // application-side platform headers
import std;
import imgui;
import imgui.impl.wgpu;
static_assert(sizeof(ImGui_ImplWGPU_InitInfo) > 0);
void RenderFrameWGPU(WGPUDevice device, WGPURenderPassEncoder pass_encoder)
{
ImGui_ImplWGPU_InitInfo init_info;
init_info.Device = device;
init_info.RenderTargetFormat = WGPUTextureFormat_BGRA8Unorm;
ImGui_ImplWGPU_Init(&init_info);
ImGui_ImplWGPU_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass_encoder);
std::println("{}", ImGui_ImplWGPU_GetBackendTypeName(WGPUBackendType_Vulkan));
}

View File

@ -0,0 +1,93 @@
// dear imgui: compile/link test for the C++20 backend modules for Windows:
// 'imgui.impl.win32', 'imgui.impl.dx9', 'imgui.impl.dx10', 'imgui.impl.dx11', 'imgui.impl.dx12', 'imgui.impl.opengl2', 'imgui.impl.opengl3'.
// Checks that their API is exported, usable from an importer, and links (not run).
#include <windows.h> // application-side platform headers
#include <d3d9.h>
#include <d3d10_1.h>
#include <d3d10.h>
#include <d3d11.h>
#include <d3d12.h>
import std;
import imgui;
import imgui.impl.win32;
import imgui.impl.dx9;
import imgui.impl.dx10;
import imgui.impl.dx11;
import imgui.impl.dx12;
import imgui.impl.opengl2;
import imgui.impl.opengl3;
static_assert(sizeof(ImGui_ImplDX12_InitInfo) > 0);
static_assert(std::is_same_v<decltype(&ImGui_ImplWin32_WndProcHandler), LRESULT (*)(HWND, UINT, WPARAM, LPARAM)>);
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
return true;
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
void RenderFrameDX9(HWND hwnd, IDirect3DDevice9* device)
{
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplDX9_Init(device);
ImGui_ImplDX9_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData());
}
void RenderFrameDX10(ID3D10Device* device)
{
ImGui_ImplDX10_Init(device);
ImGui_ImplDX10_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplDX10_RenderDrawData(ImGui::GetDrawData());
}
void RenderFrameDX11(ID3D11Device* device, ID3D11DeviceContext* device_context)
{
ImGui_ImplDX11_Init(device, device_context);
ImGui_ImplDX11_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
void RenderFrameDX12(ID3D12Device* device, ID3D12CommandQueue* command_queue, ID3D12DescriptorHeap* srv_heap, ID3D12GraphicsCommandList* command_list)
{
ImGui_ImplDX12_InitInfo init_info;
init_info.Device = device;
init_info.CommandQueue = command_queue;
init_info.NumFramesInFlight = 2;
init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
init_info.DSVFormat = DXGI_FORMAT_UNKNOWN;
init_info.SrvDescriptorHeap = srv_heap;
ImGui_ImplDX12_Init(&init_info);
ImGui_ImplDX12_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), command_list);
}
void RenderFrameOpenGL(HWND hwnd)
{
ImGui_ImplWin32_InitForOpenGL(hwnd);
ImGui_ImplOpenGL2_Init();
ImGui_ImplOpenGL3_Init();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
}
int main()
{
ImGui_ImplWin32_EnableDpiAwareness();
return 0;
}

View File

@ -0,0 +1,86 @@
// dear imgui: helpers for the differential tests (see imgui_add_test(... DIFF ...) in CMakeLists.txt).
// Each test is built twice, with 'import imgui;' and with '#include "imgui.h"' (IMGUI_TEST_HEADER), runs the same scripted frames,
// and prints a hash of everything rendered (draw data, texture uploads): the outputs of both builds must match.
// Included after the imports/includes: uses no other header.
#pragma once
static const int TestDiff_FrameCount = 120;
// FNV-1a hash
struct TestHash
{
unsigned long long Value = 14695981039346656037ull;
int VtxCount = 0;
void Add(const void* data, size_t size) { const unsigned char* p = (const unsigned char*)data; for (size_t n = 0; n < size; n++) Value = (Value ^ p[n]) * 1099511628211ull; }
template<typename T> void Add(const T& v) { Add(&v, sizeof(v)); }
void Print(const char* name) const { printf("%s: frames=%d vertices=%d hash=%08x%08x\n", name, TestDiff_FrameCount, VtxCount, (unsigned int)(Value >> 32), (unsigned int)Value); }
};
static void TestDiff_Init()
{
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::GetPlatformIO().Platform_OpenInShellFn = [](ImGuiContext*, const char*) { return false; }; // scripted clicks may hit links
}
// Call after the backends' NewFrame(): scripted input (mouse moves and clicks, keyboard navigation and text), fixed time step
static void TestDiff_NewFrame(int frame)
{
ImGuiIO& io = ImGui::GetIO();
io.DeltaTime = 1.0f / 60.0f;
io.AddMousePosEvent((float)(frame * 37 % 1000), (float)(frame * 23 % 700));
io.AddMouseButtonEvent(ImGuiMouseButton_Left, frame % 10 == 5);
io.AddKeyEvent(ImGuiKey_DownArrow, frame % 4 == 1);
io.AddKeyEvent(ImGuiKey_Space, frame % 12 == 7);
if (frame % 8 == 3)
io.AddInputCharacter('a' + frame % 26);
}
static void TestDiff_ShowWindows()
{
ImGui::ShowDemoWindow();
ImGui::ShowDebugLogWindow();
ImGui::ShowIDStackToolWindow();
ImGui::Begin("Style Editor");
ImGui::ShowStyleEditor();
ImGui::End();
// Not compared: ShowMetricsWindow() displays pointers, which differ between runs, and ShowAboutWindow() displays build
// information, which differs between builds (e.g. like imgui_single_file.h, imgui.cppm compiles imgui_demo.cpp after
// imgui_internal.h, which defines IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS when IMGUI_DISABLE_FILE_FUNCTIONS is defined).
}
// Call after ImGui::Render(), before the renderer backend processes the texture requests
static void TestDiff_HashDrawData(TestHash& hash, const ImDrawData* draw_data)
{
hash.Add(draw_data->DisplayPos);
hash.Add(draw_data->DisplaySize);
hash.VtxCount += draw_data->TotalVtxCount;
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
hash.Add(draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.size_in_bytes());
hash.Add(draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.size_in_bytes());
for (const ImDrawCmd& cmd : draw_list->CmdBuffer)
{
hash.Add(cmd.ClipRect);
hash.Add(cmd.VtxOffset);
hash.Add(cmd.IdxOffset);
hash.Add(cmd.ElemCount);
hash.Add(cmd.TexRef._TexData ? cmd.TexRef._TexData->UniqueID : -1); // (not the texture pointer/identifier)
}
}
if (draw_data->Textures != nullptr)
for (const ImTextureData* tex : *draw_data->Textures)
{
hash.Add(tex->UniqueID);
hash.Add(tex->Status);
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
{
hash.Add(tex->Format);
hash.Add(tex->Width);
hash.Add(tex->Height);
hash.Add(tex->Pixels, (size_t)tex->GetSizeInBytes());
}
}
}

View File

@ -0,0 +1,31 @@
// dear imgui: header-only (non-module) test for imgui_internal.h.
// imgui_internal.h must work on its own, including after imgui_macros.h (which defines IMGUI_VERSION without the declarations of imgui.h).
#include "imgui_macros.h"
#include "imgui_internal.h"
#include <stdio.h>
int main()
{
IMGUI_CHECKVERSION();
ImGuiContext* ctx = ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(ctx); // imgui_internal.h API
io.DisplaySize = ImVec2(1280.0f, 720.0f);
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // (no renderer: textures are never uploaded)
ImGuiWindow* window = nullptr;
for (int frame = 0; frame < 2; frame++) // new windows are hidden on their first frame
{
ImGui::NewFrame();
ImGui::Begin("Test");
window = ImGui::GetCurrentWindow();
ImGui::Text("Hello");
ImGui::End();
ImGui::Render();
}
const bool ok = window != nullptr && ImGui::GetDrawData()->TotalVtxCount > 0 && ctx->FrameCount == 2;
printf("window=%p vtx=%d frame=%d\n", (void*)window, ImGui::GetDrawData()->TotalVtxCount, ctx->FrameCount);
ImGui::DestroyContext(ctx);
return ok ? 0 : 1;
}

View File

@ -0,0 +1,103 @@
// dear imgui: compile/run test for the C++20 named module 'imgui' (see imgui.cppm).
// This translation unit only imports modules: no macros are available, so it uses the constexpr alternatives.
// See test_module_macros.cpp for a translation unit that also includes imgui_macros.h.
import std;
import imgui;
#if defined(IMGUI_VERSION) || defined(IM_COL32) || defined(IM_ASSERT) || defined(IMGUI_EXPORT)
#error "Macros must not leak through 'import imgui;'"
#endif
// Constexpr alternatives to macros
static_assert(ImGuiVersionNum >= 19200);
static_assert(ImGuiVersion[0] != 0);
static_assert(ImCol32(255, 255, 255, 255) == ImCol32_White);
static_assert(ImCol32(0, 0, 0, 255) == ImCol32_Black);
static_assert(ImCol32_BlackTrans == 0);
static_assert(((ImCol32(0x12, 0x34, 0x56, 0x78) >> ImCol32_GShift) & 0xFF) == 0x34);
static_assert((ImCol32_White & ImCol32_AMask) == ImCol32_AMask);
static_assert(ImUnicodeCodepoint_Invalid == 0xFFFD);
static_assert(std::string_view(ImGuiPayloadType_Color4F) == "_COL4F");
constexpr int ints[] = { 1, 2, 3 };
static_assert(ImCountOf(ints) == 3);
// Math operators are exported
static_assert(requires(ImVec2 a, ImVec2 b) { a + b; a * 2.0f; a += b; });
// Exported types/enums
static_assert(std::is_enum_v<ImGuiWindowFlags_>);
static_assert(sizeof(ImDrawIdx) == 2 || sizeof(ImDrawIdx) == 4);
static_assert(std::is_same_v<decltype(ImGui::GetIO()), ImGuiIO&>);
// Internal types stay incomplete (imgui_internal.h is not exported)
template<typename T> concept IsComplete = requires { sizeof(T); };
static_assert(!IsComplete<ImGuiContext>);
int TestModuleWithMacros(); // test_module_macros.cpp
static void RenderTextures(ImDrawData* draw_data)
{
// Minimal renderer: acknowledge texture requests (same as imgui_impl_null.cpp)
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
{
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
tex->SetStatus(ImTextureStatus_OK);
else if (tex->Status == ImTextureStatus_WantDestroy)
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
int main()
{
if (!ImGui::CheckVersion())
return 1;
ImGuiContext* ctx = ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures;
io.IniFilename = nullptr;
int vtx_count = 0;
for (int n = 0; n < 5; n++)
{
io.DisplaySize = ImVec2(1280, 720);
io.DeltaTime = 1.0f / 60.0f;
ImGui::NewFrame();
static float f = 0.5f;
static std::array<char, 64> buf = { "Hello, module!" };
ImGui::Begin("Module test");
ImGui::Text("Dear ImGui %s (%d)", ImGui::GetVersion(), ImGuiVersionNum);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
ImGui::InputText("text", buf.data(), buf.size());
if (ImGui::BeginTable("table", 2))
{
ImGui::TableNextColumn(); ImGui::TextUnformatted("A");
ImGui::TableNextColumn(); ImGui::TextUnformatted("B");
ImGui::EndTable();
}
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
draw_list->AddRect(p, p + ImVec2(50, 50) * 2.0f, ImCol32(255, 0, 0, 255));
ImGui::End();
ImGui::ShowDemoWindow(nullptr);
ImGui::Render();
ImDrawData* draw_data = ImGui::GetDrawData();
RenderTextures(draw_data);
vtx_count = draw_data->TotalVtxCount;
}
ImVector<int> v;
v.push_back(1);
v.push_back(2);
const int macros_result = TestModuleWithMacros();
ImGui::DestroyContext(ctx);
std::println("imgui {}: vtx_count={} vector_size={} macros_result={}", ImGuiVersion, vtx_count, v.Size, macros_result);
return (vtx_count > 0 && v.Size == 2 && macros_result == 0) ? 0 : 1;
}

View File

@ -0,0 +1,46 @@
// dear imgui: compile test for the C++20 named module 'imgui' (see imgui.cppm), using the macros from imgui_macros.h.
// Modules don't export macros: include imgui_macros.h (with the same configuration as the module) before importing.
#include "imgui_macros.h"
import std;
import imgui;
// Macros and constexpr alternatives must agree
static_assert(IMGUI_VERSION_NUM == ImGuiVersionNum);
static_assert(IM_COL32(1, 2, 3, 4) == ImCol32(1, 2, 3, 4));
static_assert(IM_COL32_WHITE == ImCol32_White);
static_assert(IM_UNICODE_CODEPOINT_MAX == ImUnicodeCodepoint_Max);
struct TestObject
{
int Value;
TestObject(int value) : Value(value) {}
};
int TestModuleWithMacros()
{
if (!IMGUI_CHECKVERSION())
return 1;
// IM_NEW/IM_DELETE rely on the exported ImNewWrapper placement new and IM_DELETE template
TestObject* obj = IM_NEW(TestObject)(42);
IM_ASSERT(obj->Value == 42);
IM_DELETE(obj);
const char* names[] = { "a", "b", "c", "d" };
IM_UNUSED(names);
static_assert(IM_COUNTOF(names) == 4);
ImGui::NewFrame();
ImGui::Begin("Macros");
ImGui::GetWindowDrawList()->AddLine(ImVec2(0, 0), ImVec2(10, 10), IM_COL32_WHITE);
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
{
const float col[4] = { 1, 0, 0, 1 };
ImGui::SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, col, sizeof(col));
ImGui::EndDragDropSource();
}
ImGui::End();
ImGui::EndFrame();
return 0;
}