diff --git a/GPU/GPUTracking/Base/GPUParam.h b/GPU/GPUTracking/Base/GPUParam.h index dad4785ce9617..30cc4d1e7dca9 100644 --- a/GPU/GPUTracking/Base/GPUParam.h +++ b/GPU/GPUTracking/Base/GPUParam.h @@ -18,6 +18,7 @@ #include "GPUCommonDef.h" #include "GPUCommonMath.h" #include "GPUDef.h" +#define GPUSETTINGS_PARAM #include "GPUSettings.h" #include "GPUTPCGMPolynomialField.h" #include "DataFormatsTPC/Constants.h" diff --git a/GPU/GPUTracking/Base/GPUParamRTC.h b/GPU/GPUTracking/Base/GPUParamRTC.h index 2377666ee7b07..96341a0555398 100644 --- a/GPU/GPUTracking/Base/GPUParamRTC.h +++ b/GPU/GPUTracking/Base/GPUParamRTC.h @@ -16,6 +16,8 @@ #define GPUPARAMRTC_H #include "GPUParam.h" +#define GPUSETTINGS_ALL +#include "GPUSettings.h" #include namespace o2::gpu diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index 6f2e643fc98f6..2e66c258b5da1 100644 --- a/GPU/GPUTracking/CMakeLists.txt +++ b/GPU/GPUTracking/CMakeLists.txt @@ -69,7 +69,7 @@ set(SRCS_DATATYPES DataTypes/GPUDataTypesConfig.cxx DataTypes/GPUConfigDump.cxx set(HDRS_CINT_O2 Merger/GPUTPCGMTrackParam.h Merger/GPUTPCGMMergedTrack.h Merger/GPUTPCGMSectorTrack.h Merger/GPUTPCGMBorderTrack.h TRDTracking/GPUTRDInterfaces.h) set(HDRS_CINT_DATATYPES DataTypes/GPUTPCGMMergedTrackHit.h) -set(HDRS_CINT_O2_ADDITIONAL DataTypes/GPUSettings.h Definitions/GPUSettingsList.h DataTypes/GPUDataTypesIO.h DataTypes/GPUDataTypesConfig.h DataTypes/GPUDataTypesQA.h DataTypes/GPUTRDTrack.h DataTypes/GPUTRDRecoParam.h DataTypes/CalibdEdxTrackTopologyPol.h DataTypes/CalibdEdxTrackTopologySpline.h) # Manual dependencies for ROOT dictionary generation +set(HDRS_CINT_O2_ADDITIONAL DataTypes/GPUSettings.h DataTypes/GPUSettingsTypes.h Definitions/GPUSettingsList.h DataTypes/GPUDataTypesIO.h DataTypes/GPUDataTypesConfig.h DataTypes/GPUDataTypesQA.h DataTypes/GPUTRDTrack.h DataTypes/GPUTRDRecoParam.h DataTypes/CalibdEdxTrackTopologyPol.h DataTypes/CalibdEdxTrackTopologySpline.h) # Manual dependencies for ROOT dictionary generation set(SRCS_NO_CINT DataTypes/GPUMemorySizeScalers.cxx @@ -120,6 +120,7 @@ set(HDRS_INSTALL Base/GPUReconstructionIncludesITS.h Base/GPUReconstructionKernelMacros.h Base/GPUReconstructionCPUKernels.h + Base/GPUStdSystemHeaders.h DataCompression/GPUTPCClusterRejection.h DataCompression/GPUTPCCompressionKernels.inc DataCompression/TPCClusterDecompressionCore.inc @@ -156,6 +157,7 @@ set(HDRS_INSTALL SectorTracker/GPUTPCMCInfo.h SectorTracker/GPUTPCTracklet.h SectorTracker/GPUTPCTrackLinearisation.h + SectorTracker/GPUTPCRow.h TPCConvert/GPUTPCConvertImpl.h TRDTracking/GPUTRDGeometry.h TRDTracking/GPUTRDSpacePoint.h @@ -165,6 +167,7 @@ set(HDRS_INSTALL TRDTracking/GPUTRDTrackPoint.h TRDTracking/GPUTRDTrackPoint.h DataTypes/GPUTPCGMPolynomialField.h + DataTypes/GPUSettingsParam.h ) set(SRCS_NO_CINT ${SRCS_NO_CINT} display/GPUDisplayInterface.cxx) diff --git a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx index 9353e3664d05a..6fd2a0acda6ef 100644 --- a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx +++ b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx @@ -14,6 +14,7 @@ #include "GPUConfigDump.h" #include "GPUDataTypesIO.h" +#define GPUSETTINGS_ALL #include "GPUSettings.h" #include @@ -27,10 +28,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/DataTypes/GPUSettings.h b/GPU/GPUTracking/DataTypes/GPUSettings.h index dff33328e437f..bfbd3df33526f 100644 --- a/GPU/GPUTracking/DataTypes/GPUSettings.h +++ b/GPU/GPUTracking/DataTypes/GPUSettings.h @@ -16,70 +16,11 @@ #define GPUSETTINGS_H #include "GPUCommonDef.h" -#include "GPUDataTypesConfig.h" -#include "GPUTPCGMMergedTrackHit.h" -#ifndef GPUCA_GPUCODE_DEVICE -#include -#include -#endif - -namespace o2::gpu -{ -class GPUDisplayFrontendInterface; -class GPUReconstruction; - -class GPUSettings -{ - public: - enum CompressionModes { CompressionTruncate = 1, - CompressionDifferences = 2, - CompressionTrackModel = 4, - CompressionFull = 7 }; - enum CompressionSort { SortTime = 0, - SortPad = 1, - SortZTimePad = 2, - SortZPadTime = 3, - SortNoSort = 4 }; - enum CompressionRejection { RejectionNone = 0, - RejectionStrategyA = 1, - RejectionStrategyB = 2 }; - - static GPUglobalconstexpr() const uint32_t TPC_MAX_TF_TIME_BIN = ((256 * 3564 + 2 * 8 - 2) / 8); -}; - -// Settings describing the global run parameters -struct GPUSettingsGRP { - // All new members must be sizeof(int32_t) resp. sizeof(float) for alignment reasons!, default value for newly added members for old data will be 0. - float solenoidBzNominalGPU = -5.00668f; // solenoid field strength - int32_t constBz = 0; // for test-MC events with constant Bz - int32_t removed0 = 0; // Obsolete parameter, dummy value needed to support reading old dumps - int32_t grpContinuousMaxTimeBin = -2; // 0 for triggered events, -1 for automatic setting, -2 invalid default - int32_t needsClusterer = 0; // Set to true if the data requires the clusterizer - int32_t doCompClusterDecode = 0; // Set to true if the data contains compressed TPC clusters - int32_t tpcCutTimeBin = 0; // Cut TPC clusters and digits >= this cut -}; +#include "GPUSettingsTypes.h" -// Parameters of the current time frame -struct GPUSettingsTF { - int32_t hasTfStartOrbit = 0; - int32_t tfStartOrbit = 0; - int32_t hasRunStartOrbit = 0; - int32_t runStartOrbit = 0; - int32_t hasSimStartOrbit = 0; - int32_t simStartOrbit = 0; - int32_t hasNHBFPerTF = 0; - int32_t nHBFPerTF = 0; -}; - -// Settings defining the setup of the GPUReconstruction processing (basically selecting the device / class instance) -struct GPUSettingsDeviceBackend { - uint32_t deviceType = gpudatatypes::DeviceType::CPU; // Device type, shall use gpudatatypes::DEVICE_TYPE constants, e.g. CPU / CUDA - uint8_t forceDeviceType = 1; // Fail if device initialization fails, otherwise falls back to CPU - GPUReconstruction* master = nullptr; // GPUReconstruction master object -}; - -} // namespace o2::gpu +#endif // GPUSETTINGS_H +// Outside of header guard, since might run it multiple times for different config structs #ifdef GPUCA_GPUCODE_DEVICE #define QCONFIG_GPU #endif @@ -87,5 +28,3 @@ struct GPUSettingsDeviceBackend { #ifndef GPUCA_GPUCODE_GENRTC #include "utils/qconfig.h" #endif - -#endif diff --git a/GPU/GPUTracking/DataTypes/GPUSettingsParam.h b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h new file mode 100644 index 0000000000000..1c6a7bcf8a259 --- /dev/null +++ b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h @@ -0,0 +1,23 @@ +// 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 GPUSettingsParam.h +/// \author David Rohr + +// clang-format off + +// No include guard: included multiple times by qconfig with different macro definitions + +// Derrived parameters used in GPUParam +BeginHiddenConfig(GPUSettingsParam, param) +AddVariableRTC(continuousTracking, int8_t, 0) // Continuous tracking, estimate bz and errors for abs(z) = 125cm during seeding +AddVariableRTC(dodEdx, int8_t, 0) // Do dEdx computation +EndConfig() diff --git a/GPU/GPUTracking/DataTypes/GPUSettingsTypes.h b/GPU/GPUTracking/DataTypes/GPUSettingsTypes.h new file mode 100644 index 0000000000000..cefbf5b8d4f01 --- /dev/null +++ b/GPU/GPUTracking/DataTypes/GPUSettingsTypes.h @@ -0,0 +1,83 @@ +// 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 GPUSettingsTypes.h +/// \author David Rohr + +#ifndef GPUSETTINGSTYPES_H +#define GPUSETTINGSTYPES_H + +#include "GPUCommonDef.h" +#include "GPUDataTypesConfig.h" +#include "GPUTPCGMMergedTrackHit.h" +#ifndef GPUCA_GPUCODE_DEVICE +#include +#include +#endif + +namespace o2::gpu +{ +class GPUDisplayFrontendInterface; +class GPUReconstruction; + +class GPUSettings +{ + public: + enum CompressionModes { CompressionTruncate = 1, + CompressionDifferences = 2, + CompressionTrackModel = 4, + CompressionFull = 7 }; + enum CompressionSort { SortTime = 0, + SortPad = 1, + SortZTimePad = 2, + SortZPadTime = 3, + SortNoSort = 4 }; + enum CompressionRejection { RejectionNone = 0, + RejectionStrategyA = 1, + RejectionStrategyB = 2 }; + + static GPUglobalconstexpr() const uint32_t TPC_MAX_TF_TIME_BIN = ((256 * 3564 + 2 * 8 - 2) / 8); +}; + +// Settings describing the global run parameters +struct GPUSettingsGRP { + // All new members must be sizeof(int32_t) resp. sizeof(float) for alignment reasons!, default value for newly added members for old data will be 0. + float solenoidBzNominalGPU = -5.00668f; // solenoid field strength + int32_t constBz = 0; // for test-MC events with constant Bz + int32_t removed0 = 0; // Obsolete parameter, dummy value needed to support reading old dumps + int32_t grpContinuousMaxTimeBin = -2; // 0 for triggered events, -1 for automatic setting, -2 invalid default + int32_t needsClusterer = 0; // Set to true if the data requires the clusterizer + int32_t doCompClusterDecode = 0; // Set to true if the data contains compressed TPC clusters + int32_t tpcCutTimeBin = 0; // Cut TPC clusters and digits >= this cut +}; + +// Parameters of the current time frame +struct GPUSettingsTF { + int32_t hasTfStartOrbit = 0; + int32_t tfStartOrbit = 0; + int32_t hasRunStartOrbit = 0; + int32_t runStartOrbit = 0; + int32_t hasSimStartOrbit = 0; + int32_t simStartOrbit = 0; + int32_t hasNHBFPerTF = 0; + int32_t nHBFPerTF = 0; +}; + +// Settings defining the setup of the GPUReconstruction processing (basically selecting the device / class instance) +struct GPUSettingsDeviceBackend { + uint32_t deviceType = gpudatatypes::DeviceType::CPU; // Device type, shall use gpudatatypes::DEVICE_TYPE constants, e.g. CPU / CUDA + uint8_t forceDeviceType = 1; // Fail if device initialization fails, otherwise falls back to CPU + GPUReconstruction* master = nullptr; // GPUReconstruction master object +}; + +} // namespace o2::gpu + +#endif // GPUSETTINGSTYPES_H diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index fa560ec535313..ee82d5c8f084c 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -19,19 +19,12 @@ // Create plain-C struct for GPU code. // Create static constexpr with default values for GPU run time compilation -#include "GPUCommonDef.h" -#ifndef GPUSETTINGS_H -#error Please include GPUSettings.h! -#endif +#include "GPUSettingsTypes.h" -// clang-format off +#if (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) // File should not be included without defining the macros, but rootcling will do for dictionary generation -#ifdef QCONFIG_INSTANCE -using namespace o2::gpu; -#endif -#ifdef BeginNamespace // File should not be included without defining the macros, but rootcling will do for dictionary generation -BeginNamespace(o2) -BeginNamespace(gpu) +// clang-format off +BeginNamespace(o2::gpu) // Settings concerning the reconstruction, stored as parameters in GPU constant memory // There must be no bool in here, use int8_t, as sizeof(bool) is compiler dependent and fails on GPUs!!!!!! @@ -436,7 +429,7 @@ EndConfig() #ifndef GPUCA_GPUCODE_DEVICE // Light settings concerning the event display (can be changed without rebuilding vertices) BeginSubConfig(GPUSettingsDisplayLight, light, configStandalone.display, "GLL", 'g', "Light OpenGL display settings", display_light) -AddOption(animationMode, int32_t, 0, "", 0, "") +AddOption(animationMode, int32_t, 0, "", 0, "", min(0), max(6)) AddOption(smoothPoints, bool, true, "", 0, "Apply smoothing to points") AddOption(smoothLines, bool, false, "", 0, "Apply smoothing to lines") AddOption(depthBuffer, bool, false, "", 0, "Enable Z-buffer") @@ -449,19 +442,19 @@ AddOption(drawTracks, bool, false, "", 0, "Highlight sector tracks") AddOption(drawExtrapolatedTracks, bool, false, "", 0, "Highlight global sector tracks prolonged into adjacent sector") AddOption(drawFinal, bool, false, "", 0, "Highlight final tracks") AddOption(excludeClusters, int32_t, 0, "", 0, "Exclude clusters from selected draw objects from display, (2 = exclude clusters but still show tracks)") -AddOption(drawSector, int32_t, -1, "", 0, "Show individual sector") +AddOption(drawSector, int32_t, -1, "", 0, "Show individual sector", min(-1), max(35)) AddOption(drawRelatedSectors, int32_t, 0, "", 0, "Show related sectors (if drawSector != -1)") AddOption(drawGrid, int32_t, 0, "", 0, "Highlight grid") AddOption(propagateTracks, int32_t, 0, "", 0, "Propagate final tracks further (inward / outward / show MC tracks)") 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") -AddOption(drawITS, bool, true, "", 0, "Enabale drawing ITS data") +AddOption(drawTRD, bool, true, "", 0, "Enable drawing TRD data") +AddOption(drawTOF, bool, true, "", 0, "Enable drawing TOF data") +AddOption(drawITS, bool, true, "", 0, "Enable drawing ITS data") AddOption(drawField, bool, false, "", 0, "Enable drawing magnetic field") AddOption(bFieldStepSize, float, 5.0f, "", 0, "Set field line step size") AddOption(bFieldStepCount, int32_t, 100, "", 0, "Set field line step count") @@ -477,7 +470,7 @@ AddOption(drawITSTracks, bool, true, "", 0, "Show tracks with ITS contribution") AddOption(drawTRDTracks, bool, true, "", 0, "Show tracks with TRD contribution") AddOption(drawTOFTracks, bool, true, "", 0, "Show tracks with TOF contribution") AddOption(drawTracksAndFilter, bool, false, "", 0, "Use AND filter instead of OR filter for selecting tracks") -AddOption(propagateLoopers, bool, false, "", 0, "Enabale propagation of loopers") +AddOption(propagateLoopers, bool, false, "", 0, "Enable propagation of loopers") AddOption(clustersOnly, bool, false, "", 0, "Visualize clusters only") AddOption(clustersOnNominalRow, bool, false, "", 0, "Show clusters at nominal x of pad row for early-transformed data") AddOption(separateExtrapolatedTracks, bool, false, "", 0, "Draw track segments propagated to adjacent sectors separately") @@ -490,8 +483,7 @@ AddOption(hideRejectedTracks, int32_t, 1, "", 0, "Hide rejected tracks") AddOption(hideUnmatchedClusters, int32_t, 0, "", 0, "Hide unmatched clusters") AddOption(trackFilter, int32_t, 0, "", 0, "Apply filter on tracks to be displayed") AddOption(projectXY, int32_t, 0, "", 0, "Project everything on the XY-plane") -AddOption(xAdd, float, 0, "", 0, "Separate sectors, increase X coordinate") -AddOption(zAdd, float, 0, "", 0, "Separate sides, increase Z coordinate") +AddOption(xzAdd, float, 0, "", 0, "Separate sectors, increase X coordinate", min(0.f), max(300.f)) AddHelp("help", 'h') EndConfig() @@ -502,15 +494,14 @@ 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") +AddOption(screenshotScaleFactor, int32_t, 1, "", 0, "Resolution scale factor when taking screenshots", min(1), max(4)) +AddOption(fov, float, 45.f, "", 0, "Display FOV", min(5.f), max(175.f)) AddHelp("help", 'h') EndConfig() @@ -534,6 +525,12 @@ 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") +AddOption(consoleDelay, float, 0.5f, "", 0, "Time in seconds to open / close the console (^ key)") +AddOption(consoleAlpha, float, 0.8f, "", 0, "Opacity of the console background") +AddOption(consoleVertical, float, 0.5f, "", 0, "Fraction of the window height covered by the fully opened console") +AddOption(runScript, std::string, "", "", 0, "Console script file, run before the first frame of the display") +AddOption(consoleHistorySize, uint32_t, 1000000, "", 0, "Number of console lines kept, the history is shortened to this when it reaches twice the size") AddSubConfig(GPUSettingsDisplayLight, light) AddSubConfig(GPUSettingsDisplayHeavy, heavy) AddSubConfig(GPUSettingsDisplayRenderer, renderer) @@ -720,14 +717,6 @@ EndConfig() #endif // !GPUCA_STANDALONE #endif // !GPUCA_GPUCODE_DEVICE -// Derrived parameters used in GPUParam -BeginHiddenConfig(GPUSettingsParam, param) -AddVariableRTC(continuousTracking, int8_t, 0) // Continuous tracking, estimate bz and errors for abs(z) = 125cm during seeding -AddVariableRTC(dodEdx, int8_t, 0) // Do dEdx computation -EndConfig() - -EndNamespace() // gpu -EndNamespace() // o2 -#endif // #ifdef BeginNamespace +EndNamespace() // o2::gpu - // clang-format on +#endif // (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index f05ca6983e8ba..8a22e2c03ea47 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -860,6 +860,15 @@ int32_t GPUChainTracking::RunChainFinalize() usleep(10000); if (GetProcessingSettings().runsStandalone) { iKey = kbhit() ? getch() : 0; + if (iKey == 27 && kbhit()) { // Escape sequence (arrow keys, ...), not the ESC key: the display translates up to 8 characters after the ESC + uint64_t extendedKey = 0; + for (uint32_t i = 0; kbhit(); i++) { + const int32_t c = getch(); + extendedKey |= i < 8 ? (uint64_t)(uint8_t)c << (8 * i) : 0; + } + GetProcessingSettings().eventDisplay->sendExternalGUIKey(0, 1, extendedKey); + iKey = 0; + } if (iKey == 27) { GetProcessingSettings().eventDisplay->setDisplayControl(2); } else if (iKey == 'n') { diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 9edefc19a072c..682dfbe60c61c 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) @@ -101,6 +117,7 @@ set(SRCS ../utils/qsem.cxx GPUDisplay.cxx helpers/GPUDisplayMagneticField.cxx frontend/GPUDisplayFrontend.cxx + frontend/GPUDisplayConsole.cxx frontend/GPUDisplayFrontendNone.cxx backend/GPUDisplayBackend.cxx backend/GPUDisplayBackendNone.cxx) @@ -113,9 +130,11 @@ 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 + frontend/GPUDisplayFrontendKeyTranslation.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h frontend/GPUDisplayFrontendKeyVars.h GPUDisplayControl.h GPUDisplayTypes.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) @@ -150,10 +169,11 @@ 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 ${SRCS} frontend/GPUDisplayFrontendWayland.cxx) + set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() if(X11_FOUND) @@ -169,11 +189,10 @@ if(GLFW_FOUND) endif() if(GPUCA_EVENT_DISPLAY_QT) - qt_wrap_cpp(GPUCA_QT_MOC_SRCS frontend/GPUDisplayGUI.h) # Explicit moc / uic instead of AUTOMOC / AUTOUIC: rerun only if these files change; add new Q_OBJECT headers / .ui files here - qt_wrap_ui(GPUCA_QT_UI_HDRS frontend/GPUDisplayGUI.ui) + qt_wrap_cpp(GPUCA_QT_MOC_SRCS frontend/GPUDisplayGUI.h) # Explicit moc instead of AUTOMOC: rerun only if these files change; add new Q_OBJECT headers here set(SRCS ${SRCS} frontend/GPUDisplayGUI.cxx frontend/GPUDisplayGUIWrapper.cxx) - set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS} ${GPUCA_QT_UI_HDRS}) + set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS}) set(HDRS_INSTALL ${HDRS_INSTALL} frontend/GPUDisplayGUIChannel.h) endif() @@ -234,7 +253,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") @@ -298,7 +317,6 @@ endif() if(GPUCA_EVENT_DISPLAY_QT) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_QT) target_link_libraries(${targetName} PRIVATE Qt6::Widgets) - target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # ui_*.h endif() target_link_libraries(${targetName} PRIVATE TBB::tbb) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 2db90ea9a5fed..9f9abda84cc21 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; } @@ -172,11 +171,22 @@ int32_t GPUDisplay::InitDisplay(bool initFailure) if (!initFailure) { retVal = InitDisplay_internal(); } + if (retVal == 0) { + buildConfig(); + if (mConfig.listFrontendOptionHandling) { + mFrontend->listConfig(); + } + } } catch (const std::runtime_error& e) { GPUError("%s", e.what()); retVal = 1; } + mOldCfg = {mCfgH, mCfgL, mCfgR}; + std::unique_lock lock(mFrontend->mExitStateMutex); mInitResult = retVal == 0 ? 1 : -1; + mFrontend->mDisplayRunning = retVal == 0; // Only after successful initialization, StopDisplay waits for it to become false again + mFrontend->mInitCV.notify_all(); + mFrontend->mInitCV.wait(lock, [this] { return mFrontend->mStartDisplayDone; }); // Both threads have finished their initialization return (retVal); } @@ -188,7 +198,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 +213,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(); @@ -219,8 +228,12 @@ int32_t GPUDisplay::DrawGLScene() } else if (!mOverlayTFClusters.size()) { mNCollissions = std::max(1u, mIOPtrs->nMCInfosTPCCol); } + if (mCfgL.showCollision >= mNCollissions) { // Fewer collisions in the new event + mCfgL.showCollision = -1; + mUpdateDrawCommands = true; + } try { - DrawGLScene_internal(); + DrawGLScene_internal(suspended); } catch (const std::runtime_error& e) { GPUError("Runtime error %s during display", e.what()); retVal = 1; @@ -238,15 +251,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 +279,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; @@ -278,20 +291,20 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mCfgL.lineWidth = 1.4f; mCfgL.drawSector = -1; mCfgL.showCollision = -1; - mCfgH.xAdd = mCfgH.zAdd = 0; + mCfgH.xzAdd = 0; mCfgR.camLookOrigin = mCfgR.camYUp = false; mAngleRollOrigin = -1e9f; 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,14 +318,16 @@ 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)'<']) { - mAnimationDelay += moveX; - if (mAnimationDelay < 0.05f) { - mAnimationDelay = 0.05f; + if (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_CHANGE_ANIMATION_INTERVAL]]) { + mCfgC.mAnimationDelay += moveX; + if (mCfgC.mAnimationDelay < 0.05f) { + mCfgC.mAnimationDelay = 0.05f; + } else if (mCfgC.mAnimationDelay > 20.f) { + mCfgC.mAnimationDelay = 20.f; } moveX = 0.f; moveY = 0.f; - SetInfo("Animation delay set to %1.2f", mAnimationDelay); + SetInfo("Animation delay set to %1.2f", mCfgC.mAnimationDelay); } if (yUp) { @@ -395,7 +410,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 +420,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 +642,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 +665,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 +677,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 +687,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); @@ -700,12 +722,12 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) char info[1024]; float fps = (double)mFramesDoneFPS / fpstime; snprintf(info, 1024, - "FPS: %6.2f (Sector: %d, 1:Clusters %d, 2:Prelinks %d, 3:Links %d, 4:Seeds %d, 5:Tracklets %d, 6:Tracks %d, 7:GTracks %d, 8:Merger %d) (%d frames, %d draw calls) " - "(X %1.2f Y %1.2f Z %1.2f / R %1.2f Phi %1.1f Theta %1.1f) / Yaw %1.1f Pitch %1.1f Roll %1.1f)", + "FPS:%8.2f (Sector: %d, 1:Clusters %d, 2:Prelinks %d, 3:Links %d, 4:Seeds %d, 5:Tracklets %d, 6:Tracks %d, 7:GTracks %d, 8:Merger %d) (%d frames, %d draw calls) " + "(X %1.2f Y %1.2f Z %1.2f / R %1.2f Phi %1.1f Theta %1.1f) / Yaw %1.1f Pitch %1.1f Roll %1.1f - Size: %ux%u", 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()); + mRPhiTheta[2] * 180 / CAMath::Pi(), mAngle[1] * 180 / CAMath::Pi(), mAngle[0] * 180 / CAMath::Pi(), mAngle[2] * 180 / CAMath::Pi(), mBackend->mScreenWidth, mBackend->mScreenHeight); if (fpstime > 1.) { - if (mPrintInfoText & 2) { + if (mCfgC.mPrintInfoText & 2) { GPUInfo("%s", info); } if (mFPSScaleadjust++) { @@ -715,20 +737,34 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) mFramesDoneFPS = 0; } - if (mPrintInfoText & 1) { + const float consoleFraction = consoleVerticalFraction(); + const bool drawText = (mCfgC.mPrintInfoText & 1) || consoleFraction > 0.f; + if (drawText) { + mBackend->updateFontScale(mFrontend->getDisplayScale()); + mBackend->prepareText(); + } + if (mCfgC.mPrintInfoText & 1 || mCfgC.mPrintInfoTextGUI) { showInfo(info); } + if (consoleFraction > 0.f) { + const bool cursor = mCfgC.showConsole && std::fmod(mConsoleTimer.GetCurrentElapsedTime(), 1.0) < 0.5; // Blinking, the timer runs while the console is open + mBackend->GLDrawConsole(consoleFraction, mFrontend->mConsole->mConsoleHistory, mFrontend->mConsole->shownText(), cursor ? (int32_t)mFrontend->mConsole->mConsoleTextPos : -1); + } + if (drawText) { + mBackend->finishText(); + } + mFrontend->mConsole->consoleLimitHistory(mConfig.consoleHistorySize); } - 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) { - snprintf(tmpFileName, 48, "mAnimation%d_%05d.bmp", mAnimationExport, mAnimationFrame); + if (animateScreenshot) { + snprintf(tmpFileName, 48, "%d_%05d.bmp", mAnimationExport, mAnimationFrame); } - DoScreenshot(mAnimateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); + DoScreenshot(animateScreenshot ? (mCfgC.mScreenShotname + tmpFileName).c_str() : mScreenshotFile.c_str(), pixels); } } @@ -738,9 +774,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(); @@ -765,11 +800,5 @@ void GPUDisplay::WaitTillEventShown() int32_t GPUDisplay::StartDisplay() { mLoadAndShowEvent = true; - if (mFrontend->StartDisplay()) { - return (1); - } - while (mInitResult == 0) { - Sleep(10); - } - return (mInitResult != 1); + return mFrontend->StartDisplay(); } diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 67ab1a10334c9..ddc239c300297 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -15,13 +15,16 @@ #ifndef GPUDISPLAY_H #define GPUDISPLAY_H +#define GPUSETTINGS_DISPLAYCONTROL +#include "GPUSettings.h" #include "frontend/GPUDisplayFrontend.h" #include "backend/GPUDisplayBackend.h" #include "GPUDisplayInterface.h" -#include "GPUSettings.h" #include "DataFormatsTPC/Constants.h" +#include #include +#include #include #include @@ -58,12 +61,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, bool external = false); // external: from the Qt GUI or the console, not typed into the event display console + 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 +86,16 @@ 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; } + GPUDisplayControl 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); @@ -98,6 +107,7 @@ class GPUDisplay : public GPUDisplayInterface static constexpr const int32_t N_LINES_TYPE = 7; static constexpr const int32_t N_FINAL_TYPE = 4; static constexpr int32_t TRACK_TYPE_ID_LIMIT = 100; + static const std::array ANIMATION_MODE_NAMES_SHORT, ANIMATION_MODE_NAMES_LONG; // By mCfgL.animationMode enum PointTypes { tCLUSTER = 0, tINITLINK = 1, tLINK = 2, @@ -154,9 +164,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(); @@ -164,6 +174,7 @@ class GPUDisplay : public GPUDisplayInterface void updateOptions(); void disableUnsupportedOptions(); int32_t buildTrackFilter(); + std::string trackFilterName() const; // Selected track filter: macro file from filterMacros, or "Custom Macro" const GPUTPCTracker& sectorTracker(int32_t iSector); const GPUTRDGeometry* trdGeometry(); const GPUTrackingInOutPointers* mIOPtrs = nullptr; @@ -175,12 +186,19 @@ class GPUDisplay : public GPUDisplayInterface void calcXYZ(const float*); void mAnimationCloseAngle(float& newangle, float lastAngle); void mAnimateCloseQuaternion(float* v, float lastx, float lasty, float lastz, float lastw); + void SaveGLPos(); // Camera position to mCfgC.mGLPosFile + void LoadGLPos(); + void SaveGLAnimation(); // Animation path to mCfgC.mGLAnimationFile + void LoadGLAnimation(); + void takeScreenshot(); + void runAnimation(); // Starts the animation if it has enough points void setAnimationPoint(); 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); + float consoleVerticalFraction(); // Fraction of the window covered by the console (animated), 0 if hidden void ActivateColor(); void SetColorTRD(); void SetColorTOF(); @@ -217,7 +235,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 +246,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 +254,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 +262,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; @@ -286,11 +302,12 @@ class GPUDisplay : public GPUDisplayInterface std::vector mTrackFilter; volatile bool mUpdateTrackFilter = false; + std::string mTrackFilterMacroLoaded; // Track filter macro file loaded into ROOT, unloaded before loading another one (same function name) + bool mUpdateGUIOptions = false; // The Qt GUI must call the dynamic option lambdas of its elements again (new event, GUI started) volatile bool mUpdateVertexLists = true; volatile bool mUpdateEventData = false; volatile bool mUpdateDrawCommands = true; volatile bool mUpdateRenderPipeline = false; - volatile bool mResetScene = false; volatile bool mLoadAndShowEvent = false; bool mTracksArePropagated = false; @@ -299,22 +316,21 @@ 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; + std::string mGUIInfoStatus; // Latest status line (FPS etc.) for the Qt GUI + std::string mGUIInfoText; // Latest info message (SetInfo) for the Qt GUI + uint64_t mGUIInfoTextCounter = 0; // Number of info messages, the GUI shows a new one for some seconds + HighResTimer mInfoText2Timer, mInfoHelpTimer, mConsoleTimer; // mConsoleTimer: opening / closing animation of the console + bool mConsoleShown = false; // showConsole of the last checkAndApplyConfigChanges std::vector mThreadBuffers; std::vector, 2>, NSECTORS>>> mThreadTracks; - volatile int32_t mInitResult = 0; + int32_t mInitResult = 0; // 1 success, -1 failure, protected by mFrontend->mExitStateMutex float mFPSScale = 1, mFPSScaleadjust = 0; int32_t mFramesDone = 0, mFramesDoneFPS = 0; @@ -326,7 +342,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/GPUDisplayControl.h b/GPU/GPUTracking/display/GPUDisplayControl.h new file mode 100644 index 0000000000000..1d433a8f13751 --- /dev/null +++ b/GPU/GPUTracking/display/GPUDisplayControl.h @@ -0,0 +1,46 @@ +// 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 GPUDisplayControl.h +/// \author David Rohr + +// No include guard: included multiple times by qconfig with different macro definitions + +#include +#include +#include +#include "GPUDisplayTypes.h" + +// clang-format off +BeginNamespace(o2::gpu) +BeginHiddenConfig(GPUDisplayControl, control) + AddVariable(mDisplayControl, GPUDisplayCopyableAtomic, 0) // Control for next event (=1) or quit (=2) + AddVariable(mResetScene, GPUDisplayCopyableAtomic, false) + AddVariable(mPrintInfoText, int32_t, 1) + AddVariable(mPrintInfoTextAlways, bool, false) + AddVariable(mRequestScreenshot, bool, false) + AddVariable(mAnimateScreenshot, int32_t, 0) + AddVariable(mAnimationChangeConfig, bool, true) + AddVariable(mAnimationDelay, float, 2.f) + AddVariable(mPrintInfoTextGUI, bool, true) // Send the status and info text to the Qt GUI, shown at its bottom + AddVariable(mGuiPrintInfo, bool, false) // Show the info of options changed with Qt GUI elements (not for key presses in the GUI) + AddVariable(mRotateModel, bool, false) // Rotate the model instead of the camera, like holding RCTRL (set in the Qt GUI) + AddVariable(mRotateAroundZ, bool, false) // Rotate the TPC around the beamline, like holding RALT (set in the Qt GUI) + AddVariable(mFilterMacroCustom, std::string, "") // Code of the custom track filter ROOT macro (trackFilter = filterMacros.size() + 1), set in the Qt GUI + AddVariable(mScreenShotname, std::string, "screenshot") // Name of screenshot and animation frame files, followed by numbers + AddVariable(mGLPosFile, std::string, "glpos.tmp") // File for saving / loading the camera position + AddVariable(mGLAnimationFile, std::string, "glanimation.tmp") // File for saving / loading the animation path + AddVariable(mGuiDarkMode, int8_t, -1) // Dark mode of the Qt GUI, -1 = not yet set: the GUI reports the system scheme when it starts + AddVariable(mGuiScale, float, 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 + AddVariable(showConsole, bool, false) // Console shown (^ key): all other keys go to the console + AddVariable(dummy, bool, false) // dummy value to reference for keys that do not change variables +EndConfig() +EndNamespace() // o2::gpu diff --git a/GPU/GPUTracking/display/GPUDisplayInterface.h b/GPU/GPUTracking/display/GPUDisplayInterface.h index aabc1c25b75e1..470e95acfbe86 100644 --- a/GPU/GPUTracking/display/GPUDisplayInterface.h +++ b/GPU/GPUTracking/display/GPUDisplayInterface.h @@ -59,7 +59,7 @@ class GPUDisplayFrontendInterface virtual void StopDisplay() = 0; virtual int32_t getDisplayControl() const = 0; virtual void setDisplayControl(int32_t v) = 0; - virtual void sendExternalGUIKey(int32_t v) = 0; + virtual void sendExternalGUIKey(int32_t v, uint32_t translationMode = 0, uint64_t extendedKey = 0) = 0; // translationMode != 0: key from extendedKey (1 = console escape sequence, up to 8 characters after the ESC) virtual const char* frontendName() const = 0; protected: diff --git a/GPU/GPUTracking/display/GPUDisplayTypes.h b/GPU/GPUTracking/display/GPUDisplayTypes.h new file mode 100644 index 0000000000000..eec53710bed17 --- /dev/null +++ b/GPU/GPUTracking/display/GPUDisplayTypes.h @@ -0,0 +1,32 @@ +// 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 GPUDisplayTypes.h +/// \author David Rohr + +#ifndef GPUDISPLAYTYPES_H +#define GPUDISPLAYTYPES_H + +namespace o2::gpu +{ +template +struct GPUDisplayCopyableAtomic : std::atomic { // std::atomic deletes the copy, here it copies the value + using std::atomic::atomic; + GPUDisplayCopyableAtomic(const GPUDisplayCopyableAtomic& o) : std::atomic(o.load()) {} + GPUDisplayCopyableAtomic& operator=(const GPUDisplayCopyableAtomic& o) + { + this->store(o.load()); + return *this; + } +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index c1e39311b1768..400967da1c442 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -132,7 +132,7 @@ void GPUDisplayBackend::loadFont() mFontRasterScale = mDisplay->frontend()->getDisplayScale(); FT_Set_Pixel_Sizes(face, 0, (uint32_t)(fontSize * mFontRasterScale + 0.5f)); // Rasterize at physical pixel size for HiDPI - for (uint32_t i = 0; i < 128; i++) { + for (uint32_t i = 0; i < SOLID_GLYPH; i++) { if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { GPUError("Error loading freetype symbol"); FT_Done_Face(face); @@ -140,8 +140,13 @@ 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); } + std::vector solid(8 * 8, 255); + addFontSymbol(SOLID_GLYPH, 8, 8, 0, 0, 0, solid.data()); initializeTextDrawing(); FT_Done_Face(face); FT_Done_FreeType(ft); @@ -163,13 +168,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); } } } @@ -201,3 +209,42 @@ bool GPUDisplayBackend::smoothFont() { return mDisplay->cfg().smoothFont < 0 ? (mDisplay->cfg().fontSize > 12) : mDisplay->cfg().smoothFont; } + +void GPUDisplayBackend::GLDrawLine(float x0, float y0, float x1, float y1, float width, const float* color) +{ + const float dx = x1 - x0, dy = y1 - y0, length = std::sqrt(dx * dx + dy * dy); + if (length == 0.f) { + return; + } + const float nx = -dy / length * width * 0.5f, ny = dx / length * width * 0.5f; // Perpendicular, half the width + GLDrawQuad({{x0 + nx, y0 + ny}, {x0 - nx, y0 - ny}, {x1 - nx, y1 - ny}, {x1 + nx, y1 + ny}}, color); +} + +void GPUDisplayBackend::GLDrawConsole(float verticalFraction, std::span previous, const std::string& current, int32_t cursorPos) +{ + if (!mFreetypeInitialized) { + return; + } + const float scale = mDisplay->frontend()->getDisplayScale(); + const float colorValue = mDisplay->cfgL().invertColors ? 0.f : 1.f; + float textColor[4] = {colorValue, colorValue, colorValue, 1.f}; + const float backgroundColor[4] = {1.f - colorValue, 1.f - colorValue, 1.f - colorValue, mDisplay->cfg().consoleAlpha}; + const float width = mScreenWidth, top = mScreenHeight, bottom = top * (1.f - verticalFraction); + GLDrawQuad({{0.f, bottom}, {width, bottom}, {width, top}, {0.f, top}}, backgroundColor); + GLDrawLine(0.f, bottom, width, bottom, scale, textColor); + + static constexpr float MARGIN = 6.f; // Logical pixels from the text to the left border and to the line + const float lineHeight = std::max(20, mDisplay->drawTextFontSize() + 4) * scale; // As the info text of GPUDisplay + const float descent = 0.25f * mDisplay->drawTextFontSize() * scale; // Below the baseline, approximately + const size_t maxChars = mFontCharWidth > 0.f ? std::max(0.f, (width / scale - 2 * MARGIN) / mFontCharWidth) : SIZE_MAX; // Longer lines are cut + float y = bottom + (0.5f + MARGIN) * scale + descent; // Baseline of the lowest line, MARGIN above the line (half its width) + if (cursorPos >= 0 && (size_t)cursorPos < maxChars && mFontCharWidth > 0.f) { // Monospace font: position from the character width + GLPrintText("_", (MARGIN + cursorPos * mFontCharWidth) * scale, y, textColor, scale / mFontRasterScale, 0); + } + for (size_t i = 0; i <= previous.size() && y + lineHeight - descent <= top; i++, y += lineHeight) { + const std::string& text = i == 0 ? current : previous[previous.size() - i]; + if (!text.empty() && maxChars) { + GLPrintText(text.c_str(), MARGIN * scale, y, textColor, scale / mFontRasterScale, std::min(text.size(), maxChars)); + } + } +} diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 38114662714af..9abe2859ee9c4 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -17,6 +17,8 @@ #include "GPUCommonDef.h" #include "../utils/vecpod.h" +#include +#include #include #include #include @@ -39,8 +41,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 +101,10 @@ 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 + virtual void GLDrawQuad(const float (&corners)[4][2], const float* color) = 0; // Filled quad with the corners counterclockwise, in pixels with y from the bottom, drawn with the text (SOLID_GLYPH) + void GLDrawLine(float x0, float y0, float x1, float y1, float width, const float* color); // As GLDrawQuad + void GLDrawConsole(float verticalFraction, std::span previous, const std::string& current, int32_t cursorPos); // Console over the top verticalFraction of the window: current text in the lowest line, previous ones above (last one lowest), cursor _ at cursorPos (< 0: none) static GPUDisplayBackend* getBackend(const char* type); std::vector getPixels(); virtual float getYFactor() const { return 1.0f; } @@ -121,8 +124,9 @@ class GPUDisplayBackend std::vector mIndirectSectorOffset; vecpod mCmdBuffer; bool mFreetypeInitialized = false; - float mFontRasterScale = 1.f; // Display scale the font was rasterized for - bool mFrontendCompatTextDraw = false; + float mFontRasterScale = 1.f; // Display scale the font was rasterized for + static constexpr int32_t SOLID_GLYPH = 128; // Fully opaque glyph after the characters (0 - 127), for quads drawn with the text + 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..d5ec0e22b8254 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,8 @@ 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 GLDrawQuad(const float (&corners)[4][2], const float* color) 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..3d91ee802c16f 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])); } } @@ -303,6 +269,11 @@ int32_t GPUDisplayBackendOpenGL::checkProgramStatus(uint32_t program) int32_t GPUDisplayBackendOpenGL::InitBackendA() { + for (GLenum err = glGetError(); err != GL_NO_ERROR; err = glGetError()) { // Left by the frontend (freeglut with a core profile context leaves GL_INVALID_ENUM), CHKERR below would report it for the wrong call + if (mDisplay->GetProcessingSettings().debugLevel >= 2) { + GPUInfo("Ignoring OpenGL error %d from before the backend initialization", (int32_t)err); + } + } if (mDisplay->GetProcessingSettings().debugLevel >= 2) { auto renderer = glGetString(GL_RENDERER); GPUInfo("Renderer: %s", (const char*)renderer); @@ -325,9 +296,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 +427,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 +461,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 +541,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 +553,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 +583,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 +610,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 +673,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,9 +683,9 @@ 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++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { + if (*c < 32 || *c >= SOLID_GLYPH) { // Printable ASCII only, SOLID_GLYPH is not a character + GPUError("Trying to draw unsupported symbol: %d", (int32_t)*c); continue; } FontSymbolOpenGL sym = mFontSymbols[*c]; @@ -815,11 +701,7 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float {xpos, ypos + h, 0.0f, 0.0f}, {xpos + w, ypos, 1.0f, 1.0f}, {xpos + w, ypos + h, 1.0f, 0.0f}}; - CHKERR(glBindTexture(GL_TEXTURE_2D, sym.texId)); - CHKERR(glBindBuffer(GL_ARRAY_BUFFER, VBO_text)); - CHKERR(glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); - CHKERR(glBindBuffer(GL_ARRAY_BUFFER, 0)); - CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); + drawTextVertices(vertices, sym.texId); } x += (sym.advance >> 6) * scale; // shift is in 1/64th of a pixel } @@ -828,6 +710,34 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float } } +void GPUDisplayBackendOpenGL::drawTextVertices(const float (&vertices)[6][4], uint32_t texId) +{ + CHKERR(glBindTexture(GL_TEXTURE_2D, texId)); + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, VBO_text)); + CHKERR(glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, 0)); + CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); +} + +void GPUDisplayBackendOpenGL::GLDrawQuad(const float (&corners)[4][2], const float* color) +{ + if (!mFreetypeInitialized) { + return; + } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); + static constexpr int32_t order[6] = {0, 1, 2, 0, 2, 3}; + float vertices[6][4]; + for (int32_t i = 0; i < 6; i++) { + vertices[i][0] = corners[order[i]][0]; + vertices[i][1] = corners[order[i]][1]; + vertices[i][2] = vertices[i][3] = 0.5f; // Center of the opaque glyph + } + drawTextVertices(vertices, mFontSymbols[SOLID_GLYPH].texId); + if (!mDisplay->useMultiVBO()) { + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[0])); // As in GLPrintText + } +} + void GPUDisplayBackendOpenGL::ClearOffscreenBuffers() { if (mMixBuffer.created) { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index f30a22846a1ac..e7bc6480878f5 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,9 @@ 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; + void GLDrawQuad(const float (&corners)[4][2], const float* color) override; + void drawTextVertices(const float (&vertices)[6][4], uint32_t texId); // One quad with the text shader, after GLPrintText / GLDrawQuad set the color struct FontSymbolOpenGL : public FontSymbol { uint32_t texId; @@ -101,10 +101,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..df689d1fdfd0f 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,9 +1899,9 @@ 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++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { + if (*c < 32 || *c >= SOLID_GLYPH) { // Printable ASCII only, SOLID_GLYPH is not a character + GPUError("Trying to draw unsupported symbol: %d", (int32_t)*c); continue; } const FontSymbolVulkan& sym = mFontSymbols[*c]; @@ -1930,6 +1925,11 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float x += (sym.advance >> 6) * scale; // shift is in 1/64th of a pixel } + addTextDrawCommand(firstVertex, color); +} + +void GPUDisplayBackendVulkan::addTextDrawCommand(size_t firstVertex, const float* color) +{ size_t nVertices = mFontVertexBufferHost.size() / 4 - firstVertex; if (nVertices) { @@ -1942,6 +1942,21 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float } } +void GPUDisplayBackendVulkan::GLDrawQuad(const float (&corners)[4][2], const float* color) +{ + if (!mFreetypeInitialized) { + return; + } + const FontSymbolVulkan& sym = mFontSymbols[SOLID_GLYPH]; + const float u = (sym.x0 + sym.x1) * 0.5f, v = (sym.y0 + sym.y1) * 0.5f; // Center of the opaque glyph in the font atlas + const size_t firstVertex = mFontVertexBufferHost.size() / 4; + for (int32_t i : {0, 2, 1, 0, 3, 2}) { // Winding as the glyph quads of GLPrintText, the text pipeline culls back faces + mFontVertexBufferHost.insert(mFontVertexBufferHost.end(), {corners[i][0], mScreenHeight - 1 - corners[i][1], u, v}); + } + mHasDrawnText = true; + addTextDrawCommand(firstVertex, color); +} + void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageView view, vk::ImageLayout layout, std::vector& pixels) { const vk::Format format = mSurfaceFormat.format; @@ -2024,11 +2039,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..1b24f2199e3a6 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,9 @@ 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; + void GLDrawQuad(const float (&corners)[4][2], const float* color) override; + void addTextDrawCommand(size_t firstVertex, const float* color); // Draw the text vertices from firstVertex on with this color uint32_t mGraphicsFamily; SwapChainSupportDetails mSwapChainDetails; @@ -218,12 +220,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/GPUDisplayConsole.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx new file mode 100644 index 0000000000000..17134912d37af --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx @@ -0,0 +1,274 @@ +// 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 GPUDisplayConsole.cxx +/// \author David Rohr + +#include "GPUDisplayConsole.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::gpu; + +GPUDisplayConsole::GPUDisplayConsole(const std::string& title) : mConsoleHistory{title} {} + +void GPUDisplayConsole::handleConsoleKey(uint8_t key) +{ + const uint32_t nHistory = mConsoleHistory.size(); + auto isOutput = [this](uint32_t i) { return mConsoleHistory[i].size() && mConsoleHistory[i][0] == ' '; }; // Indented output of commands, not selectable + if (key == KEY_UP || key == KEY_DOWN) { // Entry 0 is the title, not selectable + if (key == KEY_UP) { + uint32_t i = mConsoleTextCurrent ? mConsoleTextCurrent : nHistory; + while (--i >= 1 && isOutput(i)) { + } + mConsoleTextCurrent = i >= 1 ? i : mConsoleTextCurrent; + } else if (mConsoleTextCurrent) { + uint32_t i = mConsoleTextCurrent; + while (++i < nHistory && isOutput(i)) { + } + mConsoleTextCurrent = i < nHistory ? i : 0; + } + mConsoleTextPos = 0; + return; + } else if (key == KEY_ENTER) { + std::string command = mConsoleTextCurrent ? mConsoleHistory[mConsoleTextCurrent] : mConsoleText; + command.erase(0, std::min(command.size(), command.find_first_not_of(' '))); // Leading spaces would mark it as output in the history + mConsoleText.clear(); + mConsoleTextCurrent = mConsoleTextPos = 0; + if (!command.empty()) { + consoleAppend(command); // Before its output + handleConsoleCommand(command); + } + return; + } else if (key != KEY_LEFT && key != KEY_RIGHT && key != KEY_BACKSPACE && key != KEY_DELETE && (key < 32 || key >= 127)) { // Other special keys are ignored + return; + } + if (mConsoleTextCurrent) { // Edit a copy of the history entry, with the cursor at its end (left: on its last character), delete then removes nothing + mConsoleText = mConsoleHistory[mConsoleTextCurrent]; + mConsoleTextCurrent = 0; + mConsoleTextPos = mConsoleText.size() - (key == KEY_LEFT && mConsoleText.size()); + if (key == KEY_LEFT || key == KEY_RIGHT) { + return; + } + } + mConsoleTextPos = std::min(mConsoleTextPos, mConsoleText.size()); + if (key == KEY_LEFT) { + mConsoleTextPos -= mConsoleTextPos > 0; + } else if (key == KEY_RIGHT) { + mConsoleTextPos += mConsoleTextPos < mConsoleText.size(); + } else if (key == KEY_BACKSPACE) { + if (mConsoleTextPos > 0) { + mConsoleText.erase(--mConsoleTextPos, 1); + } + } else if (key == KEY_DELETE) { + if (mConsoleTextPos < mConsoleText.size()) { + mConsoleText.erase(mConsoleTextPos, 1); + } + } else { + mConsoleText.insert(mConsoleTextPos++, 1, (char)key); + } +} + +void GPUDisplayConsole::consolePrint(const char* format, ...) +{ + char buffer[1024]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + consoleAppend(std::string(" ") + buffer); // Indented: output, not a command, skipped by up / down +} + +void GPUDisplayConsole::consoleInfo(const char* format, const char* text) +{ + const auto now = std::chrono::steady_clock::now(); + if (!mConsoleInfoLastMessage.empty() && mConsoleInfoLastMessage == format && now - mConsoleInfoLastTime < std::chrono::seconds(2)) { + mConsoleHistory.back() = std::string(" ") + text; // Same message again shortly after (animation progress, held keys): update its line + } else { + consoleAppend(std::string(" ") + text); + } + mConsoleInfoLastMessage = strcmp(format, "%s") ? format : ""; // A plain "%s" can be any message, never merged + mConsoleInfoLastTime = now; +} + +void GPUDisplayConsole::consoleAppend(std::string line) +{ + mConsoleHistory.emplace_back(std::move(line)); + mConsoleInfoLastMessage.clear(); +} + +void GPUDisplayConsole::consoleLimitHistory(uint32_t historySize) +{ + const size_t size = std::max(historySize, 2); + if (mConsoleHistory.size() < 2 * size) { + return; + } + const size_t removed = mConsoleHistory.size() - size; + std::move(mConsoleHistory.begin() + 1 + removed, mConsoleHistory.end(), mConsoleHistory.begin() + 1); // Keep the title (entry 0) + mConsoleHistory.resize(size); + mConsoleTextCurrent = mConsoleTextCurrent > removed ? mConsoleTextCurrent - removed : 0; +} + +// Tokens separated by spaces: text in single / double quotes and characters after a backslash are plain text (the token is a string), +// otherwise a token is int64_t for [+-]digits, double for [+-]digits with one '.', a string otherwise. Throws std::runtime_error for an unterminated quote / trailing backslash. + +std::tuple> GPUDisplayConsole::parseConsoleCommand(std::string_view cmd) const +{ + std::vector> tokens; // Text, plain text (quoted / escaped) + std::string token; + bool inToken = false, plainText = false, escape = false; + char quote = 0; + auto finishToken = [&]() { + if (inToken) { + tokens.emplace_back(std::move(token), plainText); + token.clear(); + inToken = plainText = false; + } + }; + for (char c : cmd) { + if (escape) { + token += c; + escape = false; + } else if (quote) { + if (c == quote) { + quote = 0; + } else { + token += c; + } + } else if (c == '\\') { + escape = inToken = plainText = true; + } else if (c == '"' || c == '\'') { + quote = c; + inToken = plainText = true; + } else if (c == ' ') { + finishToken(); + } else { + token += c; + inToken = true; + } + } + if (quote || escape) { + throw std::runtime_error(quote ? "unterminated quote" : "backslash at the end"); + } + finishToken(); + if (tokens.empty()) { + throw std::runtime_error("empty command"); + } + + const std::string& name = tokens[0].first; + const auto it = mConsoleCommands.find(name); + if (it == mConsoleCommands.end()) { + throw std::runtime_error("unknown command " + name); + } + const std::string& types = it->second.args; + const bool more = types.size() && types.back() == '+'; + const size_t nFixed = types.size() - more, nArgs = tokens.size() - 1; + if (nArgs < nFixed || (!more && nArgs > nFixed)) { + throw std::runtime_error(name + ": " + std::to_string(nFixed) + (more ? " or more" : "") + " arguments expected, got " + std::to_string(nArgs)); + } + auto autoType = [](const std::string& text, bool plain) -> consoleArg { // int64_t for [+-]digits, double for [+-]digits with one '.', string otherwise or if plain text + const char *begin = text.c_str() + (text[0] == '+' || text[0] == '-'), *end = text.c_str() + text.size(); + const size_t nDigits = std::count_if(begin, end, [](char c) { return c >= '0' && c <= '9'; }); + const size_t nDots = std::count(begin, end, '.'); + const bool number = !plain && nDigits && nDigits + nDots == (size_t)(end - begin); + int64_t intValue = 0; + if (number && nDots == 0 && std::from_chars(begin, end, intValue).ec == std::errc()) { + return text[0] == '-' ? -intValue : intValue; + } else if (number && nDots <= 1) { + return std::stod(text); // Also integers out of the int64_t range + } + return text; + }; + std::vector args; + for (size_t i = 0; i < nArgs; i++) { + const auto& [text, plain] = tokens[i + 1]; + const char type = i < nFixed ? types[i] : '+'; + consoleArg value = type == 's' ? consoleArg(text) : autoType(text, plain); + if (type == 'i' && !std::holds_alternative(value)) { + throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be an integer"); + } else if (type == 'd') { + if (std::holds_alternative(value)) { + value = (double)std::get(value); + } else if (!std::holds_alternative(value)) { + throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be a number"); + } + } else if (type != 's' && type != 'i' && type != '+') { + throw std::runtime_error(name + ": invalid argument types " + types); + } + args.emplace_back(std::move(value)); + } + return {it->second, std::move(args)}; +} + +void GPUDisplayConsole::handleConsoleCommand(std::string_view cmd) +{ + if (cmd.find_first_not_of(' ') == std::string_view::npos) { // Empty line + return; + } + const GPUDisplayConsoleCommand* command = nullptr; + std::vector args; + try { + auto parsed = parseConsoleCommand(cmd); + command = &std::get<0>(parsed); + args = std::move(std::get<1>(parsed)); + } catch (const std::runtime_error& e) { + consolePrint("Invalid command: %s", e.what()); + return; + } + command->func(args); +} + +void GPUDisplayConsole::runScript(std::string_view script) +{ + if (mScriptDepth >= 16) { + consolePrint("Scripts nested too deeply"); + return; + } + mScriptDepth++; + for (size_t pos = 0; pos < script.size();) { + const size_t end = std::min(script.find_first_of("\r\n", pos), script.size()); + handleConsoleCommand(script.substr(pos, end - pos)); // Empty lines (consecutive \r / \n) are ignored + pos = end + 1; + } + mScriptDepth--; +} + +void GPUDisplayConsole::runScriptFile(const std::string& filename) +{ + std::ifstream file(filename, std::ios::binary); + if (!file) { + consolePrint("Cannot open script file %s", filename.c_str()); + return; + } + std::stringstream content; + content << file.rdbuf(); + runScript(content.str()); +} + +void GPUDisplayConsole::printCommands(const std::function& print) +{ + std::vector names; + for (const auto& [name, command] : mConsoleCommands) { + names.emplace_back(name); + } + std::sort(names.begin(), names.end()); + print("Console commands (argument types: s string, i integer, d number, + further arguments):"); + for (const auto& name : names) { + const auto& command = mConsoleCommands[name]; + print(" " + name + (command.args.empty() ? "" : " ") + command.args + ": " + command.description); + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h new file mode 100644 index 0000000000000..9529bdb33387e --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h @@ -0,0 +1,66 @@ +// 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 GPUDisplayConsole.h +/// \author David Rohr + +#ifndef GPUDISPLAYCONSOLE_H +#define GPUDISPLAYCONSOLE_H + +#include "GPUDisplayFrontendKeyVars.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::gpu +{ +class GPUDisplayConsole : public GPUDisplayFrontendKeyVars // Text console of the event display (^ key): typed text, history with the output, commands (filled by GPUDisplayFrontend::buildConsoleCommands) +{ + public: + using consoleArg = std::variant; // Argument of a console command + struct GPUDisplayConsoleCommand { + std::function)> func; + std::string args; // One character per argument: s string, i int64_t, d double (also from an integer), + at the end: any further arguments (type from the text) + std::string description; // For help + }; + GPUDisplayConsole(const std::string& title); // First history entry, not selectable + + void handleConsoleKey(uint8_t key); // Key typed while the console is shown + void handleConsoleCommand(std::string_view cmd); // Runs the command named by the first token, error if unknown + void runScript(std::string_view script); // Runs each line (separated by \n / \r) as console command + void runScriptFile(const std::string& filename); // runScript with the content of the file + void printCommands(const std::function& print); // List of the commands with argument types and description, one line per call + std::tuple> parseConsoleCommand(std::string_view cmd) const; // Command and its arguments with the types it requests, throws std::runtime_error for syntax errors, unknown commands, wrong arguments + void consolePrint(const char* format, ...); // printf-like output to the console, indented by 2 spaces in mConsoleHistory + void consoleInfo(const char* format, const char* text); // Output of SetInfo, updates the last line instead for the same message within 2 s + void consoleAppend(std::string line); // Every other new line of the console + void consoleLimitHistory(uint32_t historySize); // Once per frame: keeps historySize lines when the history reaches twice that + const std::string& shownText() const { return mConsoleTextCurrent ? mConsoleHistory[mConsoleTextCurrent] : mConsoleText; } + + std::unordered_map mConsoleCommands; // Console commands by name, filled by GPUDisplayFrontend::buildConsoleCommands + uint32_t mScriptDepth = 0; // Scripts running scripts + std::string mConsoleInfoLastMessage; // Format of the SetInfo that wrote the last console line, empty if something else came after it + std::chrono::steady_clock::time_point mConsoleInfoLastTime; // + std::vector mConsoleHistory; // Title, then commands entered in the console and their output (indented) + std::string mConsoleText; // Text being typed in the console + uint32_t mConsoleTextCurrent = 0; // Entry of mConsoleHistory shown in the console (selected with up / down, the title entry 0 is not selectable), 0 for mConsoleText + uint32_t mConsoleTextPos = 0; // Cursor position in the shown text (left / right), the character there is the current one +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index b48d7f6c59d5c..37d1eadeeedab 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -13,13 +13,14 @@ /// \author David Rohr #include "GPUDisplayFrontend.h" +#include "GPUDisplayConsole.h" #include "GPUDisplay.h" #ifdef _WIN32 #include "GPUDisplayFrontendWindows.h" -#else -#include #endif +#include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include "GPUDisplayFrontendX11.h" @@ -44,7 +45,6 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include #include #include -#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -60,48 +60,67 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; -GPUDisplayFrontend::GPUDisplayFrontend() = default; -GPUDisplayFrontend::~GPUDisplayFrontend() = default; +GPUDisplayFrontend::GPUDisplayFrontend() : mConsole(std::make_unique(std::string(DISPLAY_WINDOW_NAME) + " Console")) {} +GPUDisplayFrontend::~GPUDisplayFrontend() +{ + if (mFrontendThread.joinable()) { // Started but not stopped, as the detached thread before + mFrontendThread.detach(); + } +} -void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) +void GPUDisplayFrontend::FrontendThreadWrapper() { - GPUDisplayFrontend* me = reinterpret_cast(ptr); int32_t retVal; try { - retVal = me->FrontendMain(); + retVal = FrontendMain(); } catch (const std::exception& e) { GPUError("Error in display frontend: %s", e.what()); retVal = -1; } - if (retVal && me->mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever - me->InitDisplay(true); + if (retVal) { + std::unique_lock lock(mExitStateMutex); + if (mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever + lock.unlock(); + InitDisplay(true); + } else { + mDisplay->mInitResult = -1; // Error after the initialization: StopDisplay does not reset the state for a new start + } } - return ((void*)(size_t)retVal); } 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 +133,27 @@ void GPUDisplayFrontend::HandleExternalGUISignals() for (int32_t key : update->keys) { mDisplay->HandleExternalGUIKey(key); } - if (update->lineWidth) { - mDisplay->mCfgL.lineWidth = *update->lineWidth; + applyGUIValues(*update); + if (update->guiScale) { + mDisplay->mCfgC.mGuiScale = *update->guiScale; + } + if (update->guiDarkMode) { + mDisplay->mCfgC.mGuiDarkMode = *update->guiDarkMode; } - changes = true; } - if (changes) { - mDisplay->checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } if (gui) { + GPUDisplayGUIChannel::Settings cfg{mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR, mDisplay->mCfgC}; { std::lock_guard lock(mGUIChannel->mutex); - mGUIChannel->state.cfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; + mGUIChannel->state.cfg = std::move(cfg); + mGUIChannel->state.infoStatus = mDisplay->mCfgC.mPrintInfoTextGUI ? mDisplay->mGUIInfoStatus : ""; + mGUIChannel->state.infoText = mDisplay->mCfgC.mPrintInfoTextGUI ? mDisplay->mGUIInfoText : ""; + mGUIChannel->state.infoTextCounter = mDisplay->mGUIInfoTextCounter; + if (mDisplay->mUpdateGUIOptions) { + mGUIChannel->state.optionsRevision++; + mDisplay->mUpdateGUIOptions = false; + } if (update) { mGUIChannel->state.appliedUpdate = update->id; } @@ -134,31 +161,46 @@ void GPUDisplayFrontend::HandleExternalGUISignals() mGUIChannel->displayProcessingCurrentUpdate = false; } mGUIChannel->stateUpdated.notify_all(); + std::unique_lock lock(mGUIChannel->mutex); + if (mGUIChannel->optionsReadRevision != mGUIChannel->state.optionsRevision) { // The GUI calls the dynamic option lambdas (reading display data) only while we wait here, short timeout, retried in the next frame + mGUIChannel->displayWaitingForOptions = true; + mGUIChannel->optionsRead.wait_for(lock, std::chrono::milliseconds(20), [this] { return mGUIChannel->optionsReadRevision == mGUIChannel->state.optionsRevision || mGUIChannel->stop; }); + mGUIChannel->displayWaitingForOptions = false; + } } } -bool GPUDisplayFrontend::HasExternalGUISignals() const +void GPUDisplayFrontend::sendExternalGUIKey(int32_t v, uint32_t translationMode, uint64_t extendedKey) { - return mExternalGUIKeys.size(); -} - -bool GPUDisplayFrontend::HandleExternalGUIKeys() -{ - std::lock_guard lock(mExternalGUIKeyMutex); - for (auto key : mExternalGUIKeys) { - mDisplay->HandleExternalGUIKey(key); + if (translationMode && (v = translateExternalKey(translationMode, extendedKey)) < 0) { + return; } - return !std::exchange(mExternalGUIKeys, {}).empty(); + { + std::lock_guard lock(mExternalGUIKeyMutex); + mExternalGUIKeys.emplace_back(v); + } + signalConfigUpdate(); } -void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) -{ - std::lock_guard lock(mExternalGUIKeyMutex); - mExternalGUIKeys.emplace_back(v); -} +bool GPUDisplayFrontend::consoleShown() const { return mDisplay->mCfgC.showConsole; } + +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 bool startupScript = !mStartupScriptDone && !mDisplay->cfg().runScript.empty(); + const int32_t retVal = mDisplay->DrawGLScene(suspended || startupScript); // Before the startup script, the first frame only loads the event, it is not rendered + mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + if (startupScript) { + mStartupScriptDone = true; + mConsole->runScriptFile(mDisplay->cfg().runScript); // Settings it changes are applied with the next frame + } + return retVal; +} void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) { @@ -189,6 +231,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()) { @@ -196,10 +239,13 @@ int32_t GPUDisplayFrontend::startGUI() std::lock_guard lock(mGUIChannel->mutex); mGUIChannel->update.reset(); mGUIChannel->state = {}; - mGUIChannel->guiSleeping = mGUIChannel->displayProcessingCurrentUpdate = mGUIChannel->stop = false; + mGUIChannel->guiSleeping = mGUIChannel->displayProcessingCurrentUpdate = mGUIChannel->stop = mGUIChannel->displayWaitingForOptions = false; + mGUIChannel->optionsReadRevision = 0; + mDisplay->mUpdateGUIOptions = true; 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 +310,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); } } @@ -296,34 +355,37 @@ int32_t& GPUDisplayFrontend::drawTextFontSize() int32_t GPUDisplayFrontend::StartDisplay() { -#ifndef _WIN32 - static pthread_t hThread; - if (pthread_create(&hThread, nullptr, FrontendThreadWrapper, this)) { - GPUError("Coult not Create frontend Thread..."); - return (1); - } -#else - HANDLE hThread; - if ((hThread = CreateThread(nullptr, nullptr, &OpenGLWrapper, this, nullptr, nullptr)) == nullptr) { - GPUError("Coult not Create GL Thread..."); + try { + mFrontendThread = std::thread(&GPUDisplayFrontend::FrontendThreadWrapper, this); + } catch (const std::system_error& e) { + GPUError("Could not create frontend thread: %s", e.what()); return (1); } -#endif - return (0); + std::unique_lock lock(mExitStateMutex); + mStartDisplayDone = true; + mInitCV.notify_all(); + mInitCV.wait(lock, [this] { return mDisplay->mInitResult != 0; }); // Both threads have finished their initialization + return (mDisplay->mInitResult != 1); } void GPUDisplayFrontend::StopDisplay() { { - std::lock_guard lock(mExitStateMutex); + std::unique_lock lock(mExitStateMutex); if (mDisplayRunning) { - mDisplayControl = 2; + mDisplay->mCfgC.mDisplayControl.store(2); StopDisplayA(); mDisplay->UnblockDisplay(); } + mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); + } + if (mFrontendThread.joinable()) { // Also the wrapper has finished (it sets mInitResult = -1 for errors after the initialization), not holding mExitStateMutex, which the wrapper uses + mFrontendThread.join(); } - while (mDisplayRunning) { - std::this_thread::sleep_for(std::chrono::microseconds(10000)); // TODO: Signal by semaphore + std::lock_guard lock(mExitStateMutex); + if (mDisplay->mInitResult == 1) { // For a new StartDisplay, not after a failed initialization or an error (undefined state) + mStartDisplayDone = mStartupScriptDone = false; + mDisplay->mInitResult = 0; } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 1bfbc36883f41..f29f57687c35a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -17,23 +17,30 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" +#include "GPUDisplayFrontendKeyVars.h" +#include #include #include #include +#include +#include +#include +#include #include namespace o2::gpu { +struct GPUDisplayGUIUpdate; class GPUReconstruction; class GPUDisplay; class GPUDisplayBackend; class GPUDisplayGUIWrapper; +class GPUDisplayConsole; struct GPUDisplayGUIChannel; -class GPUDisplayFrontend : public GPUDisplayFrontendInterface +class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplayFrontendKeyVars { friend class GPUDisplay; - friend class GPUDisplayGUI; // Key codes public: GPUDisplayFrontend(); @@ -53,13 +60,12 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface static constexpr int32_t GL_MIN_VERSION_MAJOR = 4; static constexpr int32_t GL_MIN_VERSION_MINOR = 6; - int32_t StartDisplay(); // Start the display. This function returns, and should spawn a thread that runs the display, and calls InitDisplay - void StopDisplay() override; // Stop the display. Display thread should call ExitDisplay. Functions waits for display thread to terminate - virtual void SwitchFullscreen(bool set) = 0; // Toggle full-screen mode - 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) + int32_t StartDisplay(); // Start the display. This function returns, and should spawn a thread that runs the display, and calls InitDisplay + void StopDisplay() override; // Stop the display. Display thread should call ExitDisplay. Functions waits for display thread to terminate + virtual void SwitchFullscreen(bool set) = 0; // Toggle full-screen mode + 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 GPUDisplayBackend* backend(); static GPUDisplayFrontend* getFrontend(const char* type); virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; } @@ -68,9 +74,9 @@ 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; } - void sendExternalGUIKey(int32_t v) override; + int32_t getDisplayControl() const override; + void setDisplayControl(int32_t v) override; + void sendExternalGUIKey(int32_t v, uint32_t translationMode = 0, uint64_t extendedKey = 0) override; frontendTypes frontendType() const { return mFrontendType; } const char* frontendName() const override { return mFrontendName; } @@ -82,62 +88,40 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface protected: 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 FrontendThreadWrapper(); // Runs FrontendMain in mFrontendThread + 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 + 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::condition_variable mInitCV; // With mExitStateMutex: InitDisplay (frontend thread) and StartDisplay (main thread) wait for each other at their end + std::thread mFrontendThread; // Started by StartDisplay, joined by StopDisplay + bool mStartDisplayDone = false; // Protected by mExitStateMutex + bool mStartupScriptDone = false; // GPUSettingsDisplay::runScript runs after the first (suspended) frame + std::array mKeyDownMap = {}; // Keyboard / Mouse actions - bool mMouseDn = false; // Mouse button down - bool mMouseDnR = false; // Right mouse button down - bool mMouseDnM = false; // Middle mouse button down (not used yet) - float mMouseDnX, mMouseDnY; // X/Y position where mouse button was pressed - 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 + bool mMouseDn = false; // Mouse button down + bool mMouseDnR = false; // Right mouse button down + bool mMouseDnM = false; // Middle mouse button down (not used yet) + float mMouseDnX, mMouseDnY; // X/Y position where mouse button was pressed + 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 (not while the console is shown) 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; @@ -146,15 +130,19 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface frontendTypes mFrontendType = TYPE_INVALID; const char* mFrontendName = nullptr; - volatile bool mDisplayRunning = false; + std::atomic mDisplayRunning = false; // Set at the end of a successful InitDisplay, StopDisplay waits for it to become false again 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 + bool consoleShown() const; // Key presses go to the console, frontends do not set mKeys 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 +151,28 @@ 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(); + static int32_t translateExternalKey(uint32_t translationMode, uint64_t extendedKey); // Key code of an encoded key of sendExternalGUIKey (+ SPECIAL_KEYS_SHIFTED with SHIFT), -1 if unknown + void buildConsoleCommands(); // Fills mConsole->mConsoleCommands, GPUDisplayFrontendConfigConsole.inc (commands need the config RTTI) + std::unique_ptr mConsole; + 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 + void applyGUIValues(const GPUDisplayGUIUpdate& update); // 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..42de138145b7d --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -0,0 +1,618 @@ +// 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 "GPUDisplayConsole.h" +#define GPUSETTINGS_ALL +#include "GPUSettings.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 +#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, &GPUDisplay::mCfgC}; // 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::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); + } else if (e.type == "std::string") { + return *(const std::string*)ptr; + } + return std::nullopt; + } + template + uint32_t getRTTIIndex(GPUDisplay& display, M T::* member) // RTTI index of a direct member of a config struct + { + 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; + } + mCfgC.mRotateModel |= mCfgC.mRotateAroundZ; + if (mCfgC.showConsole != mConsoleShown) { + mConsoleShown = mCfgC.showConsole; + const double start = mConsoleTimer.IsRunning() ? std::max(0., mConfig.consoleDelay - mConsoleTimer.GetCurrentElapsedTime()) : 0.; // Reverse a running animation from its current position + mConsoleTimer.Reset(); + mConsoleTimer.AddTime(start); + mConsoleTimer.Start(); + if (mCfgC.showConsole) { + std::fill(std::begin(mFrontend->mKeys), std::end(mFrontend->mKeys), false); + } + } +} + +void GPUDisplay::HandleExternalGUIKey(int32_t key) +{ + // GPUError("key %d '%c'", key, (char) key); + + bool shifted = key >= 'A' && key <= 'Z'; + if (key >= 256) { // Special key with SHIFT, from translateExternalKey + shifted = true; + key -= mFrontend->SPECIAL_KEYS_SHIFTED; + } + bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; + mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; + HandleKey(key, true); + 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 = {}, std::function o = {}, uint32_t l = 0, int64_t b = 0) : type(t), choices(std::move(c)), options(std::move(o)), lines(l), bitMask(b) {} + GPUDisplayGUIChannel::ElementType type; + std::vector> choices; + std::function options; // GUI_SLIDER_DYNAMIC / GUI_DROPDOWN_DYNAMIC + uint32_t lines = 0; // GUI_TEXTEDIT + int64_t bitMask = 0; // GUI_CHECKBOX_BIT +}; + +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; +} + +template +static std::vector> guiNamedChoices(const T& names) // Choices of GUI_DROPDOWN_NAMES +{ + std::vector> retVal; + for (const auto& name : names) { + retVal.emplace_back(name, (int64_t)retVal.size()); + } + return retVal; +} + +static GPUDisplayGUIChannel::Options guiRange(double min, double max) { return {.min = min, .max = max, .choices = {}}; } // For the lambda of GUI_SLIDER_DYNAMIC + +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 (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 +static GPUDisplayGUIValue toGUIValue(const GPUDisplayCopyableAtomic& v) +{ + return toGUIValue(v.load()); +} + +struct guiVertical { // Argument between the elements of add_gui_binding: alignment of the following elements + bool vertical; +}; + +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>); + constexpr size_t nVertical = (0 + ... + std::is_same_v, guiVertical>); + static_assert(nElements >= 1 && nHorizontal <= 1 && sizeof...(Args) <= nElements + nHorizontal + nVertical + 1, "add_gui_binding needs at least one GUI element, and optionally the description, the horizontal flag, and guiVertical between elements"); + GPUDisplayGUIChannel::Frame frame; + bool horizontal = false; // Initially the flag of the frame, changed by guiVertical + auto getHorizontal = [&](auto& arg) { + if constexpr (std::is_same_v, bool>) { + horizontal = arg; + } + }; + (getHorizontal(args), ...); + auto addElement = [&](auto& element) { + using Type = GPUDisplayGUIChannel::ElementType; + if (element.type.bitMask) { // The element sets / shows only these bits of the option + element.set = [this, set = element.set, get = element.get, mask = element.type.bitMask](const GPUDisplayGUIValue& v) { + const int64_t current = std::get(get({mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR, mDisplay->mCfgC})); + set(std::get(v) ? (current | mask) : (current & ~mask)); + }; + element.get = [get = element.get, mask = element.type.bitMask](const GPUDisplayGUIChannel::Settings& cfg) -> GPUDisplayGUIValue { return (int64_t)((std::get(get(cfg)) & mask) != 0); }; + } + static const GPUDisplayGUIChannel::Settings defaults; // Only for the types of the values + const uint32_t id = mConfigRTTI->guiElements.size(); + auto& el = frame.elements.emplace_back(GPUDisplayGUIChannel::Element{.name = element.name, .description = element.rttiIndex < mConfigRTTI->rttiEntries.size() ? mConfigRTTI->rttiEntries[element.rttiIndex].description : "", .rttiIndex = element.rttiIndex, .type = element.type.type, .id = id, .isInteger = std::holds_alternative(element.get(defaults)), .isString = std::holds_alternative(element.get(defaults)), .lines = element.type.lines, .choices = std::move(element.type.choices), .dynamicOptions = std::move(element.type.options), .get = element.get}); + if (el.dynamicOptions && !((el.type == Type::SLIDER && el.choices.empty()) || el.type == Type::DROPDOWN)) { + throw std::runtime_error("GUI element " + element.name + ": dynamic options only for sliders with range and dropdown menus"); + } + if (el.type == Type::SLIDER && !el.dynamicOptions) { + 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.dynamicOptions) : (!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"); + } + el.horizontal = horizontal; + mConfigRTTI->guiElements.emplace_back(configRTTIInfo::guiElementCallbacks{element.rttiIndex, std::move(element.set)}); + }; + auto addArg = [&](auto& arg) { + if constexpr (std::is_same_v, guiElement>) { + addElement(arg); + } else if constexpr (std::is_same_v, guiVertical>) { + horizontal = !arg.vertical; + } else if constexpr (!std::is_same_v, bool>) { + frame.description = arg; + } + }; + (addArg(args), ...); + mConfigRTTI->guiLayout.emplace_back(std::move(frame)); +} + +void GPUDisplayFrontend::setGUILayout(GPUDisplayGUIChannel& channel) const { channel.layout = mConfigRTTI->guiLayout; } + +void GPUDisplayFrontend::applyGUIValues(const GPUDisplayGUIUpdate& update) +{ + for (const auto& [id, value] : update.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, default, in Qt GUI, keys):"); + uint32_t nOptionsWithKey = 0, nOptionsInGUI = 0; + int32_t defaultWidth = 0; + for (const auto& e : rtti.rttiEntries) { + defaultWidth = std::max(defaultWidth, e.defaultValue.size()); + } + 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 %-*s %-3s %s", e.configStruct.c_str(), e.name.c_str(), e.type.c_str(), defaultWidth, e.defaultValue.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(""); + static constexpr std::string_view availablePrefix = "Available keys: "; + static constexpr size_t MAX_LINE_LENGTH = 240; + std::string available(availablePrefix); + for (uint16_t k : availableKeys) { + const std::string key = "[" + rtti.getKeyName(k) + "]"; + if (available.size() > availablePrefix.size() && available.size() + 1 + key.size() > MAX_LINE_LENGTH) { // Wrap, without splitting a key + GPUInfo("%s", available.c_str()); + available.assign(availablePrefix.size(), ' '); + } else if (available.size() > availablePrefix.size()) { + available += ' '; + } + available += key; + } + GPUInfo("%s", available.c_str()); + GPUInfo("Help text:"); + for (const auto& line : rtti.helpText) { + GPUInfo(" %s", line.c_str()); + } + GPUInfo(""); + mConsole->printCommands([](const std::string& line) { 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" +#include "GPUDisplayFrontendConfigConsole.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(); + mFrontend->buildConsoleCommands(); + 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, bool external) +{ + if (mCfgC.showConsole && mFrontend->mConfigRTTI->configKeys[key].configRTTIIndex != mFrontend->mConfigRTTI->getRTTIIndex(*this, &GPUDisplayControl::showConsole)) { // All keys except the one toggling the console go to the console + if (!external) { + mFrontend->mConsole->handleConsoleKey(key); + } + return; + } + 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/GPUDisplayFrontendConfigConsole.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc new file mode 100644 index 0000000000000..5d0353dc9458e --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc @@ -0,0 +1,32 @@ +// 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 GPUDisplayFrontendConfigConsole.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGCONSOLE_INC +#define GPUDISPLAYFRONTENDCONFIGCONSOLE_INC + +void GPUDisplayFrontend::buildConsoleCommands() +{ + using args = std::span; + auto& commands = mConsole->mConsoleCommands; + commands["help"] = {[this](args) { mConsole->printCommands([this](const std::string& line) { mConsole->consolePrint("%s", line.c_str()); }); }, "", "List available console commands"}; + commands["clearHistory"] = {[this](args) { + mConsole->mConsoleHistory.resize(1); // Keep the title + mConsole->mConsoleTextCurrent = 0; + mConsole->mConsoleInfoLastMessage.clear(); + }, + "", "Clear the console history"}; + commands["runScript"] = {[this](args a) { mConsole->runScriptFile(std::get(a[0])); }, "s", "Execute script file"}; +} + +#endif // GPUDISPLAYFRONTENDCONFIGCONSOLE_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc new file mode 100644 index 0000000000000..66710fbdbd89d --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -0,0 +1,126 @@ +// 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); }; + static constexpr std::tuple noInfo{&GPUDisplayControl::dummy, &GPUDisplayControl::showConsole, &GPUSettingsDisplayLight::drawClusters, &GPUSettingsDisplayLight::drawInitLinks, &GPUSettingsDisplayLight::drawLinks, &GPUSettingsDisplayLight::drawSeeds, + &GPUSettingsDisplayLight::drawTracklets, &GPUSettingsDisplayLight::drawTracks, &GPUSettingsDisplayLight::drawExtrapolatedTracks, &GPUSettingsDisplayLight::drawFinal}; // Options without info, not even the generic one + std::apply([this](auto... member) { ((mFrontend->mConfigRTTI->configInfos[mFrontend->mConfigRTTI->getRTTIIndex(*this, member)] = []() {}), ...); }, noInfo); + addInfo(INFO_LAMBDA(mCfgC, mDisplayControl, if (val == 2) { SetInfo("Exiting", 1); })); + addInfo(INFO_LAMBDA(mCfgC, mResetScene, SetInfo("View reset", 1);)); + addInfo(INFO_LAMBDA(mCfgC, mRequestScreenshot, SetInfo("Taking screenshot (%s)", mScreenshotFile.c_str());)); + addInfo(INFO_LAMBDA(mCfgC, mAnimateScreenshot, SetInfo("Writing screenshots from animation set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgC, mAnimationChangeConfig, SetInfo("Interpolating visualization settings during Animation %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgC, mPrintInfoTextAlways, SetInfo(val ? "Showing help text until disabled" : (mInfoHelpTimer.IsRunning() ? "Showing help text" : "Showing help text disabled"), 1);)); + addInfo(INFO_LAMBDA(mCfgC, mPrintInfoText, SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled");)); + 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, xzAdd, SetInfo("TPC sector separation: %f", mCfgH.xzAdd);)); + addInfo(INFO_LAMBDA(mCfgL, animationMode, SetInfo("Animation mode set to %s", ANIMATION_MODE_NAMES_LONG[val].c_str());)); + 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" : trackFilterName().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..fe81c6ac05449 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -0,0 +1,317 @@ +// 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);), + KEY_LAMBDA('n', mCfgC, mDisplayControl, val.store(1);), + KEY_LAMBDA('r', mCfgC, mResetScene, val.store(true);), // + 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, xzAdd, val += 60;), // + "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 adjacent clusters"); + + mFrontend->add_key_binding( + KEY_LAMBDA('v', mCfgH, hideRejectedClusters, val ^= 1;), + KEY_LAMBDA('j', mCfgH, separateExtrapolatedTracks, val ^= 1;), + KEY_LAMBDA('m', mCfgH, splitCETracks, 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, takeScreenshot();), + KEY_LAMBDA('T', mCfgC, mAnimateScreenshot, val ^= 1;), // + KEY_LAMBDA('Z', mCfgR, screenshotScaleFactor, { + val += 1; + if (val > 4) { + val = 1; + } + }), // + "Take Screenshot / Record Animation to pictures / Change screenshot resolution (scaling factor)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('y', mCfgC, dummy, runAnimation();), + 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;), + 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, SaveGLPos();), + KEY_LAMBDA('p', mCfgC, dummy, LoadGLPos();), + KEY_LAMBDA('O', mCfgC, dummy, SaveGLAnimation();), + KEY_LAMBDA('P', mCfgC, dummy, LoadGLAnimation();), + "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; + } else if (mInfoHelpTimer.IsRunning()) { + val = true; + mInfoHelpTimer.Reset(); + } else { + PrintHelp(); + } + }), + KEY_LAMBDA('H', mCfgC, mPrintInfoText, { + val += 1; + val &= 3; + }), // + "Print Help / Show info texts"); + + mFrontend->add_key_binding( + KEY_LAMBDA('^', mCfgC, showConsole, val ^= 1;), // + "Show / hide the console"); +} + +#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..ac6c82232d79e --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc @@ -0,0 +1,194 @@ +// 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_CHECKBOX_BIT(mask) guiType(GPUDisplayGUIChannel::ElementType::CHECKBOX, {}, {}, 0, mask) // Checkbox for these bits of an integer option +#define GUI_TEXTBOX guiType(GPUDisplayGUIChannel::ElementType::TEXTBOX) +#define GUI_TEXTEDIT(lines) guiType(GPUDisplayGUIChannel::ElementType::TEXTEDIT, {}, {}, lines) // Multi-line text edit, initially that many lines, resizeable with the mouse, applied when it loses the focus +#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_DROPDOWN_NAMES(names) guiType(GPUDisplayGUIChannel::ElementType::DROPDOWN, guiNamedChoices(names)) // Choices with these names, the value is the index +#define GUI_SLIDER_DYNAMIC(...) guiType(GPUDisplayGUIChannel::ElementType::SLIDER, {}, __VA_ARGS__) // Range from a lambda returning guiRange(min, max), called by the GUI after mUpdateGUIOptions while the display waits +#define GUI_DROPDOWN_DYNAMIC(...) guiType(GPUDisplayGUIChannel::ElementType::DROPDOWN, {}, __VA_ARGS__) // Choices from a lambda returning GPUDisplayGUIChannel::Options{.choices = ...}, as GUI_SLIDER_DYNAMIC +#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); }, [](const GPUDisplayGUIChannel::Settings& cfg) { return toGUIValue(cfg.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("Toggle y upward", 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), + GUI_LAMBDA("FOV", mCfgR, fov, 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("Maximized", mCfgR, maximized, GUI_CHECKBOX), + GUI_LAMBDA("Full Screen", mCfgR, fullScreen, GUI_CHECKBOX), + true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Sector", mCfgL, drawSector, GUI_SLIDER), // -1 = all sectors + GUI_LAMBDA("Separate Sectors", mCfgH, xzAdd, GUI_SLIDER, guiAssign(val, guiVal);), + guiVertical{false}, + GUI_LAMBDA("Draw related sectors", mCfgL, drawRelatedSectors, GUI_CHECKBOX), + GUI_LAMBDA("Separate extrapolated tracks", mCfgH, separateExtrapolatedTracks, GUI_CHECKBOX), + GUI_LAMBDA("Split CE tracks", mCfgH, splitCETracks, GUI_CHECKBOX), + "TPC Sectors"); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Exclude tracking step clusters", mCfgL, excludeClusters, GUI_DROPDOWN({"No", 0}, {"Clusters and tracks", 1}, {"Clusters, keep tracks", 2})), + GUI_LAMBDA("Exclude rejected tracks", mCfgH, hideRejectedTracks, GUI_CHECKBOX), + GUI_LAMBDA("Exclude rejected clusters", mCfgH, hideRejectedClusters, GUI_CHECKBOX), + GUI_LAMBDA("Unmatched Clusters", mCfgH, hideUnmatchedClusters, GUI_CHECKBOX), + "Exclude", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("splitPad", mCfgH, markClusters, GUI_CHECKBOX_BIT(0x1)), + GUI_LAMBDA("splitTime", mCfgH, markClusters, GUI_CHECKBOX_BIT(0x2)), + GUI_LAMBDA("edge", mCfgH, markClusters, GUI_CHECKBOX_BIT(0x4)), + GUI_LAMBDA("singlePad", mCfgH, markClusters, GUI_CHECKBOX_BIT(0x8)), + GUI_LAMBDA("rejectDistance", mCfgH, markClusters, GUI_CHECKBOX_BIT(0x10)), + GUI_LAMBDA("rejectErr", mCfgH, markClusters, GUI_CHECKBOX_BIT(0x20)), + GUI_LAMBDA("fakeAttached", mCfgH, markFakeClusters, GUI_CHECKBOX), + GUI_LAMBDA("adjacent", mCfgH, markAdjacentClusters, GUI_CHECKBOX, val = std::get(guiVal) ? 31 : 0;), // All adjacent categories + GUI_LAMBDA("Auto-color track clusters", mCfgL, colorClusters, GUI_CHECKBOX), + "Mark Clusters", true); + + std::vector> filterChoices = {{"0: No Filter", (int64_t)0}}; + for (uint32_t i = 0; i < mConfig.filterMacros.size(); i++) { + std::string name = mConfig.filterMacros[i]; + std::erase_if(name, [](char c) { return c == '\n' || c == '\r'; }); + filterChoices.emplace_back(std::to_string(i + 1) + ": " + name.substr(0, 16), (int64_t)(i + 1)); + } + filterChoices.emplace_back(std::to_string(mConfig.filterMacros.size() + 1) + ": Custom Macro", (int64_t)(mConfig.filterMacros.size() + 1)); + mFrontend->add_gui_binding( + GUI_LAMBDA("TPC", mCfgH, drawTPCTracks, GUI_CHECKBOX), + GUI_LAMBDA("TRD", mCfgH, drawTRDTracks, GUI_CHECKBOX), + GUI_LAMBDA("TOF", mCfgH, drawTOFTracks, GUI_CHECKBOX), + GUI_LAMBDA("ITS", mCfgH, drawITSTracks, GUI_CHECKBOX), + GUI_LAMBDA("", mCfgH, drawTracksAndFilter, GUI_RADIO_BUTTONS({"AND", 1}, {"OR", 0})), + GUI_LAMBDA("Filter Macro", mCfgH, trackFilter, guiType(GPUDisplayGUIChannel::ElementType::DROPDOWN, filterChoices)), + GUI_LAMBDA("Custom Macro", mCfgC, mFilterMacroCustom, GUI_TEXTEDIT(1), { // ROOT macro code, reload and apply the custom filter if selected, the change is not visible in the config structs + guiAssign(val, guiVal); + if ((uint32_t)mCfgH.trackFilter == mConfig.filterMacros.size() + 1) { + mUpdateTrackFilter = true; + mUpdateEventData = true; + } + }), + "Track Filters", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Tracks", mCfgL, propagateTracks, GUI_DROPDOWN({"Hits connected", 0}, {"Propagated to vertex", 1}, {"Propagated inwards and outwards", 2}, {"Monte Carlo track", 3})), + GUI_LAMBDA("Loopers", mCfgH, propagateLoopers, GUI_CHECKBOX), + "Extrapolation", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Collision", mCfgL, showCollision, GUI_SLIDER_DYNAMIC([this]() { return guiRange(-1, mNCollissions - 1); })), // -1 = all collisions + GUI_LAMBDA("Color-code collisions", mCfgL, colorCollisions, GUI_CHECKBOX), + "Collisions"); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Show clusters Only", mCfgH, clustersOnly, GUI_CHECKBOX), + GUI_LAMBDA("Clusters on nominal row x", mCfgH, clustersOnNominalRow, GUI_CHECKBOX), + "Tracking Debug", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Save Camera", mCfgC, dummy, GUI_BUTTON, SaveGLPos();), + GUI_LAMBDA("Load Camera", mCfgC, dummy, GUI_BUTTON, LoadGLPos();), + GUI_LAMBDA("Save Animation", mCfgC, dummy, GUI_BUTTON, SaveGLAnimation();), + GUI_LAMBDA("Load Animation", mCfgC, dummy, GUI_BUTTON, LoadGLAnimation();), + GUI_LAMBDA("Camera File", mCfgC, mGLPosFile, GUI_TEXTBOX), + GUI_LAMBDA("Animation File", mCfgC, mGLAnimationFile, GUI_TEXTBOX), + "Load / Save", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Screenshot", mCfgC, mRequestScreenshot, GUI_BUTTON, takeScreenshot();), + GUI_LAMBDA("Animation", mCfgC, dummy, GUI_BUTTON, runAnimation();), + GUI_LAMBDA("Record Animation", mCfgC, mAnimateScreenshot, GUI_DROPDOWN({"no", 0}, {"to screenshots", 1})), + GUI_LAMBDA("Filename", mCfgC, mScreenShotname, GUI_TEXTBOX), + guiVertical{true}, + GUI_LAMBDA("Scale Factor", mCfgR, screenshotScaleFactor, GUI_SLIDER), + GUI_LAMBDA("Animation Delay", mCfgC, mAnimationDelay, GUI_SLIDER_DYNAMIC([]() { return guiRange(0.05f, 20.f); })), + guiVertical{false}, + GUI_LAMBDA("Add Point", mCfgC, dummy, GUI_BUTTON, setAnimationPoint(); SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimateVectors[0].size(), mAnimateVectors[0].back());), + GUI_LAMBDA("Remove Point", mCfgC, dummy, GUI_BUTTON, removeAnimationPoint(); SetInfo("Removed Animation point", 1);), + GUI_LAMBDA("Reset Animation", mCfgC, dummy, GUI_BUTTON, resetAnimation(); SetInfo("Reset Animation points", 1);), + GUI_LAMBDA("Animation Mode", mCfgL, animationMode, GUI_DROPDOWN_NAMES(ANIMATION_MODE_NAMES_SHORT), guiAssign(val, guiVal); if (!mCfgC.mGuiPrintInfo) { SetInfo("Animation mode set to %s", ANIMATION_MODE_NAMES_LONG[val].c_str()); }), "Screenshots / Animation", 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("SendInfoText", mCfgC, mGuiPrintInfo, GUI_CHECKBOX), + GUI_LAMBDA("GUIInfo", mCfgC, mPrintInfoTextGUI, GUI_CHECKBOX), + GUI_LAMBDA("HelpText", mCfgC, mPrintInfoTextAlways, GUI_CHECKBOX), + GUI_LAMBDA("StatusText", mCfgC, mPrintInfoText, GUI_CHECKBOX_BIT(1)), // Bit 0: status line on screen (bit 1: in the console) + GUI_LAMBDA("DarkMode", mCfgC, mGuiDarkMode, GUI_CHECKBOX), + true); +} + +#endif // GPUDISPLAYFRONTENDCONFIGQTGUI_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index ac61ddacdd0f6..6e345ab1a9366 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -92,6 +92,15 @@ int32_t GPUDisplayFrontendGlfw::GetKey(int32_t key) if (key == GLFW_KEY_INSERT) { return (KEY_INSERT); } + if (key == GLFW_KEY_BACKSPACE) { + return (KEY_BACKSPACE); + } + if (key == GLFW_KEY_DELETE) { + return (KEY_DELETE); + } + if (key == GLFW_KEY_GRAVE_ACCENT) { // Key left of 1 (named after the US layout): console key (^), independent of the layout + return ('^'); + } if (key == GLFW_KEY_ENTER) { return (KEY_ENTER); } @@ -161,21 +170,22 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 int32_t handleKey = 0, keyPress = 0; GetKey(key, scancode, mods, handleKey, keyPress); if (handleKey < 32) { - if (action == GLFW_PRESS) { - me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = mods & GLFW_MOD_SHIFT; + if (action == GLFW_PRESS || action == GLFW_REPEAT) { // Repeat as the other frontends (e.g. backspace in the console), characters are repeated by char_callback + me->mKeys[keyPress] = !me->consoleShown(); 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; + if (key == GLFW_KEY_GRAVE_ACCENT) { // Console key, its character (if not a dead key) follows in the same glfwPollEvents + me->mConsoleKeyPressed = true; + me->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; } } } @@ -183,14 +193,17 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 void GPUDisplayFrontendGlfw::char_callback(GLFWwindow* window, uint32_t codepoint) { // GPUInfo("Key (char callback): %d %c - key: %d", codepoint, (char)codepoint, (int32_t)me->mLastKeyDown); + if (me->mConsoleKeyPressed) { // Character of the console key, handled in key_callback + me->mConsoleKeyPressed = false; + return; + } if (codepoint < 256) { uint8_t keyPress = codepoint; if (keyPress >= 'a' && keyPress <= 'z') { keyPress += 'A' - 'a'; } - me->mKeyDownMap[me->mLastKeyDown] = keyPress; - me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; + me->mGLFWKeyDownMap[me->mLastKeyDown] = keyPress; + me->mKeys[keyPress] = !me->consoleShown(); me->HandleKey(codepoint); } } @@ -275,7 +288,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); @@ -319,15 +332,9 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; // Only after successful initialization, StopDisplay waits for it to become false again - } - int32_t retVal = 0; while (!glfwWindowShouldClose(mWindow)) { - HandleExternalGUISignals(); - if (DrawGLScene()) { + if (HandleUpdatesAndDrawGLScene()) { fprintf(stderr, "Error drawing GL scene\n"); retVal = 1; break; // Cleanup below @@ -337,6 +344,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } waitForFPSLimit(); glfwPollEvents(); + mConsoleKeyPressed = false; // No character followed (dead key) if (mRestoreWindowedSize) { mRestoreWindowedSize = false; if (mWindowedWidth > 0 && mWindowedHeight > 0 && !glfwGetWindowAttrib(mWindow, GLFW_MAXIMIZED)) { @@ -348,10 +356,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 +371,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..87a166c27f61f 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,8 +66,9 @@ 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; + bool mConsoleKeyPressed = false; // Console key pressed in this glfwPollEvents, skip its character in char_callback }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 368a86413194c..ebe63b5bc78ca 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -20,15 +20,15 @@ #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 +36,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; - } - if (key == GLUT_KEY_F5) { - return KEY_F5; - } - if (key == GLUT_KEY_F6) { - return KEY_F6; - } - if (key == GLUT_KEY_F7) { - return KEY_F7; + 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_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) @@ -138,90 +123,85 @@ void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPr if (specialKey) { keyOut = keyPressOut = specialKey; } else { - keyOut = keyPressOut = key; + keyOut = keyPressOut = key == 8 ? KEY_BACKSPACE : key == 127 ? KEY_DELETE + : key; // Otherwise 8 = KEY_SHIFT if (keyPressOut >= 'a' && keyPressOut <= 'z') { keyPressOut += 'A' - 'a'; } } } -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 && !consoleShown(); + 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); @@ -251,34 +229,31 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Error initializing GL extension wrapper\n"); return (-1); } - if (InitDisplay()) { - fprintf(stderr, "Error in OpenGL initialization\n"); - 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()); - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; + if (InitDisplay()) { + fprintf(stderr, "Error in OpenGL initialization\n"); + return (1); } glutMainLoop(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } ExitDisplay(); @@ -290,18 +265,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 +275,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/GPUDisplayFrontendKeyTranslation.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx new file mode 100644 index 0000000000000..31ab4705fae6c --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx @@ -0,0 +1,115 @@ +// 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 GPUDisplayFrontendKeyTranslation.cxx +/// \author David Rohr + +#include "GPUDisplayFrontend.h" +#include + +using namespace o2::gpu; + +static constexpr uint64_t encodeSequence(const char* s) // As encoded by the console: first character in the lowest byte +{ + uint64_t retVal = 0; + for (uint32_t i = 0; i < 8 && s[i]; i++) { + retVal |= (uint64_t)(uint8_t)s[i] << (8 * i); + } + return retVal; +} + +int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint64_t extendedKey) +{ + if (translationMode == 1) { + // Console escape sequences, characters after the ESC: VT100 / xterm, cursor keys also in application mode (O instead of [), F1 to F5 also of rxvt ([11~) and the Linux console ([[A), with SHIFT of xterm (;2) and rxvt ([a, $) + static constexpr std::pair sequences[] = { + {"[A", KEY_UP}, + {"[B", KEY_DOWN}, + {"[C", KEY_RIGHT}, + {"[D", KEY_LEFT}, + {"OA", KEY_UP}, + {"OB", KEY_DOWN}, + {"OC", KEY_RIGHT}, + {"OD", KEY_LEFT}, + {"[H", KEY_HOME}, + {"[F", KEY_END}, + {"OH", KEY_HOME}, + {"OF", KEY_END}, + {"[1~", KEY_HOME}, + {"[4~", KEY_END}, + {"[7~", KEY_HOME}, + {"[8~", KEY_END}, + {"[2~", KEY_INSERT}, + {"[3~", KEY_DELETE}, + {"[5~", KEY_PAGEUP}, + {"[6~", KEY_PAGEDOWN}, + {"OP", KEY_F1}, + {"OQ", KEY_F2}, + {"OR", KEY_F3}, + {"OS", KEY_F4}, + {"[11~", KEY_F1}, + {"[12~", KEY_F2}, + {"[13~", KEY_F3}, + {"[14~", KEY_F4}, + {"[[A", KEY_F1}, + {"[[B", KEY_F2}, + {"[[C", KEY_F3}, + {"[[D", KEY_F4}, + {"[[E", KEY_F5}, + {"[15~", KEY_F5}, + {"[17~", KEY_F6}, + {"[18~", KEY_F7}, + {"[19~", KEY_F8}, + {"[20~", KEY_F9}, + {"[21~", KEY_F10}, + {"[23~", KEY_F11}, + {"[24~", KEY_F12}, + {"[1;2A", KEY_UP + SPECIAL_KEYS_SHIFTED}, + {"[1;2B", KEY_DOWN + SPECIAL_KEYS_SHIFTED}, + {"[1;2C", KEY_RIGHT + SPECIAL_KEYS_SHIFTED}, + {"[1;2D", KEY_LEFT + SPECIAL_KEYS_SHIFTED}, + {"[1;2H", KEY_HOME + SPECIAL_KEYS_SHIFTED}, + {"[1;2F", KEY_END + SPECIAL_KEYS_SHIFTED}, + {"[2;2~", KEY_INSERT + SPECIAL_KEYS_SHIFTED}, + {"[3;2~", KEY_DELETE + SPECIAL_KEYS_SHIFTED}, + {"[5;2~", KEY_PAGEUP + SPECIAL_KEYS_SHIFTED}, + {"[6;2~", KEY_PAGEDOWN + SPECIAL_KEYS_SHIFTED}, + {"[a", KEY_UP + SPECIAL_KEYS_SHIFTED}, + {"[b", KEY_DOWN + SPECIAL_KEYS_SHIFTED}, + {"[c", KEY_RIGHT + SPECIAL_KEYS_SHIFTED}, + {"[d", KEY_LEFT + SPECIAL_KEYS_SHIFTED}, + {"[7$", KEY_HOME + SPECIAL_KEYS_SHIFTED}, + {"[8$", KEY_END + SPECIAL_KEYS_SHIFTED}, + {"[2$", KEY_INSERT + SPECIAL_KEYS_SHIFTED}, + {"[3$", KEY_DELETE + SPECIAL_KEYS_SHIFTED}, + {"[5$", KEY_PAGEUP + SPECIAL_KEYS_SHIFTED}, + {"[6$", KEY_PAGEDOWN + SPECIAL_KEYS_SHIFTED}, + {"[1;2P", KEY_F1 + SPECIAL_KEYS_SHIFTED}, + {"[1;2Q", KEY_F2 + SPECIAL_KEYS_SHIFTED}, + {"[1;2R", KEY_F3 + SPECIAL_KEYS_SHIFTED}, + {"[1;2S", KEY_F4 + SPECIAL_KEYS_SHIFTED}, + {"[15;2~", KEY_F5 + SPECIAL_KEYS_SHIFTED}, + {"[17;2~", KEY_F6 + SPECIAL_KEYS_SHIFTED}, + {"[18;2~", KEY_F7 + SPECIAL_KEYS_SHIFTED}, + {"[19;2~", KEY_F8 + SPECIAL_KEYS_SHIFTED}, + {"[20;2~", KEY_F9 + SPECIAL_KEYS_SHIFTED}, + {"[21;2~", KEY_F10 + SPECIAL_KEYS_SHIFTED}, + {"[23;2~", KEY_F11 + SPECIAL_KEYS_SHIFTED}, + {"[24;2~", KEY_F12 + SPECIAL_KEYS_SHIFTED}, + }; + for (const auto& [sequence, key] : sequences) { + if (encodeSequence(sequence) == extendedKey) { + return key; + } + } + } + return -1; +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h new file mode 100644 index 0000000000000..499d4dac5e200 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h @@ -0,0 +1,32 @@ +// 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 GPUDisplayFrontendKeyVars.h +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDKEYVARS_H +#define GPUDISPLAYFRONTENDKEYVARS_H + +#include + +namespace o2::gpu +{ +class GPUDisplayFrontendKeyVars +{ + public: + // Constant key codes for special mKeys (to unify different treatment in X11 / Windows / GLUT / etc.) +#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" +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h new file mode 100644 index 0000000000000..ad8db27502761 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h @@ -0,0 +1,76 @@ +// 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(30, BACKSPACE); +GPUDisplayFrontendAddKey(31, DELETE); +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..98a5654dcdbcd 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -28,9 +28,14 @@ int32_t GPUDisplayFrontendNone::FrontendMain() return 1; } 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..54a915d25f551 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,13 +49,12 @@ 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; + if (key == 41) { // evdev KEY_GRAVE, key left of 1: console key (^), independent of the layout (linux/input-event-codes.h must not be included here) + return ('^'); + } if (mXKBkeymap) { xkb_keysym_t sym = xkb_state_key_get_one_sym(mXKBstate, key + 8); if (sym == XKB_KEY_KP_Subtract) { @@ -95,6 +93,10 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) return (KEY_HOME); } else if (sym == XKB_KEY_Insert) { return (KEY_INSERT); + } else if (sym == XKB_KEY_BackSpace) { + return (KEY_BACKSPACE); + } else if (sym == XKB_KEY_Delete) { + return (KEY_DELETE); } else if (sym == XKB_KEY_F1) { return (KEY_F1); } else if (sym == XKB_KEY_F2) { @@ -174,24 +176,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(); }; @@ -258,8 +244,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() int32_t symbol = me->GetKey(key, state); 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->mKeys[keyPress] = !me->consoleShown(); 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 +253,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 +495,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) { @@ -580,12 +564,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - 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 +587,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 +607,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..229428af427bd 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -219,6 +219,12 @@ BOOL CreateGLWindow(char* title, int32_t width, int32_t height, int32_t bits, bo int32_t GetKey(int32_t key) { + if (key == VK_BACK) { + return (GPUDisplayFrontendKeyVars::KEY_BACKSPACE); + } + if (key == VK_DELETE) { + return (GPUDisplayFrontendKeyVars::KEY_DELETE); + } if (key == 107 || key == 187) { return ('+'); } @@ -257,16 +263,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) PostQuitMessage(0); return 0; - case WM_KEYDOWN: // Is A Key Being Held Down? - wParam = GetKey(wParam); - mKeys[wParam] = TRUE; - mKeysShift[wParam] = mKeys[KEY_SHIFT]; + case WM_KEYDOWN: // Is A Key Being Held Down? + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); // Scan code of the key left of 1: console key, independent of the layout + mKeys[wParam] = !consoleShown(); HandleKey(wParam); return 0; case WM_KEYUP: - wParam = GetKey(wParam); - mKeysShift[wParam] = false; + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); mKeys[wParam] = false; GPUInfo("Key: %d", wParam); return 0; @@ -352,13 +356,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 +374,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..2b9f37b33efd6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -97,6 +97,12 @@ int32_t GPUDisplayFrontendX11::GetKey(int32_t key) if (key == 65379) { return (KEY_INSERT); } + if (key == 65288) { + return (KEY_BACKSPACE); + } + if (key == 65535) { + return (KEY_DELETE); + } if (key == 65470) { return (KEY_F1); } @@ -144,6 +150,10 @@ int32_t GPUDisplayFrontendX11::GetKey(int32_t key) void GPUDisplayFrontendX11::GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut) { + if (event.xkey.keycode == 49) { // Key left of 1: console key (^), independent of the layout + keyOut = keyPressOut = '^'; + return; + } char tmpString[9]; KeySym sym; if (XLookupString(&event.xkey, tmpString, 8, &sym, nullptr) == 0) { @@ -163,28 +173,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 +249,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 +288,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"); @@ -348,14 +311,9 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - while (1) { do { - if (mDisplayControl == 2) { + if (getDisplayControl() == 2) { break; } if (!XPending(mXDisplay)) { @@ -399,8 +357,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() case KeyPress: { int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); - mKeysShift[keyPress] = mKeys[KEY_SHIFT]; - mKeys[keyPress] = true; + mKeys[keyPress] = !consoleShown(); HandleKey(handleKey); break; } @@ -409,7 +366,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); mKeys[keyPress] = false; - mKeysShift[keyPress] = false; break; } @@ -444,30 +400,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 +428,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return (0); @@ -513,8 +463,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..abc4d3c31bf2f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -13,34 +13,366 @@ /// \author David Rohr #include "GPUDisplayGUI.h" -#include "GPUDisplayFrontend.h" -#include "ui_GPUDisplayGUI.h" +#include "GPUDisplayFrontendKeyVars.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 +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include using namespace o2::gpu; -GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mUi(std::make_unique()), mChannel(channel) +namespace { - mUi->setupUi(this); +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.translated(0, (lineHeight - geometry.height()) / 2)); // Otherwise vertically centered in the row + } + } + 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 +}; +class GUIStyle : public QProxyStyle // Tooltips only after hovering for 1.5 seconds, also directly after another tooltip +{ + public: + int styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const override + { + return hint == SH_ToolTip_WakeUpDelay ? 1500 : hint == SH_ToolTip_FallAsleepDelay ? 0 + : QProxyStyle::styleHint(hint, option, widget, returnData); + } +}; + +class ResizeGrip : public QWidget // Size grip in the bottom right corner of its parent, reports the new size while dragged (QSizeGrip resizes only windows) +{ + public: + ResizeGrip(QWidget* parent, std::function resize) : QWidget(parent), mResize(std::move(resize)) + { + setCursor(Qt::SizeFDiagCursor); + setFixedSize(16, 16); + } + + protected: + void paintEvent(QPaintEvent*) override + { + QStylePainter painter(this); + QStyleOptionSizeGrip opt; + opt.initFrom(this); + opt.corner = Qt::BottomRightCorner; + painter.drawControl(QStyle::CE_SizeGrip, opt); + } + void mousePressEvent(QMouseEvent* e) override + { + mStartPos = e->globalPosition().toPoint(); + mStartSize = parentWidget()->size(); + } + void mouseMoveEvent(QMouseEvent* e) override + { + if (e->buttons() & Qt::LeftButton) { + const QPoint d = e->globalPosition().toPoint() - mStartPos; + mResize(mStartSize + QSize(d.x(), d.y())); + } + } + + private: + std::function mResize; + QPoint mStartPos; + QSize mStartSize; +}; + +class TextEdit : public QPlainTextEdit // Text edit with a resize grip, reports a changed text when it loses the focus +{ + public: + TextEdit(uint32_t lines, std::function changed) : mChanged(std::move(changed)), mGrip(new ResizeGrip(this, [this](QSize size) { + mUnscaledSize = size.expandedTo(QSize(60, 30) * mScale) / mScale; + setScale(mScale); + })) + { + const int margins = 2 * (frameWidth() + std::lround(document()->documentMargin())) + 6; // Few more pixels, otherwise a scroll bar appears + mUnscaledSize = QSize(400, std::max(lines, 1) * fontMetrics().lineSpacing() + margins); + } + QSize sizeHint() const override { return mPreferredSize; } + void setScale(float scale) // Fixed height, but narrower than preferred if the window is too small, so that it does not determine the width of the other elements + { + mScale = scale; + mPreferredSize = mUnscaledSize * scale; + setFixedHeight(mPreferredSize.height()); + setMinimumWidth(std::min(mPreferredSize.width(), 100)); + setMaximumWidth(mPreferredSize.width()); + updateGeometry(); + } + void showText(const QString& text) // Text from the display, not shown while the user edits + { + mCurrent = text; + if (!hasFocus() && toPlainText() != text) { + setPlainText(text); + document()->setModified(false); + } + } + + protected: + void focusOutEvent(QFocusEvent* e) override + { + QPlainTextEdit::focusOutEvent(e); + if (document()->isModified()) { + document()->setModified(false); + mCurrent = toPlainText(); + mChanged(mCurrent); + } else { + showText(mCurrent); + } + } + void resizeEvent(QResizeEvent* e) override + { + QPlainTextEdit::resizeEvent(e); + mGrip->move(width() - mGrip->width(), height() - mGrip->height()); + mGrip->raise(); + } + + private: + std::function mChanged; + QString mCurrent; + QSize mUnscaledSize; // At GUI scale 1, changed by the resize grip + QSize mPreferredSize; + float mScale = 1.f; + ResizeGrip* mGrip; +}; + +struct ValueEdit { // Text box for a value from the display, copied into the lambdas of the element + QLineEdit* edit = new QLineEdit; + std::shared_ptr current = std::make_shared(); // Restored when the text box is left without a valid new value + + void show(const QString& text) const // Value from the display, not shown while the user edits + { + *current = text; + if (!edit->hasFocus()) { + edit->setText(text); + } + } + void commit(const QString& text) const // New value entered by the user, possibly corrected + { + *current = text; + edit->setText(text); + } + std::optional entered() const // Text if changed by the user, otherwise restores the current value + { + if (edit->isModified()) { + return edit->text(); + } + edit->setText(*current); + return std::nullopt; + } + std::optional number() const // Number if entered by the user, otherwise restores the current value + { + bool ok = false; + const double v = edit->text().toDouble(&ok); + if (edit->isModified() && ok && std::isfinite(v)) { + return v; + } + edit->setText(*current); + return std::nullopt; + } +}; + +struct SliderRange { // Slider positions of a range of values + static constexpr int32_t FLOAT_STEPS = 1000; // Resolution of sliders for floating point options + double min, step; + int32_t steps; + SliderRange(double minValue, double maxValue, bool isInteger) : min(minValue), step(isInteger ? 1. : (maxValue - minValue) / FLOAT_STEPS), steps(isInteger ? std::max(std::lround(maxValue - minValue), 0) : FLOAT_STEPS) {} + double value(int32_t position) const { return min + position * step; } + int32_t position(double v) const { return step ? std::lround((v - min) / step) : 0; } +}; +} // namespace + +static bool fixedLengthSlider(const GPUDisplayGUIChannel::Element& el) // Discrete / integer sliders with few steps, others stretch to the frame border +{ + static constexpr double MAX_FIXED_LENGTH_STEPS = 16; + return !el.dynamicOptions && (el.choices.size() || (el.isInteger && el.max - el.min <= MAX_FIXED_LENGTH_STEPS)); // Dynamic range: number of steps may change +} + +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 QString toQString(const GPUDisplayGUIValue& v) +{ + return std::visit([](const auto& x) { + if constexpr (std::is_same_v, std::string>) { + return QString::fromStdString(x); + } else { + return QString::number(x); + } + }, + v); +} + +static GPUDisplayGUIValue toValue(double v, bool isInteger) { return isInteger ? GPUDisplayGUIValue((int64_t)std::llround(v)) : GPUDisplayGUIValue(v); } + +static uint32_t nearestChoice(const std::vector>& choices, double value) // For values not in the list +{ + uint32_t best = 0; + for (uint32_t i = 1; i < choices.size(); i++) { + if (std::abs(toDouble(choices[i].second) - value) < std::abs(toDouble(choices[best].second) - value)) { + best = i; + } + } + return best; +} + +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), mChannel(channel) +{ + qApp->setStyle(new GUIStyle); // Owned by the application + resize(1370, 1210); + setMinimumSize(500, 500); + mStatusLabel = new QLabel; + mStatusLabel->setWordWrap(true); + mStatusLabel->hide(); + statusBar()->addWidget(mStatusLabel, 1); + mDarkMode = darkPalette(); // System scheme, until the display tells otherwise + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &GPUDisplayGUI::colorSchemeChanged, Qt::QueuedConnection); // Queued: after the palette was updated + mInfoTextTimer.setSingleShot(true); + connect(&mInfoTextTimer, &QTimer::timeout, this, &GPUDisplayGUI::updateInfoLabels); + 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 + auto* content = new QWidget; + auto* contentLayout = new QVBoxLayout(content); + contentLayout->addWidget(buildElements(layout)); + contentLayout->addStretch(); + auto* scroll = new QScrollArea; // Vertical scroll bar if the frames do not fit into the window, they wrap to the window width + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidget(content); + setCentralWidget(scroll); + colorSchemeChanged(); // Style sheets, with the GUI scale, which comes 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)); } -GPUDisplayGUI::~GPUDisplayGUI() = default; - void GPUDisplayGUI::timerTick() { std::unique_lock lock(mChannel.mutex); @@ -64,14 +396,21 @@ void GPUDisplayGUI::timerTick() mLastAppliedId = mLastSentId; } } + std::vector> options; // Read while the display waits, applied to the widgets after unlocking + if (mChannel.displayWaitingForOptions && mChannel.state.optionsRevision != mOptionsRevision) { + for (uint32_t id = 0; id < mElements.size(); id++) { + if (mElements[id].getOptions) { + options.emplace_back(id, mElements[id].getOptions()); + } + } + mOptionsRevision = mChannel.optionsReadRevision = mChannel.state.optionsRevision; + mChannel.optionsRead.notify_all(); + } 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; - } + mChannel.update->merge(mUpdate); } else { mChannel.update = std::move(mUpdate); } @@ -81,16 +420,391 @@ void GPUDisplayGUI::timerTick() } lock.unlock(); + if (post) { + mChannel.updatePosted(); + } + if (newState) { + if (mState.cfg.mCfgC.mGuiDarkMode < 0) { + mUpdate.guiDarkMode = mDarkMode; // Report the system scheme, also shows up in the DarkMode checkbox + } else if ((mState.cfg.mCfgC.mGuiDarkMode != 0) != mDarkMode) { + setDarkMode(mState.cfg.mCfgC.mGuiDarkMode != 0); + } + if (mState.infoTextCounter != mInfoTextCounter) { + mInfoTextCounter = mState.infoTextCounter; + mInfoTextTimer.start(std::chrono::seconds(5)); + } + updateInfoLabels(); + } + for (const auto& [id, o] : options) { // New range / choices, then the current value again + mElements[id].setOptions(o); + mElements[id].applied.reset(); + applyElement(mElements[id]); + } if (apply) { applySettings(); } } +bool GPUDisplayGUI::darkPalette() const { return palette().window().color().lightness() < 128; } + +void GPUDisplayGUI::setDarkMode(bool dark) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + QGuiApplication::styleHints()->setColorScheme(dark ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light); // No effect if the platform theme does not support it +#endif + mDarkMode = dark; +} + +void GPUDisplayGUI::colorSchemeChanged() +{ + setStyleSheet(QString()); // Otherwise the widgets keep their old palette + setScale(mScale); // Sets the style sheet again + updateInfoLabels(); +} + +void GPUDisplayGUI::resizeEvent(QResizeEvent* event) +{ + QMainWindow::resizeEvent(event); + updateInfoLabels(); // Shows the GUI size +} + +void GPUDisplayGUI::fitStatusLabel() +{ + mStatusLabel->setMinimumHeight(0); // heightForWidth of QLabel does not return less than the minimum height + mStatusLabel->setFixedHeight(mStatusLabel->heightForWidth(mStatusLabel->width())); +} + +void GPUDisplayGUI::updateInfoLabels() +{ + const QString status = QString::fromStdString(mState.infoStatus) + QString(" GUI %1x%2").arg(width()).arg(height()); + if (mInfoTextTimer.isActive() && !mState.infoText.empty()) { // Info message for some seconds, in bold and color, in the same line + const char* color = darkPalette() ? "lightblue" : "darkblue"; + mStatusLabel->setTextFormat(Qt::RichText); + mStatusLabel->setText("" + status.toHtmlEscaped() + QString(" - %2").arg(color, QString::fromStdString(mState.infoText).toHtmlEscaped())); + } else { + mStatusLabel->setTextFormat(Qt::PlainText); + mStatusLabel->setText(status); + } + mStatusLabel->setVisible(!mState.infoStatus.empty()); + fitStatusLabel(); +} + 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.cfg.mCfgC.mGuiScale != mScale) { // Changed by the display (Reset GUI button) or from a previous GUI session + setScale(mState.cfg.mCfgC.mGuiScale); + } + for (auto& element : mElements) { + applyElement(element); + } +} + +void GPUDisplayGUI::applyElement(GUIElement& element) +{ + if (!element.set) { + return; + } + GPUDisplayGUIValue value = element.get(mState.cfg); + if (element.applied != value) { + element.set(value); + element.applied = std::move(value); + } +} + +void GPUDisplayGUI::setValue(uint32_t id, GPUDisplayGUIValue value) +{ + mUpdate.setValue(id, std::move(value)); + mElements[id].applied.reset(); +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createCheckbox(const GPUDisplayGUIChannel::Element& el) +{ + auto* w = new QCheckBox(QString::fromStdString(el.name)); // Own text instead of a label + connect(w, &QCheckBox::toggled, this, [this, id = el.id](bool v) { setValue(id, (int64_t)v); }); + return {w, [w](const GPUDisplayGUIValue& v) { const QSignalBlocker blocker(w); w->setChecked(toDouble(v) != 0.); }, {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createTextbox(const GPUDisplayGUIChannel::Element& el) +{ + ValueEdit text; + connect(text.edit, &QLineEdit::editingFinished, this, [this, text, id = el.id, isInteger = el.isInteger, isString = el.isString, hasRange = el.hasRange, min = el.min, max = el.max]() { + if (isString) { + if (const auto str = text.entered()) { + text.commit(*str); + setValue(id, str->toStdString()); + } + } else if (const auto entered = text.number()) { + const GPUDisplayGUIValue v = toValue(hasRange ? std::clamp(*entered, min, max) : *entered, isInteger); + text.commit(toQString(v)); // Show the value actually set, the display state does not overwrite the focused field + setValue(id, v); + } + }); + return {text.edit, [text](const GPUDisplayGUIValue& v) { text.show(toQString(v)); }, {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createSlider(const GPUDisplayGUIChannel::Element& el) +{ + ElementWidget retVal; + retVal.widget = new QWidget; + auto* row = new QHBoxLayout(retVal.widget); + row->setContentsMargins(0, 0, 0, 0); + auto* slider = new QSlider(Qt::Horizontal); + ValueEdit text; + const bool fixedLength = fixedLengthSlider(el); + const int32_t length = fixedLength ? (el.choices.size() ? el.choices.size() - 1 : std::lround(el.max - el.min)) * 2 * style()->pixelMetric(QStyle::PM_IndicatorWidth) : 150; // Fixed: about twice a checkbox indicator per step + mScaleSetters.emplace_back([slider, length, fixedLength](float scale) { + if (fixedLength) { + slider->setFixedWidth(std::lround(length * scale)); + } else { + slider->setMinimumWidth(std::lround(length * scale)); + } + }); + mScaleSetters.emplace_back([edit = text.edit](float scale) { edit->setFixedWidth(std::lround(60 * scale)); }); + row->addWidget(slider); + row->addWidget(text.edit); + if (fixedLength) { + row->addStretch(); // Left-aligned, otherwise the free space is distributed around the fixed-size widgets + } + if (el.choices.size()) { // Positions are the indices of the choices + auto choices = std::make_shared>>(el.choices); + slider->setRange(0, choices->size() - 1); + connect(slider, &QSlider::valueChanged, this, [this, text, id = el.id, choices](int32_t pos) { + text.show(QString::fromStdString((*choices)[pos].first)); + setValue(id, (*choices)[pos].second); + }); + connect(text.edit, &QLineEdit::editingFinished, this, [this, text, slider, id = el.id, choices]() { + if (const auto entered = text.number()) { + const uint32_t pos = nearestChoice(*choices, *entered); + text.commit(QString::fromStdString((*choices)[pos].first)); + const QSignalBlocker blocker(slider); + slider->setValue(pos); + setValue(id, (*choices)[pos].second); + } + }); + retVal.set = [slider, text, choices](const GPUDisplayGUIValue& value) { + const uint32_t best = nearestChoice(*choices, toDouble(value)); + const QSignalBlocker blocker(slider); + slider->setValue(best); + text.show(sameValue((*choices)[best].second, value) ? QString::fromStdString((*choices)[best].first) : toQString(value)); + }; + } else { + auto range = std::make_shared(el.min, el.max, el.isInteger); // Changed by dynamic options + slider->setRange(0, range->steps); + connect(slider, &QSlider::valueChanged, this, [this, text, id = el.id, range, isInteger = el.isInteger](int32_t pos) { + const GPUDisplayGUIValue v = toValue(range->value(pos), isInteger); + text.show(toQString(v)); + setValue(id, v); + }); + connect(text.edit, &QLineEdit::editingFinished, this, [this, text, slider, id = el.id, range, isInteger = el.isInteger]() { + if (const auto entered = text.number()) { + const GPUDisplayGUIValue v = toValue(std::clamp(*entered, range->min, range->value(range->steps)), isInteger); // Floating point values need not be on a slider step + text.commit(toQString(v)); + const QSignalBlocker blocker(slider); + slider->setValue(range->position(toDouble(v))); + setValue(id, v); + } + }); + retVal.set = [slider, text, range](const GPUDisplayGUIValue& value) { + const QSignalBlocker blocker(slider); + slider->setValue(range->position(toDouble(value))); + text.show(toQString(value)); + }; + retVal.setOptions = [slider, range, isInteger = el.isInteger](const GPUDisplayGUIChannel::Options& o) { + *range = SliderRange(o.min, o.max, isInteger); + const QSignalBlocker blocker(slider); + slider->setRange(0, range->steps); + }; + } + return retVal; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createRadioButtons(const GPUDisplayGUIChannel::Element& el) +{ + auto* 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 = el.id, choices = el.choices](int32_t i) { setValue(id, choices[i].second); }); // idClicked only on user clicks + return {widget, [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); + } + } + }, + {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createTextEdit(const GPUDisplayGUIChannel::Element& el) +{ + auto* w = new TextEdit(el.lines, [this, id = el.id](const QString& text) { setValue(id, text.toStdString()); }); + mScaleSetters.emplace_back([w](float scale) { w->setScale(scale); }); + return {w, [w](const GPUDisplayGUIValue& v) { w->showText(toQString(v)); }, {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createButton(const GPUDisplayGUIChannel::Element& el) // No value from the display +{ + auto* w = new QPushButton(QString::fromStdString(el.name)); + connect(w, &QPushButton::clicked, this, [this, id = el.id]() { setValue(id, (int64_t)1); }); + return {w, {}, {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createDropdown(const GPUDisplayGUIChannel::Element& el) +{ + auto* w = new QComboBox; + auto choices = std::make_shared>>(el.choices); // Changed by dynamic options + for (const auto& choice : *choices) { + w->addItem(QString::fromStdString(choice.first)); + } + connect(w, &QComboBox::currentIndexChanged, this, [this, id = el.id, choices](int32_t i) { + if (i >= 0 && i < (int32_t)choices->size()) { + setValue(id, (*choices)[i].second); + } + }); + return {w, [w, 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); + } + } }, + [w, choices](const GPUDisplayGUIChannel::Options& o) { + *choices = o.choices; + const QSignalBlocker blocker(w); + w->clear(); + for (const auto& choice : *choices) { + w->addItem(QString::fromStdString(choice.first)); + } + }}; +} + +QWidget* GPUDisplayGUI::buildElements(const std::vector& layout) +{ + using Type = GPUDisplayGUIChannel::ElementType; + 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); + framesLayout->setContentsMargins(0, 0, 0, 0); + 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 + } + auto* boxLayout = new QVBoxLayout(box); + QLayout* section = nullptr; // Consecutive elements with the same alignment + bool sectionHorizontal = false; + for (const auto& el : frame.elements) { + ElementWidget created; + switch (el.type) { + case Type::CHECKBOX: + created = createCheckbox(el); + break; + case Type::TEXTBOX: + created = createTextbox(el); + break; + case Type::SLIDER: + created = createSlider(el); + break; + case Type::RADIO_BUTTONS: + created = createRadioButtons(el); + break; + case Type::TEXTEDIT: + created = createTextEdit(el); + break; + case Type::BUTTON: + created = createButton(el); + break; + case Type::DROPDOWN: + created = createDropdown(el); + break; + } + QWidget* widget = created.widget; + QLabel* name = el.type == Type::CHECKBOX || el.type == Type::BUTTON || el.name.empty() ? nullptr : new QLabel(QString::fromStdString(el.name)); // Checkboxes and buttons show their own text, an empty name means no caption + widget->setToolTip(QString::fromStdString(el.description)); + if (name) { + name->setToolTip(widget->toolTip()); + } + if (!section || el.horizontal != sectionHorizontal) { + sectionHorizontal = el.horizontal; + if (sectionHorizontal) { + section = new FlowLayout(nullptr, 3, false); // More space between the elements than between the rows + } else { + auto* form = new QFormLayout; + form->setLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter); // Set explicitly, the defaults depend on the style: e.g. Breeze centers the form and aligns labels right + form->setFormAlignment(Qt::AlignLeft | Qt::AlignTop); + form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + form->setRowWrapPolicy(QFormLayout::DontWrapRows); + section = form; + } + section->setContentsMargins(0, 0, 0, 0); + boxLayout->addLayout(section); + } + if (!sectionHorizontal) { + name ? ((QFormLayout*)section)->addRow(name, widget) : ((QFormLayout*)section)->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); + section->addWidget(item); + } else { + section->addWidget(widget); + } + mElements.push_back({std::move(created.set), std::move(created.setOptions), el.get, el.dynamicOptions, std::nullopt}); // Element ids are sequential + } + framesLayout->addWidget(box); + if (std::any_of(frame.elements.begin(), frame.elements.end(), [](const auto& el) { return el.type == Type::SLIDER && !fixedLengthSlider(el); })) { // Sliders range to the frame border, frame limited to the window width by the layout + mScaleSetters.emplace_back([this, framesLayout, box](float scale) { framesLayout->setPreferredWidth(box, std::lround(screen()->availableGeometry().width() / 3 * scale)); }); + } + } + mScaleSetters.emplace_back([framesLayout](float) { framesLayout->invalidate(); }); + return frames; +} + +void GPUDisplayGUI::setScale(float scale) +{ + mScale = std::clamp(scale, 0.25f, 4.f); + for (const auto& setter : mScaleSetters) { + setter(mScale); + } + 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 (watched == mStatusLabel && event->type() == QEvent::Resize) { + fitStatusLabel(); + 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() @@ -113,36 +827,47 @@ void GPUDisplayGUI::keyPressEvent(QKeyEvent* event) int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event) { +#ifdef Q_OS_WIN + if (event->nativeScanCode() == 0x29) { // Key left of 1: console key (^), independent of the layout +#else + if (event->nativeScanCode() == 49) { // Key left of 1 (XKB keycode): console key (^), independent of the layout +#endif + return '^'; + } switch (event->key()) { case Qt::Key_Up: - return GPUDisplayFrontend::KEY_UP; + return GPUDisplayFrontendKeyVars::KEY_UP; case Qt::Key_Down: - return GPUDisplayFrontend::KEY_DOWN; + return GPUDisplayFrontendKeyVars::KEY_DOWN; case Qt::Key_Left: - return GPUDisplayFrontend::KEY_LEFT; + return GPUDisplayFrontendKeyVars::KEY_LEFT; case Qt::Key_Right: - return GPUDisplayFrontend::KEY_RIGHT; + return GPUDisplayFrontendKeyVars::KEY_RIGHT; case Qt::Key_PageUp: - return GPUDisplayFrontend::KEY_PAGEUP; + return GPUDisplayFrontendKeyVars::KEY_PAGEUP; case Qt::Key_PageDown: - return GPUDisplayFrontend::KEY_PAGEDOWN; + return GPUDisplayFrontendKeyVars::KEY_PAGEDOWN; case Qt::Key_Home: - return GPUDisplayFrontend::KEY_HOME; + return GPUDisplayFrontendKeyVars::KEY_HOME; case Qt::Key_End: - return GPUDisplayFrontend::KEY_END; + return GPUDisplayFrontendKeyVars::KEY_END; case Qt::Key_Insert: - return GPUDisplayFrontend::KEY_INSERT; + return GPUDisplayFrontendKeyVars::KEY_INSERT; case Qt::Key_Escape: - return GPUDisplayFrontend::KEY_ESCAPE; + return GPUDisplayFrontendKeyVars::KEY_ESCAPE; + case Qt::Key_Backspace: + return GPUDisplayFrontendKeyVars::KEY_BACKSPACE; + case Qt::Key_Delete: + return GPUDisplayFrontendKeyVars::KEY_DELETE; case Qt::Key_Return: case Qt::Key_Enter: - return GPUDisplayFrontend::KEY_ENTER; + return GPUDisplayFrontendKeyVars::KEY_ENTER; default: break; } if (event->key() >= Qt::Key_F1 && event->key() <= Qt::Key_F12) { - static_assert(GPUDisplayFrontend::KEY_F12 - GPUDisplayFrontend::KEY_F1 == Qt::Key_F12 - Qt::Key_F1); - return GPUDisplayFrontend::KEY_F1 + (event->key() - Qt::Key_F1); + static_assert(GPUDisplayFrontendKeyVars::KEY_F12 - GPUDisplayFrontendKeyVars::KEY_F1 == Qt::Key_F12 - Qt::Key_F1); + return GPUDisplayFrontendKeyVars::KEY_F1 + (event->key() - Qt::Key_F1); } const QString text = event->text(); if (text.size() == 1 && text[0].unicode() >= 32 && text[0].unicode() < 127) { // Printable ASCII, with shift applied diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h index e0da6423e94d2..786fb34c3548e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h @@ -15,24 +15,16 @@ #ifndef GPUDISPLAYGUI_H #define GPUDISPLAYGUI_H -#define QT_BEGIN_NAMESPACE \ - namespace o2::gpu::qtgui \ - { -#define QT_END_NAMESPACE } - #include "GPUDisplayGUIChannel.h" #include #include -#include +#include +#include +#include +#include class QKeyEvent; - -QT_BEGIN_NAMESPACE -namespace Ui -{ -class GPUDisplayGUI; -} -QT_END_NAMESPACE +class QLabel; namespace o2::gpu { @@ -42,24 +34,62 @@ class GPUDisplayGUI : public QMainWindow public: explicit GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent = nullptr); - ~GPUDisplayGUI() override; void bringToFront(); protected: void keyPressEvent(QKeyEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + bool eventFilter(QObject* watched, QEvent* event) override; // Ctrl + mouse wheel / Ctrl + 0 for all widgets of the window private: + struct ElementWidget { // Result of the create* functions for a GUI element + QWidget* widget = nullptr; + std::function set; // Set the element to a value from the display, signals are blocked + std::function setOptions; // Update range / choices of elements with dynamic options + }; + struct GUIElement { // By element id + std::function set; + std::function setOptions; + std::function get; // Value of the element from mState.cfg + std::function getOptions; // Called only while the display waits for it + std::optional applied; // Value last set from the display, empty after a change by the user: the display might not accept it + }; + 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 applyElement(GUIElement& element); // Set the element from mState.cfg if the value changed + void updateInfoLabels(); + void fitStatusLabel(); // Height needed for the wrapped text, otherwise the size hint of the label makes the status bar too high + bool darkPalette() const; + void setDarkMode(bool dark); // Requests the color scheme from the platform theme + void colorSchemeChanged(); // Style sheets and colors depending on the color scheme + QWidget* buildElements(const std::vector& layout); // One frame per add_gui_binding call, with a labeled element per option + ElementWidget createCheckbox(const GPUDisplayGUIChannel::Element& el); + ElementWidget createTextbox(const GPUDisplayGUIChannel::Element& el); + ElementWidget createSlider(const GPUDisplayGUIChannel::Element& el); + ElementWidget createRadioButtons(const GPUDisplayGUIChannel::Element& el); + ElementWidget createTextEdit(const GPUDisplayGUIChannel::Element& el); + ElementWidget createButton(const GPUDisplayGUIChannel::Element& el); + ElementWidget createDropdown(const GPUDisplayGUIChannel::Element& el); + 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 + std::vector mElements; + uint64_t mOptionsRevision = 0; // optionsRevision for which the option lambdas were called + std::vector> mScaleSetters; // Sizes of widgets and layouts that depend on the GUI scale + QLabel* mStatusLabel = nullptr; // Status line and, for 5 seconds, info message from the display, at the bottom of the window + QTimer mInfoTextTimer; + uint64_t mInfoTextCounter = 0; + bool mDarkMode = false; + 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 deleted file mode 100644 index 77b2a7401be11..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui +++ /dev/null @@ -1,66 +0,0 @@ - - - GPUDisplayGUI - - - - 0 - 0 - 899 - 679 - - - - GPUDisplayGUI - - - - - - - - - Line width - - - - - - - 1 - - - 50 - - - Qt::Orientation::Horizontal - - - - - - - - 40 - 0 - - - - - - - - - - Qt::Orientation::Vertical - - - - - - - - - - - diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h index cf24951477d0a..f5a85662ebdc1 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h @@ -15,40 +15,109 @@ #ifndef GPUDISPLAYGUICHANNEL_H #define GPUDISPLAYGUICHANNEL_H +#define GPUSETTINGS_DISPLAYCONTROL #include "GPUSettings.h" +#include #include #include #include +#include #include #include #include +#include +#include #include namespace o2::gpu { +using GPUDisplayGUIValue = std::variant; // Value of a Qt GUI element + +struct GPUDisplayGUIUpdate { // GUI → display + uint64_t id = 0; + 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 + std::optional guiDarkMode; // System color scheme, reported when the GUI starts if not yet set + bool empty() const { return keys.empty() && values.empty() && !guiScale && !guiDarkMode; } + void setValue(uint32_t element, GPUDisplayGUIValue value) // Replaces an earlier value of the same element + { + auto it = std::find_if(values.begin(), values.end(), [element](const auto& v) { return v.first == element; }); + if (it != values.end()) { + it->second = std::move(value); + } else { + values.emplace_back(element, std::move(value)); + } + } + void merge(const GPUDisplayGUIUpdate& other) // Newer changes on top of this update, id not changed + { + keys.insert(keys.end(), other.keys.begin(), other.keys.end()); + for (const auto& [element, value] : other.values) { + setValue(element, value); + } + guiScale = other.guiScale ? other.guiScale : guiScale; + guiDarkMode = other.guiDarkMode ? other.guiDarkMode : guiDarkMode; + } +}; + // Communication between the Qt GUI thread (GPUDisplayGUI) and the display thread (GPUDisplayFrontend::HandleExternalGUISignals), Qt-free. // The GUI sends at most one update per timer tick, the display processes it once per frame and publishes the complete settings with the id of the last processed update. // If the GUI went to sleep with an update ready and the display picks it up, the GUI waits for the reply when it wakes up, so it continues with the settings including its update. struct GPUDisplayGUIChannel { - struct Settings { - GPUSettingsDisplayHeavy cfgH; - GPUSettingsDisplayLight cfgL; - GPUSettingsDisplayRenderer cfgR; + struct Settings { // Copy of the config structs of GPUDisplay, with the same names, the GUI reads the values of its elements from it + GPUSettingsDisplayHeavy mCfgH; + GPUSettingsDisplayLight mCfgL; + GPUSettingsDisplayRenderer mCfgR; + GPUDisplayControl mCfgC; + }; + enum class ElementType : uint8_t { CHECKBOX, + TEXTBOX, + SLIDER, + RADIO_BUTTONS, + DROPDOWN, + BUTTON, + TEXTEDIT }; + + struct Options { // Dynamic range (slider) or choices (dropdown) of an element, returned by its option lambda + double min = 0, max = 0; + std::vector> choices; + }; + struct Element { // GUI element for one option, from GUI_LAMBDA in GPUDisplayFrontendConfigQtGUI.inc + std::string name; // Label + std::string description; // Tooltip, from the RTTI description of the option + uint32_t rttiIndex; // Option in the display config RTTI, invalid for options without RTTI (GPUDisplayControl) + ElementType type; // Widget type + uint32_t id; // Index of the element in Update::values + bool isInteger; // Integer / bool option, otherwise floating point + bool isString = false; // String option (text field sends the text) + bool horizontal = false; // In a row with the neighbouring horizontal elements, wrapping at the window border, otherwise an own row + uint32_t lines = 0; // Text edit: initial number of lines + bool hasRange = false; // Text field: value 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 + std::function dynamicOptions; // Called by the GUI only while the display waits for it (see optionsRevision), reads display data + std::function get; // Value of the option from a copy of the settings, callable from the GUI thread }; - 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; } + struct Frame { // One add_gui_binding call + std::string description; + std::vector elements; }; + using Update = GPUDisplayGUIUpdate; 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::string infoStatus; // Status line for the bottom of the GUI, empty if not sent + std::string infoText; // Latest info message, empty if not sent + uint64_t infoTextCounter = 0; // Incremented with every info message + uint64_t optionsRevision = 0; // Incremented when the GUI must call the dynamic option lambdas again (new event, GUI started) + 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 std::condition_variable stateUpdated; // Notified when a new state is published or the GUI shall stop + std::condition_variable optionsRead; // Notified by the GUI when it called the dynamic option lambdas + uint64_t optionsReadRevision = 0; // optionsRevision for which the GUI called the dynamic option lambdas + bool displayWaitingForOptions = false; // Display waits until the GUI called the dynamic option lambdas, only then the GUI may call them std::optional update; // Pending update, not yet picked up by the display State state; // Latest state published by the display bool guiSleeping = false; // GUI waits for its next timer tick with an update ready @@ -57,6 +126,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/GPUDisplayInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc index b6ac78b31f315..584c3578684d8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc @@ -17,6 +17,7 @@ #include "GPUDisplay.h" #include "GPULogging.h" +#include "frontend/GPUDisplayConsole.h" namespace o2::gpu { @@ -29,6 +30,9 @@ void GPUDisplay::SetInfo(Args... args) snprintf(mInfoText2, 1024, args...); #pragma GCC diagnostic pop GPUInfo("%s", mInfoText2); + mFrontend->mConsole->consoleInfo(std::get<0>(std::tuple(args...)), mInfoText2); + mGUIInfoText = mInfoText2; + mGUIInfoTextCounter++; mInfoText2Timer.ResetStart(); } } // namespace o2::gpu 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..6873a4b93b76a 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -19,6 +19,9 @@ using namespace o2::gpu; +const std::array GPUDisplay::ANIMATION_MODE_NAMES_SHORT = {"Cartesian, quaternion", "Cartesian, Euler angles", "Spherical Rotation, quaternion", "Spherical Rotation, Euler angles", "Spherical, quaternion", "Spherical, Euler angles", "Spherical, centered on origin"}; +const std::array GPUDisplay::ANIMATION_MODE_NAMES_LONG = {"Position: Cartesian, Direction: Quaternion", "Position: Cartesian, Direction: Euler angles", "Position: Spherical (spherical rotation), Direction: Quaternion", "Position: Spherical (spherical rotation), Direction: Euler angles", "Position: Spherical (Euler angles), Direction: Quaternion", "Position: Spherical (Euler angles), Direction: Euler angles", "Position: Spherical (Euler angles), Direction: Centered on origin"}; + constexpr hmm_mat4 MY_HMM_FROM(float (&v)[16]) { return {{{v[0], v[1], v[2], v[3]}, {v[4], v[5], v[6], v[7]}, {v[8], v[9], v[10], v[11]}, {v[12], v[13], v[14], v[15]}}}; } void GPUDisplay::setAnimationPoint() @@ -67,7 +70,7 @@ void GPUDisplay::setAnimationPoint() } float delay = 0.f; if (mAnimateVectors[0].size()) { - delay = mAnimateVectors[0].back() + ((int32_t)(mAnimationDelay * 20)) / 20.f; + delay = mAnimateVectors[0].back() + ((int32_t)(mCfgC.mAnimationDelay * 20)) / 20.f; } mAnimateVectors[0].emplace_back(delay); mAnimateConfig.emplace_back(mCfgL); @@ -93,6 +96,19 @@ void GPUDisplay::removeAnimationPoint() mAnimateConfig.pop_back(); } +void GPUDisplay::runAnimation() +{ + if (mAnimateVectors[0].size() > 1) { + if (mCfgC.mAnimateScreenshot) { + mAnimationExport++; + } + startAnimation(); + SetInfo("Starting Animation", 1); + } else { + SetInfo("Insufficient Animation points to start Animation", 1); + } +} + void GPUDisplay::startAnimation() { for (int32_t i = 0; i < 8; i++) { @@ -105,11 +121,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 +134,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 +148,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 +168,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..6d7d29426c10c 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -13,6 +13,8 @@ /// \author David Rohr #include "GPUDisplay.h" +#include "frontend/GPUDisplayInfo.inc" +#include "HandMadeMath.h" #include "GPUChainTracking.h" #include "GPULogging.h" #include "GPUParam.h" @@ -54,6 +56,105 @@ void GPUDisplay::disableUnsupportedOptions() } } +void GPUDisplay::takeScreenshot() +{ + static int32_t nScreenshot = 1; + mCfgC.mRequestScreenshot = true; + mScreenshotFile = mCfgC.mScreenShotname + std::to_string(nScreenshot++) + ".bmp"; +} + +void GPUDisplay::SaveGLPos() +{ + const char* file = mCfgC.mGLPosFile.c_str(); + FILE* ftmp = fopen(file, "w+b"); + if (!ftmp) { + SetInfo("Error opening file %s", file); + return; + } + bool ok = fwrite(mViewMatrix.get(), sizeof(*mViewMatrix), 1, ftmp) == 1; + ok = fclose(ftmp) == 0 && ok; + SetInfo(ok ? "Camera position stored to file %s" : "Error writing camera position to file %s", file); +} + +void GPUDisplay::LoadGLPos() +{ + const char* file = mCfgC.mGLPosFile.c_str(); + FILE* ftmp = fopen(file, "rb"); + if (!ftmp) { + SetInfo("Error opening file %s", file); + return; + } + hmm_mat4 matrix; + const bool ok = fread(&matrix, sizeof(matrix), 1, ftmp) == 1; + fclose(ftmp); + if (ok) { + *mViewMatrix = matrix; + } + SetInfo(ok ? "Camera position loaded from file %s" : "Error reading camera position from file %s", file); +} + +void GPUDisplay::SaveGLAnimation() +{ + const char* file = mCfgC.mGLAnimationFile.c_str(); + FILE* ftmp = fopen(file, "w+b"); + if (!ftmp) { + SetInfo("Error opening file %s", file); + return; + } + const int32_t size = mAnimateVectors[0].size(); + bool ok = fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp) == 1 && fwrite(&size, sizeof(size), 1, ftmp) == 1; + for (int32_t i = 0; i < 9; i++) { + ok = ok && fwrite(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp) == (size_t)size; + } + ok = ok && fwrite(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp) == (size_t)size; + ok = fclose(ftmp) == 0 && ok; + SetInfo(ok ? "Animation path stored to file %s" : "Error writing animation path to file %s", file); +} + +void GPUDisplay::LoadGLAnimation() +{ + const char* file = mCfgC.mGLAnimationFile.c_str(); + FILE* ftmp = fopen(file, "rb"); + if (!ftmp) { + SetInfo("Error opening file %s", file); + return; + } + GPUSettingsDisplayLight cfg; + int32_t size = 0; + bool ok = fread(&cfg, sizeof(cfg), 1, ftmp) == 1 && fread(&size, sizeof(size), 1, ftmp) == 1 && size >= 0; + if (ok) { // Check the file size before allocating memory or changing anything + const long start = ftell(ftmp); + fseek(ftmp, 0, SEEK_END); + ok = (size_t)ftell(ftmp) == start + (size_t)size * (9 * sizeof(mAnimateVectors[0][0]) + sizeof(mAnimateConfig[0])); + fseek(ftmp, start, SEEK_SET); + } + if (ok) { + for (int32_t i = 0; i < 9; i++) { + mAnimateVectors[i].resize(size); + ok = ok && fread(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp) == (size_t)size; + } + mAnimateConfig.resize(size); + ok = ok && fread(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp) == (size_t)size; + if (ok) { + mCfgL = cfg; + std::get<1>(mOldCfg).animationMode = cfg.animationMode; // Otherwise the mode change resets the animation which was just loaded + updateConfig(); + } else { + resetAnimation(); // Read error after the size check: no incomplete path + } + } + fclose(ftmp); + SetInfo(ok ? "Animation path loaded from file %s" : "Error reading animation path from file %s", file); +} + +std::string GPUDisplay::trackFilterName() const +{ + if (mCfgH.trackFilter <= 0) { + return ""; + } + return (uint32_t)mCfgH.trackFilter <= mConfig.filterMacros.size() ? "displayTrackFilter/" + mConfig.filterMacros[mCfgH.trackFilter - 1] : "Custom Macro"; +} + void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, float animateTime) { size_t screenshot_x = mBackend->mScreenWidth * mCfgR.screenshotScaleFactor; @@ -99,28 +200,45 @@ void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, f void GPUDisplay::showInfo(const char* info) { - mBackend->updateFontScale(mFrontend->getDisplayScale()); - mBackend->prepareText(); + if (mCfgC.mPrintInfoTextGUI) { + mGUIInfoStatus = info; + } + if (!(mCfgC.mPrintInfoText & 1)) { + return; + } 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 { PrintGLHelpText(colorValue); } } - mBackend->finishText(); } -void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) +float GPUDisplay::consoleVerticalFraction() +{ + const bool running = mConsoleTimer.IsRunning(); + if (!mCfgC.showConsole && !running) { + return 0.f; + } + const double time = mConsoleTimer.GetCurrentElapsedTime(); + if (running && !mCfgC.showConsole && time >= mConfig.consoleDelay) { // Keeps running while open, for the blinking cursor + mConsoleTimer.Stop(); + } + const float progress = running && mConfig.consoleDelay > 0.f ? std::min(1.f, (float)(time / mConfig.consoleDelay)) : 1.f; + return mConfig.consoleVertical * (mCfgC.showConsole ? progress : 1.f - progress); +} + +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 +248,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/display/helpers/GPUDisplayROOT.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx index 7108386edccf4..5d2ee260fa838 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx @@ -27,6 +27,10 @@ using namespace o2::gpu; #include "TROOT.h" #include "TSystem.h" #include "TMethodCall.h" +#include "TInterpreter.h" +#include +#include +#include int32_t GPUDisplay::buildTrackFilter() { @@ -34,13 +38,38 @@ int32_t GPUDisplay::buildTrackFilter() return 0; } if (mUpdateTrackFilter) { - std::string name = "displayTrackFilter/"; - name += mConfig.filterMacros[mCfgH.trackFilter - 1]; + std::string name = trackFilterName(); + const bool custom = (uint32_t)mCfgH.trackFilter > mConfig.filterMacros.size(); + if (custom) { // Code from the Qt GUI, written to a temporary file with a new name for every change, so that ROOT loads it again + if (mCfgC.mFilterMacroCustom.empty()) { + GPUError("No custom track filter macro set"); + return 1; + } + static uint32_t nCustomMacro = 0; + name = (std::filesystem::temp_directory_path() / ("o2_gpu_display_track_filter_" + std::to_string(getpid()) + "_" + std::to_string(nCustomMacro++) + ".C")).string(); + std::ofstream(name) << mCfgC.mFilterMacroCustom << "\n"; + } + if (!mTrackFilterMacroLoaded.empty()) { // All macros define gpuDisplayTrackFilter, the previous one must be unloaded + const bool recreate = !std::filesystem::exists(mTrackFilterMacroLoaded); // ROOT cannot unload a macro if its file no longer exists, the content does not matter + if (recreate) { + std::ofstream{mTrackFilterMacroLoaded}; + } + gInterpreter->UnloadFile(mTrackFilterMacroLoaded.c_str()); + if (recreate) { + std::filesystem::remove(mTrackFilterMacroLoaded); + } + mTrackFilterMacroLoaded.clear(); + } gROOT->Reset(); - if (gROOT->LoadMacro(name.c_str())) { + const int32_t loadError = gROOT->LoadMacro(name.c_str()); + if (custom) { + std::filesystem::remove(name); + } + if (loadError) { GPUError("Error loading trackFilter macro %s", name.c_str()); return 1; } + mTrackFilterMacroLoaded = name; } TMethodCall call; call.InitWithPrototype("gpuDisplayTrackFilter", "std::vector*, const o2::gpu::GPUTrackingInOutPointers*, const o2::gpu::GPUConstantMem*"); diff --git a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx index a18b3e3fe2140..7e85a14674319 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx @@ -654,7 +654,7 @@ void GPUDisplay::DrawFinal(int32_t iSector, int32_t /*iCol*/, const GPUTPCGMProp break; } float sa = sinf(alpha), ca = cosf(alpha); - float drawX = trkParam.X() + mCfgH.xAdd; + float drawX = trkParam.X() + mCfgH.xzAdd; useBuffer.emplace_back((ca * drawX - sa * trkParam.Y()) * GL_SCALE_FACTOR, (ca * trkParam.Y() + sa * drawX) * mYFactor * GL_SCALE_FACTOR, mCfgH.projectXY ? 0 : (trkParam.Z() + ZOffset) * GL_SCALE_FACTOR); x += inFlyDirection ? 1 : -1; } @@ -685,17 +685,17 @@ GPUDisplay::vboList GPUDisplay::DrawGrid(const GPUTPCTracker& tracker) for (int32_t j = 0; j <= (signed)row.Grid().Ny(); j++) { float z1 = row.Grid().ZMin(); float z2 = row.Grid().ZMax(); - float x = row.X() + mCfgH.xAdd; + float x = row.X() + mCfgH.xzAdd; float y = row.Grid().YMin() + (float)j / row.Grid().StepYInv(); float zz1, zz2, yy1, yy2, xx1, xx2; mParam->Sector2Global(tracker.ISector(), x, y, z1, &xx1, &yy1, &zz1); mParam->Sector2Global(tracker.ISector(), x, y, z2, &xx2, &yy2, &zz2); if (iSector < 18) { - zz1 += mCfgH.zAdd; - zz2 += mCfgH.zAdd; + zz1 += mCfgH.xzAdd; + zz2 += mCfgH.xzAdd; } else { - zz1 -= mCfgH.zAdd; - zz2 -= mCfgH.zAdd; + zz1 -= mCfgH.xzAdd; + zz2 -= mCfgH.xzAdd; } mVertexBuffer[iSector].emplace_back(xx1 * GL_SCALE_FACTOR, yy1 * GL_SCALE_FACTOR * mYFactor, zz1 * GL_SCALE_FACTOR); mVertexBuffer[iSector].emplace_back(xx2 * GL_SCALE_FACTOR, yy2 * GL_SCALE_FACTOR * mYFactor, zz2 * GL_SCALE_FACTOR); @@ -703,17 +703,17 @@ GPUDisplay::vboList GPUDisplay::DrawGrid(const GPUTPCTracker& tracker) for (int32_t j = 0; j <= (signed)row.Grid().Nz(); j++) { float y1 = row.Grid().YMin(); float y2 = row.Grid().YMax(); - float x = row.X() + mCfgH.xAdd; + float x = row.X() + mCfgH.xzAdd; float z = row.Grid().ZMin() + (float)j / row.Grid().StepZInv(); float zz1, zz2, yy1, yy2, xx1, xx2; mParam->Sector2Global(tracker.ISector(), x, y1, z, &xx1, &yy1, &zz1); mParam->Sector2Global(tracker.ISector(), x, y2, z, &xx2, &yy2, &zz2); if (iSector < 18) { - zz1 += mCfgH.zAdd; - zz2 += mCfgH.zAdd; + zz1 += mCfgH.xzAdd; + zz2 += mCfgH.xzAdd; } else { - zz1 -= mCfgH.zAdd; - zz2 -= mCfgH.zAdd; + zz1 -= mCfgH.xzAdd; + zz2 -= mCfgH.xzAdd; } mVertexBuffer[iSector].emplace_back(xx1 * GL_SCALE_FACTOR, yy1 * GL_SCALE_FACTOR * mYFactor, zz1 * GL_SCALE_FACTOR); mVertexBuffer[iSector].emplace_back(xx2 * GL_SCALE_FACTOR, yy2 * GL_SCALE_FACTOR * mYFactor, zz2 * GL_SCALE_FACTOR); diff --git a/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx b/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx index 27c9f98f01fa6..5209dc9262819 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx @@ -40,6 +40,7 @@ using namespace o2::gpu; void GPUDisplay::DrawGLScene_updateEventData() { + mUpdateGUIOptions = true; // E.g. number of collisions HighResTimer timer(mChain->GetProcessingSettings().debugLevel >= 2); if (mIOPtrs->clustersNative) { mCurrentClusters = mIOPtrs->clustersNative->nClustersTotal; @@ -119,14 +120,14 @@ void GPUDisplay::DrawGLScene_updateEventData() mTrackFilter.resize(nTracks); std::fill(mTrackFilter.begin(), mTrackFilter.end(), true); if (buildTrackFilter()) { - SetInfo("Error running track filter from %s", mConfig.filterMacros[mCfgH.trackFilter - 1].c_str()); + SetInfo("Error running track filter from %s", trackFilterName().c_str()); } else { uint32_t nFiltered = 0; for (uint32_t i = 0; i < mTrackFilter.size(); i++) { nFiltered += !mTrackFilter[i]; } if (mUpdateTrackFilter) { - SetInfo("Applied track filter %s - filtered %u / %u", mConfig.filterMacros[mCfgH.trackFilter - 1].c_str(), nFiltered, (uint32_t)mTrackFilter.size()); + SetInfo("Applied track filter %s - filtered %u / %u", trackFilterName().c_str(), nFiltered, (uint32_t)mTrackFilter.size()); } } } @@ -155,12 +156,12 @@ void GPUDisplay::DrawGLScene_updateEventData() if (mCfgH.clustersOnNominalRow) { x = GPUTPCGeometry::Row2X(row); } - mParam->Sector2Global(iSector, x + mCfgH.xAdd, y, z, &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(iSector, x + mCfgH.xzAdd, y, z, &ptr->x, &ptr->y, &ptr->z); if (fabsf(ptr->z) > maxClusterZ) { maxClusterZ = fabsf(ptr->z); } - ptr->z += iSector < 18 ? mCfgH.zAdd : -mCfgH.zAdd; + ptr->z += iSector < 18 ? mCfgH.xzAdd : -mCfgH.xzAdd; ptr->x *= GL_SCALE_FACTOR; ptr->y *= GL_SCALE_FACTOR; ptr->z *= GL_SCALE_FACTOR; @@ -185,7 +186,7 @@ void GPUDisplay::DrawGLScene_updateEventData() const auto& sp = mIOPtrs->trdSpacePoints[i]; int32_t iSec = trdGeometry()->GetSector(mIOPtrs->trdTracklets[i].GetDetector()); float4* ptr = &mGlobalPosTRD[i]; - mParam->Sector2Global(iSec, sp.getX() + mCfgH.xAdd, sp.getY(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(iSec, sp.getX() + mCfgH.xzAdd, sp.getY(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); ptr->z += ptr->z > 0 ? trdZoffset : -trdZoffset; if (fabsf(ptr->z) > maxClusterZ) { maxClusterZ = fabsf(ptr->z); @@ -195,7 +196,7 @@ void GPUDisplay::DrawGLScene_updateEventData() ptr->z *= GL_SCALE_FACTOR; ptr->w = tTRDCLUSTER; ptr = &mGlobalPosTRD2[i]; - mParam->Sector2Global(iSec, sp.getX() + mCfgH.xAdd + 4.5f, sp.getY() + 1.5f * sp.getDy(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(iSec, sp.getX() + mCfgH.xzAdd + 4.5f, sp.getY() + 1.5f * sp.getDy(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); ptr->z += ptr->z > 0 ? trdZoffset : -trdZoffset; if (fabsf(ptr->z) > maxClusterZ) { maxClusterZ = fabsf(ptr->z); @@ -214,7 +215,7 @@ void GPUDisplay::DrawGLScene_updateEventData() mMaxClusterZ = tbb::parallel_reduce(tbb::blocked_range(0, mCurrentClustersTOF, 32), float(mMaxClusterZ), [&](const tbb::blocked_range& r, float maxClusterZ) { for (int32_t i = r.begin(); i < r.end(); i++) { float4* ptr = &mGlobalPosTOF[i]; - mParam->Sector2Global(mIOPtrs->tofClusters[i].getSector(), mIOPtrs->tofClusters[i].getX() + mCfgH.xAdd, mIOPtrs->tofClusters[i].getY(), mIOPtrs->tofClusters[i].getZ(), &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(mIOPtrs->tofClusters[i].getSector(), mIOPtrs->tofClusters[i].getX() + mCfgH.xzAdd, mIOPtrs->tofClusters[i].getY(), mIOPtrs->tofClusters[i].getZ(), &ptr->x, &ptr->y, &ptr->z); float ZOffset = 0; if (mParam->par.continuousTracking) { float tofTime = mIOPtrs->tofClusters[i].getTime() * 1e-3 / o2::constants::lhc::LHCBunchSpacingNS / o2::tpc::constants::LHCBCPERTIMEBIN; diff --git a/GPU/GPUTracking/qconfigoptions.h b/GPU/GPUTracking/qconfigoptions.h index 2a43c3f59cc3f..9e0bd05705e48 100644 --- a/GPU/GPUTracking/qconfigoptions.h +++ b/GPU/GPUTracking/qconfigoptions.h @@ -12,6 +12,18 @@ /// \file qconfigoptions.h /// \author David Rohr -#include "GPUSettings.h" // Usually we are included from GPUSettings.h, so this doesn't do anything. - // But ROOT or qconfig.cpp will include us directly, so load the settings first. +// clang-format off +#if !defined(QCONFIGOPTIONS_H_LIST) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_LIST #include "GPUSettingsList.h" +#endif + +#if ((defined(GPUSETTINGS_PARAM) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_PARAM)) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_PARAM +#include "GPUSettingsParam.h" +#endif + +#if ((defined(GPUSETTINGS_DISPLAYCONTROL) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_DISPLAYCONTROL)) || (defined(QCONFIG_INTERNAL) && !defined(QCONFIG_PRINT_RTC)) +#define QCONFIGOPTIONS_H_DISPLAYCONTROL +#include "display/GPUDisplayControl.h" +#endif diff --git a/GPU/GPUTracking/utils/linux_helpers.h b/GPU/GPUTracking/utils/linux_helpers.h index 9de9f49d8207d..7f3be49dcd97b 100644 --- a/GPU/GPUTracking/utils/linux_helpers.h +++ b/GPU/GPUTracking/utils/linux_helpers.h @@ -26,7 +26,8 @@ static inline int32_t getch() newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); - int32_t retVal = getchar(); + unsigned char c; + int32_t retVal = read(STDIN_FILENO, &c, 1) == 1 ? c : -1; // Not getchar(): its buffer would hide the remaining bytes from kbhit() tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return (retVal); } diff --git a/GPU/GPUTracking/utils/qconfig.cxx b/GPU/GPUTracking/utils/qconfig.cxx index 8a15a6ecdd9ef..1b3adaa3a78ee 100644 --- a/GPU/GPUTracking/utils/qconfig.cxx +++ b/GPU/GPUTracking/utils/qconfig.cxx @@ -17,11 +17,19 @@ #include #include #include +#include +#include #include #include +#include +#include +#include #include +#define QCONFIG_INTERNAL #include "qconfig.h" +#undef QCONFIG_INTERNAL #include "qconfig_helpers.h" +#include "qconfig_rtti.h" #include "qconfig_internal_helpers.h" // Create config instances @@ -41,19 +49,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 +56,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 +116,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 +217,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 +425,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 +433,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.h b/GPU/GPUTracking/utils/qconfig.h index d510cd727d12c..21261cd23ff3e 100644 --- a/GPU/GPUTracking/utils/qconfig.h +++ b/GPU/GPUTracking/utils/qconfig.h @@ -13,13 +13,7 @@ /// \author David Rohr #include "qconfig_general.h" - -#if !defined(QCONFIG_HEADER_GUARD_NO_INCLUDE) || defined(QCONFIG_GENRTC) - -#if defined(QCONFIG_HEADER_GUARD) && !defined(QCONFIG_GENRTC) -#define QCONFIG_HEADER_GUARD_NO_INCLUDE -#else -#define QCONFIG_HEADER_GUARD +#define QCONFIG_H #ifndef BeginNamespace #define BeginNamespace(name) \ @@ -66,8 +60,4 @@ }; // clang-format on -#endif - #include "qconfig_wrapinclude.h" - -#endif // QCONFIG_HEADER_GUARD_NO_INCLUDE 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..7e71bcedc226b 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) \ @@ -210,6 +208,7 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // // End QCONFIG_PRINT #elif defined(QCONFIG_INSTANCE) #define BeginNamespace(name) \ + using namespace name; \ namespace name \ { #define EndNamespace() } @@ -254,7 +253,7 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // out << "struct " << qon_mxstr(name) << " {\n"; #define BeginSubConfig(name, instance, parent, preoptname, preoptnameshort, descr, ...) BeginConfig(name, instance) #define EndConfig() \ - out << "};"; \ + out << "};\n"; \ } #define BeginNamespace(name) out << "namespace " << qon_mxstr(name) << " {\n"; #define EndNamespace() out << "}\n"; @@ -262,12 +261,37 @@ 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 #endif +#define QCONFIG_INTERNAL #include "qconfig_wrapinclude.h" - -#endif // QCONFIG_HEADER_GUARD_NO_INCLUDE +#undef QCONFIG_INTERNAL 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 diff --git a/GPU/GPUTracking/utils/qconfig_wrapinclude.h b/GPU/GPUTracking/utils/qconfig_wrapinclude.h index ae6350ebd5f43..40a52b45948f7 100644 --- a/GPU/GPUTracking/utils/qconfig_wrapinclude.h +++ b/GPU/GPUTracking/utils/qconfig_wrapinclude.h @@ -12,7 +12,10 @@ /// \file qconfig_wrapinclude.h /// \author David Rohr -#ifndef QCONFIG_HEADER_GUARD_NO_INCLUDE +#ifndef QCONFIG_H +#error Must include qconfig.h before using qconfig_internal.h, etc... +#endif + #ifndef AddHelp #define AddHelp(cmd, cmdshort) #endif @@ -53,6 +56,10 @@ #define BeginHiddenConfig(name, instance) BeginSubConfig(name, instance, , , , ) #endif +#ifdef QCONFIG_GENRTC +#define QCONFIG_INTERNAL +#endif + #include "qconfigoptions.h" #undef AddOption @@ -79,7 +86,6 @@ #ifdef QCONFIG_COMPARE #undef QCONFIG_COMPARE #endif - -#else -#undef QCONFIG_HEADER_GUARD_NO_INCLUDE +#ifdef QCONFIG_INTERNAL +#undef QCONFIG_INTERNAL #endif