From a92dba93d902696099a6f6d2d2724b27931a34c0 Mon Sep 17 00:00:00 2001 From: Sharadh Rajaraman Date: Mon, 28 Sep 2026 16:30:53 +0100 Subject: [PATCH] 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/. --- .gitignore | 2 + misc/cxx_module/CMakeLists.txt | 231 ++++++++++++++++++++++ misc/cxx_module/CMakePresets.json | 38 ++++ misc/cxx_module/compare_output.cmake | 13 ++ misc/cxx_module/test_backend_allegro5.cpp | 23 +++ misc/cxx_module/test_backend_android.cpp | 49 +++++ misc/cxx_module/test_backend_gpu.cpp | 63 ++++++ misc/cxx_module/test_backend_null.cpp | 38 ++++ misc/cxx_module/test_backend_sdl2.cpp | 74 +++++++ misc/cxx_module/test_backend_sdl3.cpp | 92 +++++++++ misc/cxx_module/test_backend_wgpu.cpp | 21 ++ misc/cxx_module/test_backend_windows.cpp | 93 +++++++++ misc/cxx_module/test_diff.h | 86 ++++++++ misc/cxx_module/test_header_internal.cpp | 31 +++ misc/cxx_module/test_module.cpp | 103 ++++++++++ misc/cxx_module/test_module_macros.cpp | 46 +++++ 16 files changed, 1003 insertions(+) create mode 100644 misc/cxx_module/CMakeLists.txt create mode 100644 misc/cxx_module/CMakePresets.json create mode 100644 misc/cxx_module/compare_output.cmake create mode 100644 misc/cxx_module/test_backend_allegro5.cpp create mode 100644 misc/cxx_module/test_backend_android.cpp create mode 100644 misc/cxx_module/test_backend_gpu.cpp create mode 100644 misc/cxx_module/test_backend_null.cpp create mode 100644 misc/cxx_module/test_backend_sdl2.cpp create mode 100644 misc/cxx_module/test_backend_sdl3.cpp create mode 100644 misc/cxx_module/test_backend_wgpu.cpp create mode 100644 misc/cxx_module/test_backend_windows.cpp create mode 100644 misc/cxx_module/test_diff.h create mode 100644 misc/cxx_module/test_header_internal.cpp create mode 100644 misc/cxx_module/test_module.cpp create mode 100644 misc/cxx_module/test_module_macros.cpp diff --git a/.gitignore b/.gitignore index 009e61ede..23c174e03 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/misc/cxx_module/CMakeLists.txt b/misc/cxx_module/CMakeLists.txt new file mode 100644 index 000000000..4e7993287 --- /dev/null +++ b/misc/cxx_module/CMakeLists.txt @@ -0,0 +1,231 @@ +# dear imgui: build and test the C++20 named modules 'imgui' (imgui.cppm) and 'imgui.impl.' (backends/imgui_impl_.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, '_' otherwise (see imgui_add_config()). + +# imgui_add_backend( [OPTIONS ]) +# - imgui_impl_: module 'imgui.impl.' +# - imgui_header_impl_: 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( RUN|DIFF|LINK|COMPILE LIBS ) +# - RUN: compile, link, and run (natively or with CMAKE_CROSSCOMPILING_EMULATOR); LINK: compile and link; COMPILE: compile only. +# - DIFF: like RUN, and also build 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=$" "-DACTUAL=$" -P "${CMAKE_CURRENT_SOURCE_DIR}/compare_output.cmake") + endif() + endif() +endfunction() + +# imgui_add_config( [LIBRARIES_ONLY] ) +# 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.' (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 (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 diff --git a/misc/cxx_module/CMakePresets.json b/misc/cxx_module/CMakePresets.json new file mode 100644 index 000000000..556fe8be1 --- /dev/null +++ b/misc/cxx_module/CMakePresets.json @@ -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" } + ] + } + ] +} diff --git a/misc/cxx_module/compare_output.cmake b/misc/cxx_module/compare_output.cmake new file mode 100644 index 000000000..e0ad2b9aa --- /dev/null +++ b/misc/cxx_module/compare_output.cmake @@ -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=] -DEXPECTED=
-DACTUAL= -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() diff --git a/misc/cxx_module/test_backend_allegro5.cpp b/misc/cxx_module/test_backend_allegro5.cpp new file mode 100644 index 000000000..d18efd825 --- /dev/null +++ b/misc/cxx_module/test_backend_allegro5.cpp @@ -0,0 +1,23 @@ +// dear imgui: compile/link test for the C++20 backend module 'imgui.impl.allegro5' (needs a display to run). + +#include // application-side platform headers +import std; +import imgui; +import imgui.impl.allegro5; + +static_assert(std::is_same_v); + +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; +} diff --git a/misc/cxx_module/test_backend_android.cpp b/misc/cxx_module/test_backend_android.cpp new file mode 100644 index 000000000..e55acac82 --- /dev/null +++ b/misc/cxx_module/test_backend_android.cpp @@ -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 // application-side platform headers +#include +#include +import std; +import imgui; +import imgui.impl.android; +import imgui.impl.opengl3; +import imgui.impl.vulkan; + +static_assert(std::is_same_v); +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; +} diff --git a/misc/cxx_module/test_backend_gpu.cpp b/misc/cxx_module/test_backend_gpu.cpp new file mode 100644 index 000000000..336d262f2 --- /dev/null +++ b/misc/cxx_module/test_backend_gpu.cpp @@ -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 // application-side platform headers +#include +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); + +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; +} diff --git a/misc/cxx_module/test_backend_null.cpp b/misc/cxx_module/test_backend_null.cpp new file mode 100644 index 000000000..9f99e3965 --- /dev/null +++ b/misc/cxx_module/test_backend_null.cpp @@ -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 +#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; +} diff --git a/misc/cxx_module/test_backend_sdl2.cpp b/misc/cxx_module/test_backend_sdl2.cpp new file mode 100644 index 000000000..ca342c9f5 --- /dev/null +++ b/misc/cxx_module/test_backend_sdl2.cpp @@ -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 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 +#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 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; +} diff --git a/misc/cxx_module/test_backend_sdl3.cpp b/misc/cxx_module/test_backend_sdl3.cpp new file mode 100644 index 000000000..0e0293fed --- /dev/null +++ b/misc/cxx_module/test_backend_sdl3.cpp @@ -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 // 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 +#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; +} diff --git a/misc/cxx_module/test_backend_wgpu.cpp b/misc/cxx_module/test_backend_wgpu.cpp new file mode 100644 index 000000000..d387d7136 --- /dev/null +++ b/misc/cxx_module/test_backend_wgpu.cpp @@ -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 // 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)); +} diff --git a/misc/cxx_module/test_backend_windows.cpp b/misc/cxx_module/test_backend_windows.cpp new file mode 100644 index 000000000..0838d2a8b --- /dev/null +++ b/misc/cxx_module/test_backend_windows.cpp @@ -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 // application-side platform headers +#include +#include +#include +#include +#include +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); + +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; +} diff --git a/misc/cxx_module/test_diff.h b/misc/cxx_module/test_diff.h new file mode 100644 index 000000000..69c44990a --- /dev/null +++ b/misc/cxx_module/test_diff.h @@ -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 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()); + } + } +} diff --git a/misc/cxx_module/test_header_internal.cpp b/misc/cxx_module/test_header_internal.cpp new file mode 100644 index 000000000..2f7423c6d --- /dev/null +++ b/misc/cxx_module/test_header_internal.cpp @@ -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 + +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; +} diff --git a/misc/cxx_module/test_module.cpp b/misc/cxx_module/test_module.cpp new file mode 100644 index 000000000..7797f7a8b --- /dev/null +++ b/misc/cxx_module/test_module.cpp @@ -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); +static_assert(sizeof(ImDrawIdx) == 2 || sizeof(ImDrawIdx) == 4); +static_assert(std::is_same_v); + +// Internal types stay incomplete (imgui_internal.h is not exported) +template concept IsComplete = requires { sizeof(T); }; +static_assert(!IsComplete); + +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 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 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; +} diff --git a/misc/cxx_module/test_module_macros.cpp b/misc/cxx_module/test_module_macros.cpp new file mode 100644 index 000000000..bd56ec7db --- /dev/null +++ b/misc/cxx_module/test_module_macros.cpp @@ -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; +}