From 69a317bd452a6ff3c109c0fe83fad5c0d2e371ef Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 17 Oct 2026 00:34:05 +0200 Subject: [PATCH 01/41] GPU: remove obsolete config code --- GPU/GPUTracking/utils/qconfig_internal.h | 4 ---- GPU/GPUTracking/utils/qconfig_rtc.h | 9 --------- 2 files changed, 13 deletions(-) diff --git a/GPU/GPUTracking/utils/qconfig_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index 04ecced0cb34a..72fb6fbe023ef 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) \ @@ -269,5 +267,3 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // #endif #include "qconfig_wrapinclude.h" - -#endif // QCONFIG_HEADER_GUARD_NO_INCLUDE diff --git a/GPU/GPUTracking/utils/qconfig_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) { From ec2c994a90739d9ce2e5538956c65adeab33271f Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 22 Oct 2026 07:49:57 +0200 Subject: [PATCH 02/41] GPU Display: Fix FPE with display width = 0, e.g. with none backend --- GPU/GPUTracking/display/GPUDisplay.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 2db90ea9a5fed..991b9b4736ef9 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -673,7 +673,8 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) // 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); + 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, doScreenshot || mRequestScreenshot, renderToMixBuffer, mixSlaveImage)) { return; // No image available (e.g. hidden window), retry with the next frame } From 4cf5418c098584bc146fd611c93ec197ee978e66 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Fri, 16 Oct 2026 10:36:03 +0200 Subject: [PATCH 03/41] GPU: Possibility to generate run time type information about config structure --- GPU/GPUTracking/DataTypes/GPUConfigDump.cxx | 5 +- .../display/frontend/GPUDisplayFrontend.cxx | 11 +++ GPU/GPUTracking/utils/qconfig.cxx | 83 ++++--------------- GPU/GPUTracking/utils/qconfig_general.h | 11 ++- GPU/GPUTracking/utils/qconfig_genrtti.inc | 78 +++++++++++++++++ GPU/GPUTracking/utils/qconfig_internal.h | 22 +++++ .../utils/qconfig_internal_helpers.h | 72 ++++++++++++++++ GPU/GPUTracking/utils/qconfig_print.inc | 26 ++++++ GPU/GPUTracking/utils/qconfig_rtti.h | 43 ++++++++++ 9 files changed, 275 insertions(+), 76 deletions(-) create mode 100644 GPU/GPUTracking/utils/qconfig_genrtti.inc create mode 100644 GPU/GPUTracking/utils/qconfig_print.inc create mode 100644 GPU/GPUTracking/utils/qconfig_rtti.h diff --git a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx index 9353e3664d05a..3aab0270671f7 100644 --- a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx +++ b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx @@ -27,10 +27,7 @@ using namespace o2::gpu; namespace { GPUSettingsStandalone configStandalone; -std::vector> qprint_global; -#define QCONFIG_PRINT -#include "utils/qconfig_internal.h" -#undef QCONFIG_PRINT +#include "utils/qconfig_print.inc" } // namespace void GPUConfigDump::dumpConfig(const GPUSettingsRec* rec, const GPUSettingsProcessing* proc, const GPUSettingsQA* qa, const GPUSettingsDisplay* display, const GPUSettingsDeviceBackend* device, const GPURecoStepConfiguration* workflow) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index b48d7f6c59d5c..3bf8011eddf43 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -40,11 +40,17 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include "GPUDisplayGUIChannel.h" #include "GPULogging.h" +#include "utils/qconfig_rtti.h" +#include "utils/qconfig_internal_helpers.h" #include #include #include #include +#include +#include +#include #include +#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -60,6 +66,11 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; +namespace +{ +#include "utils/qconfig_genrtti.inc" +} // namespace + GPUDisplayFrontend::GPUDisplayFrontend() = default; GPUDisplayFrontend::~GPUDisplayFrontend() = default; diff --git a/GPU/GPUTracking/utils/qconfig.cxx b/GPU/GPUTracking/utils/qconfig.cxx index 8a15a6ecdd9ef..6186c8950ca6f 100644 --- a/GPU/GPUTracking/utils/qconfig.cxx +++ b/GPU/GPUTracking/utils/qconfig.cxx @@ -17,11 +17,16 @@ #include #include #include +#include #include #include +#include +#include +#include #include #include "qconfig.h" #include "qconfig_helpers.h" +#include "qconfig_rtti.h" #include "qconfig_internal_helpers.h" // Create config instances @@ -41,19 +46,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 +53,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 +113,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 +214,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 +422,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 +430,10 @@ void qConfigPrint() f(); } } +namespace internal +{ +#include "qconfig_genrtti.inc" +} // namespace internal + +std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs) { return internal::qConfigGenRTTI(configStructs); } } // end namespace qConfig diff --git a/GPU/GPUTracking/utils/qconfig_general.h b/GPU/GPUTracking/utils/qconfig_general.h index 225808107a4cf..6804a91666ef0 100644 --- a/GPU/GPUTracking/utils/qconfig_general.h +++ b/GPU/GPUTracking/utils/qconfig_general.h @@ -17,16 +17,21 @@ #ifndef GPUCA_GPUCODE_DEVICE #include #include +#include +#include #endif namespace qConfig { +struct qConfigRTTIentry; #ifndef GPUCA_GPUCODE_DEVICE -int32_t qConfigParse(int argc, const char** argv, const char* filename = nullptr); -void qConfigPrint(); // Print all settings +int32_t qConfigParse(int argc, const char** argv, const char* filename = nullptr); // Parse qConfig options +void qConfigPrint(); // Print all settings template -void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName = ""); // Print one sub config, specializations created in qconfig.cxx +void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName = ""); // Print one sub config, specializations created in qconfig.cxx +std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs = nullptr); // Entries and list of config structs, for the given config structs (all if nullptr) #endif + enum qConfigRetVal { qcrOK = 0, qcrError = 1, qcrMinFailure = 2, diff --git a/GPU/GPUTracking/utils/qconfig_genrtti.inc b/GPU/GPUTracking/utils/qconfig_genrtti.inc new file mode 100644 index 0000000000000..c0a06920375a1 --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_genrtti.inc @@ -0,0 +1,78 @@ +// 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", "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); +} + +static inline std::optional qConfigRTTIStructIndex(std::vector& configStructs, const char* name, bool filter) // Index of name in configStructs, appended if not filtering, nullopt if filtered out +{ + 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 +static inline void qConfigRTTIEntry(std::vector<::qConfig::qConfigRTTIentry>& retVal, ::qConfig::qConfigRTTIentry 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)); +} + +static inline std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs) // Entries and list of config structs, for the given config structs (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 (configStructs) { + retStructs = *configStructs; + } +#define QCONFIG_GEN_RTTI +#include "qconfig_internal.h" +#undef QCONFIG_GEN_RTTI +#undef QCONFIG_RTTI + 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 72fb6fbe023ef..cb646efe5d7c5 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -260,6 +260,28 @@ 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, \ + {.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 (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 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_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 From 92fa8461956615494f7057b79c29c46931d460dc Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 17 Oct 2026 00:53:38 +0200 Subject: [PATCH 04/41] GPU Display: Reorganize frontend to have GPUSettingsFrontendConfig.cxx file, with all code for configuration updates --- GPU/GPUTracking/display/CMakeLists.txt | 3 +- .../display/frontend/GPUDisplayFrontend.cxx | 25 ----- .../frontend/GPUDisplayFrontendConfig.cxx | 91 +++++++++++++++++++ .../display/frontend/GPUDisplayKeys.cxx | 45 --------- GPU/GPUTracking/utils/qconfig_genrtti.inc | 2 +- 5 files changed, 94 insertions(+), 72 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 9edefc19a072c..a274c481d0365 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -113,7 +113,8 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx helpers/GPUDisplayQuaternion.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx - frontend/GPUDisplayKeys.cxx) + frontend/GPUDisplayKeys.cxx + frontend/GPUDisplayFrontendConfig.cxx) set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 3bf8011eddf43..43983b688de8a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -40,17 +40,11 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include "GPUDisplayGUIChannel.h" #include "GPULogging.h" -#include "utils/qconfig_rtti.h" -#include "utils/qconfig_internal_helpers.h" #include #include #include #include -#include -#include -#include #include -#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -66,11 +60,6 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; -namespace -{ -#include "utils/qconfig_genrtti.inc" -} // namespace - GPUDisplayFrontend::GPUDisplayFrontend() = default; GPUDisplayFrontend::~GPUDisplayFrontend() = default; @@ -148,20 +137,6 @@ void GPUDisplayFrontend::HandleExternalGUISignals() } } -bool GPUDisplayFrontend::HasExternalGUISignals() const -{ - return mExternalGUIKeys.size(); -} - -bool GPUDisplayFrontend::HandleExternalGUIKeys() -{ - std::lock_guard lock(mExternalGUIKeyMutex); - for (auto key : mExternalGUIKeys) { - mDisplay->HandleExternalGUIKey(key); - } - return !std::exchange(mExternalGUIKeys, {}).empty(); -} - void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) { std::lock_guard lock(mExternalGUIKeyMutex); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx new file mode 100644 index 0000000000000..73fe498a17a64 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -0,0 +1,91 @@ +// 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 "utils/qconfig_rtti.h" +#include "utils/qconfig_internal_helpers.h" + +#include +#include +#include +#include +#include +#include + +using namespace o2::gpu; + +namespace +{ +#include "utils/qconfig_genrtti.inc" +} // namespace + +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; +} + +bool GPUDisplayFrontend::HasExternalGUISignals() const +{ + return mExternalGUIKeys.size(); +} + +bool GPUDisplayFrontend::HandleExternalGUIKeys() +{ + std::lock_guard lock(mExternalGUIKeyMutex); + for (auto key : mExternalGUIKeys) { + mDisplay->HandleExternalGUIKey(key); + } + return !std::exchange(mExternalGUIKeys, {}).empty(); +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index b7c5c55cfdf22..5b2e8599c5838 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -511,51 +511,6 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) } } -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++) { diff --git a/GPU/GPUTracking/utils/qconfig_genrtti.inc b/GPU/GPUTracking/utils/qconfig_genrtti.inc index c0a06920375a1..5d817f5a26b93 100644 --- a/GPU/GPUTracking/utils/qconfig_genrtti.inc +++ b/GPU/GPUTracking/utils/qconfig_genrtti.inc @@ -13,7 +13,7 @@ /// \author David Rohr // Provides static inline qConfigGenRTTI() in the namespace it is included in. -// Requires , , , , , , , "qconfig_rtti.h", "qconfig_internal_helpers.h" to be included before (outside of the namespace), and the config structs to be visible unqualified. +// 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 From 27de674164f47f3e95cbd1ad467b0424118319a5 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 24 Oct 2026 18:20:55 +0200 Subject: [PATCH 05/41] GPU Display Frontend: move key binding constants to separate header --- GPU/GPUTracking/display/CMakeLists.txt | 2 +- .../display/frontend/GPUDisplayFrontend.h | 32 ++----------- .../display/frontend/GPUDisplayFrontendKeys.h | 46 +++++++++++++++++++ 3 files changed, 50 insertions(+), 30 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index a274c481d0365..5f770ddf7b914 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -116,7 +116,7 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx frontend/GPUDisplayKeys.cxx frontend/GPUDisplayFrontendConfig.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 1bfbc36883f41..fb4e8dc73b3aa 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -89,35 +89,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 +#define GPUDisplayFrontendAddKey(code, key) static constexpr int32_t KEY_##key = code +#include "GPUDisplayFrontendKeys.h" +#undef GPUDisplayFrontendAddKey // volatile variables to exchange control informations between display and backend volatile int32_t mDisplayControl = 0; // Control for next event (=1) or quit (=2) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h new file mode 100644 index 0000000000000..a22eaeb5fbcd8 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.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 GPUDisplayFrontendKeys.h +/// \author David Rohr + +// Key codes for special keys (to unify different treatment in X11 / Windows / GLUT / etc.), as GPUDisplayFrontendAddKey(code, name) X-macros. +// No include guard: included multiple times with different definitions of GPUDisplayFrontendAddKey. + +GPUDisplayFrontendAddKey(1, UP); +GPUDisplayFrontendAddKey(2, DOWN); +GPUDisplayFrontendAddKey(3, LEFT); +GPUDisplayFrontendAddKey(4, RIGHT); +GPUDisplayFrontendAddKey(5, PAGEUP); +GPUDisplayFrontendAddKey(6, PAGEDOWN); +GPUDisplayFrontendAddKey(8, SHIFT); +GPUDisplayFrontendAddKey(9, ALT); +GPUDisplayFrontendAddKey(10, RALT); +GPUDisplayFrontendAddKey(11, CTRL); +GPUDisplayFrontendAddKey(12, RCTRL); +GPUDisplayFrontendAddKey(13, ENTER); // fixed at 13 +GPUDisplayFrontendAddKey(14, F1); +GPUDisplayFrontendAddKey(15, F2); +GPUDisplayFrontendAddKey(16, F3); +GPUDisplayFrontendAddKey(17, F4); +GPUDisplayFrontendAddKey(18, F5); +GPUDisplayFrontendAddKey(19, F6); +GPUDisplayFrontendAddKey(20, F7); +GPUDisplayFrontendAddKey(21, F8); +GPUDisplayFrontendAddKey(22, F9); +GPUDisplayFrontendAddKey(23, F10); +GPUDisplayFrontendAddKey(24, F11); +GPUDisplayFrontendAddKey(25, F12); +GPUDisplayFrontendAddKey(26, INSERT); +GPUDisplayFrontendAddKey(27, ESCAPE); // fixed at 27 +GPUDisplayFrontendAddKey(28, HOME); +GPUDisplayFrontendAddKey(29, END); +GPUDisplayFrontendAddKey(32, SPACE); // fixed at 32 From a2ad9a85f1a3221a01d705271c7c8e11f793b705 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 22 Oct 2026 07:23:35 +0200 Subject: [PATCH 06/41] GPU Display: Build config RTTI Structs --- GPU/GPUTracking/display/GPUDisplay.h | 2 + .../display/frontend/GPUDisplayFrontend.cxx | 1 + .../display/frontend/GPUDisplayFrontend.h | 7 ++ .../frontend/GPUDisplayFrontendConfig.cxx | 106 ++++++++++++++++++ GPU/GPUTracking/utils/qconfig.cxx | 1 + GPU/GPUTracking/utils/qconfig_genrtti.inc | 34 ++++-- GPU/GPUTracking/utils/qconfig_internal.h | 19 ++-- 7 files changed, 154 insertions(+), 16 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 67ab1a10334c9..0fb99ada77b7b 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -87,6 +87,8 @@ class GPUDisplay : public GPUDisplayInterface int32_t& drawTextFontSize() { return mDrawTextFontSize; } private: + void buildConfig(); // Key bindings / config RTTI of the frontend, in GPUDisplayFrontendConfig.cxx + static constexpr int32_t NSECTORS = o2::tpc::constants::MAXSECTOR; static constexpr float GL_SCALE_FACTOR = (1.f / 100.f); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 43983b688de8a..aafe316160d82 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -295,6 +295,7 @@ int32_t GPUDisplayFrontend::StartDisplay() return (1); } #endif + mDisplay->buildConfig(); return (0); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index fb4e8dc73b3aa..badf42242e106 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -138,7 +138,14 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 private: + struct configRTTIInfo; + struct configRTTIExt; + struct configKey; + std::unique_ptr mConfigRTTI{nullptr, nullptr}; // Deleter set in GPUDisplayFrontendConfig.cxx, the only place where configRTTIInfo is defined + bool HandleExternalGUIKeys(); // Returns whether keys were handled, config changes are not handled + 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 }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 73fe498a17a64..ae8567d8a6d64 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -18,13 +18,77 @@ #include "utils/qconfig_internal_helpers.h" #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; + configKeyCallback callback; +}; + +#define KEY_LAMBDA(character, obj, value, ...) std::tuple((uint8_t)(character), mFrontend->mConfigRTTI->getRTTIIndex(*this, &std::remove_cvref_t::value), configKeyCallback([this]() { [[maybe_unused]] auto& val = obj.value; __VA_ARGS__ })) + +struct GPUDisplayFrontend::configRTTIInfo { + static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR}; // Config structs with RTTI, as members of GPUDisplay + std::vector rttiEntries; + std::vector> rttiReverseId; // [configStructIndex][offset] -> index in rttiEntries, INVALID_INDEX if no member at that offset + static constexpr uint32_t INVALID_INDEX = std::numeric_limits::max(); + std::vector configStructNames; + std::array configKeys; + std::vector keyNames; + std::vector> helpTextEntries; // Keys and text of each add_key_binding call + std::string helpText; // Built from helpTextEntries by buildHelpText + + 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); + } + 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 += e.first + std::string(width - e.first.size(), ' ') + ": " + e.second + "\n"; + } + } + std::string getKeyName(uint8_t key) const // Name of a special key (KEY_*), the character otherwise, the code if neither + { + return key < keyNames.size() && !keyNames[key].empty() ? keyNames[key] : (key < 32 ? std::to_string(key) : std::string(1, (char)key)); + } +}; namespace { @@ -89,3 +153,45 @@ bool GPUDisplayFrontend::HandleExternalGUIKeys() } return !std::exchange(mExternalGUIKeys, {}).empty(); } + +template +void GPUDisplayFrontend::add_key_binding(Args&&... args) +{ + constexpr size_t nKeys = (0 + ... + 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 = [&](std::tuple& key) { + const std::string name = mConfigRTTI->getKeyName(std::get<0>(key)); + auto& entry = mConfigRTTI->configKeys[std::get<0>(key)]; + if (entry.callback) { + throw std::runtime_error("Key [" + name + "] assigned twice in display frontend"); + } + 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)); +} + +void GPUDisplay::buildConfig() +{ + auto& rtti = mFrontend->mConfigRTTI; + rtti = {new GPUDisplayFrontend::configRTTIInfo, [](GPUDisplayFrontend::configRTTIInfo* p) { delete p; }}; + + rtti->keyNames.resize(rtti->configKeys.size()); +#define GPUDisplayFrontendAddKey(code, key) rtti->keyNames[code] = #key +#include "frontend/GPUDisplayFrontendKeys.h" +#undef GPUDisplayFrontendAddKey + + 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; + } +} diff --git a/GPU/GPUTracking/utils/qconfig.cxx b/GPU/GPUTracking/utils/qconfig.cxx index 6186c8950ca6f..ddfe56ae78cb6 100644 --- a/GPU/GPUTracking/utils/qconfig.cxx +++ b/GPU/GPUTracking/utils/qconfig.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/GPU/GPUTracking/utils/qconfig_genrtti.inc b/GPU/GPUTracking/utils/qconfig_genrtti.inc index 5d817f5a26b93..b309727799f9e 100644 --- a/GPU/GPUTracking/utils/qconfig_genrtti.inc +++ b/GPU/GPUTracking/utils/qconfig_genrtti.inc @@ -12,8 +12,8 @@ /// \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. +// 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 @@ -27,8 +27,17 @@ static inline T qConfigRTTICheckedCast(size_t v, const char* what) return static_cast(v); } -static inline std::optional qConfigRTTIStructIndex(std::vector& configStructs, const char* name, bool filter) // Index of name in configStructs, appended if not filtering, nullopt if filtered out +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(); } @@ -39,8 +48,11 @@ static inline std::optional qConfigRTTIStructIndex(std::vector -static inline void qConfigRTTIEntry(std::vector<::qConfig::qConfigRTTIentry>& retVal, ::qConfig::qConfigRTTIentry entry, size_t arraySize, size_t configStructIndex, std::string defaultValue, const char* help, Args&&... args) // Complete the entry, check the narrow members for overflow +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"); @@ -60,18 +72,24 @@ static inline void qConfigRTTIEntry(std::vector<::qConfig::qConfigRTTIentry>& re retVal.emplace_back(std::move(entry)); } -static inline std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs) // Entries and list of config structs, for the given config structs (all if nullptr) +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 retVal; std::vector retStructs; - if (configStructs) { + 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)}; } diff --git a/GPU/GPUTracking/utils/qconfig_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index cb646efe5d7c5..17ac6b55cb530 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -261,9 +261,9 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // // End QCONFIG_PRINT_RTC #elif defined(QCONFIG_GEN_RTTI) -#define QCONFIG_RTTI(vName, vType, rtc, variable, vector, count, defaultValue, ...) \ - qConfigRTTIEntry(retVal, \ - {.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}, \ +#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__) @@ -274,12 +274,15 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // #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 (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 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() } +#define EndConfig() \ + } \ + } // End QCONFIG_GEN_RTTI #else // Define structures From b0b09395bbcdab98d1dec1e6f5077b704da51647 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 24 Oct 2026 18:56:40 +0200 Subject: [PATCH 07/41] GPU Display: Remove mKeysShifted helper array --- GPU/GPUTracking/display/GPUDisplay.cxx | 6 +- .../display/frontend/GPUDisplayFrontend.cxx | 1 - .../display/frontend/GPUDisplayFrontend.h | 1 - .../frontend/GPUDisplayFrontendConfig.cxx | 14 ++-- .../frontend/GPUDisplayFrontendGlfw.cxx | 4 -- .../frontend/GPUDisplayFrontendGlut.cxx | 4 -- .../frontend/GPUDisplayFrontendWayland.cxx | 4 +- .../frontend/GPUDisplayFrontendWindows.cxx | 2 - .../frontend/GPUDisplayFrontendX11.cxx | 2 - .../display/frontend/GPUDisplayKeys.cxx | 70 +++++++++---------- 10 files changed, 43 insertions(+), 65 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 991b9b4736ef9..af2eb8def6b6e 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -305,7 +305,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla rotPitch += rotatescalefactor * mouseScale * ((float)mFrontend->mMouseMvY - (float)mFrontend->mMouseDnY); } - if (mFrontend->mKeys[(uint8_t)'<'] && !mFrontend->mKeysShift[(uint8_t)'<']) { + if (mFrontend->mKeys[(uint8_t)'<']) { mAnimationDelay += moveX; if (mAnimationDelay < 0.05f) { mAnimationDelay = 0.05f; @@ -395,7 +395,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[(uint8_t)'+'] - mFrontend->mKeys[(uint8_t)'-']) * mFrontend->mKeys[mFrontend->KEY_SHIFT]; mCfgL.lineWidth += (float)deltaLine * mFPSScale * 0.02f * mCfgL.lineWidth; if (mCfgL.lineWidth < minSize) { mCfgL.lineWidth = minSize; @@ -405,7 +405,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[(uint8_t)'+'] - mFrontend->mKeys[(uint8_t)'-']) * !mFrontend->mKeys[mFrontend->KEY_SHIFT]; mCfgL.pointSize += (float)deltaPoint * mFPSScale * 0.02f * mCfgL.pointSize; if (mCfgL.pointSize < minSize) { mCfgL.pointSize = minSize; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index aafe316160d82..2e271bada2b00 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -257,7 +257,6 @@ void GPUDisplayFrontend::setWindowState(bool fullScreen, bool maximized) 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; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index badf42242e106..67af4ec11e92a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -108,7 +108,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface float mMouseMvX, mMouseMvY; // Current mouse pointer position int32_t mMouseWheel = 0; // Incremental value of mouse wheel, ca +/- 100 per wheel tick bool mKeys[256] = {false}; // Array of mKeys currently pressed - bool mKeysShift[256] = {false}; // Array whether shift was held during key-press int32_t mDisplayHeight = INIT_HEIGHT; int32_t mDisplayWidth = INIT_WIDTH; int32_t mCanDrawText = 0; // 1 = in compat mode, 2 = with shader diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index ae8567d8a6d64..9d6f861e669ae 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -14,6 +14,7 @@ #include "GPUDisplayFrontend.h" #include "GPUDisplay.h" +#include "GPULogging.h" #include "utils/qconfig_rtti.h" #include "utils/qconfig_internal_helpers.h" @@ -123,6 +124,9 @@ void GPUDisplay::checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCf if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { mUpdateEventData = true; } + if (oldCfgL.animationMode != mCfgL.animationMode) { + resetAnimation(); + } } void GPUDisplay::HandleExternalGUIKey(int32_t key) @@ -130,14 +134,10 @@ 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; + bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; + mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; HandleKey(key, true); - mFrontend->mKeysShift[press] = oldShift; + mFrontend->mKeys[mFrontend->KEY_SHIFT] = oldShift; } bool GPUDisplayFrontend::HasExternalGUISignals() const diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index ac61ddacdd0f6..1e0e9bafe0d7d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -163,11 +163,9 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 if (handleKey < 32) { if (action == GLFW_PRESS) { me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = mods & GLFW_MOD_SHIFT; me->HandleKey(handleKey); } else if (action == GLFW_RELEASE) { me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; } } else if (handleKey < 256) { if (action == GLFW_PRESS) { @@ -175,7 +173,6 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 } else if (action == GLFW_RELEASE) { keyPress = (uint8_t)me->mKeyDownMap[handleKey]; me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; } } } @@ -190,7 +187,6 @@ void GPUDisplayFrontendGlfw::char_callback(GLFWwindow* window, uint32_t codepoin } me->mKeyDownMap[me->mLastKeyDown] = keyPress; me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; me->HandleKey(codepoint); } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 368a86413194c..1abf2c578d030 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -149,7 +149,6 @@ void GPUDisplayFrontendGlut::keyboardDownFunc(uint8_t key, int32_t x, int32_t y) { 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); } @@ -159,14 +158,12 @@ 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); } @@ -176,7 +173,6 @@ 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; } void GPUDisplayFrontendGlut::ResizeSceneWrapper(int32_t width, int32_t height) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 66701557a8068..f8c1190e978a2 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -95,7 +95,7 @@ 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_F1) { + } else if (sym == XKB_KEY_F1) { // TODO: Fix F* keys not working return (KEY_F1); } else if (sym == XKB_KEY_F2) { return (KEY_F2); @@ -259,7 +259,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() int32_t keyPress = (symbol >= 'a' && symbol <= 'z') ? symbol + 'A' - 'a' : symbol; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; me->HandleKey(symbol); if (symbol && me->mRepeatRate > 0 && me->mXKBkeymap && xkb_keymap_key_repeats(me->mXKBkeymap, key + 8)) { // Wayland compositors do not repeat keys, the client must do it me->mRepeatKeyCode = key; @@ -268,7 +267,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } } else { me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; if ((int64_t)key == me->mRepeatKeyCode) { me->mRepeatKeyCode = -1; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 80537c15c4700..9a641b5c6ef05 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -260,13 +260,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_KEYDOWN: // Is A Key Being Held Down? wParam = GetKey(wParam); mKeys[wParam] = TRUE; - mKeysShift[wParam] = mKeys[KEY_SHIFT]; HandleKey(wParam); return 0; case WM_KEYUP: wParam = GetKey(wParam); - mKeysShift[wParam] = false; mKeys[wParam] = false; GPUInfo("Key: %d", wParam); return 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index a06f75d094cda..23de92432cfd1 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -399,7 +399,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() case KeyPress: { int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); - mKeysShift[keyPress] = mKeys[KEY_SHIFT]; mKeys[keyPress] = true; HandleKey(handleKey); break; @@ -409,7 +408,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); mKeys[keyPress] = false; - mKeysShift[keyPress] = false; break; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index 5b2e8599c5838..a498553a9e7de 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -85,17 +85,17 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) if (key == 'n') { mFrontend->mDisplayControl = 1; SetInfo("Showing next event", 1); - } else if (key == 27 || key == mFrontend->KEY_ESCAPE) { + } else if (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]) { + } else if (key == mFrontend->KEY_ALT && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { 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]) { + } else if (key == mFrontend->KEY_CTRL && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { mCfgR.camYUp ^= 1; mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * mCfgR.camYUp; SetInfo("Camera locked on y-axis facing upwards: %s", mCfgR.camYUp ? "enabled" : "disabled"); @@ -330,39 +330,31 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) 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]) { + } else if (key == mFrontend->KEY_F1 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + 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 if (key == mFrontend->KEY_F1 && !mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + mCfgL.drawTPC ^= 1; + SetInfo("Showing TPC Clusters: %d", (int32_t)mCfgL.drawTPC); + } else if (key == mFrontend->KEY_F2 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + 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 if (key == mFrontend->KEY_F2 && !mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + mCfgL.drawTRD ^= 1; + SetInfo("Showing TRD Tracklets: %d", (int32_t)mCfgL.drawTRD); + } else if (key == mFrontend->KEY_F3 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + 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 if (key == mFrontend->KEY_F3 && !mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + mCfgL.drawTOF ^= 1; + SetInfo("Showing TOF Hits: %d", (int32_t)mCfgL.drawTOF); + } else if (key == mFrontend->KEY_F4 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + 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 if (key == mFrontend->KEY_F4 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + mCfgL.drawITS ^= 1; + SetInfo("Showing ITS Clusters: %d", (int32_t)mCfgL.drawITS); + } else if (key == mFrontend->KEY_F12 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { mCfgH.drawTracksAndFilter ^= 1; SetInfo("Track filter: %s", mCfgH.drawTracksAndFilter ? "AND" : "OR"); } else if (key == 't') { @@ -378,7 +370,10 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) mCfgR.screenshotScaleFactor = 1; } SetInfo("Screenshot scaling factor set to %d", mCfgR.screenshotScaleFactor); - } else if (key == 'y' || key == 'T') { + } else if (key == 'T') { + mAnimateScreenshot ^= 1; + SetInfo("Writing screenshots from animation set to %d", (int32_t)mAnimateScreenshot); + } else if (key == 'y') { if (mAnimateVectors[0].size() > 1) { if ((mAnimateScreenshot = (key == 'T'))) { mAnimationExport++; @@ -405,7 +400,6 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) if (mCfgL.animationMode == 7) { mCfgL.animationMode = 0; } - resetAnimation(); if (mCfgL.animationMode == 6) { SetInfo("Animation mode %d - Centered on origin", mCfgL.animationMode); } else { From 82fd9b57b5b60fe2fd970abd1978f51b5ba45286 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 25 Oct 2026 17:34:34 +0100 Subject: [PATCH 08/41] GPU Display: Assemble all variables controlling the display in a control struct --- GPU/GPUTracking/display/GPUDisplay.cxx | 35 +++++++++---------- GPU/GPUTracking/display/GPUDisplay.h | 18 ++++++---- .../display/frontend/GPUDisplayFrontend.cxx | 6 ++-- .../display/frontend/GPUDisplayFrontend.h | 7 ++-- .../frontend/GPUDisplayFrontendConfig.cxx | 11 ++++-- .../frontend/GPUDisplayFrontendGlfw.cxx | 2 +- .../frontend/GPUDisplayFrontendGlut.cxx | 2 +- .../frontend/GPUDisplayFrontendNone.cxx | 2 +- .../frontend/GPUDisplayFrontendWayland.cxx | 6 ++-- .../frontend/GPUDisplayFrontendX11.cxx | 6 ++-- .../display/frontend/GPUDisplayKeys.cxx | 31 ++++++++-------- .../display/helpers/GPUDisplayAnimation.cxx | 6 ++-- .../display/helpers/GPUDisplayHelpers.cxx | 2 +- 13 files changed, 72 insertions(+), 62 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index af2eb8def6b6e..e2102660e6a29 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -148,7 +148,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; } @@ -268,7 +268,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla if (animateCamera(animateTime, mixSlaveImage, nextViewMatrix)) { // Do nothing else - } else if (mResetScene) { + } else if (mCfgC.mResetScene.load()) { const float initialZpos = mCfgH.projectXY ? 16 : (mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR + 8) : 8); nextViewMatrix = nextViewMatrix * HMM_Translate({{0, 0, -initialZpos}}); *mViewMatrix = MY_HMM_IDENTITY; @@ -284,7 +284,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mCfgR.fov = 45.f; mUpdateDrawCommands = true; - mResetScene = false; + mCfgC.mResetScene.store(false); } else { float moveZ = scalefactor * ((float)mMouseWheelTmp / 150 + (float)(mFrontend->mKeys[(uint8_t)'W'] - mFrontend->mKeys[(uint8_t)'S']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); float moveY = scalefactor * ((float)(mFrontend->mKeys[mFrontend->KEY_PAGEDOWN] - mFrontend->mKeys[mFrontend->KEY_PAGEUP]) * 0.2f * mFPSScale); @@ -629,18 +629,18 @@ void GPUDisplay::DrawGLScene_drawCommands() void GPUDisplay::DrawGLScene_internal(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; + bool doScreenshot = (mCfgC.mRequestScreenshot || mCfgC.mAnimateScreenshot) && 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 +648,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(); @@ -675,7 +675,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) const float zFar = ((mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR) : 8.f) + 50.f) * 2.f; 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, doScreenshot || mRequestScreenshot, renderToMixBuffer, mixSlaveImage)) { + if (!mBackend->prepareDraw(proj, nextViewMatrix, doScreenshot || mCfgC.mRequestScreenshot, renderToMixBuffer, mixSlaveImage)) { return; // No image available (e.g. hidden window), retry with the next frame } mBackend->pointSizeFactor(1); @@ -706,7 +706,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) fps, mCfgL.drawSector, mCfgL.drawClusters, mCfgL.drawInitLinks, mCfgL.drawLinks, mCfgL.drawSeeds, mCfgL.drawTracklets, mCfgL.drawTracks, mCfgL.drawExtrapolatedTracks, mCfgL.drawFinal, mFramesDone, mNDrawCalls, mXYZ[0], mXYZ[1], mXYZ[2], mRPhiTheta[0], mRPhiTheta[1] * 180 / CAMath::Pi(), mRPhiTheta[2] * 180 / CAMath::Pi(), mAngle[1] * 180 / CAMath::Pi(), mAngle[0] * 180 / CAMath::Pi(), mAngle[2] * 180 / CAMath::Pi()); if (fpstime > 1.) { - if (mPrintInfoText & 2) { + if (mCfgC.mPrintInfoText & 2) { GPUInfo("%s", info); } if (mFPSScaleadjust++) { @@ -716,20 +716,20 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) mFramesDoneFPS = 0; } - if (mPrintInfoText & 1) { + if (mCfgC.mPrintInfoText & 1) { showInfo(info); } } - mBackend->finishFrame(mRequestScreenshot, renderToMixBuffer, mixSlaveImage); + mBackend->finishFrame(mCfgC.mRequestScreenshot, renderToMixBuffer, mixSlaveImage); if (doScreenshot) { - mRequestScreenshot = false; + mCfgC.mRequestScreenshot = false; std::vector pixels = mBackend->getPixels(); char tmpFileName[48]; - if (mAnimateScreenshot) { + if (mCfgC.mAnimateScreenshot) { snprintf(tmpFileName, 48, "mAnimation%d_%05d.bmp", mAnimationExport, mAnimationFrame); } - DoScreenshot(mAnimateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); + DoScreenshot(mCfgC.mAnimateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); } } @@ -739,9 +739,8 @@ void GPUDisplay::ShowNextEvent(const GPUTrackingInOutPointers* ptrs) mIOPtrs = ptrs; } if (mMaxClusterZ <= 0) { - mResetScene = true; + mCfgC.mResetScene.store(true); } - mFrontend->mNeedUpdate = 1; mUpdateEventData = true; mLoadAndShowEvent = true; mSemLockDisplay.Unlock(); diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 0fb99ada77b7b..fb78a1219b51c 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -21,6 +21,7 @@ #include "GPUSettings.h" #include "DataFormatsTPC/Constants.h" +#include #include #include #include @@ -86,6 +87,17 @@ class GPUDisplay : public GPUDisplayInterface bool drawTextInCompatMode() const { return mDrawTextInCompatMode; } int32_t& drawTextFontSize() { return mDrawTextFontSize; } + struct control { + std::atomic mDisplayControl = 0; // Control for next event (=1) or quit (=2) + std::atomic mResetScene = false; + int32_t mPrintInfoText = 1; + bool mPrintInfoTextAlways = false; + bool mRequestScreenshot = false; + int32_t mAnimateScreenshot = 0; + bool mAnimationChangeConfig = true; + bool dummy = false; // dummy value to reference for keys that do not change variables + } mCfgC; + private: void buildConfig(); // Key bindings / config RTTI of the frontend, in GPUDisplayFrontendConfig.cxx @@ -292,7 +304,6 @@ class GPUDisplay : public GPUDisplayInterface volatile bool mUpdateEventData = false; volatile bool mUpdateDrawCommands = true; volatile bool mUpdateRenderPipeline = false; - volatile bool mResetScene = false; volatile bool mLoadAndShowEvent = false; bool mTracksArePropagated = false; @@ -301,16 +312,12 @@ class GPUDisplay : public GPUDisplayInterface int32_t mAnimationFrame = 0; int32_t mAnimationLastBase = 0; int32_t mAnimationMixBase = -1; // Target keyframe of the current config blend (mixing), -1 = none - int32_t mAnimateScreenshot = 0; int32_t mAnimationExport = 0; - bool mAnimationChangeConfig = true; float mAnimationDelay = 2.f; vecpod mAnimateVectors[9]; vecpod mAnimateConfig; opengl_spline mAnimationSplines[8]; - int32_t mPrintInfoText = 1; - bool mPrintInfoTextAlways = 0; char mInfoText2[1024]; HighResTimer mInfoText2Timer, mInfoHelpTimer; @@ -328,7 +335,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/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 2e271bada2b00..7cd9878fc43c7 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -81,7 +81,6 @@ void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) void GPUDisplayFrontend::waitForFPSLimit() { - mNeedUpdate = 0; const auto now = std::chrono::steady_clock::now(); const auto& cfg = mDisplay->cfgR(); if (mBackend->fpsLimitMode() != GPUDisplayBackend::FPS_LIMIT_RATE || cfg.fpsRate <= 0) { @@ -143,6 +142,9 @@ void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) mExternalGUIKeys.emplace_back(v); } +int32_t GPUDisplayFrontend::getDisplayControl() const { return mDisplay->mCfgC.mDisplayControl.load(); } +void GPUDisplayFrontend::setDisplayControl(int32_t v) { mDisplay->mCfgC.mDisplayControl.store(v); } + void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } int32_t GPUDisplayFrontend::DrawGLScene() { return mDisplay->DrawGLScene(); } @@ -303,7 +305,7 @@ void GPUDisplayFrontend::StopDisplay() { std::lock_guard lock(mExitStateMutex); if (mDisplayRunning) { - mDisplayControl = 2; + mDisplay->mCfgC.mDisplayControl.store(2); StopDisplayA(); mDisplay->UnblockDisplay(); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 67af4ec11e92a..6345a1aaf47b5 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -68,8 +68,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual int32_t getVulkanSurface(void* instance, void* surface) { return 1; } virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; }; - int32_t getDisplayControl() const override { return mDisplayControl; } - void setDisplayControl(int32_t v) override { mDisplayControl = v; } + int32_t getDisplayControl() const override; + void setDisplayControl(int32_t v) override; void sendExternalGUIKey(int32_t v) override; frontendTypes frontendType() const { return mFrontendType; } @@ -93,9 +93,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface #include "GPUDisplayFrontendKeys.h" #undef GPUDisplayFrontendAddKey - // 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 std::mutex mExternalGUIKeyMutex; std::vector mExternalGUIKeys; // Keys sent by external entity (usually console), may be ignored by backend. std::mutex mExitStateMutex; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 9d6f861e669ae..c8db5c2b42b90 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -71,8 +71,12 @@ struct GPUDisplayFrontend::configRTTIInfo { 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]; + if constexpr (std::is_same_v) { + return INVALID_INDEX; + } else { + const T& obj = configStruct(display); + return rttiReverseId[configStructIndex()][(const char*)&(obj.*member) - (const char*)&obj]; + } } void buildHelpText() // Keys left-aligned, the ':' aligned after the longest keys { @@ -127,6 +131,9 @@ void GPUDisplay::checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCf if (oldCfgL.animationMode != mCfgL.animationMode) { resetAnimation(); } + if (oldCfgH.trackFilter != mCfgH.trackFilter) { + mUpdateTrackFilter = true; + } } void GPUDisplay::HandleExternalGUIKey(int32_t key) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 1e0e9bafe0d7d..176d2b916040e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -344,7 +344,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() ExitDisplay(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); glfwDestroyWindow(mWindow); glfwTerminate(); mDisplayRunning = false; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 1abf2c578d030..90391fff43bf0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -273,7 +273,7 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() glutMainLoop(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); mDisplayRunning = false; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index 5d5748e13349a..e0b6f96933612 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -31,6 +31,6 @@ int32_t GPUDisplayFrontendNone::FrontendMain() HandleExternalGUISignals(); DrawGLScene(); waitForFPSLimit(); - } while (mDisplayControl != 2); + } while (getDisplayControl() != 2); return 0; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index f8c1190e978a2..efee3ca538973 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -509,7 +509,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) { @@ -583,7 +583,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() mDisplayRunning = true; } - while (mDisplayControl != 2) { + while (mDisplay->mCfgC.mDisplayControl.load() != 2) { bool error = false; while (!error && wl_display_prepare_read(mWayland) != 0) { error = wl_display_dispatch_pending(mWayland) == -1; @@ -606,7 +606,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() reportError("dispatching events"); break; } - if (mDisplayControl == 2) { + if (mDisplay->mCfgC.mDisplayControl.load() == 2) { break; } handleKeyRepeat(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 23de92432cfd1..50cdd13fd9d19 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -355,7 +355,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() while (1) { do { - if (mDisplayControl == 2) { + if (getDisplayControl() == 2) { break; } if (!XPending(mXDisplay)) { @@ -442,13 +442,13 @@ 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; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index a498553a9e7de..70a3083d23906 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -83,13 +83,13 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) const GPUSettingsDisplayLight oldCfgL = mCfgL; const GPUSettingsDisplayRenderer oldCfgR = mCfgR; if (key == 'n') { - mFrontend->mDisplayControl = 1; + mCfgC.mDisplayControl.store(1); SetInfo("Showing next event", 1); } else if (key == mFrontend->KEY_ESCAPE) { - mFrontend->mDisplayControl = 2; + mCfgC.mDisplayControl.store(2); SetInfo("Exiting", 1); } else if (key == 'r') { - mResetScene = true; + mCfgC.mResetScene.store(true); SetInfo("View reset", 1); } else if (key == mFrontend->KEY_ALT && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { mCfgR.camLookOrigin ^= 1; @@ -178,9 +178,9 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) 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"); + mCfgC.mPrintInfoText += 1; + mCfgC.mPrintInfoText &= 3; + SetInfo("Info text display - console: %s, onscreen %s", (mCfgC.mPrintInfoText & 2) ? "enabled" : "disabled", (mCfgC.mPrintInfoText & 1) ? "enabled" : "disabled"); } else if (key == 'j') { if (mCfgH.separateExtrapolatedTracks) { mCfgH.splitCETracks ^= 1; @@ -361,7 +361,7 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) static int32_t nScreenshot = 1; char fname[32]; snprintf(fname, 32, "screenshot%d.bmp", nScreenshot++); - mRequestScreenshot = true; + mCfgC.mRequestScreenshot = true; mScreenshotFile = fname; SetInfo("Taking screenshot (%s)", fname); } else if (key == 'Z') { @@ -371,11 +371,11 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) } SetInfo("Screenshot scaling factor set to %d", mCfgR.screenshotScaleFactor); } else if (key == 'T') { - mAnimateScreenshot ^= 1; - SetInfo("Writing screenshots from animation set to %d", (int32_t)mAnimateScreenshot); + mCfgC.mAnimateScreenshot ^= 1; + SetInfo("Writing screenshots from animation set to %d", mCfgC.mAnimateScreenshot); } else if (key == 'y') { if (mAnimateVectors[0].size() > 1) { - if ((mAnimateScreenshot = (key == 'T'))) { + if (mCfgC.mAnimateScreenshot) { mAnimationExport++; } startAnimation(); @@ -384,8 +384,8 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) SetInfo("Insufficient Animation points to start Animation", 1); } } else if (key == '>') { - mAnimationChangeConfig ^= 1; - SetInfo("Interpolating visualization settings during Animation %s", mAnimationChangeConfig ? "enabled" : "disabled"); + mCfgC.mAnimationChangeConfig ^= 1; + SetInfo("Interpolating visualization settings during Animation %s", mCfgC.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()); @@ -407,7 +407,6 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) } } 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"); @@ -472,11 +471,11 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) } SetInfo("Animation path loaded from file %s", "glanimation.tmp"); } else if (key == 'h') { - if (mPrintInfoTextAlways) { - mPrintInfoTextAlways = false; + if (mCfgC.mPrintInfoTextAlways) { + mCfgC.mPrintInfoTextAlways = false; SetInfo("Showing help text disabled", 1); } else if (mInfoHelpTimer.IsRunning()) { - mPrintInfoTextAlways = true; + mCfgC.mPrintInfoTextAlways = true; mInfoHelpTimer.Reset(); SetInfo("Showing help text until disabled", 1); } else { diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index bb2bacfc08434..7ef55de0113e4 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -109,7 +109,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ { float time = animateTime; if (mAnimate && time < 0) { - if (mAnimateScreenshot) { + if (mCfgC.mAnimateScreenshot) { time = mAnimationFrame / 30.f; } else { time = mAnimationTimer.GetCurrentElapsedTime(); @@ -118,7 +118,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ float maxTime = mAnimateVectors[0].back(); if (time >= maxTime) { time = maxTime; - mAnimate = mAnimateScreenshot = 0; + mAnimate = mCfgC.mAnimateScreenshot = 0; SetInfo("Animation finished. (%1.2f seconds, %d frames)", time, mAnimationFrame); } else { SetInfo("Running mAnimation: time %1.2f/%1.2f, frames %d", time, maxTime, mAnimationFrame); @@ -132,7 +132,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ for (int32_t i = 0; i < 8; i++) { vals[i] = mAnimationSplines[i].evaluate(time); } - if (mAnimationChangeConfig && animateTime < 0) { + if (mCfgC.mAnimationChangeConfig && animateTime < 0) { int32_t base = 0; int32_t k = mAnimateVectors[0].size() - 1; while (base < k && time > mAnimateVectors[0][base]) { diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index d0a6685efc742..99a14a8319c24 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -110,7 +110,7 @@ void GPUDisplay::showInfo(const char* info) OpenGLPrint(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 { From 00ce83c476654b6c9b9129f449039c9a004e7bdf Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 24 Oct 2026 18:23:28 +0200 Subject: [PATCH 09/41] GPU Display: Add option to list configuration bindings for keyboard and GUI --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 1 + .../display/frontend/GPUDisplayFrontend.cxx | 3 ++ .../display/frontend/GPUDisplayFrontend.h | 1 + .../frontend/GPUDisplayFrontendConfig.cxx | 53 ++++++++++++++++++- 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index fa560ec535313..553f0304abe24 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -534,6 +534,7 @@ AddOption(displayFrontend, std::string, "auto", "displayFrontend", 0, "Frontend AddOptionVec(filterMacros, std::string, "", 0, "ROOT macros used as track filter") AddOption(guiReplyTimeout, uint32_t, 100, "", 0, "Time in ms the Qt GUI waits at most for the display to process its update") AddOption(guiMaxUpdatesAhead, uint32_t, 3, "", 0, "Number of updates the Qt GUI may send ahead of the last one the display has processed, before it waits for the display") +AddOption(listFrontendOptionHandling, bool, false, "", 0, "List all assigned keys in frontend, Configurable settings in Qt GUI, and missing configuration options") AddSubConfig(GPUSettingsDisplayLight, light) AddSubConfig(GPUSettingsDisplayHeavy, heavy) AddSubConfig(GPUSettingsDisplayRenderer, renderer) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 7cd9878fc43c7..af36e54479f04 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -297,6 +297,9 @@ int32_t GPUDisplayFrontend::StartDisplay() } #endif mDisplay->buildConfig(); + if (mDisplay->cfg().listFrontendOptionHandling) { + listConfig(); + } return (0); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 6345a1aaf47b5..5bafc1c289a2b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -142,6 +142,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface bool HandleExternalGUIKeys(); // Returns whether keys were handled, config changes are not handled 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 + void listConfig(); }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index c8db5c2b42b90..8e44d761add7b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -52,7 +53,10 @@ struct GPUDisplayFrontend::configRTTIInfo { std::array configKeys; std::vector keyNames; std::vector> helpTextEntries; // Keys and text of each add_key_binding call - std::string helpText; // Built from helpTextEntries by buildHelpText + std::string helpText; + static constexpr std::string_view possibleKeys = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#.:;>'^_"; // Bindable keys >= 32 (space), special keys < 32 are those with a name in keyNames + static constexpr char reservedKeysData[] = {'W', 'A', 'S', 'D', 'E', 'F', 'w', 'a', 's', 'd', 'e', 'f', '+', '-', '<', KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_PAGEUP, KEY_PAGEDOWN, KEY_SHIFT, KEY_ALT, KEY_RALT, KEY_CTRL, KEY_RCTRL}; + static constexpr std::string_view reservedKeys{reservedKeysData, sizeof(reservedKeysData)}; // Keys queried continuously as held keys (camera control, modifiers), not available for bindings template static constexpr uint32_t configStructIndex() // Index of struct T in configStructs, compile-time error if not present @@ -185,6 +189,53 @@ void GPUDisplayFrontend::add_key_binding(Args&&... args) mConfigRTTI->helpTextEntries.emplace_back(std::move(keys), std::get(argTuple)); } +void GPUDisplayFrontend::listConfig() +{ + const auto& rtti = *mConfigRTTI; + auto keyOrder = [](uint8_t k) { return std::pair(k >= '0' && k <= '9' ? 0 : k >= 'a' && k <= 'z' ? 1 + : k >= 'A' && k <= 'Z' ? 2 + : k > 32 ? 3 + : 4, + k); }; // 0-9, a-z, A-Z, other characters, special keys + std::vector boundKeys, availableKeys; + for (uint32_t k = 1; k < rtti.configKeys.size(); k++) { + const bool possible = (k < 32 && !rtti.keyNames[k].empty()) || rtti.possibleKeys.find((char)k) != std::string_view::npos; + if (rtti.configKeys[k].callback) { + boundKeys.emplace_back(k); + } else if (possible && rtti.reservedKeys.find((char)k) == std::string_view::npos) { + availableKeys.emplace_back(k); + } + } + std::sort(boundKeys.begin(), boundKeys.end(), [&](uint8_t a, uint8_t b) { return keyOrder(a) < keyOrder(b); }); + std::sort(availableKeys.begin(), availableKeys.end(), [&](uint8_t a, uint8_t b) { return keyOrder(a) < keyOrder(b); }); + + GPUInfo("Display config options with RTTI (struct, option, type, keys):"); + uint32_t nOptionsWithKey = 0; + for (uint32_t i = 0; i < rtti.rttiEntries.size(); i++) { + const auto& e = rtti.rttiEntries[i]; + std::string keys; + for (uint8_t k : boundKeys) { + if (rtti.configKeys[k].configRTTIIndex == i) { + keys += (keys.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + } + } + nOptionsWithKey += !keys.empty(); + GPUInfo(" %-28s %-32s %-12s %s", e.configStruct.c_str(), e.name.c_str(), e.type.c_str(), keys.c_str()); + } + GPUInfo("Bound keys (key, struct, option):"); + for (uint8_t k : boundKeys) { + const uint32_t index = rtti.configKeys[k].configRTTIIndex; + const bool hasRTTI = index != configRTTIInfo::INVALID_INDEX; + GPUInfo(" %-10s %-28s %s", ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); + } + std::string available; + for (uint8_t k : availableKeys) { + available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + } + GPUInfo("Available keys: %s", available.c_str()); + GPUInfo("%zu options with RTTI, %u of them with a key, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, boundKeys.size(), availableKeys.size()); +} + void GPUDisplay::buildConfig() { auto& rtti = mFrontend->mConfigRTTI; From 824f353c42eccf77843a917fd036e1b489e8ca11 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 24 Oct 2026 18:24:03 +0200 Subject: [PATCH 10/41] GPU Display Frontend: move key handling to RTTI callbacks instead of long if/else if --- GPU/GPUTracking/display/GPUDisplay.h | 3 +- .../display/frontend/GPUDisplayFrontend.h | 4 + .../frontend/GPUDisplayFrontendConfig.cxx | 65 ++- .../frontend/GPUDisplayFrontendConfigKeys.inc | 414 +++++++++++++++++ .../display/frontend/GPUDisplayKeys.cxx | 425 +----------------- 5 files changed, 468 insertions(+), 443 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index fb78a1219b51c..8a48d5a3fbf3b 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -99,7 +99,8 @@ class GPUDisplay : public GPUDisplayInterface } mCfgC; private: - void buildConfig(); // Key bindings / config RTTI of the frontend, in GPUDisplayFrontendConfig.cxx + void buildConfig(); + void buildConfigKeys(); static constexpr int32_t NSECTORS = o2::tpc::constants::MAXSECTOR; static constexpr float GL_SCALE_FACTOR = (1.f / 100.f); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 5bafc1c289a2b..54a5c3e332f64 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -92,6 +92,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface #define GPUDisplayFrontendAddKey(code, key) static constexpr int32_t KEY_##key = code #include "GPUDisplayFrontendKeys.h" #undef GPUDisplayFrontendAddKey + 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. @@ -125,6 +128,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface int32_t DrawGLScene(); // Callback to draw the GL scene void HandleExternalGUISignals(); // Callback to handle signals from external GUIs bool HasExternalGUISignals() const; // Returns whether there are currently pending signals from external GUIs to be handled + bool processKeyLambda(uint32_t key); // Process key lambda callback void 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 8e44d761add7b..8ba89e6d5bbfe 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -14,6 +14,7 @@ #include "GPUDisplayFrontend.h" #include "GPUDisplay.h" +#include "frontend/GPUDisplayInfo.inc" #include "GPULogging.h" #include "utils/qconfig_rtti.h" #include "utils/qconfig_internal_helpers.h" @@ -42,7 +43,11 @@ struct GPUDisplayFrontend::configKey { configKeyCallback callback; }; -#define KEY_LAMBDA(character, obj, value, ...) std::tuple((uint8_t)(character), mFrontend->mConfigRTTI->getRTTIIndex(*this, &std::remove_cvref_t::value), configKeyCallback([this]() { [[maybe_unused]] auto& val = obj.value; __VA_ARGS__ })) +#ifdef GPUCA_DISPLAY_OPENGL_CORE +static constexpr bool displayOpenGLCore = true; // OpenGL compat profile not available +#else +static constexpr bool displayOpenGLCore = false; +#endif struct GPUDisplayFrontend::configRTTIInfo { static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR}; // Config structs with RTTI, as members of GPUDisplay @@ -50,9 +55,9 @@ struct GPUDisplayFrontend::configRTTIInfo { 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(); std::vector configStructNames; - std::array configKeys; + std::array configKeys; // By key code, special keys with SHIFT at code + SPECIAL_KEYS_SHIFTED std::vector keyNames; - std::vector> helpTextEntries; // Keys and text of each add_key_binding call + std::vector> helpTextEntries; // Keys and text of each add_key_binding call std::string helpText; static constexpr std::string_view possibleKeys = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#.:;>'^_"; // Bindable keys >= 32 (space), special keys < 32 are those with a name in keyNames static constexpr char reservedKeysData[] = {'W', 'A', 'S', 'D', 'E', 'F', 'w', 'a', 's', 'd', 'e', 'f', '+', '-', '<', KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_PAGEUP, KEY_PAGEDOWN, KEY_SHIFT, KEY_ALT, KEY_RALT, KEY_CTRL, KEY_RCTRL}; @@ -93,8 +98,11 @@ struct GPUDisplayFrontend::configRTTIInfo { helpText += e.first + std::string(width - e.first.size(), ' ') + ": " + e.second + "\n"; } } - std::string getKeyName(uint8_t key) const // Name of a special key (KEY_*), the character otherwise, the code if neither + 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)); } }; @@ -168,11 +176,11 @@ bool GPUDisplayFrontend::HandleExternalGUIKeys() template void GPUDisplayFrontend::add_key_binding(Args&&... args) { - constexpr size_t nKeys = (0 + ... + std::is_same_v, std::tuple>); + constexpr size_t nKeys = (0 + ... + 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 = [&](std::tuple& key) { + auto addKey = [&](std::tuple& key) { const std::string name = mConfigRTTI->getKeyName(std::get<0>(key)); auto& entry = mConfigRTTI->configKeys[std::get<0>(key)]; if (entry.callback) { @@ -192,29 +200,31 @@ void GPUDisplayFrontend::add_key_binding(Args&&... args) void GPUDisplayFrontend::listConfig() { const auto& rtti = *mConfigRTTI; - auto keyOrder = [](uint8_t k) { return std::pair(k >= '0' && k <= '9' ? 0 : k >= 'a' && k <= 'z' ? 1 - : k >= 'A' && k <= 'Z' ? 2 - : k > 32 ? 3 - : 4, - k); }; // 0-9, a-z, A-Z, other characters, special keys - std::vector boundKeys, availableKeys; + 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 bool possible = (k < 32 && !rtti.keyNames[k].empty()) || rtti.possibleKeys.find((char)k) != std::string_view::npos; + const uint32_t base = k >= 256 ? k - SPECIAL_KEYS_SHIFTED : k; // A shifted special key is possible if the special key is + const bool possible = (base < N_SPECIAL_KEYS && !rtti.keyNames[base].empty()) || (k < 256 && rtti.possibleKeys.find((char)k) != std::string_view::npos); if (rtti.configKeys[k].callback) { boundKeys.emplace_back(k); - } else if (possible && rtti.reservedKeys.find((char)k) == std::string_view::npos) { + } else if (possible && rtti.reservedKeys.find((char)base) == std::string_view::npos) { availableKeys.emplace_back(k); } } - std::sort(boundKeys.begin(), boundKeys.end(), [&](uint8_t a, uint8_t b) { return keyOrder(a) < keyOrder(b); }); - std::sort(availableKeys.begin(), availableKeys.end(), [&](uint8_t a, uint8_t b) { return keyOrder(a) < keyOrder(b); }); + 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, keys):"); uint32_t nOptionsWithKey = 0; for (uint32_t i = 0; i < rtti.rttiEntries.size(); i++) { const auto& e = rtti.rttiEntries[i]; std::string keys; - for (uint8_t k : boundKeys) { + for (uint16_t k : boundKeys) { if (rtti.configKeys[k].configRTTIIndex == i) { keys += (keys.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; } @@ -223,19 +233,21 @@ void GPUDisplayFrontend::listConfig() GPUInfo(" %-28s %-32s %-12s %s", e.configStruct.c_str(), e.name.c_str(), e.type.c_str(), keys.c_str()); } GPUInfo("Bound keys (key, struct, option):"); - for (uint8_t k : boundKeys) { + for (uint16_t k : boundKeys) { const uint32_t index = rtti.configKeys[k].configRTTIIndex; const bool hasRTTI = index != configRTTIInfo::INVALID_INDEX; GPUInfo(" %-10s %-28s %s", ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); } std::string available; - for (uint8_t k : availableKeys) { + for (uint16_t k : availableKeys) { available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; } GPUInfo("Available keys: %s", available.c_str()); GPUInfo("%zu options with RTTI, %u of them with a key, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, boundKeys.size(), availableKeys.size()); } +#include "GPUDisplayFrontendConfigKeys.inc" + void GPUDisplay::buildConfig() { auto& rtti = mFrontend->mConfigRTTI; @@ -252,4 +264,19 @@ void GPUDisplay::buildConfig() const auto& entry = rtti->rttiEntries[i]; rtti->rttiReverseId[entry.configStructIndex][entry.offset] = i; } + + buildConfigKeys(); + 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(); + return true; + } + return false; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc new file mode 100644 index 0000000000000..6417d700b6470 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -0,0 +1,414 @@ +// 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_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->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; SetInfo("Color coding for seed / trrack attachmend %s", mCfgL.colorClusters ? "enabled" : "disabled");), // + "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_ESCAPE, mCfgC, mDisplayControl, val.store(2); SetInfo("Exiting", 1);), + KEY_LAMBDA('n', mCfgC, mDisplayControl, val.store(1);), + KEY_LAMBDA('r', mCfgC, mResetScene, val.store(true); SetInfo("View reset", 1);), // + KEY_LAMBDA('q', mCfgC, dummy, { + if (mFrontend->isGUIRunning()) { + SetInfo("Stopping GUI", 1); + mFrontend->stopGUI(); + } else { + SetInfo("Starting GUI", 1); + mFrontend->startGUI(); + } + }), // + "Quit / Next event / Reset Display Settings / Start / Stop Qt GUI"); + + mFrontend->add_key_binding( + KEY_LAMBDA('l', mCfgL, drawSector, { + if (val >= (mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1))) { + val = -1; + SetInfo("Showing all sectors", 1); + } else { + val++; + SetInfo("Showing sector %d", val); + } + }), + KEY_LAMBDA('k', mCfgL, drawSector, { + if (val <= -1) { + val = mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1); + } else { + val--; + } + if (val == -1) { + SetInfo("Showing all sectors", 1); + } else { + SetInfo("Showing sector %d", val); + } + }), + KEY_LAMBDA('J', mCfgL, drawRelatedSectors, val ^= 1; SetInfo("Drawing of related sectors %s", mCfgL.drawRelatedSectors ? "enabled" : "disabled");), // + KEY_LAMBDA(';', mCfgH, xAdd, { + mCfgH.xAdd += 60; + mCfgH.zAdd += 60; + SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); + }), + KEY_LAMBDA(':', mCfgH, xAdd, { + 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); + }), // + "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('y', mCfgC, dummy, { + if (mAnimateVectors[0].size() > 1) { + if (mCfgC.mAnimateScreenshot) { + mAnimationExport++; + } + startAnimation(); + SetInfo("Starting Animation", 1); + } else { + SetInfo("Insufficient Animation points to start Animation", 1); + } + }), + KEY_LAMBDA('Y', mCfgC, dummy, setAnimationPoint(); SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimateVectors[0].size(), mAnimateVectors[0].back());), // + KEY_LAMBDA('\'', mCfgC, dummy, removeAnimationPoint(); SetInfo("Removed Animation point", 1);), // + KEY_LAMBDA('X', mCfgC, dummy, resetAnimation(); SetInfo("Reset Animation points", 1);), // + KEY_LAMBDA('M', mCfgL, animationMode, { + val++; + if (val == 7) { + val = 0; + } + if (val == 6) { + SetInfo("Animation mode %d - Centered on origin", val); + } else { + SetInfo("Animation mode %d - Position: %s, Direction: %s", val, (val & 2) ? "Spherical (spherical rotation)" : (val & 4) ? "Spherical (Euler angles)" : "Cartesian", (val & 1) ? "Euler angles" : "Quaternion"); + } + }), // + "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('#', mCfgL, invertColors, val ^= 1;), + KEY_LAMBDA('g', mCfgL, drawGrid, val ^= 1; SetInfo("Fast Cluster Search Grid %s", mCfgL.drawGrid ? "shown" : "hidden");), + KEY_LAMBDA('i', mCfgH, projectXY, val ^= 1; SetInfo("Projection onto xy plane %s", mCfgH.projectXY ? "enabled" : "disabled");), // + "Invert colors / Draw Grid / Project onto XY-plane"); + + mFrontend->add_key_binding( + KEY_LAMBDA('x', mCfgL, excludeClusters, val = (val + 1) % 3; SetInfo(mCfgL.excludeClusters ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", mCfgL.excludeClusters);), + KEY_LAMBDA('.', mCfgH, hideRejectedTracks, val ^= 1; SetInfo("Rejected tracks are %s", mCfgH.hideRejectedTracks ? "hidden" : "shown");), // + "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; + } + 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"); + }), + KEY_LAMBDA('z', mCfgH, markFakeClusters, val ^= 1; SetInfo("Marking fake clusters: %s", mCfgH.markFakeClusters ? "on" : "off");), // + 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; + } + 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"); + } + }), // + "Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20 / Mark fake attached clusters / Mark clusters attached as adjacent"); + + mFrontend->add_key_binding( + KEY_LAMBDA('L', mCfgL, showCollision, { + if (val >= mNCollissions - 1) { + val = -1; + SetInfo("Showing all collisions", 1); + } else { + val++; + SetInfo("Showing collision %d / %d", val, mNCollissions); + } + }), + KEY_LAMBDA('K', mCfgL, showCollision, { + if (val <= -1) { + val = mNCollissions - 1; + } else { + val--; + } + if (val == -1) { + SetInfo("Showing all collisions", 1); + } else { + SetInfo("Showing collision %d / %d", val, mNCollissions); + } + }), + KEY_LAMBDA('C', mCfgL, colorCollisions, val ^= 1; SetInfo("Color coding of collisions %s", mCfgL.colorCollisions ? "enabled" : "disabled");), // + "Draw single collisions (next / previous) / Colorcode clusters of different collisions"); + + mFrontend->add_key_binding( + KEY_LAMBDA('v', mCfgH, hideRejectedClusters, val ^= 1; SetInfo("Rejected clusters are %s", mCfgH.hideRejectedClusters ? "hidden" : "shown");), // + KEY_LAMBDA('j', mCfgH, separateExtrapolatedTracks, { + if (val) { + mCfgH.splitCETracks ^= 1; + } + val ^= 1; + SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", val ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled"); + }), + KEY_LAMBDA('u', mCfgH, trackFilter, val = (val + 1) % (mConfig.filterMacros.size() + 1); SetInfo("Track filter: %s", mCfgH.trackFilter == 0 ? "None" : mConfig.filterMacros[mCfgH.trackFilter - 1].c_str());), // + "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; + } + 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]); + }), + KEY_LAMBDA('G', mCfgH, propagateLoopers, val ^= 1; SetInfo("Propagation of loopers %s", mCfgH.propagateLoopers ? "enabled" : "disabled");), // + "Extrapolate tracks / loopers"); + + mFrontend->add_key_binding( + KEY_LAMBDA('t', mCfgC, mRequestScreenshot, { + static int32_t nScreenshot = 1; + char fname[32]; + snprintf(fname, 32, "screenshot%d.bmp", nScreenshot++); + val = true; + mScreenshotFile = fname; + SetInfo("Taking screenshot (%s)", fname); + }), + KEY_LAMBDA('T', mCfgC, mAnimateScreenshot, val ^= 1; SetInfo("Writing screenshots from animation set to %d", mCfgC.mAnimateScreenshot);), // + KEY_LAMBDA('Z', mCfgR, screenshotScaleFactor, { + val += 1; + if (val == 5) { + val = 1; + } + SetInfo("Screenshot scaling factor set to %d", val); + }), // + "Take Screenshot / Record Animation to pictures / Change screenshot resolution (scaling factor)"); + + mFrontend->add_key_binding( + KEY_LAMBDA('S', mCfgL, smoothPoints, val ^= true; SetInfo("Smoothing of points %s", mCfgL.smoothPoints ? "enabled" : "disabled");), // + KEY_LAMBDA('A', mCfgL, smoothLines, val ^= true; SetInfo("Smoothing of lines %s", mCfgL.smoothLines ? "enabled" : "disabled");), // + KEY_LAMBDA('D', mCfgL, depthBuffer, { + val ^= true; + SetInfo("Depth buffer (z-buffer, %u bits) %s", mBackend->DepthBits(), val ? "enabled" : "disabled"); + 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; + } + SetInfo("Multisampling anti-aliasing factor set to %d", val); + }), + KEY_LAMBDA('U', mCfgR, drawQualityDownsampleFSAA, { + val++; + if (val == 1) { + val = 2; + } + if (val == 5) { + val = 0; + } + SetInfo("Downsampling anti-aliasing factor set to %d", val); + }), + KEY_LAMBDA('V', mCfgR, drawQualityVSync, val ^= true; SetInfo("VSync: %s", mCfgR.drawQualityVSync ? "enabled" : "disabled");), // + KEY_LAMBDA('R', mCfgR, fpsLimit, { + val = (val + 1) % 3; + 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"); + } + }), // + "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('F', mCfgR, fullScreen, val ^= 1; SetInfo("Toggling full screen (%d)", (int32_t)mCfgR.fullScreen);), // + KEY_LAMBDA('_', mCfgR, maximized, val ^= 1; SetInfo("Toggling Maximized window (%d)", (int32_t)mCfgR.maximized);), // + KEY_LAMBDA('b', mCfgR, fov, { + if ((val += 5) > 175) { + val = 5; + } + SetInfo("Set FOV to %f", val); + }), + KEY_LAMBDA('m', mCfgR, rendererCompatSmoothing, { + if (displayOpenGLCore && backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { + SetInfo("OpenGL compat profile not available, using core profile", 1); + } else { + val ^= 1; + SetInfo("Using renderer path for %s profile", val ? "compat" : "core (shader)"); + } + }), + KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true; SetInfo("OpenGL Indirect Draw %s", mCfgR.useGLIndirectDraw ? "enabled" : "disabled");), // + "Switch mFullScreen / Maximized window / Change FOV (field of view) / Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines) / Enable / disable GL indirect draw"); + + mFrontend->add_key_binding( + KEY_LAMBDA('o', mCfgC, dummy, { + FILE* ftmp = fopen("glpos.tmp", "w+b"); + if (ftmp) { + int32_t retval = fwrite(&mViewMatrix, sizeof(mViewMatrix), 1, ftmp); + if (retval != 1) { + GPUError("Error writing position to file"); + } else { + GPUInfo("Position stored to file"); + } + fclose(ftmp); + } else { + GPUError("Error opening file"); + } + SetInfo("Camera position stored to file", 1); + }), + KEY_LAMBDA('p', mCfgC, dummy, { + FILE* ftmp = fopen("glpos.tmp", "rb"); + if (ftmp) { + int32_t retval = fread(&mViewMatrix, 1, sizeof(mViewMatrix), ftmp); + if (retval == sizeof(mViewMatrix)) { + GPUInfo("Position read from file"); + } else { + GPUError("Error reading position from file"); + } + fclose(ftmp); + } else { + GPUError("Error opening file"); + } + SetInfo("Camera position loaded from file", 1); + }), + KEY_LAMBDA('O', mCfgC, dummy, { + FILE* ftmp = fopen("glanimation.tmp", "w+b"); + if (ftmp) { + fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp); + int32_t size = mAnimateVectors[0].size(); + fwrite(&size, sizeof(size), 1, ftmp); + for (int32_t i = 0; i < 9; i++) { + fwrite(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); + } + fwrite(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); + fclose(ftmp); + } else { + GPUError("Error opening file"); + } + SetInfo("Animation path stored to file %s", "glanimation.tmp"); + }), + KEY_LAMBDA('P', mCfgC, dummy, { + FILE* ftmp = fopen("glanimation.tmp", "rb"); + if (ftmp) { + int32_t retval = fread(&mCfgL, sizeof(mCfgL), 1, ftmp); + int32_t size; + retval += fread(&size, sizeof(size), 1, ftmp); + for (int32_t i = 0; i < 9; i++) { + mAnimateVectors[i].resize(size); + retval += fread(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); + } + mAnimateConfig.resize(size); + retval += fread(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); + (void)retval; // disable unused warning + fclose(ftmp); + updateConfig(); + } else { + GPUError("Error opening file"); + } + SetInfo("Animation path loaded from file %s", "glanimation.tmp"); + }), // + "Save / restore current camera position / Animation path"); + + mFrontend->add_key_binding( + KEY_LAMBDA(mFrontend->KEY_F1, mCfgL, drawTPC, val ^= 1; SetInfo("Showing TPC Clusters: %d", (int32_t)mCfgL.drawTPC);), + KEY_LAMBDA(mFrontend->KEY_F2, mCfgL, drawTRD, val ^= 1; SetInfo("Showing TRD Tracklets: %d", (int32_t)mCfgL.drawTRD);), + KEY_LAMBDA(mFrontend->KEY_F3, mCfgL, drawTOF, val ^= 1; SetInfo("Showing TOF Hits: %d", (int32_t)mCfgL.drawTOF);), + KEY_LAMBDA(mFrontend->KEY_F4, mCfgL, drawITS, val ^= 1; SetInfo("Showing ITS Clusters: %d", (int32_t)mCfgL.drawITS);), // + "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; 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);), + KEY_LAMBDA(mFrontend->KEY_F2 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTRDTracks, val ^= 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);), + KEY_LAMBDA(mFrontend->KEY_F3 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTOFTracks, val ^= 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);), + KEY_LAMBDA(mFrontend->KEY_F4 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawITSTracks, val ^= 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);), + KEY_LAMBDA(mFrontend->KEY_F12 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTracksAndFilter, val ^= 1; SetInfo("Track filter: %s", mCfgH.drawTracksAndFilter ? "AND" : "OR");), // + "Enable / disable track detector filter / SHIFT F12: Switch track detector filter between AND and OR mode", + "[SHIFT] + [F1] to [F4] / [F12]"); + + mFrontend->add_key_binding( + KEY_LAMBDA('h', mCfgC, mPrintInfoTextAlways, { + if (val) { + val = false; + SetInfo("Showing help text disabled", 1); + } else if (mInfoHelpTimer.IsRunning()) { + val = true; + mInfoHelpTimer.Reset(); + SetInfo("Showing help text until disabled", 1); + } else { + PrintHelp(); + SetInfo("Showing help text", 1); + } + }), + KEY_LAMBDA('H', mCfgC, mPrintInfoText, { + val += 1; + val &= 3; + SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled"); + }), // + "Print Help / Show info texts"); +} + +#endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index 70a3083d23906..162a738318588 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -18,53 +18,17 @@ 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: ^ }; @@ -82,16 +46,7 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) const GPUSettingsDisplayHeavy oldCfgH = mCfgH; const GPUSettingsDisplayLight oldCfgL = mCfgL; const GPUSettingsDisplayRenderer oldCfgR = mCfgR; - if (key == 'n') { - mCfgC.mDisplayControl.store(1); - SetInfo("Showing next event", 1); - } else if (key == mFrontend->KEY_ESCAPE) { - mCfgC.mDisplayControl.store(2); - SetInfo("Exiting", 1); - } else if (key == 'r') { - mCfgC.mResetScene.store(true); - SetInfo("View reset", 1); - } else if (key == mFrontend->KEY_ALT && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { + if (key == mFrontend->KEY_ALT && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { mCfgR.camLookOrigin ^= 1; mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * mCfgR.camYUp; SetInfo("Camera locked on origin: %s", mCfgR.camLookOrigin ? "enabled" : "disabled"); @@ -110,386 +65,10 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) 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') { - mCfgC.mPrintInfoText += 1; - mCfgC.mPrintInfoText &= 3; - SetInfo("Info text display - console: %s, onscreen %s", (mCfgC.mPrintInfoText & 2) ? "enabled" : "disabled", (mCfgC.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 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - 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 if (key == mFrontend->KEY_F1 && !mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - mCfgL.drawTPC ^= 1; - SetInfo("Showing TPC Clusters: %d", (int32_t)mCfgL.drawTPC); - } else if (key == mFrontend->KEY_F2 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - 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 if (key == mFrontend->KEY_F2 && !mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - mCfgL.drawTRD ^= 1; - SetInfo("Showing TRD Tracklets: %d", (int32_t)mCfgL.drawTRD); - } else if (key == mFrontend->KEY_F3 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - 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 if (key == mFrontend->KEY_F3 && !mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - mCfgL.drawTOF ^= 1; - SetInfo("Showing TOF Hits: %d", (int32_t)mCfgL.drawTOF); - } else if (key == mFrontend->KEY_F4 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - 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 if (key == mFrontend->KEY_F4 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - mCfgL.drawITS ^= 1; - SetInfo("Showing ITS Clusters: %d", (int32_t)mCfgL.drawITS); - } else if (key == mFrontend->KEY_F12 && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - 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++); - mCfgC.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 == 'T') { - mCfgC.mAnimateScreenshot ^= 1; - SetInfo("Writing screenshots from animation set to %d", mCfgC.mAnimateScreenshot); - } else if (key == 'y') { - if (mAnimateVectors[0].size() > 1) { - if (mCfgC.mAnimateScreenshot) { - mAnimationExport++; - } - startAnimation(); - SetInfo("Starting Animation", 1); - } else { - SetInfo("Insufficient Animation points to start Animation", 1); - } } else if (key == '>') { mCfgC.mAnimationChangeConfig ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.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; - } - 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); - 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 (mCfgC.mPrintInfoTextAlways) { - mCfgC.mPrintInfoTextAlways = false; - SetInfo("Showing help text disabled", 1); - } else if (mInfoHelpTimer.IsRunning()) { - mCfgC.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 (mFrontend->processKeyLambda(key)) { } /* else if (key == '^') From 1cf4604d4e290845244eee92acb1240de492c147 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 27 Oct 2026 06:35:33 +0100 Subject: [PATCH 11/41] GPU Display Frontend: Add a map for key down actions, and assign keys to them via add_key_binding --- GPU/GPUTracking/display/GPUDisplay.cxx | 26 ++++---- .../display/frontend/GPUDisplayFrontend.h | 3 +- .../frontend/GPUDisplayFrontendConfig.cxx | 56 +++++++++++----- .../frontend/GPUDisplayFrontendConfigKeys.inc | 66 +++++++++++++++++++ .../frontend/GPUDisplayFrontendGlfw.cxx | 4 +- .../display/frontend/GPUDisplayFrontendGlfw.h | 2 +- .../display/frontend/GPUDisplayFrontendKeys.h | 28 ++++++++ .../display/frontend/GPUDisplayKeys.cxx | 29 +------- 8 files changed, 152 insertions(+), 62 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index e2102660e6a29..988e0a2509a84 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -243,10 +243,10 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla 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 rotateModel = mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_MODEL]] || mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_AROUND_Z]]; + bool rotateModelTPC = mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_AROUND_Z]]; // Calculate rotation / translation scaling factors float scalefactor = mFrontend->mKeys[mFrontend->KEY_SHIFT] ? 0.2f : 1.0f; @@ -286,12 +286,12 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mCfgC.mResetScene.store(false); } else { - float moveZ = scalefactor * ((float)mMouseWheelTmp / 150 + (float)(mFrontend->mKeys[(uint8_t)'W'] - mFrontend->mKeys[(uint8_t)'S']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); - float moveY = scalefactor * ((float)(mFrontend->mKeys[mFrontend->KEY_PAGEDOWN] - mFrontend->mKeys[mFrontend->KEY_PAGEUP]) * 0.2f * mFPSScale); - float moveX = scalefactor * ((float)(mFrontend->mKeys[(uint8_t)'A'] - mFrontend->mKeys[(uint8_t)'D']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); - float rotRoll = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[(uint8_t)'E'] - mFrontend->mKeys[(uint8_t)'F']) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); - float rotYaw = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->KEY_RIGHT] - mFrontend->mKeys[mFrontend->KEY_LEFT]); - float rotPitch = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->KEY_DOWN] - mFrontend->mKeys[mFrontend->KEY_UP]); + float moveZ = scalefactor * ((float)mMouseWheelTmp / 150 + (float)(mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_FORWARD]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_BACKWARD]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float moveY = scalefactor * ((float)(mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_DOWN]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_UP]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float moveX = scalefactor * ((float)(mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_LEFT]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_STRAFE_RIGHT]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float rotRoll = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_LEFT]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_RIGHT]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); + float rotYaw = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_YAW_RIGHT]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_YAW_LEFT]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); + float rotPitch = rotatescalefactor * mFPSScale * 2 * (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_PITCH_DOWN]] - mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_PITCH_UP]]) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); float mouseScale = 1920.f / std::max(1920.f, mBackend->mScreenWidth); if (mFrontend->mMouseDnR && mFrontend->mMouseDn) { @@ -305,7 +305,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla rotPitch += rotatescalefactor * mouseScale * ((float)mFrontend->mMouseMvY - (float)mFrontend->mMouseDnY); } - if (mFrontend->mKeys[(uint8_t)'<']) { + if (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_CHANGE_ANIMATION_INTERVAL]]) { mAnimationDelay += moveX; if (mAnimationDelay < 0.05f) { mAnimationDelay = 0.05f; @@ -395,7 +395,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->mKeys[(uint8_t)'-']) * mFrontend->mKeys[mFrontend->KEY_SHIFT]; + 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 +405,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mUpdateDrawCommands = true; } minSize *= 2; - int32_t deltaPoint = (mFrontend->mKeys[(uint8_t)'+'] - mFrontend->mKeys[(uint8_t)'-']) * !mFrontend->mKeys[mFrontend->KEY_SHIFT]; + 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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 54a5c3e332f64..b75035155d66b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -90,8 +90,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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.) #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" -#undef GPUDisplayFrontendAddKey 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; @@ -99,6 +99,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface std::mutex mExternalGUIKeyMutex; std::vector mExternalGUIKeys; // Keys sent by external entity (usually console), may be ignored by backend. std::mutex mExitStateMutex; + std::array mKeyDownMap = {}; // Keyboard / Mouse actions bool mMouseDn = false; // Mouse button down diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 8ba89e6d5bbfe..90949c85bca29 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -39,7 +39,7 @@ struct GPUDisplayFrontend::configRTTIExt : public qConfig::qConfigRTTIentry { using configKeyCallback = std::function; struct GPUDisplayFrontend::configKey { - uint32_t configRTTIIndex; + uint32_t configRTTIIndex = std::numeric_limits::max(); // Index in rttiEntries, KEY_MAP_INDEX + id for a key down mapping, INVALID_INDEX otherwise configKeyCallback callback; }; @@ -54,14 +54,15 @@ struct GPUDisplayFrontend::configRTTIInfo { std::vector rttiEntries; 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::string helpText; static constexpr std::string_view possibleKeys = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#.:;>'^_"; // Bindable keys >= 32 (space), special keys < 32 are those with a name in keyNames - static constexpr char reservedKeysData[] = {'W', 'A', 'S', 'D', 'E', 'F', 'w', 'a', 's', 'd', 'e', 'f', '+', '-', '<', KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_PAGEUP, KEY_PAGEDOWN, KEY_SHIFT, KEY_ALT, KEY_RALT, KEY_CTRL, KEY_RCTRL}; - static constexpr std::string_view reservedKeys{reservedKeysData, sizeof(reservedKeysData)}; // Keys queried continuously as held keys (camera control, modifiers), not available for bindings template static constexpr uint32_t configStructIndex() // Index of struct T in configStructs, compile-time error if not present @@ -176,18 +177,28 @@ bool GPUDisplayFrontend::HandleExternalGUIKeys() template void GPUDisplayFrontend::add_key_binding(Args&&... args) { - constexpr size_t nKeys = (0 + ... + std::is_same_v, std::tuple>); + 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 = [&](std::tuple& key) { - const std::string name = mConfigRTTI->getKeyName(std::get<0>(key)); - auto& entry = mConfigRTTI->configKeys[std::get<0>(key)]; - if (entry.callback) { + 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"); } - entry.configRTTIIndex = std::get<1>(key); - entry.callback = std::move(std::get<2>(key)); + 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()); @@ -208,11 +219,11 @@ void GPUDisplayFrontend::listConfig() 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 - const bool possible = (base < N_SPECIAL_KEYS && !rtti.keyNames[base].empty()) || (k < 256 && rtti.possibleKeys.find((char)k) != std::string_view::npos); - if (rtti.configKeys[k].callback) { + 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 && rtti.reservedKeys.find((char)base) == std::string_view::npos) { + } else if (possible) { availableKeys.emplace_back(k); } } @@ -233,10 +244,20 @@ void GPUDisplayFrontend::listConfig() GPUInfo(" %-28s %-32s %-12s %s", e.configStruct.c_str(), e.name.c_str(), e.type.c_str(), keys.c_str()); } GPUInfo("Bound keys (key, struct, option):"); + std::vector mappedKeys(KEY_MAP_MAX); for (uint16_t k : boundKeys) { const uint32_t index = rtti.configKeys[k].configRTTIIndex; - const bool hasRTTI = index != configRTTIInfo::INVALID_INDEX; - GPUInfo(" %-10s %-28s %s", ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); + if (rtti.isKeyMap(index)) { + mappedKeys[index - configRTTIInfo::KEY_MAP_INDEX] = "[" + rtti.getKeyName(k) + "]"; + GPUInfo(" %-10s %-28s %s", ("[" + 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(" %-10s %-28s %s", ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); + } + } + 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()); } std::string available; for (uint16_t k : availableKeys) { @@ -254,9 +275,10 @@ void GPUDisplay::buildConfig() 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" -#undef GPUDisplayFrontendAddKey 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); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index 6417d700b6470..069c00fc696dc 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -15,6 +15,7 @@ #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() @@ -409,6 +410,71 @@ void GPUDisplay::buildConfigKeys() SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled"); }), // "Print Help / Show info texts"); + + 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), // + "Strafe up / down"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP('e', ROTATE_LEFT), + KEY_DOWN_MAP('f', ROTATE_RIGHT), // + "Rotate left / right"); + + 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('+', INCREASE_WIDTH), + KEY_DOWN_MAP('-', DECREASE_WIDTH), // + "Increase / decrease point size (Hold SHIFT for lines)"); + + 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; + SetInfo("Camera locked on origin: %s", val ? "enabled" : "disabled"); + }), + KEY_LAMBDA(mFrontend->KEY_CTRL + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgR, camYUp, { + val ^= 1; + mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * val; + SetInfo("Camera locked on y-axis facing upwards: %s", val ? "enabled" : "disabled"); + }), + KEY_LAMBDA(mFrontend->KEY_ENTER, mCfgR, cameraMode, { + val++; + if (val == 4) { + val = 0; + } + mCfgR.camLookOrigin = val & 1; + mCfgR.camYUp = val & 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", val, modeText[val]); + }), + "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('>', mCfgC, mAnimationChangeConfig, val ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled");), + KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // + "Toggle config interpolation during Animation / change Animation interval (via movement)"); } #endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 176d2b916040e..336161216b351 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -171,7 +171,7 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 if (action == GLFW_PRESS) { me->mLastKeyDown = handleKey; } else if (action == GLFW_RELEASE) { - keyPress = (uint8_t)me->mKeyDownMap[handleKey]; + keyPress = (uint8_t)me->mGLFWKeyDownMap[handleKey]; me->mKeys[keyPress] = false; } } @@ -185,7 +185,7 @@ void GPUDisplayFrontendGlfw::char_callback(GLFWwindow* window, uint32_t codepoin if (keyPress >= 'a' && keyPress <= 'z') { keyPress += 'A' - 'a'; } - me->mKeyDownMap[me->mLastKeyDown] = keyPress; + me->mGLFWKeyDownMap[me->mLastKeyDown] = keyPress; me->mKeys[keyPress] = true; me->HandleKey(codepoint); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index f079c87852e13..58de6b0990aa7 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -67,7 +67,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend int32_t mWindowY = 0; int32_t mWindowWidth = INIT_WIDTH; int32_t mWindowHeight = INIT_HEIGHT; - uint8_t mKeyDownMap[256] = {0}; + uint8_t mGLFWKeyDownMap[256] = {0}; uint8_t mLastKeyDown = 0; }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h index a22eaeb5fbcd8..4fbe81054c1d2 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h @@ -44,3 +44,31 @@ GPUDisplayFrontendAddKey(27, ESCAPE); // fixed at 27 GPUDisplayFrontendAddKey(28, HOME); GPUDisplayFrontendAddKey(29, END); GPUDisplayFrontendAddKey(32, SPACE); // fixed at 32 + +#ifndef GPUDisplayFrontendKeyDownMap +#define GPUDisplayFrontendKeyDownMap(...) +#endif + +GPUDisplayFrontendKeyDownMap(0, FORWARD); +GPUDisplayFrontendKeyDownMap(1, BACKWARD); +GPUDisplayFrontendKeyDownMap(2, STRAFE_LEFT); +GPUDisplayFrontendKeyDownMap(3, STRAFE_RIGHT); +GPUDisplayFrontendKeyDownMap(4, STRAFE_UP); +GPUDisplayFrontendKeyDownMap(5, STRAFE_DOWN); +GPUDisplayFrontendKeyDownMap(6, ROTATE_LEFT); +GPUDisplayFrontendKeyDownMap(7, ROTATE_RIGHT); +GPUDisplayFrontendKeyDownMap(8, INCREASE_WIDTH); +GPUDisplayFrontendKeyDownMap(9, DECREASE_WIDTH); +GPUDisplayFrontendKeyDownMap(10, FOCUS_ORIGIN); +GPUDisplayFrontendKeyDownMap(11, FOCUS_ORIENT_UP); +GPUDisplayFrontendKeyDownMap(12, ROTATE_MODEL); +GPUDisplayFrontendKeyDownMap(13, ROTATE_AROUND_Z); +GPUDisplayFrontendKeyDownMap(14, CHANGE_ANIMATION_INTERVAL); +GPUDisplayFrontendKeyDownMap(15, ROTATE_YAW_LEFT); +GPUDisplayFrontendKeyDownMap(16, ROTATE_YAW_RIGHT); +GPUDisplayFrontendKeyDownMap(17, ROTATE_PITCH_UP); +GPUDisplayFrontendKeyDownMap(18, ROTATE_PITCH_DOWN); +GPUDisplayFrontendKeyDownMap(19, MAX); // Must be the maximum value! + +#undef GPUDisplayFrontendAddKey +#undef GPUDisplayFrontendKeyDownMap diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index 162a738318588..0ea230c0625a9 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -18,17 +18,10 @@ using namespace o2::gpu; const char* HelpText[] = { - "[>] / [<] Toggle config interpolation during Animation / change Animation interval (via movement)", - "[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)", "[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", // FREE: [SPACE] [Q] // Test setting: ^ }; @@ -46,28 +39,8 @@ void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) const GPUSettingsDisplayHeavy oldCfgH = mCfgH; const GPUSettingsDisplayLight oldCfgL = mCfgL; const GPUSettingsDisplayRenderer oldCfgR = mCfgR; - if (key == mFrontend->KEY_ALT && mFrontend->mKeys[mFrontend->KEY_SHIFT]) { - 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->mKeys[mFrontend->KEY_SHIFT]) { - 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) { + if (key == mFrontend->KEY_ALT) { mFrontend->mKeys[mFrontend->KEY_CTRL] = false; // Release CTRL with alt, to avoid orienting along y automatically! - } else if (key == '>') { - mCfgC.mAnimationChangeConfig ^= 1; - SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled"); } else if (mFrontend->processKeyLambda(key)) { } /* From fd74233a853e34e4f31eae6924a5611f16b632c1 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 27 Oct 2026 07:10:15 +0100 Subject: [PATCH 12/41] GPU Display: Switch help-text to auto-generated help text from key bindings --- GPU/GPUTracking/display/CMakeLists.txt | 2 +- GPU/GPUTracking/display/GPUDisplay.h | 2 - .../display/frontend/GPUDisplayFrontend.h | 2 + .../frontend/GPUDisplayFrontendConfig.cxx | 29 +++++++-- .../frontend/GPUDisplayFrontendConfigKeys.inc | 2 + .../display/frontend/GPUDisplayHelp.cxx | 34 ++++++++++ .../display/frontend/GPUDisplayKeys.cxx | 64 ------------------- 7 files changed, 64 insertions(+), 71 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx delete mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 5f770ddf7b914..592483293303c 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -113,7 +113,7 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx helpers/GPUDisplayQuaternion.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx - frontend/GPUDisplayKeys.cxx + frontend/GPUDisplayHelp.cxx frontend/GPUDisplayFrontendConfig.cxx) set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h) diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 8a48d5a3fbf3b..92d8d194e8eef 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -259,8 +259,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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index b75035155d66b..3b26f22eea4c3 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace o2::gpu @@ -148,6 +149,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 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 index 90949c85bca29..3fd6dd81d99b6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -61,7 +61,7 @@ struct GPUDisplayFrontend::configRTTIInfo { 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::string helpText; + std::vector helpText; static constexpr std::string_view possibleKeys = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#.:;>'^_"; // Bindable keys >= 32 (space), special keys < 32 are those with a name in keyNames template @@ -96,7 +96,7 @@ struct GPUDisplayFrontend::configRTTIInfo { } helpText.clear(); for (const auto& e : helpTextEntries) { - helpText += e.first + std::string(width - e.first.size(), ' ') + ": " + e.second + "\n"; + 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 @@ -208,6 +208,8 @@ void GPUDisplayFrontend::add_key_binding(Args&&... args) mConfigRTTI->helpTextEntries.emplace_back(std::move(keys), std::get(argTuple)); } +const std::vector& GPUDisplayFrontend::getHelpText() const { return mConfigRTTI->helpText; } + void GPUDisplayFrontend::listConfig() { const auto& rtti = *mConfigRTTI; @@ -245,14 +247,18 @@ void GPUDisplayFrontend::listConfig() } 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(" %-10s %-28s %s", ("[" + rtti.getKeyName(k) + "]").c_str(), "(key down map)", rtti.keyMapNames[index - configRTTIInfo::KEY_MAP_INDEX].c_str()); + 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(" %-10s %-28s %s", ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); + 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("Key down mappings (id, name, key):"); @@ -302,3 +308,18 @@ bool GPUDisplayFrontend::processKeyLambda(uint32_t key) } return false; } + +void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) +{ + const GPUSettingsDisplayHeavy oldCfgH = mCfgH; + const GPUSettingsDisplayLight oldCfgL = mCfgL; + const GPUSettingsDisplayRenderer oldCfgR = mCfgR; + 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)) { + } + + if (!doNotHandleConfigChanges) { + checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index 069c00fc696dc..86925d5c8c3bf 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -475,6 +475,8 @@ void GPUDisplay::buildConfigKeys() KEY_LAMBDA('>', mCfgC, mAnimationChangeConfig, val ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled");), KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // "Toggle config interpolation during Animation / change Animation interval (via movement)"); + + mFrontend->mConfigRTTI->helpTextEntries.emplace_back("[MOUSE 1] / [MOUSE 2] / [MOUSE 1+2] / [SHIFT]", "Look around / Strafe camera / Zoom / Rotate / Slow Zoom / Move / Rotate"); } #endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx new file mode 100644 index 0000000000000..275ac348653d8 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx @@ -0,0 +1,34 @@ +// 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" + +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(); + for (uint32_t i = 0; i < lines.size(); i++) { + OpenGLPrint(lines[i].c_str(), 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/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx deleted file mode 100644 index 0ea230c0625a9..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ /dev/null @@ -1,64 +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[] = { - "[MOUSE 1] Look around", - "[MOUSE 2] Strafe camera", - "[MOUSE 1+2] Zoom / Rotate", - "[SHIFT] Slow Zoom / Move / Rotate", - // 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 == mFrontend->KEY_ALT) { - mFrontend->mKeys[mFrontend->KEY_CTRL] = false; // Release CTRL with alt, to avoid orienting along y automatically! - } else if (mFrontend->processKeyLambda(key)) { - } - /* - else if (key == '^') - { - mTestSetting++; - SetInfo("Debug test variable set to %d", mTestSetting); - } - */ - - if (!doNotHandleConfigChanges) { - checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } -} - -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); - } -} From a87a9405988cdcecf39b90b7d3292045487ec152 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 27 Oct 2026 07:38:04 +0100 Subject: [PATCH 13/41] GPU Display: Info messages per configuration value, not per key, so they can be reused for the GUI --- GPU/GPUTracking/display/GPUDisplay.h | 1 + .../frontend/GPUDisplayFrontendConfig.cxx | 51 +++++++ .../frontend/GPUDisplayFrontendConfigInfo.inc | 129 ++++++++++++++++++ .../frontend/GPUDisplayFrontendConfigKeys.inc | 121 ++++------------ 4 files changed, 209 insertions(+), 93 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 92d8d194e8eef..896fef6f3893b 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -101,6 +101,7 @@ class GPUDisplay : public GPUDisplayInterface private: void buildConfig(); void buildConfigKeys(); + void buildConfigInfo(); static constexpr int32_t NSECTORS = o2::tpc::constants::MAXSECTOR; static constexpr float GL_SCALE_FACTOR = (1.f / 100.f); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 3fd6dd81d99b6..23070ec813baf 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -52,6 +52,7 @@ static constexpr bool displayOpenGLCore = false; struct GPUDisplayFrontend::configRTTIInfo { static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR}; // Config structs with RTTI, as members of GPUDisplay std::vector rttiEntries; + std::vector configInfos; // SetInfo callback for an option, indexed like rttiEntries, called after the callback of a key bound to it std::vector> rttiReverseId; // [configStructIndex][offset] -> index in rttiEntries, INVALID_INDEX if no member at that offset static constexpr uint32_t INVALID_INDEX = std::numeric_limits::max(); static constexpr uint32_t KEY_MAP_INDEX = 0x80000000; // configRTTIIndex of keys mapped to mKeyDownMap[configRTTIIndex - KEY_MAP_INDEX] @@ -78,6 +79,13 @@ struct GPUDisplayFrontend::configRTTIInfo { { 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; + } template uint32_t getRTTIIndex(GPUDisplay& display, M T::* member) // RTTI index of a direct member of a config struct { @@ -274,6 +282,7 @@ void GPUDisplayFrontend::listConfig() } #include "GPUDisplayFrontendConfigKeys.inc" +#include "GPUDisplayFrontendConfigInfo.inc" void GPUDisplay::buildConfig() { @@ -294,6 +303,45 @@ void GPUDisplay::buildConfig() } buildConfigKeys(); + + rtti->configInfos.resize(rtti->rttiEntries.size()); + auto messageInfo = [this](const std::string& message, const T* value) { // Info from the qConfig message, value printed with print_type as in qconfig.cxx + return configKeyCallback([this, message, value]() { + char buffer[256]; + const int32_t n = snprintf(buffer, sizeof(buffer), message.c_str(), qConfig::print_type(*value).c_str()); + if (n < 0 || n >= (int32_t)sizeof(buffer)) { + GPUError("Info message too long: %s", message.c_str()); + return; + } + SetInfo("%s", buffer); + }); + }; + for (uint32_t i = 0; i < rtti->rttiEntries.size(); i++) { + const auto& e = rtti->rttiEntries[i]; + if (e.message.empty() || e.isVector || e.arraySize) { + continue; + } + const char* ptr = GPUDisplayFrontend::configRTTIInfo::configStructPtr(*this, e.configStructIndex) + e.offset; + configKeyCallback info; + if (e.type == "bool") { + info = messageInfo(e.message, (const bool*)ptr); + } else if (e.type == "float") { + info = messageInfo(e.message, (const float*)ptr); + } else if (e.type == "int8_t") { + info = messageInfo(e.message, (const int8_t*)ptr); + } else if (e.type == "uint8_t") { + info = messageInfo(e.message, (const uint8_t*)ptr); + } else if (e.type == "int32_t" || e.type == "int") { + info = messageInfo(e.message, (const int32_t*)ptr); + } else if (e.type == "uint32_t") { + info = messageInfo(e.message, (const uint32_t*)ptr); + } else { + GPUError("No info message support for type %s of %s", e.type.c_str(), e.name.c_str()); + continue; + } + rtti->configInfos[i] = std::move(info); + } + buildConfigInfo(); rtti->buildHelpText(); } @@ -304,6 +352,9 @@ bool GPUDisplayFrontend::processKeyLambda(uint32_t key) } 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() && mConfigRTTI->configInfos[index]) { + mConfigRTTI->configInfos[index](); + } return true; } return false; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc new file mode 100644 index 0000000000000..2644f4e87949b --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -0,0 +1,129 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigInfo.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGINFO_INC +#define GPUDISPLAYFRONTENDCONFIGINFO_INC + +#define INFO_LAMBDA(obj, value, ...) std::pair(mFrontend->mConfigRTTI->getRTTIIndex(*this, &std::remove_cvref_t::value), configKeyCallback([=, this]() { [[maybe_unused]] auto& val = obj.value; __VA_ARGS__ })) // Info printed after the callback of a key bound to the option, same val as in KEY_LAMBDA, local helpers captured by copy + +void GPUDisplay::buildConfigInfo() +{ + auto addInfo = [this](std::pair&& info) { mFrontend->mConfigRTTI->configInfos[info.first] = std::move(info.second); }; + addInfo(INFO_LAMBDA(mCfgL, colorClusters, SetInfo("Color coding for seed / trrack attachmend %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, drawSector, { + if (val == -1) { + SetInfo("Showing all sectors", 1); + } else { + SetInfo("Showing sector %d", val); + } + })); + addInfo(INFO_LAMBDA(mCfgL, drawRelatedSectors, SetInfo("Drawing of related sectors %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, xAdd, SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd);)); + addInfo(INFO_LAMBDA(mCfgL, animationMode, { + if (val == 6) { + SetInfo("Animation mode %d - Centered on origin", val); + } else { + SetInfo("Animation mode %d - Position: %s, Direction: %s", val, (val & 2) ? "Spherical (spherical rotation)" : ((val & 4) ? "Spherical (Euler angles)" : "Cartesian"), (val & 1) ? "Euler angles" : "Quaternion"); + } + })); + addInfo(INFO_LAMBDA(mCfgL, drawGrid, SetInfo("Fast Cluster Search Grid %s", val ? "shown" : "hidden");)); + addInfo(INFO_LAMBDA(mCfgH, projectXY, SetInfo("Projection onto xy plane %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, excludeClusters, SetInfo(val ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", val);)); + addInfo(INFO_LAMBDA(mCfgH, hideRejectedTracks, SetInfo("Rejected tracks are %s", val ? "hidden" : "shown");)); + addInfo(INFO_LAMBDA(mCfgH, markClusters, { + SetInfo("Cluster flag highlight mask set to %d (%s)", val, val == 0 ? "off" : (val == 1 ? "split pad" : (val == 2 ? "split time" : (val == 4 ? "edge" : (val == 8 ? "singlePad" : (val == 0x10 ? "reject distance" : "reject error")))))); + })); + addInfo(INFO_LAMBDA(mCfgH, markFakeClusters, SetInfo("Marking fake clusters: %s", val ? "on" : "off");)); + addInfo(INFO_LAMBDA(mCfgH, markAdjacentClusters, { + if (val == 33) { + SetInfo("Marking protected clusters (%d)", val); + } else if (val == 32) { + SetInfo("Marking removable clusters (%d)", val); + } else { + SetInfo("Marking adjacent clusters (%d): rejected %s, tube %s, looper leg %s, low Pt %s, high incl %s", val, (val & 1) ? "yes" : " no", (val & 2) ? "yes" : " no", (val & 4) ? "yes" : " no", (val & 8) ? "yes" : " no", (val & 16) ? "yes" : " no"); + } + })); + addInfo(INFO_LAMBDA(mCfgL, showCollision, { + if (val == -1) { + SetInfo("Showing all collisions", 1); + } else { + SetInfo("Showing collision %d / %d", val, mNCollissions); + } + })); + addInfo(INFO_LAMBDA(mCfgL, colorCollisions, SetInfo("Color coding of collisions %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, hideRejectedClusters, SetInfo("Rejected clusters are %s", val ? "hidden" : "shown");)); + addInfo(INFO_LAMBDA(mCfgH, separateExtrapolatedTracks, SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", val ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, trackFilter, SetInfo("Track filter: %s", val == 0 ? "None" : mConfig.filterMacros[val - 1].c_str());)); + addInfo(INFO_LAMBDA(mCfgL, propagateTracks, { + const char* infoText[] = {"Hits connected", "Hits connected and propagated to vertex", "Reconstructed track propagated inwards and outwards", "Monte Carlo track"}; + SetInfo("Display of propagated tracks: %s", infoText[val]); + })); + addInfo(INFO_LAMBDA(mCfgH, propagateLoopers, SetInfo("Propagation of loopers %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, screenshotScaleFactor, SetInfo("Screenshot scaling factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgL, smoothPoints, SetInfo("Smoothing of points %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, smoothLines, SetInfo("Smoothing of lines %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, depthBuffer, SetInfo("Depth buffer (z-buffer, %u bits) %s", mBackend->DepthBits(), 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, rendererCompatSmoothing, { + if (displayOpenGLCore && backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { + SetInfo("OpenGL compat profile not available, using core profile", 1); + } else { + SetInfo("Using renderer path for %s profile", val ? "compat" : "core (shader)"); + } + })); + 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 index 86925d5c8c3bf..62dbe363d38d5 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -29,7 +29,7 @@ void GPUDisplay::buildConfigKeys() 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; SetInfo("Color coding for seed / trrack attachmend %s", mCfgL.colorClusters ? "enabled" : "disabled");), // + 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]"); @@ -52,10 +52,8 @@ void GPUDisplay::buildConfigKeys() KEY_LAMBDA('l', mCfgL, drawSector, { if (val >= (mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1))) { val = -1; - SetInfo("Showing all sectors", 1); } else { val++; - SetInfo("Showing sector %d", val); } }), KEY_LAMBDA('k', mCfgL, drawSector, { @@ -64,17 +62,11 @@ void GPUDisplay::buildConfigKeys() } else { val--; } - if (val == -1) { - SetInfo("Showing all sectors", 1); - } else { - SetInfo("Showing sector %d", val); - } }), - KEY_LAMBDA('J', mCfgL, drawRelatedSectors, val ^= 1; SetInfo("Drawing of related sectors %s", mCfgL.drawRelatedSectors ? "enabled" : "disabled");), // + KEY_LAMBDA('J', mCfgL, drawRelatedSectors, val ^= 1;), // KEY_LAMBDA(';', mCfgH, xAdd, { mCfgH.xAdd += 60; mCfgH.zAdd += 60; - SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); }), KEY_LAMBDA(':', mCfgH, xAdd, { mCfgH.xAdd -= 60; @@ -82,7 +74,6 @@ void GPUDisplay::buildConfigKeys() if (mCfgH.zAdd < 0 || mCfgH.xAdd < 0) { mCfgH.zAdd = mCfgH.xAdd = 0; } - SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); }), // "Draw single sector (next / previous sector), draw related sectors (same plane in phi) / Show splitting of TPC in sectors by extruding volume, [:] resets"); @@ -106,23 +97,18 @@ void GPUDisplay::buildConfigKeys() if (val == 7) { val = 0; } - if (val == 6) { - SetInfo("Animation mode %d - Centered on origin", val); - } else { - SetInfo("Animation mode %d - Position: %s, Direction: %s", val, (val & 2) ? "Spherical (spherical rotation)" : (val & 4) ? "Spherical (Euler angles)" : "Cartesian", (val & 1) ? "Euler angles" : "Quaternion"); - } }), // "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)"); mFrontend->add_key_binding( KEY_LAMBDA('#', mCfgL, invertColors, val ^= 1;), - KEY_LAMBDA('g', mCfgL, drawGrid, val ^= 1; SetInfo("Fast Cluster Search Grid %s", mCfgL.drawGrid ? "shown" : "hidden");), - KEY_LAMBDA('i', mCfgH, projectXY, val ^= 1; SetInfo("Projection onto xy plane %s", mCfgH.projectXY ? "enabled" : "disabled");), // + 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_LAMBDA('x', mCfgL, excludeClusters, val = (val + 1) % 3; SetInfo(mCfgL.excludeClusters ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", mCfgL.excludeClusters);), - KEY_LAMBDA('.', mCfgH, hideRejectedTracks, val ^= 1; SetInfo("Rejected tracks are %s", mCfgH.hideRejectedTracks ? "hidden" : "shown");), // + 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( @@ -134,10 +120,8 @@ void GPUDisplay::buildConfigKeys() } else { val <<= 1; } - 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"); }), - KEY_LAMBDA('z', mCfgH, markFakeClusters, val ^= 1; SetInfo("Marking fake clusters: %s", mCfgH.markFakeClusters ? "on" : "off");), // + KEY_LAMBDA('z', mCfgH, markFakeClusters, val ^= 1;), // KEY_LAMBDA('B', mCfgH, markAdjacentClusters, { val++; if (val == 5) { @@ -152,13 +136,6 @@ void GPUDisplay::buildConfigKeys() if (val == 34) { val = 0; } - 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"); - } }), // "Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20 / Mark fake attached clusters / Mark clusters attached as adjacent"); @@ -166,10 +143,8 @@ void GPUDisplay::buildConfigKeys() KEY_LAMBDA('L', mCfgL, showCollision, { if (val >= mNCollissions - 1) { val = -1; - SetInfo("Showing all collisions", 1); } else { val++; - SetInfo("Showing collision %d / %d", val, mNCollissions); } }), KEY_LAMBDA('K', mCfgL, showCollision, { @@ -178,25 +153,19 @@ void GPUDisplay::buildConfigKeys() } else { val--; } - if (val == -1) { - SetInfo("Showing all collisions", 1); - } else { - SetInfo("Showing collision %d / %d", val, mNCollissions); - } }), - KEY_LAMBDA('C', mCfgL, colorCollisions, val ^= 1; SetInfo("Color coding of collisions %s", mCfgL.colorCollisions ? "enabled" : "disabled");), // + KEY_LAMBDA('C', mCfgL, colorCollisions, val ^= 1;), // "Draw single collisions (next / previous) / Colorcode clusters of different collisions"); mFrontend->add_key_binding( - KEY_LAMBDA('v', mCfgH, hideRejectedClusters, val ^= 1; SetInfo("Rejected clusters are %s", mCfgH.hideRejectedClusters ? "hidden" : "shown");), // + KEY_LAMBDA('v', mCfgH, hideRejectedClusters, val ^= 1;), // KEY_LAMBDA('j', mCfgH, separateExtrapolatedTracks, { if (val) { mCfgH.splitCETracks ^= 1; } val ^= 1; - SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", val ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled"); }), - KEY_LAMBDA('u', mCfgH, trackFilter, val = (val + 1) % (mConfig.filterMacros.size() + 1); SetInfo("Track filter: %s", mCfgH.trackFilter == 0 ? "None" : mConfig.filterMacros[mCfgH.trackFilter - 1].c_str());), // + 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( @@ -205,10 +174,8 @@ void GPUDisplay::buildConfigKeys() if (val == 4) { val = 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[val]); }), - KEY_LAMBDA('G', mCfgH, propagateLoopers, val ^= 1; SetInfo("Propagation of loopers %s", mCfgH.propagateLoopers ? "enabled" : "disabled");), // + KEY_LAMBDA('G', mCfgH, propagateLoopers, val ^= 1;), // "Extrapolate tracks / loopers"); mFrontend->add_key_binding( @@ -226,16 +193,14 @@ void GPUDisplay::buildConfigKeys() if (val == 5) { val = 1; } - SetInfo("Screenshot scaling factor set to %d", val); }), // "Take Screenshot / Record Animation to pictures / Change screenshot resolution (scaling factor)"); mFrontend->add_key_binding( - KEY_LAMBDA('S', mCfgL, smoothPoints, val ^= true; SetInfo("Smoothing of points %s", mCfgL.smoothPoints ? "enabled" : "disabled");), // - KEY_LAMBDA('A', mCfgL, smoothLines, val ^= true; SetInfo("Smoothing of lines %s", mCfgL.smoothLines ? "enabled" : "disabled");), // + KEY_LAMBDA('S', mCfgL, smoothPoints, val ^= true;), // + KEY_LAMBDA('A', mCfgL, smoothLines, val ^= true;), // KEY_LAMBDA('D', mCfgL, depthBuffer, { val ^= true; - SetInfo("Depth buffer (z-buffer, %u bits) %s", mBackend->DepthBits(), val ? "enabled" : "disabled"); mBackend->setDepthBuffer(); }), // "Enable or disable smoothing of points / smoothing of lines / depth buffer"); @@ -249,7 +214,6 @@ void GPUDisplay::buildConfigKeys() if (val > 16 || val > mBackend->getMaxMSAA()) { val = 0; } - SetInfo("Multisampling anti-aliasing factor set to %d", val); }), KEY_LAMBDA('U', mCfgR, drawQualityDownsampleFSAA, { val++; @@ -259,50 +223,25 @@ void GPUDisplay::buildConfigKeys() if (val == 5) { val = 0; } - SetInfo("Downsampling anti-aliasing factor set to %d", val); }), - KEY_LAMBDA('V', mCfgR, drawQualityVSync, val ^= true; SetInfo("VSync: %s", mCfgR.drawQualityVSync ? "enabled" : "disabled");), // - KEY_LAMBDA('R', mCfgR, fpsLimit, { - val = (val + 1) % 3; - 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"); - } - }), // + 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('F', mCfgR, fullScreen, val ^= 1; SetInfo("Toggling full screen (%d)", (int32_t)mCfgR.fullScreen);), // - KEY_LAMBDA('_', mCfgR, maximized, val ^= 1; SetInfo("Toggling Maximized window (%d)", (int32_t)mCfgR.maximized);), // + KEY_LAMBDA('F', mCfgR, fullScreen, val ^= 1;), // + KEY_LAMBDA('_', mCfgR, maximized, val ^= 1;), // KEY_LAMBDA('b', mCfgR, fov, { if ((val += 5) > 175) { val = 5; } - SetInfo("Set FOV to %f", val); }), KEY_LAMBDA('m', mCfgR, rendererCompatSmoothing, { - if (displayOpenGLCore && backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - SetInfo("OpenGL compat profile not available, using core profile", 1); - } else { + if (!displayOpenGLCore || backend()->backendType() != GPUDisplayBackend::TYPE_OPENGL) { val ^= 1; - SetInfo("Using renderer path for %s profile", val ? "compat" : "core (shader)"); } }), - KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true; SetInfo("OpenGL Indirect Draw %s", mCfgR.useGLIndirectDraw ? "enabled" : "disabled");), // + KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true;), // "Switch mFullScreen / Maximized window / Change FOV (field of view) / Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines) / Enable / disable GL indirect draw"); mFrontend->add_key_binding( @@ -375,18 +314,18 @@ void GPUDisplay::buildConfigKeys() "Save / restore current camera position / Animation path"); mFrontend->add_key_binding( - KEY_LAMBDA(mFrontend->KEY_F1, mCfgL, drawTPC, val ^= 1; SetInfo("Showing TPC Clusters: %d", (int32_t)mCfgL.drawTPC);), - KEY_LAMBDA(mFrontend->KEY_F2, mCfgL, drawTRD, val ^= 1; SetInfo("Showing TRD Tracklets: %d", (int32_t)mCfgL.drawTRD);), - KEY_LAMBDA(mFrontend->KEY_F3, mCfgL, drawTOF, val ^= 1; SetInfo("Showing TOF Hits: %d", (int32_t)mCfgL.drawTOF);), - KEY_LAMBDA(mFrontend->KEY_F4, mCfgL, drawITS, val ^= 1; SetInfo("Showing ITS Clusters: %d", (int32_t)mCfgL.drawITS);), // + 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; 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);), - KEY_LAMBDA(mFrontend->KEY_F2 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTRDTracks, val ^= 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);), - KEY_LAMBDA(mFrontend->KEY_F3 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTOFTracks, val ^= 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);), - KEY_LAMBDA(mFrontend->KEY_F4 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawITSTracks, val ^= 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);), - KEY_LAMBDA(mFrontend->KEY_F12 + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgH, drawTracksAndFilter, val ^= 1; SetInfo("Track filter: %s", mCfgH.drawTracksAndFilter ? "AND" : "OR");), // + 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]"); @@ -446,12 +385,10 @@ void GPUDisplay::buildConfigKeys() KEY_LAMBDA(mFrontend->KEY_ALT + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgR, camLookOrigin, { val ^= 1; mCfgR.cameraMode = val + 2 * mCfgR.camYUp; - SetInfo("Camera locked on origin: %s", val ? "enabled" : "disabled"); }), KEY_LAMBDA(mFrontend->KEY_CTRL + mFrontend->SPECIAL_KEYS_SHIFTED, mCfgR, camYUp, { val ^= 1; mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * val; - SetInfo("Camera locked on y-axis facing upwards: %s", val ? "enabled" : "disabled"); }), KEY_LAMBDA(mFrontend->KEY_ENTER, mCfgR, cameraMode, { val++; @@ -460,8 +397,6 @@ void GPUDisplay::buildConfigKeys() } mCfgR.camLookOrigin = val & 1; mCfgR.camYUp = val & 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", val, modeText[val]); }), "Focus camera on origin / orient y-axis upwards (combine with [SHIFT] to lock) / Cycle through modes", // "[ALT] / [CTRL] / [ENTER]"); From c8c6f66797e18251450c6a3690d950400bbd6058 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 27 Oct 2026 17:47:02 +0100 Subject: [PATCH 14/41] GPU Display: Unify calling DrawGLScene and handling of events --- GPU/GPUTracking/display/GPUDisplay.cxx | 16 ++++++++++------ GPU/GPUTracking/display/GPUDisplay.h | 8 ++++---- .../display/frontend/GPUDisplayFrontend.cxx | 6 +++++- .../display/frontend/GPUDisplayFrontend.h | 3 ++- .../display/frontend/GPUDisplayFrontendGlfw.cxx | 3 +-- .../display/frontend/GPUDisplayFrontendGlut.cxx | 11 ++++++++--- .../display/frontend/GPUDisplayFrontendGlut.h | 1 + .../display/frontend/GPUDisplayFrontendNone.cxx | 3 +-- .../frontend/GPUDisplayFrontendWayland.cxx | 5 +---- .../frontend/GPUDisplayFrontendWindows.cxx | 12 +++++------- .../display/frontend/GPUDisplayFrontendX11.cxx | 3 +-- .../display/helpers/GPUDisplayAnimation.cxx | 4 ++-- 12 files changed, 41 insertions(+), 34 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 988e0a2509a84..59d169bc33482 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -204,7 +204,7 @@ void GPUDisplay::ExitDisplay() mBackend->ExitBackend(); } -int32_t GPUDisplay::DrawGLScene() +int32_t GPUDisplay::DrawGLScene(bool suspended) { // Make sure event gets not overwritten during display mSemLockDisplay.Lock(); @@ -220,7 +220,7 @@ int32_t GPUDisplay::DrawGLScene() mNCollissions = std::max(1u, mIOPtrs->nMCInfosTPCCol); } try { - DrawGLScene_internal(); + DrawGLScene_internal(suspended); } catch (const std::runtime_error& e) { GPUError("Runtime error %s during display", e.what()); retVal = 1; @@ -238,7 +238,7 @@ 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; @@ -266,7 +266,7 @@ 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 (mCfgC.mResetScene.load()) { const float initialZpos = mCfgH.projectXY ? 16 : (mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR + 8) : 8); @@ -627,7 +627,7 @@ 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 (mCfgC.mRequestScreenshot || mCfgC.mAnimateScreenshot) { const uint32_t factor = std::max(1, mCfgR.drawQualityDownsampleFSAA) * std::max(1, mCfgR.screenshotScaleFactor); @@ -660,7 +660,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,6 +670,10 @@ 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; diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 896fef6f3893b..25fec52afae18 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -60,7 +60,7 @@ class GPUDisplay : public GPUDisplayInterface 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(); + int32_t DrawGLScene(bool suspended = false); void HandleExternalGUIKey(int32_t key); // Does not handle config changes, see HandleKey int32_t InitDisplay(bool initFailure = false); void ResizeScene(int32_t width, int32_t height, bool init = false); @@ -170,9 +170,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(); @@ -195,7 +195,7 @@ class GPUDisplay : public GPUDisplayInterface void resetAnimation(); void removeAnimationPoint(); void startAnimation(); - int32_t animateCamera(float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); + int32_t animateCamera(bool suspended, float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); void showInfo(const char* info); void ActivateColor(); void SetColorTRD(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index af36e54479f04..f4da34fab1534 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -146,7 +146,11 @@ int32_t GPUDisplayFrontend::getDisplayControl() const { return mDisplay->mCfgC.m 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(); + return mDisplay->DrawGLScene(suspended); +} void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 3b26f22eea4c3..dd66c4bd6d085 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -126,8 +126,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface std::unique_ptr mGUIChannel; // Must outlive mGUI std::unique_ptr mGUI; + int32_t HandleUpdatesAndDrawGLScene(bool suspended = false); // Handle external GUI signals and config changes, then draw the scene (suspended: only update, no drawing, e.g. for a hidden window) + void HandleKey(uint8_t key); // Callback for handling key presses - int32_t DrawGLScene(); // Callback to draw the GL scene void HandleExternalGUISignals(); // Callback to handle signals from external GUIs bool HasExternalGUISignals() const; // Returns whether there are currently pending signals from external GUIs to be handled bool processKeyLambda(uint32_t key); // Process key lambda callback diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 336161216b351..442cc7224509a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -322,8 +322,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() int32_t retVal = 0; while (!glfwWindowShouldClose(mWindow)) { - HandleExternalGUISignals(); - if (DrawGLScene()) { + if (HandleUpdatesAndDrawGLScene()) { fprintf(stderr, "Error drawing GL scene\n"); retVal = 1; break; // Cleanup below diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 90391fff43bf0..db8998060117d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -38,15 +38,20 @@ GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, to get rid of the static me variable! { - me->DrawGLScene(); + me->mRedisplayPending = false; + me->HandleUpdatesAndDrawGLScene(); glutSwapBuffers(); me->waitForFPSLimit(); } void GPUDisplayFrontendGlut::glutLoopFunc() { - me->HandleExternalGUISignals(); - displayFunc(); + if (me->mRedisplayPending) { // Last redisplay not served (window not visible): handle updates without drawing + me->HandleUpdatesAndDrawGLScene(true); + me->waitForFPSLimit(); + } + me->mRedisplayPending = true; + glutPostRedisplay(); // Drawing only in displayFunc, so that expose events do not draw twice } int32_t GPUDisplayFrontendGlut::GetKey(int32_t key) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index 9fe8e7b44d018..ca04b093a2589 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -51,6 +51,7 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend 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/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index e0b6f96933612..ce616814e208b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -28,8 +28,7 @@ int32_t GPUDisplayFrontendNone::FrontendMain() return 1; } do { - HandleExternalGUISignals(); - DrawGLScene(); + HandleUpdatesAndDrawGLScene(); waitForFPSLimit(); } while (getDisplayControl() != 2); return 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index efee3ca538973..9f6a70a63faac 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -610,10 +610,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() 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(); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 9a641b5c6ef05..bb72a330bba0b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -350,13 +350,11 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() DispatchMessage(&msg); // Dispatch The Message } } else { - HandleExternalGUISignals(); - if (active) // Program Active? - { - if (mKeys[VK_ESCAPE]) { - done = TRUE; - } else { - DrawGLScene(); // Draw The Scene + if (active && mKeys[VK_ESCAPE]) { + done = TRUE; + } else { + HandleUpdatesAndDrawGLScene(!active); // Only draw if the program is active + if (active) { SwapBuffers(hDC); // Swap Buffers (Double Buffering) waitForFPSLimit(); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 50cdd13fd9d19..e44a5f5e50048 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -452,8 +452,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() break; } - HandleExternalGUISignals(); - DrawGLScene(); + HandleUpdatesAndDrawGLScene(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXSwapBuffers(mXDisplay, mWindow); } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index 7ef55de0113e4..e8c1e16ff9b9e 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -105,7 +105,7 @@ 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) { @@ -152,7 +152,7 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ if (mixSlaveImage > 0) { mCfgL = mAnimateConfig[mAnimationLastBase]; updateConfig(); - DrawGLScene_internal(time, true); + DrawGLScene_internal(suspended, time, true); } } From 2e8bf7f702b1d88e3dce74c8ed2c5f70d4a1618b Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 28 Oct 2026 23:27:40 +0100 Subject: [PATCH 15/41] GPU Display: Clean up full screen / maximized setting, make them idempotent --- .../display/frontend/GPUDisplayFrontend.cxx | 22 +++++++++++++++---- .../display/frontend/GPUDisplayFrontend.h | 6 ++++- .../frontend/GPUDisplayFrontendConfig.cxx | 4 ++-- .../frontend/GPUDisplayFrontendX11.cxx | 4 ++-- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index f4da34fab1534..4c3309cff0834 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -256,8 +256,22 @@ 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() @@ -269,9 +283,9 @@ void GPUDisplayFrontend::focusLost() void GPUDisplayFrontend::applyInitialWindowState() { if (mDisplay->cfgR().fullScreen) { - SwitchFullscreen(true); + requestFullScreen(true); } else if (mDisplay->cfgR().maximized) { - ToggleMaximized(true); + requestMaximized(true); } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index dd66c4bd6d085..b3db83c7e83e9 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -140,13 +140,17 @@ 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: struct configRTTIInfo; struct configRTTIExt; struct configKey; std::unique_ptr mConfigRTTI{nullptr, nullptr}; // Deleter set in GPUDisplayFrontendConfig.cxx, the only place where configRTTIInfo is defined - bool HandleExternalGUIKeys(); // Returns whether keys were handled, config changes are not handled + void HandleExternalGUIKeys(); + bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system template void add_key_binding(Args&&... args); // KEY_LAMBDA(key, ...) entries, the help text, and optionally the keys to show in the help text, only used in GPUDisplayFrontendConfig.cxx void listConfig(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 23070ec813baf..b7d87e87dcbfb 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -141,10 +141,10 @@ void GPUDisplay::checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCf mBackend->SetVSync(mCfgR.drawQualityVSync); } if (oldCfgR.fullScreen != mCfgR.fullScreen) { - mFrontend->SwitchFullscreen(mCfgR.fullScreen); + mFrontend->requestFullScreen(mCfgR.fullScreen); } if (oldCfgR.maximized != mCfgR.maximized) { - mFrontend->ToggleMaximized(mCfgR.maximized); + mFrontend->requestMaximized(mCfgR.maximized); } if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { mUpdateEventData = true; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index e44a5f5e50048..3f0cfcbf77b78 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -510,8 +510,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); } From 653dd56a9ebcc59c20113d64e06ed22070d25d65 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 28 Oct 2026 23:27:54 +0100 Subject: [PATCH 16/41] GPU Display: Cleanup: check for config changes only once --- GPU/GPUTracking/display/GPUDisplay.cxx | 1 + GPU/GPUTracking/display/GPUDisplay.h | 8 +++++--- .../display/frontend/GPUDisplayFrontend.cxx | 14 +++++--------- .../frontend/GPUDisplayFrontendConfig.cxx | 18 ++++++------------ 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 59d169bc33482..4faceadf34ec1 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -177,6 +177,7 @@ int32_t GPUDisplay::InitDisplay(bool initFailure) retVal = 1; } mInitResult = retVal == 0 ? 1 : -1; + mOldCfg = {mCfgH, mCfgL, mCfgR}; return (retVal); } diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 25fec52afae18..905db94448773 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -59,12 +60,12 @@ class GPUDisplay : public GPUDisplayInterface void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; } void UpdateParam(const GPUParam* param) override { mParam = param; } - void HandleKey(uint8_t key, bool doNotHandleConfigChanges = false); // With doNotHandleConfigChanges, the caller must call checkAndApplyConfigChanges + void HandleKey(uint8_t key); int32_t DrawGLScene(bool suspended = false); - void HandleExternalGUIKey(int32_t key); // Does not handle config changes, see HandleKey + 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; } @@ -244,6 +245,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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 4c3309cff0834..aecac676c6349 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -97,10 +97,7 @@ void GPUDisplayFrontend::waitForFPSLimit() void GPUDisplayFrontend::HandleExternalGUISignals() { - const GPUSettingsDisplayHeavy oldCfgH = mDisplay->mCfgH; - const GPUSettingsDisplayLight oldCfgL = mDisplay->mCfgL; - const GPUSettingsDisplayRenderer oldCfgR = mDisplay->mCfgR; - bool changes = HandleExternalGUIKeys(); + HandleExternalGUIKeys(); const bool gui = isGUIRunning(); std::optional update; @@ -116,12 +113,8 @@ void GPUDisplayFrontend::HandleExternalGUISignals() if (update->lineWidth) { mDisplay->mCfgL.lineWidth = *update->lineWidth; } - changes = true; } - if (changes) { - mDisplay->checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } if (gui) { { std::lock_guard lock(mGUIChannel->mutex); @@ -149,7 +142,10 @@ void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } int32_t GPUDisplayFrontend::HandleUpdatesAndDrawGLScene(bool suspended) { HandleExternalGUISignals(); - return mDisplay->DrawGLScene(suspended); + mDisplay->checkAndApplyConfigChanges(); + const int32_t retVal = mDisplay->DrawGLScene(suspended); + mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + return retVal; } void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index b7d87e87dcbfb..e068e5865714a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -121,8 +121,9 @@ namespace #include "utils/qconfig_genrtti.inc" } // namespace -void GPUDisplay::checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCfgH, const GPUSettingsDisplayLight& oldCfgL, const GPUSettingsDisplayRenderer& oldCfgR) +void GPUDisplay::checkAndApplyConfigChanges() { + const auto& [oldCfgH, oldCfgL, oldCfgR] = mOldCfg; if (memcmp((void*)&oldCfgH, (void*)&mCfgH, sizeof(mCfgH)) != 0) { mUpdateEventData = true; } @@ -164,7 +165,7 @@ void GPUDisplay::HandleExternalGUIKey(int32_t key) bool shifted = key >= 'A' && key <= 'Z'; bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; - HandleKey(key, true); + HandleKey(key); mFrontend->mKeys[mFrontend->KEY_SHIFT] = oldShift; } @@ -173,13 +174,13 @@ bool GPUDisplayFrontend::HasExternalGUISignals() const return mExternalGUIKeys.size(); } -bool GPUDisplayFrontend::HandleExternalGUIKeys() +void GPUDisplayFrontend::HandleExternalGUIKeys() { std::lock_guard lock(mExternalGUIKeyMutex); for (auto key : mExternalGUIKeys) { mDisplay->HandleExternalGUIKey(key); } - return !std::exchange(mExternalGUIKeys, {}).empty(); + mExternalGUIKeys.clear(); } template @@ -360,17 +361,10 @@ bool GPUDisplayFrontend::processKeyLambda(uint32_t key) return false; } -void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) +void GPUDisplay::HandleKey(uint8_t key) { - const GPUSettingsDisplayHeavy oldCfgH = mCfgH; - const GPUSettingsDisplayLight oldCfgL = mCfgL; - const GPUSettingsDisplayRenderer oldCfgR = mCfgR; 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)) { } - - if (!doNotHandleConfigChanges) { - checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } } From f4413412ce33324fe27d0bf07fe4a0605d69d8ea Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 28 Oct 2026 23:32:54 +0100 Subject: [PATCH 17/41] GPU Display backend glut: support maximizing the window --- .../frontend/GPUDisplayFrontendGlut.cxx | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index db8998060117d..7ae870dd97355 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -24,6 +24,7 @@ #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include #include +#include #endif #include @@ -187,6 +188,24 @@ void GPUDisplayFrontendGlut::ResizeSceneWrapper(int32_t width, int32_t height) me->mHeight = height; } me->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); + me->setWindowState(me->mFullScreen, isMaxHorz && isMaxVert); +#endif } void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y) @@ -313,7 +332,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 From c260ddf233d1ebf327488d4516b77748b72b93dc Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 28 Oct 2026 23:48:05 +0100 Subject: [PATCH 18/41] GPU Display: Modernize GLUT backend: Use callbacks with user-data for this ptr, update key constants --- GPU/GPUTracking/display/CMakeLists.txt | 16 ++ .../frontend/GPUDisplayFrontendGlut.cxx | 242 ++++++++---------- .../display/frontend/GPUDisplayFrontendGlut.h | 18 +- 3 files changed, 125 insertions(+), 151 deletions(-) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 592483293303c..b96862db705cf 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) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 7ae870dd97355..2f2dfb204fa50 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include @@ -29,7 +30,6 @@ #include using namespace o2::gpu; -static GPUDisplayFrontendGlut* me = nullptr; GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() { @@ -37,103 +37,83 @@ GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() mFrontendName = "GLUT"; } -void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, to get rid of the static me variable! +void GPUDisplayFrontendGlut::displayFunc() { - me->mRedisplayPending = false; - me->HandleUpdatesAndDrawGLScene(); + mRedisplayPending = false; + HandleUpdatesAndDrawGLScene(); glutSwapBuffers(); - me->waitForFPSLimit(); + waitForFPSLimit(); } void GPUDisplayFrontendGlut::glutLoopFunc() { - if (me->mRedisplayPending) { // Last redisplay not served (window not visible): handle updates without drawing - me->HandleUpdatesAndDrawGLScene(true); - me->waitForFPSLimit(); + if (mRedisplayPending) { // Last redisplay not served (window not visible): handle updates without drawing + HandleUpdatesAndDrawGLScene(true); + waitForFPSLimit(); } - me->mRedisplayPending = true; + 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; + 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_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; - } - 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) @@ -151,43 +131,23 @@ void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPr } } -void GPUDisplayFrontendGlut::keyboardDownFunc(uint8_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, false); - me->mKeys[keyPress] = true; - me->HandleKey(handleKey); -} - -void GPUDisplayFrontendGlut::keyboardUpFunc(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->mKeys[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->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; + GetKey(key, handleKey, keyPress, special); + mKeys[keyPress] = down; + if (down) { + HandleKey(handleKey); + } } void GPUDisplayFrontendGlut::ResizeSceneWrapper(int32_t width, int32_t height) { - if (!me->mFullScreen) { - me->mWidth = width; - me->mHeight = height; + if (!mFullScreen) { + mWidth = width; + mHeight = height; } - me->ResizeScene(width, height); + ResizeScene(width, height); #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 Display* display = glXGetCurrentDisplay(); // Maximizing / unmaximizing by the window manager changes the size, read the state as freeglut does not report it (fullscreen stays as requested, freeglut tracks it itself) Atom type; @@ -204,44 +164,44 @@ void GPUDisplayFrontendGlut::ResizeSceneWrapper(int32_t width, int32_t height) isMaxVert |= ((Atom*)data)[i] == maxVert; } XFree(data); - me->setWindowState(me->mFullScreen, isMaxHorz && isMaxVert); + 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() { @@ -249,7 +209,6 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Only OpenGL backend supported\n"); return 1; } - me = this; mCanDrawText = 1; if (drawTextFontSize() == 0) { drawTextFontSize() = 12; @@ -276,17 +235,18 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() return (1); } - glutDisplayFunc(displayFunc); - glutIdleFunc(glutLoopFunc); - glutReshapeFunc(ResizeSceneWrapper); - glutKeyboardFunc(keyboardDownFunc); - glutKeyboardUpFunc(keyboardUpFunc); - glutSpecialFunc(specialDownFunc); - glutSpecialUpFunc(specialUpFunc); - glutMouseFunc(mouseFunc); - glutMotionFunc(mouseMoveFunc); - glutPassiveMotionFunc(mouseMoveFunc); - glutMouseWheelFunc(mMouseWheelFunc); + auto mouseMoveFunc = [](int32_t x, int32_t y, void* p) { self(p)->mouseMoveFunc(x, y); }; + glutDisplayFuncUcall([](void* p) { self(p)->displayFunc(); }, this); // Callbacks with this as user data, freeglut >= 3.2 + glutIdleFuncUcall([](void* p) { self(p)->glutLoopFunc(); }, this); + glutReshapeFuncUcall([](int32_t width, int32_t height, void* p) { self(p)->ResizeSceneWrapper(width, height); }, this); + glutKeyboardFuncUcall([](uint8_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, false, true); }, this); + glutKeyboardUpFuncUcall([](uint8_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, false, false); }, this); + glutSpecialFuncUcall([](int32_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, true, true); }, this); + glutSpecialUpFuncUcall([](int32_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, true, false); }, this); + glutMouseFuncUcall([](int32_t button, int32_t state, int32_t x, int32_t y, void* p) { self(p)->mouseFunc(button, state, x, y); }, this); + glutMotionFuncUcall(mouseMoveFunc, this); + glutPassiveMotionFuncUcall(mouseMoveFunc, this); + glutMouseWheelFuncUcall([](int32_t button, int32_t dir, int32_t x, int32_t y, void* p) { self(p)->mMouseWheelFunc(button, dir, x, y); }, this); applyInitialWindowState(); SetVSync(vsyncEnabled()); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index ca04b093a2589..eb6d4a220d0ce 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -35,16 +35,14 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend 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); From 9d94db0aa976bcb0c219d080496b944001a1c89e Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 04:05:35 +0100 Subject: [PATCH 19/41] GPU Display Frontend: Use condition_variable to signal stop and config updates instead of busy loop / sleep --- .../display/frontend/GPUDisplayFrontend.cxx | 43 ++++++++++++------- .../display/frontend/GPUDisplayFrontend.h | 8 +++- .../frontend/GPUDisplayFrontendGlfw.cxx | 1 + .../frontend/GPUDisplayFrontendGlut.cxx | 1 + .../frontend/GPUDisplayFrontendNone.cxx | 10 +++++ .../frontend/GPUDisplayFrontendWayland.cxx | 1 + .../frontend/GPUDisplayFrontendX11.cxx | 1 + .../display/frontend/GPUDisplayGUI.cxx | 6 ++- .../display/frontend/GPUDisplayGUIChannel.h | 2 + 9 files changed, 56 insertions(+), 17 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index aecac676c6349..0db41feae33e9 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -44,7 +44,6 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include #include #include -#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -87,16 +86,30 @@ void GPUDisplayFrontend::waitForFPSLimit() 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() { + { + 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(); @@ -131,8 +144,11 @@ void GPUDisplayFrontend::HandleExternalGUISignals() void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) { - std::lock_guard lock(mExternalGUIKeyMutex); - mExternalGUIKeys.emplace_back(v); + { + std::lock_guard lock(mExternalGUIKeyMutex); + mExternalGUIKeys.emplace_back(v); + } + signalConfigUpdate(); } int32_t GPUDisplayFrontend::getDisplayControl() const { return mDisplay->mCfgC.mDisplayControl.load(); } @@ -177,6 +193,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()) { @@ -319,17 +336,13 @@ int32_t GPUDisplayFrontend::StartDisplay() void GPUDisplayFrontend::StopDisplay() { - { - std::lock_guard lock(mExitStateMutex); - if (mDisplayRunning) { - mDisplay->mCfgC.mDisplayControl.store(2); - StopDisplayA(); - mDisplay->UnblockDisplay(); - } - } - while (mDisplayRunning) { - std::this_thread::sleep_for(std::chrono::microseconds(10000)); // TODO: Signal by semaphore + std::unique_lock lock(mExitStateMutex); + if (mDisplayRunning) { + mDisplay->mCfgC.mDisplayControl.store(2); + StopDisplayA(); + mDisplay->UnblockDisplay(); } + mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); } void GPUDisplayFrontend::StopDisplayA() {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index b3db83c7e83e9..a18920ae0acb9 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -84,7 +85,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual int32_t FrontendMain() = 0; virtual void StopDisplayA(); static void* FrontendThreadWrapper(void*); - void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode) + void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode), or until an external config update is signalled + void signalConfigUpdate(); // Signal a config update from an external source (console keys, Qt GUI), wakes up waitForFPSLimit static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing static constexpr int32_t MIN_WIDTH = 200, MIN_HEIGHT = 150; // Minimum window size @@ -99,7 +101,11 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface std::mutex mExternalGUIKeyMutex; std::vector mExternalGUIKeys; // Keys sent by external entity (usually console), may be ignored by backend. + std::mutex mConfigUpdateMutex; + std::condition_variable mConfigUpdateCV; // Notified by signalConfigUpdate + bool mConfigUpdatePending = false; // Protected by mConfigUpdateMutex, set by signalConfigUpdate, cleared by HandleExternalGUISignals std::mutex mExitStateMutex; + std::condition_variable mDisplayStoppedCV; // Notified with mExitStateMutex held when the frontend sets mDisplayRunning to false, StopDisplay waits for it std::array mKeyDownMap = {}; // Keyboard / Mouse actions diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 442cc7224509a..255ba11828be1 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -347,6 +347,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwDestroyWindow(mWindow); glfwTerminate(); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return retVal; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 2f2dfb204fa50..0d64a7f79d40a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -259,6 +259,7 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() std::lock_guard lock(mExitStateMutex); setDisplayControl(2); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } ExitDisplay(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index ce616814e208b..f195fce1977ce 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -27,9 +27,19 @@ int32_t GPUDisplayFrontendNone::FrontendMain() if (InitDisplay()) { return 1; } + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } do { HandleUpdatesAndDrawGLScene(); waitForFPSLimit(); } while (getDisplayControl() != 2); + ExitDisplay(); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); + } return 0; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 9f6a70a63faac..5d1d175a14838 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -626,6 +626,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } if (mCursorShapeDevice) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 3f0cfcbf77b78..e90a3062a54ce 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -476,6 +476,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return (0); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 93a5caa2fa4dd..c2e98b4e6e754 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -65,7 +65,8 @@ void GPUDisplayGUI::timerTick() } } const bool apply = newState && mUpdate.empty() && mState.appliedUpdate == mLastSentId; // State contains all our updates, and we have no unsent changes - if (!mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead) { + const bool post = !mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead; + if (post) { if (mChannel.update) { // Previous update not yet picked up by the display, merge auto& update = *mChannel.update; update.keys.insert(update.keys.end(), mUpdate.keys.begin(), mUpdate.keys.end()); @@ -81,6 +82,9 @@ void GPUDisplayGUI::timerTick() } lock.unlock(); + if (post) { + mChannel.updatePosted(); + } if (apply) { applySettings(); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h index cf24951477d0a..0ee36a4ec213e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ 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::function updatePosted; // Set by the display, called by the GUI (without holding mutex) after posting an update, wakes up the display }; } // namespace o2::gpu From 37d5fc02521eb61326f6d5ed602795c0329fbab8 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 04:32:06 +0100 Subject: [PATCH 20/41] GPU Wayland Display: Fix handling of F keys --- GPU/GPUTracking/display/CMakeLists.txt | 6 +-- .../frontend/GPUDisplayFrontendWayland.cxx | 23 ++---------- .../frontend/GPUDisplayFrontendWayland.h | 1 + .../frontend/GPUDisplayFrontendWaylandEv.cxx | 37 +++++++++++++++++++ 4 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index b96862db705cf..4d242b5a0930e 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -167,10 +167,10 @@ if(GPUCA_EVENT_DISPLAY_WAYLAND) list(APPEND WAYLAND_PROTOCOL_SOURCES ${source}) endforeach() - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) - set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) + set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() if(X11_FOUND) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 5d1d175a14838..900af8061b587 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; @@ -95,7 +94,7 @@ 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_F1) { // TODO: Fix F* keys not working + } else if (sym == XKB_KEY_F1) { return (KEY_F1); } else if (sym == XKB_KEY_F2) { return (KEY_F2); @@ -174,24 +173,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(); }; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index bd55aa53f4b55..068135eb2b725 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -52,6 +52,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; + } +} From 573edd20ac6f22ea15734e404fe0c80f8de976bc Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 04:47:40 +0100 Subject: [PATCH 21/41] GPU Display: Drop / implement some simple TODOs --- .../display/backend/GPUDisplayBackend.cxx | 11 ++-- .../backend/GPUDisplayBackendOpenGL.cxx | 1 - .../backend/GPUDisplayBackendVulkan.cxx | 66 ++++++++++++------- .../display/backend/GPUDisplayBackendVulkan.h | 8 ++- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index c1e39311b1768..bb8d1ac8176fc 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -163,13 +163,16 @@ std::vector GPUDisplayBackend::getPixels() void GPUDisplayBackend::fillIndirectCmdBuffer() { - mCmdBuffer.clear(); mIndirectSectorOffset.resize(GPUTPCGeometry::NSECTORS); - // TODO: Check if this can be parallelized + size_t total = 0; + for (uint32_t iSector = 0; iSector < GPUTPCGeometry::NSECTORS; iSector++) { + mIndirectSectorOffset[iSector] = total; + total += mDisplay->vertexBufferStart()[iSector].size(); + } + mCmdBuffer.resize(total); for (uint32_t iSector = 0; iSector < GPUTPCGeometry::NSECTORS; iSector++) { - mIndirectSectorOffset[iSector] = mCmdBuffer.size(); for (uint32_t k = 0; k < mDisplay->vertexBufferStart()[iSector].size(); k++) { - mCmdBuffer.emplace_back(mDisplay->vertexBufferCount()[iSector][k], 1, mDisplay->vertexBufferStart()[iSector][k], 0); + mCmdBuffer[mIndirectSectorOffset[iSector] + k] = DrawArraysIndirectCommand(mDisplay->vertexBufferCount()[iSector][k], 1, mDisplay->vertexBufferStart()[iSector][k], 0); } } } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index d5342e0ab5abb..91b431a7d07c6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -459,7 +459,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)); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index b0d63bfda17cf..7a9a95996b3ce 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -617,7 +617,7 @@ void GPUDisplayBackendVulkan::clearDevice() // ---------------------------- VULKAN COMMAND BUFFERS ---------------------------- -void GPUDisplayBackendVulkan::createCommandBuffers() +void GPUDisplayBackendVulkan::createCommandBuffersForImages() { vk::CommandBufferAllocateInfo allocInfo{}; allocInfo.commandPool = mCommandPool; @@ -632,7 +632,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 +648,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 +683,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 +696,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 +715,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 +732,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 +832,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; } @@ -1397,8 +1413,10 @@ void GPUDisplayBackendVulkan::ExitBackendA() clearOffscreenBuffers(); clearSwapChain(); if (mCommandInfrastructureCreated) { + clearSemaphoresForImages(); clearSemaphores(); - clearCommandBuffers(); + clearCommandBuffersForImages(); + clearUniformLayoutsAndBuffersForImages(); clearUniformLayoutsAndBuffers(); } clearTextureSampler(); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 4a621edc096d0..3e76e0a2c8360 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -116,17 +116,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(); From e44b44f0913b43a4ca463a1f9cf2da66052820a2 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 05:09:12 +0100 Subject: [PATCH 22/41] GPU Display: Fix creating unrequested screenshot, when enabling saving of an animation without running an animation --- GPU/GPUTracking/display/GPUDisplay.cxx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 4faceadf34ec1..3e57de651e1c5 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -638,7 +638,9 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re mCfgC.mAnimateScreenshot = 0; } } - bool doScreenshot = (mCfgC.mRequestScreenshot || mCfgC.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 || mCfgC.mResetScene.load() || mUpdateVertexLists) && mIOPtrs) { @@ -680,7 +682,7 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re const float zFar = ((mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR) : 8.f) + 50.f) * 2.f; 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, doScreenshot || mCfgC.mRequestScreenshot, renderToMixBuffer, mixSlaveImage)) { + if (!mBackend->prepareDraw(proj, nextViewMatrix, screenshotFrame, renderToMixBuffer, mixSlaveImage)) { return; // No image available (e.g. hidden window), retry with the next frame } mBackend->pointSizeFactor(1); @@ -726,15 +728,15 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re } } - mBackend->finishFrame(mCfgC.mRequestScreenshot, renderToMixBuffer, mixSlaveImage); + mBackend->finishFrame(doScreenshot, renderToMixBuffer, mixSlaveImage); if (doScreenshot) { mCfgC.mRequestScreenshot = false; std::vector pixels = mBackend->getPixels(); char tmpFileName[48]; - if (mCfgC.mAnimateScreenshot) { + if (animateScreenshot) { snprintf(tmpFileName, 48, "mAnimation%d_%05d.bmp", mAnimationExport, mAnimationFrame); } - DoScreenshot(mCfgC.mAnimateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); + DoScreenshot(animateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); } } From 89d46eaeb543045ab6237b3a0d4f5300f227f673 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 05:23:53 +0100 Subject: [PATCH 23/41] GPU Display Frontend: auto-generate info message for key-press if none given --- .../display/frontend/GPUDisplayFrontend.h | 1 + .../frontend/GPUDisplayFrontendConfig.cxx | 76 +++++++++++-------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index a18920ae0acb9..222b69f315c8f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -138,6 +138,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index e068e5865714a..7e2299eac84d6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -86,6 +86,27 @@ struct GPUDisplayFrontend::configRTTIInfo { std::apply([&](auto... s) { ((retVal = i++ == index ? reinterpret_cast(&(display.*s)) : retVal), ...); }, configStructs); return retVal; } + std::optional valueString(GPUDisplay& display, uint32_t index) const // Current value of an option, printed with print_type as in qconfig.cxx, nullopt for unsupported types + { + const auto& e = rttiEntries[index]; + const char* ptr = configStructPtr(display, e.configStructIndex) + e.offset; + if (e.isVector || e.arraySize) { + return std::nullopt; + } else if (e.type == "bool") { + return qConfig::print_type(*(const bool*)ptr); + } else if (e.type == "float") { + return qConfig::print_type(*(const float*)ptr); + } else if (e.type == "int8_t") { + return qConfig::print_type(*(const int8_t*)ptr); + } else if (e.type == "uint8_t") { + return qConfig::print_type(*(const uint8_t*)ptr); + } else if (e.type == "int32_t" || e.type == "int") { + return qConfig::print_type(*(const int32_t*)ptr); + } else if (e.type == "uint32_t") { + return qConfig::print_type(*(const uint32_t*)ptr); + } + return std::nullopt; + } template uint32_t getRTTIIndex(GPUDisplay& display, M T::* member) // RTTI index of a direct member of a config struct { @@ -306,41 +327,24 @@ void GPUDisplay::buildConfig() buildConfigKeys(); rtti->configInfos.resize(rtti->rttiEntries.size()); - auto messageInfo = [this](const std::string& message, const T* value) { // Info from the qConfig message, value printed with print_type as in qconfig.cxx - return configKeyCallback([this, message, value]() { - char buffer[256]; - const int32_t n = snprintf(buffer, sizeof(buffer), message.c_str(), qConfig::print_type(*value).c_str()); - if (n < 0 || n >= (int32_t)sizeof(buffer)) { - GPUError("Info message too long: %s", message.c_str()); - return; - } - SetInfo("%s", buffer); - }); - }; - for (uint32_t i = 0; i < rtti->rttiEntries.size(); i++) { + 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() || e.isVector || e.arraySize) { + if (e.message.empty()) { continue; } - const char* ptr = GPUDisplayFrontend::configRTTIInfo::configStructPtr(*this, e.configStructIndex) + e.offset; - configKeyCallback info; - if (e.type == "bool") { - info = messageInfo(e.message, (const bool*)ptr); - } else if (e.type == "float") { - info = messageInfo(e.message, (const float*)ptr); - } else if (e.type == "int8_t") { - info = messageInfo(e.message, (const int8_t*)ptr); - } else if (e.type == "uint8_t") { - info = messageInfo(e.message, (const uint8_t*)ptr); - } else if (e.type == "int32_t" || e.type == "int") { - info = messageInfo(e.message, (const int32_t*)ptr); - } else if (e.type == "uint32_t") { - info = messageInfo(e.message, (const uint32_t*)ptr); - } else { + 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] = std::move(info); + 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(); rtti->buildHelpText(); @@ -353,14 +357,24 @@ bool GPUDisplayFrontend::processKeyLambda(uint32_t key) } 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() && mConfigRTTI->configInfos[index]) { - mConfigRTTI->configInfos[index](); + if (const uint32_t index = mConfigRTTI->configKeys[key].configRTTIIndex; index < mConfigRTTI->configInfos.size()) { + showConfigInfo(index); } return true; } return false; } +void GPUDisplayFrontend::showConfigInfo(uint32_t index) +{ + if (mConfigRTTI->configInfos[index]) { + mConfigRTTI->configInfos[index](); + } else if (const auto value = mConfigRTTI->valueString(*mDisplay, index)) { // Generic info for options without info callback + const auto& e = mConfigRTTI->rttiEntries[index]; + mDisplay->SetInfo("%s.%s changed to %s", mConfigRTTI->configStructNames[e.configStructIndex].c_str(), e.name.c_str(), value->c_str()); + } +} + void GPUDisplay::HandleKey(uint8_t key) { if (key == mFrontend->KEY_ALT) { From 8ef27cb8f8142c62b41cf51b0e49a533143847da Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 16:06:31 +0100 Subject: [PATCH 24/41] GPU Display: Order key bindings for better grouping in the help text --- .../frontend/GPUDisplayFrontendConfigKeys.inc | 318 +++++++++--------- 1 file changed, 158 insertions(+), 160 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index 62dbe363d38d5..a981576edcc2b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -20,18 +20,22 @@ 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_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]"); + KEY_DOWN_MAP('w', FORWARD), + KEY_DOWN_MAP('s', BACKWARD), + KEY_DOWN_MAP('a', STRAFE_LEFT), + KEY_DOWN_MAP('d', STRAFE_RIGHT), // + "Zoom / Strafe Left and Right"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_PAGEUP, STRAFE_UP), + KEY_DOWN_MAP(mFrontend->KEY_PAGEDOWN, STRAFE_DOWN), + KEY_DOWN_MAP('e', ROTATE_LEFT), + KEY_DOWN_MAP('f', ROTATE_RIGHT), // + "Strafe up / down / Rotate left / right"); mFrontend->add_key_binding( KEY_LAMBDA(mFrontend->KEY_ESCAPE, mCfgC, mDisplayControl, val.store(2); SetInfo("Exiting", 1);), @@ -48,6 +52,99 @@ void GPUDisplay::buildConfigKeys() }), // "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('m', mCfgR, rendererCompatSmoothing, { + if (!displayOpenGLCore || backend()->backendType() != GPUDisplayBackend::TYPE_OPENGL) { + val ^= 1; + } + }), + KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true;), // + "Switch mFullScreen / Maximized window / Change FOV (field of view) / Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines) / 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))) { @@ -77,35 +174,6 @@ void GPUDisplay::buildConfigKeys() }), // "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('y', mCfgC, dummy, { - if (mAnimateVectors[0].size() > 1) { - if (mCfgC.mAnimateScreenshot) { - mAnimationExport++; - } - startAnimation(); - SetInfo("Starting Animation", 1); - } else { - SetInfo("Insufficient Animation points to start Animation", 1); - } - }), - KEY_LAMBDA('Y', mCfgC, dummy, setAnimationPoint(); SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimateVectors[0].size(), mAnimateVectors[0].back());), // - KEY_LAMBDA('\'', mCfgC, dummy, removeAnimationPoint(); SetInfo("Removed Animation point", 1);), // - KEY_LAMBDA('X', mCfgC, dummy, resetAnimation(); SetInfo("Reset Animation points", 1);), // - KEY_LAMBDA('M', mCfgL, animationMode, { - val++; - if (val == 7) { - val = 0; - } - }), // - "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)"); - - mFrontend->add_key_binding( - KEY_LAMBDA('#', 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_LAMBDA('x', mCfgL, excludeClusters, val = (val + 1) % 3;), KEY_LAMBDA('.', mCfgH, hideRejectedTracks, val ^= 1;), // @@ -139,24 +207,6 @@ void GPUDisplay::buildConfigKeys() }), // "Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20 / Mark fake attached clusters / Mark clusters attached as adjacent"); - mFrontend->add_key_binding( - KEY_LAMBDA('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('v', mCfgH, hideRejectedClusters, val ^= 1;), // KEY_LAMBDA('j', mCfgH, separateExtrapolatedTracks, { @@ -178,6 +228,24 @@ void GPUDisplay::buildConfigKeys() KEY_LAMBDA('G', mCfgH, propagateLoopers, val ^= 1;), // "Extrapolate tracks / loopers"); + mFrontend->add_key_binding( + KEY_LAMBDA('L', mCfgL, showCollision, { + if (val >= mNCollissions - 1) { + val = -1; + } else { + val++; + } + }), + KEY_LAMBDA('K', mCfgL, showCollision, { + if (val <= -1) { + val = mNCollissions - 1; + } else { + val--; + } + }), + KEY_LAMBDA('C', mCfgL, colorCollisions, val ^= 1;), // + "Draw single collisions (next / previous) / Colorcode clusters of different collisions"); + mFrontend->add_key_binding( KEY_LAMBDA('t', mCfgC, mRequestScreenshot, { static int32_t nScreenshot = 1; @@ -197,52 +265,32 @@ void GPUDisplay::buildConfigKeys() "Take Screenshot / Record Animation to pictures / Change screenshot resolution (scaling factor)"); 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('y', mCfgC, dummy, { + if (mAnimateVectors[0].size() > 1) { + if (mCfgC.mAnimateScreenshot) { + mAnimationExport++; + } + startAnimation(); + SetInfo("Starting Animation", 1); + } else { + SetInfo("Insufficient Animation points to start Animation", 1); } }), - KEY_LAMBDA('U', mCfgR, drawQualityDownsampleFSAA, { + 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 == 1) { - val = 2; - } - if (val == 5) { + if (val == 7) { 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)"); + }), // + "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)"); 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('m', mCfgR, rendererCompatSmoothing, { - if (!displayOpenGLCore || backend()->backendType() != GPUDisplayBackend::TYPE_OPENGL) { - val ^= 1; - } - }), - KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true;), // - "Switch mFullScreen / Maximized window / Change FOV (field of view) / Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines) / Enable / disable GL indirect draw"); + KEY_LAMBDA('>', mCfgC, mAnimationChangeConfig, val ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled");), + KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // + "Toggle config interpolation during Animation / change Animation interval (via movement)"); mFrontend->add_key_binding( KEY_LAMBDA('o', mCfgC, dummy, { @@ -313,6 +361,19 @@ void GPUDisplay::buildConfigKeys() }), // "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;), @@ -349,69 +410,6 @@ void GPUDisplay::buildConfigKeys() SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled"); }), // "Print Help / Show info texts"); - - 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), // - "Strafe up / down"); - - mFrontend->add_key_binding( - KEY_DOWN_MAP('e', ROTATE_LEFT), - KEY_DOWN_MAP('f', ROTATE_RIGHT), // - "Rotate left / right"); - - 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('+', INCREASE_WIDTH), - KEY_DOWN_MAP('-', DECREASE_WIDTH), // - "Increase / decrease point size (Hold SHIFT for lines)"); - - 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('>', mCfgC, mAnimationChangeConfig, val ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled");), - KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // - "Toggle config interpolation during Animation / change Animation interval (via movement)"); - - mFrontend->mConfigRTTI->helpTextEntries.emplace_back("[MOUSE 1] / [MOUSE 2] / [MOUSE 1+2] / [SHIFT]", "Look around / Strafe camera / Zoom / Rotate / Slow Zoom / Move / Rotate"); } #endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC From 76fd07d709ee27bb47774fe7cf56cdb2e0f93ba5 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 31 Oct 2026 05:45:09 +0100 Subject: [PATCH 25/41] GPU Display: Drop obsolete compat profile for OpenGL --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 1 - GPU/GPUTracking/display/CMakeLists.txt | 2 +- GPU/GPUTracking/display/GPUDisplay.cxx | 2 - GPU/GPUTracking/display/GPUDisplay.h | 2 - .../display/backend/GPUDisplayBackend.h | 3 - .../display/backend/GPUDisplayBackendNone.h | 1 - .../backend/GPUDisplayBackendOpenGL.cxx | 202 ++++-------------- .../display/backend/GPUDisplayBackendOpenGL.h | 8 +- .../backend/GPUDisplayBackendVulkan.cxx | 54 ++--- .../display/backend/GPUDisplayBackendVulkan.h | 6 - .../display/frontend/GPUDisplayFrontend.h | 2 - .../frontend/GPUDisplayFrontendConfig.cxx | 6 - .../frontend/GPUDisplayFrontendConfigInfo.inc | 9 +- .../frontend/GPUDisplayFrontendConfigKeys.inc | 7 +- .../frontend/GPUDisplayFrontendGlfw.cxx | 6 +- .../display/frontend/GPUDisplayFrontendGlfw.h | 1 - .../frontend/GPUDisplayFrontendGlut.cxx | 15 +- .../display/frontend/GPUDisplayFrontendGlut.h | 1 - .../display/frontend/GPUDisplayFrontendNone.h | 1 - .../frontend/GPUDisplayFrontendWayland.cxx | 4 - .../frontend/GPUDisplayFrontendWayland.h | 1 - .../frontend/GPUDisplayFrontendWindows.cxx | 1 - .../frontend/GPUDisplayFrontendWindows.h | 1 - .../frontend/GPUDisplayFrontendX11.cxx | 55 +---- .../display/frontend/GPUDisplayFrontendX11.h | 3 - .../display/helpers/GPUDisplayHelpers.cxx | 2 - 26 files changed, 63 insertions(+), 333 deletions(-) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 553f0304abe24..afb40211247d9 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -502,7 +502,6 @@ 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(drawQualityVSync, bool, true, "", 0, "Enable Vertical Sync") diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 4d242b5a0930e..e5674b91d52b2 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -251,7 +251,7 @@ if (OPENGL_GLU_FOUND) endif() if(GPUCA_CONFIG_GL3W) - target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) + target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W) endif() if(ALIGPU_BUILD_TYPE STREQUAL "O2") diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 3e57de651e1c5..8651c054bc034 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) @@ -189,7 +188,6 @@ int32_t GPUDisplay::InitDisplay_internal() return 1; } mYFactor = mBackend->getYFactor(); - mDrawTextInCompatMode = !mBackend->mFreetypeInitialized && mFrontend->mCanDrawText == 1; int32_t height = 0, width = 0; mFrontend->getSize(width, height); if (height == 0 || width == 0) { diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 905db94448773..977bbc0b46b0c 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -85,7 +85,6 @@ class GPUDisplay : public GPUDisplayInterface const GPUParam* param() { return mParam; } const GPUSettingsProcessing& GetProcessingSettings() const { return mProcessingSettings; } GPUDisplayFrontend* frontend() { return mFrontend; } - bool drawTextInCompatMode() const { return mDrawTextInCompatMode; } int32_t& drawTextFontSize() { return mDrawTextFontSize; } struct control { @@ -253,7 +252,6 @@ class GPUDisplay : public GPUDisplayInterface std::mutex mMutexLoadAndShowEvent; std::condition_variable mCVLoadAndShowEvent; - bool mDrawTextInCompatMode = false; int32_t mDrawTextFontSize = 0; int32_t mNDrawCalls = 0; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 38114662714af..4245a16cee303 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -39,8 +39,6 @@ class GPUDisplayBackend virtual ~GPUDisplayBackend(); virtual int32_t ExtInit() { return 0; }; - virtual bool CoreProfile() { return false; }; - virtual uint32_t DepthBits() = 0; typedef std::tuple vboList; @@ -122,7 +120,6 @@ class GPUDisplayBackend vecpod mCmdBuffer; bool mFreetypeInitialized = false; float mFontRasterScale = 1.f; // Display scale the font was rasterized for - bool mFrontendCompatTextDraw = false; 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..8746d5214db82 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 {} diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index 91b431a7d07c6..be9a805c27739 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -66,15 +66,6 @@ bool GPUDisplayBackendOpenGL::canLimitToVSync() const return mDisplay->frontend()->canSetVSync(); } -bool GPUDisplayBackendOpenGL::CoreProfile() -{ -#ifdef GPUCA_DISPLAY_OPENGL_CORE - return true; -#else - return false; -#endif -} - // #define CHKERR(cmd) {cmd;} #define CHKERR(cmd) \ do { \ @@ -86,15 +77,6 @@ bool GPUDisplayBackendOpenGL::CoreProfile() } \ } while (false) -uint32_t GPUDisplayBackendOpenGL::DepthBits() -{ - GLint depthBits = 0; -#ifndef GPUCA_DISPLAY_OPENGL_CORE - glGetIntegerv(GL_DEPTH_BITS, &depthBits); -#endif - return depthBits; -} - void GPUDisplayBackendOpenGL::createFB(GLfb& fb, bool tex, bool withDepth, bool msaa, uint32_t width, uint32_t height) { fb.tex = tex; @@ -185,22 +167,13 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } if (mDisplay->useMultiVBO()) { - if (!mDisplay->cfgR().rendererCompatSmoothing) { // TODO: Remove OpenGL compat profile completely - CHKERR(glBindVertexArray(mVertexArray)); - } + CHKERR(glBindVertexArray(mVertexArray)); CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[iSector])); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); - } else -#endif - { - CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); - glEnableVertexAttribArray(0); - } + CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); + glEnableVertexAttribArray(0); } - const uint32_t smoothProgram = tt == POINTS ? (mUseSmoothPointShader ? mShaderProgramSmoothPoint : 0) : (mUseSmoothLineShader ? mShaderProgramSmoothLine : 0); + const uint32_t smoothProgram = tt == POINTS ? (mDisplay->cfgL().smoothPoints ? mShaderProgramSmoothPoint : 0) : (mDisplay->cfgL().smoothLines ? mShaderProgramSmoothLine : 0); if (smoothProgram) { CHKERR(glUseProgram(smoothProgram)); } @@ -222,19 +195,12 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glColor4f(color[0], color[1], color[2], color[3]); - } else -#endif - { - CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); - if (mUseSmoothPointShader) { - CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); - } - if (mUseSmoothLineShader) { - CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); - } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); + if (mDisplay->cfgL().smoothPoints) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); + } + if (mDisplay->cfgL().smoothLines) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); } } @@ -325,9 +291,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[0])); CHKERR(glGenBuffers(1, &mIndirectId)); CHKERR(glBindBuffer(GL_DRAW_INDIRECT_BUFFER, mIndirectId)); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - CHKERR(glShadeModel(GL_SMOOTH)); // Enable Smooth Shading -#endif setDepthBuffer(); setQuality(); CHKERR(mVertexShader = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertex_vert_spv_start, _binary_opengl_vertex_vert_spv_len)); @@ -493,77 +456,34 @@ bool GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& setFrameBuffer(0); } clearScreen(); - mUseSmoothPointShader = mDisplay->cfgL().smoothPoints && !mDisplay->cfgR().rendererCompatSmoothing; // The compat profile uses GL_POINT_SMOOTH / GL_LINE_SMOOTH - mUseSmoothLineShader = mDisplay->cfgL().smoothLines && !mDisplay->cfgR().rendererCompatSmoothing; -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mUseSmoothPointShader) { // Compatibility context: gl_PointCoord is only defined with point sprites - CHKERR(glEnable(GL_POINT_SPRITE)); - } else { - CHKERR(glDisable(GL_POINT_SPRITE)); - } - if (mDisplay->cfgL().smoothPoints && mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnable(GL_POINT_SMOOTH)); - } else { - CHKERR(glDisable(GL_POINT_SMOOTH)); - } - if (mDisplay->cfgL().smoothLines && mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnable(GL_LINE_SMOOTH)); - } else { - CHKERR(glDisable(GL_LINE_SMOOTH)); - } -#endif CHKERR(glEnable(GL_BLEND)); CHKERR(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); glViewport(0, 0, mRenderWidth, mRenderHeight); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnableClientState(GL_VERTEX_ARRAY)); - CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); - } else -#endif - { - CHKERR(glBindVertexArray(mVertexArray)); - CHKERR(glUseProgram(mShaderProgram)); - CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); - CHKERR(glEnableVertexAttribArray(0)); - } -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glMatrixMode(GL_PROJECTION)); - CHKERR(glLoadMatrixf(&proj.Elements[0][0])); - CHKERR(glMatrixMode(GL_MODELVIEW)); - CHKERR(glLoadMatrixf(&view.Elements[0][0])); - } else -#endif - { - const hmm_mat4 modelViewProj = proj * view; - CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - if (mUseSmoothPointShader) { - CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - } - if (mUseSmoothLineShader) { - CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); - } - if (mMagneticFieldVisualization) { - CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); - } + CHKERR(glBindVertexArray(mVertexArray)); + CHKERR(glUseProgram(mShaderProgram)); + CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); + CHKERR(glEnableVertexAttribArray(0)); + + const hmm_mat4 modelViewProj = proj * view; + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + if (mDisplay->cfgL().smoothPoints) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + } + if (mDisplay->cfgL().smoothLines) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); + } + if (mMagneticFieldVisualization) { + CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); } return true; } void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glDisableClientState(GL_VERTEX_ARRAY)); - } else -#endif - { - CHKERR(glBindVertexArray(0)); - CHKERR(glUseProgram(0)); - } + CHKERR(glBindVertexArray(0)); + CHKERR(glUseProgram(0)); if (mOffscreenBufferMSAA.created) { uint32_t dstId = toMixBuffer ? mMixBuffer.fb_id : (mDownsampleFactor != 1 ? mOffscreenBuffer.fb_id : 0); @@ -616,15 +536,7 @@ void GPUDisplayBackendOpenGL::prepareText() setFrameBuffer(0); glViewport(0, 0, mScreenWidth, mScreenHeight); hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); - if (mDisplay->drawTextInCompatMode()) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&proj.Elements[0][0]); - glViewport(0, 0, mScreenWidth, mScreenHeight); -#endif - } else if (mFreetypeInitialized) { + if (mFreetypeInitialized) { CHKERR(glUseProgram(mShaderProgramText)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_text)); @@ -636,7 +548,7 @@ void GPUDisplayBackendOpenGL::prepareText() void GPUDisplayBackendOpenGL::finishText() { - if (!mDisplay->drawTextInCompatMode() && mFreetypeInitialized) { + if (mFreetypeInitialized) { CHKERR(glBindVertexArray(0)); CHKERR(glBindTexture(GL_TEXTURE_2D, 0)); CHKERR(glUseProgram(0)); @@ -666,55 +578,25 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) { hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1.f, 1.f); glDisable(GL_DEPTH_TEST); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&proj.Elements[0][0]); - CHKERR(glEnable(GL_TEXTURE_2D)); - } else -#endif - { - CHKERR(glUseProgram(mShaderProgramTexture)); - CHKERR(glActiveTexture(GL_TEXTURE0)); - CHKERR(glBindVertexArray(VAO_texture)); - CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); - CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); - } + CHKERR(glUseProgram(mShaderProgramTexture)); + CHKERR(glActiveTexture(GL_TEXTURE0)); + CHKERR(glBindVertexArray(VAO_texture)); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); + CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); CHKERR(glBindTexture(GL_TEXTURE_2D, mMixBuffer.fbCol_id)); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glColor4f(1, 1, 1, mixSlaveImage); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); - glVertex3f(0, 0, 0); - glTexCoord2f(0, 1); - glVertex3f(0, mRenderHeight, 0); - glTexCoord2f(1, 1); - glVertex3f(mRenderWidth, mRenderHeight, 0); - glTexCoord2f(1, 0); - glVertex3f(mRenderWidth, 0, 0); - glEnd(); - glColor4f(1, 1, 1, 0); - CHKERR(glDisable(GL_TEXTURE_2D)); - } else -#endif - { - CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); - CHKERR(glBindVertexArray(0)); - CHKERR(glUseProgram(0)); - } + CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); + CHKERR(glBindVertexArray(0)); + CHKERR(glUseProgram(0)); CHKERR(glBindTexture(GL_TEXTURE_2D, 0)); setDepthBuffer(); } void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) { - const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mUseSmoothPointShader ? SMOOTH_POINT_EXTRA_SIZE : 0.f); - if (mUseSmoothPointShader) { + const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mDisplay->cfgL().smoothPoints ? SMOOTH_POINT_EXTRA_SIZE : 0.f); + if (mDisplay->cfgL().smoothPoints) { CHKERR(glProgramUniform1f(mShaderProgramSmoothPoint, UNIFORM_LOCATION_POINT_SIZE, size)); } CHKERR(glPointSize(size)); @@ -723,7 +605,7 @@ void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) { float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; - if (mUseSmoothLineShader) { + if (mDisplay->cfgL().smoothLines) { width += SMOOTH_LINE_EXTRA_WIDTH; CHKERR(glProgramUniform1f(mShaderProgramSmoothLine, UNIFORM_LOCATION_LINE_WIDTH, width)); width *= 1.4143f; // Wide lines are parallelograms in OpenGL, widen so that the perpendicular width is covered @@ -788,7 +670,7 @@ void GPUDisplayBackendOpenGL::initializeTextDrawing() void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float* color, float scale) { - if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { + if (!mFreetypeInitialized) { return; } CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index f30a22846a1ac..768f046a89664 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); @@ -101,10 +99,8 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mFragmentShaderDownsample; uint32_t mGeometryShader; uint32_t mShaderProgram; - uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints with the shader path (not rendererCompatSmoothing / GL_POINT_SMOOTH) - bool mUseSmoothPointShader = false; - uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines with the shader path (not rendererCompatSmoothing / GL_LINE_SMOOTH) - bool mUseSmoothLineShader = false; + uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints + uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines uint32_t mShaderProgramText; uint32_t mShaderProgramTexture; uint32_t mShaderProgramDownsample; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 7a9a95996b3ce..114966c964136 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{}; @@ -877,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(); } @@ -1112,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; @@ -1133,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; @@ -1146,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; @@ -1163,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; @@ -1471,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; @@ -1514,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) { @@ -1799,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); @@ -1913,7 +1890,7 @@ void GPUDisplayBackendVulkan::clearFontSymbols() void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float* color, float scale) { - if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { + if (!mFreetypeInitialized) { return; } @@ -2042,11 +2019,6 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageView v } } -uint32_t GPUDisplayBackendVulkan::DepthBits() -{ - return 32; -} - bool GPUDisplayBackendVulkan::backendNeedRedraw() { return !mCommandBufferUpToDate[mCurrentImageIndex]; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 3e76e0a2c8360..2949e8cedfeb7 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; @@ -222,12 +218,10 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mStencilSupported = false; float mMaxLineWidth = 1.f, mMaxPointSize = 1.f; bool mStrictLines = false; // Wide lines are rectangles, not parallelograms - bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines bool mFifoLatestReadySupported = false; // Optional present mode FIFO_LATEST_READY (VK_KHR / VK_EXT_present_mode_fifo_latest_ready) vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16; bool mZActive = false; - bool mSmoothLines = false; bool mPresentModeErrorShown = false; bool mPresentModeForVSyncLimit = false; // Swapchain created with FIFO for the refresh rate FPS limit std::vector mPresentFences; // Per swapchain image, signaled when its last present is done (swapchain_maintenance1) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 222b69f315c8f..6953e8c4b20d9 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -61,7 +61,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual void ToggleMaximized(bool set) = 0; // Maximize window virtual void SetVSync(bool enable) = 0; // Enable / disable vsync virtual bool canSetVSync() { return false; } // OpenGL: swap interval can be set - virtual void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) = 0; // Print text on the display (needs the backend to build the font) GPUDisplayBackend* backend(); static GPUDisplayFrontend* getFrontend(const char* type); virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; } @@ -118,7 +117,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface bool mKeys[256] = {false}; // Array of mKeys currently pressed 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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 7e2299eac84d6..8fc2f962df400 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -43,12 +43,6 @@ struct GPUDisplayFrontend::configKey { configKeyCallback callback; }; -#ifdef GPUCA_DISPLAY_OPENGL_CORE -static constexpr bool displayOpenGLCore = true; // OpenGL compat profile not available -#else -static constexpr bool displayOpenGLCore = false; -#endif - struct GPUDisplayFrontend::configRTTIInfo { static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR}; // Config structs with RTTI, as members of GPUDisplay std::vector rttiEntries; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc index 2644f4e87949b..0cbc8a4cdd571 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -73,7 +73,7 @@ void GPUDisplay::buildConfigInfo() 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, %u bits) %s", mBackend->DepthBits(), 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");)); @@ -100,13 +100,6 @@ void GPUDisplay::buildConfigInfo() 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, rendererCompatSmoothing, { - if (displayOpenGLCore && backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - SetInfo("OpenGL compat profile not available, using core profile", 1); - } else { - SetInfo("Using renderer path for %s profile", val ? "compat" : "core (shader)"); - } - })); 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);)); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index a981576edcc2b..8cb8fecec59f2 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -94,13 +94,8 @@ void GPUDisplay::buildConfigKeys() val = 5; } }), - KEY_LAMBDA('m', mCfgR, rendererCompatSmoothing, { - if (!displayOpenGLCore || backend()->backendType() != GPUDisplayBackend::TYPE_OPENGL) { - val ^= 1; - } - }), KEY_LAMBDA('I', mCfgR, useGLIndirectDraw, val ^= true;), // - "Switch mFullScreen / Maximized window / Change FOV (field of view) / Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines) / Enable / disable GL indirect draw"); + "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;), // diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 255ba11828be1..e9b861abd131c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -271,7 +271,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_MIN_VERSION_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_MIN_VERSION_MINOR); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 0); - glfwWindowHint(GLFW_OPENGL_PROFILE, mBackend->CoreProfile() ? GLFW_OPENGL_CORE_PROFILE : GLFW_OPENGL_COMPAT_PROFILE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); } glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Initial size scaled by the content scale on X11 / Win32 mWindow = glfwCreateWindow(INIT_WIDTH, INIT_HEIGHT, DISPLAY_WINDOW_NAME, nullptr, nullptr); @@ -358,10 +358,6 @@ void GPUDisplayFrontendGlfw::StopDisplayA() glfwSetWindowShouldClose(mWindow, true); } -void GPUDisplayFrontendGlfw::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -} - void GPUDisplayFrontendGlfw::SwitchFullscreen(bool set) { GPUInfo("Setting Full Screen %d", (int32_t)set); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 58de6b0990aa7..fafde0ca89cc2 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -31,7 +31,6 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } float getRefreshRate() override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 0d64a7f79d40a..5e79e85b6cf84 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -209,7 +209,6 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Only OpenGL backend supported\n"); return 1; } - mCanDrawText = 1; if (drawTextFontSize() == 0) { drawTextFontSize() = 12; } @@ -220,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); @@ -271,18 +270,6 @@ void GPUDisplayFrontendGlut::StopDisplayA() glutLeaveMainLoop(); } -void GPUDisplayFrontendGlut::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!fromBotton) { - y = mDisplayHeight - y; - } - glColor4f(r, g, b, a); - glRasterPos2f(x, y); - glutBitmapString(GLUT_BITMAP_HELVETICA_12, (const uint8_t*)s); -#endif -} - void GPUDisplayFrontendGlut::SwitchFullscreen(bool set) { mFullScreen = set; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index eb6d4a220d0ce..9d3efac3996d0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -30,7 +30,6 @@ 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; 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 900af8061b587..68177449f06c8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -49,10 +49,6 @@ GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() mFrontendName = "Wayland"; } -void GPUDisplayFrontendWayland::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -} - int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) { int32_t retVal = 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 068135eb2b725..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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index bb72a330bba0b..d71afb3414668 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -368,7 +368,6 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() return (0); } -void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) {} void SwitchFullscreen(bool set) {} void ToggleMaximized(bool set) {} void SetVSync(bool enable) {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h index e28a9f4ced75b..ac38427367dc5 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h @@ -28,7 +28,6 @@ class GPUDisplayFrontendWindows : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; private: int32_t FrontendMain() override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index e90a3062a54ce..6448c83303c0a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -163,28 +163,6 @@ void GPUDisplayFrontendX11::GetKey(XEvent& event, int32_t& keyOut, int32_t& keyP } } -void GPUDisplayFrontendX11::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - if (!fromBotton) { - y = mDisplayHeight - y; - } - glColor4f(r, g, b, a); - glRasterPos2f(x, y); - if (!glIsList(mFontBase)) { - GPUError("print string: Bad display list."); - exit(1); - } else if (s && strlen(s)) { - glPushAttrib(GL_LIST_BIT); - glListBase(mFontBase); - glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte*)s); - glPopAttrib(); - } - } -#endif -} - int32_t GPUDisplayFrontendX11::FrontendMain() { XSetWindowAttributes windowAttributes; @@ -261,7 +239,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, GL_MIN_VERSION_MAJOR, GLX_CONTEXT_MINOR_VERSION_ARB, GL_MIN_VERSION_MINOR, - GLX_CONTEXT_PROFILE_MASK_ARB, mBackend->CoreProfile() ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, None}; glxContext = glXCreateContextAttribsARB(mXDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); } else { @@ -300,31 +278,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() // Receive signal when window closed Atom WM_DELETE_WINDOW = XInternAtom(mXDisplay, "WM_DELETE_WINDOW", False); XSetWMProtocols(mXDisplay, mWindow, &WM_DELETE_WINDOW, 1); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - XFontStruct* font_info = nullptr; - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - // Prepare fonts - mFontBase = glGenLists(256); - if (!glIsList(mFontBase)) { - GPUError("Out of display lists."); - return (-1); - } - const char* f = "fixed"; - font_info = XLoadQueryFont(mXDisplay, f); - if (!font_info) { - GPUError("XLoadQueryFont failed."); - return (-1); - } else { - int32_t first = font_info->min_char_or_byte2; - int32_t last = font_info->max_char_or_byte2; - glXUseXFont(font_info->fid, first, last - first + 1, mFontBase + first); - } - } - mCanDrawText = 1; - if (drawTextFontSize() == 0) { - drawTextFontSize() = 12; - } -#endif if (mBackend->ExtInit()) { fprintf(stderr, "Error initializing backend\n"); @@ -459,12 +412,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() 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); 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/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 99a14a8319c24..37d8e4b5cde56 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -131,7 +131,5 @@ void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, } 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); } } From aecd797f3b2fadba10779587e2a7336f2ec1b235 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 31 Oct 2026 06:01:23 +0100 Subject: [PATCH 26/41] GPU Display: Wrap lines of help text if they do not fit on screen --- GPU/GPUTracking/display/GPUDisplay.h | 2 +- .../display/backend/GPUDisplayBackend.cxx | 3 ++ .../display/backend/GPUDisplayBackend.h | 3 +- .../display/backend/GPUDisplayBackendNone.h | 2 +- .../backend/GPUDisplayBackendOpenGL.cxx | 4 +-- .../display/backend/GPUDisplayBackendOpenGL.h | 2 +- .../backend/GPUDisplayBackendVulkan.cxx | 4 +-- .../display/backend/GPUDisplayBackendVulkan.h | 2 +- .../display/frontend/GPUDisplayHelp.cxx | 28 +++++++++++++++++-- .../display/helpers/GPUDisplayHelpers.cxx | 8 +++--- 10 files changed, 43 insertions(+), 15 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 977bbc0b46b0c..cfdfde0b9c0d9 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -233,7 +233,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; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index bb8d1ac8176fc..2f353ea8b60c2 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -140,6 +140,9 @@ void GPUDisplayBackend::loadFont() return; } const auto& glyph = face->glyph; + if (i == 'M') { + mFontCharWidth = (glyph->advance.x >> 6) * (smoothFont() ? 0.25f : 1.f) / mFontRasterScale; // As in GLPrintText of the backends + } addFontSymbol(i, glyph->bitmap.width, glyph->bitmap.rows, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x, glyph->bitmap.buffer); } initializeTextDrawing(); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 4245a16cee303..411af384221b5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -99,7 +99,7 @@ class GPUDisplayBackend const char* backendName() const { return mBackendName; } virtual void resizeScene(uint32_t width, uint32_t height) {} virtual size_t needMultiVBO() { return 0; } - virtual void OpenGLPrint(const char* s, float x, float y, float* color, float scale) = 0; + virtual void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) = 0; // nCharacters > 0: at most that many characters, otherwise until the terminating 0 static GPUDisplayBackend* getBackend(const char* type); std::vector getPixels(); virtual float getYFactor() const { return 1.0f; } @@ -120,6 +120,7 @@ class GPUDisplayBackend vecpod mCmdBuffer; bool mFreetypeInitialized = false; float mFontRasterScale = 1.f; // Display scale the font was rasterized for + 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 8746d5214db82..825fd9b353aa6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h @@ -39,7 +39,7 @@ class GPUDisplayBackendNone : public GPUDisplayBackend void finishText() override {} void pointSizeFactor(float factor) override {} void lineWidthFactor(float factor) override {} - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override {} + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override {} void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override {} void initializeTextDrawing() override {} }; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index be9a805c27739..f79675cbad8a6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -668,7 +668,7 @@ 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) { return; @@ -678,7 +678,7 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float if (smoothFont()) { scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const unsigned char* c = (const unsigned char*)s; *c; c++) { + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); continue; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index 768f046a89664..f6e9d8801b6d0 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -79,7 +79,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; void clearFontSymbols() override; - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; struct FontSymbolOpenGL : public FontSymbol { uint32_t texId; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 114966c964136..b76fc71de66d1 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1888,7 +1888,7 @@ 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) { return; @@ -1899,7 +1899,7 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const unsigned char* c = (const unsigned char*)s; *c; c++) { + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); continue; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 2949e8cedfeb7..d76fa357bb216 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -136,7 +136,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; void clearFontSymbols() override; - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; uint32_t mGraphicsFamily; SwapChainSupportDetails mSwapChainDetails; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx index 275ac348653d8..73b11a253c0d4 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx @@ -15,6 +15,8 @@ #include "GPUDisplay.h" #include "GPULogging.h" +#include + using namespace o2::gpu; void GPUDisplay::PrintHelp() @@ -28,7 +30,29 @@ void GPUDisplay::PrintHelp() void GPUDisplay::PrintGLHelpText(float colorValue) { const auto& lines = mFrontend->getHelpText(); - for (uint32_t i = 0; i < lines.size(); i++) { - OpenGLPrint(lines[i].c_str(), 40.f, 35 + std::max(20, mDrawTextFontSize + 4) * (1 + i), colorValue, colorValue, colorValue, mInfoHelpTimer.GetCurrentElapsedTime() >= 5 ? (6 - mInfoHelpTimer.GetCurrentElapsedTime()) : 1, false); + 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/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 37d8e4b5cde56..b08a55967eda4 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -102,12 +102,12 @@ void GPUDisplay::showInfo(const char* info) mBackend->updateFontScale(mFrontend->getDisplayScale()); mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; - OpenGLPrint(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); + GLPrintText(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); if (mInfoText2Timer.IsRunning()) { if (mInfoText2Timer.GetCurrentElapsedTime() >= 6) { mInfoText2Timer.Reset(); } else { - OpenGLPrint(mInfoText2, 40.f, 20.f, colorValue, colorValue, colorValue, 6 - mInfoText2Timer.GetCurrentElapsedTime()); + GLPrintText(mInfoText2, 40.f, 20.f, colorValue, colorValue, colorValue, 6 - mInfoText2Timer.GetCurrentElapsedTime()); } } if (mInfoHelpTimer.IsRunning() || mCfgC.mPrintInfoTextAlways) { @@ -120,7 +120,7 @@ void GPUDisplay::showInfo(const char* info) mBackend->finishText(); } -void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) +void GPUDisplay::GLPrintText(const char* s, float x, float y, float r, float g, float b, float a, uint32_t nCharacters, bool fromBotton) { if (mBackend->mFreetypeInitialized) { const float scale = mFrontend->getDisplayScale(); @@ -130,6 +130,6 @@ void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, y = mBackend->mScreenHeight - y; } float color[4] = {r, g, b, a}; - mBackend->OpenGLPrint(s, x, y, color, scale / mBackend->mFontRasterScale); + mBackend->GLPrintText(s, x, y, color, scale / mBackend->mFontRasterScale, nCharacters); } } From 996a6ca4134d39869219f3d145ce057eb9f69f6c Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 3 Nov 2026 19:44:36 +0100 Subject: [PATCH 27/41] GPU Display: Fix do not exit on arrow key presses in the console --- GPU/GPUTracking/Global/GPUChainTracking.cxx | 6 ++++++ GPU/GPUTracking/utils/linux_helpers.h | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index f05ca6983e8ba..7b848ed360579 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -860,6 +860,12 @@ 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 + while (kbhit()) { + getch(); + } + iKey = 0; + } if (iKey == 27) { GetProcessingSettings().eventDisplay->setDisplayControl(2); } else if (iKey == 'n') { 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); } From cff6c046d3805e6300e7a598b8f3c639e178080c Mon Sep 17 00:00:00 2001 From: David Rohr Date: Thu, 29 Oct 2026 06:20:23 +0100 Subject: [PATCH 28/41] GPU Display Qt GUI: Add proper passing of frames with config elements to the GUI and returning lambda functions with value updates --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 29 +- GPU/GPUTracking/display/CMakeLists.txt | 2 +- GPU/GPUTracking/display/GPUDisplay.cxx | 32 +- GPU/GPUTracking/display/GPUDisplay.h | 27 +- .../display/frontend/GPUDisplayControl.h | 58 ++ .../display/frontend/GPUDisplayFrontend.cxx | 29 +- .../display/frontend/GPUDisplayFrontend.h | 32 +- .../frontend/GPUDisplayFrontendConfig.cxx | 210 ++++- .../frontend/GPUDisplayFrontendConfigInfo.inc | 12 +- .../frontend/GPUDisplayFrontendConfigKeys.inc | 117 +-- .../GPUDisplayFrontendConfigQtGUI.inc | 194 +++++ .../display/frontend/GPUDisplayGUI.cxx | 727 +++++++++++++++++- .../display/frontend/GPUDisplayGUI.h | 51 +- .../display/frontend/GPUDisplayGUI.ui | 38 +- .../display/frontend/GPUDisplayGUIChannel.h | 91 ++- .../display/frontend/GPUDisplayInfo.inc | 2 + .../display/helpers/GPUDisplayAnimation.cxx | 18 +- .../display/helpers/GPUDisplayHelpers.cxx | 108 +++ .../display/helpers/GPUDisplayROOT.cxx | 35 +- .../display/render/GPUDisplayDraw.cxx | 22 +- .../display/render/GPUDisplayImportEvent.cxx | 15 +- 21 files changed, 1586 insertions(+), 263 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayControl.h create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index afb40211247d9..7c23b7881cab4 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -436,7 +436,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 +449,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 +477,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 +490,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,14 +501,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(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() diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index e5674b91d52b2..47cbe8ad68feb 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -132,7 +132,7 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx frontend/GPUDisplayHelp.cxx frontend/GPUDisplayFrontendConfig.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h frontend/GPUDisplayControl.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 8651c054bc034..f6762f5850de7 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -218,6 +218,10 @@ int32_t GPUDisplay::DrawGLScene(bool suspended) } 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(suspended); } catch (const std::runtime_error& e) { @@ -244,8 +248,8 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(bool suspended, float animateTim mFrontend->mMouseWheel = 0; 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 rotateModel = mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_MODEL]] || mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_AROUND_Z]]; - bool rotateModelTPC = mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_ROTATE_AROUND_Z]]; + 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; @@ -277,7 +281,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(bool suspended, float animateTim 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; @@ -305,13 +309,15 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(bool suspended, float animateTim } if (mFrontend->mKeys[mFrontend->mKeyDownMap[mFrontend->KEY_MAP_CHANGE_ANIMATION_INTERVAL]]) { - mAnimationDelay += moveX; - if (mAnimationDelay < 0.05f) { - mAnimationDelay = 0.05f; + 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) { @@ -706,10 +712,10 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re 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 (mCfgC.mPrintInfoText & 2) { GPUInfo("%s", info); @@ -721,7 +727,7 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re mFramesDoneFPS = 0; } - if (mCfgC.mPrintInfoText & 1) { + if (mCfgC.mPrintInfoText & 1 || mCfgC.mPrintInfoTextGUI) { showInfo(info); } } @@ -732,9 +738,9 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re std::vector pixels = mBackend->getPixels(); char tmpFileName[48]; if (animateScreenshot) { - snprintf(tmpFileName, 48, "mAnimation%d_%05d.bmp", mAnimationExport, mAnimationFrame); + snprintf(tmpFileName, 48, "%d_%05d.bmp", mAnimationExport, mAnimationFrame); } - DoScreenshot(animateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); + DoScreenshot(animateScreenshot ? (mCfgC.mScreenShotname + tmpFileName).c_str() : mScreenshotFile.c_str(), pixels); } } diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index cfdfde0b9c0d9..40b21d7de3eec 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -16,6 +16,7 @@ #define GPUDISPLAY_H #include "frontend/GPUDisplayFrontend.h" +#include "frontend/GPUDisplayControl.h" #include "backend/GPUDisplayBackend.h" #include "GPUDisplayInterface.h" #include "GPUSettings.h" @@ -87,21 +88,13 @@ class GPUDisplay : public GPUDisplayInterface GPUDisplayFrontend* frontend() { return mFrontend; } int32_t& drawTextFontSize() { return mDrawTextFontSize; } - struct control { - std::atomic mDisplayControl = 0; // Control for next event (=1) or quit (=2) - std::atomic mResetScene = false; - int32_t mPrintInfoText = 1; - bool mPrintInfoTextAlways = false; - bool mRequestScreenshot = false; - int32_t mAnimateScreenshot = 0; - bool mAnimationChangeConfig = true; - bool dummy = false; // dummy value to reference for keys that do not change variables - } mCfgC; + 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); @@ -114,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, @@ -180,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; @@ -191,6 +186,12 @@ 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(); @@ -300,6 +301,8 @@ 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; @@ -313,12 +316,14 @@ class GPUDisplay : public GPUDisplayInterface int32_t mAnimationLastBase = 0; int32_t mAnimationMixBase = -1; // Target keyframe of the current config blend (mixing), -1 = none int32_t mAnimationExport = 0; - float mAnimationDelay = 2.f; vecpod mAnimateVectors[9]; vecpod mAnimateConfig; opengl_spline mAnimationSplines[8]; char mInfoText2[1024]; + 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; std::vector mThreadBuffers; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayControl.h b/GPU/GPUTracking/display/frontend/GPUDisplayControl.h new file mode 100644 index 0000000000000..c5d2969174576 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayControl.h @@ -0,0 +1,58 @@ +// 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 + +#ifndef GPUDISPLAYCONTROL_H +#define GPUDISPLAYCONTROL_H + +#include +#include +#include + +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; + } +}; + +struct GPUDisplayControl { // Settings of the display without RTTI, copied to the Qt GUI + GPUDisplayCopyableAtomic mDisplayControl = 0; // Control for next event (=1) or quit (=2) + GPUDisplayCopyableAtomic mResetScene = false; + int32_t mPrintInfoText = 1; + bool mPrintInfoTextAlways = false; + bool mRequestScreenshot = false; + int32_t mAnimateScreenshot = 0; + bool mAnimationChangeConfig = true; + float mAnimationDelay = 2.f; + bool mPrintInfoTextGUI = true; // Send the status and info text to the Qt GUI, shown at its bottom + bool mGuiPrintInfo = false; // Show the info of options changed with Qt GUI elements (not for key presses in the GUI) + bool mRotateModel = false; // Rotate the model instead of the camera, like holding RCTRL (set in the Qt GUI) + bool mRotateAroundZ = false; // Rotate the TPC around the beamline, like holding RALT (set in the Qt GUI) + std::string mFilterMacroCustom; // Code of the custom track filter ROOT macro (trackFilter = filterMacros.size() + 1), set in the Qt GUI + std::string mScreenShotname = "screenshot"; // Name of screenshot and animation frame files, followed by numbers + std::string mGLPosFile = "glpos.tmp"; // File for saving / loading the camera position + std::string mGLAnimationFile = "glanimation.tmp"; // File for saving / loading the animation path + int8_t mGuiDarkMode = -1; // Dark mode of the Qt GUI, -1 = not yet set: the GUI reports the system scheme when it starts + float mGuiScale = 1.f; // Scale factor of text and elements of the Qt GUI (on top of the system scaling), Ctrl + mouse wheel / Ctrl + 0 in the GUI + bool dummy = false; // dummy value to reference for keys that do not change variables +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 0db41feae33e9..503e3a2abf042 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -123,15 +123,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; } } 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; } @@ -139,6 +151,12 @@ 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; + } } } @@ -201,10 +219,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 { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 6953e8c4b20d9..0e3ee58fc7ee3 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -22,10 +22,12 @@ #include #include #include +#include #include namespace o2::gpu { +struct GPUDisplayGUIUpdate; class GPUReconstruction; class GPUDisplay; class GPUDisplayBackend; @@ -55,12 +57,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 + 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; } @@ -108,13 +110,13 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 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 int32_t mDisplayHeight = INIT_HEIGHT; int32_t mDisplayWidth = INIT_WIDTH; @@ -158,6 +160,10 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 }; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 8fc2f962df400..00ed3f9cc8df6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -16,6 +16,7 @@ #include "GPUDisplay.h" #include "frontend/GPUDisplayInfo.inc" #include "GPULogging.h" +#include "GPUDisplayGUIChannel.h" #include "utils/qconfig_rtti.h" #include "utils/qconfig_internal_helpers.h" @@ -31,6 +32,7 @@ #include #include #include +#include using namespace o2::gpu; struct GPUDisplayFrontend::configRTTIExt : public qConfig::qConfigRTTIentry { @@ -57,6 +59,12 @@ struct GPUDisplayFrontend::configRTTIInfo { 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 @@ -104,7 +112,7 @@ struct GPUDisplayFrontend::configRTTIInfo { template uint32_t getRTTIIndex(GPUDisplay& display, M T::* member) // RTTI index of a direct member of a config struct { - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) { return INVALID_INDEX; } else { const T& obj = configStruct(display); @@ -171,6 +179,7 @@ void GPUDisplay::checkAndApplyConfigChanges() if (oldCfgH.trackFilter != mCfgH.trackFilter) { mUpdateTrackFilter = true; } + mCfgC.mRotateModel |= mCfgC.mRotateAroundZ; } void GPUDisplay::HandleExternalGUIKey(int32_t key) @@ -232,6 +241,181 @@ void GPUDisplayFrontend::add_key_binding(Args&&... args) 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() @@ -256,8 +440,12 @@ void GPUDisplayFrontend::listConfig() 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, keys):"); - uint32_t nOptionsWithKey = 0; + 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; @@ -266,9 +454,12 @@ void GPUDisplayFrontend::listConfig() 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(); - GPUInfo(" %-28s %-32s %-12s %s", e.configStruct.c_str(), e.name.c_str(), e.type.c_str(), keys.c_str()); + 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; @@ -285,20 +476,28 @@ void GPUDisplayFrontend::listConfig() GPUInfo(" %-*s %-28s %s", keyWidth, ("[" + rtti.getKeyName(k) + "]").c_str(), hasRTTI ? rtti.rttiEntries[index].configStruct.c_str() : "(no config option)", hasRTTI ? rtti.rttiEntries[index].name.c_str() : ""); } } + GPUInfo(""); GPUInfo("Key down mappings (id, name, key):"); for (uint32_t i = 0; i < KEY_MAP_MAX; i++) { GPUInfo(" %2u %-28s %s", i, rtti.keyMapNames[i].c_str(), mappedKeys[i].c_str()); } + GPUInfo(""); std::string available; for (uint16_t k : availableKeys) { available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; } GPUInfo("Available keys: %s", available.c_str()); - GPUInfo("%zu options with RTTI, %u of them with a key, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, boundKeys.size(), availableKeys.size()); + GPUInfo("Help text:"); + for (const auto& line : rtti.helpText) { + GPUInfo(" %s", line.c_str()); + } + GPUInfo(""); + GPUInfo("%zu options with RTTI, %u of them with a key, %u in the Qt GUI, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, nOptionsInGUI, boundKeys.size(), availableKeys.size()); } #include "GPUDisplayFrontendConfigKeys.inc" #include "GPUDisplayFrontendConfigInfo.inc" +#include "GPUDisplayFrontendConfigQtGUI.inc" void GPUDisplay::buildConfig() { @@ -341,6 +540,7 @@ void GPUDisplay::buildConfig() }; } buildConfigInfo(); + buildConfigQtGUI(); rtti->buildHelpText(); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc index 0cbc8a4cdd571..d86650eecc12b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -29,14 +29,8 @@ void GPUDisplay::buildConfigInfo() } })); addInfo(INFO_LAMBDA(mCfgL, drawRelatedSectors, SetInfo("Drawing of related sectors %s", val ? "enabled" : "disabled");)); - addInfo(INFO_LAMBDA(mCfgH, xAdd, SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd);)); - addInfo(INFO_LAMBDA(mCfgL, animationMode, { - if (val == 6) { - SetInfo("Animation mode %d - Centered on origin", val); - } else { - SetInfo("Animation mode %d - Position: %s, Direction: %s", val, (val & 2) ? "Spherical (spherical rotation)" : ((val & 4) ? "Spherical (Euler angles)" : "Cartesian"), (val & 1) ? "Euler angles" : "Quaternion"); - } - })); + addInfo(INFO_LAMBDA(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);)); @@ -64,7 +58,7 @@ void GPUDisplay::buildConfigInfo() addInfo(INFO_LAMBDA(mCfgL, colorCollisions, SetInfo("Color coding of collisions %s", val ? "enabled" : "disabled");)); addInfo(INFO_LAMBDA(mCfgH, hideRejectedClusters, SetInfo("Rejected clusters are %s", val ? "hidden" : "shown");)); addInfo(INFO_LAMBDA(mCfgH, separateExtrapolatedTracks, SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", val ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled");)); - addInfo(INFO_LAMBDA(mCfgH, trackFilter, SetInfo("Track filter: %s", val == 0 ? "None" : mConfig.filterMacros[val - 1].c_str());)); + addInfo(INFO_LAMBDA(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]); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index 8cb8fecec59f2..db5d4509bcc88 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -156,17 +156,7 @@ void GPUDisplay::buildConfigKeys() } }), KEY_LAMBDA('J', mCfgL, drawRelatedSectors, val ^= 1;), // - KEY_LAMBDA(';', mCfgH, xAdd, { - mCfgH.xAdd += 60; - mCfgH.zAdd += 60; - }), - KEY_LAMBDA(':', mCfgH, xAdd, { - mCfgH.xAdd -= 60; - mCfgH.zAdd -= 60; - if (mCfgH.zAdd < 0 || mCfgH.xAdd < 0) { - mCfgH.zAdd = mCfgH.xAdd = 0; - } - }), // + 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( @@ -200,16 +190,12 @@ void GPUDisplay::buildConfigKeys() val = 0; } }), // - "Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20 / Mark fake attached clusters / Mark clusters attached as adjacent"); + "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, { - if (val) { - mCfgH.splitCETracks ^= 1; - } - val ^= 1; - }), + 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"); @@ -242,35 +228,18 @@ void GPUDisplay::buildConfigKeys() "Draw single collisions (next / previous) / Colorcode clusters of different collisions"); mFrontend->add_key_binding( - KEY_LAMBDA('t', mCfgC, mRequestScreenshot, { - static int32_t nScreenshot = 1; - char fname[32]; - snprintf(fname, 32, "screenshot%d.bmp", nScreenshot++); - val = true; - mScreenshotFile = fname; - SetInfo("Taking screenshot (%s)", fname); - }), + KEY_LAMBDA('t', mCfgC, mRequestScreenshot, takeScreenshot();), KEY_LAMBDA('T', mCfgC, mAnimateScreenshot, val ^= 1; SetInfo("Writing screenshots from animation set to %d", mCfgC.mAnimateScreenshot);), // KEY_LAMBDA('Z', mCfgR, screenshotScaleFactor, { val += 1; - if (val == 5) { + 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, { - if (mAnimateVectors[0].size() > 1) { - if (mCfgC.mAnimateScreenshot) { - mAnimationExport++; - } - startAnimation(); - SetInfo("Starting Animation", 1); - } else { - SetInfo("Insufficient Animation points to start Animation", 1); - } - }), + KEY_LAMBDA('y', mCfgC, dummy, 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);), // @@ -288,72 +257,10 @@ void GPUDisplay::buildConfigKeys() "Toggle config interpolation during Animation / change Animation interval (via movement)"); mFrontend->add_key_binding( - KEY_LAMBDA('o', mCfgC, dummy, { - FILE* ftmp = fopen("glpos.tmp", "w+b"); - if (ftmp) { - int32_t retval = fwrite(&mViewMatrix, sizeof(mViewMatrix), 1, ftmp); - if (retval != 1) { - GPUError("Error writing position to file"); - } else { - GPUInfo("Position stored to file"); - } - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Camera position stored to file", 1); - }), - KEY_LAMBDA('p', mCfgC, dummy, { - FILE* ftmp = fopen("glpos.tmp", "rb"); - if (ftmp) { - int32_t retval = fread(&mViewMatrix, 1, sizeof(mViewMatrix), ftmp); - if (retval == sizeof(mViewMatrix)) { - GPUInfo("Position read from file"); - } else { - GPUError("Error reading position from file"); - } - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Camera position loaded from file", 1); - }), - KEY_LAMBDA('O', mCfgC, dummy, { - FILE* ftmp = fopen("glanimation.tmp", "w+b"); - if (ftmp) { - fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp); - int32_t size = mAnimateVectors[0].size(); - fwrite(&size, sizeof(size), 1, ftmp); - for (int32_t i = 0; i < 9; i++) { - fwrite(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); - } - fwrite(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Animation path stored to file %s", "glanimation.tmp"); - }), - KEY_LAMBDA('P', mCfgC, dummy, { - FILE* ftmp = fopen("glanimation.tmp", "rb"); - if (ftmp) { - int32_t retval = fread(&mCfgL, sizeof(mCfgL), 1, ftmp); - int32_t size; - retval += fread(&size, sizeof(size), 1, ftmp); - for (int32_t i = 0; i < 9; i++) { - mAnimateVectors[i].resize(size); - retval += fread(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); - } - mAnimateConfig.resize(size); - retval += fread(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); - (void)retval; // disable unused warning - fclose(ftmp); - updateConfig(); - } else { - GPUError("Error opening file"); - } - SetInfo("Animation path loaded from file %s", "glanimation.tmp"); - }), // + 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( 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/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index c2e98b4e6e754..35a5b9a31600e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -16,25 +16,361 @@ #include "GPUDisplayFrontend.h" #include "ui_GPUDisplayGUI.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include using namespace o2::gpu; +namespace +{ +class FlowLayout : public QLayout // Places the items in a row with their preferred width, and wraps into further rows at the right border (Qt has no such layout) +{ + public: + FlowLayout(QWidget* parent, int hSpacingFactor, bool equalRowHeight) : QLayout(parent), mHSpacingFactor(hSpacingFactor), mEqualRowHeight(equalRowHeight) {} + ~FlowLayout() override + { + while (QLayoutItem* item = takeAt(0)) { + delete item; + } + } + void addItem(QLayoutItem* item) override { mItems.append(item); } + int count() const override { return mItems.size(); } + QLayoutItem* itemAt(int index) const override { return mItems.value(index); } + QLayoutItem* takeAt(int index) override { return index >= 0 && index < mItems.size() ? mItems.takeAt(index) : nullptr; } + Qt::Orientations expandingDirections() const override { return {}; } + bool hasHeightForWidth() const override { return true; } + int heightForWidth(int width) const override { return doLayout(QRect(0, 0, width, 0), true); } + void setGeometry(const QRect& rect) override + { + QLayout::setGeometry(rect); + doLayout(rect, false); + } + QSize sizeHint() const override // All items in one row + { + QSize size(0, 0); + for (const QLayoutItem* item : mItems) { + size = QSize(size.width() + (size.width() ? hSpacing() : 0) + itemWidth(item), std::max(size.height(), item->sizeHint().height())); + } + const QMargins m = contentsMargins(); + return size + QSize(m.left() + m.right(), m.top() + m.bottom()); + } + QSize minimumSize() const override // Widest item, height for that width is given by heightForWidth + { + QSize size; + for (const QLayoutItem* item : mItems) { + size = size.expandedTo(item->minimumSize()); + } + const QMargins m = contentsMargins(); + return size + QSize(m.left() + m.right(), m.top() + m.bottom()); + } + void setPreferredWidth(QWidget* widget, int width) { mPreferredWidth[widget] = width; } // Instead of the size hint of the widget + + private: + int hSpacing() const { return mHSpacingFactor * spacing(); } + int itemWidth(const QLayoutItem* item) const { return mPreferredWidth.value(item->widget(), item->sizeHint().width()); } + int doLayout(const QRect& rect, bool testOnly) const // Returns the height needed + { + const QRect r = rect.marginsRemoved(contentsMargins()); + int x = r.x(), y = r.y(), lineHeight = 0; + std::vector> line; // Items of the current row, placed when the row is complete (height of the row known) + auto placeLine = [&]() { + if (!testOnly) { + for (auto& [item, geometry] : line) { + item->setGeometry(mEqualRowHeight ? QRect(geometry.x(), geometry.y(), geometry.width(), lineHeight) : geometry.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), mUi(std::make_unique()), mChannel(channel) { + qApp->setStyle(new GUIStyle); // Owned by the application mUi->setupUi(this); + 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(takeCentralWidget()); + auto* central = new QWidget; + auto* centralLayout = new QVBoxLayout(central); + centralLayout->setContentsMargins(0, 0, 0, 0); + centralLayout->setSpacing(0); + centralLayout->addWidget(scroll, 1); + mStatusLabel = new QLabel; + mStatusLabel->setWordWrap(true); + mStatusLabel->hide(); + mStatusLabel->setStyleSheet("background-color: rgba(0, 0, 0, 25); padding: 2px 4px;"); // Slightly darker than the window, in light and dark mode + centralLayout->addWidget(mStatusLabel); + 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); + setCentralWidget(central); + std::vector layout; { std::lock_guard lock(mChannel.mutex); setWindowTitle(QString::fromStdString(mChannel.windowName + " GUI")); + layout = mChannel.layout; } - connect(mUi->lineWidthSlider, &QSlider::valueChanged, this, [this](int32_t value) { - mUpdate.lineWidth = value * 0.1f; - mUi->lineWidthValue->setNum(*mUpdate.lineWidth); - }); + mBaseFontSize = font().pointSizeF() > 0 ? font().pointSizeF() : 10.f; // Fonts with pixel size have no point size + buildElements(layout); + 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)); } @@ -64,15 +400,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 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); } @@ -85,16 +427,377 @@ void GPUDisplayGUI::timerTick() 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::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()); +} + 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)); + } + }}; +} + +void 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); + mUi->mainLayout->insertWidget(mUi->mainLayout->count() - 1, frames); // Before the vertical spacer + for (const auto& frame : layout) { + auto* box = new QGroupBox(QString::fromStdString(frame.description)); + if (frame.description.empty()) { + box->setStyleSheet("QGroupBox { margin-top: 0; }"); // No space for a title + } + 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(); }); +} + +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 (event->type() == QEvent::Wheel && (static_cast(event)->modifiers() & Qt::ControlModifier)) { + const int delta = static_cast(event)->angleDelta().y(); // 120 per notch + if (delta) { + setScale(mScale * std::pow(1.1f, delta / 120.f)); + mUpdate.guiScale = mScale; + } + return true; + } + if (event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_0 && (static_cast(event)->modifiers() & Qt::ControlModifier)) { + setScale(1.f); + mUpdate.guiScale = mScale; + return true; + } + return false; } void GPUDisplayGUI::bringToFront() diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h index e0da6423e94d2..f42d4b2cc11e0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h @@ -15,17 +15,17 @@ #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; +class QLabel; QT_BEGIN_NAMESPACE namespace Ui @@ -47,11 +47,41 @@ class GPUDisplayGUI : public QMainWindow 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(); + 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 + void 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; @@ -60,6 +90,15 @@ class GPUDisplayGUI : public QMainWindow 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 index 77b2a7401be11..1b019e073e702 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui @@ -6,8 +6,8 @@ 0 0 - 899 - 679 + 1370 + 1210 @@ -15,40 +15,6 @@ - - - - - - Line width - - - - - - - 1 - - - 50 - - - Qt::Orientation::Horizontal - - - - - - - - 40 - 0 - - - - - - diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h index 0ee36a4ec213e..f460e9933c639 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h @@ -16,6 +16,8 @@ #define GPUDISPLAYGUICHANNEL_H #include "GPUSettings.h" +#include "GPUDisplayControl.h" +#include #include #include #include @@ -23,33 +25,99 @@ #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 @@ -58,6 +126,7 @@ 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/GPUDisplayInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc index b6ac78b31f315..9df428783a394 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc @@ -29,6 +29,8 @@ void GPUDisplay::SetInfo(Args... args) snprintf(mInfoText2, 1024, args...); #pragma GCC diagnostic pop GPUInfo("%s", mInfoText2); + mGUIInfoText = mInfoText2; + mGUIInfoTextCounter++; mInfoText2Timer.ResetStart(); } } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index e8c1e16ff9b9e..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++) { diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index b08a55967eda4..dd817eed0e056 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,106 @@ void GPUDisplay::disableUnsupportedOptions() } } +void GPUDisplay::takeScreenshot() +{ + static int32_t nScreenshot = 1; + mCfgC.mRequestScreenshot = true; + mScreenshotFile = mCfgC.mScreenShotname + std::to_string(nScreenshot++) + ".bmp"; + SetInfo("Taking screenshot (%s)", mScreenshotFile.c_str()); +} + +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,6 +201,12 @@ void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, f void GPUDisplay::showInfo(const char* info) { + if (mCfgC.mPrintInfoTextGUI) { + mGUIInfoStatus = info; + } + if (!(mCfgC.mPrintInfoText & 1)) { + return; + } mBackend->updateFontScale(mFrontend->getDisplayScale()); mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; 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; From e6058702ca27cc5e1b127b7b9302954334b35089 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 4 Nov 2026 18:16:48 +0100 Subject: [PATCH 29/41] GPU Display GUI: Get rid of Qt ui file, since all elements are created dynamically --- GPU/GPUTracking/display/CMakeLists.txt | 6 +-- .../display/frontend/GPUDisplayGUI.cxx | 47 +++++++++++-------- .../display/frontend/GPUDisplayGUI.h | 17 ++----- .../display/frontend/GPUDisplayGUI.ui | 32 ------------- 4 files changed, 33 insertions(+), 69 deletions(-) delete mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 47cbe8ad68feb..39e1210074ada 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -186,11 +186,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() @@ -315,7 +314,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/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 35a5b9a31600e..3a3ab4d4456f4 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -14,7 +14,6 @@ #include "GPUDisplayGUI.h" #include "GPUDisplayFrontend.h" -#include "ui_GPUDisplayGUI.h" #include #include @@ -40,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -338,29 +338,19 @@ static bool sameValue(const GPUDisplayGUIValue& a, const GPUDisplayGUIValue& b) return aString || bString ? a == b : toDouble(a) == toDouble(b); } -GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mUi(std::make_unique()), mChannel(channel) +GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mChannel(channel) { qApp->setStyle(new GUIStyle); // Owned by the application - mUi->setupUi(this); - 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(takeCentralWidget()); - auto* central = new QWidget; - auto* centralLayout = new QVBoxLayout(central); - centralLayout->setContentsMargins(0, 0, 0, 0); - centralLayout->setSpacing(0); - centralLayout->addWidget(scroll, 1); + resize(1370, 1210); + setMinimumSize(500, 500); mStatusLabel = new QLabel; mStatusLabel->setWordWrap(true); mStatusLabel->hide(); - mStatusLabel->setStyleSheet("background-color: rgba(0, 0, 0, 25); padding: 2px 4px;"); // Slightly darker than the window, in light and dark mode - centralLayout->addWidget(mStatusLabel); + 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); - setCentralWidget(central); std::vector layout; { std::lock_guard lock(mChannel.mutex); @@ -368,15 +358,21 @@ GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : Q layout = mChannel.layout; } mBaseFontSize = font().pointSizeF() > 0 ? font().pointSizeF() : 10.f; // Fonts with pixel size have no point size - buildElements(layout); + 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); @@ -472,6 +468,12 @@ void GPUDisplayGUI::resizeEvent(QResizeEvent* 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()); @@ -484,6 +486,7 @@ void GPUDisplayGUI::updateInfoLabels() mStatusLabel->setText(status); } mStatusLabel->setVisible(!mState.infoStatus.empty()); + fitStatusLabel(); } void GPUDisplayGUI::applySettings() @@ -680,13 +683,12 @@ GPUDisplayGUI::ElementWidget GPUDisplayGUI::createDropdown(const GPUDisplayGUICh }}; } -void GPUDisplayGUI::buildElements(const std::vector& layout) +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); - mUi->mainLayout->insertWidget(mUi->mainLayout->count() - 1, frames); // Before the vertical spacer for (const auto& frame : layout) { auto* box = new QGroupBox(QString::fromStdString(frame.description)); if (frame.description.empty()) { @@ -761,6 +763,7 @@ void GPUDisplayGUI::buildElements(const std::vector } } mScaleSetters.emplace_back([framesLayout](float) { framesLayout->invalidate(); }); + return frames; } void GPUDisplayGUI::setScale(float scale) @@ -784,6 +787,10 @@ bool GPUDisplayGUI::eventFilter(QObject* watched, QEvent* event) 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) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h index f42d4b2cc11e0..786fb34c3548e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -27,13 +26,6 @@ class QKeyEvent; class QLabel; -QT_BEGIN_NAMESPACE -namespace Ui -{ -class GPUDisplayGUI; -} -QT_END_NAMESPACE - namespace o2::gpu { class GPUDisplayGUI : public QMainWindow @@ -42,7 +34,6 @@ class GPUDisplayGUI : public QMainWindow public: explicit GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent = nullptr); - ~GPUDisplayGUI() override; void bringToFront(); protected: @@ -68,10 +59,11 @@ class GPUDisplayGUI : public QMainWindow void applySettings(); 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 - void buildElements(const std::vector& layout); // One frame per add_gui_binding call, with a labeled element per option + 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); @@ -83,7 +75,6 @@ class GPUDisplayGUI : public QMainWindow 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui deleted file mode 100644 index 1b019e073e702..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui +++ /dev/null @@ -1,32 +0,0 @@ - - - GPUDisplayGUI - - - - 0 - 0 - 1370 - 1210 - - - - GPUDisplayGUI - - - - - - - Qt::Orientation::Vertical - - - - - - - - - - - From 8388aeada35c22179e4f86de460fcb29cef181fb Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 4 Nov 2026 18:51:13 +0100 Subject: [PATCH 30/41] GPU: Can load GPUSettingsList.h - like structs from multiple files --- GPU/GPUTracking/CMakeLists.txt | 3 +++ GPU/GPUTracking/DataTypes/GPUSettingsParam.h | 21 +++++++++++++++++++ GPU/GPUTracking/Definitions/GPUSettingsList.h | 21 ++----------------- GPU/GPUTracking/display/CMakeLists.txt | 3 ++- GPU/GPUTracking/qconfigoptions.h | 5 +++++ GPU/GPUTracking/utils/qconfig_internal.h | 1 + 6 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 GPU/GPUTracking/DataTypes/GPUSettingsParam.h diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index 6f2e643fc98f6..2d12834cb0b3e 100644 --- a/GPU/GPUTracking/CMakeLists.txt +++ b/GPU/GPUTracking/CMakeLists.txt @@ -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/GPUSettingsParam.h b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h new file mode 100644 index 0000000000000..2107e92e60ba1 --- /dev/null +++ b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h @@ -0,0 +1,21 @@ +// 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 + +// 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/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 7c23b7881cab4..f56c0cbd7c71f 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -19,20 +19,13 @@ // Create plain-C struct for GPU code. // Create static constexpr with default values for GPU run time compilation -#include "GPUCommonDef.h" +#ifdef BeginConfig // File should not be included without defining the macros, but rootcling will do for dictionary generation #ifndef GPUSETTINGS_H #error Please include GPUSettings.h! #endif // clang-format off -#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) - // 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!!!!!! // Split in different blocks for global and per Detector @@ -719,14 +712,4 @@ 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 - - // clang-format on +#endif // #ifndef __CLING__ diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 39e1210074ada..20283a803a76e 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -170,7 +170,8 @@ if(GPUCA_EVENT_DISPLAY_WAYLAND) 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 frontend/GPUDisplayFrontendWaylandEv.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) diff --git a/GPU/GPUTracking/qconfigoptions.h b/GPU/GPUTracking/qconfigoptions.h index 2a43c3f59cc3f..c755cc86c5c45 100644 --- a/GPU/GPUTracking/qconfigoptions.h +++ b/GPU/GPUTracking/qconfigoptions.h @@ -14,4 +14,9 @@ #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 +BeginNamespace(o2::gpu) #include "GPUSettingsList.h" +#include "GPUSettingsParam.h" +EndNamespace() // o2::gpu diff --git a/GPU/GPUTracking/utils/qconfig_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index 17ac6b55cb530..01c65ec52f604 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -208,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() } From 3bed93fa73714a4e6f8059c633581825d0823b62 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Fri, 6 Nov 2026 07:06:00 +0100 Subject: [PATCH 31/41] GPU Config: Clean up header guards --- GPU/GPUTracking/CMakeLists.txt | 2 +- GPU/GPUTracking/DataTypes/GPUSettings.h | 66 +-------------- GPU/GPUTracking/DataTypes/GPUSettingsTypes.h | 83 +++++++++++++++++++ GPU/GPUTracking/Definitions/GPUSettingsList.h | 3 - GPU/GPUTracking/qconfigoptions.h | 12 ++- GPU/GPUTracking/utils/qconfig.h | 12 +-- GPU/GPUTracking/utils/qconfig_internal.h | 2 + GPU/GPUTracking/utils/qconfig_wrapinclude.h | 14 +++- 8 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 GPU/GPUTracking/DataTypes/GPUSettingsTypes.h diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index 2d12834cb0b3e..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 diff --git a/GPU/GPUTracking/DataTypes/GPUSettings.h b/GPU/GPUTracking/DataTypes/GPUSettings.h index dff33328e437f..3b00d80e01220 100644 --- a/GPU/GPUTracking/DataTypes/GPUSettings.h +++ b/GPU/GPUTracking/DataTypes/GPUSettings.h @@ -16,69 +16,7 @@ #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 -}; - -// 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 +#include "GPUSettingsTypes.h" #ifdef GPUCA_GPUCODE_DEVICE #define QCONFIG_GPU @@ -88,4 +26,4 @@ struct GPUSettingsDeviceBackend { #include "utils/qconfig.h" #endif -#endif +#endif // GPUSETTINGS_H 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 f56c0cbd7c71f..a81b5433f1c43 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -20,9 +20,6 @@ // Create static constexpr with default values for GPU run time compilation #ifdef BeginConfig // File should not be included without defining the macros, but rootcling will do for dictionary generation -#ifndef GPUSETTINGS_H -#error Please include GPUSettings.h! -#endif // clang-format off diff --git a/GPU/GPUTracking/qconfigoptions.h b/GPU/GPUTracking/qconfigoptions.h index c755cc86c5c45..90f862bc937cb 100644 --- a/GPU/GPUTracking/qconfigoptions.h +++ b/GPU/GPUTracking/qconfigoptions.h @@ -12,11 +12,19 @@ /// \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. +#ifndef QCONFIG_INTERNAL +#include "GPUSettingsTypes.h" +#endif // clang-format off BeginNamespace(o2::gpu) +#if !defined(QCONFIGOPTIONS_H_LIST) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_LIST #include "GPUSettingsList.h" +#endif + +#if !defined(QCONFIGOPTIONS_H_PARAM) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_PARAM #include "GPUSettingsParam.h" +#endif EndNamespace() // o2::gpu 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_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index 01c65ec52f604..70d63d26686d7 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -292,4 +292,6 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // #endif +#define QCONFIG_INTERNAL #include "qconfig_wrapinclude.h" +#undef QCONFIG_INTERNAL 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 From 83417f4fc195510849da1c925c8252817188b7e0 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Fri, 6 Nov 2026 07:49:58 +0100 Subject: [PATCH 32/41] GPU: Fix missing newline in RTC param code --- GPU/GPUTracking/utils/qconfig_internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUTracking/utils/qconfig_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index 70d63d26686d7..7e71bcedc226b 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -253,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"; From 498f3a0d65a748934ecf9259ffd14286651da7e8 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 7 Nov 2026 09:37:49 +0100 Subject: [PATCH 33/41] GPU: Improve settings handling and make GPUDisplayControl a qconfig GPUSettings struct --- GPU/GPUTracking/Base/GPUParam.h | 1 + GPU/GPUTracking/Base/GPUParamRTC.h | 2 + GPU/GPUTracking/DataTypes/GPUConfigDump.cxx | 1 + GPU/GPUTracking/DataTypes/GPUSettings.h | 5 +- GPU/GPUTracking/DataTypes/GPUSettingsParam.h | 2 + GPU/GPUTracking/Definitions/GPUSettingsList.h | 9 ++- GPU/GPUTracking/display/CMakeLists.txt | 2 +- GPU/GPUTracking/display/GPUDisplay.h | 4 +- GPU/GPUTracking/display/GPUDisplayControl.h | 45 ++++++++++++++ GPU/GPUTracking/display/GPUDisplayTypes.h | 32 ++++++++++ .../display/frontend/GPUDisplayControl.h | 58 ------------------- .../frontend/GPUDisplayFrontendConfig.cxx | 2 + .../display/frontend/GPUDisplayGUIChannel.h | 2 +- GPU/GPUTracking/qconfigoptions.h | 13 ++--- GPU/GPUTracking/utils/qconfig.cxx | 2 + 15 files changed, 107 insertions(+), 73 deletions(-) create mode 100644 GPU/GPUTracking/display/GPUDisplayControl.h create mode 100644 GPU/GPUTracking/display/GPUDisplayTypes.h delete mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayControl.h 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/DataTypes/GPUConfigDump.cxx b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx index 3aab0270671f7..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 diff --git a/GPU/GPUTracking/DataTypes/GPUSettings.h b/GPU/GPUTracking/DataTypes/GPUSettings.h index 3b00d80e01220..bfbd3df33526f 100644 --- a/GPU/GPUTracking/DataTypes/GPUSettings.h +++ b/GPU/GPUTracking/DataTypes/GPUSettings.h @@ -18,6 +18,9 @@ #include "GPUCommonDef.h" #include "GPUSettingsTypes.h" +#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 @@ -25,5 +28,3 @@ #ifndef GPUCA_GPUCODE_GENRTC #include "utils/qconfig.h" #endif - -#endif // GPUSETTINGS_H diff --git a/GPU/GPUTracking/DataTypes/GPUSettingsParam.h b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h index 2107e92e60ba1..1c6a7bcf8a259 100644 --- a/GPU/GPUTracking/DataTypes/GPUSettingsParam.h +++ b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h @@ -14,6 +14,8 @@ // 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 diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index a81b5433f1c43..cf4d4348075bf 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -19,9 +19,12 @@ // Create plain-C struct for GPU code. // Create static constexpr with default values for GPU run time compilation -#ifdef BeginConfig // File should not be included without defining the macros, but rootcling will do for dictionary generation +#include "GPUSettingsTypes.h" + +#if (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) // File should not be included without defining the macros, but rootcling will do for dictionary generation // 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!!!!!! @@ -709,4 +712,6 @@ EndConfig() #endif // !GPUCA_STANDALONE #endif // !GPUCA_GPUCODE_DEVICE -#endif // #ifndef __CLING__ +EndNamespace() // o2::gpu + +#endif // (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 20283a803a76e..596c0f25c1b3d 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -132,7 +132,7 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx frontend/GPUDisplayHelp.cxx frontend/GPUDisplayFrontendConfig.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h frontend/GPUDisplayControl.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h GPUDisplayControl.h GPUDisplayTypes.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 40b21d7de3eec..55519fdd39b56 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -15,11 +15,11 @@ #ifndef GPUDISPLAY_H #define GPUDISPLAY_H +#define GPUSETTINGS_DISPLAYCONTROL +#include "GPUSettings.h" #include "frontend/GPUDisplayFrontend.h" -#include "frontend/GPUDisplayControl.h" #include "backend/GPUDisplayBackend.h" #include "GPUDisplayInterface.h" -#include "GPUSettings.h" #include "DataFormatsTPC/Constants.h" #include diff --git a/GPU/GPUTracking/display/GPUDisplayControl.h b/GPU/GPUTracking/display/GPUDisplayControl.h new file mode 100644 index 0000000000000..0e2585501d239 --- /dev/null +++ b/GPU/GPUTracking/display/GPUDisplayControl.h @@ -0,0 +1,45 @@ +// 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(dummy, bool, false) // dummy value to reference for keys that do not change variables +EndConfig() +EndNamespace() // o2::gpu 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/frontend/GPUDisplayControl.h b/GPU/GPUTracking/display/frontend/GPUDisplayControl.h deleted file mode 100644 index c5d2969174576..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayControl.h +++ /dev/null @@ -1,58 +0,0 @@ -// 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 - -#ifndef GPUDISPLAYCONTROL_H -#define GPUDISPLAYCONTROL_H - -#include -#include -#include - -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; - } -}; - -struct GPUDisplayControl { // Settings of the display without RTTI, copied to the Qt GUI - GPUDisplayCopyableAtomic mDisplayControl = 0; // Control for next event (=1) or quit (=2) - GPUDisplayCopyableAtomic mResetScene = false; - int32_t mPrintInfoText = 1; - bool mPrintInfoTextAlways = false; - bool mRequestScreenshot = false; - int32_t mAnimateScreenshot = 0; - bool mAnimationChangeConfig = true; - float mAnimationDelay = 2.f; - bool mPrintInfoTextGUI = true; // Send the status and info text to the Qt GUI, shown at its bottom - bool mGuiPrintInfo = false; // Show the info of options changed with Qt GUI elements (not for key presses in the GUI) - bool mRotateModel = false; // Rotate the model instead of the camera, like holding RCTRL (set in the Qt GUI) - bool mRotateAroundZ = false; // Rotate the TPC around the beamline, like holding RALT (set in the Qt GUI) - std::string mFilterMacroCustom; // Code of the custom track filter ROOT macro (trackFilter = filterMacros.size() + 1), set in the Qt GUI - std::string mScreenShotname = "screenshot"; // Name of screenshot and animation frame files, followed by numbers - std::string mGLPosFile = "glpos.tmp"; // File for saving / loading the camera position - std::string mGLAnimationFile = "glanimation.tmp"; // File for saving / loading the animation path - int8_t mGuiDarkMode = -1; // Dark mode of the Qt GUI, -1 = not yet set: the GUI reports the system scheme when it starts - float mGuiScale = 1.f; // Scale factor of text and elements of the Qt GUI (on top of the system scaling), Ctrl + mouse wheel / Ctrl + 0 in the GUI - bool dummy = false; // dummy value to reference for keys that do not change variables -}; -} // namespace o2::gpu - -#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 00ed3f9cc8df6..db0ba56428c2d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -17,6 +17,8 @@ #include "frontend/GPUDisplayInfo.inc" #include "GPULogging.h" #include "GPUDisplayGUIChannel.h" +#define GPUSETTINGS_ALL +#include "GPUSettings.h" #include "utils/qconfig_rtti.h" #include "utils/qconfig_internal_helpers.h" diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h index f460e9933c639..f5a85662ebdc1 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h @@ -15,8 +15,8 @@ #ifndef GPUDISPLAYGUICHANNEL_H #define GPUDISPLAYGUICHANNEL_H +#define GPUSETTINGS_DISPLAYCONTROL #include "GPUSettings.h" -#include "GPUDisplayControl.h" #include #include #include diff --git a/GPU/GPUTracking/qconfigoptions.h b/GPU/GPUTracking/qconfigoptions.h index 90f862bc937cb..9e0bd05705e48 100644 --- a/GPU/GPUTracking/qconfigoptions.h +++ b/GPU/GPUTracking/qconfigoptions.h @@ -12,19 +12,18 @@ /// \file qconfigoptions.h /// \author David Rohr -#ifndef QCONFIG_INTERNAL -#include "GPUSettingsTypes.h" -#endif - // clang-format off -BeginNamespace(o2::gpu) #if !defined(QCONFIGOPTIONS_H_LIST) || defined(QCONFIG_INTERNAL) #define QCONFIGOPTIONS_H_LIST #include "GPUSettingsList.h" #endif -#if !defined(QCONFIGOPTIONS_H_PARAM) || defined(QCONFIG_INTERNAL) +#if ((defined(GPUSETTINGS_PARAM) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_PARAM)) || defined(QCONFIG_INTERNAL) #define QCONFIGOPTIONS_H_PARAM #include "GPUSettingsParam.h" #endif -EndNamespace() // o2::gpu + +#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/qconfig.cxx b/GPU/GPUTracking/utils/qconfig.cxx index ddfe56ae78cb6..1b3adaa3a78ee 100644 --- a/GPU/GPUTracking/utils/qconfig.cxx +++ b/GPU/GPUTracking/utils/qconfig.cxx @@ -25,7 +25,9 @@ #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" From 6dd68184a9d7e5a8458358b71de87e9d7de91ce2 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Fri, 6 Nov 2026 08:30:26 +0100 Subject: [PATCH 34/41] GPU Display Frontend: Use RTTI for configuring GPUDisplayControl --- .../display/frontend/GPUDisplayFrontendConfig.cxx | 15 +++++++++------ .../frontend/GPUDisplayFrontendConfigInfo.inc | 7 +++++++ .../frontend/GPUDisplayFrontendConfigKeys.inc | 12 ++++-------- .../display/helpers/GPUDisplayHelpers.cxx | 1 - 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index db0ba56428c2d..da4709e9ba776 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -48,7 +48,7 @@ struct GPUDisplayFrontend::configKey { }; struct GPUDisplayFrontend::configRTTIInfo { - static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR}; // Config structs with RTTI, as members of GPUDisplay + 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 @@ -108,18 +108,21 @@ struct GPUDisplayFrontend::configRTTIInfo { 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 { - if constexpr (std::is_same_v) { - return INVALID_INDEX; - } else { - const T& obj = configStruct(display); - return rttiReverseId[configStructIndex()][(const char*)&(obj.*member) - (const char*)&obj]; + if constexpr (std::is_same_v && std::is_same_v) { + if (member == &GPUDisplayControl::dummy) { // Keys / buttons that do not change a setting + return INVALID_INDEX; + } } + 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 { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc index d86650eecc12b..ac4e291d7e710 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -20,6 +20,13 @@ void GPUDisplay::buildConfigInfo() { auto addInfo = [this](std::pair&& info) { mFrontend->mConfigRTTI->configInfos[info.first] = std::move(info.second); }; + 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) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index db5d4509bcc88..b97c281896a12 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -38,9 +38,9 @@ void GPUDisplay::buildConfigKeys() "Strafe up / down / Rotate left / right"); mFrontend->add_key_binding( - KEY_LAMBDA(mFrontend->KEY_ESCAPE, mCfgC, mDisplayControl, val.store(2); SetInfo("Exiting", 1);), + KEY_LAMBDA(mFrontend->KEY_ESCAPE, mCfgC, mDisplayControl, val.store(2);), KEY_LAMBDA('n', mCfgC, mDisplayControl, val.store(1);), - KEY_LAMBDA('r', mCfgC, mResetScene, val.store(true); SetInfo("View reset", 1);), // + KEY_LAMBDA('r', mCfgC, mResetScene, val.store(true);), // KEY_LAMBDA('q', mCfgC, dummy, { if (mFrontend->isGUIRunning()) { SetInfo("Stopping GUI", 1); @@ -229,7 +229,7 @@ void GPUDisplay::buildConfigKeys() mFrontend->add_key_binding( KEY_LAMBDA('t', mCfgC, mRequestScreenshot, takeScreenshot();), - KEY_LAMBDA('T', mCfgC, mAnimateScreenshot, val ^= 1; SetInfo("Writing screenshots from animation set to %d", mCfgC.mAnimateScreenshot);), // + KEY_LAMBDA('T', mCfgC, mAnimateScreenshot, val ^= 1;), // KEY_LAMBDA('Z', mCfgR, screenshotScaleFactor, { val += 1; if (val > 4) { @@ -252,7 +252,7 @@ void GPUDisplay::buildConfigKeys() "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)"); mFrontend->add_key_binding( - KEY_LAMBDA('>', mCfgC, mAnimationChangeConfig, val ^= 1; SetInfo("Interpolating visualization settings during Animation %s", mCfgC.mAnimationChangeConfig ? "enabled" : "disabled");), + KEY_LAMBDA('>', mCfgC, mAnimationChangeConfig, val ^= 1;), KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // "Toggle config interpolation during Animation / change Animation interval (via movement)"); @@ -296,20 +296,16 @@ void GPUDisplay::buildConfigKeys() KEY_LAMBDA('h', mCfgC, mPrintInfoTextAlways, { if (val) { val = false; - SetInfo("Showing help text disabled", 1); } else if (mInfoHelpTimer.IsRunning()) { val = true; mInfoHelpTimer.Reset(); - SetInfo("Showing help text until disabled", 1); } else { PrintHelp(); - SetInfo("Showing help text", 1); } }), KEY_LAMBDA('H', mCfgC, mPrintInfoText, { val += 1; val &= 3; - SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled"); }), // "Print Help / Show info texts"); } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index dd817eed0e056..8d8aec9c306ea 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -61,7 +61,6 @@ void GPUDisplay::takeScreenshot() static int32_t nScreenshot = 1; mCfgC.mRequestScreenshot = true; mScreenshotFile = mCfgC.mScreenShotname + std::to_string(nScreenshot++) + ".bmp"; - SetInfo("Taking screenshot (%s)", mScreenshotFile.c_str()); } void GPUDisplay::SaveGLPos() From 0d94fe5a8c5eed7e11dbb7c64ec4d3df6a728e37 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sat, 7 Nov 2026 09:54:43 +0100 Subject: [PATCH 35/41] GPU Display: Suppress generic SetInfo change-messages for certain variables --- .../display/frontend/GPUDisplayFrontendConfig.cxx | 5 ----- .../display/frontend/GPUDisplayFrontendConfigInfo.inc | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index da4709e9ba776..59f98c86c2ddb 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -116,11 +116,6 @@ struct GPUDisplayFrontend::configRTTIInfo { template uint32_t getRTTIIndex(GPUDisplay& display, M T::* member) // RTTI index of a direct member of a config struct { - if constexpr (std::is_same_v && std::is_same_v) { - if (member == &GPUDisplayControl::dummy) { // Keys / buttons that do not change a setting - return INVALID_INDEX; - } - } const T& obj = configStruct(display); return rttiReverseId[configStructIndex()][(const char*)&(obj.*member) - (const char*)&obj]; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc index ac4e291d7e710..3c7c742ff9a74 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -20,6 +20,9 @@ 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, &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());)); From 6c735808895cb5c7adaac6354b3d094356cff480 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 8 Nov 2026 16:33:07 +0100 Subject: [PATCH 36/41] GPUD Display: Move frontend key number constants to separate header --- GPU/GPUTracking/display/CMakeLists.txt | 2 +- .../display/frontend/GPUDisplayFrontend.h | 8 ++--- .../frontend/GPUDisplayFrontendKeyVars.h | 32 +++++++++++++++++++ .../display/frontend/GPUDisplayGUI.cxx | 28 ++++++++-------- 4 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 596c0f25c1b3d..1cb5553a9ebc2 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -132,7 +132,7 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx frontend/GPUDisplayHelp.cxx frontend/GPUDisplayFrontendConfig.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h GPUDisplayControl.h GPUDisplayTypes.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) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 0e3ee58fc7ee3..193524ec17e2f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -17,6 +17,7 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" +#include "GPUDisplayFrontendKeyVars.h" #include #include #include @@ -34,10 +35,9 @@ class GPUDisplayBackend; class GPUDisplayGUIWrapper; struct GPUDisplayGUIChannel; -class GPUDisplayFrontend : public GPUDisplayFrontendInterface +class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplayFrontendKeyVars { friend class GPUDisplay; - friend class GPUDisplayGUI; // Key codes public: GPUDisplayFrontend(); @@ -92,10 +92,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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.) -#define GPUDisplayFrontendAddKey(code, key) static constexpr int32_t KEY_##key = code -#define GPUDisplayFrontendKeyDownMap(code, key) static constexpr int32_t KEY_MAP_##key = code -#include "GPUDisplayFrontendKeys.h" static constexpr uint32_t N_SPECIAL_KEYS = 32; static constexpr uint32_t SPECIAL_KEYS_SHIFTED = 255; static constexpr uint32_t N_TOTAL_KEYS = 256 + N_SPECIAL_KEYS; 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/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 3a3ab4d4456f4..6c866b4b166a8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -13,7 +13,7 @@ /// \author David Rohr #include "GPUDisplayGUI.h" -#include "GPUDisplayFrontend.h" +#include "GPUDisplayFrontendKeyVars.h" #include #include @@ -829,34 +829,34 @@ int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event) { 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_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 From 12db80ad0009de9a8e4093203a442ad402903ddc Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 8 Nov 2026 16:52:24 +0100 Subject: [PATCH 37/41] GPU Display: Correctly handle special cars pressed in the console window --- GPU/GPUTracking/Global/GPUChainTracking.cxx | 9 +- GPU/GPUTracking/display/CMakeLists.txt | 3 +- GPU/GPUTracking/display/GPUDisplayInterface.h | 2 +- .../display/frontend/GPUDisplayFrontend.cxx | 5 +- .../display/frontend/GPUDisplayFrontend.h | 5 +- .../frontend/GPUDisplayFrontendConfig.cxx | 4 + .../GPUDisplayFrontendKeyTranslation.cxx | 112 ++++++++++++++++++ 7 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index 7b848ed360579..8a22e2c03ea47 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -860,10 +860,13 @@ 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 - while (kbhit()) { - getch(); + 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) { diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 1cb5553a9ebc2..70ac9bdd4282c 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -130,7 +130,8 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx frontend/GPUDisplayHelp.cxx - frontend/GPUDisplayFrontendConfig.cxx) + frontend/GPUDisplayFrontendConfig.cxx + frontend/GPUDisplayFrontendKeyTranslation.cxx) set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h frontend/GPUDisplayFrontendKeyVars.h GPUDisplayControl.h GPUDisplayTypes.h) 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/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 503e3a2abf042..a299f7669f3ab 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -160,8 +160,11 @@ void GPUDisplayFrontend::HandleExternalGUISignals() } } -void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) +void GPUDisplayFrontend::sendExternalGUIKey(int32_t v, uint32_t translationMode, uint64_t extendedKey) { + if (translationMode && (v = translateExternalKey(translationMode, extendedKey)) < 0) { + return; + } { std::lock_guard lock(mExternalGUIKeyMutex); mExternalGUIKeys.emplace_back(v); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 193524ec17e2f..7686ad9489d1b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -73,7 +73,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay int32_t getDisplayControl() const override; void setDisplayControl(int32_t v) override; - void sendExternalGUIKey(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; } @@ -153,7 +153,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay std::unique_ptr mConfigRTTI{nullptr, nullptr}; // Deleter set in GPUDisplayFrontendConfig.cxx, the only place where configRTTIInfo is defined void HandleExternalGUIKeys(); - bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system + 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 + 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index 59f98c86c2ddb..ee9266e15b483 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -187,6 +187,10 @@ 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); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx new file mode 100644 index 0000000000000..2bbc9928aa774 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx @@ -0,0 +1,112 @@ +// 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}, + {"[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}, + {"[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}, + {"[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; +} From 1bf74f131d3bf35a32b6424fee98fa81db0189aa Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 8 Nov 2026 18:22:27 +0100 Subject: [PATCH 38/41] GPU Display OpenGL: Ignore stale errors from the frontend --- GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index f79675cbad8a6..e9f4248ad4503 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -269,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); From 2aee29773c0ef21a002c975e1791b05c6ef3fc24 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 8 Nov 2026 22:00:45 +0100 Subject: [PATCH 39/41] GPU Display: Add an integrated console for manipulating the configuration and steering the display --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 4 + GPU/GPUTracking/display/CMakeLists.txt | 1 + GPU/GPUTracking/display/GPUDisplay.cxx | 14 ++ GPU/GPUTracking/display/GPUDisplay.h | 12 +- GPU/GPUTracking/display/GPUDisplayControl.h | 1 + .../display/backend/GPUDisplayBackend.cxx | 43 +++- .../display/backend/GPUDisplayBackend.h | 12 +- .../display/backend/GPUDisplayBackendNone.h | 1 + .../backend/GPUDisplayBackendOpenGL.cxx | 38 +++- .../display/backend/GPUDisplayBackendOpenGL.h | 2 + .../backend/GPUDisplayBackendVulkan.cxx | 24 ++- .../display/backend/GPUDisplayBackendVulkan.h | 2 + .../display/frontend/GPUDisplayConsole.cxx | 200 ++++++++++++++++++ .../display/frontend/GPUDisplayConsole.h | 55 +++++ .../display/frontend/GPUDisplayFrontend.cxx | 5 +- .../display/frontend/GPUDisplayFrontend.h | 8 +- .../frontend/GPUDisplayFrontendConfig.cxx | 29 ++- .../GPUDisplayFrontendConfigConsole.inc | 47 ++++ .../frontend/GPUDisplayFrontendConfigInfo.inc | 2 +- .../frontend/GPUDisplayFrontendConfigKeys.inc | 4 + .../frontend/GPUDisplayFrontendGlfw.cxx | 24 ++- .../display/frontend/GPUDisplayFrontendGlfw.h | 1 + .../frontend/GPUDisplayFrontendGlut.cxx | 5 +- .../GPUDisplayFrontendKeyTranslation.cxx | 3 + .../display/frontend/GPUDisplayFrontendKeys.h | 2 + .../frontend/GPUDisplayFrontendWayland.cxx | 9 +- .../frontend/GPUDisplayFrontendWindows.cxx | 14 +- .../frontend/GPUDisplayFrontendX11.cxx | 12 +- .../display/frontend/GPUDisplayGUI.cxx | 11 + .../display/frontend/GPUDisplayInfo.inc | 2 + .../display/helpers/GPUDisplayHelpers.cxx | 17 +- 31 files changed, 566 insertions(+), 38 deletions(-) create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayConsole.h create mode 100644 GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index cf4d4348075bf..987593b00599c 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -526,6 +526,10 @@ 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(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) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 70ac9bdd4282c..682dfbe60c61c 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -117,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) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index f6762f5850de7..d4c7557b7c088 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -727,9 +727,23 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re mFramesDoneFPS = 0; } + 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(doScreenshot, renderToMixBuffer, mixSlaveImage); diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 55519fdd39b56..05e2bed6296db 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -61,7 +61,7 @@ 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); + 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); @@ -198,6 +198,7 @@ class GPUDisplay : public GPUDisplayInterface void startAnimation(); 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(); @@ -321,10 +322,11 @@ class GPUDisplay : public GPUDisplayInterface opengl_spline mAnimationSplines[8]; char mInfoText2[1024]; - 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; + 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; diff --git a/GPU/GPUTracking/display/GPUDisplayControl.h b/GPU/GPUTracking/display/GPUDisplayControl.h index 0e2585501d239..1d433a8f13751 100644 --- a/GPU/GPUTracking/display/GPUDisplayControl.h +++ b/GPU/GPUTracking/display/GPUDisplayControl.h @@ -40,6 +40,7 @@ BeginHiddenConfig(GPUDisplayControl, control) 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/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index 2f353ea8b60c2..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); @@ -145,6 +145,8 @@ void GPUDisplayBackend::loadFont() } 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); @@ -207,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 411af384221b5..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 @@ -99,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 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 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; } @@ -119,8 +124,9 @@ class GPUDisplayBackend std::vector mIndirectSectorOffset; vecpod mCmdBuffer; bool mFreetypeInitialized = false; - float mFontRasterScale = 1.f; // Display scale the font was rasterized for - float mFontCharWidth = 0.f; // Advance of a character of the (monospace) font in logical pixels, 0 without Freetype font + 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 825fd9b353aa6..d5ec0e22b8254 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h @@ -40,6 +40,7 @@ class GPUDisplayBackendNone : public GPUDisplayBackend void pointSizeFactor(float factor) override {} void lineWidthFactor(float factor) 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 e9f4248ad4503..3d91ee802c16f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -684,8 +684,8 @@ void GPUDisplayBackendOpenGL::GLPrintText(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 && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + 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]; @@ -701,11 +701,7 @@ void GPUDisplayBackendOpenGL::GLPrintText(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 } @@ -714,6 +710,34 @@ void GPUDisplayBackendOpenGL::GLPrintText(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 f6e9d8801b6d0..e7bc6480878f5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -80,6 +80,8 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void initializeTextDrawing() override; void clearFontSymbols() 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; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index b76fc71de66d1..df689d1fdfd0f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1900,8 +1900,8 @@ void GPUDisplayBackendVulkan::GLPrintText(const char* s, float x, float y, float } for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + 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]; @@ -1925,6 +1925,11 @@ void GPUDisplayBackendVulkan::GLPrintText(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) { @@ -1937,6 +1942,21 @@ void GPUDisplayBackendVulkan::GLPrintText(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; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index d76fa357bb216..1b24f2199e3a6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -137,6 +137,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void initializeTextDrawing() override; void clearFontSymbols() 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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx new file mode 100644 index 0000000000000..69e83e7430d44 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx @@ -0,0 +1,200 @@ +// 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 "GPULogging.h" +#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::vector GPUDisplayConsole::parseConsoleCommand(const std::string& cmd) +{ + std::vector retVal; + std::string token; + bool inToken = false, plainText = false, escape = false; + char quote = 0; + auto finishToken = [&]() { + if (!inToken) { + return; + } + const char* begin = token.c_str() + (token[0] == '+' || token[0] == '-'); + const size_t nDigits = std::count_if(begin, token.c_str() + token.size(), [](char c) { return c >= '0' && c <= '9'; }); + const size_t nDots = std::count(begin, token.c_str() + token.size(), '.'); + const bool number = !plainText && nDigits && nDigits + nDots == (size_t)(token.c_str() + token.size() - begin); + int64_t intValue = 0; + if (number && nDots == 0 && std::from_chars(begin, token.c_str() + token.size(), intValue).ec == std::errc()) { + retVal.emplace_back(token[0] == '-' ? -intValue : intValue); + } else if (number && nDots <= 1) { + retVal.emplace_back(std::stod(token)); // Also integers out of the int64_t range + } else { + retVal.emplace_back(token); + } + 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(); + return retVal; +} + +void GPUDisplayConsole::handleConsoleCommand(const std::string& cmd) +{ + std::vector args; + try { + args = parseConsoleCommand(cmd); + } catch (const std::runtime_error& e) { + consolePrint("Invalid command: %s", e.what()); + return; + } + if (args.empty()) { + return; + } + const std::string* name = std::get_if(&args[0]); + const auto it = name ? mConsoleCommands.find(*name) : mConsoleCommands.end(); + if (it == mConsoleCommands.end()) { + consolePrint("Unknown command: %s", cmd.c_str()); + return; + } + it->second(std::span(args).subspan(1)); +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h new file mode 100644 index 0000000000000..75a6cdccf911e --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h @@ -0,0 +1,55 @@ +// 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 + +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 + GPUDisplayConsole(const std::string& title); // First history entry, not selectable + + void handleConsoleKey(uint8_t key); // Key typed while the console is shown + void handleConsoleCommand(const std::string& cmd); // Runs the command named by the first token, error if unknown + static std::vector parseConsoleCommand(const std::string& cmd); // Split into arguments, throws std::runtime_error for invalid input + 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 + 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 a299f7669f3ab..2c95421a20a46 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -13,6 +13,7 @@ /// \author David Rohr #include "GPUDisplayFrontend.h" +#include "GPUDisplayConsole.h" #include "GPUDisplay.h" #ifdef _WIN32 @@ -59,7 +60,7 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; -GPUDisplayFrontend::GPUDisplayFrontend() = default; +GPUDisplayFrontend::GPUDisplayFrontend() : mConsole(std::make_unique(std::string(DISPLAY_WINDOW_NAME) + " Console")) {} GPUDisplayFrontend::~GPUDisplayFrontend() = default; void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) @@ -172,6 +173,8 @@ void GPUDisplayFrontend::sendExternalGUIKey(int32_t v, uint32_t translationMode, signalConfigUpdate(); } +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); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 7686ad9489d1b..ee34f82321fc8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -33,6 +33,7 @@ class GPUReconstruction; class GPUDisplay; class GPUDisplayBackend; class GPUDisplayGUIWrapper; +class GPUDisplayConsole; struct GPUDisplayGUIChannel; class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplayFrontendKeyVars @@ -112,7 +113,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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 mKeys[256] = {false}; // Array of mKeys currently pressed (not while the console is shown) int32_t mDisplayHeight = INIT_HEIGHT; int32_t mDisplayWidth = INIT_WIDTH; @@ -131,6 +132,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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 + 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 @@ -154,7 +156,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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 - bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system + 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index ee9266e15b483..ec548f886029a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -17,24 +17,31 @@ #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 { @@ -180,6 +187,16 @@ void GPUDisplay::checkAndApplyConfigChanges() 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) @@ -193,7 +210,7 @@ void GPUDisplay::HandleExternalGUIKey(int32_t key) } bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; - HandleKey(key); + HandleKey(key, true); mFrontend->mKeys[mFrontend->KEY_SHIFT] = oldShift; } @@ -502,6 +519,7 @@ void GPUDisplayFrontend::listConfig() #include "GPUDisplayFrontendConfigKeys.inc" #include "GPUDisplayFrontendConfigInfo.inc" #include "GPUDisplayFrontendConfigQtGUI.inc" +#include "GPUDisplayFrontendConfigConsole.inc" void GPUDisplay::buildConfig() { @@ -545,6 +563,7 @@ void GPUDisplay::buildConfig() } buildConfigInfo(); buildConfigQtGUI(); + mFrontend->buildConsoleCommands(); rtti->buildHelpText(); } @@ -573,8 +592,14 @@ void GPUDisplayFrontend::showConfigInfo(uint32_t index) } } -void GPUDisplay::HandleKey(uint8_t key) +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..1ccf9d5285c23 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc @@ -0,0 +1,47 @@ +// 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() +{ + auto& commands = mConsole->mConsoleCommands; + commands["help"] = [this](std::span args) { + if (args.size()) { + mConsole->consolePrint("help: no arguments expected"); + return; + } + std::vector names; + for (const auto& [name, function] : mConsole->mConsoleCommands) { + names.emplace_back(name); + } + std::sort(names.begin(), names.end()); + mConsole->consolePrint("Console commands:"); + for (const auto& name : names) { + mConsole->consolePrint(" %s", name.c_str()); + } + }; + commands["clearHistory"] = [this](std::span args) { + if (args.size()) { + mConsole->consolePrint("clearHistory: no arguments expected"); + return; + } + mConsole->mConsoleHistory.resize(1); // Keep the title + mConsole->mConsoleTextCurrent = 0; + mConsole->mConsoleInfoLastMessage.clear(); + }; +} + +#endif // GPUDISPLAYFRONTENDCONFIGCONSOLE_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc index 3c7c742ff9a74..66710fbdbd89d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -20,7 +20,7 @@ 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, &GPUSettingsDisplayLight::drawClusters, &GPUSettingsDisplayLight::drawInitLinks, &GPUSettingsDisplayLight::drawLinks, &GPUSettingsDisplayLight::drawSeeds, + 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); })); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index b97c281896a12..fe81c6ac05449 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -308,6 +308,10 @@ void GPUDisplay::buildConfigKeys() 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/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index e9b861abd131c..0fee1700d953a 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,8 +170,8 @@ 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; + 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; @@ -170,6 +179,10 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 } 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->mGLFWKeyDownMap[handleKey]; me->mKeys[keyPress] = false; @@ -180,13 +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->mGLFWKeyDownMap[me->mLastKeyDown] = keyPress; - me->mKeys[keyPress] = true; + me->mKeys[keyPress] = !me->consoleShown(); me->HandleKey(codepoint); } } @@ -332,6 +349,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)) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index fafde0ca89cc2..87a166c27f61f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -68,6 +68,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend int32_t mWindowHeight = INIT_HEIGHT; 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 5e79e85b6cf84..2b0e0aafb4b0b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -124,7 +124,8 @@ 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'; } @@ -135,7 +136,7 @@ void GPUDisplayFrontendGlut::keyboardFunc(int32_t key, bool special, bool down) { int32_t handleKey = 0, keyPress = 0; GetKey(key, handleKey, keyPress, special); - mKeys[keyPress] = down; + mKeys[keyPress] = down && !consoleShown(); if (down) { HandleKey(handleKey); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx index 2bbc9928aa774..31ab4705fae6c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx @@ -48,6 +48,7 @@ int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint6 {"[7~", KEY_HOME}, {"[8~", KEY_END}, {"[2~", KEY_INSERT}, + {"[3~", KEY_DELETE}, {"[5~", KEY_PAGEUP}, {"[6~", KEY_PAGEDOWN}, {"OP", KEY_F1}, @@ -78,6 +79,7 @@ int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint6 {"[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}, @@ -87,6 +89,7 @@ int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint6 {"[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}, diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h index 4fbe81054c1d2..ad8db27502761 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h @@ -43,6 +43,8 @@ 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 68177449f06c8..ab743dc6dd942 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -52,6 +52,9 @@ GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() 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) { @@ -90,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) { @@ -237,7 +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->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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index d71afb3414668..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,14 +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; + 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); + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); mKeys[wParam] = false; GPUInfo("Key: %d", wParam); return 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 6448c83303c0a..c7bea8912f05b 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) { @@ -352,7 +362,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() case KeyPress: { int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); - mKeys[keyPress] = true; + mKeys[keyPress] = !consoleShown(); HandleKey(handleKey); break; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 6c866b4b166a8..abc4d3c31bf2f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -827,6 +827,13 @@ 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 GPUDisplayFrontendKeyVars::KEY_UP; @@ -848,6 +855,10 @@ int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event) return GPUDisplayFrontendKeyVars::KEY_INSERT; case Qt::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 GPUDisplayFrontendKeyVars::KEY_ENTER; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc index 9df428783a394..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,7 @@ 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(); diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 8d8aec9c306ea..6d7d29426c10c 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -206,8 +206,6 @@ void GPUDisplay::showInfo(const char* info) if (!(mCfgC.mPrintInfoText & 1)) { return; } - mBackend->updateFontScale(mFrontend->getDisplayScale()); - mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; GLPrintText(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); if (mInfoText2Timer.IsRunning()) { @@ -224,7 +222,20 @@ void GPUDisplay::showInfo(const char* info) PrintGLHelpText(colorValue); } } - mBackend->finishText(); +} + +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) From 6d223b2564534ebfa4ee00b19a8ebe0d736248bc Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 8 Nov 2026 22:59:51 +0100 Subject: [PATCH 40/41] GPU Display: Improve synchronization and avoid possible race condition at startup --- GPU/GPUTracking/display/GPUDisplay.cxx | 20 +++-- GPU/GPUTracking/display/GPUDisplay.h | 2 +- .../display/frontend/GPUDisplayFrontend.cxx | 74 +++++++++++-------- .../display/frontend/GPUDisplayFrontend.h | 13 +++- .../frontend/GPUDisplayFrontendGlfw.cxx | 5 -- .../frontend/GPUDisplayFrontendGlut.cxx | 12 +-- .../frontend/GPUDisplayFrontendNone.cxx | 4 - .../frontend/GPUDisplayFrontendWayland.cxx | 5 -- .../frontend/GPUDisplayFrontendX11.cxx | 5 -- 9 files changed, 68 insertions(+), 72 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index d4c7557b7c088..9f9abda84cc21 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -171,12 +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; } - mInitResult = retVal == 0 ? 1 : -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); } @@ -790,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 05e2bed6296db..ddc239c300297 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -330,7 +330,7 @@ class GPUDisplay : public GPUDisplayInterface 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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 2c95421a20a46..5c6c09a334061 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -18,9 +18,9 @@ #ifdef _WIN32 #include "GPUDisplayFrontendWindows.h" -#else -#include #endif +#include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include "GPUDisplayFrontendX11.h" @@ -61,22 +61,31 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; GPUDisplayFrontend::GPUDisplayFrontend() : mConsole(std::make_unique(std::string(DISPLAY_WINDOW_NAME) + " Console")) {} -GPUDisplayFrontend::~GPUDisplayFrontend() = default; +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() @@ -341,35 +350,38 @@ 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 - mDisplay->buildConfig(); - if (mDisplay->cfg().listFrontendOptionHandling) { - listConfig(); - } - 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::unique_lock lock(mExitStateMutex); - if (mDisplayRunning) { - mDisplay->mCfgC.mDisplayControl.store(2); - StopDisplayA(); - mDisplay->UnblockDisplay(); + { + std::unique_lock lock(mExitStateMutex); + if (mDisplayRunning) { + 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(); + } + std::lock_guard lock(mExitStateMutex); + if (mDisplay->mInitResult == 1) { // For a new StartDisplay, not after a failed initialization or an error (undefined state) + mStartDisplayDone = false; + mDisplay->mInitResult = 0; } - mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); } void GPUDisplayFrontend::StopDisplayA() {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index ee34f82321fc8..4f103f80768dc 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -18,9 +18,11 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" #include "GPUDisplayFrontendKeyVars.h" +#include #include #include #include +#include #include #include #include @@ -86,9 +88,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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), 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 + 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 @@ -104,6 +106,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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 std::array mKeyDownMap = {}; // Keyboard / Mouse actions @@ -124,7 +129,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 0fee1700d953a..6e345ab1a9366 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -332,11 +332,6 @@ 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)) { if (HandleUpdatesAndDrawGLScene()) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 2b0e0aafb4b0b..ebe63b5bc78ca 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -28,7 +28,6 @@ #include #endif -#include using namespace o2::gpu; GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() @@ -230,11 +229,6 @@ 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); - } - 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); @@ -250,9 +244,9 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() applyInitialWindowState(); SetVSync(vsyncEnabled()); - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; + if (InitDisplay()) { + fprintf(stderr, "Error in OpenGL initialization\n"); + return (1); } glutMainLoop(); { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index f195fce1977ce..98a5654dcdbcd 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -27,10 +27,6 @@ int32_t GPUDisplayFrontendNone::FrontendMain() if (InitDisplay()) { return 1; } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } do { HandleUpdatesAndDrawGLScene(); waitForFPSLimit(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index ab743dc6dd942..54a915d25f551 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -564,11 +564,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - while (mDisplay->mCfgC.mDisplayControl.load() != 2) { bool error = false; while (!error && wl_display_prepare_read(mWayland) != 0) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index c7bea8912f05b..2b9f37b33efd6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -311,11 +311,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - while (1) { do { if (getDisplayControl() == 2) { From 1efa249faacf9cd7faea00bde3b612f12ea0f374 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Sun, 8 Nov 2026 23:30:23 +0100 Subject: [PATCH 41/41] GPU Display Console: Add the possibility to execute a script file --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 1 + .../display/frontend/GPUDisplayConsole.cxx | 130 ++++++++++++++---- .../display/frontend/GPUDisplayConsole.h | 41 ++++-- .../display/frontend/GPUDisplayFrontend.cxx | 11 +- .../display/frontend/GPUDisplayFrontend.h | 1 + .../frontend/GPUDisplayFrontendConfig.cxx | 17 ++- .../GPUDisplayFrontendConfigConsole.inc | 33 ++--- 7 files changed, 161 insertions(+), 73 deletions(-) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 987593b00599c..ee82d5c8f084c 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -529,6 +529,7 @@ AddOption(listFrontendOptionHandling, bool, false, "", 0, "List all assigned key 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) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx index 69e83e7430d44..17134912d37af 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx @@ -13,13 +13,14 @@ /// \author David Rohr #include "GPUDisplayConsole.h" -#include "GPULogging.h" #include #include #include #include #include #include +#include +#include using namespace o2::gpu; @@ -124,30 +125,19 @@ void GPUDisplayConsole::consoleLimitHistory(uint32_t historySize) // 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::vector GPUDisplayConsole::parseConsoleCommand(const std::string& cmd) + +std::tuple> GPUDisplayConsole::parseConsoleCommand(std::string_view cmd) const { - std::vector retVal; + 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) { - return; + if (inToken) { + tokens.emplace_back(std::move(token), plainText); + token.clear(); + inToken = plainText = false; } - const char* begin = token.c_str() + (token[0] == '+' || token[0] == '-'); - const size_t nDigits = std::count_if(begin, token.c_str() + token.size(), [](char c) { return c >= '0' && c <= '9'; }); - const size_t nDots = std::count(begin, token.c_str() + token.size(), '.'); - const bool number = !plainText && nDigits && nDigits + nDots == (size_t)(token.c_str() + token.size() - begin); - int64_t intValue = 0; - if (number && nDots == 0 && std::from_chars(begin, token.c_str() + token.size(), intValue).ec == std::errc()) { - retVal.emplace_back(token[0] == '-' ? -intValue : intValue); - } else if (number && nDots <= 1) { - retVal.emplace_back(std::stod(token)); // Also integers out of the int64_t range - } else { - retVal.emplace_back(token); - } - token.clear(); - inToken = plainText = false; }; for (char c : cmd) { if (escape) { @@ -175,26 +165,110 @@ std::vector GPUDisplayConsole::parseConsoleComman throw std::runtime_error(quote ? "unterminated quote" : "backslash at the end"); } finishToken(); - return retVal; + 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(const std::string& cmd) +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 { - args = parseConsoleCommand(cmd); + 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; } - if (args.empty()) { + command->func(args); +} + +void GPUDisplayConsole::runScript(std::string_view script) +{ + if (mScriptDepth >= 16) { + consolePrint("Scripts nested too deeply"); return; } - const std::string* name = std::get_if(&args[0]); - const auto it = name ? mConsoleCommands.find(*name) : mConsoleCommands.end(); - if (it == mConsoleCommands.end()) { - consolePrint("Unknown command: %s", cmd.c_str()); + 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; } - it->second(std::span(args).subspan(1)); + 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 index 75a6cdccf911e..9529bdb33387e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -31,24 +33,33 @@ class GPUDisplayConsole : public GPUDisplayFrontendKeyVars // Text console of th { public: using consoleArg = std::variant; // Argument of a console command - GPUDisplayConsole(const std::string& title); // First history entry, not selectable + 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(const std::string& cmd); // Runs the command named by the first token, error if unknown - static std::vector parseConsoleCommand(const std::string& cmd); // Split into arguments, throws std::runtime_error for invalid input - 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 + 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 - 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 + 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 5c6c09a334061..37d1eadeeedab 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -192,8 +192,13 @@ int32_t GPUDisplayFrontend::HandleUpdatesAndDrawGLScene(bool suspended) { HandleExternalGUISignals(); mDisplay->checkAndApplyConfigChanges(); - const int32_t retVal = mDisplay->DrawGLScene(suspended); - mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + 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; } @@ -379,7 +384,7 @@ void GPUDisplayFrontend::StopDisplay() } std::lock_guard lock(mExitStateMutex); if (mDisplay->mInitResult == 1) { // For a new StartDisplay, not after a failed initialization or an error (undefined state) - mStartDisplayDone = false; + mStartDisplayDone = mStartupScriptDone = false; mDisplay->mInitResult = 0; } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 4f103f80768dc..f29f57687c35a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -109,6 +109,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index ec548f886029a..42de138145b7d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -503,16 +503,27 @@ void GPUDisplayFrontend::listConfig() GPUInfo(" %2u %-28s %s", i, rtti.keyMapNames[i].c_str(), mappedKeys[i].c_str()); } GPUInfo(""); - std::string available; + 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) { - available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + 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("Available keys: %s", available.c_str()); + 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()); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc index 1ccf9d5285c23..5d0353dc9458e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc @@ -17,31 +17,16 @@ void GPUDisplayFrontend::buildConsoleCommands() { + using args = std::span; auto& commands = mConsole->mConsoleCommands; - commands["help"] = [this](std::span args) { - if (args.size()) { - mConsole->consolePrint("help: no arguments expected"); - return; - } - std::vector names; - for (const auto& [name, function] : mConsole->mConsoleCommands) { - names.emplace_back(name); - } - std::sort(names.begin(), names.end()); - mConsole->consolePrint("Console commands:"); - for (const auto& name : names) { - mConsole->consolePrint(" %s", name.c_str()); - } - }; - commands["clearHistory"] = [this](std::span args) { - if (args.size()) { - mConsole->consolePrint("clearHistory: no arguments expected"); - return; - } - mConsole->mConsoleHistory.resize(1); // Keep the title - mConsole->mConsoleTextCurrent = 0; - mConsole->mConsoleInfoLastMessage.clear(); - }; + 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