diff --git a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx index 9353e3664d05a..3aab0270671f7 100644 --- a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx +++ b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx @@ -27,10 +27,7 @@ using namespace o2::gpu; namespace { GPUSettingsStandalone configStandalone; -std::vector> qprint_global; -#define QCONFIG_PRINT -#include "utils/qconfig_internal.h" -#undef QCONFIG_PRINT +#include "utils/qconfig_print.inc" } // namespace void GPUConfigDump::dumpConfig(const GPUSettingsRec* rec, const GPUSettingsProcessing* proc, const GPUSettingsQA* qa, const GPUSettingsDisplay* display, const GPUSettingsDeviceBackend* device, const GPURecoStepConfiguration* workflow) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index fa560ec535313..aca6b98f07dce 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -456,8 +456,8 @@ AddOption(propagateTracks, int32_t, 0, "", 0, "Propagate final tracks further (i AddOption(showCollision, int32_t, -1, "", 0, "Show only individual collision") AddOption(colorCollisions, int32_t, 0, "", 0, "Distinguish collisions in timeframe by color") AddOption(colorClusters, int32_t, 1, "", 0, "Color clusters belonging to track objects") -AddOption(pointSize, float, 2.0f, "", 0, "Set point size") -AddOption(lineWidth, float, 1.4f, "", 0, "Set line width") +AddOption(pointSize, float, 2.0f, "", 0, "Set point size", min(0.8f), max(20.f)) +AddOption(lineWidth, float, 1.4f, "", 0, "Set line width", min(0.4f), max(20.f)) AddOption(drawTPC, bool, true, "", 0, "Enable drawing TPC data") AddOption(drawTRD, bool, true, "", 0, "Enabale drawing TRD data") AddOption(drawTOF, bool, true, "", 0, "Enabale drawing TOF data") @@ -502,12 +502,11 @@ AddOption(camYUp, bool, false, "", 0, "Orient the camera such that the y-axis is AddOption(cameraMode, int32_t, 0, "", 0, "Camera mode") AddOption(fullScreen, bool, false, "", 0, "Full Screen") AddOption(maximized, bool, true, "", 0, "Maximized window") -AddOption(rendererCompatSmoothing, bool, false, "", 0, "Use compat smoothing (if available) instead of shader for smooth lines / points") -AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing") -AddOption(drawQualityDownsampleFSAA, int32_t, 0, "", 0, "Downsampling Anti Aliasing") +AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing", min(0), max(8)) +AddOption(drawQualityDownsampleFSAA, int32_t, 0, "", 0, "Downsampling Anti Aliasing", min(0), max(4)) AddOption(drawQualityVSync, bool, true, "", 0, "Enable Vertical Sync") AddOption(fpsLimit, int32_t, 1, "", 0, "FPS limit: 0 = none (maximum rate), 1 = display refresh rate (no limit without VSync), 2 = fpsRate") -AddOption(fpsRate, int32_t, 100, "", 0, "Target frame rate (frames per second) for fpsLimit 2, or if the backend cannot limit to the refresh rate (0 = no limit)") +AddOption(fpsRate, int32_t, 100, "", 0, "Target frame rate (frames per second) for fpsLimit 2, or if the backend cannot limit to the refresh rate", min(1), max(9999)) AddOption(useGLIndirectDraw, bool, true, "", 0, "Use OpenGL indirect draws to reduce number of draw calls") AddOption(screenshotScaleFactor, int32_t, 1, "", 0, "Resolution scale factor when taking screenshots") AddOption(fov, int32_t, 45, "", 0, "Display FOV") @@ -534,6 +533,7 @@ AddOption(displayFrontend, std::string, "auto", "displayFrontend", 0, "Frontend AddOptionVec(filterMacros, std::string, "", 0, "ROOT macros used as track filter") AddOption(guiReplyTimeout, uint32_t, 100, "", 0, "Time in ms the Qt GUI waits at most for the display to process its update") AddOption(guiMaxUpdatesAhead, uint32_t, 3, "", 0, "Number of updates the Qt GUI may send ahead of the last one the display has processed, before it waits for the display") +AddOption(listFrontendOptionHandling, bool, false, "", 0, "List all assigned keys in frontend, Configurable settings in Qt GUI, and missing configuration options") AddSubConfig(GPUSettingsDisplayLight, light) AddSubConfig(GPUSettingsDisplayHeavy, heavy) AddSubConfig(GPUSettingsDisplayRenderer, renderer) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 9edefc19a072c..e5674b91d52b2 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -69,6 +69,22 @@ if(NOT GPUCA_EVENT_DISPLAY_OPENGL) set(X11_FOUND 0) endif() +if(GLUT_FOUND) + include(CheckSymbolExists) + set(CMAKE_REQUIRED_INCLUDES ${GLUT_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${GLUT_glut_LIBRARY}) + check_symbol_exists(glutDisplayFuncUcall "GL/freeglut.h;GL/freeglut_ucall.h" GPUCA_GLUT_HAS_UCALL) # Callbacks with user data, freeglut >= 3.2 (modifier key codes exist since 2.8) + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) + if(NOT GPUCA_GLUT_HAS_UCALL) + if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") + message(FATAL_ERROR "GLUT event display frontend needs freeglut >= 3.2") + endif() + message(STATUS "GLUT event display frontend disabled: needs freeglut >= 3.2") + set(GLUT_FOUND 0) + endif() +endif() + if(Vulkan_FOUND) string(FIND "${Vulkan_VERSION}" "." vulkan_dot_index) if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.4.328" OR vulkan_dot_index EQUAL -1 OR NOT GPUCA_GLSLC) @@ -113,9 +129,10 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx helpers/GPUDisplayQuaternion.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx - frontend/GPUDisplayKeys.cxx) + frontend/GPUDisplayHelp.cxx + frontend/GPUDisplayFrontendConfig.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) @@ -150,10 +167,10 @@ if(GPUCA_EVENT_DISPLAY_WAYLAND) list(APPEND WAYLAND_PROTOCOL_SOURCES ${source}) endforeach() - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) - set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) + set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() if(X11_FOUND) @@ -234,7 +251,7 @@ if (OPENGL_GLU_FOUND) endif() if(GPUCA_CONFIG_GL3W) - target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) + target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W) endif() if(ALIGPU_BUILD_TYPE STREQUAL "O2") diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 2db90ea9a5fed..a3c117b23d805 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -75,7 +75,6 @@ GPUDisplay::GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GP mBackend->mDisplay = this; frontend->mDisplay = this; frontend->mBackend = mBackend.get(); - mCfgR.rendererCompatSmoothing &= !mBackend->CoreProfile(); } void GPUDisplay::calcXYZ(const float* matrix) @@ -148,7 +147,7 @@ void GPUDisplay::ResizeScene(int32_t width, int32_t height, bool init) mBackend->resizeScene(width, height); if (init) { - mResetScene = true; + mCfgC.mResetScene.store(true); *mViewMatrix = MY_HMM_IDENTITY; *mModelMatrix = MY_HMM_IDENTITY; } @@ -177,6 +176,7 @@ int32_t GPUDisplay::InitDisplay(bool initFailure) retVal = 1; } mInitResult = retVal == 0 ? 1 : -1; + mOldCfg = {mCfgH, mCfgL, mCfgR}; return (retVal); } @@ -188,7 +188,6 @@ int32_t GPUDisplay::InitDisplay_internal() return 1; } mYFactor = mBackend->getYFactor(); - mDrawTextInCompatMode = !mBackend->mFreetypeInitialized && mFrontend->mCanDrawText == 1; int32_t height = 0, width = 0; mFrontend->getSize(width, height); if (height == 0 || width == 0) { @@ -204,7 +203,7 @@ void GPUDisplay::ExitDisplay() mBackend->ExitBackend(); } -int32_t GPUDisplay::DrawGLScene() +int32_t GPUDisplay::DrawGLScene(bool suspended) { // Make sure event gets not overwritten during display mSemLockDisplay.Lock(); @@ -220,7 +219,7 @@ int32_t GPUDisplay::DrawGLScene() mNCollissions = std::max(1u, mIOPtrs->nMCInfosTPCCol); } try { - DrawGLScene_internal(); + DrawGLScene_internal(suspended); } catch (const std::runtime_error& e) { GPUError("Runtime error %s during display", e.what()); retVal = 1; @@ -238,15 +237,15 @@ int32_t GPUDisplay::DrawGLScene() return retVal; } -void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix) +void GPUDisplay::DrawGLScene_cameraAndAnimation(bool suspended, float animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix) { HighResTimer timer(mUpdateVertexLists && mChain->GetProcessingSettings().debugLevel >= 2); int32_t mMouseWheelTmp = mFrontend->mMouseWheel; mFrontend->mMouseWheel = 0; - bool lookOrigin = mCfgR.camLookOrigin ^ mFrontend->mKeys[mFrontend->KEY_ALT]; - bool yUp = mCfgR.camYUp ^ mFrontend->mKeys[mFrontend->KEY_CTRL] ^ lookOrigin; - bool rotateModel = mFrontend->mKeys[mFrontend->KEY_RCTRL] || mFrontend->mKeys[mFrontend->KEY_RALT]; - bool rotateModelTPC = mFrontend->mKeys[mFrontend->KEY_RALT]; + bool lookOrigin = mCfgR.camLookOrigin ^ mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_FOCUS_ORIGIN]]; + bool yUp = mCfgR.camYUp ^ mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_FOCUS_ORIENT_UP]] ^ lookOrigin; + bool rotateModelTPC = mCfgC.mRotateAroundZ || mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_AROUND_Z]]; + bool rotateModel = rotateModelTPC || mCfgC.mRotateModel || mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_MODEL]]; // Calculate rotation / translation scaling factors float scalefactor = mFrontend->mKeys[mFrontend->KEY_SHIFT] ? 0.2f : 1.0f; @@ -266,9 +265,9 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla // Perform new rotation / translation / animation - if (animateCamera(animateTime, mixSlaveImage, nextViewMatrix)) { + if (animateCamera(suspended, animateTime, mixSlaveImage, nextViewMatrix)) { // Do nothing else - } else if (mResetScene) { + } else if (mCfgC.mResetScene.load()) { const float initialZpos = mCfgH.projectXY ? 16 : (mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR + 8) : 8); nextViewMatrix = nextViewMatrix * HMM_Translate({{0, 0, -initialZpos}}); *mViewMatrix = MY_HMM_IDENTITY; @@ -284,14 +283,14 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mCfgR.fov = 45.f; mUpdateDrawCommands = true; - mResetScene = false; + mCfgC.mResetScene.store(false); } else { - float moveZ = scalefactor * ((float)mMouseWheelTmp / 150 + (float)(mFrontend->mKeys[(uint8_t)'W'] - mFrontend->mKeys[(uint8_t)'S']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); - float moveY = scalefactor * ((float)(mFrontend->mKeys[mFrontend->KEY_PAGEDOWN] - mFrontend->mKeys[mFrontend->KEY_PAGEUP]) * 0.2f * mFPSScale); - float moveX = scalefactor * ((float)(mFrontend->mKeys[(uint8_t)'A'] - mFrontend->mKeys[(uint8_t)'D']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); - float rotRoll = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[(uint8_t)'E'] - mFrontend->mKeys[(uint8_t)'F']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); - float rotYaw = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->KEY_RIGHT] - mFrontend->mKeys[mFrontend->KEY_LEFT]); - float rotPitch = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->KEY_DOWN] - mFrontend->mKeys[mFrontend->KEY_UP]); + float moveZ = scalefactor * ((float)mMouseWheelTmp / 150 + (float)(mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_FORWARD]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_BACKWARD]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float moveY = scalefactor * ((float)(mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_DOWN]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_UP]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float moveX = scalefactor * ((float)(mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_LEFT]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_RIGHT]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float rotRoll = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_LEFT]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_RIGHT]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); + float rotYaw = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_YAW_RIGHT]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_YAW_LEFT]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); + float rotPitch = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_PITCH_DOWN]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_PITCH_UP]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); float mouseScale = 1920.f / std::max(1920.f, mBackend->mScreenWidth); if (mFrontend->mMouseDnR && mFrontend->mMouseDn) { @@ -305,7 +304,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla rotPitch += rotatescalefactor * mouseScale * ((float)mFrontend->mMouseMvY - (float)mFrontend->mMouseDnY); } - if (mFrontend->mKeys[(uint8_t)'<'] && !mFrontend->mKeysShift[(uint8_t)'<']) { + if (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_CHANGE_ANIMATION_INTERVAL]]) { mAnimationDelay += moveX; if (mAnimationDelay < 0.05f) { mAnimationDelay = 0.05f; @@ -395,7 +394,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla // Graphichs Options float minSize = 0.4f / (mCfgR.drawQualityDownsampleFSAA > 1 ? mCfgR.drawQualityDownsampleFSAA : 1); - int32_t deltaLine = mFrontend->mKeys[(uint8_t)'+'] * mFrontend->mKeysShift[(uint8_t)'+'] - mFrontend->mKeys[(uint8_t)'-'] * mFrontend->mKeysShift[(uint8_t)'-']; + int32_t deltaLine = (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_INCREASE_WIDTH]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_DECREASE_WIDTH]]) * mFrontend->mKeys[mFrontend->KEY_SHIFT]; mCfgL.lineWidth += (float)deltaLine * mFPSScale * 0.02f * mCfgL.lineWidth; if (mCfgL.lineWidth < minSize) { mCfgL.lineWidth = minSize; @@ -405,7 +404,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mUpdateDrawCommands = true; } minSize *= 2; - int32_t deltaPoint = mFrontend->mKeys[(uint8_t)'+'] * (!mFrontend->mKeysShift[(uint8_t)'+']) - mFrontend->mKeys[(uint8_t)'-'] * (!mFrontend->mKeysShift[(uint8_t)'-']); + int32_t deltaPoint = (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_INCREASE_WIDTH]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_DECREASE_WIDTH]]) * !mFrontend->mKeys[mFrontend->KEY_SHIFT]; mCfgL.pointSize += (float)deltaPoint * mFPSScale * 0.02f * mCfgL.pointSize; if (mCfgL.pointSize < minSize) { mCfgL.pointSize = minSize; @@ -627,20 +626,22 @@ void GPUDisplay::DrawGLScene_drawCommands() } } -void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) // negative time = no mixing +void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool renderToMixBuffer) // negative time = no mixing { - if (mRequestScreenshot || mAnimateScreenshot) { + if (mCfgC.mRequestScreenshot || mCfgC.mAnimateScreenshot) { const uint32_t factor = std::max(1, mCfgR.drawQualityDownsampleFSAA) * std::max(1, mCfgR.screenshotScaleFactor); if (std::max(mBackend->mScreenWidth, mBackend->mScreenHeight) * factor > mBackend->mMaxRenderSize) { GPUError("Screenshot not taken: requested render size %u x %u (window x FSAA x screenshot scale) exceeds the maximum size %u x %u", mBackend->mScreenWidth * factor, mBackend->mScreenHeight * factor, mBackend->mMaxRenderSize, mBackend->mMaxRenderSize); - mRequestScreenshot = false; - mAnimateScreenshot = 0; + mCfgC.mRequestScreenshot = false; + mCfgC.mAnimateScreenshot = 0; } } - bool doScreenshot = (mRequestScreenshot || mAnimateScreenshot) && animateTime < 0; + const bool animateScreenshot = mCfgC.mAnimateScreenshot && mAnimate; // Copy, the animation resets mAnimateScreenshot in its last frame + const bool screenshotFrame = mCfgC.mRequestScreenshot || animateScreenshot; // Also for the mix pass (animateTime >= 0), which must use the same render size + const bool doScreenshot = screenshotFrame && animateTime < 0; updateOptions(); - if (animateTime < 0 && (mUpdateEventData || mResetScene || mUpdateVertexLists) && mIOPtrs) { + if (animateTime < 0 && (mUpdateEventData || mCfgC.mResetScene.load() || mUpdateVertexLists) && mIOPtrs) { disableUnsupportedOptions(); } if (mUpdateEventData || mUpdateVertexLists) { @@ -648,7 +649,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) } HighResTimer timerDraw(mUpdateVertexLists); - if (animateTime < 0 && (mUpdateEventData || mResetScene) && mIOPtrs) { + if (animateTime < 0 && (mUpdateEventData || mCfgC.mResetScene.load()) && mIOPtrs) { timerDraw.ResetStart(); DrawGLScene_updateEventData(); mTimerFPS.ResetStart(); @@ -660,7 +661,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) hmm_mat4 nextViewMatrix = MY_HMM_IDENTITY; float mixSlaveImage = 0.f; - DrawGLScene_cameraAndAnimation(animateTime, mixSlaveImage, nextViewMatrix); + DrawGLScene_cameraAndAnimation(suspended, animateTime, mixSlaveImage, nextViewMatrix); // Prepare Event if (mUpdateVertexLists && mIOPtrs) { @@ -670,11 +671,16 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) } } + if (suspended) { + return; + } + // Draw Event nextViewMatrix = nextViewMatrix * *mModelMatrix; const float zFar = ((mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR) : 8.f) + 50.f) * 2.f; - const hmm_mat4 proj = HMM_Perspective(mCfgR.fov, (float)mBackend->mRenderWidth / (float)mBackend->mRenderHeight, 0.1f, zFar); - if (!mBackend->prepareDraw(proj, nextViewMatrix, doScreenshot || mRequestScreenshot, renderToMixBuffer, mixSlaveImage)) { + const float aspect = mBackend->mRenderHeight ? (float)mBackend->mRenderWidth / (float)mBackend->mRenderHeight : 1.f; // No render size with the None backend + const hmm_mat4 proj = HMM_Perspective(mCfgR.fov, aspect, 0.1f, zFar); + if (!mBackend->prepareDraw(proj, nextViewMatrix, screenshotFrame, renderToMixBuffer, mixSlaveImage)) { return; // No image available (e.g. hidden window), retry with the next frame } mBackend->pointSizeFactor(1); @@ -705,7 +711,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) fps, mCfgL.drawSector, mCfgL.drawClusters, mCfgL.drawInitLinks, mCfgL.drawLinks, mCfgL.drawSeeds, mCfgL.drawTracklets, mCfgL.drawTracks, mCfgL.drawExtrapolatedTracks, mCfgL.drawFinal, mFramesDone, mNDrawCalls, mXYZ[0], mXYZ[1], mXYZ[2], mRPhiTheta[0], mRPhiTheta[1] * 180 / CAMath::Pi(), mRPhiTheta[2] * 180 / CAMath::Pi(), mAngle[1] * 180 / CAMath::Pi(), mAngle[0] * 180 / CAMath::Pi(), mAngle[2] * 180 / CAMath::Pi()); if (fpstime > 1.) { - if (mPrintInfoText & 2) { + if (mCfgC.mPrintInfoText & 2) { GPUInfo("%s", info); } if (mFPSScaleadjust++) { @@ -715,20 +721,20 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) mFramesDoneFPS = 0; } - if (mPrintInfoText & 1) { + if (mCfgC.mPrintInfoText & 1) { showInfo(info); } } - mBackend->finishFrame(mRequestScreenshot, renderToMixBuffer, mixSlaveImage); + mBackend->finishFrame(doScreenshot, renderToMixBuffer, mixSlaveImage); if (doScreenshot) { - mRequestScreenshot = false; + mCfgC.mRequestScreenshot = false; std::vector pixels = mBackend->getPixels(); char tmpFileName[48]; - if (mAnimateScreenshot) { + if (animateScreenshot) { snprintf(tmpFileName, 48, "mAnimation%d_%05d.bmp", mAnimationExport, mAnimationFrame); } - DoScreenshot(mAnimateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); + DoScreenshot(animateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); } } @@ -738,9 +744,8 @@ void GPUDisplay::ShowNextEvent(const GPUTrackingInOutPointers* ptrs) mIOPtrs = ptrs; } if (mMaxClusterZ <= 0) { - mResetScene = true; + mCfgC.mResetScene.store(true); } - mFrontend->mNeedUpdate = 1; mUpdateEventData = true; mLoadAndShowEvent = true; mSemLockDisplay.Unlock(); diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 67ab1a10334c9..8d92c3d903e8b 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -21,7 +21,9 @@ #include "GPUSettings.h" #include "DataFormatsTPC/Constants.h" +#include #include +#include #include #include @@ -58,12 +60,12 @@ class GPUDisplay : public GPUDisplayInterface void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; } void UpdateParam(const GPUParam* param) override { mParam = param; } - void HandleKey(uint8_t key, bool doNotHandleConfigChanges = false); // With doNotHandleConfigChanges, the caller must call checkAndApplyConfigChanges - int32_t DrawGLScene(); - void HandleExternalGUIKey(int32_t key); // Does not handle config changes, see HandleKey + void HandleKey(uint8_t key); + int32_t DrawGLScene(bool suspended = false); + void HandleExternalGUIKey(int32_t key); int32_t InitDisplay(bool initFailure = false); void ResizeScene(int32_t width, int32_t height, bool init = false); - void checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCfgH, const GPUSettingsDisplayLight& oldCfgL, const GPUSettingsDisplayRenderer& oldCfgR); // Set update flags / apply the settings that changed compared to the old copies + void checkAndApplyConfigChanges(); // Set update flags / apply the settings that changed compared to mOldCfg const GPUSettingsDisplayRenderer& cfgR() const { return mCfgR; } const GPUSettingsDisplayLight& cfgL() const { return mCfgL; } @@ -83,10 +85,29 @@ class GPUDisplay : public GPUDisplayInterface const GPUParam* param() { return mParam; } const GPUSettingsProcessing& GetProcessingSettings() const { return mProcessingSettings; } GPUDisplayFrontend* frontend() { return mFrontend; } - bool drawTextInCompatMode() const { return mDrawTextInCompatMode; } int32_t& drawTextFontSize() { return mDrawTextFontSize; } + struct control { + std::atomic mDisplayControl = 0; // Control for next event (=1) or quit (=2) + std::atomic mResetScene = false; + int32_t mPrintInfoText = 1; + bool mPrintInfoTextAlways = false; + bool mRequestScreenshot = false; + int32_t mAnimateScreenshot = 0; + bool mAnimationChangeConfig = true; + bool mGuiPrintInfo = false; // Show the info of options changed with Qt GUI elements (not for key presses in the GUI) + bool mRotateModel = false; // Rotate the model instead of the camera, like holding RCTRL (set in the Qt GUI) + bool mRotateAroundZ = false; // Rotate the TPC around the beamline, like holding RALT (set in the Qt GUI) + float mGuiScale = 1.f; // Scale factor of text and elements of the Qt GUI (on top of the system scaling), Ctrl + mouse wheel / Ctrl + 0 in the GUI + bool dummy = false; // dummy value to reference for keys that do not change variables + } mCfgC; + private: + void buildConfig(); + void buildConfigKeys(); + void buildConfigInfo(); + void buildConfigQtGUI(); + static constexpr int32_t NSECTORS = o2::tpc::constants::MAXSECTOR; static constexpr float GL_SCALE_FACTOR = (1.f / 100.f); @@ -154,9 +175,9 @@ class GPUDisplay : public GPUDisplayInterface static const GPUSettingsDisplay& GetConfig(GPUChainTracking* chain); static const GPUSettingsProcessing& GetProcessingConfig(GPUChainTracking* chain); - void DrawGLScene_internal(float animateTime = -1.f, bool renderToMixBuffer = false); + void DrawGLScene_internal(bool suspended, float animateTime = -1.f, bool renderToMixBuffer = false); // suspended: return before drawing void DrawGLScene_updateEventData(); - void DrawGLScene_cameraAndAnimation(float animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); + void DrawGLScene_cameraAndAnimation(bool suspended, float animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); size_t DrawGLScene_updateVertexList(); void DrawGLScene_drawCommands(); int32_t InitDisplay_internal(); @@ -179,7 +200,7 @@ class GPUDisplay : public GPUDisplayInterface void resetAnimation(); void removeAnimationPoint(); void startAnimation(); - int32_t animateCamera(float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); + int32_t animateCamera(bool suspended, float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); void showInfo(const char* info); void ActivateColor(); void SetColorTRD(); @@ -217,7 +238,7 @@ class GPUDisplay : public GPUDisplayInterface void PrintHelp(); void createQuaternionFromMatrix(float* v, const float* mat); void drawVertices(const vboList& v, const GPUDisplayBackend::drawType t); - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true); + void GLPrintText(const char* s, float x, float y, float r, float g, float b, float a, uint32_t nCharacters = 0, bool fromBotton = true); // nCharacters > 0: print at most that many characters GPUDisplayFrontend* mFrontend = nullptr; std::unique_ptr mBackend; @@ -228,6 +249,7 @@ class GPUDisplay : public GPUDisplayInterface GPUSettingsDisplayLight mCfgL; GPUSettingsDisplayHeavy mCfgH; GPUSettingsDisplayRenderer mCfgR; + std::tuple mOldCfg; // Settings after the last draw, for checkAndApplyConfigChanges const GPUSettingsProcessing& mProcessingSettings; GPUQA* mQA; @@ -235,7 +257,6 @@ class GPUDisplay : public GPUDisplayInterface std::mutex mMutexLoadAndShowEvent; std::condition_variable mCVLoadAndShowEvent; - bool mDrawTextInCompatMode = false; int32_t mDrawTextFontSize = 0; int32_t mNDrawCalls = 0; @@ -244,8 +265,6 @@ class GPUDisplay : public GPUDisplayInterface std::array mDrawColor = {1.f, 1.f, 1.f, 1.f}; - int32_t mTestSetting = 0; - float mAngleRollOrigin = -1e9; float mMaxClusterZ = -1; @@ -290,7 +309,6 @@ class GPUDisplay : public GPUDisplayInterface volatile bool mUpdateEventData = false; volatile bool mUpdateDrawCommands = true; volatile bool mUpdateRenderPipeline = false; - volatile bool mResetScene = false; volatile bool mLoadAndShowEvent = false; bool mTracksArePropagated = false; @@ -299,16 +317,12 @@ class GPUDisplay : public GPUDisplayInterface int32_t mAnimationFrame = 0; int32_t mAnimationLastBase = 0; int32_t mAnimationMixBase = -1; // Target keyframe of the current config blend (mixing), -1 = none - int32_t mAnimateScreenshot = 0; int32_t mAnimationExport = 0; - bool mAnimationChangeConfig = true; float mAnimationDelay = 2.f; vecpod mAnimateVectors[9]; vecpod mAnimateConfig; opengl_spline mAnimationSplines[8]; - int32_t mPrintInfoText = 1; - bool mPrintInfoTextAlways = 0; char mInfoText2[1024]; HighResTimer mInfoText2Timer, mInfoHelpTimer; @@ -326,7 +340,6 @@ class GPUDisplay : public GPUDisplayInterface vboList mGlDLGrid[NSECTORS]; vboList mGlDLGridTRD[NSECTORS / 2]; - bool mRequestScreenshot = false; std::string mScreenshotFile; float mYFactor = 1.0f; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index c1e39311b1768..2f353ea8b60c2 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -140,6 +140,9 @@ void GPUDisplayBackend::loadFont() return; } const auto& glyph = face->glyph; + if (i == 'M') { + mFontCharWidth = (glyph->advance.x >> 6) * (smoothFont() ? 0.25f : 1.f) / mFontRasterScale; // As in GLPrintText of the backends + } addFontSymbol(i, glyph->bitmap.width, glyph->bitmap.rows, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x, glyph->bitmap.buffer); } initializeTextDrawing(); @@ -163,13 +166,16 @@ std::vector GPUDisplayBackend::getPixels() void GPUDisplayBackend::fillIndirectCmdBuffer() { - mCmdBuffer.clear(); mIndirectSectorOffset.resize(GPUTPCGeometry::NSECTORS); - // TODO: Check if this can be parallelized + size_t total = 0; + for (uint32_t iSector = 0; iSector < GPUTPCGeometry::NSECTORS; iSector++) { + mIndirectSectorOffset[iSector] = total; + total += mDisplay->vertexBufferStart()[iSector].size(); + } + mCmdBuffer.resize(total); for (uint32_t iSector = 0; iSector < GPUTPCGeometry::NSECTORS; iSector++) { - mIndirectSectorOffset[iSector] = mCmdBuffer.size(); for (uint32_t k = 0; k < mDisplay->vertexBufferStart()[iSector].size(); k++) { - mCmdBuffer.emplace_back(mDisplay->vertexBufferCount()[iSector][k], 1, mDisplay->vertexBufferStart()[iSector][k], 0); + mCmdBuffer[mIndirectSectorOffset[iSector] + k] = DrawArraysIndirectCommand(mDisplay->vertexBufferCount()[iSector][k], 1, mDisplay->vertexBufferStart()[iSector][k], 0); } } } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 38114662714af..411af384221b5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -39,8 +39,6 @@ class GPUDisplayBackend virtual ~GPUDisplayBackend(); virtual int32_t ExtInit() { return 0; }; - virtual bool CoreProfile() { return false; }; - virtual uint32_t DepthBits() = 0; typedef std::tuple vboList; @@ -101,7 +99,7 @@ class GPUDisplayBackend const char* backendName() const { return mBackendName; } virtual void resizeScene(uint32_t width, uint32_t height) {} virtual size_t needMultiVBO() { return 0; } - virtual void OpenGLPrint(const char* s, float x, float y, float* color, float scale) = 0; + virtual void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) = 0; // nCharacters > 0: at most that many characters, otherwise until the terminating 0 static GPUDisplayBackend* getBackend(const char* type); std::vector getPixels(); virtual float getYFactor() const { return 1.0f; } @@ -122,7 +120,7 @@ class GPUDisplayBackend vecpod mCmdBuffer; bool mFreetypeInitialized = false; float mFontRasterScale = 1.f; // Display scale the font was rasterized for - bool mFrontendCompatTextDraw = false; + float mFontCharWidth = 0.f; // Advance of a character of the (monospace) font in logical pixels, 0 without Freetype font std::vector mScreenshotPixels; static constexpr float SMOOTH_POINT_EXTRA_SIZE = 0.6f; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h index 6d4160c093206..825fd9b353aa6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h @@ -26,7 +26,6 @@ class GPUDisplayBackendNone : public GPUDisplayBackend ~GPUDisplayBackendNone() override = default; protected: - uint32_t DepthBits() override { return 32; }; uint32_t drawVertices(const vboList& v, const drawType t) override { return 0; } void ActivateColor(std::array& color) override {} void setDepthBuffer() override {} @@ -40,7 +39,7 @@ class GPUDisplayBackendNone : public GPUDisplayBackend void finishText() override {} void pointSizeFactor(float factor) override {} void lineWidthFactor(float factor) override {} - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override {} + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override {} void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override {} void initializeTextDrawing() override {} }; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index d5342e0ab5abb..f79675cbad8a6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -66,15 +66,6 @@ bool GPUDisplayBackendOpenGL::canLimitToVSync() const return mDisplay->frontend()->canSetVSync(); } -bool GPUDisplayBackendOpenGL::CoreProfile() -{ -#ifdef GPUCA_DISPLAY_OPENGL_CORE - return true; -#else - return false; -#endif -} - // #define CHKERR(cmd) {cmd;} #define CHKERR(cmd) \ do { \ @@ -86,15 +77,6 @@ bool GPUDisplayBackendOpenGL::CoreProfile() } \ } while (false) -uint32_t GPUDisplayBackendOpenGL::DepthBits() -{ - GLint depthBits = 0; -#ifndef GPUCA_DISPLAY_OPENGL_CORE - glGetIntegerv(GL_DEPTH_BITS, &depthBits); -#endif - return depthBits; -} - void GPUDisplayBackendOpenGL::createFB(GLfb& fb, bool tex, bool withDepth, bool msaa, uint32_t width, uint32_t height) { fb.tex = tex; @@ -185,22 +167,13 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } if (mDisplay->useMultiVBO()) { - if (!mDisplay->cfgR().rendererCompatSmoothing) { // TODO: Remove OpenGL compat profile completely - CHKERR(glBindVertexArray(mVertexArray)); - } + CHKERR(glBindVertexArray(mVertexArray)); CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[iSector])); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); - } else -#endif - { - CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); - glEnableVertexAttribArray(0); - } + CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); + glEnableVertexAttribArray(0); } - const uint32_t smoothProgram = tt == POINTS ? (mUseSmoothPointShader ? mShaderProgramSmoothPoint : 0) : (mUseSmoothLineShader ? mShaderProgramSmoothLine : 0); + const uint32_t smoothProgram = tt == POINTS ? (mDisplay->cfgL().smoothPoints ? mShaderProgramSmoothPoint : 0) : (mDisplay->cfgL().smoothLines ? mShaderProgramSmoothLine : 0); if (smoothProgram) { CHKERR(glUseProgram(smoothProgram)); } @@ -222,19 +195,12 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glColor4f(color[0], color[1], color[2], color[3]); - } else -#endif - { - CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); - if (mUseSmoothPointShader) { - CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); - } - if (mUseSmoothLineShader) { - CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); - } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); + if (mDisplay->cfgL().smoothPoints) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); + } + if (mDisplay->cfgL().smoothLines) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); } } @@ -325,9 +291,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[0])); CHKERR(glGenBuffers(1, &mIndirectId)); CHKERR(glBindBuffer(GL_DRAW_INDIRECT_BUFFER, mIndirectId)); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - CHKERR(glShadeModel(GL_SMOOTH)); // Enable Smooth Shading -#endif setDepthBuffer(); setQuality(); CHKERR(mVertexShader = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertex_vert_spv_start, _binary_opengl_vertex_vert_spv_len)); @@ -459,7 +422,6 @@ void GPUDisplayBackendOpenGL::clearScreen(bool alphaOnly) void GPUDisplayBackendOpenGL::loadDataToGPU(size_t totalVertizes) { - // TODO: Check if this can be parallelized if (mDisplay->useMultiVBO()) { for (uint32_t i = 0; i < GPUTPCGeometry::NSECTORS; i++) { CHKERR(glNamedBufferData(mVBOId[i], mDisplay->vertexBuffer()[i].size() * sizeof(mDisplay->vertexBuffer()[i][0]), mDisplay->vertexBuffer()[i].data(), GL_STATIC_DRAW)); @@ -494,77 +456,34 @@ bool GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& setFrameBuffer(0); } clearScreen(); - mUseSmoothPointShader = mDisplay->cfgL().smoothPoints && !mDisplay->cfgR().rendererCompatSmoothing; // The compat profile uses GL_POINT_SMOOTH / GL_LINE_SMOOTH - mUseSmoothLineShader = mDisplay->cfgL().smoothLines && !mDisplay->cfgR().rendererCompatSmoothing; -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mUseSmoothPointShader) { // Compatibility context: gl_PointCoord is only defined with point sprites - CHKERR(glEnable(GL_POINT_SPRITE)); - } else { - CHKERR(glDisable(GL_POINT_SPRITE)); - } - if (mDisplay->cfgL().smoothPoints && mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnable(GL_POINT_SMOOTH)); - } else { - CHKERR(glDisable(GL_POINT_SMOOTH)); - } - if (mDisplay->cfgL().smoothLines && mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnable(GL_LINE_SMOOTH)); - } else { - CHKERR(glDisable(GL_LINE_SMOOTH)); - } -#endif CHKERR(glEnable(GL_BLEND)); CHKERR(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); glViewport(0, 0, mRenderWidth, mRenderHeight); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnableClientState(GL_VERTEX_ARRAY)); - CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); - } else -#endif - { - CHKERR(glBindVertexArray(mVertexArray)); - CHKERR(glUseProgram(mShaderProgram)); - CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); - CHKERR(glEnableVertexAttribArray(0)); - } -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glMatrixMode(GL_PROJECTION)); - CHKERR(glLoadMatrixf(&proj.Elements[0][0])); - CHKERR(glMatrixMode(GL_MODELVIEW)); - CHKERR(glLoadMatrixf(&view.Elements[0][0])); - } else -#endif - { - const hmm_mat4 modelViewProj = proj * view; - CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - if (mUseSmoothPointShader) { - CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - } - if (mUseSmoothLineShader) { - CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); - } - if (mMagneticFieldVisualization) { - CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); - } + CHKERR(glBindVertexArray(mVertexArray)); + CHKERR(glUseProgram(mShaderProgram)); + CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); + CHKERR(glEnableVertexAttribArray(0)); + + const hmm_mat4 modelViewProj = proj * view; + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + if (mDisplay->cfgL().smoothPoints) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + } + if (mDisplay->cfgL().smoothLines) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); + } + if (mMagneticFieldVisualization) { + CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); } return true; } void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glDisableClientState(GL_VERTEX_ARRAY)); - } else -#endif - { - CHKERR(glBindVertexArray(0)); - CHKERR(glUseProgram(0)); - } + CHKERR(glBindVertexArray(0)); + CHKERR(glUseProgram(0)); if (mOffscreenBufferMSAA.created) { uint32_t dstId = toMixBuffer ? mMixBuffer.fb_id : (mDownsampleFactor != 1 ? mOffscreenBuffer.fb_id : 0); @@ -617,15 +536,7 @@ void GPUDisplayBackendOpenGL::prepareText() setFrameBuffer(0); glViewport(0, 0, mScreenWidth, mScreenHeight); hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); - if (mDisplay->drawTextInCompatMode()) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&proj.Elements[0][0]); - glViewport(0, 0, mScreenWidth, mScreenHeight); -#endif - } else if (mFreetypeInitialized) { + if (mFreetypeInitialized) { CHKERR(glUseProgram(mShaderProgramText)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_text)); @@ -637,7 +548,7 @@ void GPUDisplayBackendOpenGL::prepareText() void GPUDisplayBackendOpenGL::finishText() { - if (!mDisplay->drawTextInCompatMode() && mFreetypeInitialized) { + if (mFreetypeInitialized) { CHKERR(glBindVertexArray(0)); CHKERR(glBindTexture(GL_TEXTURE_2D, 0)); CHKERR(glUseProgram(0)); @@ -667,55 +578,25 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) { hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1.f, 1.f); glDisable(GL_DEPTH_TEST); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&proj.Elements[0][0]); - CHKERR(glEnable(GL_TEXTURE_2D)); - } else -#endif - { - CHKERR(glUseProgram(mShaderProgramTexture)); - CHKERR(glActiveTexture(GL_TEXTURE0)); - CHKERR(glBindVertexArray(VAO_texture)); - CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); - CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); - } + CHKERR(glUseProgram(mShaderProgramTexture)); + CHKERR(glActiveTexture(GL_TEXTURE0)); + CHKERR(glBindVertexArray(VAO_texture)); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); + CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); CHKERR(glBindTexture(GL_TEXTURE_2D, mMixBuffer.fbCol_id)); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glColor4f(1, 1, 1, mixSlaveImage); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); - glVertex3f(0, 0, 0); - glTexCoord2f(0, 1); - glVertex3f(0, mRenderHeight, 0); - glTexCoord2f(1, 1); - glVertex3f(mRenderWidth, mRenderHeight, 0); - glTexCoord2f(1, 0); - glVertex3f(mRenderWidth, 0, 0); - glEnd(); - glColor4f(1, 1, 1, 0); - CHKERR(glDisable(GL_TEXTURE_2D)); - } else -#endif - { - CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); - CHKERR(glBindVertexArray(0)); - CHKERR(glUseProgram(0)); - } + CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); + CHKERR(glBindVertexArray(0)); + CHKERR(glUseProgram(0)); CHKERR(glBindTexture(GL_TEXTURE_2D, 0)); setDepthBuffer(); } void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) { - const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mUseSmoothPointShader ? SMOOTH_POINT_EXTRA_SIZE : 0.f); - if (mUseSmoothPointShader) { + const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mDisplay->cfgL().smoothPoints ? SMOOTH_POINT_EXTRA_SIZE : 0.f); + if (mDisplay->cfgL().smoothPoints) { CHKERR(glProgramUniform1f(mShaderProgramSmoothPoint, UNIFORM_LOCATION_POINT_SIZE, size)); } CHKERR(glPointSize(size)); @@ -724,7 +605,7 @@ void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) { float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; - if (mUseSmoothLineShader) { + if (mDisplay->cfgL().smoothLines) { width += SMOOTH_LINE_EXTRA_WIDTH; CHKERR(glProgramUniform1f(mShaderProgramSmoothLine, UNIFORM_LOCATION_LINE_WIDTH, width)); width *= 1.4143f; // Wide lines are parallelograms in OpenGL, widen so that the perpendicular width is covered @@ -787,9 +668,9 @@ void GPUDisplayBackendOpenGL::initializeTextDrawing() CHKERR(glBindVertexArray(0)); } -void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float* color, float scale) +void GPUDisplayBackendOpenGL::GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) { - if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { + if (!mFreetypeInitialized) { return; } CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); @@ -797,7 +678,7 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float if (smoothFont()) { scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const unsigned char* c = (const unsigned char*)s; *c; c++) { + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); continue; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index f30a22846a1ac..f6e9d8801b6d0 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -34,9 +34,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend GPUDisplayBackendOpenGL(); ~GPUDisplayBackendOpenGL() override = default; int32_t ExtInit() override; - bool CoreProfile() override; bool canLimitToVSync() const override; // Buffer swap blocks with VSync, if the frontend can set the swap interval - uint32_t DepthBits() override; protected: void createFB(GLfb& fb, bool tex, bool withDepth, bool msaa, uint32_t width, uint32_t height); @@ -81,7 +79,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; void clearFontSymbols() override; - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; struct FontSymbolOpenGL : public FontSymbol { uint32_t texId; @@ -101,10 +99,8 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mFragmentShaderDownsample; uint32_t mGeometryShader; uint32_t mShaderProgram; - uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints with the shader path (not rendererCompatSmoothing / GL_POINT_SMOOTH) - bool mUseSmoothPointShader = false; - uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines with the shader path (not rendererCompatSmoothing / GL_LINE_SMOOTH) - bool mUseSmoothLineShader = false; + uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints + uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines uint32_t mShaderProgramText; uint32_t mShaderProgramTexture; uint32_t mShaderProgramDownsample; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index b0d63bfda17cf..b76fc71de66d1 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -556,8 +556,6 @@ void GPUDisplayBackendVulkan::createDevice() if (checkDevice(mPhysicalDevice, reqDeviceExtensions, reqDeviceFeatures, true) <= 0) { throw std::runtime_error("Selected vulkan device is incompatible"); } - mSmoothLinesSupported = getFeatures(mPhysicalDevice, reqDeviceFeatures).get().smoothLines; - reqDeviceFeatures.get().smoothLines = mSmoothLinesSupported; std::vector deviceExtensions = reqDeviceExtensions; void* featureChain = &reqDeviceFeatures.get(); const std::vector availableDeviceExtensions = mPhysicalDevice.enumerateDeviceExtensionProperties(nullptr); @@ -578,7 +576,7 @@ void GPUDisplayBackendVulkan::createDevice() mStencilSupported = (bool)(depth64FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); bool mailboxSupported = std::find(mSwapChainDetails.presentModes.begin(), mSwapChainDetails.presentModes.end(), vk::PresentModeKHR::eMailbox) != mSwapChainDetails.presentModes.end(); if (mDisplay->GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d, FIFO latest ready present mode %d, smooth lines %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported, (int32_t)mFifoLatestReadySupported, (int32_t)mSmoothLinesSupported); + GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d, FIFO latest ready present mode %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported, (int32_t)mFifoLatestReadySupported); } vk::DeviceQueueCreateInfo queueCreateInfo{}; @@ -617,7 +615,7 @@ void GPUDisplayBackendVulkan::clearDevice() // ---------------------------- VULKAN COMMAND BUFFERS ---------------------------- -void GPUDisplayBackendVulkan::createCommandBuffers() +void GPUDisplayBackendVulkan::createCommandBuffersForImages() { vk::CommandBufferAllocateInfo allocInfo{}; allocInfo.commandPool = mCommandPool; @@ -632,7 +630,7 @@ void GPUDisplayBackendVulkan::createCommandBuffers() mCommandBuffersPresent = mDevice.allocateCommandBuffers(allocInfo); } -void GPUDisplayBackendVulkan::clearCommandBuffers() +void GPUDisplayBackendVulkan::clearCommandBuffersForImages() { mDevice.freeCommandBuffers(mCommandPool, mCommandBuffers.size(), mCommandBuffers.data()); mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersText.size(), mCommandBuffersText.data()); @@ -648,18 +646,22 @@ void GPUDisplayBackendVulkan::createSemaphores() { vk::SemaphoreCreateInfo semaphoreInfo{}; mImageAvailableSemaphore.resize(mFramesInFlight); - mPresentSemaphore.resize(mImageCount); for (uint32_t i = 0; i < mFramesInFlight; i++) { mImageAvailableSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); } - for (uint32_t i = 0; i < mImageCount; i++) { - mPresentSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - } vk::SemaphoreTypeCreateInfo timelineTypeInfo{vk::SemaphoreType::eTimeline, 0}; semaphoreInfo.pNext = &timelineTypeInfo; mTimelineSemaphore = mDevice.createSemaphore(semaphoreInfo, nullptr); mTimelineValue = 0; mFrameTimelineValue.assign(mFramesInFlight, 0); +} + +void GPUDisplayBackendVulkan::createSemaphoresForImages() +{ + mPresentSemaphore.resize(mImageCount); + for (uint32_t i = 0; i < mImageCount; i++) { + mPresentSemaphore[i] = mDevice.createSemaphore(vk::SemaphoreCreateInfo{}, nullptr); + } mImageTimelineValue.assign(mImageCount, 0); for (uint32_t i = 0; i < mImageCount; i++) { mPresentFences.emplace_back(mDevice.createFence(vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled}, nullptr)); @@ -679,8 +681,12 @@ void GPUDisplayBackendVulkan::waitPresentFences() void GPUDisplayBackendVulkan::clearSemaphores() { clearVector(mImageAvailableSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); mDevice.destroySemaphore(mTimelineSemaphore, nullptr); +} + +void GPUDisplayBackendVulkan::clearSemaphoresForImages() +{ + clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mPresentFences, [&](auto& x) { mDevice.destroyFence(x, nullptr); }); } @@ -688,13 +694,6 @@ void GPUDisplayBackendVulkan::clearSemaphores() void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() { - for (int32_t j = 0; j < 3; j++) { - mUniformBuffersMat[j].resize(mImageCount); - for (uint32_t i = 0; i < mImageCount; i++) { - mUniformBuffersMat[j][i] = createBuffer(sizeof(hmm_mat4), nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); - } - } - vk::DescriptorSetLayoutBinding uboLayoutBindingMat{}; uboLayoutBindingMat.binding = 0; uboLayoutBindingMat.descriptorType = vk::DescriptorType::eUniformBuffer; @@ -714,7 +713,16 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() mUniformDescriptor = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); layoutInfo.bindingCount = 2; mUniformDescriptorTexture = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); +} +void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffersForImages() +{ + for (int32_t j = 0; j < 3; j++) { + mUniformBuffersMat[j].resize(mImageCount); + for (uint32_t i = 0; i < mImageCount; i++) { + mUniformBuffersMat[j][i] = createBuffer(sizeof(hmm_mat4), nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); + } + } mUpdateUBOdrawMatrix = mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; } @@ -722,6 +730,10 @@ void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffers() { mDevice.destroyDescriptorSetLayout(mUniformDescriptor, nullptr); mDevice.destroyDescriptorSetLayout(mUniformDescriptorTexture, nullptr); +} + +void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffersForImages() +{ for (int32_t j = 0; j < 3; j++) { clearVector(mUniformBuffersMat[j], [&](auto& x) { clearBuffer(x); }); } @@ -818,22 +830,24 @@ void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain) } mSwapChainImages = mDevice.getSwapchainImagesKHR(mSwapChain); - uint32_t oldFramesInFlight = mFramesInFlight, oldImageCount = mImageCount; - mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; + uint32_t oldImageCount = mImageCount; mImageCount = mSwapChainImages.size(); if (mImageCount > sizeof(mUpdateUBOdrawMatrix) * 8) { throw std::runtime_error("Too many swapchain images"); } - if (mFramesInFlight != oldFramesInFlight || mImageCount != oldImageCount || !mCommandInfrastructureCreated) { + if (mImageCount != oldImageCount || !mCommandInfrastructureCreated) { if (mCommandInfrastructureCreated) { - clearUniformLayoutsAndBuffers(); // TODO: Check what must actually be cleaned up if frames in flight changes - clearCommandBuffers(); - clearSemaphores(); + clearUniformLayoutsAndBuffersForImages(); + clearCommandBuffersForImages(); + clearSemaphoresForImages(); + } else { + createUniformLayoutsAndBuffers(); + createSemaphores(); } - createUniformLayoutsAndBuffers(); - createCommandBuffers(); - createSemaphores(); + createUniformLayoutsAndBuffersForImages(); + createCommandBuffersForImages(); + createSemaphoresForImages(); mCommandInfrastructureCreated = true; } @@ -861,24 +875,21 @@ void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixi needRecordCommandBuffers(); const bool needUpdateSwapChain = mMustUpdateSwapChain; const bool needUpdateOffscreenBuffers = needUpdateSwapChain || mDownsampleFactor != getDownsampleFactor(forScreenshot) || mMSAASampleCount != getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)) || mZActive != (mZSupported && mDisplay->cfgL().depthBuffer) || mMixingSupported != forMixing; - const bool smoothLinesChanged = mSmoothLines != useNativeSmoothLines(); - if (!needUpdateOffscreenBuffers && !smoothLinesChanged) { + if (!needUpdateOffscreenBuffers) { return; } mDevice.waitIdle(); const vk::Format oldFormat = mSurfaceFormat.format; const vk::SampleCountFlagBits oldMSAASampleCount = mMSAASampleCount; const bool oldZActive = mZActive; - if (needUpdateOffscreenBuffers) { - clearOffscreenBuffers(); - if (needUpdateSwapChain) { - clearSwapChain(true); - createSwapChain(mSwapChain); - } - createOffscreenBuffers(forScreenshot, forMixing); - mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed + clearOffscreenBuffers(); + if (needUpdateSwapChain) { + clearSwapChain(true); + createSwapChain(mSwapChain); } - if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive || smoothLinesChanged) { // Viewport / scissor are dynamic + createOffscreenBuffers(forScreenshot, forMixing); + mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed + if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive) { // Viewport / scissor are dynamic clearPipeline(); createPipeline(); } @@ -1096,15 +1107,10 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional - mSmoothLines = useNativeSmoothLines(); - vk::PipelineRasterizationLineStateCreateInfo smoothLineState{}; - smoothLineState.lineRasterizationMode = vk::LineRasterizationMode::eRectangularSmooth; // Coverage goes to alpha, blending is enabled - mPipelines.resize(9); // 0 points, 1 lines, 2 line strip (drawType index), 3 text, 4 texture, 5-7 smooth (shader-based) points, lines, line strip, 8 FSAA downsampling static constexpr vk::PrimitiveTopology types[3] = {vk::PrimitiveTopology::ePointList, vk::PrimitiveTopology::eLineList, vk::PrimitiveTopology::eLineStrip}; for (uint32_t i = 0; i < mPipelines.size(); i++) { if (i == 8) { // FSAA downsampling, full-screen triangle without vertex input (last pipeline, the changed states are not reset) - rasterizer.pNext = nullptr; rasterizer.cullMode = vk::CullModeFlagBits::eNone; vertexInputInfo.vertexBindingDescriptionCount = vertexInputInfo.vertexAttributeDescriptionCount = 0; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1117,7 +1123,6 @@ void GPUDisplayBackendVulkan::createPipeline() colorBlendAttachment.blendEnable = false; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; } else if (i == 4) { // Texture rendering - rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1130,7 +1135,6 @@ void GPUDisplayBackendVulkan::createPipeline() colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; } else if (i == 3) { // Text rendering - rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1147,7 +1151,6 @@ void GPUDisplayBackendVulkan::createPipeline() attributeDescriptions.format = vk::Format::eR32G32B32Sfloat; const uint32_t drawIdx = i >= 5 ? i - 5 : i; inputAssembly.topology = types[drawIdx]; - rasterizer.pNext = mSmoothLines && (i == 1 || i == 2) ? &smoothLineState : nullptr; vertShaderStageInfo.pNext = &mShaders.at(i < 5 ? (drawIdx == 0 ? "vertexPoint" : "vertex") : (drawIdx == 0 ? "vertexSmoothPoint" : "vertexSmoothLine")); fragShaderStageInfo.pNext = &mShaders.at(i < 5 ? "fragment" : (drawIdx == 0 ? "fragmentSmoothPoint" : "fragmentSmoothLine")); pipelineInfo.layout = mPipelineLayout; @@ -1397,8 +1400,10 @@ void GPUDisplayBackendVulkan::ExitBackendA() clearOffscreenBuffers(); clearSwapChain(); if (mCommandInfrastructureCreated) { + clearSemaphoresForImages(); clearSemaphores(); - clearCommandBuffers(); + clearCommandBuffersForImages(); + clearUniformLayoutsAndBuffersForImages(); clearUniformLayoutsAndBuffers(); } clearTextureSampler(); @@ -1453,7 +1458,7 @@ uint32_t GPUDisplayBackendVulkan::drawVertices(const vboList& v, const drawType return count; } - const int32_t pipeline = tt + ((tt == POINTS ? mDisplay->cfgL().smoothPoints : useSmoothLineShader()) ? 5 : 0); + const int32_t pipeline = tt + ((tt == POINTS ? mDisplay->cfgL().smoothPoints : mDisplay->cfgL().smoothLines) ? 5 : 0); if (mCurrentCommandBufferLastPipeline != pipeline) { mCurrentCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[pipeline]); mCurrentCommandBufferLastPipeline = pipeline; @@ -1496,7 +1501,7 @@ bool GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& if (mPresentModeForVSyncLimit != (fpsLimitMode() == FPS_LIMIT_VSYNC)) { // Present mode depends on the FPS limit mode mMustUpdateSwapChain = true; } - bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != useNativeSmoothLines(); + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot); if (!mustUpdateSwapChain) { retVal = getImage(); if (retVal == vk::Result::eErrorOutOfDateKHR) { @@ -1781,23 +1786,13 @@ void GPUDisplayBackendVulkan::pointSizeFactor(float factor) mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, sizeof(std::array), sizeof(size), &size); } -bool GPUDisplayBackendVulkan::useNativeSmoothLines() const -{ - return mDisplay->cfgL().smoothLines && mSmoothLinesSupported && mDisplay->cfgR().rendererCompatSmoothing; -} - -bool GPUDisplayBackendVulkan::useSmoothLineShader() const -{ - return mDisplay->cfgL().smoothLines && !useNativeSmoothLines(); -} - void GPUDisplayBackendVulkan::lineWidthFactor(float factor) { if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; - if (useSmoothLineShader()) { + if (mDisplay->cfgL().smoothLines) { const float expand = mStrictLines ? 1.f : 1.4143f; // Non-strict wide lines may be parallelograms, widen so that the perpendicular width is covered width = std::min(width + SMOOTH_LINE_EXTRA_WIDTH, mMaxLineWidth / expand); // The shader must see the width that is actually rasterized mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, 5 * sizeof(float), sizeof(width), &width); @@ -1893,9 +1888,9 @@ void GPUDisplayBackendVulkan::clearFontSymbols() mFontSymbols.clear(); } -void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float* color, float scale) +void GPUDisplayBackendVulkan::GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) { - if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { + if (!mFreetypeInitialized) { return; } @@ -1904,7 +1899,7 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const unsigned char* c = (const unsigned char*)s; *c; c++) { + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); continue; @@ -2024,11 +2019,6 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageView v } } -uint32_t GPUDisplayBackendVulkan::DepthBits() -{ - return 32; -} - bool GPUDisplayBackendVulkan::backendNeedRedraw() { return !mCommandBufferUpToDate[mCurrentImageIndex]; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 4a621edc096d0..d76fa357bb216 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -32,8 +32,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend GPUDisplayBackendVulkan(); ~GPUDisplayBackendVulkan() override; - uint32_t DepthBits() override; - protected: struct SwapChainSupportDetails { vk::SurfaceCapabilitiesKHR capabilities; @@ -81,8 +79,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void cmdPushDescriptors(vk::CommandBuffer commandBuffer, vk::PipelineLayout layout, const VulkanBuffer& ubo, vk::ImageView textureView = {}); // Push descriptors: UBO at binding 0, optional texture at binding 2 void pointSizeFactor(float factor) override; void lineWidthFactor(float factor) override; - bool useNativeSmoothLines() const; - bool useSmoothLineShader() const; bool canLimitToVSync() const override { return true; } // Present wait void waitPresentFences(); void resizeScene(uint32_t width, uint32_t height) override; @@ -116,17 +112,21 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void createDevice(); void loadShaders(); void createTextureSampler(); - void createCommandBuffers(); + void createCommandBuffersForImages(); void createSemaphores(); + void createSemaphoresForImages(); void createUniformLayoutsAndBuffers(); + void createUniformLayoutsAndBuffersForImages(); void createSwapChain(vk::SwapchainKHR oldSwapChain = {}); void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); void createPipeline(); void clearDevice(); void clearTextureSampler(); - void clearCommandBuffers(); + void clearCommandBuffersForImages(); void clearSemaphores(); + void clearSemaphoresForImages(); void clearUniformLayoutsAndBuffers(); + void clearUniformLayoutsAndBuffersForImages(); void clearOffscreenBuffers(); void clearSwapChain(bool keepSwapChain = false); void clearPipeline(); @@ -136,7 +136,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; void clearFontSymbols() override; - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; uint32_t mGraphicsFamily; SwapChainSupportDetails mSwapChainDetails; @@ -218,12 +218,10 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mStencilSupported = false; float mMaxLineWidth = 1.f, mMaxPointSize = 1.f; bool mStrictLines = false; // Wide lines are rectangles, not parallelograms - bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines bool mFifoLatestReadySupported = false; // Optional present mode FIFO_LATEST_READY (VK_KHR / VK_EXT_present_mode_fifo_latest_ready) vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16; bool mZActive = false; - bool mSmoothLines = false; bool mPresentModeErrorShown = false; bool mPresentModeForVSyncLimit = false; // Swapchain created with FIFO for the refresh rate FPS limit std::vector mPresentFences; // Per swapchain image, signaled when its last present is done (swapchain_maintenance1) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index b48d7f6c59d5c..14abcd1193203 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -44,7 +44,6 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include #include #include -#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -81,27 +80,37 @@ void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) void GPUDisplayFrontend::waitForFPSLimit() { - mNeedUpdate = 0; const auto now = std::chrono::steady_clock::now(); const auto& cfg = mDisplay->cfgR(); if (mBackend->fpsLimitMode() != GPUDisplayBackend::FPS_LIMIT_RATE || cfg.fpsRate <= 0) { mNextFrameTime = now; return; } - const auto period = std::chrono::duration_cast(std::chrono::duration(1. / cfg.fpsRate)); // TODO: wake up on HasExternalGUISignals + const auto period = std::chrono::duration_cast(std::chrono::duration(1. / cfg.fpsRate)); mNextFrameTime += period; if (mNextFrameTime < now - period) { // More than one frame behind (slow rendering, first frame), do not try to catch up mNextFrameTime = now; } - std::this_thread::sleep_until(mNextFrameTime); + std::unique_lock lock(mConfigUpdateMutex); + mConfigUpdateCV.wait_until(lock, mNextFrameTime, [this] { return mConfigUpdatePending; }); +} + +void GPUDisplayFrontend::signalConfigUpdate() +{ + { + std::lock_guard lock(mConfigUpdateMutex); + mConfigUpdatePending = true; + } + mConfigUpdateCV.notify_one(); } void GPUDisplayFrontend::HandleExternalGUISignals() { - const GPUSettingsDisplayHeavy oldCfgH = mDisplay->mCfgH; - const GPUSettingsDisplayLight oldCfgL = mDisplay->mCfgL; - const GPUSettingsDisplayRenderer oldCfgR = mDisplay->mCfgR; - bool changes = HandleExternalGUIKeys(); + { + std::lock_guard lock(mConfigUpdateMutex); + mConfigUpdatePending = false; // Before fetching the updates, so that updates sent in the meantime signal again + } + HandleExternalGUIKeys(); const bool gui = isGUIRunning(); std::optional update; @@ -114,19 +123,19 @@ void GPUDisplayFrontend::HandleExternalGUISignals() for (int32_t key : update->keys) { mDisplay->HandleExternalGUIKey(key); } - if (update->lineWidth) { - mDisplay->mCfgL.lineWidth = *update->lineWidth; + applyGUIValues(update->values); + if (update->guiScale) { + mDisplay->mCfgC.mGuiScale = *update->guiScale; } - changes = true; } - if (changes) { - mDisplay->checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } if (gui) { + auto values = getGUIValues(); { std::lock_guard lock(mGUIChannel->mutex); mGUIChannel->state.cfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; + mGUIChannel->state.values = std::move(values); + mGUIChannel->state.guiScale = mDisplay->mCfgC.mGuiScale; if (update) { mGUIChannel->state.appliedUpdate = update->id; } @@ -137,28 +146,27 @@ void GPUDisplayFrontend::HandleExternalGUISignals() } } -bool GPUDisplayFrontend::HasExternalGUISignals() const -{ - return mExternalGUIKeys.size(); -} - -bool GPUDisplayFrontend::HandleExternalGUIKeys() +void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) { - std::lock_guard lock(mExternalGUIKeyMutex); - for (auto key : mExternalGUIKeys) { - mDisplay->HandleExternalGUIKey(key); + { + std::lock_guard lock(mExternalGUIKeyMutex); + mExternalGUIKeys.emplace_back(v); } - return !std::exchange(mExternalGUIKeys, {}).empty(); + signalConfigUpdate(); } -void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) -{ - std::lock_guard lock(mExternalGUIKeyMutex); - mExternalGUIKeys.emplace_back(v); -} +int32_t GPUDisplayFrontend::getDisplayControl() const { return mDisplay->mCfgC.mDisplayControl.load(); } +void GPUDisplayFrontend::setDisplayControl(int32_t v) { mDisplay->mCfgC.mDisplayControl.store(v); } void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } -int32_t GPUDisplayFrontend::DrawGLScene() { return mDisplay->DrawGLScene(); } +int32_t GPUDisplayFrontend::HandleUpdatesAndDrawGLScene(bool suspended) +{ + HandleExternalGUISignals(); + mDisplay->checkAndApplyConfigChanges(); + const int32_t retVal = mDisplay->DrawGLScene(suspended); + mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + return retVal; +} void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) { @@ -189,6 +197,7 @@ int32_t GPUDisplayFrontend::startGUI() #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT if (!mGUI) { mGUIChannel = std::make_unique(); + mGUIChannel->updatePosted = [this]() { signalConfigUpdate(); }; mGUI = std::make_unique(*mGUIChannel); } if (!mGUI->isRunning()) { @@ -200,6 +209,7 @@ int32_t GPUDisplayFrontend::startGUI() mGUIChannel->replyTimeout = std::chrono::milliseconds(mDisplay->cfg().guiReplyTimeout); mGUIChannel->maxUpdatesAhead = std::max(mDisplay->cfg().guiMaxUpdatesAhead, 1); // 0 would block all updates mGUIChannel->windowName = DISPLAY_WINDOW_NAME; + setGUILayout(*mGUIChannel); } mGUI->start(); } else { @@ -264,23 +274,36 @@ GPUDisplayBackend* GPUDisplayFrontend::backend() void GPUDisplayFrontend::setWindowState(bool fullScreen, bool maximized) { - mDisplay->mCfgR.fullScreen = fullScreen; - mDisplay->mCfgR.maximized = maximized; + mDisplay->mCfgR.fullScreen = mWindowFullScreen = fullScreen; + mDisplay->mCfgR.maximized = mWindowMaximized = maximized; +} + +void GPUDisplayFrontend::requestFullScreen(bool set) +{ + if (set != std::exchange(mWindowFullScreen, set)) { + SwitchFullscreen(set); + } +} + +void GPUDisplayFrontend::requestMaximized(bool set) +{ + if (set != std::exchange(mWindowMaximized, set)) { + ToggleMaximized(set); + } } void GPUDisplayFrontend::focusLost() { std::fill(std::begin(mKeys), std::end(mKeys), false); - std::fill(std::begin(mKeysShift), std::end(mKeysShift), false); mMouseDn = mMouseDnR = mMouseDnM = false; } void GPUDisplayFrontend::applyInitialWindowState() { if (mDisplay->cfgR().fullScreen) { - SwitchFullscreen(true); + requestFullScreen(true); } else if (mDisplay->cfgR().maximized) { - ToggleMaximized(true); + requestMaximized(true); } } @@ -309,22 +332,22 @@ int32_t GPUDisplayFrontend::StartDisplay() return (1); } #endif + mDisplay->buildConfig(); + if (mDisplay->cfg().listFrontendOptionHandling) { + listConfig(); + } return (0); } void GPUDisplayFrontend::StopDisplay() { - { - std::lock_guard lock(mExitStateMutex); - if (mDisplayRunning) { - mDisplayControl = 2; - StopDisplayA(); - mDisplay->UnblockDisplay(); - } - } - while (mDisplayRunning) { - std::this_thread::sleep_for(std::chrono::microseconds(10000)); // TODO: Signal by semaphore + std::unique_lock lock(mExitStateMutex); + if (mDisplayRunning) { + mDisplay->mCfgC.mDisplayControl.store(2); + StopDisplayA(); + mDisplay->UnblockDisplay(); } + mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); } void GPUDisplayFrontend::StopDisplayA() {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 1bfbc36883f41..3866f082bd89b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -20,10 +20,15 @@ #include #include #include +#include +#include +#include +#include #include namespace o2::gpu { +using GPUDisplayGUIValue = std::variant; // Value of a Qt GUI element class GPUReconstruction; class GPUDisplay; class GPUDisplayBackend; @@ -59,7 +64,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual void ToggleMaximized(bool set) = 0; // Maximize window virtual void SetVSync(bool enable) = 0; // Enable / disable vsync virtual bool canSetVSync() { return false; } // OpenGL: swap interval can be set - virtual void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) = 0; // Print text on the display (needs the backend to build the font) GPUDisplayBackend* backend(); static GPUDisplayFrontend* getFrontend(const char* type); virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; } @@ -68,8 +72,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual int32_t getVulkanSurface(void* instance, void* surface) { return 1; } virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; }; - int32_t getDisplayControl() const override { return mDisplayControl; } - void setDisplayControl(int32_t v) override { mDisplayControl = v; } + int32_t getDisplayControl() const override; + void setDisplayControl(int32_t v) override; void sendExternalGUIKey(int32_t v) override; frontendTypes frontendType() const { return mFrontendType; } @@ -83,48 +87,28 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual int32_t FrontendMain() = 0; virtual void StopDisplayA(); static void* FrontendThreadWrapper(void*); - void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode) + void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode), or until an external config update is signalled + void signalConfigUpdate(); // Signal a config update from an external source (console keys, Qt GUI), wakes up waitForFPSLimit static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing static constexpr int32_t MIN_WIDTH = 200, MIN_HEIGHT = 150; // Minimum window size static constexpr const char* DISPLAY_WINDOW_NAME = "GPU CA Standalone Event Display"; // Title of event display set by backend // Constant key codes for special mKeys (to unify different treatment in X11 / Windows / GLUT / etc.) - static constexpr int32_t KEY_UP = 1; - static constexpr int32_t KEY_DOWN = 2; - static constexpr int32_t KEY_LEFT = 3; - static constexpr int32_t KEY_RIGHT = 4; - static constexpr int32_t KEY_PAGEUP = 5; - static constexpr int32_t KEY_PAGEDOWN = 6; - static constexpr int32_t KEY_SHIFT = 8; - static constexpr int32_t KEY_ALT = 9; - static constexpr int32_t KEY_RALT = 10; - static constexpr int32_t KEY_CTRL = 11; - static constexpr int32_t KEY_RCTRL = 12; - static constexpr int32_t KEY_ENTER = 13; // fixed at 13 - static constexpr int32_t KEY_F1 = 14; - static constexpr int32_t KEY_F2 = 15; - static constexpr int32_t KEY_F3 = 16; - static constexpr int32_t KEY_F4 = 17; - static constexpr int32_t KEY_F5 = 18; - static constexpr int32_t KEY_F6 = 19; - static constexpr int32_t KEY_F7 = 20; - static constexpr int32_t KEY_F8 = 21; - static constexpr int32_t KEY_F9 = 22; - static constexpr int32_t KEY_F10 = 23; - static constexpr int32_t KEY_F11 = 24; - static constexpr int32_t KEY_F12 = 25; - static constexpr int32_t KEY_INSERT = 26; - static constexpr int32_t KEY_ESCAPE = 27; // fixed at 27 - static constexpr int32_t KEY_HOME = 28; - static constexpr int32_t KEY_END = 29; - static constexpr int32_t KEY_SPACE = 32; // fixed at 32 - - // volatile variables to exchange control informations between display and backend - volatile int32_t mDisplayControl = 0; // Control for next event (=1) or quit (=2) - volatile int32_t mNeedUpdate = 0; // flag that backend shall update the GL window, and call DrawGLScene +#define GPUDisplayFrontendAddKey(code, key) static constexpr int32_t KEY_##key = code +#define GPUDisplayFrontendKeyDownMap(code, key) static constexpr int32_t KEY_MAP_##key = code +#include "GPUDisplayFrontendKeys.h" + static constexpr uint32_t N_SPECIAL_KEYS = 32; + static constexpr uint32_t SPECIAL_KEYS_SHIFTED = 255; + static constexpr uint32_t N_TOTAL_KEYS = 256 + N_SPECIAL_KEYS; + std::mutex mExternalGUIKeyMutex; std::vector mExternalGUIKeys; // Keys sent by external entity (usually console), may be ignored by backend. + std::mutex mConfigUpdateMutex; + std::condition_variable mConfigUpdateCV; // Notified by signalConfigUpdate + bool mConfigUpdatePending = false; // Protected by mConfigUpdateMutex, set by signalConfigUpdate, cleared by HandleExternalGUISignals std::mutex mExitStateMutex; + std::condition_variable mDisplayStoppedCV; // Notified with mExitStateMutex held when the frontend sets mDisplayRunning to false, StopDisplay waits for it + std::array mKeyDownMap = {}; // Keyboard / Mouse actions bool mMouseDn = false; // Mouse button down @@ -134,10 +118,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface float mMouseMvX, mMouseMvY; // Current mouse pointer position int32_t mMouseWheel = 0; // Incremental value of mouse wheel, ca +/- 100 per wheel tick bool mKeys[256] = {false}; // Array of mKeys currently pressed - bool mKeysShift[256] = {false}; // Array whether shift was held during key-press int32_t mDisplayHeight = INIT_HEIGHT; int32_t mDisplayWidth = INIT_WIDTH; - int32_t mCanDrawText = 0; // 1 = in compat mode, 2 = with shader std::chrono::steady_clock::time_point mNextFrameTime; @@ -151,10 +133,13 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface std::unique_ptr mGUIChannel; // Must outlive mGUI std::unique_ptr mGUI; + int32_t HandleUpdatesAndDrawGLScene(bool suspended = false); // Handle external GUI signals and config changes, then draw the scene (suspended: only update, no drawing, e.g. for a hidden window) + void HandleKey(uint8_t key); // Callback for handling key presses - int32_t DrawGLScene(); // Callback to draw the GL scene void HandleExternalGUISignals(); // Callback to handle signals from external GUIs bool HasExternalGUISignals() const; // Returns whether there are currently pending signals from external GUIs to be handled + bool processKeyLambda(uint32_t key); // Process key lambda callback + void showConfigInfo(uint32_t index); // Info for a changed option (index in the config RTTI): its info callback, or "struct.option changed to value" void ResizeScene(int32_t width, int32_t height); // Callback when GL window is resized int32_t InitDisplay(bool initFailure = false); // Callback to initialize the GL Display (to be called in StartDisplay) void ExitDisplay(); // Callback to clean up the GL Display @@ -163,8 +148,26 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void setWindowState(bool fullScreen, bool maximized); // Sync the settings if the window manager changed the window state void focusLost(); // Release all keys and mouse buttons when the window loses focus, since the release events go to another window void applyInitialWindowState(); // Switch to fullscreen / maximize at startup according to the settings + void requestFullScreen(bool set); // Call SwitchFullscreen / ToggleMaximized only if the state differs from the last requested or reported (setWindowState) one + void requestMaximized(bool set); + private: - bool HandleExternalGUIKeys(); // Returns whether keys were handled, config changes are not handled + struct configRTTIInfo; + struct configRTTIExt; + struct configKey; + std::unique_ptr mConfigRTTI{nullptr, nullptr}; // Deleter set in GPUDisplayFrontendConfig.cxx, the only place where configRTTIInfo is defined + + void HandleExternalGUIKeys(); + bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system + template + void add_key_binding(Args&&... args); // KEY_LAMBDA(key, ...) entries, the help text, and optionally the keys to show in the help text, only used in GPUDisplayFrontendConfig.cxx + template + void add_gui_binding(Args&&... args); // GUI_LAMBDA(name, ...) entries shown in one frame of the Qt GUI, and optionally its description and a flag to place the elements horizontally (default vertically), only used in GPUDisplayFrontendConfig.cxx + void setGUILayout(GPUDisplayGUIChannel& channel) const; // Copy the GUI elements of the add_gui_binding calls to the channel + std::vector getGUIValues(); // Current values of the options of the GUI elements, by element id + void applyGUIValues(const std::vector>& values); // Call the setter of the GUI elements changed in the GUI (element id, value), and show the info of their options + void listConfig(); + const std::vector& getHelpText() const; // Help text of all key bindings, one line per binding }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx new file mode 100644 index 0000000000000..e85443f757a8a --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -0,0 +1,541 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfig.cxx +/// \author David Rohr + +#include "GPUDisplayFrontend.h" +#include "GPUDisplay.h" +#include "frontend/GPUDisplayInfo.inc" +#include "GPULogging.h" +#include "GPUDisplayGUIChannel.h" +#include "utils/qconfig_rtti.h" +#include "utils/qconfig_internal_helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::gpu; +struct GPUDisplayFrontend::configRTTIExt : public qConfig::qConfigRTTIentry { +}; + +using configKeyCallback = std::function; + +struct GPUDisplayFrontend::configKey { + uint32_t configRTTIIndex = std::numeric_limits::max(); // Index in rttiEntries, KEY_MAP_INDEX + id for a key down mapping, INVALID_INDEX otherwise + configKeyCallback callback; +}; + +struct GPUDisplayFrontend::configRTTIInfo { + static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR}; // Config structs with RTTI, as members of GPUDisplay + std::vector rttiEntries; + std::vector configInfos; // SetInfo callback for an option, indexed like rttiEntries, called after the callback of a key bound to it + std::vector> rttiReverseId; // [configStructIndex][offset] -> index in rttiEntries, INVALID_INDEX if no member at that offset + static constexpr uint32_t INVALID_INDEX = std::numeric_limits::max(); + static constexpr uint32_t KEY_MAP_INDEX = 0x80000000; // configRTTIIndex of keys mapped to mKeyDownMap[configRTTIIndex - KEY_MAP_INDEX] + static bool isKeyMap(uint32_t index) { return index >= KEY_MAP_INDEX && index != INVALID_INDEX; } + std::vector configStructNames; + std::array configKeys; // By key code, special keys with SHIFT at code + SPECIAL_KEYS_SHIFTED + std::vector keyNames; + std::vector keyMapNames; // Names of the key down mapping ids (KEY_MAP_*) + std::vector> helpTextEntries; // Keys and text of each add_key_binding call + std::vector helpText; + struct guiElementCallbacks { + uint32_t rttiIndex; + std::function set; // GUI_LAMBDA setter, applies a value from the GUI + std::function get; // Current value of the option of the element + }; + std::vector guiElements; // By element id + std::vector guiLayout; // Frames of the add_gui_binding calls + static constexpr std::string_view possibleKeys = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#.:;>'^_"; // Bindable keys >= 32 (space), special keys < 32 are those with a name in keyNames + + template + static constexpr uint32_t configStructIndex() // Index of struct T in configStructs, compile-time error if not present + { + if constexpr (std::is_same_v>, T GPUDisplay::*>) { + return I; + } else { + return configStructIndex(); + } + } + template + static T& configStruct(GPUDisplay& display) + { + return display.*std::get()>(configStructs); + } + static char* configStructPtr(GPUDisplay& display, uint32_t index) // Runtime version of configStruct by index + { + char* retVal = nullptr; + uint32_t i = 0; + std::apply([&](auto... s) { ((retVal = i++ == index ? reinterpret_cast(&(display.*s)) : retVal), ...); }, configStructs); + return retVal; + } + std::optional valueString(GPUDisplay& display, uint32_t index) const // Current value of an option, printed with print_type as in qconfig.cxx, nullopt for unsupported types + { + const auto& e = rttiEntries[index]; + const char* ptr = configStructPtr(display, e.configStructIndex) + e.offset; + if (e.isVector || e.arraySize) { + return std::nullopt; + } else if (e.type == "bool") { + return qConfig::print_type(*(const bool*)ptr); + } else if (e.type == "float") { + return qConfig::print_type(*(const float*)ptr); + } else if (e.type == "int8_t") { + return qConfig::print_type(*(const int8_t*)ptr); + } else if (e.type == "uint8_t") { + return qConfig::print_type(*(const uint8_t*)ptr); + } else if (e.type == "int32_t" || e.type == "int") { + return qConfig::print_type(*(const int32_t*)ptr); + } else if (e.type == "uint32_t") { + return qConfig::print_type(*(const uint32_t*)ptr); + } + return std::nullopt; + } + template + uint32_t getRTTIIndex(GPUDisplay& display, M T::* member) // RTTI index of a direct member of a config struct + { + if constexpr (std::is_same_v) { + return INVALID_INDEX; + } else { + const T& obj = configStruct(display); + return rttiReverseId[configStructIndex()][(const char*)&(obj.*member) - (const char*)&obj]; + } + } + void buildHelpText() // Keys left-aligned, the ':' aligned after the longest keys + { + size_t width = 0; + for (const auto& e : helpTextEntries) { + width = std::max(width, e.first.size()); + } + helpText.clear(); + for (const auto& e : helpTextEntries) { + helpText.emplace_back(e.first + std::string(width - e.first.size(), ' ') + ": " + e.second); + } + } + std::string getKeyName(uint16_t key) const // Name of a special key (KEY_*), the character otherwise, the code if neither, SHIFT+ for shifted special keys + { + if (key >= 256) { + return "SHIFT+" + getKeyName(key - SPECIAL_KEYS_SHIFTED); + } + return key < keyNames.size() && !keyNames[key].empty() ? keyNames[key] : (key < 32 ? std::to_string(key) : std::string(1, (char)key)); + } +}; + +namespace +{ +#include "utils/qconfig_genrtti.inc" +} // namespace + +void GPUDisplay::checkAndApplyConfigChanges() +{ + const auto& [oldCfgH, oldCfgL, oldCfgR] = mOldCfg; + if (memcmp((void*)&oldCfgH, (void*)&mCfgH, sizeof(mCfgH)) != 0) { + mUpdateEventData = true; + } + if (mCfgL.propagateTracks != 0 && !mTracksArePropagated) { + mUpdateVertexLists = true; + } + if (memcmp((void*)&oldCfgL, (void*)&mCfgL, sizeof(mCfgL)) != 0 || memcmp((void*)&oldCfgR, (void*)&mCfgR, sizeof(mCfgR)) != 0) { + mUpdateDrawCommands = true; + } + if (oldCfgR.drawQualityMSAA != mCfgR.drawQualityMSAA || oldCfgR.drawQualityDownsampleFSAA != mCfgR.drawQualityDownsampleFSAA || oldCfgL.depthBuffer != mCfgL.depthBuffer || oldCfgR.screenshotScaleFactor != mCfgR.screenshotScaleFactor) { + mUpdateRenderPipeline = true; + } + + if (oldCfgR.drawQualityVSync != mCfgR.drawQualityVSync) { + mFrontend->SetVSync(mCfgR.drawQualityVSync); + mBackend->SetVSync(mCfgR.drawQualityVSync); + } + if (oldCfgR.fullScreen != mCfgR.fullScreen) { + mFrontend->requestFullScreen(mCfgR.fullScreen); + } + if (oldCfgR.maximized != mCfgR.maximized) { + mFrontend->requestMaximized(mCfgR.maximized); + } + if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { + mUpdateEventData = true; + } + if (oldCfgL.animationMode != mCfgL.animationMode) { + resetAnimation(); + } + if (oldCfgH.trackFilter != mCfgH.trackFilter) { + mUpdateTrackFilter = true; + } +} + +void GPUDisplay::HandleExternalGUIKey(int32_t key) +{ + // GPUError("key %d '%c'", key, (char) key); + + bool shifted = key >= 'A' && key <= 'Z'; + bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; + mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; + HandleKey(key); + mFrontend->mKeys[mFrontend->KEY_SHIFT] = oldShift; +} + +bool GPUDisplayFrontend::HasExternalGUISignals() const +{ + return mExternalGUIKeys.size(); +} + +void GPUDisplayFrontend::HandleExternalGUIKeys() +{ + std::lock_guard lock(mExternalGUIKeyMutex); + for (auto key : mExternalGUIKeys) { + mDisplay->HandleExternalGUIKey(key); + } + mExternalGUIKeys.clear(); +} + +template +void GPUDisplayFrontend::add_key_binding(Args&&... args) +{ + constexpr size_t nKeys = (0 + ... + (std::is_same_v, std::tuple> || std::is_same_v, std::tuple>)); + static_assert(nKeys >= 1 && (sizeof...(Args) == nKeys + 1 || sizeof...(Args) == nKeys + 2), "add_key_binding needs at least one key, the help text, and optionally the keys to show in the help text"); + auto argTuple = std::forward_as_tuple(args...); + std::string keys; + auto addKey = [&](auto& key) { + const uint16_t character = std::get<0>(key); + const std::string name = mConfigRTTI->getKeyName(character); + auto& entry = mConfigRTTI->configKeys[character]; + if (entry.callback || entry.configRTTIIndex != configRTTIInfo::INVALID_INDEX) { + throw std::runtime_error("Key [" + name + "] assigned twice in display frontend"); + } + if constexpr (std::is_same_v, std::tuple>) { + const uint8_t id = std::get<1>(key); + if (mKeyDownMap[id]) { + throw std::runtime_error("Key down mapping " + mConfigRTTI->keyMapNames[id] + " assigned twice in display frontend"); + } + mKeyDownMap[id] = character >= 'a' && character <= 'z' ? character + 'A' - 'a' : character; // mKeys stores letters in upper case + entry.configRTTIIndex = configRTTIInfo::KEY_MAP_INDEX + id; + } else { + entry.configRTTIIndex = std::get<1>(key); + entry.callback = std::move(std::get<2>(key)); + } + keys += (keys.empty() ? "[" : " [") + name + "]"; + }; + [&](std::index_sequence) { (addKey(std::get(argTuple)), ...); }(std::make_index_sequence()); + if constexpr (sizeof...(Args) == nKeys + 2) { + keys = std::get(argTuple); + } + mConfigRTTI->helpTextEntries.emplace_back(std::move(keys), std::get(argTuple)); +} + +struct guiType { // Element type and choices, GUI_CHECKBOX, GUI_DROPDOWN(...), etc. in GPUDisplayFrontendConfigQtGUI.inc + guiType(GPUDisplayGUIChannel::ElementType t, std::vector> c = {}) : type(t), choices(std::move(c)) {} + GPUDisplayGUIChannel::ElementType type; + std::vector> choices; +}; + +static std::vector> guiSliderValues(std::initializer_list values) // Choices of GUI_SLIDER_VALUES, named by their value +{ + auto name = [](const auto& x) -> std::string { + if constexpr (std::is_same_v, std::string>) { + return x; + } else { + return qConfig::print_type(x); + } + }; + std::vector> retVal; + for (const auto& v : values) { + retVal.emplace_back(std::visit(name, v), v); + } + return retVal; +} + +struct guiElement { // GUI_LAMBDA(...) + guiElement(std::string n, uint32_t i, guiType t, std::function s, std::function g) : name(std::move(n)), rttiIndex(i), type(std::move(t)), set(std::move(s)), get(std::move(g)) {} + std::string name; + uint32_t rttiIndex; + guiType type; + std::function set; + std::function get; +}; + +template +static void guiAssign(T& dst, const GPUDisplayGUIValue& value) // Default GUI_LAMBDA setter: assign the GUI value, converted to the type of the option +{ + std::visit([&](const auto& v) { + using V = std::remove_cvref_t; + if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { + dst = v; + } else { + dst = std::to_string(v); + } + } else if constexpr (std::is_same_v) { + dst = static_cast(std::stod(v)); // Throws for invalid text + } else { + dst = static_cast(v); + } + }, + value); +} + +template +static GPUDisplayGUIValue toGUIValue(const T& v) +{ + if constexpr (requires { v.load(); }) { // std::atomic in GPUDisplay::control + return toGUIValue(v.load()); + } else if constexpr (std::is_same_v) { + return v; + } else if constexpr (std::is_integral_v) { + return (int64_t)v; + } else { + static_assert(std::is_floating_point_v, "Unsupported option type for the Qt GUI"); + return (double)v; + } +} + +template +void GPUDisplayFrontend::add_gui_binding(Args&&... args) +{ + constexpr size_t nElements = (0 + ... + std::is_same_v, guiElement>); + constexpr size_t nHorizontal = (0 + ... + std::is_same_v, bool>); + static_assert(nElements >= 1 && nHorizontal <= 1 && sizeof...(Args) <= nElements + nHorizontal + 1, "add_gui_binding needs at least one GUI element, and optionally the description and the horizontal flag"); + GPUDisplayGUIChannel::Frame frame; + auto addElement = [&](auto& element) { + using Type = GPUDisplayGUIChannel::ElementType; + const uint32_t id = mConfigRTTI->guiElements.size(); + auto& el = frame.elements.emplace_back(GPUDisplayGUIChannel::Element{.name = element.name, .rttiIndex = element.rttiIndex, .type = element.type.type, .id = id, .isInteger = std::holds_alternative(element.get()), .choices = std::move(element.type.choices)}); + if (el.type == Type::SLIDER) { + if (element.rttiIndex >= mConfigRTTI->rttiEntries.size()) { + throw std::runtime_error("GUI slider " + element.name + " needs an option with RTTI for min and max values"); + } + const auto& e = mConfigRTTI->rttiEntries[element.rttiIndex]; + if (e.min.empty() || e.max.empty()) { + throw std::runtime_error("GUI slider " + element.name + ": option " + e.name + " needs min and max values"); + } + el.min = std::stod(e.min); + el.max = std::stod(e.max); + } + if (el.type == Type::TEXTBOX && element.rttiIndex < mConfigRTTI->rttiEntries.size() && !mConfigRTTI->rttiEntries[element.rttiIndex].min.empty() && !mConfigRTTI->rttiEntries[element.rttiIndex].max.empty()) { + el.min = std::stod(mConfigRTTI->rttiEntries[element.rttiIndex].min); + el.max = std::stod(mConfigRTTI->rttiEntries[element.rttiIndex].max); + el.hasRange = true; + } + if ((el.type == Type::RADIO_BUTTONS || el.type == Type::DROPDOWN) ? el.choices.empty() : (!el.choices.empty() && el.type != Type::SLIDER)) { + throw std::runtime_error("GUI element " + element.name + ": choices are needed for radio buttons and dropdown menus, and only allowed for them and sliders"); + } + mConfigRTTI->guiElements.emplace_back(configRTTIInfo::guiElementCallbacks{element.rttiIndex, std::move(element.set), std::move(element.get)}); + }; + auto addArg = [&](auto& arg) { + if constexpr (std::is_same_v, guiElement>) { + addElement(arg); + } else if constexpr (std::is_same_v, bool>) { + frame.horizontal = arg; + } else { + frame.description = arg; + } + }; + (addArg(args), ...); + mConfigRTTI->guiLayout.emplace_back(std::move(frame)); +} + +void GPUDisplayFrontend::setGUILayout(GPUDisplayGUIChannel& channel) const { channel.layout = mConfigRTTI->guiLayout; } + +std::vector GPUDisplayFrontend::getGUIValues() +{ + std::vector retVal; + retVal.reserve(mConfigRTTI->guiElements.size()); + for (const auto& e : mConfigRTTI->guiElements) { + retVal.emplace_back(e.get()); + } + return retVal; +} + +void GPUDisplayFrontend::applyGUIValues(const std::vector>& values) +{ + for (const auto& [id, value] : values) { + if (id >= mConfigRTTI->guiElements.size()) { + continue; + } + const auto& e = mConfigRTTI->guiElements[id]; + try { + e.set(value); + } catch (const std::exception& ex) { + GPUError("Invalid value from the GUI for element %u: %s", id, ex.what()); + continue; + } + if (mDisplay->mCfgC.mGuiPrintInfo && e.rttiIndex < mConfigRTTI->configInfos.size()) { + showConfigInfo(e.rttiIndex); + } + } +} + +const std::vector& GPUDisplayFrontend::getHelpText() const { return mConfigRTTI->helpText; } + +void GPUDisplayFrontend::listConfig() +{ + const auto& rtti = *mConfigRTTI; + auto keyOrder = [](uint16_t k) { return std::pair(k >= '0' && k <= '9' ? 0 : k >= 'a' && k <= 'z' ? 1 + : k >= 'A' && k <= 'Z' ? 2 + : k >= 256 ? 5 + : k > 32 ? 3 + : 4, + k); }; // 0-9, a-z, A-Z, other characters, special keys, special keys with SHIFT + std::vector boundKeys, availableKeys; + for (uint32_t k = 1; k < rtti.configKeys.size(); k++) { + const uint32_t base = k >= 256 ? k - SPECIAL_KEYS_SHIFTED : k; // A shifted special key is possible if the special key is, SHIFT itself is the modifier + const bool possible = base != KEY_SHIFT && ((base < N_SPECIAL_KEYS && !rtti.keyNames[base].empty()) || (k < 256 && rtti.possibleKeys.find((char)k) != std::string_view::npos)); + if (rtti.configKeys[k].callback || rtti.isKeyMap(rtti.configKeys[k].configRTTIIndex)) { + boundKeys.emplace_back(k); + } else if (possible) { + availableKeys.emplace_back(k); + } + } + std::sort(boundKeys.begin(), boundKeys.end(), [&](uint16_t a, uint16_t b) { return keyOrder(a) < keyOrder(b); }); + std::sort(availableKeys.begin(), availableKeys.end(), [&](uint16_t a, uint16_t b) { return keyOrder(a) < keyOrder(b); }); + + GPUInfo("Display config options with RTTI (struct, option, type, in Qt GUI, keys):"); + uint32_t nOptionsWithKey = 0, nOptionsInGUI = 0; + for (uint32_t i = 0; i < rtti.rttiEntries.size(); i++) { + const auto& e = rtti.rttiEntries[i]; + std::string keys; + for (uint16_t k : boundKeys) { + if (rtti.configKeys[k].configRTTIIndex == i) { + keys += (keys.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + } + } + const bool inGUI = std::any_of(rtti.guiElements.begin(), rtti.guiElements.end(), [i](const auto& g) { return g.rttiIndex == i; }); + nOptionsWithKey += !keys.empty(); + nOptionsInGUI += inGUI; + GPUInfo(" %-28s %-32s %-12s %-3s %s", e.configStruct.c_str(), e.name.c_str(), e.type.c_str(), inGUI ? "GUI" : "---", keys.c_str()); + } + GPUInfo(""); + GPUInfo("Bound keys (key, struct, option):"); + std::vector mappedKeys(KEY_MAP_MAX); + int32_t keyWidth = 0; + for (uint16_t k : boundKeys) { + keyWidth = std::max(keyWidth, rtti.getKeyName(k).size() + 2); + } + for (uint16_t k : boundKeys) { + const uint32_t index = rtti.configKeys[k].configRTTIIndex; + if (rtti.isKeyMap(index)) { + mappedKeys[index - configRTTIInfo::KEY_MAP_INDEX] = "[" + rtti.getKeyName(k) + "]"; + GPUInfo(" %-*s %-28s %s", keyWidth, ("[" + rtti.getKeyName(k) + "]").c_str(), "(key down map)", rtti.keyMapNames[index - configRTTIInfo::KEY_MAP_INDEX].c_str()); + } else { + const bool hasRTTI = index != configRTTIInfo::INVALID_INDEX; + GPUInfo(" %-*s %-28s %s", keyWidth, ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); + } + } + GPUInfo(""); + GPUInfo("Key down mappings (id, name, key):"); + for (uint32_t i = 0; i < KEY_MAP_MAX; i++) { + GPUInfo(" %2u %-28s %s", i, rtti.keyMapNames[i].c_str(), mappedKeys[i].c_str()); + } + GPUInfo(""); + std::string available; + for (uint16_t k : availableKeys) { + available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + } + GPUInfo("Available keys: %s", available.c_str()); + GPUInfo("Help text:"); + for (const auto& line : rtti.helpText) { + GPUInfo(" %s", line.c_str()); + } + GPUInfo(""); + GPUInfo("%zu options with RTTI, %u of them with a key, %u in the Qt GUI, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, nOptionsInGUI, boundKeys.size(), availableKeys.size()); +} + +#include "GPUDisplayFrontendConfigKeys.inc" +#include "GPUDisplayFrontendConfigInfo.inc" +#include "GPUDisplayFrontendConfigQtGUI.inc" + +void GPUDisplay::buildConfig() +{ + auto& rtti = mFrontend->mConfigRTTI; + rtti = {new GPUDisplayFrontend::configRTTIInfo, [](GPUDisplayFrontend::configRTTIInfo* p) { delete p; }}; + + rtti->keyNames.resize(rtti->configKeys.size()); + rtti->keyMapNames.resize(GPUDisplayFrontend::KEY_MAP_MAX + 1); // Including MAX +#define GPUDisplayFrontendAddKey(code, key) rtti->keyNames[code] = #key +#define GPUDisplayFrontendKeyDownMap(code, key) rtti->keyMapNames[code] = #key +#include "frontend/GPUDisplayFrontendKeys.h" + + std::tie(rtti->rttiEntries, rtti->configStructNames) = std::apply([this](auto... s) { return qConfigGenRTTI*s)>...>(); }, rtti->configStructs); + std::apply([&](auto... s) { (rtti->rttiReverseId.emplace_back(sizeof(this->*s), GPUDisplayFrontend::configRTTIInfo::INVALID_INDEX), ...); }, rtti->configStructs); + for (uint32_t i = 0; i < rtti->rttiEntries.size(); i++) { + const auto& entry = rtti->rttiEntries[i]; + rtti->rttiReverseId[entry.configStructIndex][entry.offset] = i; + } + + buildConfigKeys(); + + rtti->configInfos.resize(rtti->rttiEntries.size()); + for (uint32_t i = 0; i < rtti->rttiEntries.size(); i++) { // Info from the qConfig message, with the value as %s as in qconfig.cxx + const auto& e = rtti->rttiEntries[i]; + if (e.message.empty()) { + continue; + } + if (!rtti->valueString(*this, i)) { + GPUError("No info message support for type %s of %s", e.type.c_str(), e.name.c_str()); + continue; + } + rtti->configInfos[i] = [this, i, message = e.message]() { + char buffer[256]; + const int32_t n = snprintf(buffer, sizeof(buffer), message.c_str(), mFrontend->mConfigRTTI->valueString(*this, i)->c_str()); + if (n < 0 || n >= (int32_t)sizeof(buffer)) { + GPUError("Info message too long: %s", message.c_str()); + return; + } + SetInfo("%s", buffer); + }; + } + buildConfigInfo(); + buildConfigQtGUI(); + rtti->buildHelpText(); +} + +bool GPUDisplayFrontend::processKeyLambda(uint32_t key) +{ + if (key < N_SPECIAL_KEYS && mKeys[KEY_SHIFT]) { + key += SPECIAL_KEYS_SHIFTED; + } + if (key < mConfigRTTI->configKeys.size() && mConfigRTTI->configKeys[key].callback) { + mConfigRTTI->configKeys[key].callback(); + if (const uint32_t index = mConfigRTTI->configKeys[key].configRTTIIndex; index < mConfigRTTI->configInfos.size()) { + showConfigInfo(index); + } + return true; + } + return false; +} + +void GPUDisplayFrontend::showConfigInfo(uint32_t index) +{ + if (mConfigRTTI->configInfos[index]) { + mConfigRTTI->configInfos[index](); + } else if (const auto value = mConfigRTTI->valueString(*mDisplay, index)) { // Generic info for options without info callback + const auto& e = mConfigRTTI->rttiEntries[index]; + mDisplay->SetInfo("%s.%s changed to %s", mConfigRTTI->configStructNames[e.configStructIndex].c_str(), e.name.c_str(), value->c_str()); + } +} + +void GPUDisplay::HandleKey(uint8_t key) +{ + if (key == mFrontend->KEY_ALT) { + mFrontend->mKeys[mFrontend->KEY_CTRL] = false; // Release CTRL with alt, to avoid orienting along y automatically! + } else if (mFrontend->processKeyLambda(key)) { + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc new file mode 100644 index 0000000000000..0cbc8a4cdd571 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -0,0 +1,122 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigInfo.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGINFO_INC +#define GPUDISPLAYFRONTENDCONFIGINFO_INC + +#define INFO_LAMBDA(obj, value, ...) std::pair(mFrontend->mConfigRTTI->getRTTIIndex(*this, &std::remove_cvref_t::value), configKeyCallback([=, this]() { [[maybe_unused]] auto& val = obj.value; __VA_ARGS__ })) // Info printed after the callback of a key bound to the option, same val as in KEY_LAMBDA, local helpers captured by copy + +void GPUDisplay::buildConfigInfo() +{ + auto addInfo = [this](std::pair&& info) { mFrontend->mConfigRTTI->configInfos[info.first] = std::move(info.second); }; + addInfo(INFO_LAMBDA(mCfgL, colorClusters, SetInfo("Color coding for seed / trrack attachmend %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, drawSector, { + if (val == -1) { + SetInfo("Showing all sectors", 1); + } else { + SetInfo("Showing sector %d", val); + } + })); + addInfo(INFO_LAMBDA(mCfgL, drawRelatedSectors, SetInfo("Drawing of related sectors %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, xAdd, SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd);)); + addInfo(INFO_LAMBDA(mCfgL, animationMode, { + if (val == 6) { + SetInfo("Animation mode %d - Centered on origin", val); + } else { + SetInfo("Animation mode %d - Position: %s, Direction: %s", val, (val & 2) ? "Spherical (spherical rotation)" : ((val & 4) ? "Spherical (Euler angles)" : "Cartesian"), (val & 1) ? "Euler angles" : "Quaternion"); + } + })); + addInfo(INFO_LAMBDA(mCfgL, drawGrid, SetInfo("Fast Cluster Search Grid %s", val ? "shown" : "hidden");)); + addInfo(INFO_LAMBDA(mCfgH, projectXY, SetInfo("Projection onto xy plane %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, excludeClusters, SetInfo(val ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", val);)); + addInfo(INFO_LAMBDA(mCfgH, hideRejectedTracks, SetInfo("Rejected tracks are %s", val ? "hidden" : "shown");)); + addInfo(INFO_LAMBDA(mCfgH, markClusters, { + SetInfo("Cluster flag highlight mask set to %d (%s)", val, val == 0 ? "off" : (val == 1 ? "split pad" : (val == 2 ? "split time" : (val == 4 ? "edge" : (val == 8 ? "singlePad" : (val == 0x10 ? "reject distance" : "reject error")))))); + })); + addInfo(INFO_LAMBDA(mCfgH, markFakeClusters, SetInfo("Marking fake clusters: %s", val ? "on" : "off");)); + addInfo(INFO_LAMBDA(mCfgH, markAdjacentClusters, { + if (val == 33) { + SetInfo("Marking protected clusters (%d)", val); + } else if (val == 32) { + SetInfo("Marking removable clusters (%d)", val); + } else { + SetInfo("Marking adjacent clusters (%d): rejected %s, tube %s, looper leg %s, low Pt %s, high incl %s", val, (val & 1) ? "yes" : " no", (val & 2) ? "yes" : " no", (val & 4) ? "yes" : " no", (val & 8) ? "yes" : " no", (val & 16) ? "yes" : " no"); + } + })); + addInfo(INFO_LAMBDA(mCfgL, showCollision, { + if (val == -1) { + SetInfo("Showing all collisions", 1); + } else { + SetInfo("Showing collision %d / %d", val, mNCollissions); + } + })); + addInfo(INFO_LAMBDA(mCfgL, colorCollisions, SetInfo("Color coding of collisions %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, hideRejectedClusters, SetInfo("Rejected clusters are %s", val ? "hidden" : "shown");)); + addInfo(INFO_LAMBDA(mCfgH, separateExtrapolatedTracks, SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", val ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, trackFilter, SetInfo("Track filter: %s", val == 0 ? "None" : mConfig.filterMacros[val - 1].c_str());)); + addInfo(INFO_LAMBDA(mCfgL, propagateTracks, { + const char* infoText[] = {"Hits connected", "Hits connected and propagated to vertex", "Reconstructed track propagated inwards and outwards", "Monte Carlo track"}; + SetInfo("Display of propagated tracks: %s", infoText[val]); + })); + addInfo(INFO_LAMBDA(mCfgH, propagateLoopers, SetInfo("Propagation of loopers %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, screenshotScaleFactor, SetInfo("Screenshot scaling factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgL, smoothPoints, SetInfo("Smoothing of points %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, smoothLines, SetInfo("Smoothing of lines %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, depthBuffer, SetInfo("Depth buffer (z-buffer) %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, drawQualityMSAA, SetInfo("Multisampling anti-aliasing factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgR, drawQualityDownsampleFSAA, SetInfo("Downsampling anti-aliasing factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgR, drawQualityVSync, SetInfo("VSync: %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, fpsLimit, { + if (val == GPUDisplayBackend::FPS_LIMIT_RATE) { + SetInfo("FPS limit: %d FPS", mCfgR.fpsRate); + } else if (val == GPUDisplayBackend::FPS_LIMIT_VSYNC) { + char rate[32] = "unknown rate"; + if (mFrontend->getRefreshRate() > 0.f) { + snprintf(rate, sizeof(rate), "%.1f Hz", mFrontend->getRefreshRate()); + } + const int32_t mode = mBackend->fpsLimitMode(); + if (mode == GPUDisplayBackend::FPS_LIMIT_NONE) { + SetInfo("FPS limit: refresh rate (%s) - VSync disabled, no limit", rate); + } else if (mode == GPUDisplayBackend::FPS_LIMIT_RATE) { + SetInfo("FPS limit: refresh rate (%s) - not supported by the backend, fallback to %d FPS", rate, mCfgR.fpsRate); + } else { + SetInfo("FPS limit: refresh rate (%s)", rate); + } + } else { + SetInfo("FPS limit: none"); + } + })); + addInfo(INFO_LAMBDA(mCfgR, fullScreen, SetInfo("Toggling full screen (%d)", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgR, maximized, SetInfo("Toggling Maximized window (%d)", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgR, fov, SetInfo("Set FOV to %f", val);)); + addInfo(INFO_LAMBDA(mCfgR, useGLIndirectDraw, SetInfo("OpenGL Indirect Draw %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, drawTPC, SetInfo("Showing TPC Clusters: %d", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgL, drawTRD, SetInfo("Showing TRD Tracklets: %d", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgL, drawTOF, SetInfo("Showing TOF Hits: %d", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgL, drawITS, SetInfo("Showing ITS Clusters: %d", (int32_t)val);)); + auto trackFilterMaskInfo = [this]() { SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); }; + addInfo(INFO_LAMBDA(mCfgH, drawTPCTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawTRDTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawTOFTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawITSTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawTracksAndFilter, SetInfo("Track filter: %s", val ? "AND" : "OR");)); + addInfo(INFO_LAMBDA(mCfgR, camLookOrigin, SetInfo("Camera locked on origin: %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, camYUp, SetInfo("Camera locked on y-axis facing upwards: %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, cameraMode, { + const char* modeText[] = {"Descent (free movement)", "Focus locked on origin (y-axis forced upwards)", "Spectator (y-axis forced upwards)", "Focus locked on origin (with free rotation)"}; + SetInfo("Camera mode %d: %s", val, modeText[val]); + })); +} + +#endif // GPUDISPLAYFRONTENDCONFIGINFO_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc new file mode 100644 index 0000000000000..8cb8fecec59f2 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -0,0 +1,410 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigKeys.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGKEYS_INC +#define GPUDISPLAYFRONTENDCONFIGKEYS_INC + +#define KEY_DOWN_MAP(character, id) std::tuple((uint8_t)(character), (uint8_t)GPUDisplayFrontend::KEY_MAP_##id) // Key queried as held key: GPUDisplay uses mKeys[mKeyDownMap[KEY_MAP_...]] +#define KEY_LAMBDA(character, obj, value, ...) std::tuple((uint16_t)(character), mFrontend->mConfigRTTI->getRTTIIndex(*this, &std::remove_cvref_t::value), configKeyCallback([this]() { [[maybe_unused]] auto& val = obj.value; __VA_ARGS__ })) + +void GPUDisplay::buildConfigKeys() +{ + mFrontend->mConfigRTTI->helpTextEntries.emplace_back("[MOUSE 1] / [MOUSE 2] / [MOUSE 1+2]", "Look around / Strafe camera / Zoom / Rotate"); + mFrontend->mConfigRTTI->helpTextEntries.emplace_back("[SHIFT]", "Slow Zoom / Move / Rotate"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP('w', FORWARD), + KEY_DOWN_MAP('s', BACKWARD), + KEY_DOWN_MAP('a', STRAFE_LEFT), + KEY_DOWN_MAP('d', STRAFE_RIGHT), // + "Zoom / Strafe Left and Right"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_PAGEUP, STRAFE_UP), + KEY_DOWN_MAP(mFrontend->KEY_PAGEDOWN, STRAFE_DOWN), + KEY_DOWN_MAP('e', ROTATE_LEFT), + KEY_DOWN_MAP('f', ROTATE_RIGHT), // + "Strafe up / down / Rotate left / right"); + + mFrontend->add_key_binding( + KEY_LAMBDA(mFrontend->KEY_ESCAPE, mCfgC, mDisplayControl, val.store(2); SetInfo("Exiting", 1);), + KEY_LAMBDA('n', mCfgC, mDisplayControl, val.store(1);), + KEY_LAMBDA('r', mCfgC, mResetScene, val.store(true); SetInfo("View reset", 1);), // + KEY_LAMBDA('q', mCfgC, dummy, { + if (mFrontend->isGUIRunning()) { + SetInfo("Stopping GUI", 1); + mFrontend->stopGUI(); + } else { + SetInfo("Starting GUI", 1); + mFrontend->startGUI(); + } + }), // + "Quit / Next event / Reset Display Settings / Start / Stop Qt GUI"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_LEFT, ROTATE_YAW_LEFT), + KEY_DOWN_MAP(mFrontend->KEY_RIGHT, ROTATE_YAW_RIGHT), + KEY_DOWN_MAP(mFrontend->KEY_UP, ROTATE_PITCH_UP), + KEY_DOWN_MAP(mFrontend->KEY_DOWN, ROTATE_PITCH_DOWN), // + "Turn camera left / right / up / down"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_ALT, FOCUS_ORIGIN), + KEY_DOWN_MAP(mFrontend->KEY_CTRL, FOCUS_ORIENT_UP), // + KEY_LAMBDA(mFrontend->KEY_ALT + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgR, camLookOrigin, { + val ^= 1; + mCfgR.cameraMode = val + 2 * mCfgR.camYUp; + }), + KEY_LAMBDA(mFrontend->KEY_CTRL + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgR, camYUp, { + val ^= 1; + mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * val; + }), + KEY_LAMBDA(mFrontend->KEY_ENTER, mCfgR, cameraMode, { + val++; + if (val == 4) { + val = 0; + } + mCfgR.camLookOrigin = val & 1; + mCfgR.camYUp = val & 2; + }), + "Focus camera on origin / orient y-axis upwards (combine with [SHIFT] to lock) / Cycle through modes", // + "[ALT] / [CTRL] / [ENTER]"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_RCTRL, ROTATE_MODEL), + KEY_DOWN_MAP(mFrontend->KEY_RALT, ROTATE_AROUND_Z), // + "Rotate model instead of camera / rotate TPC around beamline"); + + mFrontend->add_key_binding( + KEY_LAMBDA('F', mCfgR, fullScreen, val ^= 1;), // + KEY_LAMBDA('_', mCfgR, maximized, val ^= 1;), // + KEY_LAMBDA('b', mCfgR, fov, { + if ((val += 5) > 175) { + val = 5; + } + }), + KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true;), // + "Switch Full Screen / Maximized window / Change FOV (field of view) / Enable / disable GL indirect draw"); + + mFrontend->add_key_binding( + KEY_LAMBDA('S', mCfgL, smoothPoints, val ^= true;), // + KEY_LAMBDA('A', mCfgL, smoothLines, val ^= true;), // + KEY_LAMBDA('D', mCfgL, depthBuffer, { + val ^= true; + mBackend->setDepthBuffer(); + }), // + "Enable or disable smoothing of points / smoothing of lines / depth buffer"); + + mFrontend->add_key_binding( + KEY_LAMBDA('W', mCfgR, drawQualityMSAA, { + val *= 2; + if (val < 2) { + val = 2; + } + if (val > 16 || val > mBackend->getMaxMSAA()) { + val = 0; + } + }), + KEY_LAMBDA('U', mCfgR, drawQualityDownsampleFSAA, { + val++; + if (val == 1) { + val = 2; + } + if (val == 5) { + val = 0; + } + }), + KEY_LAMBDA('V', mCfgR, drawQualityVSync, val ^= true;), // + KEY_LAMBDA('R', mCfgR, fpsLimit, val = (val + 1) % 3;), // + "Toggle anti-aliasing (MSAA at raster level / change downsampling FSAA factor) / toggle VSync / FPS limit (none, refresh rate, fpsRate)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('#', mCfgL, invertColors, val ^= 1;), + KEY_LAMBDA('g', mCfgL, drawGrid, val ^= 1;), + KEY_LAMBDA('i', mCfgH, projectXY, val ^= 1;), // + "Invert colors / Draw Grid / Project onto XY-plane"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP('+', INCREASE_WIDTH), + KEY_DOWN_MAP('-', DECREASE_WIDTH), // + "Increase / decrease point size (Hold SHIFT for lines)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('l', mCfgL, drawSector, { + if (val >= (mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1))) { + val = -1; + } else { + val++; + } + }), + KEY_LAMBDA('k', mCfgL, drawSector, { + if (val <= -1) { + val = mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1); + } else { + val--; + } + }), + KEY_LAMBDA('J', mCfgL, drawRelatedSectors, val ^= 1;), // + KEY_LAMBDA(';', mCfgH, xAdd, { + mCfgH.xAdd += 60; + mCfgH.zAdd += 60; + }), + KEY_LAMBDA(':', mCfgH, xAdd, { + mCfgH.xAdd -= 60; + mCfgH.zAdd -= 60; + if (mCfgH.zAdd < 0 || mCfgH.xAdd < 0) { + mCfgH.zAdd = mCfgH.xAdd = 0; + } + }), // + "Draw single sector (next / previous sector), draw related sectors (same plane in phi) / Show splitting of TPC in sectors by extruding volume, [:] resets"); + + mFrontend->add_key_binding( + KEY_LAMBDA('x', mCfgL, excludeClusters, val = (val + 1) % 3;), + KEY_LAMBDA('.', mCfgH, hideRejectedTracks, val ^= 1;), // + "Exclude Clusters used in the tracking steps enabled for visualization ([1]-[8]) / Exclude rejected tracks"); + + mFrontend->add_key_binding( + KEY_LAMBDA('c', mCfgH, markClusters, { + if (val == 0) { + val = 1; + } else if (val >= 0x20) { + val = 0; + } else { + val <<= 1; + } + }), + KEY_LAMBDA('z', mCfgH, markFakeClusters, val ^= 1;), // + KEY_LAMBDA('B', mCfgH, markAdjacentClusters, { + val++; + if (val == 5) { + val = 7; + } + if (val == 9) { + val = 15; + } + if (val == 17) { + val = 31; + } + if (val == 34) { + val = 0; + } + }), // + "Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20 / Mark fake attached clusters / Mark clusters attached as adjacent"); + + mFrontend->add_key_binding( + KEY_LAMBDA('v', mCfgH, hideRejectedClusters, val ^= 1;), // + KEY_LAMBDA('j', mCfgH, separateExtrapolatedTracks, { + if (val) { + mCfgH.splitCETracks ^= 1; + } + val ^= 1; + }), + KEY_LAMBDA('u', mCfgH, trackFilter, val = (val + 1) % (mConfig.filterMacros.size() + 1);), // + "Hide rejected clusters from tracks / Show tracks segments propagated to adjacent sector in different color / splt CE tracks / Cycle through track filter"); + + mFrontend->add_key_binding( + KEY_LAMBDA('E', mCfgL, propagateTracks, { + val += 1; + if (val == 4) { + val = 0; + } + }), + KEY_LAMBDA('G', mCfgH, propagateLoopers, val ^= 1;), // + "Extrapolate tracks / loopers"); + + mFrontend->add_key_binding( + KEY_LAMBDA('L', mCfgL, showCollision, { + if (val >= mNCollissions - 1) { + val = -1; + } else { + val++; + } + }), + KEY_LAMBDA('K', mCfgL, showCollision, { + if (val <= -1) { + val = mNCollissions - 1; + } else { + val--; + } + }), + KEY_LAMBDA('C', mCfgL, colorCollisions, val ^= 1;), // + "Draw single collisions (next / previous) / Colorcode clusters of different collisions"); + + mFrontend->add_key_binding( + KEY_LAMBDA('t', mCfgC, mRequestScreenshot, { + static int32_t nScreenshot = 1; + char fname[32]; + snprintf(fname, 32, "screenshot%d.bmp", nScreenshot++); + val = true; + mScreenshotFile = fname; + SetInfo("Taking screenshot (%s)", fname); + }), + KEY_LAMBDA('T', mCfgC, mAnimateScreenshot, val ^= 1; SetInfo("Writing screenshots from animation set to %d", mCfgC.mAnimateScreenshot);), // + KEY_LAMBDA('Z', mCfgR, screenshotScaleFactor, { + val += 1; + if (val == 5) { + val = 1; + } + }), // + "Take Screenshot / Record Animation to pictures / Change screenshot resolution (scaling factor)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('y', mCfgC, dummy, { + if (mAnimateVectors[0].size() > 1) { + if (mCfgC.mAnimateScreenshot) { + mAnimationExport++; + } + startAnimation(); + SetInfo("Starting Animation", 1); + } else { + SetInfo("Insufficient Animation points to start Animation", 1); + } + }), + KEY_LAMBDA('Y', mCfgC, dummy, setAnimationPoint(); SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimateVectors[0].size(), mAnimateVectors[0].back());), // + KEY_LAMBDA('\'', mCfgC, dummy, removeAnimationPoint(); SetInfo("Removed Animation point", 1);), // + KEY_LAMBDA('X', mCfgC, dummy, resetAnimation(); SetInfo("Reset Animation points", 1);), // + KEY_LAMBDA('M', mCfgL, animationMode, { + val++; + if (val == 7) { + val = 0; + } + }), // + "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('>', mCfgC, mAnimationChangeConfig, val ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled");), + KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // + "Toggle config interpolation during Animation / change Animation interval (via movement)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('o', mCfgC, dummy, { + FILE* ftmp = fopen("glpos.tmp", "w+b"); + if (ftmp) { + int32_t retval = fwrite(&mViewMatrix, sizeof(mViewMatrix), 1, ftmp); + if (retval != 1) { + GPUError("Error writing position to file"); + } else { + GPUInfo("Position stored to file"); + } + fclose(ftmp); + } else { + GPUError("Error opening file"); + } + SetInfo("Camera position stored to file", 1); + }), + KEY_LAMBDA('p', mCfgC, dummy, { + FILE* ftmp = fopen("glpos.tmp", "rb"); + if (ftmp) { + int32_t retval = fread(&mViewMatrix, 1, sizeof(mViewMatrix), ftmp); + if (retval == sizeof(mViewMatrix)) { + GPUInfo("Position read from file"); + } else { + GPUError("Error reading position from file"); + } + fclose(ftmp); + } else { + GPUError("Error opening file"); + } + SetInfo("Camera position loaded from file", 1); + }), + KEY_LAMBDA('O', mCfgC, dummy, { + FILE* ftmp = fopen("glanimation.tmp", "w+b"); + if (ftmp) { + fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp); + int32_t size = mAnimateVectors[0].size(); + fwrite(&size, sizeof(size), 1, ftmp); + for (int32_t i = 0; i < 9; i++) { + fwrite(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); + } + fwrite(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); + fclose(ftmp); + } else { + GPUError("Error opening file"); + } + SetInfo("Animation path stored to file %s", "glanimation.tmp"); + }), + KEY_LAMBDA('P', mCfgC, dummy, { + FILE* ftmp = fopen("glanimation.tmp", "rb"); + if (ftmp) { + int32_t retval = fread(&mCfgL, sizeof(mCfgL), 1, ftmp); + int32_t size; + retval += fread(&size, sizeof(size), 1, ftmp); + for (int32_t i = 0; i < 9; i++) { + mAnimateVectors[i].resize(size); + retval += fread(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); + } + mAnimateConfig.resize(size); + retval += fread(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); + (void)retval; // disable unused warning + fclose(ftmp); + updateConfig(); + } else { + GPUError("Error opening file"); + } + SetInfo("Animation path loaded from file %s", "glanimation.tmp"); + }), // + "Save / restore current camera position / Animation path"); + + mFrontend->add_key_binding( + KEY_LAMBDA('1', mCfgL, drawClusters, val ^= 1;), + KEY_LAMBDA('2', mCfgL, drawInitLinks, val ^= 1;), + KEY_LAMBDA('3', mCfgL, drawLinks, val ^= 1;), + KEY_LAMBDA('4', mCfgL, drawSeeds, val ^= 1;), + KEY_LAMBDA('5', mCfgL, drawTracklets, val ^= 1;), + KEY_LAMBDA('6', mCfgL, drawTracks, val ^= 1;), + KEY_LAMBDA('7', mCfgL, drawExtrapolatedTracks, val ^= 1;), + KEY_LAMBDA('8', mCfgL, drawFinal, val ^= 1;), + KEY_LAMBDA('N', mCfgL, colorClusters, val ^= 1;), // + "Enable display of clusters, preseeds, seeds, starthits, tracklets, tracks, extrapolated tracks, merged tracks / Show assigned clusters in colors", + "[1] ... [8] / [N]"); + + mFrontend->add_key_binding( + KEY_LAMBDA(mFrontend->KEY_F1, mCfgL, drawTPC, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F2, mCfgL, drawTRD, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F3, mCfgL, drawTOF, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F4, mCfgL, drawITS, val ^= 1;), // + "Enable / disable drawing of TPC / TRD / TOF / ITS"); + + mFrontend->add_key_binding( + KEY_LAMBDA(mFrontend->KEY_F1 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTPCTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F2 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTRDTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F3 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTOFTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F4 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawITSTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F12 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTracksAndFilter, val ^= 1;), // + "Enable / disable track detector filter / SHIFT F12: Switch track detector filter between AND and OR mode", + "[SHIFT] + [F1] to [F4] / [F12]"); + + mFrontend->add_key_binding( + KEY_LAMBDA('h', mCfgC, mPrintInfoTextAlways, { + if (val) { + val = false; + SetInfo("Showing help text disabled", 1); + } else if (mInfoHelpTimer.IsRunning()) { + val = true; + mInfoHelpTimer.Reset(); + SetInfo("Showing help text until disabled", 1); + } else { + PrintHelp(); + SetInfo("Showing help text", 1); + } + }), + KEY_LAMBDA('H', mCfgC, mPrintInfoText, { + val += 1; + val &= 3; + SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled"); + }), // + "Print Help / Show info texts"); +} + +#endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc new file mode 100644 index 0000000000000..e9198c5621fbd --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc @@ -0,0 +1,92 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigQtGUI.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGQTGUI_INC +#define GPUDISPLAYFRONTENDCONFIGQTGUI_INC + +#define GUI_CHECKBOX guiType(GPUDisplayGUIChannel::ElementType::CHECKBOX) +#define GUI_TEXTBOX guiType(GPUDisplayGUIChannel::ElementType::TEXTBOX) +#define GUI_SLIDER guiType(GPUDisplayGUIChannel::ElementType::SLIDER) // Range from the min / max of the option +#define GUI_SLIDER_VALUES(...) guiType(GPUDisplayGUIChannel::ElementType::SLIDER, guiSliderValues({__VA_ARGS__})) // Slider with only these values +#define GUI_RADIO_BUTTONS(...) guiType(GPUDisplayGUIChannel::ElementType::RADIO_BUTTONS, {__VA_ARGS__}) // Choices {"name", value}, ... +#define GUI_DROPDOWN(...) guiType(GPUDisplayGUIChannel::ElementType::DROPDOWN, {__VA_ARGS__}) +#define GUI_BUTTON guiType(GPUDisplayGUIChannel::ElementType::BUTTON) // Body of GUI_LAMBDA runs when the button is clicked +// GUI element for an option, type one of the GUI_* above. The optional body sets the value from the GUI: val is a reference to obj.value, guiVal the GPUDisplayGUIValue from the GUI (int64_t, double, or std::string). Without body, guiVal is assigned to val. +#define GUI_LAMBDA(name, obj, value, type, ...) guiElement(std::string(name), mFrontend->mConfigRTTI->getRTTIIndex(*this, &std::remove_cvref_t::value), type, [this](const GPUDisplayGUIValue& guiVal) { [[maybe_unused]] auto& val = obj.value; __VA_OPT__(if constexpr (true) { __VA_ARGS__ } else) guiAssign(val, guiVal); }, [this]() { return toGUIValue(obj.value); }) + +void GPUDisplay::buildConfigQtGUI() +{ + mFrontend->add_gui_binding( + GUI_LAMBDA("Clusters", mCfgL, drawClusters, GUI_CHECKBOX), + GUI_LAMBDA("Initial links", mCfgL, drawInitLinks, GUI_CHECKBOX), + GUI_LAMBDA("Links", mCfgL, drawLinks, GUI_CHECKBOX), + GUI_LAMBDA("Seeds", mCfgL, drawSeeds, GUI_CHECKBOX), + GUI_LAMBDA("Tracklets", mCfgL, drawTracklets, GUI_CHECKBOX), + GUI_LAMBDA("Sector tracks", mCfgL, drawTracks, GUI_CHECKBOX), + GUI_LAMBDA("Extrapolated tracks", mCfgL, drawExtrapolatedTracks, GUI_CHECKBOX), + GUI_LAMBDA("Final tracks", mCfgL, drawFinal, GUI_CHECKBOX), + "Draw Types", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("TPC", mCfgL, drawTPC, GUI_CHECKBOX), + GUI_LAMBDA("TRD", mCfgL, drawTRD, GUI_CHECKBOX), + GUI_LAMBDA("TOF", mCfgL, drawTOF, GUI_CHECKBOX), + GUI_LAMBDA("ITS", mCfgL, drawITS, GUI_CHECKBOX), + "Detectors", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Look at origin", mCfgR, camLookOrigin, GUI_CHECKBOX, guiAssign(val, guiVal); mCfgR.cameraMode = val + 2 * mCfgR.camYUp;), + GUI_LAMBDA("Upward Orientation", mCfgR, camYUp, GUI_CHECKBOX, guiAssign(val, guiVal); mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * val;), + GUI_LAMBDA("Rotate Model", mCfgC, mRotateModel, GUI_CHECKBOX), + GUI_LAMBDA("Rotate around Beamline", mCfgC, mRotateAroundZ, GUI_CHECKBOX), + GUI_LAMBDA("Project onto XY", mCfgH, projectXY, GUI_CHECKBOX), + "Camera Options", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Line width", mCfgL, lineWidth, GUI_SLIDER), + GUI_LAMBDA("Point size", mCfgL, pointSize, GUI_SLIDER)); + + mFrontend->add_gui_binding( + GUI_LAMBDA("MSAA", mCfgR, drawQualityMSAA, GUI_SLIDER_VALUES(0, 2, 4, 8)), + GUI_LAMBDA("FSAA", mCfgR, drawQualityDownsampleFSAA, GUI_SLIDER_VALUES(0, 2, 3, 4)), + "Anti-Aliasing"); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Smooth points", mCfgL, smoothPoints, GUI_CHECKBOX), + GUI_LAMBDA("Smooth lines", mCfgL, smoothLines, GUI_CHECKBOX), + GUI_LAMBDA("Depth buffer", mCfgL, depthBuffer, GUI_CHECKBOX, guiAssign(val, guiVal); mBackend->setDepthBuffer();), + GUI_LAMBDA("VSync", mCfgR, drawQualityVSync, GUI_CHECKBOX), + GUI_LAMBDA("FPS limit", mCfgR, fpsLimit, GUI_DROPDOWN({"None", 0}, {"Refresh rate", 1}, {"fpsRate", 2})), + GUI_LAMBDA("fpsRate", mCfgR, fpsRate, GUI_TEXTBOX), + GUI_LAMBDA("GL Indirect Draw", mCfgR, useGLIndirectDraw, GUI_CHECKBOX), + "Draw Quality", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Invert Colors", mCfgL, invertColors, GUI_CHECKBOX), + GUI_LAMBDA("Draw Grid", mCfgL, drawGrid, GUI_CHECKBOX), + "Colors / Grid", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Next Event", mCfgC, mDisplayControl, GUI_BUTTON, val.store(1);), + GUI_LAMBDA("Exit", mCfgC, mDisplayControl, GUI_BUTTON, val.store(2);), + GUI_LAMBDA("Reset", mCfgC, mResetScene, GUI_BUTTON, val.store(true);), + GUI_LAMBDA("Reset GUI", mCfgC, mGuiScale, GUI_BUTTON, val = 1.f;), + GUI_LAMBDA("Close GUI", mCfgC, dummy, GUI_BUTTON, mFrontend->stopGUI();), + GUI_LAMBDA("InfoText", mCfgC, mGuiPrintInfo, GUI_CHECKBOX), + GUI_LAMBDA("HelpText", mCfgC, mPrintInfoTextAlways, GUI_CHECKBOX), + GUI_LAMBDA("StatusText", mCfgC, mPrintInfoText, GUI_CHECKBOX, val = std::get(guiVal) ? (val | 1) : (val & ~1);), // Bit 0: status line on screen (bit 1: in the console) + true); +} + +#endif // GPUDISPLAYFRONTENDCONFIGQTGUI_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index ac61ddacdd0f6..e9b861abd131c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -163,19 +163,16 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 if (handleKey < 32) { if (action == GLFW_PRESS) { me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = mods & GLFW_MOD_SHIFT; me->HandleKey(handleKey); } else if (action == GLFW_RELEASE) { me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; } } else if (handleKey < 256) { if (action == GLFW_PRESS) { me->mLastKeyDown = handleKey; } else if (action == GLFW_RELEASE) { - keyPress = (uint8_t)me->mKeyDownMap[handleKey]; + keyPress = (uint8_t)me->mGLFWKeyDownMap[handleKey]; me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; } } } @@ -188,9 +185,8 @@ void GPUDisplayFrontendGlfw::char_callback(GLFWwindow* window, uint32_t codepoin if (keyPress >= 'a' && keyPress <= 'z') { keyPress += 'A' - 'a'; } - me->mKeyDownMap[me->mLastKeyDown] = keyPress; + me->mGLFWKeyDownMap[me->mLastKeyDown] = keyPress; me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; me->HandleKey(codepoint); } } @@ -275,7 +271,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_MIN_VERSION_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_MIN_VERSION_MINOR); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 0); - glfwWindowHint(GLFW_OPENGL_PROFILE, mBackend->CoreProfile() ? GLFW_OPENGL_CORE_PROFILE : GLFW_OPENGL_COMPAT_PROFILE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); } glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Initial size scaled by the content scale on X11 / Win32 mWindow = glfwCreateWindow(INIT_WIDTH, INIT_HEIGHT, DISPLAY_WINDOW_NAME, nullptr, nullptr); @@ -326,8 +322,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() int32_t retVal = 0; while (!glfwWindowShouldClose(mWindow)) { - HandleExternalGUISignals(); - if (DrawGLScene()) { + if (HandleUpdatesAndDrawGLScene()) { fprintf(stderr, "Error drawing GL scene\n"); retVal = 1; break; // Cleanup below @@ -348,10 +343,11 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() ExitDisplay(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); glfwDestroyWindow(mWindow); glfwTerminate(); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return retVal; @@ -362,10 +358,6 @@ void GPUDisplayFrontendGlfw::StopDisplayA() glfwSetWindowShouldClose(mWindow, true); } -void GPUDisplayFrontendGlfw::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -} - void GPUDisplayFrontendGlfw::SwitchFullscreen(bool set) { GPUInfo("Setting Full Screen %d", (int32_t)set); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index f079c87852e13..fafde0ca89cc2 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -31,7 +31,6 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } float getRefreshRate() override; @@ -67,7 +66,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend int32_t mWindowY = 0; int32_t mWindowWidth = INIT_WIDTH; int32_t mWindowHeight = INIT_HEIGHT; - uint8_t mKeyDownMap[256] = {0}; + uint8_t mGLFWKeyDownMap[256] = {0}; uint8_t mLastKeyDown = 0; }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 368a86413194c..5e79e85b6cf84 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -20,15 +20,16 @@ #include #include #include +#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include #include +#include #endif #include using namespace o2::gpu; -static GPUDisplayFrontendGlut* me = nullptr; GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() { @@ -36,98 +37,83 @@ GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() mFrontendName = "GLUT"; } -void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, to get rid of the static me variable! +void GPUDisplayFrontendGlut::displayFunc() { - me->DrawGLScene(); + mRedisplayPending = false; + HandleUpdatesAndDrawGLScene(); glutSwapBuffers(); - me->waitForFPSLimit(); + waitForFPSLimit(); } void GPUDisplayFrontendGlut::glutLoopFunc() { - me->HandleExternalGUISignals(); - displayFunc(); + if (mRedisplayPending) { // Last redisplay not served (window not visible): handle updates without drawing + HandleUpdatesAndDrawGLScene(true); + waitForFPSLimit(); + } + mRedisplayPending = true; + glutPostRedisplay(); // Drawing only in displayFunc, so that expose events do not draw twice } int32_t GPUDisplayFrontendGlut::GetKey(int32_t key) { - if (key == GLUT_KEY_UP) { - return KEY_UP; - } - if (key == GLUT_KEY_DOWN) { - return KEY_DOWN; - } - if (key == GLUT_KEY_LEFT) { - return KEY_LEFT; - } - if (key == GLUT_KEY_RIGHT) { - return KEY_RIGHT; - } - if (key == GLUT_KEY_PAGE_UP) { - return KEY_PAGEUP; - } - if (key == GLUT_KEY_PAGE_DOWN) { - return KEY_PAGEDOWN; - } - if (key == GLUT_KEY_HOME) { - return KEY_HOME; - } - if (key == GLUT_KEY_END) { - return KEY_END; - } - if (key == GLUT_KEY_INSERT) { - return KEY_INSERT; - } - if (key == GLUT_KEY_F1) { - return KEY_F1; - } - if (key == GLUT_KEY_F2) { - return KEY_F2; - } - if (key == GLUT_KEY_F3) { - return KEY_F3; - } - if (key == GLUT_KEY_F4) { - return KEY_F4; + switch (key) { + case GLUT_KEY_UP: + return KEY_UP; + case GLUT_KEY_DOWN: + return KEY_DOWN; + case GLUT_KEY_LEFT: + return KEY_LEFT; + case GLUT_KEY_RIGHT: + return KEY_RIGHT; + case GLUT_KEY_PAGE_UP: + return KEY_PAGEUP; + case GLUT_KEY_PAGE_DOWN: + return KEY_PAGEDOWN; + case GLUT_KEY_HOME: + return KEY_HOME; + case GLUT_KEY_END: + return KEY_END; + case GLUT_KEY_INSERT: + return KEY_INSERT; + case GLUT_KEY_F1: + return KEY_F1; + case GLUT_KEY_F2: + return KEY_F2; + case GLUT_KEY_F3: + return KEY_F3; + case GLUT_KEY_F4: + return KEY_F4; + case GLUT_KEY_F5: + return KEY_F5; + case GLUT_KEY_F6: + return KEY_F6; + case GLUT_KEY_F7: + return KEY_F7; + case GLUT_KEY_F8: + return KEY_F8; + case GLUT_KEY_F9: + return KEY_F9; + case GLUT_KEY_F10: + return KEY_F10; + case GLUT_KEY_F11: + return KEY_F11; + case GLUT_KEY_F12: + return KEY_F12; + case GLUT_KEY_SHIFT_L: + case GLUT_KEY_SHIFT_R: + return KEY_SHIFT; + case GLUT_KEY_CTRL_L: + return KEY_CTRL; + case GLUT_KEY_CTRL_R: + return KEY_RCTRL; + case GLUT_KEY_ALT_L: + return KEY_ALT; + case GLUT_KEY_ALT_R: + return KEY_RALT; + default: + return 0; } - if (key == GLUT_KEY_F5) { - return KEY_F5; - } - if (key == GLUT_KEY_F6) { - return KEY_F6; - } - if (key == GLUT_KEY_F7) { - return KEY_F7; - } - if (key == GLUT_KEY_F8) { - return KEY_F8; - } - if (key == GLUT_KEY_F9) { - return KEY_F9; - } - if (key == GLUT_KEY_F10) { - return KEY_F10; - } - if (key == GLUT_KEY_F11) { - return KEY_F11; - } - if (key == GLUT_KEY_F12) { - return KEY_F12; - } - if (key == 112 || key == 113) { - return KEY_SHIFT; - } - if (key == 114) { - return KEY_CTRL; - } - if (key == 115) { - return KEY_RCTRL; - } - if (key == 116) { - return KEY_ALT; - } - - return (0); } void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPressOut, bool special) @@ -145,83 +131,77 @@ void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPr } } -void GPUDisplayFrontendGlut::keyboardDownFunc(uint8_t key, int32_t x, int32_t y) +void GPUDisplayFrontendGlut::keyboardFunc(int32_t key, bool special, bool down) { int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, false); - me->mKeysShift[keyPress] = glutGetModifiers() & GLUT_ACTIVE_SHIFT; - me->mKeys[keyPress] = true; - me->HandleKey(handleKey); -} - -void GPUDisplayFrontendGlut::keyboardUpFunc(uint8_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, false); - me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; -} - -void GPUDisplayFrontendGlut::specialDownFunc(int32_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, true); - me->mKeysShift[keyPress] = glutGetModifiers() & GLUT_ACTIVE_SHIFT; - me->mKeys[keyPress] = true; - me->HandleKey(handleKey); -} - -void GPUDisplayFrontendGlut::specialUpFunc(int32_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, true); - me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; + GetKey(key, handleKey, keyPress, special); + mKeys[keyPress] = down; + if (down) { + HandleKey(handleKey); + } } void GPUDisplayFrontendGlut::ResizeSceneWrapper(int32_t width, int32_t height) { - if (!me->mFullScreen) { - me->mWidth = width; - me->mHeight = height; + if (!mFullScreen) { + mWidth = width; + mHeight = height; } - me->ResizeScene(width, height); + ResizeScene(width, height); +#ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 + Display* display = glXGetCurrentDisplay(); // Maximizing / unmaximizing by the window manager changes the size, read the state as freeglut does not report it (fullscreen stays as requested, freeglut tracks it itself) + Atom type; + int32_t format; + unsigned long nItems, bytesAfter; + unsigned char* data = nullptr; + if (!display || XGetWindowProperty(display, glXGetCurrentDrawable(), XInternAtom(display, "_NET_WM_STATE", False), 0, 1024, False, XA_ATOM, &type, &format, &nItems, &bytesAfter, &data) != Success || data == nullptr) { + return; + } + const Atom maxHorz = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", False), maxVert = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + bool isMaxHorz = false, isMaxVert = false; + for (unsigned long i = 0; i < nItems; i++) { + isMaxHorz |= ((Atom*)data)[i] == maxHorz; + isMaxVert |= ((Atom*)data)[i] == maxVert; + } + XFree(data); + setWindowState(mFullScreen, isMaxHorz && isMaxVert); +#endif } void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y) { if (button == 3) { - me->mMouseWheel += 100; + mMouseWheel += 100; } else if (button == 4) { - me->mMouseWheel -= 100; + mMouseWheel -= 100; } else if (state == GLUT_DOWN) { if (button == GLUT_LEFT_BUTTON) { - me->mMouseDn = true; + mMouseDn = true; } else if (button == GLUT_RIGHT_BUTTON) { - me->mMouseDnR = true; + mMouseDnR = true; } else if (button == GLUT_MIDDLE_BUTTON) { - me->mMouseDnM = true; + mMouseDnM = true; } - me->mMouseDnX = x; - me->mMouseDnY = y; + mMouseDnX = x; + mMouseDnY = y; } else if (state == GLUT_UP) { if (button == GLUT_LEFT_BUTTON) { - me->mMouseDn = false; + mMouseDn = false; } else if (button == GLUT_RIGHT_BUTTON) { - me->mMouseDnR = false; + mMouseDnR = false; } else if (button == GLUT_MIDDLE_BUTTON) { - me->mMouseDnM = false; + mMouseDnM = false; } } } void GPUDisplayFrontendGlut::mouseMoveFunc(int32_t x, int32_t y) { - me->mMouseMvX = x; - me->mMouseMvY = y; + mMouseMvX = x; + mMouseMvY = y; } -void GPUDisplayFrontendGlut::mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y) { me->mMouseWheel += dir; } +void GPUDisplayFrontendGlut::mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y) { mMouseWheel += dir; } int32_t GPUDisplayFrontendGlut::FrontendMain() { @@ -229,8 +209,6 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Only OpenGL backend supported\n"); return 1; } - me = this; - mCanDrawText = 1; if (drawTextFontSize() == 0) { drawTextFontSize() = 12; } @@ -241,7 +219,7 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() char* opts[] = {opt1, opt2}; glutInit(&nopts, opts); glutInitContextVersion(GL_MIN_VERSION_MAJOR, GL_MIN_VERSION_MINOR); - glutInitContextProfile(mBackend->CoreProfile() ? GLUT_CORE_PROFILE : GLUT_COMPATIBILITY_PROFILE); + glutInitContextProfile(GLUT_CORE_PROFILE); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(INIT_WIDTH, INIT_HEIGHT); glutCreateWindow(DISPLAY_WINDOW_NAME); @@ -256,17 +234,18 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() return (1); } - glutDisplayFunc(displayFunc); - glutIdleFunc(glutLoopFunc); - glutReshapeFunc(ResizeSceneWrapper); - glutKeyboardFunc(keyboardDownFunc); - glutKeyboardUpFunc(keyboardUpFunc); - glutSpecialFunc(specialDownFunc); - glutSpecialUpFunc(specialUpFunc); - glutMouseFunc(mouseFunc); - glutMotionFunc(mouseMoveFunc); - glutPassiveMotionFunc(mouseMoveFunc); - glutMouseWheelFunc(mMouseWheelFunc); + auto mouseMoveFunc = [](int32_t x, int32_t y, void* p) { self(p)->mouseMoveFunc(x, y); }; + glutDisplayFuncUcall([](void* p) { self(p)->displayFunc(); }, this); // Callbacks with this as user data, freeglut >= 3.2 + glutIdleFuncUcall([](void* p) { self(p)->glutLoopFunc(); }, this); + glutReshapeFuncUcall([](int32_t width, int32_t height, void* p) { self(p)->ResizeSceneWrapper(width, height); }, this); + glutKeyboardFuncUcall([](uint8_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, false, true); }, this); + glutKeyboardUpFuncUcall([](uint8_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, false, false); }, this); + glutSpecialFuncUcall([](int32_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, true, true); }, this); + glutSpecialUpFuncUcall([](int32_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, true, false); }, this); + glutMouseFuncUcall([](int32_t button, int32_t state, int32_t x, int32_t y, void* p) { self(p)->mouseFunc(button, state, x, y); }, this); + glutMotionFuncUcall(mouseMoveFunc, this); + glutPassiveMotionFuncUcall(mouseMoveFunc, this); + glutMouseWheelFuncUcall([](int32_t button, int32_t dir, int32_t x, int32_t y, void* p) { self(p)->mMouseWheelFunc(button, dir, x, y); }, this); applyInitialWindowState(); SetVSync(vsyncEnabled()); @@ -277,8 +256,9 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() glutMainLoop(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } ExitDisplay(); @@ -290,18 +270,6 @@ void GPUDisplayFrontendGlut::StopDisplayA() glutLeaveMainLoop(); } -void GPUDisplayFrontendGlut::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!fromBotton) { - y = mDisplayHeight - y; - } - glColor4f(r, g, b, a); - glRasterPos2f(x, y); - glutBitmapString(GLUT_BITMAP_HELVETICA_12, (const uint8_t*)s); -#endif -} - void GPUDisplayFrontendGlut::SwitchFullscreen(bool set) { mFullScreen = set; @@ -312,7 +280,27 @@ void GPUDisplayFrontendGlut::SwitchFullscreen(bool set) } } -void GPUDisplayFrontendGlut::ToggleMaximized(bool set) {} +void GPUDisplayFrontendGlut::ToggleMaximized(bool set) // GLUT has no API for maximizing, request it from the window manager via GLX / X11 directly (no-op if freeglut does not use GLX) +{ +#ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 + Display* display = glXGetCurrentDisplay(); + if (!display) { + return; + } + XEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = glXGetCurrentDrawable(); // freeglut renders directly to the X window + xev.xclient.message_type = XInternAtom(display, "_NET_WM_STATE", False); + xev.xclient.format = 32; + xev.xclient.data.l[0] = set ? 1 : 0; // _NET_WM_STATE_ADD / _NET_WM_STATE_REMOVE + xev.xclient.data.l[1] = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + xev.xclient.data.l[2] = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + XSendEvent(display, DefaultRootWindow(display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); + XFlush(display); +#endif +} + void GPUDisplayFrontendGlut::SetVSync(bool enable) // GLUT has no swap interval API, set it via GLX directly (no-op if freeglut does not use GLX) { #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index 9fe8e7b44d018..9d3efac3996d0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -30,27 +30,25 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; bool canSetVSync() override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; private: int32_t FrontendMain() override; - static void displayFunc(); - static void glutLoopFunc(); - static void keyboardUpFunc(uint8_t key, int32_t x, int32_t y); - static void keyboardDownFunc(uint8_t key, int32_t x, int32_t y); - static void specialUpFunc(int32_t key, int32_t x, int32_t y); - static void specialDownFunc(int32_t key, int32_t x, int32_t y); - static void mouseMoveFunc(int32_t x, int32_t y); - static void mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y); - static void mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y); - static void ResizeSceneWrapper(int32_t width, int32_t height); + static GPUDisplayFrontendGlut* self(void* p) { return static_cast(p); } // GLUT callback user data + void displayFunc(); + void glutLoopFunc(); + void keyboardFunc(int32_t key, bool special, bool down); + void mouseMoveFunc(int32_t x, int32_t y); + void mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y); + void mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y); + void ResizeSceneWrapper(int32_t width, int32_t height); static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t& keyOut, int32_t& keyPressOut, bool special); int32_t mWidth = INIT_WIDTH; int32_t mHeight = INIT_HEIGHT; bool mFullScreen = false; + bool mRedisplayPending = false; // glutPostRedisplay called in glutLoopFunc, not yet served by displayFunc }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h new file mode 100644 index 0000000000000..4fbe81054c1d2 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h @@ -0,0 +1,74 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendKeys.h +/// \author David Rohr + +// Key codes for special keys (to unify different treatment in X11 / Windows / GLUT / etc.), as GPUDisplayFrontendAddKey(code, name) X-macros. +// No include guard: included multiple times with different definitions of GPUDisplayFrontendAddKey. + +GPUDisplayFrontendAddKey(1, UP); +GPUDisplayFrontendAddKey(2, DOWN); +GPUDisplayFrontendAddKey(3, LEFT); +GPUDisplayFrontendAddKey(4, RIGHT); +GPUDisplayFrontendAddKey(5, PAGEUP); +GPUDisplayFrontendAddKey(6, PAGEDOWN); +GPUDisplayFrontendAddKey(8, SHIFT); +GPUDisplayFrontendAddKey(9, ALT); +GPUDisplayFrontendAddKey(10, RALT); +GPUDisplayFrontendAddKey(11, CTRL); +GPUDisplayFrontendAddKey(12, RCTRL); +GPUDisplayFrontendAddKey(13, ENTER); // fixed at 13 +GPUDisplayFrontendAddKey(14, F1); +GPUDisplayFrontendAddKey(15, F2); +GPUDisplayFrontendAddKey(16, F3); +GPUDisplayFrontendAddKey(17, F4); +GPUDisplayFrontendAddKey(18, F5); +GPUDisplayFrontendAddKey(19, F6); +GPUDisplayFrontendAddKey(20, F7); +GPUDisplayFrontendAddKey(21, F8); +GPUDisplayFrontendAddKey(22, F9); +GPUDisplayFrontendAddKey(23, F10); +GPUDisplayFrontendAddKey(24, F11); +GPUDisplayFrontendAddKey(25, F12); +GPUDisplayFrontendAddKey(26, INSERT); +GPUDisplayFrontendAddKey(27, ESCAPE); // fixed at 27 +GPUDisplayFrontendAddKey(28, HOME); +GPUDisplayFrontendAddKey(29, END); +GPUDisplayFrontendAddKey(32, SPACE); // fixed at 32 + +#ifndef GPUDisplayFrontendKeyDownMap +#define GPUDisplayFrontendKeyDownMap(...) +#endif + +GPUDisplayFrontendKeyDownMap(0, FORWARD); +GPUDisplayFrontendKeyDownMap(1, BACKWARD); +GPUDisplayFrontendKeyDownMap(2, STRAFE_LEFT); +GPUDisplayFrontendKeyDownMap(3, STRAFE_RIGHT); +GPUDisplayFrontendKeyDownMap(4, STRAFE_UP); +GPUDisplayFrontendKeyDownMap(5, STRAFE_DOWN); +GPUDisplayFrontendKeyDownMap(6, ROTATE_LEFT); +GPUDisplayFrontendKeyDownMap(7, ROTATE_RIGHT); +GPUDisplayFrontendKeyDownMap(8, INCREASE_WIDTH); +GPUDisplayFrontendKeyDownMap(9, DECREASE_WIDTH); +GPUDisplayFrontendKeyDownMap(10, FOCUS_ORIGIN); +GPUDisplayFrontendKeyDownMap(11, FOCUS_ORIENT_UP); +GPUDisplayFrontendKeyDownMap(12, ROTATE_MODEL); +GPUDisplayFrontendKeyDownMap(13, ROTATE_AROUND_Z); +GPUDisplayFrontendKeyDownMap(14, CHANGE_ANIMATION_INTERVAL); +GPUDisplayFrontendKeyDownMap(15, ROTATE_YAW_LEFT); +GPUDisplayFrontendKeyDownMap(16, ROTATE_YAW_RIGHT); +GPUDisplayFrontendKeyDownMap(17, ROTATE_PITCH_UP); +GPUDisplayFrontendKeyDownMap(18, ROTATE_PITCH_DOWN); +GPUDisplayFrontendKeyDownMap(19, MAX); // Must be the maximum value! + +#undef GPUDisplayFrontendAddKey +#undef GPUDisplayFrontendKeyDownMap diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index 5d5748e13349a..f195fce1977ce 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -27,10 +27,19 @@ int32_t GPUDisplayFrontendNone::FrontendMain() if (InitDisplay()) { return 1; } + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } do { - HandleExternalGUISignals(); - DrawGLScene(); + HandleUpdatesAndDrawGLScene(); waitForFPSLimit(); - } while (mDisplayControl != 2); + } while (getDisplayControl() != 2); + ExitDisplay(); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); + } return 0; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h index c6eab47c60441..47080c821542b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h @@ -28,7 +28,6 @@ class GPUDisplayFrontendNone : public GPUDisplayFrontend void SwitchFullscreen(bool set) override {} void ToggleMaximized(bool set) override {} void SetVSync(bool enable) override {} - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override {} private: int32_t FrontendMain() override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 66701557a8068..68177449f06c8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -38,7 +38,6 @@ #include "viewporter-client-protocol.h" #include "cursor-shape-client-protocol.h" #include -#include using namespace o2::gpu; @@ -50,10 +49,6 @@ GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() mFrontendName = "Wayland"; } -void GPUDisplayFrontendWayland::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -} - int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) { int32_t retVal = 0; @@ -174,24 +169,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto pointer_button = [](void* data, wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - if (button == BTN_RIGHT) { - me->mMouseDnR = true; - } else if (button == BTN_LEFT) { - me->mMouseDn = true; - } else if (button == BTN_MIDDLE) { - me->mMouseDnM = true; - } - me->mMouseDnX = me->mMouseMvX; - me->mMouseDnY = me->mMouseMvY; - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - if (button == BTN_RIGHT) { - me->mMouseDnR = false; - } else if (button == BTN_LEFT) { - me->mMouseDn = false; - } else if (button == BTN_MIDDLE) { - me->mMouseDnM = false; - } + if (state == WL_POINTER_BUTTON_STATE_PRESSED || state == WL_POINTER_BUTTON_STATE_RELEASED) { + me->handlePointerButton(button, state == WL_POINTER_BUTTON_STATE_PRESSED); } }; auto pointer_frame = [](void* data, wl_pointer* wl_pointer) { ((GPUDisplayFrontendWayland*)data)->applyScroll(); }; @@ -259,7 +238,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() int32_t keyPress = (symbol >= 'a' && symbol <= 'z') ? symbol + 'A' - 'a' : symbol; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; me->HandleKey(symbol); if (symbol && me->mRepeatRate > 0 && me->mXKBkeymap && xkb_keymap_key_repeats(me->mXKBkeymap, key + 8)) { // Wayland compositors do not repeat keys, the client must do it me->mRepeatKeyCode = key; @@ -268,7 +246,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } } else { me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; if ((int64_t)key == me->mRepeatKeyCode) { me->mRepeatKeyCode = -1; } @@ -511,7 +488,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto xdg_toplevel_handle_close = [](void* data, xdg_toplevel* toplevel) { - ((GPUDisplayFrontendWayland*)data)->mDisplayControl = 2; + ((GPUDisplayFrontendWayland*)data)->mDisplay->mCfgC.mDisplayControl.store(2); }; #ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION auto xdg_toplevel_handle_configure_bounds = [](void* data, xdg_toplevel* toplevel, int32_t width, int32_t height) { @@ -585,7 +562,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() mDisplayRunning = true; } - while (mDisplayControl != 2) { + while (mDisplay->mCfgC.mDisplayControl.load() != 2) { bool error = false; while (!error && wl_display_prepare_read(mWayland) != 0) { error = wl_display_dispatch_pending(mWayland) == -1; @@ -608,14 +585,11 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() reportError("dispatching events"); break; } - if (mDisplayControl == 2) { + if (mDisplay->mCfgC.mDisplayControl.load() == 2) { break; } handleKeyRepeat(); - HandleExternalGUISignals(); - if (!mSuspended) { // Window not visible, compositor would not release swapchain images - DrawGLScene(); - } + HandleUpdatesAndDrawGLScene(mSuspended); // Window not visible, compositor would not release swapchain images waitForFPSLimit(); } @@ -631,6 +605,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } if (mCursorShapeDevice) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index bd55aa53f4b55..b6083e66e0711 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -43,7 +43,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } float getRefreshRate() override; @@ -52,6 +51,7 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend private: int32_t FrontendMain() override; + void handlePointerButton(uint32_t button, bool pressed); // In GPUDisplayFrontendWaylandEv.cxx, which needs the evdev button codes int32_t GetKey(uint32_t key, uint32_t state); void updateScale(); // Scale from wp_fractional_scale_v1, else integer wl_surface.preferred_buffer_scale, or max scale of the outputs the surface is on (older compositors) void updateSize(); // Physical size = logical size * scale, resizes the scene if changed diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx new file mode 100644 index 0000000000000..4e5e68be566b1 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx @@ -0,0 +1,37 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendWaylandEv.cxx +/// \author David Rohr + +// Functions of the Wayland frontend that need the evdev codes from linux/input-event-codes.h. +// That header defines macros KEY_UP, KEY_F1, ..., which replace GPUDisplayFrontend::KEY_* (e.g. KEY_F1 becomes 59 = ';'), so it must not be included in GPUDisplayFrontendWayland.cxx, +// and here only after all other headers, and the functions in this file must not use GPUDisplayFrontend::KEY_*. + +#include "GPUDisplayFrontendWayland.h" +#include + +using namespace o2::gpu; + +void GPUDisplayFrontendWayland::handlePointerButton(uint32_t button, bool pressed) +{ + if (button == BTN_RIGHT) { + mMouseDnR = pressed; + } else if (button == BTN_LEFT) { + mMouseDn = pressed; + } else if (button == BTN_MIDDLE) { + mMouseDnM = pressed; + } + if (pressed) { + mMouseDnX = mMouseMvX; + mMouseDnY = mMouseMvY; + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 80537c15c4700..d71afb3414668 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -260,13 +260,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_KEYDOWN: // Is A Key Being Held Down? wParam = GetKey(wParam); mKeys[wParam] = TRUE; - mKeysShift[wParam] = mKeys[KEY_SHIFT]; HandleKey(wParam); return 0; case WM_KEYUP: wParam = GetKey(wParam); - mKeysShift[wParam] = false; mKeys[wParam] = false; GPUInfo("Key: %d", wParam); return 0; @@ -352,13 +350,11 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() DispatchMessage(&msg); // Dispatch The Message } } else { - HandleExternalGUISignals(); - if (active) // Program Active? - { - if (mKeys[VK_ESCAPE]) { - done = TRUE; - } else { - DrawGLScene(); // Draw The Scene + if (active && mKeys[VK_ESCAPE]) { + done = TRUE; + } else { + HandleUpdatesAndDrawGLScene(!active); // Only draw if the program is active + if (active) { SwapBuffers(hDC); // Swap Buffers (Double Buffering) waitForFPSLimit(); } @@ -372,7 +368,6 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() return (0); } -void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) {} void SwitchFullscreen(bool set) {} void ToggleMaximized(bool set) {} void SetVSync(bool enable) {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h index e28a9f4ced75b..ac38427367dc5 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h @@ -28,7 +28,6 @@ class GPUDisplayFrontendWindows : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; private: int32_t FrontendMain() override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index a06f75d094cda..6448c83303c0a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -163,28 +163,6 @@ void GPUDisplayFrontendX11::GetKey(XEvent& event, int32_t& keyOut, int32_t& keyP } } -void GPUDisplayFrontendX11::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - if (!fromBotton) { - y = mDisplayHeight - y; - } - glColor4f(r, g, b, a); - glRasterPos2f(x, y); - if (!glIsList(mFontBase)) { - GPUError("print string: Bad display list."); - exit(1); - } else if (s && strlen(s)) { - glPushAttrib(GL_LIST_BIT); - glListBase(mFontBase); - glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte*)s); - glPopAttrib(); - } - } -#endif -} - int32_t GPUDisplayFrontendX11::FrontendMain() { XSetWindowAttributes windowAttributes; @@ -261,7 +239,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, GL_MIN_VERSION_MAJOR, GLX_CONTEXT_MINOR_VERSION_ARB, GL_MIN_VERSION_MINOR, - GLX_CONTEXT_PROFILE_MASK_ARB, mBackend->CoreProfile() ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, None}; glxContext = glXCreateContextAttribsARB(mXDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); } else { @@ -300,31 +278,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() // Receive signal when window closed Atom WM_DELETE_WINDOW = XInternAtom(mXDisplay, "WM_DELETE_WINDOW", False); XSetWMProtocols(mXDisplay, mWindow, &WM_DELETE_WINDOW, 1); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - XFontStruct* font_info = nullptr; - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - // Prepare fonts - mFontBase = glGenLists(256); - if (!glIsList(mFontBase)) { - GPUError("Out of display lists."); - return (-1); - } - const char* f = "fixed"; - font_info = XLoadQueryFont(mXDisplay, f); - if (!font_info) { - GPUError("XLoadQueryFont failed."); - return (-1); - } else { - int32_t first = font_info->min_char_or_byte2; - int32_t last = font_info->max_char_or_byte2; - glXUseXFont(font_info->fid, first, last - first + 1, mFontBase + first); - } - } - mCanDrawText = 1; - if (drawTextFontSize() == 0) { - drawTextFontSize() = 12; - } -#endif if (mBackend->ExtInit()) { fprintf(stderr, "Error initializing backend\n"); @@ -355,7 +308,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() while (1) { do { - if (mDisplayControl == 2) { + if (getDisplayControl() == 2) { break; } if (!XPending(mXDisplay)) { @@ -399,7 +352,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() case KeyPress: { int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); - mKeysShift[keyPress] = mKeys[KEY_SHIFT]; mKeys[keyPress] = true; HandleKey(handleKey); break; @@ -409,7 +361,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); mKeys[keyPress] = false; - mKeysShift[keyPress] = false; break; } @@ -444,30 +395,23 @@ int32_t GPUDisplayFrontendX11::FrontendMain() if (event.xclient.message_type == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { XFlush(mXDisplay); } else { - mDisplayControl = 2; + setDisplayControl(2); } break; } } } while (XPending(mXDisplay)); // Loop to compress events - if (mDisplayControl == 2) { + if (getDisplayControl() == 2) { break; } - HandleExternalGUISignals(); - DrawGLScene(); + HandleUpdatesAndDrawGLScene(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXSwapBuffers(mXDisplay, mWindow); } waitForFPSLimit(); } -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glDeleteLists(mFontBase, 256); - XUnloadFont(mXDisplay, font_info->fid); - } -#endif ExitDisplay(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXDestroyContext(mXDisplay, glxContext); @@ -479,6 +423,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return (0); @@ -513,8 +458,8 @@ void GPUDisplayFrontendX11::SwitchFullscreen(bool set) xev.xclient.window = mWindow; xev.xclient.message_type = XInternAtom(mXDisplay, "_NET_WM_STATE", False); xev.xclient.format = 32; - xev.xclient.data.l[0] = 2; // _NET_WM_STATE_TOGGLE - xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", True); + xev.xclient.data.l[0] = set ? 1 : 0; // _NET_WM_STATE_ADD / _NET_WM_STATE_REMOVE + xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", False); xev.xclient.data.l[2] = 0; XSendEvent(mXDisplay, DefaultRootWindow(mXDisplay), False, SubstructureNotifyMask, &xev); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index 41032debeb9f5..abceeaaa670f0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -32,7 +32,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } int32_t getVulkanSurface(void* instance, void* surface) override; @@ -43,8 +42,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend int32_t GetKey(int32_t key); void GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut); - GLuint mFontBase; - Display* mXDisplay = nullptr; Window mWindow; void updateWindowState(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 93a5caa2fa4dd..876ddd2f8e4ef 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -16,25 +16,160 @@ #include "GPUDisplayFrontend.h" #include "ui_GPUDisplayGUI.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include +#include using namespace o2::gpu; +static void mergeValue(std::vector>& values, uint32_t id, GPUDisplayGUIValue value) // Replace an earlier value of the same element +{ + auto it = std::find_if(values.begin(), values.end(), [id](const auto& v) { return v.first == id; }); + if (it != values.end()) { + it->second = std::move(value); + } else { + values.emplace_back(id, std::move(value)); + } +} + +namespace +{ +class FlowLayout : public QLayout // Places the items in a row with their preferred width, and wraps into further rows at the right border (Qt has no such layout) +{ + public: + FlowLayout(QWidget* parent, int hSpacingFactor, bool equalRowHeight) : QLayout(parent), mHSpacingFactor(hSpacingFactor), mEqualRowHeight(equalRowHeight) {} + ~FlowLayout() override + { + while (QLayoutItem* item = takeAt(0)) { + delete item; + } + } + void addItem(QLayoutItem* item) override { mItems.append(item); } + int count() const override { return mItems.size(); } + QLayoutItem* itemAt(int index) const override { return mItems.value(index); } + QLayoutItem* takeAt(int index) override { return index >= 0 && index < mItems.size() ? mItems.takeAt(index) : nullptr; } + Qt::Orientations expandingDirections() const override { return {}; } + bool hasHeightForWidth() const override { return true; } + int heightForWidth(int width) const override { return doLayout(QRect(0, 0, width, 0), true); } + void setGeometry(const QRect& rect) override + { + QLayout::setGeometry(rect); + doLayout(rect, false); + } + QSize sizeHint() const override // All items in one row + { + QSize size(0, 0); + for (const QLayoutItem* item : mItems) { + size = QSize(size.width() + (size.width() ? hSpacing() : 0) + itemWidth(item), std::max(size.height(), item->sizeHint().height())); + } + const QMargins m = contentsMargins(); + return size + QSize(m.left() + m.right(), m.top() + m.bottom()); + } + QSize minimumSize() const override // Widest item, height for that width is given by heightForWidth + { + QSize size; + for (const QLayoutItem* item : mItems) { + size = size.expandedTo(item->minimumSize()); + } + const QMargins m = contentsMargins(); + return size + QSize(m.left() + m.right(), m.top() + m.bottom()); + } + void setPreferredWidth(QWidget* widget, int width) { mPreferredWidth[widget] = width; } // Instead of the size hint of the widget + + private: + int hSpacing() const { return mHSpacingFactor * spacing(); } + int itemWidth(const QLayoutItem* item) const { return mPreferredWidth.value(item->widget(), item->sizeHint().width()); } + int doLayout(const QRect& rect, bool testOnly) const // Returns the height needed + { + const QRect r = rect.marginsRemoved(contentsMargins()); + int x = r.x(), y = r.y(), lineHeight = 0; + std::vector> line; // Items of the current row, placed when the row is complete (height of the row known) + auto placeLine = [&]() { + if (!testOnly) { + for (auto& [item, geometry] : line) { + item->setGeometry(mEqualRowHeight ? QRect(geometry.x(), geometry.y(), geometry.width(), lineHeight) : geometry); + } + } + line.clear(); + }; + for (QLayoutItem* item : mItems) { + const int width = std::max(std::min(itemWidth(item), r.width()), item->minimumSize().width()); // Items wider than the layout get its width (at least their minimum), and wrap internally if they can + const int height = item->hasHeightForWidth() ? item->heightForWidth(width) : item->sizeHint().height(); + if (x > r.x() && x + width > r.right() + 1) { + placeLine(); + x = r.x(); + y += lineHeight + spacing(); + lineHeight = 0; + } + line.emplace_back(item, QRect(x, y, width, height)); + x += width + hSpacing(); + lineHeight = std::max(lineHeight, height); + } + placeLine(); + return y + lineHeight - rect.y() + contentsMargins().bottom(); + } + QList mItems; + QHash mPreferredWidth; + int mHSpacingFactor; // Horizontal spacing in units of spacing() + bool mEqualRowHeight; // All items of a row get the height of the highest one +}; +} // namespace + +static double toDouble(const GPUDisplayGUIValue& v) +{ + return std::visit([](const auto& x) -> double { + if constexpr (std::is_same_v, std::string>) { + return QString::fromStdString(x).toDouble(); + } else { + return x; + } + }, + v); +} + +static bool sameValue(const GPUDisplayGUIValue& a, const GPUDisplayGUIValue& b) // Numbers compared by value, independent of int64_t / double +{ + const bool aString = std::holds_alternative(a), bString = std::holds_alternative(b); + return aString || bString ? a == b : toDouble(a) == toDouble(b); +} + GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mUi(std::make_unique()), mChannel(channel) { mUi->setupUi(this); + std::vector layout; { std::lock_guard lock(mChannel.mutex); setWindowTitle(QString::fromStdString(mChannel.windowName + " GUI")); + layout = mChannel.layout; } - connect(mUi->lineWidthSlider, &QSlider::valueChanged, this, [this](int32_t value) { - mUpdate.lineWidth = value * 0.1f; - mUi->lineWidthValue->setNum(*mUpdate.lineWidth); - }); + mBaseFontSize = font().pointSizeF() > 0 ? font().pointSizeF() : 10.f; // Fonts with pixel size have no point size + buildElements(layout); + setScale(mScale); // Scale from the display with the first state + qApp->installEventFilter(this); // Sliders / dropdowns would consume Ctrl + mouse wheel themselves connect(&mTimer, &QTimer::timeout, this, &GPUDisplayGUI::timerTick); mTimer.start(std::chrono::milliseconds(10)); } @@ -65,12 +200,16 @@ void GPUDisplayGUI::timerTick() } } const bool apply = newState && mUpdate.empty() && mState.appliedUpdate == mLastSentId; // State contains all our updates, and we have no unsent changes - if (!mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead) { + const bool post = !mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead; + if (post) { if (mChannel.update) { // Previous update not yet picked up by the display, merge auto& update = *mChannel.update; update.keys.insert(update.keys.end(), mUpdate.keys.begin(), mUpdate.keys.end()); - if (mUpdate.lineWidth) { - update.lineWidth = mUpdate.lineWidth; + for (const auto& [id, value] : mUpdate.values) { + mergeValue(update.values, id, value); + } + if (mUpdate.guiScale) { + update.guiScale = mUpdate.guiScale; } } else { mChannel.update = std::move(mUpdate); @@ -81,6 +220,9 @@ void GPUDisplayGUI::timerTick() } lock.unlock(); + if (post) { + mChannel.updatePosted(); + } if (apply) { applySettings(); } @@ -88,9 +230,232 @@ void GPUDisplayGUI::timerTick() void GPUDisplayGUI::applySettings() { - const QSignalBlocker blocker(mUi->lineWidthSlider); // Not a user change - mUi->lineWidthSlider->setValue(std::lround(mState.cfg.cfgL.lineWidth * 10.f)); - mUi->lineWidthValue->setNum(mState.cfg.cfgL.lineWidth); + if (mState.guiScale != mScale) { // Changed by the display (Reset GUI button) or from a previous GUI session + setScale(mState.guiScale); + } + for (uint32_t id = 0; id < mElementSetters.size() && id < mState.values.size(); id++) { + if (mElementSetters[id]) { + mElementSetters[id](mState.values[id]); + } + } +} + +void GPUDisplayGUI::setValue(uint32_t id, GPUDisplayGUIValue value) { mergeValue(mUpdate.values, id, std::move(value)); } + +void GPUDisplayGUI::buildElements(const std::vector& layout) +{ + using Type = GPUDisplayGUIChannel::ElementType; + static constexpr int32_t SLIDER_STEPS = 1000; // Resolution of sliders for floating point options + auto* frames = new QWidget; // Frames with their natural width, next to each other while they fit into the window + auto* framesLayout = new FlowLayout(frames, 1, true); + mFramesLayout = framesLayout; + framesLayout->setContentsMargins(0, 0, 0, 0); + mUi->mainLayout->insertWidget(mUi->mainLayout->count() - 1, frames); // Before the vertical spacer + for (const auto& frame : layout) { + auto* box = new QGroupBox(QString::fromStdString(frame.description)); + if (frame.description.empty()) { + box->setStyleSheet("QGroupBox { margin-top: 0; }"); // No space for a title + } + const bool horizontal = frame.horizontal; + QLayout* boxLayout = horizontal ? (QLayout*)new FlowLayout(box, 3, false) : (QLayout*)new QFormLayout(box); // More space between the elements than between the rows + for (const auto& el : frame.elements) { + const uint32_t id = el.id; + QWidget* widget = nullptr; + std::function setter; // Setting a widget programmatically is not a user change, signals emitted by it are blocked + if (el.type == Type::CHECKBOX) { + auto* w = new QCheckBox(QString::fromStdString(el.name)); // Own text instead of a label + connect(w, &QCheckBox::toggled, this, [this, id](bool v) { setValue(id, (int64_t)v); }); + setter = [w](const GPUDisplayGUIValue& v) { const QSignalBlocker blocker(w); w->setChecked(toDouble(v) != 0.); }; + widget = w; + } else if (el.type == Type::TEXTBOX) { + auto* w = new QLineEdit; + if (!el.hasRange) { // With range, any text is accepted and converted / limited + w->setValidator(el.isInteger ? (QValidator*)new QIntValidator(w) : (QValidator*)new QDoubleValidator(w)); + } + connect(w, &QLineEdit::editingFinished, this, [this, w, id, isInteger = el.isInteger, hasRange = el.hasRange, min = el.min, max = el.max]() { + bool ok = false; + GPUDisplayGUIValue v = isInteger ? GPUDisplayGUIValue((int64_t)w->text().toLongLong(&ok)) : GPUDisplayGUIValue(w->text().toDouble(&ok)); + if (hasRange) { + const double clamped = std::clamp(ok ? toDouble(v) : 0., min, max); + v = isInteger ? GPUDisplayGUIValue((int64_t)std::llround(clamped)) : GPUDisplayGUIValue(clamped); + w->setText(QString::number(clamped)); // Show the value actually set, the display state does not overwrite the focused field + } else if (!ok) { + return; + } + setValue(id, v); + }); + setter = [w](const GPUDisplayGUIValue& v) { + if (!w->hasFocus()) { // Do not overwrite while the user edits + w->setText(std::visit([](const auto& x) { + if constexpr (std::is_same_v, std::string>) { + return QString::fromStdString(x); + } else { + return QString::number(x); + } + }, + v)); + } + }; + widget = w; + } else if (el.type == Type::SLIDER) { + widget = new QWidget; + auto* row = new QHBoxLayout(widget); + row->setContentsMargins(0, 0, 0, 0); + auto* slider = new QSlider(Qt::Horizontal); + auto* label = new QLabel; + if (el.isInteger || el.choices.size()) { // Length by number of steps, about twice a checkbox indicator per step + const int steps = el.choices.size() ? el.choices.size() - 1 : std::lround(el.max - el.min); + mScaledWidths.push_back({slider, steps * 2 * style()->pixelMetric(QStyle::PM_IndicatorWidth), true}); + } else { + mScaledWidths.push_back({slider, 150, false}); + } + mScaledWidths.push_back({label, 50, false}); + row->addWidget(slider); + row->addWidget(label); + if (el.choices.size()) { // Positions are the indices of the choices + slider->setRange(0, el.choices.size() - 1); + connect(slider, &QSlider::valueChanged, this, [this, label, id, choices = el.choices](int32_t pos) { + label->setText(QString::fromStdString(choices[pos].first)); + setValue(id, choices[pos].second); + }); + setter = [slider, label, choices = el.choices](const GPUDisplayGUIValue& value) { + uint32_t best = 0; // Nearest choice for values not in the list + for (uint32_t i = 1; i < choices.size(); i++) { + if (std::abs(toDouble(choices[i].second) - toDouble(value)) < std::abs(toDouble(choices[best].second) - toDouble(value))) { + best = i; + } + } + const QSignalBlocker blocker(slider); + slider->setValue(best); + label->setText(sameValue(choices[best].second, value) ? QString::fromStdString(choices[best].first) : QString::number(toDouble(value))); + }; + } else { + const double min = el.min, step = el.isInteger ? 1. : (el.max - el.min) / SLIDER_STEPS; + slider->setRange(0, el.isInteger ? std::lround(el.max - el.min) : SLIDER_STEPS); + connect(slider, &QSlider::valueChanged, this, [this, label, id, min, step, isInteger = el.isInteger](int32_t pos) { + const double v = min + pos * step; + label->setNum(v); + setValue(id, isInteger ? GPUDisplayGUIValue((int64_t)std::llround(v)) : GPUDisplayGUIValue(v)); + }); + setter = [slider, label, min, step](const GPUDisplayGUIValue& value) { + const double v = toDouble(value); + const QSignalBlocker blocker(slider); + slider->setValue(std::lround((v - min) / step)); + label->setNum(v); + }; + } + } else if (el.type == Type::RADIO_BUTTONS) { + widget = new QWidget; + auto* row = new QHBoxLayout(widget); + row->setContentsMargins(0, 0, 0, 0); + auto* group = new QButtonGroup(widget); + for (uint32_t i = 0; i < el.choices.size(); i++) { + auto* button = new QRadioButton(QString::fromStdString(el.choices[i].first)); + group->addButton(button, i); + row->addWidget(button); + } + connect(group, &QButtonGroup::idClicked, this, [this, id, choices = el.choices](int32_t i) { setValue(id, choices[i].second); }); // idClicked only on user clicks + setter = [group, choices = el.choices](const GPUDisplayGUIValue& v) { + for (uint32_t i = 0; i < choices.size(); i++) { + if (sameValue(choices[i].second, v)) { + group->button(i)->setChecked(true); + } + } + }; + } else if (el.type == Type::BUTTON) { // No value from the display + auto* w = new QPushButton(QString::fromStdString(el.name)); + connect(w, &QPushButton::clicked, this, [this, id]() { setValue(id, (int64_t)1); }); + widget = w; + } else if (el.type == Type::DROPDOWN) { + auto* w = new QComboBox; + for (const auto& choice : el.choices) { + w->addItem(QString::fromStdString(choice.first)); + } + connect(w, &QComboBox::currentIndexChanged, this, [this, id, choices = el.choices](int32_t i) { + if (i >= 0) { + setValue(id, choices[i].second); + } + }); + setter = [w, choices = el.choices](const GPUDisplayGUIValue& v) { + const QSignalBlocker blocker(w); + for (uint32_t i = 0; i < choices.size(); i++) { + if (sameValue(choices[i].second, v)) { + w->setCurrentIndex(i); + } + } + }; + widget = w; + } + QLabel* name = el.type == Type::CHECKBOX || el.type == Type::BUTTON ? nullptr : new QLabel(QString::fromStdString(el.name)); // Checkboxes and buttons show their own text + if (!horizontal) { + name ? ((QFormLayout*)boxLayout)->addRow(name, widget) : ((QFormLayout*)boxLayout)->addRow(widget); + } else if (name) { // Label and element as one item of the flow layout + auto* item = new QWidget; + auto* row = new QHBoxLayout(item); + row->setContentsMargins(0, 0, 0, 0); + row->addWidget(name); + row->addWidget(widget); + boxLayout->addWidget(item); + } else { + boxLayout->addWidget(widget); + } + if (mElementSetters.size() <= id) { + mElementSetters.resize(id + 1); + } + mElementSetters[id] = std::move(setter); + } + framesLayout->addWidget(box); + if (std::any_of(frame.elements.begin(), frame.elements.end(), [](const auto& el) { return el.type == Type::SLIDER && !el.isInteger && el.choices.empty(); })) { + mSliderFrames.emplace_back(box); // Preferred width set in setScale(), sliders range to the frame border, frame limited to the window width by the layout + } + } +} + +void GPUDisplayGUI::setScale(float scale) +{ + mScale = std::clamp(scale, 0.25f, 4.f); + for (const auto& w : mScaledWidths) { + if (w.fixed) { + w.widget->setFixedWidth(std::lround(w.width * mScale)); + } else { + w.widget->setMinimumWidth(std::lround(w.width * mScale)); + } + } + for (auto* box : mSliderFrames) { + static_cast(mFramesLayout)->setPreferredWidth(box, std::lround(screen()->availableGeometry().width() / 3 * mScale)); + } + if (mFramesLayout) { + mFramesLayout->invalidate(); + } + const int indicator = std::lround(style()->pixelMetric(QStyle::PM_IndicatorWidth) * mScale); + setStyleSheet(QString("QGroupBox { border: 1px solid palette(mid); border-radius: 3px; margin-top: 0.6em; padding-top: 0.3em; }" + "QGroupBox::title { subcontrol-origin: margin; left: 8px; padding: 0 3px; }" // Title on the frame border instead of in an extra line above + "QCheckBox::indicator, QRadioButton::indicator { width: %1px; height: %1px; }" + "QWidget { font-size: %2pt; }") // Style sheet, since setFont() does not override the fonts the platform theme sets for some classes (QLabel, QPushButton), sizes derived from the text follow + .arg(indicator) + .arg(mBaseFontSize * mScale, 0, 'f', 1)); +} + +bool GPUDisplayGUI::eventFilter(QObject* watched, QEvent* event) +{ + const auto* w = qobject_cast(watched); + if (!w || w->window() != this) { + return false; + } + if (event->type() == QEvent::Wheel && (static_cast(event)->modifiers() & Qt::ControlModifier)) { + const int delta = static_cast(event)->angleDelta().y(); // 120 per notch + if (delta) { + setScale(mScale * std::pow(1.1f, delta / 120.f)); + mUpdate.guiScale = mScale; + } + return true; + } + if (event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_0 && (static_cast(event)->modifiers() & Qt::ControlModifier)) { + setScale(1.f); + mUpdate.guiScale = mScale; + return true; + } + return false; } void GPUDisplayGUI::bringToFront() diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h index e0da6423e94d2..b64671df7374a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h @@ -23,7 +23,10 @@ #include "GPUDisplayGUIChannel.h" #include #include +#include #include +#include +#include class QKeyEvent; @@ -47,19 +50,34 @@ class GPUDisplayGUI : public QMainWindow protected: void keyPressEvent(QKeyEvent* event) override; + bool eventFilter(QObject* watched, QEvent* event) override; // Ctrl + mouse wheel / Ctrl + 0 for all widgets of the window private: void timerTick(); // Exchange updates / settings with the display void applySettings(); - static int32_t translateKey(const QKeyEvent* event); // Qt key → display key code, -1 if not forwarded + void buildElements(const std::vector& layout); // One frame per add_gui_binding call, with a labeled element per option + void setValue(uint32_t id, GPUDisplayGUIValue value); // Element changed by the user, sent with the next update + void setScale(float scale); // Font size, fixed element sizes, and check / radio indicators (not sent to the display) + static int32_t translateKey(const QKeyEvent* event); // Qt key → display key code, -1 if not forwarded std::unique_ptr mUi; QTimer mTimer; GPUDisplayGUIChannel& mChannel; - GPUDisplayGUIChannel::Update mUpdate; // Changes collected since the last tick - GPUDisplayGUIChannel::State mState; // Last state received from the display - uint64_t mLastSentId = 0; // Id of the last update sent - uint64_t mLastAppliedId = 0; // Id of the last update for which the display state was received + GPUDisplayGUIChannel::Update mUpdate; // Changes collected since the last tick + GPUDisplayGUIChannel::State mState; // Last state received from the display + uint64_t mLastSentId = 0; // Id of the last update sent + uint64_t mLastAppliedId = 0; // Id of the last update for which the display state was received + std::vector> mElementSetters; // By element id, set the element to a value from the display + struct ScaledWidth { + QWidget* widget; + int width; // Unscaled pixels + bool fixed; // Fixed width, otherwise minimum width + }; + std::vector mScaledWidths; // Widget widths that scale with the GUI + std::vector mSliderFrames; // Frames with floating point sliders, preferred width 1/3 of the screen width times the scale + QLayout* mFramesLayout = nullptr; // FlowLayout of the frames + float mScale = 1.f; + float mBaseFontSize = 0.f; // Point size of the font at scale 1 }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui index 77b2a7401be11..fce6ed6964ca1 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui @@ -15,40 +15,6 @@ - - - - - - Line width - - - - - - - 1 - - - 50 - - - Qt::Orientation::Horizontal - - - - - - - - 40 - 0 - - - - - - diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h index cf24951477d0a..93e82d8aa1c82 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h @@ -16,12 +16,15 @@ #define GPUDISPLAYGUICHANNEL_H #include "GPUSettings.h" +#include "GPUDisplayFrontend.h" #include #include #include +#include #include #include #include +#include #include namespace o2::gpu @@ -35,16 +38,41 @@ struct GPUDisplayGUIChannel { GPUSettingsDisplayLight cfgL; GPUSettingsDisplayRenderer cfgR; }; + enum class ElementType : uint8_t { CHECKBOX, + TEXTBOX, + SLIDER, + RADIO_BUTTONS, + DROPDOWN, + BUTTON }; + + struct Element { // GUI element for one option, from GUI_LAMBDA in GPUDisplayFrontendConfigQtGUI.inc + std::string name; // Label + uint32_t rttiIndex; // Option in the display config RTTI, invalid for options without RTTI (GPUDisplay::control) + ElementType type; // Widget type + uint32_t id; // Index of the element in Update::values / State::values + bool isInteger; // Integer / bool option, otherwise floating point + bool hasRange = false; // Text field: value converted (0 if invalid) and limited to min / max + double min = 0, max = 0; // Slider range / text field range (if hasRange), from the min / max of the option + std::vector> choices; // Radio buttons / dropdown / slider with only these values: name -> value + }; + struct Frame { // One add_gui_binding call + std::string description; + std::vector elements; + bool horizontal = false; // Elements in a row, wrapping into further rows at the window border, otherwise one element per row + }; struct Update { // GUI → display uint64_t id = 0; - std::vector keys; // Keys pressed in the GUI and not handled by it, display key codes - std::optional lineWidth; // For testing, to be replaced by generic settings updates - bool empty() const { return keys.empty() && !lineWidth; } + std::vector keys; // Keys pressed in the GUI and not handled by it, display key codes + std::vector> values; // Elements changed in the GUI: element id, new value + std::optional guiScale; // GUI scale changed with Ctrl + mouse wheel / Ctrl + 0 + bool empty() const { return keys.empty() && values.empty() && !guiScale; } }; struct State { // display → GUI Settings cfg; - uint64_t appliedUpdate = 0; // Id of the last GUI update included in cfg - uint64_t counter = 0; // Incremented with every published state + std::vector values; // Current values of the options of the GUI elements, by element id + float guiScale = 1.f; // GPUDisplay::control::mGuiScale + uint64_t appliedUpdate = 0; // Id of the last GUI update included in cfg + uint64_t counter = 0; // Incremented with every published state }; std::mutex mutex; // Protects all members below @@ -57,6 +85,8 @@ struct GPUDisplayGUIChannel { std::chrono::milliseconds replyTimeout; // Maximum time the GUI waits for a reply uint32_t maxUpdatesAhead = 0; // GUI waits for the reply if it is that many updates ahead of the last one it applied std::string windowName; // Name of the display window, set when starting the GUI + std::vector layout; // GUI elements, set when starting the GUI + std::function updatePosted; // Set by the display, called by the GUI (without holding mutex) after posting an update, wakes up the display }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx new file mode 100644 index 0000000000000..73b11a253c0d4 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayHelp.cxx +/// \author David Rohr + +#include "GPUDisplay.h" +#include "GPULogging.h" + +#include + +using namespace o2::gpu; + +void GPUDisplay::PrintHelp() +{ + mInfoHelpTimer.ResetStart(); + for (const auto& line : mFrontend->getHelpText()) { + GPUInfo("%s", line.c_str()); + } +} + +void GPUDisplay::PrintGLHelpText(float colorValue) +{ + const auto& lines = mFrontend->getHelpText(); + const int32_t maxChars = mBackend->mFontCharWidth > 0.f ? (mBackend->mScreenWidth / mFrontend->getDisplayScale() - 80.f) / mBackend->mFontCharWidth : 0; // Characters fitting between the margins, 0 = unknown (no Freetype font), no wrapping + uint32_t row = 0; + auto print = [&](const char* s, uint32_t nCharacters, uint32_t indent) { GLPrintText(s, 40.f + indent * mBackend->mFontCharWidth, 35 + std::max(20, mDrawTextFontSize + 4) * (1 + row++), colorValue, colorValue, colorValue, mInfoHelpTimer.GetCurrentElapsedTime() >= 5 ? (6 - mInfoHelpTimer.GetCurrentElapsedTime()) : 1, nCharacters, false); }; + for (const auto& line : lines) { + if (maxChars <= 0 || line.size() <= (size_t)maxChars) { + print(line.c_str(), 0, 0); + continue; + } + const size_t textStart = line.find(": "); + const size_t indent = textStart != std::string::npos && textStart + 2 < (size_t)maxChars / 2 ? textStart + 2 : 0; // Continuation lines aligned with the text after the keys, if that leaves enough space + size_t pos = 0; // Start of the part not yet printed + for (bool first = true;; first = false) { // Wrap at the last space that fits, or hard if there is none (also if it is only in the keys) + const size_t lineIndent = first ? 0 : indent, available = maxChars - lineIndent; + if (line.size() - pos <= available) { + print(line.c_str() + pos, line.size() - pos, lineIndent); + break; + } + size_t cut = line.rfind(' ', pos + available); + if (cut == std::string::npos || cut <= pos + (first ? indent : 0)) { + cut = pos + available; + } + print(line.c_str() + pos, cut - pos, lineIndent); + pos = line[cut] == ' ' ? cut + 1 : cut; + } + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx deleted file mode 100644 index b7c5c55cfdf22..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ /dev/null @@ -1,564 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// \file GPUDisplayKeys.cxx -/// \author David Rohr - -#include "GPUDisplay.h" -#include "frontend/GPUDisplayInfo.inc" - -using namespace o2::gpu; - -const char* HelpText[] = { - "[ESC] Quit", - "[n] Next event", - "[r] Reset Display Settings", - "[l] / [k] / [J] Draw single sector (next / previous sector), draw related sectors (same plane in phi)", - "[;] / [:] Show splitting of TPC in sectors by extruding volume, [:] resets", - "[#] Invert colors", - "[y] / [Y] / ['] / [X] / [M] Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)", - "[>] / [<] Toggle config interpolation during Animation / change Animation interval (via movement)", - "[g] Draw Grid", - "[i] Project onto XY-plane", - "[x] Exclude Clusters used in the tracking steps enabled for visualization ([1]-[8])", - "[.] Exclude rejected tracks", - "[c] Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20", - "[z] Mark fake attached clusters", - "[B] Mark clusters attached as adjacent", - "[L] / [K] Draw single collisions (next / previous)", - "[C] Colorcode clusters of different collisions", - "[v] Hide rejected clusters from tracks", - "[j] Show tracks segments propagated to adjacent sector in different color / splt CE tracks", - "[u] Cycle through track filter", - "[E] / [G] Extrapolate tracks / loopers", - "[t] / [T] Take Screenshot / Record Animation to pictures", - "[Z] Change screenshot resolution (scaling factor)", - "[S] / [A] / [D] Enable or disable smoothing of points / smoothing of lines / depth buffer", - "[W] / [U] / [V] Toggle anti-aliasing (MSAA at raster level / change downsampling FSAA factor) / toggle VSync", - "[F] / [_] / [R] Switch mFullScreen / Maximized window / FPS limit (none, refresh rate, fpsRate)", - "[I] Enable / disable GL indirect draw", - "[o] / [p] / [O] / [P] Save / restore current camera position / Animation path", - "[h] Print Help", - "[H] Show info texts", - "[q] Start / Stop Qt GUI", - "[w] / [s] / [a] / [d] Zoom / Strafe Left and Right", - "[pgup] / [pgdn] Strafe up / down", - "[e] / [f] Rotate left / right", - "[+] / [-] Increase / decrease point size (Hold SHIFT for lines)", - "[b] Change FOV (field of view)", - "[m] Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines)", - "[MOUSE 1] Look around", - "[MOUSE 2] Strafe camera", - "[MOUSE 1+2] Zoom / Rotate", - "[SHIFT] Slow Zoom / Move / Rotate", - "[ALT] / [CTRL] / [ENTER] Focus camera on origin / orient y-axis upwards (combine with [SHIFT] to lock) / Cycle through modes", - "[RCTRL] / [RALT] Rotate model instead of camera / rotate TPC around beamline", - "[1] ... [8] / [N] Enable display of clusters, preseeds, seeds, starthits, tracklets, tracks, extrapolated tracks, merged tracks / Show assigned clusters in colors", - "[F1] / [F2] / [F3] / [F4] Enable / disable drawing of TPC / TRD / TOF / ITS", - "[SHIFT] + [F1] to [F4] Enable / disable track detector filter", - "[SHIFT] + [F12] Switch track detector filter between AND and OR mode" - // FREE: [SPACE] [Q] - // Test setting: ^ -}; - -void GPUDisplay::PrintHelp() -{ - mInfoHelpTimer.ResetStart(); - for (uint32_t i = 0; i < sizeof(HelpText) / sizeof(HelpText[0]); i++) { - GPUInfo("%s", HelpText[i]); - } -} - -void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) -{ - const GPUSettingsDisplayHeavy oldCfgH = mCfgH; - const GPUSettingsDisplayLight oldCfgL = mCfgL; - const GPUSettingsDisplayRenderer oldCfgR = mCfgR; - if (key == 'n') { - mFrontend->mDisplayControl = 1; - SetInfo("Showing next event", 1); - } else if (key == 27 || key == mFrontend->KEY_ESCAPE) { - mFrontend->mDisplayControl = 2; - SetInfo("Exiting", 1); - } else if (key == 'r') { - mResetScene = true; - SetInfo("View reset", 1); - } else if (key == mFrontend->KEY_ALT && mFrontend->mKeysShift[mFrontend->KEY_ALT]) { - mCfgR.camLookOrigin ^= 1; - mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * mCfgR.camYUp; - SetInfo("Camera locked on origin: %s", mCfgR.camLookOrigin ? "enabled" : "disabled"); - } else if (key == mFrontend->KEY_CTRL && mFrontend->mKeysShift[mFrontend->KEY_CTRL]) { - mCfgR.camYUp ^= 1; - mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * mCfgR.camYUp; - SetInfo("Camera locked on y-axis facing upwards: %s", mCfgR.camYUp ? "enabled" : "disabled"); - } else if (key == mFrontend->KEY_ENTER) { - mCfgR.cameraMode++; - if (mCfgR.cameraMode == 4) { - mCfgR.cameraMode = 0; - } - mCfgR.camLookOrigin = mCfgR.cameraMode & 1; - mCfgR.camYUp = mCfgR.cameraMode & 2; - const char* modeText[] = {"Descent (free movement)", "Focus locked on origin (y-axis forced upwards)", "Spectator (y-axis forced upwards)", "Focus locked on origin (with free rotation)"}; - SetInfo("Camera mode %d: %s", mCfgR.cameraMode, modeText[mCfgR.cameraMode]); - } else if (key == mFrontend->KEY_ALT) { - mFrontend->mKeys[mFrontend->KEY_CTRL] = false; // Release CTRL with alt, to avoid orienting along y automatically! - } else if (key == 'l') { - if (mCfgL.drawSector >= (mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1))) { - mCfgL.drawSector = -1; - SetInfo("Showing all sectors", 1); - } else { - mCfgL.drawSector++; - SetInfo("Showing sector %d", mCfgL.drawSector); - } - } else if (key == 'k') { - if (mCfgL.drawSector <= -1) { - mCfgL.drawSector = mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1); - } else { - mCfgL.drawSector--; - } - if (mCfgL.drawSector == -1) { - SetInfo("Showing all sectors", 1); - } else { - SetInfo("Showing sector %d", mCfgL.drawSector); - } - } else if (key == 'J') { - mCfgL.drawRelatedSectors ^= 1; - SetInfo("Drawing of related sectors %s", mCfgL.drawRelatedSectors ? "enabled" : "disabled"); - } else if (key == 'L') { - if (mCfgL.showCollision >= mNCollissions - 1) { - mCfgL.showCollision = -1; - SetInfo("Showing all collisions", 1); - } else { - mCfgL.showCollision++; - SetInfo("Showing collision %d / %d", mCfgL.showCollision, mNCollissions); - } - } else if (key == 'K') { - if (mCfgL.showCollision <= -1) { - mCfgL.showCollision = mNCollissions - 1; - } else { - mCfgL.showCollision--; - } - if (mCfgL.showCollision == -1) { - SetInfo("Showing all collisions", 1); - } else { - SetInfo("Showing collision %d / %d", mCfgL.showCollision, mNCollissions); - } - } else if (key == 'F') { - mCfgR.fullScreen ^= 1; - SetInfo("Toggling full screen (%d)", (int32_t)mCfgR.fullScreen); - } else if (key == '_') { - mCfgR.maximized ^= 1; - SetInfo("Toggling Maximized window (%d)", (int32_t)mCfgR.maximized); - } else if (key == 'R') { - mCfgR.fpsLimit = (mCfgR.fpsLimit + 1) % 3; - if (mCfgR.fpsLimit == GPUDisplayBackend::FPS_LIMIT_RATE) { - SetInfo("FPS limit: %d FPS", mCfgR.fpsRate); - } else if (mCfgR.fpsLimit == GPUDisplayBackend::FPS_LIMIT_VSYNC) { - char rate[32] = "unknown rate"; - if (mFrontend->getRefreshRate() > 0.f) { - snprintf(rate, sizeof(rate), "%.1f Hz", mFrontend->getRefreshRate()); - } - const int32_t mode = mBackend->fpsLimitMode(); - if (mode == GPUDisplayBackend::FPS_LIMIT_NONE) { - SetInfo("FPS limit: refresh rate (%s) - VSync disabled, no limit", rate); - } else if (mode == GPUDisplayBackend::FPS_LIMIT_RATE) { - SetInfo("FPS limit: refresh rate (%s) - not supported by the backend, fallback to %d FPS", rate, mCfgR.fpsRate); - } else { - SetInfo("FPS limit: refresh rate (%s)", rate); - } - } else { - SetInfo("FPS limit: none"); - } - } else if (key == 'H') { - mPrintInfoText += 1; - mPrintInfoText &= 3; - SetInfo("Info text display - console: %s, onscreen %s", (mPrintInfoText & 2) ? "enabled" : "disabled", (mPrintInfoText & 1) ? "enabled" : "disabled"); - } else if (key == 'j') { - if (mCfgH.separateExtrapolatedTracks) { - mCfgH.splitCETracks ^= 1; - } - mCfgH.separateExtrapolatedTracks ^= 1; - SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", mCfgH.separateExtrapolatedTracks ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled"); - } else if (key == 'c') { - if (mCfgH.markClusters == 0) { - mCfgH.markClusters = 1; - } else if (mCfgH.markClusters >= 0x20) { - mCfgH.markClusters = 0; - } else { - mCfgH.markClusters <<= 1; - } - SetInfo("Cluster flag highlight mask set to %d (%s)", mCfgH.markClusters, - mCfgH.markClusters == 0 ? "off" : mCfgH.markClusters == 1 ? "split pad" : mCfgH.markClusters == 2 ? "split time" : mCfgH.markClusters == 4 ? "edge" : mCfgH.markClusters == 8 ? "singlePad" : mCfgH.markClusters == 0x10 ? "reject distance" : "reject error"); - } else if (key == 'z') { - mCfgH.markFakeClusters ^= 1; - SetInfo("Marking fake clusters: %s", mCfgH.markFakeClusters ? "on" : "off"); - } else if (key == 'b') { - if ((mCfgR.fov += 5) > 175) { - mCfgR.fov = 5; - } - SetInfo("Set FOV to %f", mCfgR.fov); - } else if (key == 'm') { -#ifdef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - SetInfo("OpenGL compat profile not available, using core profile", 1); - } else -#endif - { - mCfgR.rendererCompatSmoothing ^= 1; - SetInfo("Using renderer path for %s profile", mCfgR.rendererCompatSmoothing ? "compat" : "core (shader)"); - } - } else if (key == 'B') { - mCfgH.markAdjacentClusters++; - if (mCfgH.markAdjacentClusters == 5) { - mCfgH.markAdjacentClusters = 7; - } - if (mCfgH.markAdjacentClusters == 9) { - mCfgH.markAdjacentClusters = 15; - } - if (mCfgH.markAdjacentClusters == 17) { - mCfgH.markAdjacentClusters = 31; - } - if (mCfgH.markAdjacentClusters == 34) { - mCfgH.markAdjacentClusters = 0; - } - if (mCfgH.markAdjacentClusters == 33) { - SetInfo("Marking protected clusters (%d)", mCfgH.markAdjacentClusters); - } else if (mCfgH.markAdjacentClusters == 32) { - SetInfo("Marking removable clusters (%d)", mCfgH.markAdjacentClusters); - } else { - SetInfo("Marking adjacent clusters (%d): rejected %s, tube %s, looper leg %s, low Pt %s, high incl %s", mCfgH.markAdjacentClusters, (mCfgH.markAdjacentClusters & 1) ? "yes" : " no", (mCfgH.markAdjacentClusters & 2) ? "yes" : " no", (mCfgH.markAdjacentClusters & 4) ? "yes" : " no", (mCfgH.markAdjacentClusters & 8) ? "yes" : " no", (mCfgH.markAdjacentClusters & 16) ? "yes" : " no"); - } - } else if (key == 'C') { - mCfgL.colorCollisions ^= 1; - SetInfo("Color coding of collisions %s", mCfgL.colorCollisions ? "enabled" : "disabled"); - } else if (key == 'N') { - mCfgL.colorClusters ^= 1; - SetInfo("Color coding for seed / trrack attachmend %s", mCfgL.colorClusters ? "enabled" : "disabled"); - } else if (key == 'E') { - mCfgL.propagateTracks += 1; - if (mCfgL.propagateTracks == 4) { - mCfgL.propagateTracks = 0; - } - const char* infoText[] = {"Hits connected", "Hits connected and propagated to vertex", "Reconstructed track propagated inwards and outwards", "Monte Carlo track"}; - SetInfo("Display of propagated tracks: %s", infoText[mCfgL.propagateTracks]); - } else if (key == 'G') { - mCfgH.propagateLoopers ^= 1; - SetInfo("Propagation of loopers %s", mCfgH.propagateLoopers ? "enabled" : "disabled"); - } else if (key == 'v') { - mCfgH.hideRejectedClusters ^= 1; - SetInfo("Rejected clusters are %s", mCfgH.hideRejectedClusters ? "hidden" : "shown"); - } else if (key == 'i') { - mCfgH.projectXY ^= 1; - SetInfo("Projection onto xy plane %s", mCfgH.projectXY ? "enabled" : "disabled"); - } else if (key == 'S') { - mCfgL.smoothPoints ^= true; - SetInfo("Smoothing of points %s", mCfgL.smoothPoints ? "enabled" : "disabled"); - } else if (key == 'A') { - mCfgL.smoothLines ^= true; - SetInfo("Smoothing of lines %s", mCfgL.smoothLines ? "enabled" : "disabled"); - } else if (key == 'D') { - mCfgL.depthBuffer ^= true; - SetInfo("Depth buffer (z-buffer, %u bits) %s", mBackend->DepthBits(), mCfgL.depthBuffer ? "enabled" : "disabled"); - mBackend->setDepthBuffer(); - } else if (key == 'W') { - mCfgR.drawQualityMSAA *= 2; - if (mCfgR.drawQualityMSAA < 2) { - mCfgR.drawQualityMSAA = 2; - } - if (mCfgR.drawQualityMSAA > 16 || mCfgR.drawQualityMSAA > mBackend->getMaxMSAA()) { - mCfgR.drawQualityMSAA = 0; - } - SetInfo("Multisampling anti-aliasing factor set to %d", mCfgR.drawQualityMSAA); - } else if (key == 'U') { - mCfgR.drawQualityDownsampleFSAA++; - if (mCfgR.drawQualityDownsampleFSAA == 1) { - mCfgR.drawQualityDownsampleFSAA = 2; - } - if (mCfgR.drawQualityDownsampleFSAA == 5) { - mCfgR.drawQualityDownsampleFSAA = 0; - } - SetInfo("Downsampling anti-aliasing factor set to %d", mCfgR.drawQualityDownsampleFSAA); - } else if (key == 'V') { - mCfgR.drawQualityVSync ^= true; - SetInfo("VSync: %s", mCfgR.drawQualityVSync ? "enabled" : "disabled"); - } else if (key == 'I') { - mCfgR.useGLIndirectDraw ^= true; - SetInfo("OpenGL Indirect Draw %s", mCfgR.useGLIndirectDraw ? "enabled" : "disabled"); - } else if (key == ';') { - mCfgH.xAdd += 60; - mCfgH.zAdd += 60; - SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); - } else if (key == ':') { - mCfgH.xAdd -= 60; - mCfgH.zAdd -= 60; - if (mCfgH.zAdd < 0 || mCfgH.xAdd < 0) { - mCfgH.zAdd = mCfgH.xAdd = 0; - } - SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); - } else if (key == '#') { - mCfgL.invertColors ^= 1; - } else if (key == 'g') { - mCfgL.drawGrid ^= 1; - SetInfo("Fast Cluster Search Grid %s", mCfgL.drawGrid ? "shown" : "hidden"); - } else if (key == 'x') { - mCfgL.excludeClusters = (mCfgL.excludeClusters + 1) % 3; - SetInfo(mCfgL.excludeClusters ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", mCfgL.excludeClusters); - } else if (key == '.') { - mCfgH.hideRejectedTracks ^= 1; - SetInfo("Rejected tracks are %s", mCfgH.hideRejectedTracks ? "hidden" : "shown"); - } else if (key == '1') { - mCfgL.drawClusters ^= 1; - } else if (key == '2') { - mCfgL.drawInitLinks ^= 1; - } else if (key == '3') { - mCfgL.drawLinks ^= 1; - } else if (key == '4') { - mCfgL.drawSeeds ^= 1; - } else if (key == '5') { - mCfgL.drawTracklets ^= 1; - } else if (key == '6') { - mCfgL.drawTracks ^= 1; - } else if (key == '7') { - mCfgL.drawExtrapolatedTracks ^= 1; - } else if (key == '8') { - mCfgL.drawFinal ^= 1; - } else if (key == mFrontend->KEY_F1) { - if (mFrontend->mKeysShift[mFrontend->KEY_F1]) { - mCfgH.drawTPCTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawTPC ^= 1; - SetInfo("Showing TPC Clusters: %d", (int32_t)mCfgL.drawTPC); - } - } else if (key == mFrontend->KEY_F2) { - if (mFrontend->mKeysShift[mFrontend->KEY_F2]) { - mCfgH.drawTRDTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawTRD ^= 1; - SetInfo("Showing TRD Tracklets: %d", (int32_t)mCfgL.drawTRD); - } - } else if (key == mFrontend->KEY_F3) { - if (mFrontend->mKeysShift[mFrontend->KEY_F3]) { - mCfgH.drawTOFTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawTOF ^= 1; - SetInfo("Showing TOF Hits: %d", (int32_t)mCfgL.drawTOF); - } - } else if (key == mFrontend->KEY_F4) { - if (mFrontend->mKeysShift[mFrontend->KEY_F4]) { - mCfgH.drawITSTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawITS ^= 1; - SetInfo("Showing ITS Clusters: %d", (int32_t)mCfgL.drawITS); - } - } else if (key == mFrontend->KEY_F12 && mFrontend->mKeysShift[mFrontend->KEY_F12]) { - mCfgH.drawTracksAndFilter ^= 1; - SetInfo("Track filter: %s", mCfgH.drawTracksAndFilter ? "AND" : "OR"); - } else if (key == 't') { - static int32_t nScreenshot = 1; - char fname[32]; - snprintf(fname, 32, "screenshot%d.bmp", nScreenshot++); - mRequestScreenshot = true; - mScreenshotFile = fname; - SetInfo("Taking screenshot (%s)", fname); - } else if (key == 'Z') { - mCfgR.screenshotScaleFactor += 1; - if (mCfgR.screenshotScaleFactor == 5) { - mCfgR.screenshotScaleFactor = 1; - } - SetInfo("Screenshot scaling factor set to %d", mCfgR.screenshotScaleFactor); - } else if (key == 'y' || key == 'T') { - if (mAnimateVectors[0].size() > 1) { - if ((mAnimateScreenshot = (key == 'T'))) { - mAnimationExport++; - } - startAnimation(); - SetInfo("Starting Animation", 1); - } else { - SetInfo("Insufficient Animation points to start Animation", 1); - } - } else if (key == '>') { - mAnimationChangeConfig ^= 1; - SetInfo("Interpolating visualization settings during Animation %s", mAnimationChangeConfig ? "enabled" : "disabled"); - } else if (key == 'Y') { - setAnimationPoint(); - SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimateVectors[0].size(), mAnimateVectors[0].back()); - } else if (key == 'X') { - resetAnimation(); - SetInfo("Reset Animation points", 1); - } else if (key == '\'') { - removeAnimationPoint(); - SetInfo("Removed Animation point", 1); - } else if (key == 'M') { - mCfgL.animationMode++; - if (mCfgL.animationMode == 7) { - mCfgL.animationMode = 0; - } - resetAnimation(); - if (mCfgL.animationMode == 6) { - SetInfo("Animation mode %d - Centered on origin", mCfgL.animationMode); - } else { - SetInfo("Animation mode %d - Position: %s, Direction: %s", mCfgL.animationMode, (mCfgL.animationMode & 2) ? "Spherical (spherical rotation)" : (mCfgL.animationMode & 4) ? "Spherical (Euler angles)" : "Cartesian", (mCfgL.animationMode & 1) ? "Euler angles" : "Quaternion"); - } - } else if (key == 'u') { - mCfgH.trackFilter = (mCfgH.trackFilter + 1) % (mConfig.filterMacros.size() + 1); - mUpdateTrackFilter = true; - SetInfo("Track filter: %s", mCfgH.trackFilter == 0 ? "None" : mConfig.filterMacros[mCfgH.trackFilter - 1].c_str()); - } else if (key == 'o') { - FILE* ftmp = fopen("glpos.tmp", "w+b"); - if (ftmp) { - int32_t retval = fwrite(&mViewMatrix, sizeof(mViewMatrix), 1, ftmp); - if (retval != 1) { - GPUError("Error writing position to file"); - } else { - GPUInfo("Position stored to file"); - } - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Camera position stored to file", 1); - } else if (key == 'p') { - FILE* ftmp = fopen("glpos.tmp", "rb"); - if (ftmp) { - int32_t retval = fread(&mViewMatrix, 1, sizeof(mViewMatrix), ftmp); - if (retval == sizeof(mViewMatrix)) { - GPUInfo("Position read from file"); - } else { - GPUError("Error reading position from file"); - } - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Camera position loaded from file", 1); - } else if (key == 'O') { - FILE* ftmp = fopen("glanimation.tmp", "w+b"); - if (ftmp) { - fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp); - int32_t size = mAnimateVectors[0].size(); - fwrite(&size, sizeof(size), 1, ftmp); - for (int32_t i = 0; i < 9; i++) { - fwrite(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); - } - fwrite(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Animation path stored to file %s", "glanimation.tmp"); - } else if (key == 'P') { - FILE* ftmp = fopen("glanimation.tmp", "rb"); - if (ftmp) { - int32_t retval = fread(&mCfgL, sizeof(mCfgL), 1, ftmp); - int32_t size; - retval += fread(&size, sizeof(size), 1, ftmp); - for (int32_t i = 0; i < 9; i++) { - mAnimateVectors[i].resize(size); - retval += fread(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); - } - mAnimateConfig.resize(size); - retval += fread(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); - (void)retval; // disable unused warning - fclose(ftmp); - updateConfig(); - } else { - GPUError("Error opening file"); - } - SetInfo("Animation path loaded from file %s", "glanimation.tmp"); - } else if (key == 'h') { - if (mPrintInfoTextAlways) { - mPrintInfoTextAlways = false; - SetInfo("Showing help text disabled", 1); - } else if (mInfoHelpTimer.IsRunning()) { - mPrintInfoTextAlways = true; - mInfoHelpTimer.Reset(); - SetInfo("Showing help text until disabled", 1); - } else { - PrintHelp(); - SetInfo("Showing help text", 1); - } - } else if (key == 'q') { - if (mFrontend->isGUIRunning()) { - SetInfo("Stopping GUI", 1); - mFrontend->stopGUI(); - } else { - SetInfo("Starting GUI", 1); - mFrontend->startGUI(); - } - } - /* - else if (key == '^') - { - mTestSetting++; - SetInfo("Debug test variable set to %d", mTestSetting); - } - */ - - if (!doNotHandleConfigChanges) { - checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } -} - -void GPUDisplay::checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCfgH, const GPUSettingsDisplayLight& oldCfgL, const GPUSettingsDisplayRenderer& oldCfgR) -{ - if (memcmp((void*)&oldCfgH, (void*)&mCfgH, sizeof(mCfgH)) != 0) { - mUpdateEventData = true; - } - if (mCfgL.propagateTracks != 0 && !mTracksArePropagated) { - mUpdateVertexLists = true; - } - if (memcmp((void*)&oldCfgL, (void*)&mCfgL, sizeof(mCfgL)) != 0 || memcmp((void*)&oldCfgR, (void*)&mCfgR, sizeof(mCfgR)) != 0) { - mUpdateDrawCommands = true; - } - if (oldCfgR.drawQualityMSAA != mCfgR.drawQualityMSAA || oldCfgR.drawQualityDownsampleFSAA != mCfgR.drawQualityDownsampleFSAA || oldCfgL.depthBuffer != mCfgL.depthBuffer || oldCfgR.screenshotScaleFactor != mCfgR.screenshotScaleFactor) { - mUpdateRenderPipeline = true; - } - - if (oldCfgR.drawQualityVSync != mCfgR.drawQualityVSync) { - mFrontend->SetVSync(mCfgR.drawQualityVSync); - mBackend->SetVSync(mCfgR.drawQualityVSync); - } - if (oldCfgR.fullScreen != mCfgR.fullScreen) { - mFrontend->SwitchFullscreen(mCfgR.fullScreen); - } - if (oldCfgR.maximized != mCfgR.maximized) { - mFrontend->ToggleMaximized(mCfgR.maximized); - } - if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { - mUpdateEventData = true; - } -} - -void GPUDisplay::HandleExternalGUIKey(int32_t key) -{ - // GPUError("key %d '%c'", key, (char) key); - - bool shifted = key >= 'A' && key <= 'Z'; - int32_t press = key; - if (press >= 'a' && press <= 'z') { - press += 'A' - 'a'; - } - bool oldShift = mFrontend->mKeysShift[press]; - mFrontend->mKeysShift[press] = shifted; - HandleKey(key, true); - mFrontend->mKeysShift[press] = oldShift; -} - -void GPUDisplay::PrintGLHelpText(float colorValue) -{ - for (uint32_t i = 0; i < sizeof(HelpText) / sizeof(HelpText[0]); i++) { - OpenGLPrint(HelpText[i], 40.f, 35 + std::max(20, mDrawTextFontSize + 4) * (1 + i), colorValue, colorValue, colorValue, mInfoHelpTimer.GetCurrentElapsedTime() >= 5 ? (6 - mInfoHelpTimer.GetCurrentElapsedTime()) : 1, false); - } -} diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index bb2bacfc08434..e8c1e16ff9b9e 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -105,11 +105,11 @@ void GPUDisplay::startAnimation() mAnimationMixBase = -1; } -int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix) +int32_t GPUDisplay::animateCamera(bool suspended, float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix) { float time = animateTime; if (mAnimate && time < 0) { - if (mAnimateScreenshot) { + if (mCfgC.mAnimateScreenshot) { time = mAnimationFrame / 30.f; } else { time = mAnimationTimer.GetCurrentElapsedTime(); @@ -118,7 +118,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ float maxTime = mAnimateVectors[0].back(); if (time >= maxTime) { time = maxTime; - mAnimate = mAnimateScreenshot = 0; + mAnimate = mCfgC.mAnimateScreenshot = 0; SetInfo("Animation finished. (%1.2f seconds, %d frames)", time, mAnimationFrame); } else { SetInfo("Running mAnimation: time %1.2f/%1.2f, frames %d", time, maxTime, mAnimationFrame); @@ -132,7 +132,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ for (int32_t i = 0; i < 8; i++) { vals[i] = mAnimationSplines[i].evaluate(time); } - if (mAnimationChangeConfig && animateTime < 0) { + if (mCfgC.mAnimationChangeConfig && animateTime < 0) { int32_t base = 0; int32_t k = mAnimateVectors[0].size() - 1; while (base < k && time > mAnimateVectors[0][base]) { @@ -152,7 +152,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ if (mixSlaveImage > 0) { mCfgL = mAnimateConfig[mAnimationLastBase]; updateConfig(); - DrawGLScene_internal(time, true); + DrawGLScene_internal(suspended, time, true); } } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index d0a6685efc742..b08a55967eda4 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -102,15 +102,15 @@ void GPUDisplay::showInfo(const char* info) mBackend->updateFontScale(mFrontend->getDisplayScale()); mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; - OpenGLPrint(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); + GLPrintText(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); if (mInfoText2Timer.IsRunning()) { if (mInfoText2Timer.GetCurrentElapsedTime() >= 6) { mInfoText2Timer.Reset(); } else { - OpenGLPrint(mInfoText2, 40.f, 20.f, colorValue, colorValue, colorValue, 6 - mInfoText2Timer.GetCurrentElapsedTime()); + GLPrintText(mInfoText2, 40.f, 20.f, colorValue, colorValue, colorValue, 6 - mInfoText2Timer.GetCurrentElapsedTime()); } } - if (mInfoHelpTimer.IsRunning() || mPrintInfoTextAlways) { + if (mInfoHelpTimer.IsRunning() || mCfgC.mPrintInfoTextAlways) { if (mInfoHelpTimer.GetCurrentElapsedTime() >= 6) { mInfoHelpTimer.Reset(); } else { @@ -120,7 +120,7 @@ void GPUDisplay::showInfo(const char* info) mBackend->finishText(); } -void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) +void GPUDisplay::GLPrintText(const char* s, float x, float y, float r, float g, float b, float a, uint32_t nCharacters, bool fromBotton) { if (mBackend->mFreetypeInitialized) { const float scale = mFrontend->getDisplayScale(); @@ -130,8 +130,6 @@ void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, y = mBackend->mScreenHeight - y; } float color[4] = {r, g, b, a}; - mBackend->OpenGLPrint(s, x, y, color, scale / mBackend->mFontRasterScale); - } else if (mFrontend->mCanDrawText) { - mFrontend->OpenGLPrint(s, x, y, r, g, b, a, fromBotton); + mBackend->GLPrintText(s, x, y, color, scale / mBackend->mFontRasterScale, nCharacters); } } diff --git a/GPU/GPUTracking/utils/qconfig.cxx b/GPU/GPUTracking/utils/qconfig.cxx index 8a15a6ecdd9ef..ddfe56ae78cb6 100644 --- a/GPU/GPUTracking/utils/qconfig.cxx +++ b/GPU/GPUTracking/utils/qconfig.cxx @@ -17,11 +17,17 @@ #include #include #include +#include +#include #include #include +#include +#include +#include #include #include "qconfig.h" #include "qconfig_helpers.h" +#include "qconfig_rtti.h" #include "qconfig_internal_helpers.h" // Create config instances @@ -41,19 +47,6 @@ static inline const char* getOptName(const char** argv, int32_t i) return (argv[i]); } -template -struct qConfigSettings { - qConfigSettings() : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(nullptr), allowEmpty(false) {}; - template - qConfigSettings(const qConfigSettings v) : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(v.message), allowEmpty(v.allowEmpty){}; - bool checkMin, checkMax; - bool doSet, doDefault; - T min, max; - T set; - const char* message; - bool allowEmpty; -}; - template static int32_t qAddOptionType(qConfigSettings& settings, T& ref, int32_t& i, const char** argv, const int argc, T def); template @@ -61,15 +54,6 @@ static void qAddOptionMessage(qConfigSettings& settings, T& ref); template static int32_t qAddOptionMinMax(qConfigSettings& settings, T& ref, const char* arg); -template -struct qSettingsType { - typedef T settingsType; -}; -template -struct qSettingsType> { - typedef int32_t settingsType; -}; - template struct qConfigTypeSpecialized { static inline int32_t qAddOptionMain(qConfigSettings& settings, T& ref, int32_t& i, const char** argv, const int argc, T def); @@ -130,41 +114,8 @@ inline int32_t qConfigTypeSpecialized>::qAddOptionMain(qConfigS } template -struct qConfigType { - // Recursive handling of additional settings - static inline void qProcessSetting(qConfigSettings& settings, qmin_t minval) - { - static_assert(!std::is_same_v, "min option not supported for boolean settings"); - settings.checkMin = true; - settings.min = minval.v; - } - static inline void qProcessSetting(qConfigSettings& settings, qmax_t maxval) - { - static_assert(!std::is_same_v, "max option not supported for boolean settings"); - settings.checkMax = true; - settings.max = maxval.v; - } - static inline void qProcessSetting(qConfigSettings& settings, qmessage_t msg) { settings.message = msg.v; } - template - static inline void qProcessSetting(qConfigSettings& settings, qset_t set) - { - settings.doSet = true; - settings.set = set.v; - } - template - static inline void qProcessSetting(qConfigSettings& settings, qdef_t set) - { - settings.doDefault = true; - settings.set = set.v; - } - - static inline void qAddOptionSettings(qConfigSettings& /*settings*/) {} - template - static inline void qAddOptionSettings(qConfigSettings& settings, Arg1&& arg1, Args&&... args) - { - qProcessSetting(settings, arg1); - qAddOptionSettings(settings, args...); - } +struct qConfigType : public qConfigSettingsParser { + using qConfigSettingsParser::qConfigGetSettings; template static inline int32_t qAddOption(T& ref, int32_t& i, const char** argv, const int argc, T def, const char* /*help*/, Args&&... args) @@ -264,14 +215,6 @@ struct qConfigType { } std::cout << "\n"; } - - template - static inline auto qConfigGetSettings(Args&... args) - { - qConfigSettings::settingsType> settings; - qConfigType::settingsType>::qAddOptionSettings(settings, args...); - return (settings); - } }; static inline const char* getArg(int32_t& i, const char** argv, const int argc, bool allowOption = false) @@ -480,10 +423,7 @@ int32_t qConfigParse(int argc, const char** argv, const char* /*filename*/) return (0); } -std::vector> qprint_global; -#define QCONFIG_PRINT -#include "qconfig_internal.h" -#undef QCONFIG_PRINT +#include "qconfig_print.inc" void qConfigPrint() { @@ -491,4 +431,10 @@ void qConfigPrint() f(); } } +namespace internal +{ +#include "qconfig_genrtti.inc" +} // namespace internal + +std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs) { return internal::qConfigGenRTTI(configStructs); } } // end namespace qConfig diff --git a/GPU/GPUTracking/utils/qconfig_general.h b/GPU/GPUTracking/utils/qconfig_general.h index 225808107a4cf..6804a91666ef0 100644 --- a/GPU/GPUTracking/utils/qconfig_general.h +++ b/GPU/GPUTracking/utils/qconfig_general.h @@ -17,16 +17,21 @@ #ifndef GPUCA_GPUCODE_DEVICE #include #include +#include +#include #endif namespace qConfig { +struct qConfigRTTIentry; #ifndef GPUCA_GPUCODE_DEVICE -int32_t qConfigParse(int argc, const char** argv, const char* filename = nullptr); -void qConfigPrint(); // Print all settings +int32_t qConfigParse(int argc, const char** argv, const char* filename = nullptr); // Parse qConfig options +void qConfigPrint(); // Print all settings template -void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName = ""); // Print one sub config, specializations created in qconfig.cxx +void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName = ""); // Print one sub config, specializations created in qconfig.cxx +std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs = nullptr); // Entries and list of config structs, for the given config structs (all if nullptr) #endif + enum qConfigRetVal { qcrOK = 0, qcrError = 1, qcrMinFailure = 2, diff --git a/GPU/GPUTracking/utils/qconfig_genrtti.inc b/GPU/GPUTracking/utils/qconfig_genrtti.inc new file mode 100644 index 0000000000000..b309727799f9e --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_genrtti.inc @@ -0,0 +1,96 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file qconfig_genrtti.inc +/// \author David Rohr + +// Provides static inline qConfigGenRTTI() in the namespace it is included in. +// Requires , , , , , "qconfig_rtti.h" (brings , , ), "qconfig_internal_helpers.h" to be included before (outside of the namespace), and the config structs to be visible unqualified. + +#ifndef QCONFIG_GENRTTI_INC +#define QCONFIG_GENRTTI_INC + +template +static inline T qConfigRTTICheckedCast(size_t v, const char* what) +{ + if (v > std::numeric_limits::max()) { + throw std::runtime_error(std::string("qConfig RTTI: ") + what + " " + std::to_string(v) + " exceeds the range of the entry"); + } + return static_cast(v); +} + +template +constexpr bool qConfigRTTISelected = sizeof...(Structs) == 0 || (std::is_same_v || ...); // Compile-time filter, all structs if none given + +template +static inline std::optional qConfigRTTIStructIndex(std::vector& configStructs, const char* name, bool filter) // Index of struct S, position in Structs if given, else of name in configStructs (appended if not filtering, nullopt if filtered out) +{ + if constexpr (sizeof...(Structs) > 0) { + constexpr size_t index = [] { size_t i = 0; ((std::is_same_v ? false : (++i, true)) && ...); return i; }(); + configStructs[index] = name; + return index; + } + if (auto it = std::find(configStructs.begin(), configStructs.end(), name); it != configStructs.end()) { + return it - configStructs.begin(); + } + if (filter) { + return std::nullopt; + } + configStructs.emplace_back(name); + return configStructs.size() - 1; +} + +template +concept qConfigRTTIentryType = std::derived_from && std::is_aggregate_v; // Aggregate, so it can be initialized from the base entry; extra members need default initializers + +template +static inline void qConfigRTTIEntry(std::vector& retVal, R entry, size_t arraySize, size_t configStructIndex, std::string defaultValue, const char* help, Args&&... args) // Complete the entry, check the narrow members for overflow +{ + auto settings = ::qConfig::qConfigSettingsParser::qConfigGetSettings(args...); + entry.size = qConfigRTTICheckedCast(sizeof(T), "size"); + entry.arraySize = qConfigRTTICheckedCast(arraySize, "arraySize"); + entry.configStructIndex = qConfigRTTICheckedCast(configStructIndex, "configStructIndex"); + entry.defaultValue = std::move(defaultValue); + entry.description = help; + if (settings.checkMin) { + entry.min = ::qConfig::print_type(settings.min); + } + if (settings.checkMax) { + entry.max = ::qConfig::print_type(settings.max); + } + if (settings.message) { + entry.message = settings.message; + } + retVal.emplace_back(std::move(entry)); +} + +template +static inline std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs = nullptr) // Entries (of type T, derived from qConfigRTTIentry) and list of config structs. Filter: struct types Structs at compile time (code only generated for those), else the names in configStructs at runtime (all if nullptr) +{ + using namespace ::qConfig; // For the settings helpers (min, max, ...) and print_type used in GPUSettingsList.h / qconfig_internal.h + std::vector retVal; + std::vector retStructs; + if constexpr (sizeof...(Structs) > 0) { + retStructs.resize(sizeof...(Structs)); + } else if (configStructs) { + retStructs = *configStructs; + } +#define QCONFIG_GEN_RTTI +#include "qconfig_internal.h" +#undef QCONFIG_GEN_RTTI +#undef QCONFIG_RTTI + if (std::find(retStructs.begin(), retStructs.end(), "") != retStructs.end()) { + throw std::runtime_error("qConfigGenRTTI: requested type is not a config struct"); + } + return {std::move(retVal), std::move(retStructs)}; +} + +#endif // QCONFIG_GENRTTI_INC diff --git a/GPU/GPUTracking/utils/qconfig_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index 04ecced0cb34a..17ac6b55cb530 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -18,8 +18,6 @@ #include "qconfig_general.h" #include "qconfig_helpers.h" -#if !defined(QCONFIG_HEADER_GUARD_NO_INCLUDE) || defined(QCONFIG_GENRTC) - #ifdef QCONFIG_PARSE #define QCONFIG_COMPARE(name, optname, optnameshort) \ @@ -262,6 +260,31 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // #define AddOptionSet(...) // End QCONFIG_PRINT_RTC +#elif defined(QCONFIG_GEN_RTTI) +#define QCONFIG_RTTI(vName, vType, rtc, variable, vector, count, defaultValue, ...) \ + qConfigRTTIEntry(retVal, \ + T{::qConfig::qConfigRTTIentry{.name = qon_mxstr(vName), .type = qon_mxstr(vType), .isRTC = rtc, .isVariable = variable, .isVector = vector, .offset = static_cast(reinterpret_cast(&qConfigObject.vName) - reinterpret_cast(&qConfigObject)), .configStruct = qConfigStruct}}, \ + count, *qConfigStructIndex, defaultValue, __VA_ARGS__); +#define AddOption(name, type, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, false, false, false, 0, print_type(static_cast(default)), __VA_ARGS__) +#define AddOptionRTC(name, type, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, true, false, false, 0, print_type(static_cast(default)), __VA_ARGS__) +#define AddVariable(name, type, default) QCONFIG_RTTI(name, type, false, true, false, 0, print_type(static_cast(default)), "") +#define AddVariableRTC(name, type, default) QCONFIG_RTTI(name, type, true, true, false, 0, print_type(static_cast(default)), "") +#define AddOptionVec(name, type, optname, optnameshort, ...) QCONFIG_RTTI(name, type, false, false, true, 0, "", __VA_ARGS__) +#define AddOptionArray(name, type, count, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, false, false, false, count, qon_mxstr(default), __VA_ARGS__) +#define AddOptionArrayRTC(name, type, count, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, true, false, false, count, qon_mxstr(default), __VA_ARGS__) +#define AddOptionSet(...) +#define AddSubConfig(name, instance) +#define BeginConfig(name, instance) \ + if constexpr (qConfigRTTISelected) { \ + if (const auto qConfigStructIndex = qConfigRTTIStructIndex(retStructs, qon_mxstr(name), configStructs != nullptr)) { \ + [[maybe_unused]] constexpr const char* qConfigStruct = qon_mxstr(name); \ + [[maybe_unused]] const name qConfigObject{}; // Default-constructed instance to compute the member offsets +#define BeginSubConfig(name, instance, parent, preoptname, preoptnameshort, descr, ...) BeginConfig(name, instance) +#define EndConfig() \ + } \ + } + +// End QCONFIG_GEN_RTTI #else // Define structures #error Internal error, qconfig_internal called with action type @@ -269,5 +292,3 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // #endif #include "qconfig_wrapinclude.h" - -#endif // QCONFIG_HEADER_GUARD_NO_INCLUDE diff --git a/GPU/GPUTracking/utils/qconfig_internal_helpers.h b/GPU/GPUTracking/utils/qconfig_internal_helpers.h index c3702e819256b..89ef328e9de4f 100644 --- a/GPU/GPUTracking/utils/qconfig_internal_helpers.h +++ b/GPU/GPUTracking/utils/qconfig_internal_helpers.h @@ -15,6 +15,10 @@ #ifndef QCONFIG_INTERNAL_HELPERS_H #define QCONFIG_INTERNAL_HELPERS_H +#include "qconfig_helpers.h" +#include +#include + namespace qConfig { #define QCONFIG_SETTING(name, type) \ @@ -41,6 +45,74 @@ QCONFIG_SETTING_TEMPLATE(min) QCONFIG_SETTING_TEMPLATE(max) QCONFIG_SETTING_TEMPLATE(set) QCONFIG_SETTING_TEMPLATE(def) + +template +struct qConfigSettings { + qConfigSettings() : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(nullptr), allowEmpty(false) {}; + template + qConfigSettings(const qConfigSettings v) : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(v.message), allowEmpty(v.allowEmpty) {}; + bool checkMin, checkMax; + bool doSet, doDefault; + T min, max; + T set; + const char* message; + bool allowEmpty; +}; + +template +struct qSettingsType { + typedef T settingsType; +}; +template +struct qSettingsType> { + typedef int32_t settingsType; +}; + +template +struct qConfigSettingsParser { // Parse the additional settings (min, max, set, def, message) of an option + // Recursive handling of additional settings + static inline void qProcessSetting(qConfigSettings& settings, qmin_t minval) + { + static_assert(!std::is_same_v, "min option not supported for boolean settings"); + settings.checkMin = true; + settings.min = minval.v; + } + static inline void qProcessSetting(qConfigSettings& settings, qmax_t maxval) + { + static_assert(!std::is_same_v, "max option not supported for boolean settings"); + settings.checkMax = true; + settings.max = maxval.v; + } + static inline void qProcessSetting(qConfigSettings& settings, qmessage_t msg) { settings.message = msg.v; } + template + static inline void qProcessSetting(qConfigSettings& settings, qset_t set) + { + settings.doSet = true; + settings.set = set.v; + } + template + static inline void qProcessSetting(qConfigSettings& settings, qdef_t set) + { + settings.doDefault = true; + settings.set = set.v; + } + + static inline void qAddOptionSettings(qConfigSettings& /*settings*/) {} + template + static inline void qAddOptionSettings(qConfigSettings& settings, Arg1&& arg1, Args&&... args) + { + qProcessSetting(settings, arg1); + qAddOptionSettings(settings, args...); + } + + template + static inline auto qConfigGetSettings(Args&... args) + { + qConfigSettings::settingsType> settings; + qConfigSettingsParser::settingsType>::qAddOptionSettings(settings, args...); + return (settings); + } +}; } // namespace qConfig #endif diff --git a/GPU/GPUTracking/utils/qconfig_print.inc b/GPU/GPUTracking/utils/qconfig_print.inc new file mode 100644 index 0000000000000..0928bae1a1592 --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_print.inc @@ -0,0 +1,26 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file qconfig_print.inc +/// \author David Rohr + +// Generates qConfigPrint for all config structs in the namespace it is included in, after the instances of the top-level configs (e.g. configStandalone). +// Requires , , , , "qconfig_helpers.h" to be included before (outside of the namespace). + +#ifndef QCONFIG_PRINT_INC +#define QCONFIG_PRINT_INC + +std::vector> qprint_global; +#define QCONFIG_PRINT +#include "qconfig_internal.h" +#undef QCONFIG_PRINT + +#endif // QCONFIG_PRINT_INC diff --git a/GPU/GPUTracking/utils/qconfig_rtc.h b/GPU/GPUTracking/utils/qconfig_rtc.h index 7e8090004a8bd..c98f1f8cf309a 100644 --- a/GPU/GPUTracking/utils/qconfig_rtc.h +++ b/GPU/GPUTracking/utils/qconfig_rtc.h @@ -18,15 +18,6 @@ #include "qconfig.h" #include "qconfig_helpers.h" -#ifndef qon_mxstr -#define qon_mstr(a) #a -#define qon_mxstr(a) qon_mstr(a) -#endif -#ifndef qon_mxcat -#define qon_mcat(a, b) a##b -#define qon_mxcat(a, b) qon_mcat(a, b) -#endif - template static std::string qConfigPrintRtc(const T& tSrc, bool useConstexpr) { diff --git a/GPU/GPUTracking/utils/qconfig_rtti.h b/GPU/GPUTracking/utils/qconfig_rtti.h new file mode 100644 index 0000000000000..63e58bd96341c --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_rtti.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file qconfig_rtti.h +/// \author David Rohr + +#ifndef QCONFIG_RTTI_H +#define QCONFIG_RTTI_H + +#include "qconfig.h" +#include "qconfig_helpers.h" +#include + +namespace qConfig +{ +struct qConfigRTTIentry { + std::string name = ""; // Name of option + std::string type = ""; // Data type of option + uint8_t size = 0; // sizeof(type) + bool isRTC = false; // enable RTC constexpr optimization + bool isVariable = false; // is a variable, not settable at command line + bool isVector = false; // is std::vector + uint16_t arraySize = 0; // size of array if std::array, 0 = no array + uint16_t configStructIndex = 0; // Index in configStructVector + size_t offset = 0; // Offset address in configStruct + std::string configStruct = ""; // Name of config struct that stores the option + std::string max = ""; // Maximum Value + std::string min = ""; // Minimum Value + std::string defaultValue = ""; // Default Value + std::string description = ""; // Description of option + std::string message = ""; // printf format string, printed with the value when the option is set +}; +} // namespace qConfig + +#endif // QCONFIG_RTTI_H