From 681841e11d396a1415bb6f5e720280c44eba2240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 22 Aug 2026 14:11:11 -0300 Subject: [PATCH] Create unit-test windows hidden from the start while preserving hardware OpenGL rendering. Force double buffering for hidden X11 windows so framebuffer capture reads the rendered back buffer reliably, and keep SDL2/SDL3 behavior consistent. Update unit-test documentation to use direct desktop execution by default, with Xvfb retained for headless environments. This change might break unit-tests in other platforms, next CI run will let us know. --- .agent/rules/unit-tests.md | 38 +++++++++++++-------- include/eepp/version.hpp | 6 ++-- include/eepp/window/window.hpp | 3 ++ premake4.lua | 1 + premake5.lua | 1 + src/eepp/window/backend/SDL2/windowsdl2.cpp | 14 +++++++- src/eepp/window/backend/SDL3/windowsdl3.cpp | 16 +++++++-- src/tests/unit_tests/compareimages.hpp | 2 +- src/tools/ecode/version.hpp | 2 +- 9 files changed, 61 insertions(+), 22 deletions(-) diff --git a/.agent/rules/unit-tests.md b/.agent/rules/unit-tests.md index 6c07be1ff..80279eda7 100644 --- a/.agent/rules/unit-tests.md +++ b/.agent/rules/unit-tests.md @@ -7,23 +7,33 @@ The test binary manages its own current working directory, so you can execute it * **Prefer the release test binary during normal development:** When AddressSanitizer or other debug-only diagnostics are not required, build and run `bin/unit_tests/eepp-unit_tests`. The optimized release suite is substantially faster and should be the default for iterative testing. Use `bin/unit_tests/eepp-unit_tests-debug` when investigating memory safety, assertions, or other behavior that specifically requires the debug configuration. -* **Default Execution for Agents on Linux & FreeBSD:** - Always run unit tests through the project wrapper unless the user explicitly asks for a different harness: - `projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests` -* **Why the wrapper is required:** - Tests open ~400 individual windows. The wrapper runs them in an isolated framebuffer, enables race-safe automatic display selection for concurrent agent test runs, sets the default screen to `1280x1024x24`, and injects `ASAN_OPTIONS=detect_leaks=0` automatically. -* **Do not skip the wrapper for filtered tests:** - A focused test still needs the same wrapper: - `projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"` -* **Fallback only when the wrapper itself fails:** - If `projects/scripts/xvfb-run-eepp` fails before launching the test binary, report that wrapper failure and then use this fallback to keep verification moving: - `xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests` - Do not use plain `xvfb-run` as the first attempt for GUI/unit tests. -* **Direct Execution (Only for non-window tests or explicit user requests):** +* **Default Execution on a Graphical Linux Desktop:** + Unit-test windows are created hidden, so run the release suite directly against the desktop: `bin/unit_tests/eepp-unit_tests` + This keeps the windows invisible and unfocused while preserving hardware OpenGL acceleration. + Confirm that the renderer log names the real GPU rather than llvmpipe when validating rendering + behavior or performance. +* **Filtered Tests on a Graphical Linux Desktop:** + Use the same direct hardware-backed command for focused runs: + `bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"` +* **Headless CI and Systems Without a Usable Desktop Display:** + Keep `projects/scripts/xvfb-run-eepp` as the fallback when no desktop display is available: + `projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests` + The wrapper provides a race-safe isolated display at `1280x1024x24` and injects + `ASAN_OPTIONS=detect_leaks=0`. Xvfb can use llvmpipe, so do not use wrapper timings to evaluate + hardware-rendered performance. +* **Wrapper Fallback:** + If `projects/scripts/xvfb-run-eepp` itself fails before launching the test binary, report that + failure and use: + `xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests` + Do not use plain `xvfb-run` as the first headless attempt. +* **Non-window Tests:** + Tests known not to create windows can run without selecting a video driver: + `bin/unit_tests/eepp-unit_tests --filter="NonWindowTest.*"` * **Filtering Tests:** Use the `--filter` parameter to run specific tests (supports glob patterns). - Keep the wrapper in front of the binary unless the test is known not to create windows. + Keep the same harness as the full suite: direct execution on a graphical Linux desktop, or the + wrapper in a genuinely headless environment. ## Writing New Tests Writing new tests is highly encouraged, but depends on the context of your changes: diff --git a/include/eepp/version.hpp b/include/eepp/version.hpp index f4d7bc501..b39e3574f 100644 --- a/include/eepp/version.hpp +++ b/include/eepp/version.hpp @@ -4,10 +4,10 @@ #include #include -#define EEPP_MAJOR_VERSION 3 -#define EEPP_MINOR_VERSION 1 +#define EEPP_MAJOR_VERSION 4 +#define EEPP_MINOR_VERSION 0 #define EEPP_PATCH_LEVEL 0 -#define EEPP_CODENAME "Khaya" +#define EEPP_CODENAME "Asādhāraṇa" /** The compiled version of the library */ #define EEPP_VERSION( x ) \ diff --git a/include/eepp/window/window.hpp b/include/eepp/window/window.hpp index cbb33143f..82322acce 100644 --- a/include/eepp/window/window.hpp +++ b/include/eepp/window/window.hpp @@ -25,8 +25,11 @@ enum WindowStyle { Resize = ( 1 << 2 ), Fullscreen = ( 1 << 3 ), UseDesktopResolution = ( 1 << 4 ), + Hidden = ( 1 << 5 ), #if EE_PLATFORM == EE_PLATFORM_IOS || EE_PLATFORM == EE_PLATFORM_ANDROID Default = Borderless +#elif defined( EE_UNIT_TESTS ) + Default = Titlebar | Resize | Hidden #else Default = Titlebar | Resize #endif diff --git a/premake4.lua b/premake4.lua index aec0f63a2..d2606701c 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1954,6 +1954,7 @@ solution "eepp" project "eepp-unit_tests" kind "ConsoleApp" targetdir("./bin/unit_tests") + defines { "EE_UNIT_TESTS" } links { "eterm-static", "languages-syntax-highlighting-static" } includedirs { "src/modules/eterm/include/", "src/thirdparty" } language "C++" diff --git a/premake5.lua b/premake5.lua index 1bdb37d85..ab79b203f 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1982,6 +1982,7 @@ workspace "eepp" project "eepp-unit_tests" kind "ConsoleApp" targetdir(_MAIN_SCRIPT_DIR .. "/bin/unit_tests") + defines { "EE_UNIT_TESTS" } links { "eterm-static", "languages-syntax-highlighting-static" } incdirs { "src/modules/eterm/include/", "src/thirdparty" } language "C++" diff --git a/src/eepp/window/backend/SDL2/windowsdl2.cpp b/src/eepp/window/backend/SDL2/windowsdl2.cpp index 57b97806d..9f55840f1 100644 --- a/src/eepp/window/backend/SDL2/windowsdl2.cpp +++ b/src/eepp/window/backend/SDL2/windowsdl2.cpp @@ -84,6 +84,13 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { mWindow.WindowConfig = Settings; mWindow.ContextConfig = Context; +#if defined( EE_X11_PLATFORM ) + // Unmapped GLX windows do not provide reliable front-buffer storage. Keep hidden windows + // double-buffered so rendering and readback use the drawable's back buffer. + if ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) + mWindow.ContextConfig.DoubleBuffering = true; +#endif + if ( !SDL_WasInit( SDL_INIT_VIDEO ) && SDL_Init( SDL_INIT_VIDEO ) != 0 ) { Log::error( "Unable to initialize SDL: %s", SDL_GetError() ); @@ -106,7 +113,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { mWindow.WindowConfig.Height = mWindow.DesktopResolution.getHeight(); } - mWindow.Flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | + mWindow.Flags = SDL_WINDOW_OPENGL | + ( ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) ? SDL_WINDOW_HIDDEN + : SDL_WINDOW_SHOWN ) | ( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_ALLOW_HIGHDPI : 0 ) ); if ( mWindow.WindowConfig.Style & WindowStyle::Resize ) { @@ -284,6 +293,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { Backend::BackendHelper::setUserTheme( (HWND)getWindowHandler() ); #endif + if ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) + SDL_HideWindow( mSDLWindow ); + logSuccessfulInit( getVersion() ); return true; diff --git a/src/eepp/window/backend/SDL3/windowsdl3.cpp b/src/eepp/window/backend/SDL3/windowsdl3.cpp index aebc32e5e..5e6200954 100644 --- a/src/eepp/window/backend/SDL3/windowsdl3.cpp +++ b/src/eepp/window/backend/SDL3/windowsdl3.cpp @@ -69,6 +69,13 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { mWindow.WindowConfig = Settings; mWindow.ContextConfig = Context; +#if defined( EE_X11_PLATFORM ) + // Unmapped GLX windows do not provide reliable front-buffer storage. Keep hidden windows + // double-buffered so rendering and readback use the drawable's back buffer. + if ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) + mWindow.ContextConfig.DoubleBuffering = true; +#endif + if ( !SDL_WasInit( SDL_INIT_VIDEO ) && !SDL_Init( SDL_INIT_VIDEO ) ) { Log::error( "Unable to initialize SDL: %s", SDL_GetError() ); @@ -96,8 +103,10 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { mWindow.WindowConfig.Height = mWindow.DesktopResolution.getHeight(); } - mWindow.Flags = SDL_WINDOW_OPENGL | - ( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_HIGH_PIXEL_DENSITY : 0 ) ); + mWindow.Flags = + SDL_WINDOW_OPENGL | + ( ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) ? SDL_WINDOW_HIDDEN : 0 ) | + ( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_HIGH_PIXEL_DENSITY : 0 ) ); if ( mWindow.WindowConfig.Style & WindowStyle::Resize ) { mWindow.Flags |= SDL_WINDOW_RESIZABLE; @@ -259,6 +268,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { Backend::BackendHelper::setUserTheme( (HWND)getWindowHandler() ); #endif + if ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) + SDL_HideWindow( mSDLWindow ); + logSuccessfulInit( getVersion() ); return true; diff --git a/src/tests/unit_tests/compareimages.hpp b/src/tests/unit_tests/compareimages.hpp index aaf1c91ac..cf4f7e407 100644 --- a/src/tests/unit_tests/compareimages.hpp +++ b/src/tests/unit_tests/compareimages.hpp @@ -14,7 +14,7 @@ using namespace EE::System; using namespace EE::Graphics; using namespace EE::Window; -static constexpr Uint32 VisualTestWindowStyle = WindowStyle::Borderless; +static constexpr Uint32 VisualTestWindowStyle = WindowStyle::Borderless | WindowStyle::Hidden; static void compareImages( utest_state_s& utest_state, int* utest_result, EE::Window::Window* win, const std::string& imageName, diff --git a/src/tools/ecode/version.hpp b/src/tools/ecode/version.hpp index 8209ef929..ef23a8f07 100644 --- a/src/tools/ecode/version.hpp +++ b/src/tools/ecode/version.hpp @@ -8,7 +8,7 @@ using namespace EE; #define ECODE_MAJOR_VERSION 0 #define ECODE_MINOR_VERSION 8 -#define ECODE_PATCH_LEVEL 1 +#define ECODE_PATCH_LEVEL 2 /* ECODE_COMMIT_NUMBER 9999 is used for official releases, nightly builds (pre-releases) will * contain the number of commits after the last official release */