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; +}