diff --git a/.clang-format b/.clang-format index f0eb7d31df8e3..6d629eef2225e 100644 --- a/.clang-format +++ b/.clang-format @@ -27,6 +27,7 @@ IndentWidth: 2 # It is broken on windows. Breaks all #include "header.h" --- Language: Cpp +AttributeMacros: ['GPUrestrict'] # GPU qualifier macro: GPUrestrict() is otherwise parsed as function call, e.g. 'a -> b' after it MaxEmptyLinesToKeep: 1 KeepEmptyLinesAtTheStartOfBlocks: true NamespaceIndentation: None diff --git a/GPU/Common/GPUCommonArray.h b/GPU/Common/GPUCommonArray.h index 4a80cbe70cc4c..287e5e1780b4d 100644 --- a/GPU/Common/GPUCommonArray.h +++ b/GPU/Common/GPUCommonArray.h @@ -41,8 +41,10 @@ struct array { } T m_internal_V__[N]; }; +// clang-format off template GPUd() array(T, E...)->array; +// clang-format on #else template using array = std::array; diff --git a/GPU/Common/GPUCommonConstants.h b/GPU/Common/GPUCommonConstants.h index f0b62a6db9650..95cf89cc6d0a7 100644 --- a/GPU/Common/GPUCommonConstants.h +++ b/GPU/Common/GPUCommonConstants.h @@ -21,6 +21,6 @@ namespace o2::gpu::gpu_common_constants { static GPUglobalconstexpr() const float kCLight = 0.000299792458f; // TODO: Duplicate of MathConstants, fix this now that we use only OpenCL CPP static GPUglobalconstexpr() const float kZeroFieldCut = 0.013f; -} +} // namespace o2::gpu::gpu_common_constants #endif 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/Base/GPUReconstructionKernelIncludes.template.h b/GPU/GPUTracking/Base/GPUReconstructionKernelIncludes.template.h index 40d53fe949153..c9712cfb7a011 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionKernelIncludes.template.h +++ b/GPU/GPUTracking/Base/GPUReconstructionKernelIncludes.template.h @@ -14,5 +14,4 @@ // clang-format off $>,APPEND,.h">,PREPEND,#include ">, -> -// clang-format on +> // clang-format on diff --git a/GPU/GPUTracking/Base/GPUReconstructionKernelList.template.h b/GPU/GPUTracking/Base/GPUReconstructionKernelList.template.h index 92fa2546d3ca0..187ba8fa95dfb 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionKernelList.template.h +++ b/GPU/GPUTracking/Base/GPUReconstructionKernelList.template.h @@ -16,5 +16,4 @@ #include "GPUReconstructionKernelMacros.h" // clang-format off -$,> -// clang-format on +$,> // clang-format on diff --git a/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx b/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx index 4570d4da5d000..8899e6d1d7c6b 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionTimeframe.cxx @@ -24,6 +24,8 @@ #include "AliHLTTPCRawCluster.h" #include "TPCFastTransformPOD.h" #include "GPUO2DataTypes.h" +#define GPUSETTINGS_STANDALONE +#define GPUSETTINGS_DISPLAY #include "GPUSettings.h" #include diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu index 63992bed65fc5..d81b1e2cdfb4a 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu @@ -331,9 +331,9 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() } } -#ifndef __HIPCC__ // CUDA +#ifndef __HIPCC__ // CUDA dummyInitKernel<<>>(mDeviceMemoryBase); // TODO: Can't we just use the CUDA version and hipify will take care of the rest? -#else // HIP +#else // HIP hipLaunchKernelGGL(HIP_KERNEL_NAME(dummyInitKernel), dim3(mMultiprocessorCount), dim3(256), 0, 0, mDeviceMemoryBase); #endif diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu index 82759aab48d70..39df85239de65 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu @@ -21,10 +21,7 @@ // clang-format off @O2_GPU_KERNEL_TEMPLATE_FILES@ -// clang-format on extern "C" { -// clang-format off @O2_GPU_KERNEL_TEMPLATE_REPLACE@ -// clang-format on -} +} // clang-format on: not before the }, the placeholders confuse clang-format diff --git a/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip b/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip index 7cb895cadd770..56e4c6ba04def 100644 --- a/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip +++ b/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip @@ -21,10 +21,7 @@ // clang-format off @O2_GPU_KERNEL_TEMPLATE_FILES@ -// clang-format on extern "C" { -// clang-format off @O2_GPU_KERNEL_TEMPLATE_REPLACE@ -// clang-format on -} +} // clang-format on: not before the }, the placeholders confuse clang-format diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index 6f2e643fc98f6..82ea55769991a 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/GPUSettingsListDisplay.h Definitions/GPUSettingsList.h DataTypes/GPUDataTypesIO.h DataTypes/GPUDataTypesConfig.h DataTypes/GPUDataTypesQA.h DataTypes/GPUTRDTrack.h DataTypes/GPUTRDRecoParam.h DataTypes/CalibdEdxTrackTopologyPol.h DataTypes/CalibdEdxTrackTopologySpline.h) # Manual dependencies for ROOT dictionary generation set(SRCS_NO_CINT DataTypes/GPUMemorySizeScalers.cxx @@ -120,6 +120,7 @@ set(HDRS_INSTALL Base/GPUReconstructionIncludesITS.h Base/GPUReconstructionKernelMacros.h Base/GPUReconstructionCPUKernels.h + Base/GPUStdSystemHeaders.h DataCompression/GPUTPCClusterRejection.h DataCompression/GPUTPCCompressionKernels.inc DataCompression/TPCClusterDecompressionCore.inc @@ -156,6 +157,7 @@ set(HDRS_INSTALL SectorTracker/GPUTPCMCInfo.h SectorTracker/GPUTPCTracklet.h SectorTracker/GPUTPCTrackLinearisation.h + SectorTracker/GPUTPCRow.h TPCConvert/GPUTPCConvertImpl.h TRDTracking/GPUTRDGeometry.h TRDTracking/GPUTRDSpacePoint.h @@ -165,6 +167,8 @@ set(HDRS_INSTALL TRDTracking/GPUTRDTrackPoint.h TRDTracking/GPUTRDTrackPoint.h DataTypes/GPUTPCGMPolynomialField.h + DataTypes/GPUSettingsParam.h + Definitions/GPUSettingsListStandalone.h ) set(SRCS_NO_CINT ${SRCS_NO_CINT} display/GPUDisplayInterface.cxx) diff --git a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx index e208f6ba44b9f..9bc6f40a052fa 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx @@ -67,7 +67,7 @@ GPUdii() void GPUTPCCompressionKernels::Thread clusters[hit.sector][hit.row][hit.num - clusters->clusterOffset[hit.sector][hit.row]]; + const ClusterNative& GPUrestrict() orgCl = clusters->clusters[hit.sector][hit.row][hit.num - clusters->clusterOffset[hit.sector][hit.row]]; constexpr GPUTPCGeometry geo; float x = geo.Row2X(hit.row); float y = track.LinearPad2Y(hit.sector, orgCl.getPad(), geo.PadWidth(hit.row), geo.NPads(hit.row)); @@ -291,7 +291,7 @@ GPUdii() void GPUTPCCompressionKernels::Thread clusters[iSector][iRow][sortBuffer[j]]; + const ClusterNative& GPUrestrict() orgCl = clusters->clusters[iSector][iRow][sortBuffer[j]]; int32_t preId = j != 0 ? (int32_t)sortBuffer[j - 1] : (totalCount != 0 ? (int32_t)smem.lastIndex : -1); GPUTPCCompression_EncodeUnattached(param.rec.tpc.compressionTypeMask, orgCl, c.timeDiffU[outidx], c.padDiffU[outidx], preId == -1 ? nullptr : &clusters->clusters[iSector][iRow][preId]); diff --git a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx index 9353e3664d05a..6fd2a0acda6ef 100644 --- a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx +++ b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx @@ -14,6 +14,7 @@ #include "GPUConfigDump.h" #include "GPUDataTypesIO.h" +#define GPUSETTINGS_ALL #include "GPUSettings.h" #include @@ -27,10 +28,7 @@ using namespace o2::gpu; namespace { GPUSettingsStandalone configStandalone; -std::vector> qprint_global; -#define QCONFIG_PRINT -#include "utils/qconfig_internal.h" -#undef QCONFIG_PRINT +#include "utils/qconfig_print.inc" } // namespace void GPUConfigDump::dumpConfig(const GPUSettingsRec* rec, const GPUSettingsProcessing* proc, const GPUSettingsQA* qa, const GPUSettingsDisplay* display, const GPUSettingsDeviceBackend* device, const GPURecoStepConfiguration* workflow) diff --git a/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.cxx b/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.cxx index f3b7e07c4c43b..3820b8c4f0054 100644 --- a/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.cxx +++ b/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.cxx @@ -35,6 +35,7 @@ using namespace o2::gpu; #define AddCustomCPP(...) #define AddHelp(...) #define AddShortcut(...) +#include "GPUSettingsListDisplay.h" #include "GPUSettingsList.h" #undef BeginNamespace #undef EndNamespace @@ -80,6 +81,7 @@ GPUSettingsO2 GPUO2InterfaceConfiguration::ReadConfigurableParam(GPUO2InterfaceC #define AddCustomCPP(...) #define AddHelp(...) #define AddShortcut(...) +#include "GPUSettingsListDisplay.h" #include "GPUSettingsList.h" #undef BeginNamespace #undef EndNamespace diff --git a/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.h b/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.h index 503ba3e0d51ae..d5102dc71c96d 100644 --- a/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.h +++ b/GPU/GPUTracking/DataTypes/GPUO2ConfigurableParam.h @@ -25,6 +25,7 @@ #include "GPUO2ExternalUser.h" #include "CommonUtils/ConfigurableParam.h" #include "CommonUtils/ConfigurableParamHelper.h" +#define GPUSETTINGS_DISPLAY #include "GPUSettings.h" #include "GPUDefMacros.h" #include @@ -59,6 +60,7 @@ #define AddShortcut(...) #define AddOptionRTC(...) AddOption(__VA_ARGS__) #define AddOptionArrayRTC(...) AddOptionArray(__VA_ARGS__) +#include "GPUSettingsListDisplay.h" #include "GPUSettingsList.h" #undef BeginNamespace #undef EndNamespace diff --git a/GPU/GPUTracking/DataTypes/GPUSettings.h b/GPU/GPUTracking/DataTypes/GPUSettings.h index dff33328e437f..bfbd3df33526f 100644 --- a/GPU/GPUTracking/DataTypes/GPUSettings.h +++ b/GPU/GPUTracking/DataTypes/GPUSettings.h @@ -16,70 +16,11 @@ #define GPUSETTINGS_H #include "GPUCommonDef.h" -#include "GPUDataTypesConfig.h" -#include "GPUTPCGMMergedTrackHit.h" -#ifndef GPUCA_GPUCODE_DEVICE -#include -#include -#endif - -namespace o2::gpu -{ -class GPUDisplayFrontendInterface; -class GPUReconstruction; - -class GPUSettings -{ - public: - enum CompressionModes { CompressionTruncate = 1, - CompressionDifferences = 2, - CompressionTrackModel = 4, - CompressionFull = 7 }; - enum CompressionSort { SortTime = 0, - SortPad = 1, - SortZTimePad = 2, - SortZPadTime = 3, - SortNoSort = 4 }; - enum CompressionRejection { RejectionNone = 0, - RejectionStrategyA = 1, - RejectionStrategyB = 2 }; - - static GPUglobalconstexpr() const uint32_t TPC_MAX_TF_TIME_BIN = ((256 * 3564 + 2 * 8 - 2) / 8); -}; - -// Settings describing the global run parameters -struct GPUSettingsGRP { - // All new members must be sizeof(int32_t) resp. sizeof(float) for alignment reasons!, default value for newly added members for old data will be 0. - float solenoidBzNominalGPU = -5.00668f; // solenoid field strength - int32_t constBz = 0; // for test-MC events with constant Bz - int32_t removed0 = 0; // Obsolete parameter, dummy value needed to support reading old dumps - int32_t grpContinuousMaxTimeBin = -2; // 0 for triggered events, -1 for automatic setting, -2 invalid default - int32_t needsClusterer = 0; // Set to true if the data requires the clusterizer - int32_t doCompClusterDecode = 0; // Set to true if the data contains compressed TPC clusters - int32_t tpcCutTimeBin = 0; // Cut TPC clusters and digits >= this cut -}; +#include "GPUSettingsTypes.h" -// Parameters of the current time frame -struct GPUSettingsTF { - int32_t hasTfStartOrbit = 0; - int32_t tfStartOrbit = 0; - int32_t hasRunStartOrbit = 0; - int32_t runStartOrbit = 0; - int32_t hasSimStartOrbit = 0; - int32_t simStartOrbit = 0; - int32_t hasNHBFPerTF = 0; - int32_t nHBFPerTF = 0; -}; - -// Settings defining the setup of the GPUReconstruction processing (basically selecting the device / class instance) -struct GPUSettingsDeviceBackend { - uint32_t deviceType = gpudatatypes::DeviceType::CPU; // Device type, shall use gpudatatypes::DEVICE_TYPE constants, e.g. CPU / CUDA - uint8_t forceDeviceType = 1; // Fail if device initialization fails, otherwise falls back to CPU - GPUReconstruction* master = nullptr; // GPUReconstruction master object -}; - -} // namespace o2::gpu +#endif // GPUSETTINGS_H +// Outside of header guard, since might run it multiple times for different config structs #ifdef GPUCA_GPUCODE_DEVICE #define QCONFIG_GPU #endif @@ -87,5 +28,3 @@ struct GPUSettingsDeviceBackend { #ifndef GPUCA_GPUCODE_GENRTC #include "utils/qconfig.h" #endif - -#endif diff --git a/GPU/GPUTracking/DataTypes/GPUSettingsParam.h b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h new file mode 100644 index 0000000000000..1c6a7bcf8a259 --- /dev/null +++ b/GPU/GPUTracking/DataTypes/GPUSettingsParam.h @@ -0,0 +1,23 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUSettingsParam.h +/// \author David Rohr + +// clang-format off + +// No include guard: included multiple times by qconfig with different macro definitions + +// Derrived parameters used in GPUParam +BeginHiddenConfig(GPUSettingsParam, param) +AddVariableRTC(continuousTracking, int8_t, 0) // Continuous tracking, estimate bz and errors for abs(z) = 125cm during seeding +AddVariableRTC(dodEdx, int8_t, 0) // Do dEdx computation +EndConfig() diff --git a/GPU/GPUTracking/DataTypes/GPUSettingsTypes.h b/GPU/GPUTracking/DataTypes/GPUSettingsTypes.h new file mode 100644 index 0000000000000..cefbf5b8d4f01 --- /dev/null +++ b/GPU/GPUTracking/DataTypes/GPUSettingsTypes.h @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUSettingsTypes.h +/// \author David Rohr + +#ifndef GPUSETTINGSTYPES_H +#define GPUSETTINGSTYPES_H + +#include "GPUCommonDef.h" +#include "GPUDataTypesConfig.h" +#include "GPUTPCGMMergedTrackHit.h" +#ifndef GPUCA_GPUCODE_DEVICE +#include +#include +#endif + +namespace o2::gpu +{ +class GPUDisplayFrontendInterface; +class GPUReconstruction; + +class GPUSettings +{ + public: + enum CompressionModes { CompressionTruncate = 1, + CompressionDifferences = 2, + CompressionTrackModel = 4, + CompressionFull = 7 }; + enum CompressionSort { SortTime = 0, + SortPad = 1, + SortZTimePad = 2, + SortZPadTime = 3, + SortNoSort = 4 }; + enum CompressionRejection { RejectionNone = 0, + RejectionStrategyA = 1, + RejectionStrategyB = 2 }; + + static GPUglobalconstexpr() const uint32_t TPC_MAX_TF_TIME_BIN = ((256 * 3564 + 2 * 8 - 2) / 8); +}; + +// Settings describing the global run parameters +struct GPUSettingsGRP { + // All new members must be sizeof(int32_t) resp. sizeof(float) for alignment reasons!, default value for newly added members for old data will be 0. + float solenoidBzNominalGPU = -5.00668f; // solenoid field strength + int32_t constBz = 0; // for test-MC events with constant Bz + int32_t removed0 = 0; // Obsolete parameter, dummy value needed to support reading old dumps + int32_t grpContinuousMaxTimeBin = -2; // 0 for triggered events, -1 for automatic setting, -2 invalid default + int32_t needsClusterer = 0; // Set to true if the data requires the clusterizer + int32_t doCompClusterDecode = 0; // Set to true if the data contains compressed TPC clusters + int32_t tpcCutTimeBin = 0; // Cut TPC clusters and digits >= this cut +}; + +// Parameters of the current time frame +struct GPUSettingsTF { + int32_t hasTfStartOrbit = 0; + int32_t tfStartOrbit = 0; + int32_t hasRunStartOrbit = 0; + int32_t runStartOrbit = 0; + int32_t hasSimStartOrbit = 0; + int32_t simStartOrbit = 0; + int32_t hasNHBFPerTF = 0; + int32_t nHBFPerTF = 0; +}; + +// Settings defining the setup of the GPUReconstruction processing (basically selecting the device / class instance) +struct GPUSettingsDeviceBackend { + uint32_t deviceType = gpudatatypes::DeviceType::CPU; // Device type, shall use gpudatatypes::DEVICE_TYPE constants, e.g. CPU / CUDA + uint8_t forceDeviceType = 1; // Fail if device initialization fails, otherwise falls back to CPU + GPUReconstruction* master = nullptr; // GPUReconstruction master object +}; + +} // namespace o2::gpu + +#endif // GPUSETTINGSTYPES_H diff --git a/GPU/GPUTracking/DataTypes/GPUTPCGeometry.h b/GPU/GPUTracking/DataTypes/GPUTPCGeometry.h index 14f2021269591..ed334aef3a75f 100644 --- a/GPU/GPUTracking/DataTypes/GPUTPCGeometry.h +++ b/GPU/GPUTracking/DataTypes/GPUTPCGeometry.h @@ -121,8 +121,7 @@ class GPUTPCGeometry GPUd() static constexpr int32_t MaxNPadsPerRow() { return 138; } GPUd() static constexpr float PadWidth(int32_t row) { return (gputpcgeometry_internal::mPadWidthRow[row]); } #else - GPUd() static constexpr int32_t GetRegion(int32_t row) { return (row < 63 ? 0 : row < 63 + 64 ? 1 - : 2); } + GPUd() static constexpr int32_t GetRegion(int32_t row) { return (row < 63 ? 0 : (row < 63 + 64 ? 1 : 2)); } GPUd() static constexpr int32_t GetRegionRows(int32_t region) { return 0; } // dummy GPUd() static constexpr int32_t GetRegionStart(int32_t region) { return 0; } // dummy GPUd() static constexpr int32_t GetROC(int32_t row) { return GetRegion(row); } diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index fa560ec535313..e038398626ba1 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -19,19 +19,12 @@ // Create plain-C struct for GPU code. // Create static constexpr with default values for GPU run time compilation -#include "GPUCommonDef.h" -#ifndef GPUSETTINGS_H -#error Please include GPUSettings.h! -#endif +#include "GPUSettingsTypes.h" -// clang-format off +#if (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) // File should not be included without defining the macros, but rootcling will do for dictionary generation -#ifdef QCONFIG_INSTANCE -using namespace o2::gpu; -#endif -#ifdef BeginNamespace // File should not be included without defining the macros, but rootcling will do for dictionary generation -BeginNamespace(o2) -BeginNamespace(gpu) +// clang-format off +BeginNamespace(o2::gpu) // Settings concerning the reconstruction, stored as parameters in GPU constant memory // There must be no bool in here, use int8_t, as sizeof(bool) is compiler dependent and fails on GPUs!!!!!! @@ -260,40 +253,40 @@ EndConfig() // Settings steering the processing of NN Clusterization BeginSubConfig(GPUSettingsProcessingNNclusterizer, nn, configStandalone.proc, "NN", 0, "Processing settings for neural network clusterizer", proc_nn) -AddOption(applyNNclusterizer, int, 0, "", 0, "(bool, default = 0), if the neural network clusterizer should be used") +AddOption(applyNNclusterizer, int32_t, 0, "", 0, "(bool, default = 0), if the neural network clusterizer should be used") AddOption(nnInferenceDevice, std::string, "CPU", "", 0, "(std::string) Specify inference device (cpu (default), rocm, cuda)") -AddOption(nnInferenceDeviceId, unsigned int, 0, "", 0, "(unsigned int) Specify inference device id") -AddOption(nnInferenceAllocateDevMem, int, 0, "", 0, "(bool, default = 0), if the device memory should be allocated for inference") +AddOption(nnInferenceDeviceId, uint32_t, 0, "", 0, "(uint32_t) Specify inference device id") +AddOption(nnInferenceAllocateDevMem, int32_t, 0, "", 0, "(bool, default = 0), if the device memory should be allocated for inference") AddOption(nnInferenceInputDType, std::string, "FP32", "", 0, "(std::string) Specify the datatype for which inference is performed (FP32: default, fp16)") // fp32 or fp16 AddOption(nnInferenceOutputDType, std::string, "FP32", "", 0, "(std::string) Specify the datatype for which inference is performed (fp32: default, fp16)") // fp32 or fp16 -AddOption(nnInferenceIntraOpNumThreads, int, 1, "", 0, "Number of threads used to evaluate one neural network (ONNX: SetIntraOpNumThreads). 0 = auto-detect, can lead to problems on SLURM systems") -AddOption(nnInferenceInterOpNumThreads, int, 1, "", 0, "Number of threads used to evaluate one neural network (ONNX: SetInterOpNumThreads). 0 = auto-detect, can lead to problems on SLURM systems") -AddOption(nnInferenceEnableOrtOptimization, unsigned int, 99, "", 0, "Enables graph optimizations in ONNX Runtime. Can be [0, 1, 2, 99] -> see https://github.com/microsoft/onnxruntime/blob/3f71d637a83dc3540753a8bb06740f67e926dc13/include/onnxruntime/core/session/onnxruntime_c_api.h#L347") -AddOption(nnInferenceUseDeterministicCompute, int, 0, "", 0, "Enables deterministic compute in ONNX Runtime were possible. Can be [0, 1] -> see https://github.com/microsoft/onnxruntime/blob/3b97d79b3c12dbf93aa0d563f345714596dc8ab6/onnxruntime/core/framework/session_options.h#L208") -AddOption(nnInferenceOrtProfiling, int, 0, "", 0, "Enables profiling of model execution in ONNX Runtime") +AddOption(nnInferenceIntraOpNumThreads, int32_t, 1, "", 0, "Number of threads used to evaluate one neural network (ONNX: SetIntraOpNumThreads). 0 = auto-detect, can lead to problems on SLURM systems") +AddOption(nnInferenceInterOpNumThreads, int32_t, 1, "", 0, "Number of threads used to evaluate one neural network (ONNX: SetInterOpNumThreads). 0 = auto-detect, can lead to problems on SLURM systems") +AddOption(nnInferenceEnableOrtOptimization, uint32_t, 99, "", 0, "Enables graph optimizations in ONNX Runtime. Can be [0, 1, 2, 99] -> see https://github.com/microsoft/onnxruntime/blob/3f71d637a83dc3540753a8bb06740f67e926dc13/include/onnxruntime/core/session/onnxruntime_c_api.h#L347") +AddOption(nnInferenceUseDeterministicCompute, int32_t, 0, "", 0, "Enables deterministic compute in ONNX Runtime were possible. Can be [0, 1] -> see https://github.com/microsoft/onnxruntime/blob/3b97d79b3c12dbf93aa0d563f345714596dc8ab6/onnxruntime/core/framework/session_options.h#L208") +AddOption(nnInferenceOrtProfiling, int32_t, 0, "", 0, "Enables profiling of model execution in ONNX Runtime") AddOption(nnInferenceOrtProfilingPath, std::string, ".", "", 0, "If nnInferenceOrtProfiling is set, the path to store the profiling data") -AddOption(nnInferenceVerbosity, int, 2, "", 0, "0: All debugs; 1: Warnings + major debugs; 2: Warnings; >=3: No messages") -AddOption(nnClusterizerAddIndexData, int, 1, "", 0, "If normalized index data (sector, row, pad), should be appended to the input") -AddOption(nnClusterizerSizeInputRow, int, 3, "", 0, "Size of the input to the NN (currently calculated as (length-1)/2") -AddOption(nnClusterizerSizeInputPad, int, 3, "", 0, "Size of the input to the NN (currently calculated as (length-1)/2") -AddOption(nnClusterizerSizeInputTime, int, 3, "", 0, "Size of the input to the NN (currently calculated as (length-1)/2") -AddOption(nnClusterizerUseCfRegression, int, 0, "", 0, "(bool, default = false) If true, use the regression from the native clusterizer and not the NN") -AddOption(nnClusterizerApplyCfDeconvolution, int, 0, "", 0, "Applies the CFDeconvolution kernel before the digits to the network are filled") -AddOption(nnClusterizerBatchedMode, unsigned int, 1, "", 0, "(int, default = 1) If >1, the NN is evaluated on batched input of size specified in this variable") -AddOption(nnClusterizerVerbosity, int, -1, "", 0, "(int, default = -1) If >0, logging messages of the clusterizer will be displayed. Higher number = higher verbosity") -AddOption(nnClusterizerBoundaryFillValue, int, -1, "", 0, "Fill value for the boundary of the input to the NN") -AddOption(nnClusterizerApplyNoiseSuppression, int, 1, "", 0, "Applies the NoiseSuppression kernel before the digits to the network are filled") -AddOption(nnClusterizerSetDeconvolutionFlags, int, 1, "", 0, "Runs the deconvolution kernel without overwriting the charge in order to make cluster-to-track attachment identical to heuristic CF") +AddOption(nnInferenceVerbosity, int32_t, 2, "", 0, "0: All debugs; 1: Warnings + major debugs; 2: Warnings; >=3: No messages") +AddOption(nnClusterizerAddIndexData, int32_t, 1, "", 0, "If normalized index data (sector, row, pad), should be appended to the input") +AddOption(nnClusterizerSizeInputRow, int32_t, 3, "", 0, "Size of the input to the NN (currently calculated as (length-1)/2") +AddOption(nnClusterizerSizeInputPad, int32_t, 3, "", 0, "Size of the input to the NN (currently calculated as (length-1)/2") +AddOption(nnClusterizerSizeInputTime, int32_t, 3, "", 0, "Size of the input to the NN (currently calculated as (length-1)/2") +AddOption(nnClusterizerUseCfRegression, int32_t, 0, "", 0, "(bool, default = false) If true, use the regression from the native clusterizer and not the NN") +AddOption(nnClusterizerApplyCfDeconvolution, int32_t, 0, "", 0, "Applies the CFDeconvolution kernel before the digits to the network are filled") +AddOption(nnClusterizerBatchedMode, uint32_t, 1, "", 0, "(int, default = 1) If >1, the NN is evaluated on batched input of size specified in this variable") +AddOption(nnClusterizerVerbosity, int32_t, -1, "", 0, "(int, default = -1) If >0, logging messages of the clusterizer will be displayed. Higher number = higher verbosity") +AddOption(nnClusterizerBoundaryFillValue, int32_t, -1, "", 0, "Fill value for the boundary of the input to the NN") +AddOption(nnClusterizerApplyNoiseSuppression, int32_t, 1, "", 0, "Applies the NoiseSuppression kernel before the digits to the network are filled") +AddOption(nnClusterizerSetDeconvolutionFlags, int32_t, 1, "", 0, "Runs the deconvolution kernel without overwriting the charge in order to make cluster-to-track attachment identical to heuristic CF") AddOption(nnClassificationPath, std::string, "network_class.onnx", "", 0, "The classification network path") AddOption(nnRegressionPath, std::string, "network_reg.onnx", "", 0, "The regression network path") AddOption(nnClassThreshold, float, 0.5, "", 0, "The cutoff at which clusters will be accepted / rejected") -AddOption(nnSigmoidTrafoClassThreshold, int, 1, "", 0, "If true (default), then the classification threshold is transformed by an inverse sigmoid function. This depends on how the network was trained (with a sigmoid as acitvation function in the last layer or not)") +AddOption(nnSigmoidTrafoClassThreshold, int32_t, 1, "", 0, "If true (default), then the classification threshold is transformed by an inverse sigmoid function. This depends on how the network was trained (with a sigmoid as acitvation function in the last layer or not)") AddOption(nnEvalMode, std::string, "c1:r1", "", 0, "Concatenation of modes, e.g. c1:r1 (classification class 1, regression class 1)") -AddOption(nnClusterizerUseClassification, int, 1, "", 0, "If 1, the classification output of the network is used to select clusters, else only the regression output is used and no clusters are rejected by classification") -AddOption(nnClusterizerForceGpuInputFill, int, 0, "", 0, "Forces to use the fillInputNNGPU function") +AddOption(nnClusterizerUseClassification, int32_t, 1, "", 0, "If 1, the classification output of the network is used to select clusters, else only the regression output is used and no clusters are rejected by classification") +AddOption(nnClusterizerForceGpuInputFill, int32_t, 0, "", 0, "Forces to use the fillInputNNGPU function") // CCDB -AddOption(nnLoadFromCCDB, int, 0, "", 0, "If 1 networks are fetched from ccdb, else locally") -AddOption(nnCCDBDumpToFile, int, 0, "", 0, "If 1, additionally dump fetched CCDB networks to nnLocalFolder") +AddOption(nnLoadFromCCDB, int32_t, 0, "", 0, "If 1 networks are fetched from ccdb, else locally") +AddOption(nnCCDBDumpToFile, int32_t, 0, "", 0, "If 1, additionally dump fetched CCDB networks to nnLocalFolder") AddOption(nnLocalFolder, std::string, ".", "", 0, "Local folder in which the networks will be fetched") AddOption(nnCCDBPath, std::string, "Users/c/csonnabe/TPC/Clusterization", "", 0, "Folder path containing the networks") AddOption(nnCCDBWithMomentum, std::string, "", "", 0, "Distinguishes between the network with and without momentum output for the regression") @@ -434,113 +427,6 @@ EndConfig() #endif // !__OPENCL__ && !__METAL__ #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(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") -AddOption(drawClusters, bool, true, "", 0, "Highlight clusters") -AddOption(drawLinks, bool, false, "", 0, "Highlight links") -AddOption(drawInitLinks, bool, false, "", 0, "Highlight cleaned-up links") -AddOption(drawSeeds, bool, false, "", 0, "Highlight seeds") -AddOption(drawTracklets, bool, false, "", 0, "Highlight tracklets") -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(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(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(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") -AddOption(bFieldLinesCount, int32_t, 2000, "", 0, "Set field lines count") -AddOption(invertColors, bool, false, "", 0, "Invert colors") -AddHelp("help", 'h') -EndConfig() - -// Heavy settings concerning the event display (can be changed only with rebuilding vertices) -BeginSubConfig(GPUSettingsDisplayHeavy, heavy, configStandalone.display, "GLH", 0, "Heavy OpenGL display settings", display_heavy) -AddOption(drawTPCTracks, bool, true, "", 0, "Show tracks with TPC contribution") -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(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") -AddOption(splitCETracks, int8_t, -1, "", 0, "Split CE tracks when they cross the central electrode (-1 = for triggered data)") -AddOption(markClusters, int32_t, 0, "", 0, "Mark clusters") -AddOption(markFakeClusters, int32_t, 0, "", 0, "Mark fake clusters") -AddOption(markAdjacentClusters, int32_t, 0, "", 0, "Mark adjacent clusters") -AddOption(hideRejectedClusters, int32_t, 1, "", 0, "Hide rejected clusters") -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") -AddHelp("help", 'h') -EndConfig() - -// Camera, window, and renderer settings for the event display -BeginSubConfig(GPUSettingsDisplayRenderer, renderer, configStandalone.display, "GLR", 'g', "Camera / window / renderer OpenGL display settings", display_camera) -AddOption(camLookOrigin, bool, false, "", 0, "Make the camera look at the origin") -AddOption(camYUp, bool, false, "", 0, "Orient the camera such that the y-axis is always upwards") -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") -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(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") -AddHelp("help", 'h') -EndConfig() - -// Vulkan Display Settings -BeginSubConfig(GPUSettingsDisplayVulkan, vulkan, configStandalone.display, "GLV", 0, "Vulkan display settings", display_vulkan) -AddOption(nFramesInFlight, uint32_t, 3, "", 0, "Max number of render frames in flight") -AddOption(uniformBuffersInDeviceMemory, bool, 1, "", 0, "Have uniform buffers in host-accessible device memory") -AddOption(forceDevice, int32_t, -1, "", 0, "Force Vulkan device number to use") -AddHelp("help", 'h') -EndConfig() - -// Settings concerning the event display (fixed settings, cannot be changed) -BeginSubConfig(GPUSettingsDisplay, display, configStandalone, "GL", 'g', "OpenGL display settings", display) -AddOption(showTPCTracksFromO2Format, bool, false, "", 0, "Use TPC tracks in O2 output format instead of GPU format") -AddOption(font, std::string, "monospace", "", 0, "Font (search patter used for Fontconfig)") -AddOption(fontSize, int32_t, 14, "", 0, "Font size") -AddOption(smoothFont, int32_t, -1, "", 0, "Smoth font when rendering (-1 for auto-select based on size") -AddOption(noFreetype, bool, false, "", 0, "Do not use Freetype for font rendering (can only draw text if supported by frontend)") -AddOption(displayRenderer, std::string, "auto", "renderer", 0, "Renderer for event display: opengl | vulkan | auto") -AddOption(displayFrontend, std::string, "auto", "displayFrontend", 0, "Frontend to use for event display: auto | win32 | x11 | glut | glfw | wayland") -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") -AddSubConfig(GPUSettingsDisplayLight, light) -AddSubConfig(GPUSettingsDisplayHeavy, heavy) -AddSubConfig(GPUSettingsDisplayRenderer, renderer) -AddSubConfig(GPUSettingsDisplayVulkan, vulkan) -AddHelp("help", 'h') -EndConfig() - // Settings concerning the standalone QA BeginSubConfig(GPUSettingsQA, QA, configStandalone, "QA", 'q', "QA settings", QA) AddOptionVec(compareInputs, std::string, "QAinput", 0, "Read histogram from these input files and include them in the output plots") @@ -578,98 +464,6 @@ AddShortcut("compare", 0, "--QAinput", "Compare QA histograms", "-c", "--qa", "- AddHelp("help", 'h') EndConfig() -#ifdef BeginConfig -// Settings concerning the standlone timeframe from run 2 events assembly tool -BeginSubConfig(GPUSettingsTFSim, TF, configStandalone, "TF", 't', "Timeframe settings") -AddOption(nMerge, int32_t, 0, "", 0, "Merge n events in a timeframe", min(0)) -AddOption(averageDistance, float, 50.f, "", 0, "Average distance in cm of events merged into timeframe", min(0.f)) -AddOption(randomizeDistance, bool, true, "", 0, "Randomize distance around average distance of merged events") -AddOption(shiftFirstEvent, bool, true, "", 0, "Also shift the first event in z when merging events to a timeframe") -AddOption(bunchSim, int32_t, 0, "", 0, "Simulate correct bunch interactions instead of placing only the average number of events. A value [n] > 1 sets TFlen for [n] collisions in average. (Incompatible to TFmerge)") -AddOption(bunchCount, int32_t, 12, "", 0, "Number of bunches per trainsort") -AddOption(bunchSpacing, int32_t, 50, "", 0, "Spacing between benches in ns") -AddOption(bunchTrainCount, int32_t, 48, "", 0, "Number of bunch trains") -AddOption(abortGapTime, int32_t, (3000), "", 0, "Length of abort gap in ns") -AddOption(interactionRate, int32_t, 50000, "", 0, "Instantaneous interaction rate") -AddOption(timeFrameLen, int64_t, (1000000000 / 44), "", 'l', "Timeframe len in ns") -AddOption(noBorder, bool, false, "", 0, "Do not simulate border effects (partial events)") -AddOption(noEventRepeat, int32_t, 0, "", 0, "0: Place random events, 1: Place events in timeframe one after another, 2: Place random events but do not repat", def(1)) -AddOption(nTotalEventsInTF, int32_t, 0, "", 0, "Total number of collisions to be placed in the interior of all time frames (excluding borders)") -AddOption(eventStride, int32_t, 0, "", 0, "Do not select random event, but walk over array of events in stride steps") -AddOption(overlayRaw, bool, false, "", 0, "Overlay raw TPC data instead of spatial clusters") -AddHelp("help", 'h') -EndConfig() - -// Settings concerning standalone toy event generator -BeginSubConfig(GPUSettingsEG, EG, configStandalone, "EG", 0, "Event generator settings") -AddOption(numberOfTracks, int32_t, 1, "", 0, "Number of tracks per generated event") -AddHelp("help", 'h') -EndConfig() - -// Settings for the standalone benchmark -BeginConfig(GPUSettingsStandalone, configStandalone) -AddOption(runGPU, uint8_t, 1, "", 'g', "Use GPU for processing", message("GPU processing enabled"), set(2)) -AddOptionSet(runGPU, uint8_t, 0, "", 'c', "Use CPU for processing", message("CPU enabled")) -AddOption(gpuType, std::string, "AUTO", "", 0, "GPU type (CUDA / HIP / OCL / CPUDEVICE) or CPU or AUTO") -AddOption(runGPUforce, bool, true, "", 0, "Force usage of the specified GPU device type, no CPU fallback") -AddOption(promptOnExit, bool, false, "", 0, "Prompt for keypress before exiting") -AddOption(continueOnError, bool, false, "", 0, "Continue processing after an error") -AddOption(seed, int32_t, -1, "", 0, "Set srand seed (-1: random)") -AddOption(StartEvent, int32_t, 0, "", 's', "First event to process", min(0)) -AddOption(nEvents, int32_t, -1, "", 'n', "Number of events to process (-1; all)", min(0)) -AddOption(runs, int32_t, 1, "runs", 'r', "Number of iterations to perform (repeat each event)", min(0)) -AddOption(runs2, int32_t, 1, "runsExternal", 0, "Number of iterations to perform (repeat full processing)", min(1)) -AddOption(runsInit, int32_t, 1, "", 0, "Number of initial iterations excluded from average", min(0)) -AddOption(eventsDir, const char*, "pp", "events", 'e', "Directory with events to process", message("Reading events from Directory %s")) -AddOption(absoluteEventsDir, bool, false, "", 0, "Events directory is absolute, and not inside './events/'") -AddOption(noEvents, bool, false, "", 0, "Run without data (e.g. for field visualization)") -AddOption(eventDisplay, int32_t, 0, "display", 'd', "Show standalone event display", def(1)) -AddOption(eventGenerator, bool, false, "", 0, "Run event generator") -AddOption(cont, bool, false, "", 0, "Process continuous timeframe data, even if input is triggered") -AddOption(setMaxTimeBin, int32_t, -2, "", 0, "maximum time bin of continuous data, 0 for triggered events, -1 for automatic continuous mode, -2 for automatic continuous / triggered") -AddOption(outputcontrolmem, uint64_t, 0, "outputMemory", 0, "Use predefined output buffer of this size", min(0ul), message("Using %s bytes as output memory")) -AddOption(inputcontrolmem, uint64_t, 0, "inputMemory", 0, "Use predefined input buffer of this size", min(0ul), message("Using %s bytes as input memory")) -AddOption(cpuAffinity, int32_t, -1, "", 0, "Pin CPU affinity to this CPU core", min(-1)) -AddOption(fifoScheduler, bool, false, "", 0, "Use FIFO realtime scheduler", message("Setting FIFO scheduler: %s")) -AddOption(fpe, int8_t, -1, "", 0, "Trap on floating point exceptions (-1 = if no ffast-math)") -AddOption(flushDenormals, int8_t, -1, "", 0, "Enable FTZ and DAZ (Flush all denormals to zero), -1 = enable automatically if not prevented by deterministic mode") -AddOption(solenoidBzNominalGPU, float, -1e6f, "", 0, "Field strength of solenoid Bz in kGaus") -AddOption(constBz, bool, false, "", 0, "Force constand Bz") -AddOption(overrideMaxTimebin, bool, false, "", 0, "Override max time bin setting for continuous data with max time bin in time frame") -AddOption(encodeZS, int32_t, -1, "", 0, "Zero-Suppress TPC data", def(1)) -AddOption(zsFilter, int32_t, -1, "", 0, "Apply Zero-Suppression when loading digits and remove those below threshold", def(1)) -AddOption(zsVersion, int32_t, 2, "", 0, "ZS Version: 1 = 10-bit ADC row based, 2 = 12-bit ADC row based, 3 = improved link based, 4 = dense link based") -AddOption(dumpEvents, bool, false, "", 0, "Dump events (after transformation such as encodeZS") -AddOption(stripDumpedEvents, bool, false, "", 0, "Remove redundant inputs (e.g. digits and ZS) before dumping") -AddOption(printSettings, int32_t, 0, "", 0, "Print all settings", def(1)) -AddOption(testSyncAsync, bool, false, "syncAsync", 0, "Test first synchronous and then asynchronous processing") -AddOption(testSync, bool, false, "sync", 0, "Test settings for synchronous phase") -AddOption(testSyncAsyncQcInSync, bool, false, "syncAsyncSyncQC", 0, "Run QC in sync phase of testSyncAsync") -AddOption(timeFrameTime, bool, false, "tfTime", 0, "Print some debug information about time frame processing time") -AddOption(controlProfiler, bool, false, "", 0, "Issues GPU profiler stop and start commands to profile only the relevant processing part") -AddOption(preloadEvents, bool, false, "", 0, "Preload events into host memory before start processing") -AddOption(recoSteps, int32_t, -1, "", 0, "Bitmask for RecoSteps. Run --listRecoSteps to get a list") -AddOption(recoStepsGPU, int32_t, -1, "", 0, "Bitmask for RecoSteps to run on GPU") -AddOption(listRecoSteps, bool, false, "", 0, "Print the list of RecoSteps with their bitmask values") -AddOption(runMerger, int32_t, 1, "", 0, "Run track merging / refit", min(0), max(1)) -AddOption(runTRD, int32_t, -1, "", 0, "Enable TRD processing (-1 = decide automatically)") -AddOption(rundEdx, int32_t, -1, "", 0, "Enable dEdx processing (-1 = decide automatically)") -AddOption(runCompression, int32_t, 1, "", 0, "Enable TPC Compression") -AddOption(runTransformation, int32_t, 1, "", 0, "Enable TPC Transformation") -AddOption(runRefit, bool, false, "", 0, "Enable final track refit") -AddOption(setO2Settings, bool, false, "", 0, "Set O2 defaults for output of shared cluster map, referenceX") -AddOption(recreateTrivialCalibObjects, bool, false, "", 0, "Recreate trivial calibration objects (TPCTransform, dEdxCorrection) from scratch") -AddHelp("help", 'h') -AddHelpAll("helpall", 'H') -AddSubConfig(GPUSettingsRec, rec) -AddSubConfig(GPUSettingsProcessing, proc) -AddSubConfig(GPUSettingsQA, QA) -AddSubConfig(GPUSettingsDisplay, display) -AddSubConfig(GPUSettingsTFSim, TF) -AddSubConfig(GPUSettingsEG, EG) -EndConfig() -#endif // BeginConfig - //Settings for the O2 workflow #if !defined(QCONFIG_PARSER_CXX) && !defined(GPUCA_STANDALONE) BeginSubConfig(GPUSettingsO2, global, configStandalone, "O2", 0, "O2 workflow settings", global) @@ -720,14 +514,6 @@ EndConfig() #endif // !GPUCA_STANDALONE #endif // !GPUCA_GPUCODE_DEVICE -// Derrived parameters used in GPUParam -BeginHiddenConfig(GPUSettingsParam, param) -AddVariableRTC(continuousTracking, int8_t, 0) // Continuous tracking, estimate bz and errors for abs(z) = 125cm during seeding -AddVariableRTC(dodEdx, int8_t, 0) // Do dEdx computation -EndConfig() - -EndNamespace() // gpu -EndNamespace() // o2 -#endif // #ifdef BeginNamespace +EndNamespace() // o2::gpu - // clang-format on +#endif // (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsListDisplay.h b/GPU/GPUTracking/Definitions/GPUSettingsListDisplay.h new file mode 100644 index 0000000000000..587d49a712b1d --- /dev/null +++ b/GPU/GPUTracking/Definitions/GPUSettingsListDisplay.h @@ -0,0 +1,151 @@ +// 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 GPUSettingsListDisplay.h +/// \author David Rohr + +// This file contains macros to generate all settings for the GPU Reconstruction. +// Macros are used in the following places: +// Create ConfigurableParam object for workflow. +// Configure standalone benchmark. +// Create plain-C struct for GPU code. +// Create static constexpr with default values for GPU run time compilation + +#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) + +// 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, "", 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") +AddOption(drawClusters, bool, true, "", 0, "Highlight clusters") +AddOption(drawLinks, bool, false, "", 0, "Highlight links") +AddOption(drawInitLinks, bool, false, "", 0, "Highlight cleaned-up links") +AddOption(drawSeeds, bool, false, "", 0, "Highlight seeds") +AddOption(drawTracklets, bool, false, "", 0, "Highlight tracklets") +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", 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)", min(0), max(3)) +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", 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, "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") +AddOption(bFieldLinesCount, int32_t, 2000, "", 0, "Set field lines count") +AddOption(invertColors, bool, false, "", 0, "Invert colors") +AddHelp("help", 'h') +EndConfig() + +// Heavy settings concerning the event display (can be changed only with rebuilding vertices) +BeginSubConfig(GPUSettingsDisplayHeavy, heavy, configStandalone.display, "GLH", 0, "Heavy OpenGL display settings", display_heavy) +AddOption(drawTPCTracks, bool, true, "", 0, "Show tracks with TPC contribution") +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, "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") +AddOption(splitCETracks, int8_t, -1, "", 0, "Split CE tracks when they cross the central electrode (-1 = for triggered data)") +AddOption(markClusters, int32_t, 0, "", 0, "Mark clusters") +AddOption(markFakeClusters, int32_t, 0, "", 0, "Mark fake clusters") +AddOption(markAdjacentClusters, int32_t, 0, "", 0, "Mark adjacent clusters") +AddOption(hideRejectedClusters, int32_t, 1, "", 0, "Hide rejected clusters") +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(xzAdd, float, 0, "", 0, "Separate sectors, increase X coordinate", min(0.f), max(300.f)) +AddHelp("help", 'h') +EndConfig() + +// Camera, window, and renderer settings for the event display +BeginSubConfig(GPUSettingsDisplayRenderer, renderer, configStandalone.display, "GLR", 'g', "Camera / window / renderer OpenGL display settings", display_camera) +AddOption(camLookOrigin, bool, false, "", 0, "Make the camera look at the origin") +AddOption(camYUp, bool, false, "", 0, "Orient the camera such that the y-axis is always upwards") +AddOption(cameraMode, int32_t, 0, "", 0, "Camera mode", min(0), max(3)) +AddOption(fullScreen, bool, false, "", 0, "Full Screen") +AddOption(maximized, bool, true, "", 0, "Maximized window") +AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing", min(0), max(16)) +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", 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", min(1), max(4)) +AddOption(fov, float, 45.f, "", 0, "Display FOV", min(5.f), max(175.f)) +AddHelp("help", 'h') +EndConfig() + +// Vulkan Display Settings +BeginSubConfig(GPUSettingsDisplayVulkan, vulkan, configStandalone.display, "GLV", 0, "Vulkan display settings", display_vulkan) +AddOption(nFramesInFlight, uint32_t, 3, "", 0, "Max number of render frames in flight") +AddOption(uniformBuffersInDeviceMemory, bool, 1, "", 0, "Have uniform buffers in host-accessible device memory") +AddOption(forceDevice, int32_t, -1, "", 0, "Force Vulkan device number to use") +AddHelp("help", 'h') +EndConfig() + +// Internal display settings (GUI protocol, console, info texts) +BeginSubConfig(GPUSettingsDisplayInternal, internal, configStandalone.display, "GLI", 0, "Internal display settings", display_internal) +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(consoleDelay, float, 0.3f, "", 0, "Time in seconds to open / close the console (^ key)") +AddOption(consoleAlpha, float, 0.85f, "", 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") +AddOption(setInfoTime, float, 5.0f, "", 0, "Time in seconds an info message (SetInfo) is shown fully opaque") +AddOption(setInfoTransition, float, 1.0f, "", 0, "Time in seconds an info message fades out after setInfoTime") +AddOption(consolePrintToTerminal, bool, false, "", 0, "Also write the replies of console commands to the terminal") +AddHelp("help", 'h') +EndConfig() + +// Settings concerning the event display (fixed settings, cannot be changed) +BeginSubConfig(GPUSettingsDisplay, display, configStandalone, "GL", 'g', "OpenGL display settings", display) +AddOption(showTPCTracksFromO2Format, bool, false, "", 0, "Use TPC tracks in O2 output format instead of GPU format") +AddOption(font, std::string, "monospace", "", 0, "Font (search patter used for Fontconfig)") +AddOption(fontSize, int32_t, 14, "", 0, "Font size") +AddOption(smoothFont, int32_t, -1, "", 0, "Smoth font when rendering (-1 for auto-select based on size") +AddOption(noFreetype, bool, false, "", 0, "Do not use Freetype for font rendering (can only draw text if supported by frontend)") +AddOption(displayRenderer, std::string, "auto", "renderer", 0, "Renderer for event display: opengl | vulkan | auto") +AddOption(displayFrontend, std::string, "auto", "displayFrontend", 0, "Frontend to use for event display: auto | win32 | x11 | glut | glfw | wayland") +AddOptionVec(filterMacros, std::string, "", 0, "ROOT macros used as track filter") +AddOption(listFrontendOptionHandling, bool, false, "", 0, "List all assigned keys in frontend, Configurable settings in Qt GUI, and missing configuration options") +AddOption(runScript, std::string, "", "", 0, "Console script file, run before the first frame of the display") +AddSubConfig(GPUSettingsDisplayLight, light) +AddSubConfig(GPUSettingsDisplayHeavy, heavy) +AddSubConfig(GPUSettingsDisplayRenderer, renderer) +AddSubConfig(GPUSettingsDisplayVulkan, vulkan) +AddSubConfig(GPUSettingsDisplayInternal, internal) +AddHelp("help", 'h') +EndConfig() + +EndNamespace() // o2::gpu + +#endif // (!defined(__CLING__) && !defined(G__DICTIONARY)) || defined(BeginSubConfig) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsListStandalone.h b/GPU/GPUTracking/Definitions/GPUSettingsListStandalone.h new file mode 100644 index 0000000000000..21ca208164d42 --- /dev/null +++ b/GPU/GPUTracking/Definitions/GPUSettingsListStandalone.h @@ -0,0 +1,110 @@ +// 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 GPUSettingsListStandalone.h +/// \author David Rohr + +// clang-format off +BeginNamespace(o2::gpu) + +#ifndef GPUCA_GPUCODE_DEVICE +// Settings concerning the standlone timeframe from run 2 events assembly tool +BeginSubConfig(GPUSettingsTFSim, TF, configStandalone, "TF", 't', "Timeframe settings") +AddOption(nMerge, int32_t, 0, "", 0, "Merge n events in a timeframe", min(0)) +AddOption(averageDistance, float, 50.f, "", 0, "Average distance in cm of events merged into timeframe", min(0.f)) +AddOption(randomizeDistance, bool, true, "", 0, "Randomize distance around average distance of merged events") +AddOption(shiftFirstEvent, bool, true, "", 0, "Also shift the first event in z when merging events to a timeframe") +AddOption(bunchSim, int32_t, 0, "", 0, "Simulate correct bunch interactions instead of placing only the average number of events. A value [n] > 1 sets TFlen for [n] collisions in average. (Incompatible to TFmerge)") +AddOption(bunchCount, int32_t, 12, "", 0, "Number of bunches per trainsort") +AddOption(bunchSpacing, int32_t, 50, "", 0, "Spacing between benches in ns") +AddOption(bunchTrainCount, int32_t, 48, "", 0, "Number of bunch trains") +AddOption(abortGapTime, int32_t, (3000), "", 0, "Length of abort gap in ns") +AddOption(interactionRate, int32_t, 50000, "", 0, "Instantaneous interaction rate") +AddOption(timeFrameLen, int64_t, (1000000000 / 44), "", 'l', "Timeframe len in ns") +AddOption(noBorder, bool, false, "", 0, "Do not simulate border effects (partial events)") +AddOption(noEventRepeat, int32_t, 0, "", 0, "0: Place random events, 1: Place events in timeframe one after another, 2: Place random events but do not repat", def(1)) +AddOption(nTotalEventsInTF, int32_t, 0, "", 0, "Total number of collisions to be placed in the interior of all time frames (excluding borders)") +AddOption(eventStride, int32_t, 0, "", 0, "Do not select random event, but walk over array of events in stride steps") +AddOption(overlayRaw, bool, false, "", 0, "Overlay raw TPC data instead of spatial clusters") +AddHelp("help", 'h') +EndConfig() + +// Settings concerning standalone toy event generator +BeginSubConfig(GPUSettingsEG, EG, configStandalone, "EG", 0, "Event generator settings") +AddOption(numberOfTracks, int32_t, 1, "", 0, "Number of tracks per generated event") +AddHelp("help", 'h') +EndConfig() + +// Settings for the standalone benchmark +BeginConfig(GPUSettingsStandalone, configStandalone) +AddOption(runGPU, uint8_t, 1, "", 'g', "Use GPU for processing", message("GPU processing enabled"), set(2)) +AddOptionSet(runGPU, uint8_t, 0, "", 'c', "Use CPU for processing", message("CPU enabled")) +AddOption(gpuType, std::string, "AUTO", "", 0, "GPU type (CUDA / HIP / OCL / CPUDEVICE) or CPU or AUTO") +AddOption(runGPUforce, bool, true, "", 0, "Force usage of the specified GPU device type, no CPU fallback") +AddOption(promptOnExit, bool, false, "", 0, "Prompt for keypress before exiting") +AddOption(continueOnError, bool, false, "", 0, "Continue processing after an error") +AddOption(seed, int32_t, -1, "", 0, "Set srand seed (-1: random)") +AddOption(StartEvent, int32_t, 0, "", 's', "First event to process", min(0)) +AddOption(nEvents, int32_t, -1, "", 'n', "Number of events to process (-1; all)", min(0)) +AddOption(runs, int32_t, 1, "runs", 'r', "Number of iterations to perform (repeat each event)", min(0)) +AddOption(runs2, int32_t, 1, "runsExternal", 0, "Number of iterations to perform (repeat full processing)", min(1)) +AddOption(runsInit, int32_t, 1, "", 0, "Number of initial iterations excluded from average", min(0)) +AddOption(eventsDir, const char*, "pp", "events", 'e', "Directory with events to process", message("Reading events from Directory %s")) +AddOption(absoluteEventsDir, bool, false, "", 0, "Events directory is absolute, and not inside './events/'") +AddOption(noEvents, bool, false, "", 0, "Run without data (e.g. for field visualization)") +AddOption(eventDisplay, int32_t, 0, "display", 'd', "Show standalone event display", def(1)) +AddOption(eventGenerator, bool, false, "", 0, "Run event generator") +AddOption(cont, bool, false, "", 0, "Process continuous timeframe data, even if input is triggered") +AddOption(setMaxTimeBin, int32_t, -2, "", 0, "maximum time bin of continuous data, 0 for triggered events, -1 for automatic continuous mode, -2 for automatic continuous / triggered") +AddOption(outputcontrolmem, uint64_t, 0, "outputMemory", 0, "Use predefined output buffer of this size", min(0ul), message("Using %s bytes as output memory")) +AddOption(inputcontrolmem, uint64_t, 0, "inputMemory", 0, "Use predefined input buffer of this size", min(0ul), message("Using %s bytes as input memory")) +AddOption(cpuAffinity, int32_t, -1, "", 0, "Pin CPU affinity to this CPU core", min(-1)) +AddOption(fifoScheduler, bool, false, "", 0, "Use FIFO realtime scheduler", message("Setting FIFO scheduler: %s")) +AddOption(fpe, int8_t, -1, "", 0, "Trap on floating point exceptions (-1 = if no ffast-math)") +AddOption(flushDenormals, int8_t, -1, "", 0, "Enable FTZ and DAZ (Flush all denormals to zero), -1 = enable automatically if not prevented by deterministic mode") +AddOption(solenoidBzNominalGPU, float, -1e6f, "", 0, "Field strength of solenoid Bz in kGaus") +AddOption(constBz, bool, false, "", 0, "Force constand Bz") +AddOption(overrideMaxTimebin, bool, false, "", 0, "Override max time bin setting for continuous data with max time bin in time frame") +AddOption(encodeZS, int32_t, -1, "", 0, "Zero-Suppress TPC data", def(1)) +AddOption(zsFilter, int32_t, -1, "", 0, "Apply Zero-Suppression when loading digits and remove those below threshold", def(1)) +AddOption(zsVersion, int32_t, 2, "", 0, "ZS Version: 1 = 10-bit ADC row based, 2 = 12-bit ADC row based, 3 = improved link based, 4 = dense link based") +AddOption(dumpEvents, bool, false, "", 0, "Dump events (after transformation such as encodeZS") +AddOption(stripDumpedEvents, bool, false, "", 0, "Remove redundant inputs (e.g. digits and ZS) before dumping") +AddOption(printSettings, int32_t, 0, "", 0, "Print all settings", def(1)) +AddOption(testSyncAsync, bool, false, "syncAsync", 0, "Test first synchronous and then asynchronous processing") +AddOption(testSync, bool, false, "sync", 0, "Test settings for synchronous phase") +AddOption(testSyncAsyncQcInSync, bool, false, "syncAsyncSyncQC", 0, "Run QC in sync phase of testSyncAsync") +AddOption(timeFrameTime, bool, false, "tfTime", 0, "Print some debug information about time frame processing time") +AddOption(controlProfiler, bool, false, "", 0, "Issues GPU profiler stop and start commands to profile only the relevant processing part") +AddOption(preloadEvents, bool, false, "", 0, "Preload events into host memory before start processing") +AddOption(recoSteps, int32_t, -1, "", 0, "Bitmask for RecoSteps. Run --listRecoSteps to get a list") +AddOption(recoStepsGPU, int32_t, -1, "", 0, "Bitmask for RecoSteps to run on GPU") +AddOption(listRecoSteps, bool, false, "", 0, "Print the list of RecoSteps with their bitmask values") +AddOption(runMerger, int32_t, 1, "", 0, "Run track merging / refit", min(0), max(1)) +AddOption(runTRD, int32_t, -1, "", 0, "Enable TRD processing (-1 = decide automatically)") +AddOption(rundEdx, int32_t, -1, "", 0, "Enable dEdx processing (-1 = decide automatically)") +AddOption(runCompression, int32_t, 1, "", 0, "Enable TPC Compression") +AddOption(runTransformation, int32_t, 1, "", 0, "Enable TPC Transformation") +AddOption(runRefit, bool, false, "", 0, "Enable final track refit") +AddOption(setO2Settings, bool, false, "", 0, "Set O2 defaults for output of shared cluster map, referenceX") +AddOption(recreateTrivialCalibObjects, bool, false, "", 0, "Recreate trivial calibration objects (TPCTransform, dEdxCorrection) from scratch") +AddHelp("help", 'h') +AddHelpAll("helpall", 'H') +AddSubConfig(GPUSettingsRec, rec) +AddSubConfig(GPUSettingsProcessing, proc) +AddSubConfig(GPUSettingsQA, QA) +AddSubConfig(GPUSettingsDisplay, display) +AddSubConfig(GPUSettingsTFSim, TF) +AddSubConfig(GPUSettingsEG, EG) +EndConfig() +#endif + +EndNamespace() // o2::gpu diff --git a/GPU/GPUTracking/Global/GPUChain.h b/GPU/GPUTracking/Global/GPUChain.h index 61107f7893e9c..4b0391dae1f5a 100644 --- a/GPU/GPUTracking/Global/GPUChain.h +++ b/GPU/GPUTracking/Global/GPUChain.h @@ -236,12 +236,12 @@ class GPUChain inline GPUChain* GetNextChainInQueue() { return mRec->GetNextChainInQueue(); } template - bool DoDebugAndDump(RecoStep step, uint32_t mask, T& processor, S T::*func, Args&&... args) + bool DoDebugAndDump(RecoStep step, uint32_t mask, T& processor, S T::* func, Args&&... args) { return DoDebugAndDump(step, mask, true, processor, func, args...); } template - bool DoDebugAndDump(RecoStep step, uint32_t mask, bool transfer, T& processor, S T::*func, Args&&... args); + bool DoDebugAndDump(RecoStep step, uint32_t mask, bool transfer, T& processor, S T::* func, Args&&... args); template bool DoDebugDump(uint32_t mask, std::function func, Args&... args); template @@ -251,11 +251,11 @@ class GPUChain } template - int32_t runRecoStep(RecoStep step, S T::*func, Args... args); + int32_t runRecoStep(RecoStep step, S T::* func, Args... args); private: template - void timeCpy(RecoStep step, int32_t toGPU, S T::*func, Args... args); + void timeCpy(RecoStep step, int32_t toGPU, S T::* func, Args... args); #define GPUCA_KRNL(x_class, x_attributes, x_arguments, x_forward, x_types, ...) \ template \ @@ -269,7 +269,7 @@ class GPUChain }; template -inline void GPUChain::timeCpy(RecoStep step, int32_t toGPU, S T::*func, Args... args) +inline void GPUChain::timeCpy(RecoStep step, int32_t toGPU, S T::* func, Args... args) { if (!Always && step != RecoStep::NoRecoStep && !(GetRecoStepsGPU() & step)) { return; @@ -295,7 +295,7 @@ inline void GPUChain::timeCpy(RecoStep step, int32_t toGPU, S T::*func, Args... } template -inline int32_t GPUChain::runRecoStep(RecoStep step, S T::*func, Args... args) +inline int32_t GPUChain::runRecoStep(RecoStep step, S T::* func, Args... args) { if (GetRecoSteps().isSet(step)) { auto* timer = GetProcessingSettings().recoTaskTiming ? &mRec->getRecoStepTimer(step) : nullptr; diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index f05ca6983e8ba..8a22e2c03ea47 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -860,6 +860,15 @@ int32_t GPUChainTracking::RunChainFinalize() usleep(10000); if (GetProcessingSettings().runsStandalone) { iKey = kbhit() ? getch() : 0; + if (iKey == 27 && kbhit()) { // Escape sequence (arrow keys, ...), not the ESC key: the display translates up to 8 characters after the ESC + uint64_t extendedKey = 0; + for (uint32_t i = 0; kbhit(); i++) { + const int32_t c = getch(); + extendedKey |= i < 8 ? (uint64_t)(uint8_t)c << (8 * i) : 0; + } + GetProcessingSettings().eventDisplay->sendExternalGUIKey(0, 1, extendedKey); + iKey = 0; + } if (iKey == 27) { GetProcessingSettings().eventDisplay->setDisplayControl(2); } else if (iKey == 'n') { diff --git a/GPU/GPUTracking/Global/GPUChainTracking.h b/GPU/GPUTracking/Global/GPUChainTracking.h index 45e9a1aeaa504..1507147bbafda 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.h +++ b/GPU/GPUTracking/Global/GPUChainTracking.h @@ -58,6 +58,7 @@ namespace o2::gpu // class GPUTRDTrackerGPU; class GPUTPCGPUTracker; class GPUDisplayInterface; +struct GPUSettingsDisplay; class GPUQA; class GPUTPCClusterStatistics; class GPUTRDGeometry; diff --git a/GPU/GPUTracking/Global/GPUChainTrackingDebug.h b/GPU/GPUTracking/Global/GPUChainTrackingDebug.h index 39e48f9f14d9a..6ceb2a89cd091 100644 --- a/GPU/GPUTracking/Global/GPUChainTrackingDebug.h +++ b/GPU/GPUTracking/Global/GPUChainTrackingDebug.h @@ -50,7 +50,7 @@ enum GPUChainTrackingDebugFlags : uint32_t { }; template -inline bool GPUChain::DoDebugAndDump(GPUChain::RecoStep step, uint32_t mask, bool transfer, T& processor, S T::*func, Args&&... args) +inline bool GPUChain::DoDebugAndDump(GPUChain::RecoStep step, uint32_t mask, bool transfer, T& processor, S T::* func, Args&&... args) { if (GetProcessingSettings().keepAllMemory) { if (transfer) { diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h b/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h index 155048859a507..b63c0585d3e2f 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.h @@ -16,6 +16,7 @@ #define GPUO2INTERFACECONFIGURATION_H #include "GPUO2ExternalUser.h" +#define GPUSETTINGS_DISPLAY #include "GPUSettings.h" #include "GPUDataTypesIO.h" #include "GPUHostDataTypes.h" diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.inc b/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.inc index 30f6d56a17f1a..948651d45cfa1 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.inc +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceConfiguration.inc @@ -21,6 +21,8 @@ #ifdef GPUCA_STANDALONE #define GPU_GET_CONFIG(configName) static_assert(false, "GPU_GET_CONFIG not available in standalone benchmark") #else +#define GPUSETTINGS_DISPLAY +#include "GPUSettings.h" #include "GPUO2ExternalUser.h" #include "GPUCommonRtypes.h" #include "GPUDefMacros.h" diff --git a/GPU/GPUTracking/Merger/GPUTPCGMMerger.cxx b/GPU/GPUTracking/Merger/GPUTPCGMMerger.cxx index 267239a4bbab9..cf8ea3c4f4675 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGMMerger.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGMMerger.cxx @@ -554,7 +554,7 @@ GPUd() int32_t GPUTPCGMMerger::RefitSectorTrack(GPUTPCGMSectorTrack& sectorTrack GPUTPCGMPropagator prop; prop.SetMaterialTPC(); prop.SetMaxSinPhi(constants::MAX_SIN_PHI); - prop.SetSeedingErrors(true); // Larger errors for seeds, better since we don't start with good hypothesis + prop.SetSeedingErrors(true); // Larger errors for seeds, better since we don't start with good hypothesis prop.SetFitInProjections(true); // TODO: Was false, consider reenabling after fitInProjection is fixed prop.SetPolynomialField(&Param().polynomialField); GPUTPCGMTrackParam trk; diff --git a/GPU/GPUTracking/Merger/GPUTPCGMMergerGPU.cxx b/GPU/GPUTracking/Merger/GPUTPCGMMergerGPU.cxx index 2a111b8ce89af..bf01ea234a645 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGMMergerGPU.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGMMergerGPU.cxx @@ -23,7 +23,7 @@ GPUdii() void GPUTPCGMMergerTrackFit::Thread<0>(int32_t nBlocks, int32_t nThread { const int32_t iEnd = mode == -1 ? merger.Memory()->nRetryRefit : merger.NMergedTracks(); GPUCA_TBB_KERNEL_LOOP(merger.GetRec(), int32_t, ii, iEnd, { - const int32_t i = mode == -1 ? merger.RetryRefitIds()[ii] : mode ? merger.TrackOrderProcess()[ii] : ii; + const int32_t i = mode == -1 ? merger.RetryRefitIds()[ii] : (mode ? merger.TrackOrderProcess()[ii] : ii); GPUTPCGMTrackParam::RefitTrack(merger.MergedTracks()[i], i, &merger, mode == -1); }); } diff --git a/GPU/GPUTracking/Merger/GPUTPCGMO2Output.cxx b/GPU/GPUTracking/Merger/GPUTPCGMO2Output.cxx index 1ba55d98f5ca1..4bcffd8a31c46 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGMO2Output.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGMO2Output.cxx @@ -191,7 +191,7 @@ GPUdii() void GPUTPCGMO2Output::Thread(int32_t nBlocks uint32_t nOutCl2 = 0; float t1 = 0, t2 = 0; int32_t sector1 = 0, sector2 = 0; - const o2::tpc::ClusterNativeAccess* GPUrestrict() clusters = merger.GetConstantMem() -> ioPtrs.clustersNative; + const o2::tpc::ClusterNativeAccess* GPUrestrict() clusters = merger.GetConstantMem()->ioPtrs.clustersNative; for (uint32_t j = 0; j < track.NClusters(); j++) { if ((trackClusters[track.FirstClusterRef() + j].state & flagsReject)) { continue; @@ -284,7 +284,7 @@ template <> GPUdii() void GPUTPCGMO2Output::Thread(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUsharedref() GPUSharedMemory& smem, processorType& GPUrestrict() merger) { #ifndef GPUCA_GPUCODE - const o2::tpc::ClusterNativeAccess* GPUrestrict() clusters = merger.GetConstantMem() -> ioPtrs.clustersNative; + const o2::tpc::ClusterNativeAccess* GPUrestrict() clusters = merger.GetConstantMem()->ioPtrs.clustersNative; if (clusters == nullptr || clusters->clustersMCTruth == nullptr) { return; } diff --git a/GPU/GPUTracking/Merger/GPUTPCGMSectorTrack.cxx b/GPU/GPUTracking/Merger/GPUTPCGMSectorTrack.cxx index e84d037c936f5..8a9669a28143b 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGMSectorTrack.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGMSectorTrack.cxx @@ -495,7 +495,7 @@ GPUd() bool GPUTPCGMSectorTrack::TransportToXAlpha(GPUTPCGMMerger* merger, float GPUd() void GPUTPCGMSectorTrack::CopyBaseTrackCov() { - const float* GPUrestrict() cov = mOrigTrack -> Param().mC; + const float* GPUrestrict() cov = mOrigTrack->Param().mC; mParam.mC0 = cov[0]; mParam.mC2 = cov[2]; mParam.mC3 = cov[3]; diff --git a/GPU/GPUTracking/Merger/GPUTPCGMTrackParam.cxx b/GPU/GPUTracking/Merger/GPUTPCGMTrackParam.cxx index 47fe78099774f..7b70d94b9cfc5 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGMTrackParam.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGMTrackParam.cxx @@ -52,7 +52,7 @@ GPUd() bool GPUTPCGMTrackParam::Fit(GPUTPCGMMerger* GPUrestrict() merger, int32_ constexpr float kDeg2Rad = M_PI / 180.f; CADEBUG(static constexpr float kSectAngle = 2 * M_PI / 18.f); - const GPUParam& GPUrestrict() param = merger -> Param(); + const GPUParam& GPUrestrict() param = merger->Param(); GPUdEdx dEdx, dEdxAlt; GPUTPCGMPropagator prop; @@ -126,7 +126,7 @@ GPUd() bool GPUTPCGMTrackParam::Fit(GPUTPCGMMerger* GPUrestrict() merger, int32_ const float clAlpha = param.Alpha(clusters[ihit].sector); float xx, yy, zz; { - const ClusterNative& GPUrestrict() cl = merger -> GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; + const ClusterNative& GPUrestrict() cl = merger->GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; merger->GetConstantMem()->calibObjects.fastTransform->Transform(clusters[ihit].sector, clusters[ihit].row, cl.getPad(), cl.getTime(), xx, yy, zz, mTOffset); } // clang-format off @@ -440,7 +440,7 @@ GPUd() int32_t GPUTPCGMTrackParam::MergeDoubleRowClusters(int32_t& ihit, int32_t xx = yy = zz = 0.f; clusterState = 0; while (true) { - const ClusterNative& GPUrestrict() cl = merger -> GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; + const ClusterNative& GPUrestrict() cl = merger->GetConstantMem()->ioPtrs.clustersNative->clustersLinear[clusters[ihit].num]; float clamp = cl.getQtot(); float clx, cly, clz; merger->GetConstantMem()->calibObjects.fastTransform->Transform(clusters[ihit].sector, clusters[ihit].row, cl.getPad(), cl.getTime(), clx, cly, clz, mTOffset); @@ -493,7 +493,7 @@ GPUd() float GPUTPCGMTrackParam::AttachClusters(const GPUTPCGMMerger* GPUrestric if (param.rec.tpc.disableRefitAttachment & 1) { return -1e6f; } - const GPUTPCTracker& GPUrestrict() tracker = *(Merger -> GetConstantMem()->tpcTrackers + sector); + const GPUTPCTracker& GPUrestrict() tracker = *(Merger->GetConstantMem()->tpcTrackers + sector); const GPUTPCRow& GPUrestrict() row = tracker.Row(iRow); GPUglobalref() const cahit2* hits = tracker.HitData(row); GPUglobalref() const calink* firsthit = tracker.FirstHitInBin(row); @@ -803,7 +803,7 @@ GPUd() float GPUTPCGMTrackParam::ShiftZ(const GPUTPCGMMergedTrackHit* clusters, if (N == 0) { N = 1; } - const auto& GPUrestrict() cls = merger -> GetConstantMem()->ioPtrs.clustersNative->clustersLinear; + const auto& GPUrestrict() cls = merger->GetConstantMem()->ioPtrs.clustersNative->clustersLinear; float z0 = cls[clusters[0].num].getTime(), zn = cls[clusters[N - 1].num].getTime(); const auto tmp = zn > z0 ? std::array{zn, z0, GPUTPCGeometry::Row2X(clusters[N - 1].row)} : std::array{z0, zn, GPUTPCGeometry::Row2X(clusters[0].row)}; return ShiftZ(merger, clusters[0].sector, tmp[0], tmp[1], tmp[2]); diff --git a/GPU/GPUTracking/Merger/GPUTPCGlobalDebugSortKernels.cxx b/GPU/GPUTracking/Merger/GPUTPCGlobalDebugSortKernels.cxx index 70665bbe743d6..a3a1db5630944 100644 --- a/GPU/GPUTracking/Merger/GPUTPCGlobalDebugSortKernels.cxx +++ b/GPU/GPUTracking/Merger/GPUTPCGlobalDebugSortKernels.cxx @@ -52,8 +52,9 @@ GPUdii() void GPUTPCGlobalDebugSortKernels::Thread= 0 && tmp[j] != j) { @@ -107,7 +108,7 @@ GPUdii() void GPUTPCGlobalDebugSortKernels::Thread= GPUCA_PAR_NEIGHBOURS_FINDER_MAX_NNEIGHUP); diff --git a/GPU/GPUTracking/SectorTracker/GPUTPCTrackletConstructor.h b/GPU/GPUTracking/SectorTracker/GPUTPCTrackletConstructor.h index b52574e39a738..ecef065a1df40 100644 --- a/GPU/GPUTracking/SectorTracker/GPUTPCTrackletConstructor.h +++ b/GPU/GPUTracking/SectorTracker/GPUTPCTrackletConstructor.h @@ -46,19 +46,19 @@ class GPUTPCTrackletConstructor : public GPUKernelTemplate protected: // WARNING: This data is copied element by element in CopyTrackletTempData. Changes to members of this class must be reflected in CopyTrackletTempData!!! - uint32_t mISH; // track index - uint32_t mFirstRow; // first row index - uint32_t mLastRow; // last row index - uint32_t mStartRow; // row index of first hit in seed - uint32_t mEndRow; // row index of last hit in seed - calink mCurrIH; // indef of the current hit - int8_t mGo; // do fit/searching flag - uint8_t mStage; // reco stage - uint32_t mNHits; // n track hits + uint32_t mISH; // track index + uint32_t mFirstRow; // first row index + uint32_t mLastRow; // last row index + uint32_t mStartRow; // row index of first hit in seed + uint32_t mEndRow; // row index of last hit in seed + calink mCurrIH; // indef of the current hit + int8_t mGo; // do fit/searching flag + uint8_t mStage; // reco stage + uint32_t mNHits; // n track hits uint32_t mNHitsEndRow; // n hits at end row uint32_t mNMissed; // n missed hits during search - float mLastY; // Y of the last fitted cluster - float mLastZ; // Z of the last fitted cluster + float mLastY; // Y of the last fitted cluster + float mLastZ; // Z of the last fitted cluster }; struct GPUSharedMemory { diff --git a/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx b/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx index 6c35a5651d45f..9518c52d9d140 100644 --- a/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx +++ b/GPU/GPUTracking/Standalone/Benchmark/standalone.cxx @@ -25,6 +25,7 @@ #include "genEvents.h" #include "GPUTPCGMMergedTrack.h" +#define GPUSETTINGS_ALL #include "GPUSettings.h" #include "GPUConstantMem.h" @@ -670,7 +671,7 @@ int32_t RunBenchmark(GPUReconstruction* recUse, GPUChainTracking* chainTrackingU if (configStandalone.testSyncAsync) { printf("Running synchronous phase\n"); } - const GPUTrackingInOutPointers& ioPtrs = ioPtrEvents[!configStandalone.preloadEvents ? 0 : configStandalone.proc.doublePipeline ? (iteration % ioPtrEvents.size()) : (iEvent - configStandalone.StartEvent)]; + const GPUTrackingInOutPointers& ioPtrs = ioPtrEvents[!configStandalone.preloadEvents ? 0 : (configStandalone.proc.doublePipeline ? (iteration % ioPtrEvents.size()) : (iEvent - configStandalone.StartEvent))]; chainTrackingUse->mIOPtrs = ioPtrs; if (iteration == (configStandalone.proc.doublePipeline ? 2 : (configStandalone.runs - 1))) { if (configStandalone.proc.doublePipeline && timerPipeline) { @@ -921,7 +922,7 @@ int32_t main(int argc, char** argv) if (grp.grpContinuousMaxTimeBin == 0) { printf("Cannot override max time bin for non-continuous data!\n"); } else { - grp.grpContinuousMaxTimeBin = chainTracking->mIOPtrs.tpcZS ? GPUReconstructionConvert::GetMaxTimeBin(*chainTracking->mIOPtrs.tpcZS) : chainTracking->mIOPtrs.tpcPackedDigits ? GPUReconstructionConvert::GetMaxTimeBin(*chainTracking->mIOPtrs.tpcPackedDigits) : GPUReconstructionConvert::GetMaxTimeBin(*chainTracking->mIOPtrs.clustersNative); + grp.grpContinuousMaxTimeBin = chainTracking->mIOPtrs.tpcZS ? GPUReconstructionConvert::GetMaxTimeBin(*chainTracking->mIOPtrs.tpcZS) : (chainTracking->mIOPtrs.tpcPackedDigits ? GPUReconstructionConvert::GetMaxTimeBin(*chainTracking->mIOPtrs.tpcPackedDigits) : GPUReconstructionConvert::GetMaxTimeBin(*chainTracking->mIOPtrs.clustersNative)); printf("Max time bin set to %d\n", grp.grpContinuousMaxTimeBin); rec->UpdateSettings(&grp); if (recAsync) { diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 9edefc19a072c..682dfbe60c61c 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -69,6 +69,22 @@ if(NOT GPUCA_EVENT_DISPLAY_OPENGL) set(X11_FOUND 0) endif() +if(GLUT_FOUND) + include(CheckSymbolExists) + set(CMAKE_REQUIRED_INCLUDES ${GLUT_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${GLUT_glut_LIBRARY}) + check_symbol_exists(glutDisplayFuncUcall "GL/freeglut.h;GL/freeglut_ucall.h" GPUCA_GLUT_HAS_UCALL) # Callbacks with user data, freeglut >= 3.2 (modifier key codes exist since 2.8) + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) + if(NOT GPUCA_GLUT_HAS_UCALL) + if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") + message(FATAL_ERROR "GLUT event display frontend needs freeglut >= 3.2") + endif() + message(STATUS "GLUT event display frontend disabled: needs freeglut >= 3.2") + set(GLUT_FOUND 0) + endif() +endif() + if(Vulkan_FOUND) string(FIND "${Vulkan_VERSION}" "." vulkan_dot_index) if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.4.328" OR vulkan_dot_index EQUAL -1 OR NOT GPUCA_GLSLC) @@ -101,6 +117,7 @@ set(SRCS ../utils/qsem.cxx GPUDisplay.cxx helpers/GPUDisplayMagneticField.cxx frontend/GPUDisplayFrontend.cxx + frontend/GPUDisplayConsole.cxx frontend/GPUDisplayFrontendNone.cxx backend/GPUDisplayBackend.cxx backend/GPUDisplayBackendNone.cxx) @@ -113,9 +130,11 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx helpers/GPUDisplayQuaternion.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx - frontend/GPUDisplayKeys.cxx) + frontend/GPUDisplayHelp.cxx + frontend/GPUDisplayFrontendConfig.cxx + frontend/GPUDisplayFrontendKeyTranslation.cxx) -set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h frontend/GPUDisplayFrontendKeys.h frontend/GPUDisplayFrontendKeyVars.h GPUDisplayControl.h GPUDisplayTypes.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) @@ -150,10 +169,11 @@ if(GPUCA_EVENT_DISPLAY_WAYLAND) list(APPEND WAYLAND_PROTOCOL_SOURCES ${source}) endforeach() - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES} APPEND PROPERTY OBJECT_DEPENDS ${WAYLAND_PROTOCOL_HEADERS}) + set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx frontend/GPUDisplayFrontendWaylandEv.cxx APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/) - set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) + set(SRCS ${SRCS} frontend/GPUDisplayFrontendWayland.cxx) + set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWaylandEv.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() if(X11_FOUND) @@ -169,11 +189,10 @@ if(GLFW_FOUND) endif() if(GPUCA_EVENT_DISPLAY_QT) - qt_wrap_cpp(GPUCA_QT_MOC_SRCS frontend/GPUDisplayGUI.h) # Explicit moc / uic instead of AUTOMOC / AUTOUIC: rerun only if these files change; add new Q_OBJECT headers / .ui files here - qt_wrap_ui(GPUCA_QT_UI_HDRS frontend/GPUDisplayGUI.ui) + qt_wrap_cpp(GPUCA_QT_MOC_SRCS frontend/GPUDisplayGUI.h) # Explicit moc instead of AUTOMOC: rerun only if these files change; add new Q_OBJECT headers here set(SRCS ${SRCS} frontend/GPUDisplayGUI.cxx frontend/GPUDisplayGUIWrapper.cxx) - set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS} ${GPUCA_QT_UI_HDRS}) + set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS}) set(HDRS_INSTALL ${HDRS_INSTALL} frontend/GPUDisplayGUIChannel.h) endif() @@ -234,7 +253,7 @@ if (OPENGL_GLU_FOUND) endif() if(GPUCA_CONFIG_GL3W) - target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) + target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W) endif() if(ALIGPU_BUILD_TYPE STREQUAL "O2") @@ -298,7 +317,6 @@ endif() if(GPUCA_EVENT_DISPLAY_QT) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_QT) target_link_libraries(${targetName} PRIVATE Qt6::Widgets) - target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # ui_*.h endif() target_link_libraries(${targetName} PRIVATE TBB::tbb) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 2db90ea9a5fed..487ffa8b3c9db 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) @@ -90,8 +89,8 @@ void GPUDisplay::calcXYZ(const float* matrix) mAngle[1] = atan2f(-matrix[2] / A, matrix[10] / A); mAngle[2] = atan2f(matrix[4] / A, matrix[5] / A); } else { - mAngle[1] = 0; - mAngle[2] = atan2f(-matrix[1], -matrix[0]); + mAngle[1] = 0; // Gimbal lock: yaw and roll rotate around the same axis, all in roll + mAngle[2] = atan2f(-matrix[1], matrix[0]); } mRPhiTheta[0] = sqrtf(mXYZ[0] * mXYZ[0] + mXYZ[1] * mXYZ[1] + mXYZ[2] * mXYZ[2]); @@ -148,7 +147,7 @@ void GPUDisplay::ResizeScene(int32_t width, int32_t height, bool init) mBackend->resizeScene(width, height); if (init) { - mResetScene = true; + mCfgC.resetScene.store(true); *mViewMatrix = MY_HMM_IDENTITY; *mModelMatrix = MY_HMM_IDENTITY; } @@ -172,11 +171,22 @@ int32_t GPUDisplay::InitDisplay(bool initFailure) if (!initFailure) { retVal = InitDisplay_internal(); } + if (retVal == 0) { + buildConfig(); + if (mConfig.listFrontendOptionHandling) { + mFrontend->listConfig(); + } + } } catch (const std::runtime_error& e) { GPUError("%s", e.what()); retVal = 1; } + mOldCfg = {mCfgH, mCfgL, mCfgR}; + std::unique_lock lock(mFrontend->mExitStateMutex); mInitResult = retVal == 0 ? 1 : -1; + mFrontend->mDisplayRunning = retVal == 0; // Only after successful initialization, StopDisplay waits for it to become false again + mFrontend->mInitCV.notify_all(); + mFrontend->mInitCV.wait(lock, [this] { return mFrontend->mStartDisplayDone; }); // Both threads have finished their initialization return (retVal); } @@ -188,7 +198,6 @@ int32_t GPUDisplay::InitDisplay_internal() return 1; } mYFactor = mBackend->getYFactor(); - mDrawTextInCompatMode = !mBackend->mFreetypeInitialized && mFrontend->mCanDrawText == 1; int32_t height = 0, width = 0; mFrontend->getSize(width, height); if (height == 0 || width == 0) { @@ -204,7 +213,7 @@ void GPUDisplay::ExitDisplay() mBackend->ExitBackend(); } -int32_t GPUDisplay::DrawGLScene() +int32_t GPUDisplay::DrawGLScene(bool suspended) { // Make sure event gets not overwritten during display mSemLockDisplay.Lock(); @@ -219,8 +228,12 @@ int32_t GPUDisplay::DrawGLScene() } else if (!mOverlayTFClusters.size()) { mNCollissions = std::max(1u, mIOPtrs->nMCInfosTPCCol); } + if (mCfgL.showCollision >= mNCollissions) { // Fewer collisions in the new event + mCfgL.showCollision = -1; + mUpdateDrawCommands = true; + } try { - DrawGLScene_internal(); + DrawGLScene_internal(suspended); } catch (const std::runtime_error& e) { GPUError("Runtime error %s during display", e.what()); retVal = 1; @@ -232,21 +245,26 @@ int32_t GPUDisplay::DrawGLScene() mLoadAndShowEvent = false; } mCVLoadAndShowEvent.notify_one(); + mNEventsShown++; } mSemLockDisplay.Unlock(); 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; + if (mCfgC.showConsole && mMouseWheelTmp) { // Scrolls the console instead of zooming, one line per step (100) + mFrontend->mConsole->scroll(mMouseWheelTmp / 100 ? mMouseWheelTmp / 100 : (mMouseWheelTmp > 0) - (mMouseWheelTmp < 0)); + mMouseWheelTmp = 0; + } 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->keyMapDown(mFrontend->KEY_MAP_FOCUS_ORIGIN); + bool yUp = mCfgR.camYUp ^ mFrontend->keyMapDown(mFrontend->KEY_MAP_FOCUS_ORIENT_UP) ^ lookOrigin; + bool rotateModelTPC = mCfgO.rotateAroundZ || mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_AROUND_Z); + bool rotateModel = rotateModelTPC || mCfgO.rotateModel || mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_MODEL); // Calculate rotation / translation scaling factors float scalefactor = mFrontend->mKeys[mFrontend->KEY_SHIFT] ? 0.2f : 1.0f; @@ -266,9 +284,9 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla // Perform new rotation / translation / animation - if (animateCamera(animateTime, mixSlaveImage, nextViewMatrix)) { + if (animateCamera(suspended, animateTime, mixSlaveImage, nextViewMatrix)) { // Do nothing else - } else if (mResetScene) { + } else if (mCfgC.resetScene.load()) { const float initialZpos = mCfgH.projectXY ? 16 : (mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR + 8) : 8); nextViewMatrix = nextViewMatrix * HMM_Translate({{0, 0, -initialZpos}}); *mViewMatrix = MY_HMM_IDENTITY; @@ -278,23 +296,23 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla mCfgL.lineWidth = 1.4f; mCfgL.drawSector = -1; mCfgL.showCollision = -1; - mCfgH.xAdd = mCfgH.zAdd = 0; + mCfgH.xzAdd = 0; mCfgR.camLookOrigin = mCfgR.camYUp = false; mAngleRollOrigin = -1e9f; mCfgR.fov = 45.f; mUpdateDrawCommands = true; - mResetScene = false; + mCfgC.resetScene.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->keyMapDown(mFrontend->KEY_MAP_FORWARD) - mFrontend->keyMapDown(mFrontend->KEY_MAP_BACKWARD)) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float moveY = scalefactor * ((float)(mFrontend->keyMapDown(mFrontend->KEY_MAP_STRAFE_DOWN) - mFrontend->keyMapDown(mFrontend->KEY_MAP_STRAFE_UP)) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float moveX = scalefactor * ((float)(mFrontend->keyMapDown(mFrontend->KEY_MAP_STRAFE_LEFT) - mFrontend->keyMapDown(mFrontend->KEY_MAP_STRAFE_RIGHT)) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]) * 0.2f * mFPSScale); + float rotRoll = rotatescalefactor * mFPSScale * 2 * (mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_LEFT) - mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_RIGHT)) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); + float rotYaw = rotatescalefactor * mFPSScale * 2 * (mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_YAW_RIGHT) - mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_YAW_LEFT)) * (!mFrontend->mKeys[mFrontend->KEY_SHIFT]); + float rotPitch = rotatescalefactor * mFPSScale * 2 * (mFrontend->keyMapDown(mFrontend->KEY_MAP_ROTATE_PITCH_DOWN) - mFrontend->keyMapDown(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) { + if ((mFrontend->mMouseDnR && mFrontend->mMouseDn) || mFrontend->mMouseDnM) { moveZ += -scalefactor * mouseScale * ((float)mFrontend->mMouseMvY - (float)mFrontend->mMouseDnY) / 4; rotRoll += -rotatescalefactor * mouseScale * ((float)mFrontend->mMouseMvX - (float)mFrontend->mMouseDnX); } else if (mFrontend->mMouseDnR) { @@ -305,14 +323,16 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla rotPitch += rotatescalefactor * mouseScale * ((float)mFrontend->mMouseMvY - (float)mFrontend->mMouseDnY); } - if (mFrontend->mKeys[(uint8_t)'<'] && !mFrontend->mKeysShift[(uint8_t)'<']) { - mAnimationDelay += moveX; - if (mAnimationDelay < 0.05f) { - mAnimationDelay = 0.05f; + if (mFrontend->keyMapDown(mFrontend->KEY_MAP_CHANGE_ANIMATION_INTERVAL)) { + mCfgO.animationDelay += moveX; + if (mCfgO.animationDelay < 0.05f) { + mCfgO.animationDelay = 0.05f; + } else if (mCfgO.animationDelay > 20.f) { + mCfgO.animationDelay = 20.f; } moveX = 0.f; moveY = 0.f; - SetInfo("Animation delay set to %1.2f", mAnimationDelay); + SetInfo("Animation delay set to %1.2f", mCfgO.animationDelay); } if (yUp) { @@ -361,15 +381,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla } else { nextViewMatrix = nextViewMatrix * HMM_Translate({{moveX, moveY * mYFactor, moveZ}}); if (!rotateModel) { - if (rotYaw != 0.f) { - nextViewMatrix = nextViewMatrix * HMM_Rotate(rotYaw, {{0, 1, 0}}); - } - if (rotPitch != 0.f) { - nextViewMatrix = nextViewMatrix * HMM_Rotate(rotPitch * mYFactor, {{1, 0, 0}}); - } - if (!yUp && rotRoll != 0.f) { - nextViewMatrix = nextViewMatrix * HMM_Rotate(rotRoll * mYFactor, {{0, 0, 1}}); - } + nextViewMatrix = nextViewMatrix * cameraRotation({rotYaw, rotPitch, yUp ? 0.f : rotRoll}); // Keyboard and mouse, as rotateCamera } nextViewMatrix = nextViewMatrix * *mViewMatrix; // Apply previous translation / rotation if (yUp) { @@ -395,7 +407,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->keyMapDown(mFrontend->KEY_MAP_INCREASE_WIDTH) - mFrontend->keyMapDown(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 +417,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->keyMapDown(mFrontend->KEY_MAP_INCREASE_WIDTH) - mFrontend->keyMapDown(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; @@ -422,7 +434,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla calcXYZ(&mViewMatrix->Elements[0][0]); } - if (mFrontend->mMouseDn || mFrontend->mMouseDnR) { + if (mFrontend->mMouseDn || mFrontend->mMouseDnR || mFrontend->mMouseDnM) { mFrontend->mMouseDnX = mFrontend->mMouseMvX; mFrontend->mMouseDnY = mFrontend->mMouseMvY; } @@ -433,8 +445,8 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla void GPUDisplay::DrawGLScene_drawCommands() { -#define LOOP_SECTOR for (int32_t iSector = (mCfgL.drawSector == -1 ? 0 : mCfgL.drawRelatedSectors ? (mCfgL.drawSector % (NSECTORS / 4)) : mCfgL.drawSector); iSector < NSECTORS; iSector += (mCfgL.drawSector == -1 ? 1 : mCfgL.drawRelatedSectors ? (NSECTORS / 4) : NSECTORS)) -#define LOOP_SECTOR2 for (int32_t iSector = (mCfgL.drawSector == -1 ? 0 : mCfgL.drawRelatedSectors ? (mCfgL.drawSector % (NSECTORS / 4)) : mCfgL.drawSector) % (NSECTORS / 2); iSector < NSECTORS / 2; iSector += (mCfgL.drawSector == -1 ? 1 : mCfgL.drawRelatedSectors ? (NSECTORS / 4) : NSECTORS)) +#define LOOP_SECTOR for (int32_t iSector = (mCfgL.drawSector == -1 ? 0 : (mCfgL.drawRelatedSectors ? (mCfgL.drawSector % (NSECTORS / 4)) : mCfgL.drawSector)); iSector < NSECTORS; iSector += (mCfgL.drawSector == -1 ? 1 : (mCfgL.drawRelatedSectors ? (NSECTORS / 4) : NSECTORS))) +#define LOOP_SECTOR2 for (int32_t iSector = (mCfgL.drawSector == -1 ? 0 : (mCfgL.drawRelatedSectors ? (mCfgL.drawSector % (NSECTORS / 4)) : mCfgL.drawSector)) % (NSECTORS / 2); iSector < NSECTORS / 2; iSector += (mCfgL.drawSector == -1 ? 1 : (mCfgL.drawRelatedSectors ? (NSECTORS / 4) : NSECTORS))) #define LOOP_COLLISION for (int32_t iCol = (mCfgL.showCollision == -1 ? 0 : mCfgL.showCollision); iCol < mNCollissions; iCol += (mCfgL.showCollision == -1 ? 1 : mNCollissions)) #define LOOP_COLLISION_COL(cmd) \ LOOP_COLLISION \ @@ -627,20 +639,22 @@ void GPUDisplay::DrawGLScene_drawCommands() } } -void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) // negative time = no mixing +void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool renderToMixBuffer) // negative time = no mixing { - if (mRequestScreenshot || mAnimateScreenshot) { + if (mCfgC.requestScreenshot || mCfgO.animateScreenshot) { 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.requestScreenshot = false; + mCfgO.animateScreenshot = 0; } } - bool doScreenshot = (mRequestScreenshot || mAnimateScreenshot) && animateTime < 0; + const bool animateScreenshot = mCfgO.animateScreenshot && mAnimate; // Copy, the animation resets animateScreenshot in its last frame + const bool screenshotFrame = mCfgC.requestScreenshot || animateScreenshot; // Also for the mix pass (animateTime >= 0), which must use the same render size + const bool doScreenshot = screenshotFrame && animateTime < 0; updateOptions(); - if (animateTime < 0 && (mUpdateEventData || mResetScene || mUpdateVertexLists) && mIOPtrs) { + if (animateTime < 0 && (mUpdateEventData || mCfgC.resetScene.load() || mUpdateVertexLists) && mIOPtrs) { disableUnsupportedOptions(); } if (mUpdateEventData || mUpdateVertexLists) { @@ -648,7 +662,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) } HighResTimer timerDraw(mUpdateVertexLists); - if (animateTime < 0 && (mUpdateEventData || mResetScene) && mIOPtrs) { + if (animateTime < 0 && (mUpdateEventData || mCfgC.resetScene.load()) && mIOPtrs) { timerDraw.ResetStart(); DrawGLScene_updateEventData(); mTimerFPS.ResetStart(); @@ -660,7 +674,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) hmm_mat4 nextViewMatrix = MY_HMM_IDENTITY; float mixSlaveImage = 0.f; - DrawGLScene_cameraAndAnimation(animateTime, mixSlaveImage, nextViewMatrix); + DrawGLScene_cameraAndAnimation(suspended, animateTime, mixSlaveImage, nextViewMatrix); // Prepare Event if (mUpdateVertexLists && mIOPtrs) { @@ -670,11 +684,16 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) } } + if (suspended) { + return; + } + // Draw Event nextViewMatrix = nextViewMatrix * *mModelMatrix; const float zFar = ((mParam->par.continuousTracking ? (mMaxClusterZ * GL_SCALE_FACTOR) : 8.f) + 50.f) * 2.f; - const hmm_mat4 proj = HMM_Perspective(mCfgR.fov, (float)mBackend->mRenderWidth / (float)mBackend->mRenderHeight, 0.1f, zFar); - if (!mBackend->prepareDraw(proj, nextViewMatrix, doScreenshot || mRequestScreenshot, renderToMixBuffer, mixSlaveImage)) { + const float aspect = mBackend->mRenderHeight ? (float)mBackend->mRenderWidth / (float)mBackend->mRenderHeight : 1.f; // No render size with the None backend + const hmm_mat4 proj = HMM_Perspective(mCfgR.fov, aspect, 0.1f, zFar); + if (!mBackend->prepareDraw(proj, nextViewMatrix, screenshotFrame, renderToMixBuffer, mixSlaveImage)) { return; // No image available (e.g. hidden window), retry with the next frame } mBackend->pointSizeFactor(1); @@ -700,12 +719,12 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) char info[1024]; float fps = (double)mFramesDoneFPS / fpstime; snprintf(info, 1024, - "FPS: %6.2f (Sector: %d, 1:Clusters %d, 2:Prelinks %d, 3:Links %d, 4:Seeds %d, 5:Tracklets %d, 6:Tracks %d, 7:GTracks %d, 8:Merger %d) (%d frames, %d draw calls) " - "(X %1.2f Y %1.2f Z %1.2f / R %1.2f Phi %1.1f Theta %1.1f) / Yaw %1.1f Pitch %1.1f Roll %1.1f)", + "FPS:%8.2f (Sector: %d, 1:Clusters %d, 2:Prelinks %d, 3:Links %d, 4:Seeds %d, 5:Tracklets %d, 6:Tracks %d, 7:GTracks %d, 8:Merger %d) (%d frames, %d draw calls) " + "(X %1.2f Y %1.2f Z %1.2f / R %1.2f Phi %1.1f Theta %1.1f) / Yaw %1.1f Pitch %1.1f Roll %1.1f - Size: %ux%u", fps, mCfgL.drawSector, mCfgL.drawClusters, mCfgL.drawInitLinks, mCfgL.drawLinks, mCfgL.drawSeeds, mCfgL.drawTracklets, mCfgL.drawTracks, mCfgL.drawExtrapolatedTracks, mCfgL.drawFinal, mFramesDone, mNDrawCalls, mXYZ[0], mXYZ[1], mXYZ[2], mRPhiTheta[0], mRPhiTheta[1] * 180 / CAMath::Pi(), - mRPhiTheta[2] * 180 / CAMath::Pi(), mAngle[1] * 180 / CAMath::Pi(), mAngle[0] * 180 / CAMath::Pi(), mAngle[2] * 180 / CAMath::Pi()); + mRPhiTheta[2] * 180 / CAMath::Pi(), mAngle[1] * 180 / CAMath::Pi(), mAngle[0] * 180 / CAMath::Pi(), mAngle[2] * 180 / CAMath::Pi(), mBackend->mScreenWidth, mBackend->mScreenHeight); if (fpstime > 1.) { - if (mPrintInfoText & 2) { + if (mCfgO.printInfoText & 2) { GPUInfo("%s", info); } if (mFPSScaleadjust++) { @@ -715,20 +734,42 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) mFramesDoneFPS = 0; } - if (mPrintInfoText & 1) { + const float consoleFraction = consoleVerticalFraction(); + const bool drawText = (mCfgO.printInfoText & 1) || consoleFraction > 0.f; + if (drawText) { + mBackend->updateFontScale(mFrontend->getDisplayScale()); + mBackend->prepareText(); + } + if (mCfgO.printInfoText & 1 || mCfgO.printInfoTextGUI) { showInfo(info); } + if (consoleFraction > 0.f) { + const bool open = mCfgC.showConsole && mConsoleTimer.GetCurrentElapsedTime() >= mConfig.internal.consoleDelay; // Cursor only when fully open + if (!open) { + mConsoleCursorTimer.Reset(); + } else if (!mConsoleCursorTimer.IsRunning() || mConsoleCursorPos != mFrontend->mConsole->mConsoleTextPos) { // Blinking restarts visible when the cursor moves + mConsoleCursorTimer.ResetStart(); + mConsoleCursorPos = mFrontend->mConsole->mConsoleTextPos; + } + const bool cursor = open && std::fmod(mConsoleCursorTimer.GetCurrentElapsedTime(), 1.0) < 0.5; + auto& console = *mFrontend->mConsole; + console.mVisibleLines = mBackend->GLDrawConsole(consoleFraction, console.mConsoleHistory, console.shownText(), cursor ? (int32_t)console.mConsoleTextPos : -1, console.mScroll); + } + if (drawText) { + mBackend->finishText(); + } + mFrontend->mConsole->consoleLimitHistory(mConfig.internal.consoleHistorySize); } - mBackend->finishFrame(mRequestScreenshot, renderToMixBuffer, mixSlaveImage); + mBackend->finishFrame(doScreenshot, renderToMixBuffer, mixSlaveImage); if (doScreenshot) { - mRequestScreenshot = false; + mCfgC.requestScreenshot = false; std::vector pixels = mBackend->getPixels(); char tmpFileName[48]; - if (mAnimateScreenshot) { - snprintf(tmpFileName, 48, "mAnimation%d_%05d.bmp", mAnimationExport, mAnimationFrame); + if (animateScreenshot) { + snprintf(tmpFileName, 48, "%d_%05d.bmp", mAnimationExport, mAnimationFrame); } - DoScreenshot(mAnimateScreenshot ? tmpFileName : mScreenshotFile.c_str(), pixels); + DoScreenshot(animateScreenshot ? (mCfgO.mscreenShotName + tmpFileName).c_str() : mScreenshotFile.c_str(), pixels); } } @@ -738,9 +779,8 @@ void GPUDisplay::ShowNextEvent(const GPUTrackingInOutPointers* ptrs) mIOPtrs = ptrs; } if (mMaxClusterZ <= 0) { - mResetScene = true; + mCfgC.resetScene.store(true); } - mFrontend->mNeedUpdate = 1; mUpdateEventData = true; mLoadAndShowEvent = true; mSemLockDisplay.Unlock(); @@ -765,11 +805,5 @@ void GPUDisplay::WaitTillEventShown() int32_t GPUDisplay::StartDisplay() { mLoadAndShowEvent = true; - if (mFrontend->StartDisplay()) { - return (1); - } - while (mInitResult == 0) { - Sleep(10); - } - return (mInitResult != 1); + return mFrontend->StartDisplay(); } diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 67ab1a10334c9..dcebb791faaec 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -15,13 +15,17 @@ #ifndef GPUDISPLAY_H #define GPUDISPLAY_H +#define GPUSETTINGS_DISPLAY +#define GPUSETTINGS_DISPLAYCONTROL +#include "GPUSettings.h" #include "frontend/GPUDisplayFrontend.h" #include "backend/GPUDisplayBackend.h" #include "GPUDisplayInterface.h" -#include "GPUSettings.h" #include "DataFormatsTPC/Constants.h" +#include #include +#include #include #include @@ -58,12 +62,12 @@ class GPUDisplay : public GPUDisplayInterface void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; } void UpdateParam(const GPUParam* param) override { mParam = param; } - void HandleKey(uint8_t key, bool doNotHandleConfigChanges = false); // With doNotHandleConfigChanges, the caller must call checkAndApplyConfigChanges - int32_t DrawGLScene(); - void HandleExternalGUIKey(int32_t key); // Does not handle config changes, see HandleKey + void HandleKey(uint8_t key, bool external = false, bool bypassConsole = false); // external: from the Qt GUI or the console, not typed into the event display console; bypassConsole: handled as key also while the console is shown (injectKey) + int32_t DrawGLScene(bool suspended = false); + void HandleExternalGUIKey(int32_t key, bool bypassConsole = false); // key >= 256: special key with SHIFT int32_t InitDisplay(bool initFailure = false); void ResizeScene(int32_t width, int32_t height, bool init = false); - void checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCfgH, const GPUSettingsDisplayLight& oldCfgL, const GPUSettingsDisplayRenderer& oldCfgR); // Set update flags / apply the settings that changed compared to the old copies + void checkAndApplyConfigChanges(); // Set update flags / apply the settings that changed compared to mOldCfg const GPUSettingsDisplayRenderer& cfgR() const { return mCfgR; } const GPUSettingsDisplayLight& cfgL() const { return mCfgL; } @@ -83,10 +87,17 @@ 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; } + GPUDisplayOptions mCfgO; + GPUDisplayControl mCfgC; + private: + void buildConfig(); + void buildConfigKeys(); + void buildConfigInfo(); + void buildConfigQtGUI(); + static constexpr int32_t NSECTORS = o2::tpc::constants::MAXSECTOR; static constexpr float GL_SCALE_FACTOR = (1.f / 100.f); @@ -98,6 +109,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, @@ -138,6 +150,11 @@ class GPUDisplay : public GPUDisplayInterface } }; + struct animationVector { // Animation point, its time is in mCfgC.animationPoints.values + float v[6]; // Camera: x, y, z, pitch, yaw, roll (as mXYZ, mAngle), independent of the animation mode + GPUSettingsDisplayLight cfgL; // Settings at the point + }; + class opengl_spline { public: @@ -154,9 +171,9 @@ class GPUDisplay : public GPUDisplayInterface static const GPUSettingsDisplay& GetConfig(GPUChainTracking* chain); static const GPUSettingsProcessing& GetProcessingConfig(GPUChainTracking* chain); - void DrawGLScene_internal(float animateTime = -1.f, bool renderToMixBuffer = false); + void DrawGLScene_internal(bool suspended, float animateTime = -1.f, bool renderToMixBuffer = false); // suspended: return before drawing void DrawGLScene_updateEventData(); - void DrawGLScene_cameraAndAnimation(float animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); + void DrawGLScene_cameraAndAnimation(bool suspended, float animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); size_t DrawGLScene_updateVertexList(); void DrawGLScene_drawCommands(); int32_t InitDisplay_internal(); @@ -164,6 +181,7 @@ class GPUDisplay : public GPUDisplayInterface void updateOptions(); void disableUnsupportedOptions(); int32_t buildTrackFilter(); + std::string trackFilterName() const; // Selected track filter: macro file from filterMacros, or "Custom Macro" const GPUTPCTracker& sectorTracker(int32_t iSector); const GPUTRDGeometry* trdGeometry(); const GPUTrackingInOutPointers* mIOPtrs = nullptr; @@ -175,12 +193,30 @@ 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 mCfgO.glPosFile + void LoadGLPos(); + void SaveGLAnimation(); // Animation path to mCfgO.glAnimationFile + void LoadGLAnimation(); + void takeScreenshot(); + void runAnimation(); // Starts the animation if it has enough points void setAnimationPoint(); + void setCamera(const float (&xyz)[3], const float (&angles)[3]); // Position and angles (radians, as mAngle: pitch, yaw, roll) + void addAnimationPoint(const float (&xyz)[3], const float (&angles)[3]); // Animation point for this camera, the current camera is kept + bool lookAt(const float (&target)[3]); // Turn the camera to the point, false if it is the camera position + void rotateCamera(const float (&angles)[3]); // Yaw, pitch, roll in degrees relative to the view, as the keyboard + hmm_mat4 cameraRotation(const float (&angles)[3]) const; // Rotation of rotateCamera, applied to the view matrix also for keyboard / mouse void resetAnimation(); - void removeAnimationPoint(); + void removeAnimationPoint(int32_t i = -1); // -1: the selected one, the last one if none is selected + void StopAnimation(); // Ends a running animation, camera and settings stay as they are; for changed settings (checkAndApplyConfigChanges), scene reset, changes of the animation points + void selectAnimationPoint(int32_t i); // -1: none, otherwise also loads its light settings and camera + void setAnimationPointsFromGUI(const GPUDisplayMultiSliderValue& points); // New times from the Qt GUI: reorders the points, selection + void updateAnimationPointsGUI(); // Range end of the GUI multi slider: last time + animationDelay, progress 0 without animation + void animationPointValues(const animationVector& p, int32_t mode, const float* last, float (&vals)[8]); // The 8 interpolated values of the point in this animation mode, angles / quaternion continuous to the values of the previous point (last, nullptr if none) + void animationViewMatrix(float (&vals)[8], int32_t mode, hmm_mat4& viewMatrix) const; // Camera of interpolated / stored animation values (vals of animationPointValues), as animateCamera void startAnimation(); - int32_t animateCamera(float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); + int32_t animateCamera(bool suspended, float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); void showInfo(const char* info); + float consoleVerticalFraction(); // Fraction of the window covered by the console (animated), 0 if hidden void ActivateColor(); void SetColorTRD(); void SetColorTOF(); @@ -217,7 +253,7 @@ class GPUDisplay : public GPUDisplayInterface void PrintHelp(); void createQuaternionFromMatrix(float* v, const float* mat); void drawVertices(const vboList& v, const GPUDisplayBackend::drawType t); - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true); + void GLPrintText(const char* s, float x, float y, float r, float g, float b, float a, uint32_t nCharacters = 0, bool fromBotton = true); // nCharacters > 0: print at most that many characters GPUDisplayFrontend* mFrontend = nullptr; std::unique_ptr mBackend; @@ -228,6 +264,7 @@ class GPUDisplay : public GPUDisplayInterface GPUSettingsDisplayLight mCfgL; GPUSettingsDisplayHeavy mCfgH; GPUSettingsDisplayRenderer mCfgR; + std::tuple mOldCfg; // Settings after the last draw, for checkAndApplyConfigChanges const GPUSettingsProcessing& mProcessingSettings; GPUQA* mQA; @@ -235,7 +272,6 @@ class GPUDisplay : public GPUDisplayInterface std::mutex mMutexLoadAndShowEvent; std::condition_variable mCVLoadAndShowEvent; - bool mDrawTextInCompatMode = false; int32_t mDrawTextFontSize = 0; int32_t mNDrawCalls = 0; @@ -244,8 +280,6 @@ class GPUDisplay : public GPUDisplayInterface std::array mDrawColor = {1.f, 1.f, 1.f, 1.f}; - int32_t mTestSetting = 0; - float mAngleRollOrigin = -1e9; float mMaxClusterZ = -1; @@ -286,12 +320,14 @@ class GPUDisplay : public GPUDisplayInterface std::vector mTrackFilter; volatile bool mUpdateTrackFilter = false; + std::string mTrackFilterMacroLoaded; // Track filter macro file loaded into ROOT, unloaded before loading another one (same function name) + bool mUpdateGUIOptions = false; // The Qt GUI must call the dynamic option lambdas of its elements again (new event, GUI started) volatile bool mUpdateVertexLists = true; volatile bool mUpdateEventData = false; volatile bool mUpdateDrawCommands = true; volatile bool mUpdateRenderPipeline = false; - volatile bool mResetScene = false; volatile bool mLoadAndShowEvent = false; + uint32_t mNEventsShown = 0; // Events loaded and drawn, in the frontend thread bool mTracksArePropagated = false; int32_t mAnimate = 0; @@ -299,22 +335,22 @@ 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; + vecpod mAnimationVectors; + std::vector> mAnimationSplines; // By animation mode: the 8 values of animationPointValues + char mInfoText2[1024]; - HighResTimer mInfoText2Timer, mInfoHelpTimer; + std::string mGUIInfoStatus; // Latest status line (FPS etc.) for the Qt GUI + std::string mGUIInfoText; // Latest info message (SetInfo) for the Qt GUI + uint64_t mGUIInfoTextCounter = 0; // Number of info messages, the GUI shows a new one for some seconds + HighResTimer mInfoText2Timer, mInfoHelpTimer, mConsoleTimer; // mConsoleTimer: opening / closing animation of the console + HighResTimer mConsoleCursorTimer; // Blinking of the console cursor, started when the console is fully open, restarted when the cursor moves + uint32_t mConsoleCursorPos = 0; // Cursor position at the last restart of mConsoleCursorTimer + bool mConsoleShown = false; // showConsole of the last checkAndApplyConfigChanges std::vector mThreadBuffers; std::vector, 2>, NSECTORS>>> mThreadTracks; - volatile int32_t mInitResult = 0; + int32_t mInitResult = 0; // 1 success, -1 failure, protected by mFrontend->mExitStateMutex float mFPSScale = 1, mFPSScaleadjust = 0; int32_t mFramesDone = 0, mFramesDoneFPS = 0; @@ -326,7 +362,6 @@ class GPUDisplay : public GPUDisplayInterface vboList mGlDLGrid[NSECTORS]; vboList mGlDLGridTRD[NSECTORS / 2]; - bool mRequestScreenshot = false; std::string mScreenshotFile; float mYFactor = 1.0f; diff --git a/GPU/GPUTracking/display/GPUDisplayControl.h b/GPU/GPUTracking/display/GPUDisplayControl.h new file mode 100644 index 0000000000000..32a9b20b7f0a8 --- /dev/null +++ b/GPU/GPUTracking/display/GPUDisplayControl.h @@ -0,0 +1,50 @@ +// 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(GPUDisplayOptions, options) // Display options without command line option, saved and reset with the other settings (console saveSettings / reset) + AddVariable(printInfoText, int32_t, 1) + AddVariable(printInfoTextAlways, bool, false) + AddVariable(animateScreenshot, int32_t, 0) + AddVariable(animationChangeConfig, bool, true) + AddVariable(animationDelay, float, 2.f) + AddVariable(printInfoTextGUI, bool, true) // Send the status and info text to the Qt GUI, shown at its bottom + AddVariable(guiPrintInfo, bool, false) // Show the info of options changed with Qt GUI elements (not for key presses in the GUI) + AddVariable(rotateModel, bool, false) // Rotate the model instead of the camera, like holding RCTRL (set in the Qt GUI) + AddVariable(rotateAroundZ, bool, false) // Rotate the TPC around the beamline, like holding RALT (set in the Qt GUI) + AddVariable(filterMacroCustom, 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(glPosFile, std::string, "glpos.tmp") // File for saving / loading the camera position + AddVariable(glAnimationFile, std::string, "glanimation.tmp") // File for saving / loading the animation path + AddVariable(guiDarkMode, int8_t, -1) // Dark mode of the Qt GUI, -1 = not yet set: the GUI reports the system scheme when it starts + AddVariable(guiScale, 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(applyToAllPoints, bool, false) // A change of the animation mode is applied to all animation points +EndConfig() + +BeginHiddenConfig(GPUDisplayControl, control) // Transient control state and actions: not saved, not reset with the other settings + AddVariable(displayControl, GPUDisplayCopyableAtomic, 0) // Control for next event (=1) or quit (=2) + AddVariable(resetScene, GPUDisplayCopyableAtomic, false) + AddVariable(requestScreenshot, bool, false) + AddVariable(showConsole, bool, false) // Console shown (^ key): all other keys go to the console + AddVariable(animationPoints, GPUDisplayMultiSliderValue, GPUDisplayMultiSliderValue()) // Times of the animation points (GPUDisplay::mAnimationVectors) and the selected one, shown in the Qt GUI +EndConfig() +EndNamespace() // o2::gpu diff --git a/GPU/GPUTracking/display/GPUDisplayInterface.h b/GPU/GPUTracking/display/GPUDisplayInterface.h index aabc1c25b75e1..470e95acfbe86 100644 --- a/GPU/GPUTracking/display/GPUDisplayInterface.h +++ b/GPU/GPUTracking/display/GPUDisplayInterface.h @@ -59,7 +59,7 @@ class GPUDisplayFrontendInterface virtual void StopDisplay() = 0; virtual int32_t getDisplayControl() const = 0; virtual void setDisplayControl(int32_t v) = 0; - virtual void sendExternalGUIKey(int32_t v) = 0; + virtual void sendExternalGUIKey(int32_t v, uint32_t translationMode = 0, uint64_t extendedKey = 0) = 0; // translationMode != 0: key from extendedKey (1 = console escape sequence, up to 8 characters after the ESC) virtual const char* frontendName() const = 0; protected: diff --git a/GPU/GPUTracking/display/GPUDisplayTypes.h b/GPU/GPUTracking/display/GPUDisplayTypes.h new file mode 100644 index 0000000000000..4d3e22a72350a --- /dev/null +++ b/GPU/GPUTracking/display/GPUDisplayTypes.h @@ -0,0 +1,52 @@ +// 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 + +#include "utils/vecpod.h" +#include +#include +#include + +namespace o2::gpu +{ +struct GPUDisplayMultiSliderValue { // Value of a Qt GUI multi slider (GUI_MULTI_SLIDER): positions in the order of the display, selected one (-1: none), end of the range, progress + vecpod values; + int16_t selected = -1; + uint16_t progress = 0; // Fraction (of 65535) of the last position highlighted, e.g. a running animation; small types: GPUDisplayGUIValue must not grow + float max = 0.f; + bool operator==(const GPUDisplayMultiSliderValue&) const = default; + friend std::ostream& operator<<(std::ostream& out, const GPUDisplayMultiSliderValue& v) // For the qconfig printout + { + for (float x : v.values) { + out << x << " "; + } + return out << "(selected " << v.selected << ")"; + } +}; + +template +struct GPUDisplayCopyableAtomic : std::atomic { // std::atomic deletes the copy, here it copies the value + using std::atomic::atomic; + GPUDisplayCopyableAtomic(const GPUDisplayCopyableAtomic& o) : std::atomic(o.load()) {} + GPUDisplayCopyableAtomic& operator=(const GPUDisplayCopyableAtomic& o) + { + this->store(o.load()); + return *this; + } +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index c1e39311b1768..4c39fd12a50ae 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -132,7 +132,7 @@ void GPUDisplayBackend::loadFont() mFontRasterScale = mDisplay->frontend()->getDisplayScale(); FT_Set_Pixel_Sizes(face, 0, (uint32_t)(fontSize * mFontRasterScale + 0.5f)); // Rasterize at physical pixel size for HiDPI - for (uint32_t i = 0; i < 128; i++) { + for (uint32_t i = 0; i < SOLID_GLYPH; i++) { if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { GPUError("Error loading freetype symbol"); FT_Done_Face(face); @@ -140,8 +140,13 @@ void GPUDisplayBackend::loadFont() return; } const auto& glyph = face->glyph; + if (i == 'M') { + mFontCharWidth = (glyph->advance.x >> 6) * (smoothFont() ? 0.25f : 1.f) / mFontRasterScale; // As in GLPrintText of the backends + } addFontSymbol(i, glyph->bitmap.width, glyph->bitmap.rows, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x, glyph->bitmap.buffer); } + std::vector solid(8 * 8, 255); + addFontSymbol(SOLID_GLYPH, 8, 8, 0, 0, 0, solid.data()); initializeTextDrawing(); FT_Done_Face(face); FT_Done_FreeType(ft); @@ -163,13 +168,16 @@ std::vector GPUDisplayBackend::getPixels() void GPUDisplayBackend::fillIndirectCmdBuffer() { - mCmdBuffer.clear(); mIndirectSectorOffset.resize(GPUTPCGeometry::NSECTORS); - // TODO: Check if this can be parallelized + size_t total = 0; + for (uint32_t iSector = 0; iSector < GPUTPCGeometry::NSECTORS; iSector++) { + mIndirectSectorOffset[iSector] = total; + total += mDisplay->vertexBufferStart()[iSector].size(); + } + mCmdBuffer.resize(total); for (uint32_t iSector = 0; iSector < GPUTPCGeometry::NSECTORS; iSector++) { - mIndirectSectorOffset[iSector] = mCmdBuffer.size(); for (uint32_t k = 0; k < mDisplay->vertexBufferStart()[iSector].size(); k++) { - mCmdBuffer.emplace_back(mDisplay->vertexBufferCount()[iSector][k], 1, mDisplay->vertexBufferStart()[iSector][k], 0); + mCmdBuffer[mIndirectSectorOffset[iSector] + k] = DrawArraysIndirectCommand(mDisplay->vertexBufferCount()[iSector][k], 1, mDisplay->vertexBufferStart()[iSector][k], 0); } } } @@ -201,3 +209,47 @@ 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); +} + +uint32_t GPUDisplayBackend::GLDrawConsole(float verticalFraction, std::span previous, const std::string& current, int32_t cursorPos, uint32_t scroll) +{ + if (!mFreetypeInitialized) { + return 1; + } + 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().internal.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 + const float x0 = (MARGIN + cursorPos * mFontCharWidth) * scale, x1 = x0 + mFontCharWidth * scale; + const float y1 = y - 0.5f * descent, y0 = y1 - 3.f * scale; + GLDrawQuad({{x0, y0}, {x1, y0}, {x1, y1}, {x0, y1}}, textColor); // Solid underline, 3 logical pixels, below the baseline + } + const uint32_t nFit = std::max(1.f, std::floor((top - y + descent) / lineHeight) - 1.f); // Lines above the current one that fit, as in the loop + scroll = std::min(scroll, previous.size()); + for (size_t i = 0; i <= previous.size() - scroll && y + lineHeight - descent <= top; i++, y += lineHeight) { + const std::string& text = i == 0 ? current : previous[previous.size() - scroll - i]; + if (!text.empty() && maxChars) { + GLPrintText(text.c_str(), MARGIN * scale, y, textColor, scale / mFontRasterScale, std::min(text.size(), maxChars)); + } + } + return nFit; +} diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 38114662714af..47678b4ed67b8 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -17,6 +17,8 @@ #include "GPUCommonDef.h" #include "../utils/vecpod.h" +#include +#include #include #include #include @@ -39,8 +41,6 @@ class GPUDisplayBackend virtual ~GPUDisplayBackend(); virtual int32_t ExtInit() { return 0; }; - virtual bool CoreProfile() { return false; }; - virtual uint32_t DepthBits() = 0; typedef std::tuple vboList; @@ -101,7 +101,10 @@ class GPUDisplayBackend const char* backendName() const { return mBackendName; } virtual void resizeScene(uint32_t width, uint32_t height) {} virtual size_t needMultiVBO() { return 0; } - virtual void OpenGLPrint(const char* s, float x, float y, float* color, float scale) = 0; + virtual void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) = 0; // nCharacters > 0: at most that many characters, otherwise until the terminating 0 + virtual void GLDrawQuad(const float (&corners)[4][2], const float* color) = 0; // Filled quad with the corners counterclockwise, in pixels with y from the bottom, drawn with the text (SOLID_GLYPH) + void GLDrawLine(float x0, float y0, float x1, float y1, float width, const float* color); // As GLDrawQuad + uint32_t GLDrawConsole(float verticalFraction, std::span previous, const std::string& current, int32_t cursorPos, uint32_t scroll); // Console over the top verticalFraction of the window: current text in the lowest line, previous ones above (last one lowest, the last scroll ones skipped), cursor underline at cursorPos (< 0: none); returns the number of previous lines that fit static GPUDisplayBackend* getBackend(const char* type); std::vector getPixels(); virtual float getYFactor() const { return 1.0f; } @@ -121,8 +124,9 @@ class GPUDisplayBackend std::vector mIndirectSectorOffset; vecpod mCmdBuffer; bool mFreetypeInitialized = false; - float mFontRasterScale = 1.f; // Display scale the font was rasterized for - bool mFrontendCompatTextDraw = false; + float mFontRasterScale = 1.f; // Display scale the font was rasterized for + static constexpr int32_t SOLID_GLYPH = 128; // Fully opaque glyph after the characters (0 - 127), for quads drawn with the text + float mFontCharWidth = 0.f; // Advance of a character of the (monospace) font in logical pixels, 0 without Freetype font std::vector mScreenshotPixels; static constexpr float SMOOTH_POINT_EXTRA_SIZE = 0.6f; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h index 6d4160c093206..d5ec0e22b8254 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h @@ -26,7 +26,6 @@ class GPUDisplayBackendNone : public GPUDisplayBackend ~GPUDisplayBackendNone() override = default; protected: - uint32_t DepthBits() override { return 32; }; uint32_t drawVertices(const vboList& v, const drawType t) override { return 0; } void ActivateColor(std::array& color) override {} void setDepthBuffer() override {} @@ -40,7 +39,8 @@ class GPUDisplayBackendNone : public GPUDisplayBackend void finishText() override {} void pointSizeFactor(float factor) override {} void lineWidthFactor(float factor) override {} - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override {} + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override {} + void GLDrawQuad(const float (&corners)[4][2], const float* color) override {} void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override {} void initializeTextDrawing() override {} }; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index d5342e0ab5abb..3d91ee802c16f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -66,15 +66,6 @@ bool GPUDisplayBackendOpenGL::canLimitToVSync() const return mDisplay->frontend()->canSetVSync(); } -bool GPUDisplayBackendOpenGL::CoreProfile() -{ -#ifdef GPUCA_DISPLAY_OPENGL_CORE - return true; -#else - return false; -#endif -} - // #define CHKERR(cmd) {cmd;} #define CHKERR(cmd) \ do { \ @@ -86,15 +77,6 @@ bool GPUDisplayBackendOpenGL::CoreProfile() } \ } while (false) -uint32_t GPUDisplayBackendOpenGL::DepthBits() -{ - GLint depthBits = 0; -#ifndef GPUCA_DISPLAY_OPENGL_CORE - glGetIntegerv(GL_DEPTH_BITS, &depthBits); -#endif - return depthBits; -} - void GPUDisplayBackendOpenGL::createFB(GLfb& fb, bool tex, bool withDepth, bool msaa, uint32_t width, uint32_t height) { fb.tex = tex; @@ -185,22 +167,13 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } if (mDisplay->useMultiVBO()) { - if (!mDisplay->cfgR().rendererCompatSmoothing) { // TODO: Remove OpenGL compat profile completely - CHKERR(glBindVertexArray(mVertexArray)); - } + CHKERR(glBindVertexArray(mVertexArray)); CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[iSector])); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); - } else -#endif - { - CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); - glEnableVertexAttribArray(0); - } + CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); + glEnableVertexAttribArray(0); } - const uint32_t smoothProgram = tt == POINTS ? (mUseSmoothPointShader ? mShaderProgramSmoothPoint : 0) : (mUseSmoothLineShader ? mShaderProgramSmoothLine : 0); + const uint32_t smoothProgram = tt == POINTS ? (mDisplay->cfgL().smoothPoints ? mShaderProgramSmoothPoint : 0) : (mDisplay->cfgL().smoothLines ? mShaderProgramSmoothLine : 0); if (smoothProgram) { CHKERR(glUseProgram(smoothProgram)); } @@ -222,19 +195,12 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glColor4f(color[0], color[1], color[2], color[3]); - } else -#endif - { - CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); - if (mUseSmoothPointShader) { - CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); - } - if (mUseSmoothLineShader) { - CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); - } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); + if (mDisplay->cfgL().smoothPoints) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_COLOR, 1, &color[0])); + } + if (mDisplay->cfgL().smoothLines) { + CHKERR(glProgramUniform4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_COLOR, 1, &color[0])); } } @@ -303,6 +269,11 @@ int32_t GPUDisplayBackendOpenGL::checkProgramStatus(uint32_t program) int32_t GPUDisplayBackendOpenGL::InitBackendA() { + for (GLenum err = glGetError(); err != GL_NO_ERROR; err = glGetError()) { // Left by the frontend (freeglut with a core profile context leaves GL_INVALID_ENUM), CHKERR below would report it for the wrong call + if (mDisplay->GetProcessingSettings().debugLevel >= 2) { + GPUInfo("Ignoring OpenGL error %d from before the backend initialization", (int32_t)err); + } + } if (mDisplay->GetProcessingSettings().debugLevel >= 2) { auto renderer = glGetString(GL_RENDERER); GPUInfo("Renderer: %s", (const char*)renderer); @@ -325,9 +296,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[0])); CHKERR(glGenBuffers(1, &mIndirectId)); CHKERR(glBindBuffer(GL_DRAW_INDIRECT_BUFFER, mIndirectId)); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - CHKERR(glShadeModel(GL_SMOOTH)); // Enable Smooth Shading -#endif setDepthBuffer(); setQuality(); CHKERR(mVertexShader = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertex_vert_spv_start, _binary_opengl_vertex_vert_spv_len)); @@ -459,7 +427,6 @@ void GPUDisplayBackendOpenGL::clearScreen(bool alphaOnly) void GPUDisplayBackendOpenGL::loadDataToGPU(size_t totalVertizes) { - // TODO: Check if this can be parallelized if (mDisplay->useMultiVBO()) { for (uint32_t i = 0; i < GPUTPCGeometry::NSECTORS; i++) { CHKERR(glNamedBufferData(mVBOId[i], mDisplay->vertexBuffer()[i].size() * sizeof(mDisplay->vertexBuffer()[i][0]), mDisplay->vertexBuffer()[i].data(), GL_STATIC_DRAW)); @@ -494,77 +461,34 @@ bool GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& setFrameBuffer(0); } clearScreen(); - mUseSmoothPointShader = mDisplay->cfgL().smoothPoints && !mDisplay->cfgR().rendererCompatSmoothing; // The compat profile uses GL_POINT_SMOOTH / GL_LINE_SMOOTH - mUseSmoothLineShader = mDisplay->cfgL().smoothLines && !mDisplay->cfgR().rendererCompatSmoothing; -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mUseSmoothPointShader) { // Compatibility context: gl_PointCoord is only defined with point sprites - CHKERR(glEnable(GL_POINT_SPRITE)); - } else { - CHKERR(glDisable(GL_POINT_SPRITE)); - } - if (mDisplay->cfgL().smoothPoints && mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnable(GL_POINT_SMOOTH)); - } else { - CHKERR(glDisable(GL_POINT_SMOOTH)); - } - if (mDisplay->cfgL().smoothLines && mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnable(GL_LINE_SMOOTH)); - } else { - CHKERR(glDisable(GL_LINE_SMOOTH)); - } -#endif CHKERR(glEnable(GL_BLEND)); CHKERR(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); glViewport(0, 0, mRenderWidth, mRenderHeight); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glEnableClientState(GL_VERTEX_ARRAY)); - CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); - } else -#endif - { - CHKERR(glBindVertexArray(mVertexArray)); - CHKERR(glUseProgram(mShaderProgram)); - CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); - CHKERR(glEnableVertexAttribArray(0)); - } -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glMatrixMode(GL_PROJECTION)); - CHKERR(glLoadMatrixf(&proj.Elements[0][0])); - CHKERR(glMatrixMode(GL_MODELVIEW)); - CHKERR(glLoadMatrixf(&view.Elements[0][0])); - } else -#endif - { - const hmm_mat4 modelViewProj = proj * view; - CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - if (mUseSmoothPointShader) { - CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - } - if (mUseSmoothLineShader) { - CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); - } - if (mMagneticFieldVisualization) { - CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); - } + CHKERR(glBindVertexArray(mVertexArray)); + CHKERR(glUseProgram(mShaderProgram)); + CHKERR(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr)); + CHKERR(glEnableVertexAttribArray(0)); + + const hmm_mat4 modelViewProj = proj * view; + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + if (mDisplay->cfgL().smoothPoints) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothPoint, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + } + if (mDisplay->cfgL().smoothLines) { + CHKERR(glProgramUniformMatrix4fv(mShaderProgramSmoothLine, UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); + CHKERR(glProgramUniform2f(mShaderProgramSmoothLine, UNIFORM_LOCATION_VIEWPORT, mRenderWidth, mRenderHeight)); + } + if (mMagneticFieldVisualization) { + CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); } return true; } void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - CHKERR(glDisableClientState(GL_VERTEX_ARRAY)); - } else -#endif - { - CHKERR(glBindVertexArray(0)); - CHKERR(glUseProgram(0)); - } + CHKERR(glBindVertexArray(0)); + CHKERR(glUseProgram(0)); if (mOffscreenBufferMSAA.created) { uint32_t dstId = toMixBuffer ? mMixBuffer.fb_id : (mDownsampleFactor != 1 ? mOffscreenBuffer.fb_id : 0); @@ -617,15 +541,7 @@ void GPUDisplayBackendOpenGL::prepareText() setFrameBuffer(0); glViewport(0, 0, mScreenWidth, mScreenHeight); hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); - if (mDisplay->drawTextInCompatMode()) { -#ifndef GPUCA_DISPLAY_OPENGL_CORE - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&proj.Elements[0][0]); - glViewport(0, 0, mScreenWidth, mScreenHeight); -#endif - } else if (mFreetypeInitialized) { + if (mFreetypeInitialized) { CHKERR(glUseProgram(mShaderProgramText)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_text)); @@ -637,7 +553,7 @@ void GPUDisplayBackendOpenGL::prepareText() void GPUDisplayBackendOpenGL::finishText() { - if (!mDisplay->drawTextInCompatMode() && mFreetypeInitialized) { + if (mFreetypeInitialized) { CHKERR(glBindVertexArray(0)); CHKERR(glBindTexture(GL_TEXTURE_2D, 0)); CHKERR(glUseProgram(0)); @@ -667,55 +583,25 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) { hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1.f, 1.f); glDisable(GL_DEPTH_TEST); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(&proj.Elements[0][0]); - CHKERR(glEnable(GL_TEXTURE_2D)); - } else -#endif - { - CHKERR(glUseProgram(mShaderProgramTexture)); - CHKERR(glActiveTexture(GL_TEXTURE0)); - CHKERR(glBindVertexArray(VAO_texture)); - CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); - CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); - } + CHKERR(glUseProgram(mShaderProgramTexture)); + CHKERR(glActiveTexture(GL_TEXTURE0)); + CHKERR(glBindVertexArray(VAO_texture)); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); + CHKERR(glUniform1f(UNIFORM_LOCATION_COLOR, mixSlaveImage)); CHKERR(glBindTexture(GL_TEXTURE_2D, mMixBuffer.fbCol_id)); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (mDisplay->cfgR().rendererCompatSmoothing) { - glColor4f(1, 1, 1, mixSlaveImage); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); - glVertex3f(0, 0, 0); - glTexCoord2f(0, 1); - glVertex3f(0, mRenderHeight, 0); - glTexCoord2f(1, 1); - glVertex3f(mRenderWidth, mRenderHeight, 0); - glTexCoord2f(1, 0); - glVertex3f(mRenderWidth, 0, 0); - glEnd(); - glColor4f(1, 1, 1, 0); - CHKERR(glDisable(GL_TEXTURE_2D)); - } else -#endif - { - CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); - CHKERR(glBindVertexArray(0)); - CHKERR(glUseProgram(0)); - } + CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); + CHKERR(glBindVertexArray(0)); + CHKERR(glUseProgram(0)); CHKERR(glBindTexture(GL_TEXTURE_2D, 0)); setDepthBuffer(); } void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) { - const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mUseSmoothPointShader ? SMOOTH_POINT_EXTRA_SIZE : 0.f); - if (mUseSmoothPointShader) { + const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mDisplay->cfgL().smoothPoints ? SMOOTH_POINT_EXTRA_SIZE : 0.f); + if (mDisplay->cfgL().smoothPoints) { CHKERR(glProgramUniform1f(mShaderProgramSmoothPoint, UNIFORM_LOCATION_POINT_SIZE, size)); } CHKERR(glPointSize(size)); @@ -724,7 +610,7 @@ void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) { float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; - if (mUseSmoothLineShader) { + if (mDisplay->cfgL().smoothLines) { width += SMOOTH_LINE_EXTRA_WIDTH; CHKERR(glProgramUniform1f(mShaderProgramSmoothLine, UNIFORM_LOCATION_LINE_WIDTH, width)); width *= 1.4143f; // Wide lines are parallelograms in OpenGL, widen so that the perpendicular width is covered @@ -787,9 +673,9 @@ void GPUDisplayBackendOpenGL::initializeTextDrawing() CHKERR(glBindVertexArray(0)); } -void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float* color, float scale) +void GPUDisplayBackendOpenGL::GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) { - if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { + if (!mFreetypeInitialized) { return; } CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); @@ -797,9 +683,9 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float if (smoothFont()) { scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const unsigned char* c = (const unsigned char*)s; *c; c++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { + if (*c < 32 || *c >= SOLID_GLYPH) { // Printable ASCII only, SOLID_GLYPH is not a character + GPUError("Trying to draw unsupported symbol: %d", (int32_t)*c); continue; } FontSymbolOpenGL sym = mFontSymbols[*c]; @@ -815,11 +701,7 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float {xpos, ypos + h, 0.0f, 0.0f}, {xpos + w, ypos, 1.0f, 1.0f}, {xpos + w, ypos + h, 1.0f, 0.0f}}; - CHKERR(glBindTexture(GL_TEXTURE_2D, sym.texId)); - CHKERR(glBindBuffer(GL_ARRAY_BUFFER, VBO_text)); - CHKERR(glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); - CHKERR(glBindBuffer(GL_ARRAY_BUFFER, 0)); - CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); + drawTextVertices(vertices, sym.texId); } x += (sym.advance >> 6) * scale; // shift is in 1/64th of a pixel } @@ -828,6 +710,34 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float } } +void GPUDisplayBackendOpenGL::drawTextVertices(const float (&vertices)[6][4], uint32_t texId) +{ + CHKERR(glBindTexture(GL_TEXTURE_2D, texId)); + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, VBO_text)); + CHKERR(glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, 0)); + CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); +} + +void GPUDisplayBackendOpenGL::GLDrawQuad(const float (&corners)[4][2], const float* color) +{ + if (!mFreetypeInitialized) { + return; + } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); + static constexpr int32_t order[6] = {0, 1, 2, 0, 2, 3}; + float vertices[6][4]; + for (int32_t i = 0; i < 6; i++) { + vertices[i][0] = corners[order[i]][0]; + vertices[i][1] = corners[order[i]][1]; + vertices[i][2] = vertices[i][3] = 0.5f; // Center of the opaque glyph + } + drawTextVertices(vertices, mFontSymbols[SOLID_GLYPH].texId); + if (!mDisplay->useMultiVBO()) { + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[0])); // As in GLPrintText + } +} + void GPUDisplayBackendOpenGL::ClearOffscreenBuffers() { if (mMixBuffer.created) { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index f30a22846a1ac..e7bc6480878f5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -34,9 +34,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend GPUDisplayBackendOpenGL(); ~GPUDisplayBackendOpenGL() override = default; int32_t ExtInit() override; - bool CoreProfile() override; bool canLimitToVSync() const override; // Buffer swap blocks with VSync, if the frontend can set the swap interval - uint32_t DepthBits() override; protected: void createFB(GLfb& fb, bool tex, bool withDepth, bool msaa, uint32_t width, uint32_t height); @@ -81,7 +79,9 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; void clearFontSymbols() override; - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; + void GLDrawQuad(const float (&corners)[4][2], const float* color) override; + void drawTextVertices(const float (&vertices)[6][4], uint32_t texId); // One quad with the text shader, after GLPrintText / GLDrawQuad set the color struct FontSymbolOpenGL : public FontSymbol { uint32_t texId; @@ -101,10 +101,8 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mFragmentShaderDownsample; uint32_t mGeometryShader; uint32_t mShaderProgram; - uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints with the shader path (not rendererCompatSmoothing / GL_POINT_SMOOTH) - bool mUseSmoothPointShader = false; - uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines with the shader path (not rendererCompatSmoothing / GL_LINE_SMOOTH) - bool mUseSmoothLineShader = false; + uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints + uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines uint32_t mShaderProgramText; uint32_t mShaderProgramTexture; uint32_t mShaderProgramDownsample; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index b0d63bfda17cf..cc73b350e7bc0 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -556,8 +556,6 @@ void GPUDisplayBackendVulkan::createDevice() if (checkDevice(mPhysicalDevice, reqDeviceExtensions, reqDeviceFeatures, true) <= 0) { throw std::runtime_error("Selected vulkan device is incompatible"); } - mSmoothLinesSupported = getFeatures(mPhysicalDevice, reqDeviceFeatures).get().smoothLines; - reqDeviceFeatures.get().smoothLines = mSmoothLinesSupported; std::vector deviceExtensions = reqDeviceExtensions; void* featureChain = &reqDeviceFeatures.get(); const std::vector availableDeviceExtensions = mPhysicalDevice.enumerateDeviceExtensionProperties(nullptr); @@ -578,7 +576,7 @@ void GPUDisplayBackendVulkan::createDevice() mStencilSupported = (bool)(depth64FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); bool mailboxSupported = std::find(mSwapChainDetails.presentModes.begin(), mSwapChainDetails.presentModes.end(), vk::PresentModeKHR::eMailbox) != mSwapChainDetails.presentModes.end(); if (mDisplay->GetProcessingSettings().debugLevel >= 2) { - GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d, FIFO latest ready present mode %d, smooth lines %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported, (int32_t)mFifoLatestReadySupported, (int32_t)mSmoothLinesSupported); + GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d, FIFO latest ready present mode %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported, (int32_t)mFifoLatestReadySupported); } vk::DeviceQueueCreateInfo queueCreateInfo{}; @@ -617,7 +615,7 @@ void GPUDisplayBackendVulkan::clearDevice() // ---------------------------- VULKAN COMMAND BUFFERS ---------------------------- -void GPUDisplayBackendVulkan::createCommandBuffers() +void GPUDisplayBackendVulkan::createCommandBuffersForImages() { vk::CommandBufferAllocateInfo allocInfo{}; allocInfo.commandPool = mCommandPool; @@ -632,7 +630,7 @@ void GPUDisplayBackendVulkan::createCommandBuffers() mCommandBuffersPresent = mDevice.allocateCommandBuffers(allocInfo); } -void GPUDisplayBackendVulkan::clearCommandBuffers() +void GPUDisplayBackendVulkan::clearCommandBuffersForImages() { mDevice.freeCommandBuffers(mCommandPool, mCommandBuffers.size(), mCommandBuffers.data()); mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersText.size(), mCommandBuffersText.data()); @@ -648,18 +646,22 @@ void GPUDisplayBackendVulkan::createSemaphores() { vk::SemaphoreCreateInfo semaphoreInfo{}; mImageAvailableSemaphore.resize(mFramesInFlight); - mPresentSemaphore.resize(mImageCount); for (uint32_t i = 0; i < mFramesInFlight; i++) { mImageAvailableSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); } - for (uint32_t i = 0; i < mImageCount; i++) { - mPresentSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - } vk::SemaphoreTypeCreateInfo timelineTypeInfo{vk::SemaphoreType::eTimeline, 0}; semaphoreInfo.pNext = &timelineTypeInfo; mTimelineSemaphore = mDevice.createSemaphore(semaphoreInfo, nullptr); mTimelineValue = 0; mFrameTimelineValue.assign(mFramesInFlight, 0); +} + +void GPUDisplayBackendVulkan::createSemaphoresForImages() +{ + mPresentSemaphore.resize(mImageCount); + for (uint32_t i = 0; i < mImageCount; i++) { + mPresentSemaphore[i] = mDevice.createSemaphore(vk::SemaphoreCreateInfo{}, nullptr); + } mImageTimelineValue.assign(mImageCount, 0); for (uint32_t i = 0; i < mImageCount; i++) { mPresentFences.emplace_back(mDevice.createFence(vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled}, nullptr)); @@ -679,8 +681,12 @@ void GPUDisplayBackendVulkan::waitPresentFences() void GPUDisplayBackendVulkan::clearSemaphores() { clearVector(mImageAvailableSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); mDevice.destroySemaphore(mTimelineSemaphore, nullptr); +} + +void GPUDisplayBackendVulkan::clearSemaphoresForImages() +{ + clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mPresentFences, [&](auto& x) { mDevice.destroyFence(x, nullptr); }); } @@ -688,13 +694,6 @@ void GPUDisplayBackendVulkan::clearSemaphores() void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() { - for (int32_t j = 0; j < 3; j++) { - mUniformBuffersMat[j].resize(mImageCount); - for (uint32_t i = 0; i < mImageCount; i++) { - mUniformBuffersMat[j][i] = createBuffer(sizeof(hmm_mat4), nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); - } - } - vk::DescriptorSetLayoutBinding uboLayoutBindingMat{}; uboLayoutBindingMat.binding = 0; uboLayoutBindingMat.descriptorType = vk::DescriptorType::eUniformBuffer; @@ -714,7 +713,16 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() mUniformDescriptor = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); layoutInfo.bindingCount = 2; mUniformDescriptorTexture = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); +} +void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffersForImages() +{ + for (int32_t j = 0; j < 3; j++) { + mUniformBuffersMat[j].resize(mImageCount); + for (uint32_t i = 0; i < mImageCount; i++) { + mUniformBuffersMat[j][i] = createBuffer(sizeof(hmm_mat4), nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); + } + } mUpdateUBOdrawMatrix = mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; } @@ -722,6 +730,10 @@ void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffers() { mDevice.destroyDescriptorSetLayout(mUniformDescriptor, nullptr); mDevice.destroyDescriptorSetLayout(mUniformDescriptorTexture, nullptr); +} + +void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffersForImages() +{ for (int32_t j = 0; j < 3; j++) { clearVector(mUniformBuffersMat[j], [&](auto& x) { clearBuffer(x); }); } @@ -801,8 +813,7 @@ void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain) { // Let the compositor scale the last image while resizing, until the new swapchain is presented const vk::SurfacePresentModeKHR surfacePresentMode{mPresentMode}; const auto caps = mPhysicalDevice.getSurfaceCapabilities2KHR(vk::PhysicalDeviceSurfaceInfo2KHR{mSurface, &surfacePresentMode}).get(); - auto gravity = [](vk::PresentGravityFlagsKHR f) { return (f & vk::PresentGravityFlagBitsKHR::eCentered) ? vk::PresentGravityFlagBitsKHR::eCentered : (f & vk::PresentGravityFlagBitsKHR::eMin) ? vk::PresentGravityFlagBitsKHR::eMin - : vk::PresentGravityFlagsKHR(f & vk::PresentGravityFlagBitsKHR::eMax); }; + auto gravity = [](vk::PresentGravityFlagsKHR f) { return (f & vk::PresentGravityFlagBitsKHR::eCentered) ? vk::PresentGravityFlagBitsKHR::eCentered : ((f & vk::PresentGravityFlagBitsKHR::eMin) ? vk::PresentGravityFlagBitsKHR::eMin : vk::PresentGravityFlagsKHR(f & vk::PresentGravityFlagBitsKHR::eMax)); }; scalingInfo.scalingBehavior = (caps.supportedPresentScaling & vk::PresentScalingFlagBitsKHR::eAspectRatioStretch) ? vk::PresentScalingFlagBitsKHR::eAspectRatioStretch : vk::PresentScalingFlagsKHR(caps.supportedPresentScaling & vk::PresentScalingFlagBitsKHR::eStretch); scalingInfo.presentGravityX = gravity(caps.supportedPresentGravityX); scalingInfo.presentGravityY = gravity(caps.supportedPresentGravityY); @@ -818,22 +829,24 @@ void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain) } mSwapChainImages = mDevice.getSwapchainImagesKHR(mSwapChain); - uint32_t oldFramesInFlight = mFramesInFlight, oldImageCount = mImageCount; - mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; + uint32_t oldImageCount = mImageCount; mImageCount = mSwapChainImages.size(); if (mImageCount > sizeof(mUpdateUBOdrawMatrix) * 8) { throw std::runtime_error("Too many swapchain images"); } - if (mFramesInFlight != oldFramesInFlight || mImageCount != oldImageCount || !mCommandInfrastructureCreated) { + if (mImageCount != oldImageCount || !mCommandInfrastructureCreated) { if (mCommandInfrastructureCreated) { - clearUniformLayoutsAndBuffers(); // TODO: Check what must actually be cleaned up if frames in flight changes - clearCommandBuffers(); - clearSemaphores(); + clearUniformLayoutsAndBuffersForImages(); + clearCommandBuffersForImages(); + clearSemaphoresForImages(); + } else { + createUniformLayoutsAndBuffers(); + createSemaphores(); } - createUniformLayoutsAndBuffers(); - createCommandBuffers(); - createSemaphores(); + createUniformLayoutsAndBuffersForImages(); + createCommandBuffersForImages(); + createSemaphoresForImages(); mCommandInfrastructureCreated = true; } @@ -861,24 +874,21 @@ void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixi needRecordCommandBuffers(); const bool needUpdateSwapChain = mMustUpdateSwapChain; const bool needUpdateOffscreenBuffers = needUpdateSwapChain || mDownsampleFactor != getDownsampleFactor(forScreenshot) || mMSAASampleCount != getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)) || mZActive != (mZSupported && mDisplay->cfgL().depthBuffer) || mMixingSupported != forMixing; - const bool smoothLinesChanged = mSmoothLines != useNativeSmoothLines(); - if (!needUpdateOffscreenBuffers && !smoothLinesChanged) { + if (!needUpdateOffscreenBuffers) { return; } mDevice.waitIdle(); const vk::Format oldFormat = mSurfaceFormat.format; const vk::SampleCountFlagBits oldMSAASampleCount = mMSAASampleCount; const bool oldZActive = mZActive; - if (needUpdateOffscreenBuffers) { - clearOffscreenBuffers(); - if (needUpdateSwapChain) { - clearSwapChain(true); - createSwapChain(mSwapChain); - } - createOffscreenBuffers(forScreenshot, forMixing); - mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed + clearOffscreenBuffers(); + if (needUpdateSwapChain) { + clearSwapChain(true); + createSwapChain(mSwapChain); } - if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive || smoothLinesChanged) { // Viewport / scissor are dynamic + createOffscreenBuffers(forScreenshot, forMixing); + mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed + if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive) { // Viewport / scissor are dynamic clearPipeline(); createPipeline(); } @@ -1096,15 +1106,10 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional - mSmoothLines = useNativeSmoothLines(); - vk::PipelineRasterizationLineStateCreateInfo smoothLineState{}; - smoothLineState.lineRasterizationMode = vk::LineRasterizationMode::eRectangularSmooth; // Coverage goes to alpha, blending is enabled - mPipelines.resize(9); // 0 points, 1 lines, 2 line strip (drawType index), 3 text, 4 texture, 5-7 smooth (shader-based) points, lines, line strip, 8 FSAA downsampling static constexpr vk::PrimitiveTopology types[3] = {vk::PrimitiveTopology::ePointList, vk::PrimitiveTopology::eLineList, vk::PrimitiveTopology::eLineStrip}; for (uint32_t i = 0; i < mPipelines.size(); i++) { if (i == 8) { // FSAA downsampling, full-screen triangle without vertex input (last pipeline, the changed states are not reset) - rasterizer.pNext = nullptr; rasterizer.cullMode = vk::CullModeFlagBits::eNone; vertexInputInfo.vertexBindingDescriptionCount = vertexInputInfo.vertexAttributeDescriptionCount = 0; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1117,7 +1122,6 @@ void GPUDisplayBackendVulkan::createPipeline() colorBlendAttachment.blendEnable = false; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; } else if (i == 4) { // Texture rendering - rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1130,7 +1134,6 @@ void GPUDisplayBackendVulkan::createPipeline() colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; } else if (i == 3) { // Text rendering - rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1147,7 +1150,6 @@ void GPUDisplayBackendVulkan::createPipeline() attributeDescriptions.format = vk::Format::eR32G32B32Sfloat; const uint32_t drawIdx = i >= 5 ? i - 5 : i; inputAssembly.topology = types[drawIdx]; - rasterizer.pNext = mSmoothLines && (i == 1 || i == 2) ? &smoothLineState : nullptr; vertShaderStageInfo.pNext = &mShaders.at(i < 5 ? (drawIdx == 0 ? "vertexPoint" : "vertex") : (drawIdx == 0 ? "vertexSmoothPoint" : "vertexSmoothLine")); fragShaderStageInfo.pNext = &mShaders.at(i < 5 ? "fragment" : (drawIdx == 0 ? "fragmentSmoothPoint" : "fragmentSmoothLine")); pipelineInfo.layout = mPipelineLayout; @@ -1397,8 +1399,10 @@ void GPUDisplayBackendVulkan::ExitBackendA() clearOffscreenBuffers(); clearSwapChain(); if (mCommandInfrastructureCreated) { + clearSemaphoresForImages(); clearSemaphores(); - clearCommandBuffers(); + clearCommandBuffersForImages(); + clearUniformLayoutsAndBuffersForImages(); clearUniformLayoutsAndBuffers(); } clearTextureSampler(); @@ -1453,7 +1457,7 @@ uint32_t GPUDisplayBackendVulkan::drawVertices(const vboList& v, const drawType return count; } - const int32_t pipeline = tt + ((tt == POINTS ? mDisplay->cfgL().smoothPoints : useSmoothLineShader()) ? 5 : 0); + const int32_t pipeline = tt + ((tt == POINTS ? mDisplay->cfgL().smoothPoints : mDisplay->cfgL().smoothLines) ? 5 : 0); if (mCurrentCommandBufferLastPipeline != pipeline) { mCurrentCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[pipeline]); mCurrentCommandBufferLastPipeline = pipeline; @@ -1496,7 +1500,7 @@ bool GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& if (mPresentModeForVSyncLimit != (fpsLimitMode() == FPS_LIMIT_VSYNC)) { // Present mode depends on the FPS limit mode mMustUpdateSwapChain = true; } - bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != useNativeSmoothLines(); + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot); if (!mustUpdateSwapChain) { retVal = getImage(); if (retVal == vk::Result::eErrorOutOfDateKHR) { @@ -1781,23 +1785,13 @@ void GPUDisplayBackendVulkan::pointSizeFactor(float factor) mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, sizeof(std::array), sizeof(size), &size); } -bool GPUDisplayBackendVulkan::useNativeSmoothLines() const -{ - return mDisplay->cfgL().smoothLines && mSmoothLinesSupported && mDisplay->cfgR().rendererCompatSmoothing; -} - -bool GPUDisplayBackendVulkan::useSmoothLineShader() const -{ - return mDisplay->cfgL().smoothLines && !useNativeSmoothLines(); -} - void GPUDisplayBackendVulkan::lineWidthFactor(float factor) { if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; - if (useSmoothLineShader()) { + if (mDisplay->cfgL().smoothLines) { const float expand = mStrictLines ? 1.f : 1.4143f; // Non-strict wide lines may be parallelograms, widen so that the perpendicular width is covered width = std::min(width + SMOOTH_LINE_EXTRA_WIDTH, mMaxLineWidth / expand); // The shader must see the width that is actually rasterized mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, 5 * sizeof(float), sizeof(width), &width); @@ -1893,9 +1887,9 @@ void GPUDisplayBackendVulkan::clearFontSymbols() mFontSymbols.clear(); } -void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float* color, float scale) +void GPUDisplayBackendVulkan::GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) { - if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { + if (!mFreetypeInitialized) { return; } @@ -1904,9 +1898,9 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } - for (const unsigned char* c = (const unsigned char*)s; *c; c++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { + if (*c < 32 || *c >= SOLID_GLYPH) { // Printable ASCII only, SOLID_GLYPH is not a character + GPUError("Trying to draw unsupported symbol: %d", (int32_t)*c); continue; } const FontSymbolVulkan& sym = mFontSymbols[*c]; @@ -1930,6 +1924,11 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float x += (sym.advance >> 6) * scale; // shift is in 1/64th of a pixel } + addTextDrawCommand(firstVertex, color); +} + +void GPUDisplayBackendVulkan::addTextDrawCommand(size_t firstVertex, const float* color) +{ size_t nVertices = mFontVertexBufferHost.size() / 4 - firstVertex; if (nVertices) { @@ -1942,6 +1941,21 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float } } +void GPUDisplayBackendVulkan::GLDrawQuad(const float (&corners)[4][2], const float* color) +{ + if (!mFreetypeInitialized) { + return; + } + const FontSymbolVulkan& sym = mFontSymbols[SOLID_GLYPH]; + const float u = (sym.x0 + sym.x1) * 0.5f, v = (sym.y0 + sym.y1) * 0.5f; // Center of the opaque glyph in the font atlas + const size_t firstVertex = mFontVertexBufferHost.size() / 4; + for (int32_t i : {0, 2, 1, 0, 3, 2}) { // Winding as the glyph quads of GLPrintText, the text pipeline culls back faces + mFontVertexBufferHost.insert(mFontVertexBufferHost.end(), {corners[i][0], mScreenHeight - 1 - corners[i][1], u, v}); + } + mHasDrawnText = true; + addTextDrawCommand(firstVertex, color); +} + void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageView view, vk::ImageLayout layout, std::vector& pixels) { const vk::Format format = mSurfaceFormat.format; @@ -2024,11 +2038,6 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageView v } } -uint32_t GPUDisplayBackendVulkan::DepthBits() -{ - return 32; -} - bool GPUDisplayBackendVulkan::backendNeedRedraw() { return !mCommandBufferUpToDate[mCurrentImageIndex]; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 4a621edc096d0..1b24f2199e3a6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -32,8 +32,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend GPUDisplayBackendVulkan(); ~GPUDisplayBackendVulkan() override; - uint32_t DepthBits() override; - protected: struct SwapChainSupportDetails { vk::SurfaceCapabilitiesKHR capabilities; @@ -81,8 +79,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void cmdPushDescriptors(vk::CommandBuffer commandBuffer, vk::PipelineLayout layout, const VulkanBuffer& ubo, vk::ImageView textureView = {}); // Push descriptors: UBO at binding 0, optional texture at binding 2 void pointSizeFactor(float factor) override; void lineWidthFactor(float factor) override; - bool useNativeSmoothLines() const; - bool useSmoothLineShader() const; bool canLimitToVSync() const override { return true; } // Present wait void waitPresentFences(); void resizeScene(uint32_t width, uint32_t height) override; @@ -116,17 +112,21 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void createDevice(); void loadShaders(); void createTextureSampler(); - void createCommandBuffers(); + void createCommandBuffersForImages(); void createSemaphores(); + void createSemaphoresForImages(); void createUniformLayoutsAndBuffers(); + void createUniformLayoutsAndBuffersForImages(); void createSwapChain(vk::SwapchainKHR oldSwapChain = {}); void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); void createPipeline(); void clearDevice(); void clearTextureSampler(); - void clearCommandBuffers(); + void clearCommandBuffersForImages(); void clearSemaphores(); + void clearSemaphoresForImages(); void clearUniformLayoutsAndBuffers(); + void clearUniformLayoutsAndBuffersForImages(); void clearOffscreenBuffers(); void clearSwapChain(bool keepSwapChain = false); void clearPipeline(); @@ -136,7 +136,9 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; void clearFontSymbols() override; - void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; + void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; + void GLDrawQuad(const float (&corners)[4][2], const float* color) override; + void addTextDrawCommand(size_t firstVertex, const float* color); // Draw the text vertices from firstVertex on with this color uint32_t mGraphicsFamily; SwapChainSupportDetails mSwapChainDetails; @@ -218,12 +220,10 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mStencilSupported = false; float mMaxLineWidth = 1.f, mMaxPointSize = 1.f; bool mStrictLines = false; // Wide lines are rectangles, not parallelograms - bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines bool mFifoLatestReadySupported = false; // Optional present mode FIFO_LATEST_READY (VK_KHR / VK_EXT_present_mode_fifo_latest_ready) vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16; bool mZActive = false; - bool mSmoothLines = false; bool mPresentModeErrorShown = false; bool mPresentModeForVSyncLimit = false; // Swapchain created with FIFO for the refresh rate FPS limit std::vector mPresentFences; // Per swapchain image, signaled when its last present is done (swapchain_maintenance1) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx new file mode 100644 index 0000000000000..c925bb84c5495 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx @@ -0,0 +1,351 @@ +// 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 +#include +#include + +using namespace o2::gpu; + +namespace +{ +struct consoleArgumentError : public std::runtime_error { // Wrong arguments for an existing command, what(): the error, usage: the argument types + consoleArgumentError(const std::string& cmd, const std::string& error, const std::string& types) : std::runtime_error(error), command(cmd), usage("[") + { + static constexpr std::pair names[] = {{'s', "string"}, {'i', "int"}, {'d', "double"}, {'a', "any"}, {'+', "[optional]"}}; + for (char t : types) { + if (const auto* n = std::find_if(std::begin(names), std::end(names), [t](const auto& entry) { return entry.first == t; }); n != std::end(names)) { + usage += (usage.size() > 1 ? " " : "") + std::string(n->second); + } + } + usage += "]"; + } + std::string command, usage; +}; +} // namespace + +GPUDisplayConsole::GPUDisplayConsole(const std::string& title) : mConsoleHistory{title} {} + +void GPUDisplayConsole::scroll(int32_t lines) +{ + const int64_t maxScroll = std::max(0, (int64_t)mConsoleHistory.size() - mVisibleLines); + mScroll = std::clamp((int64_t)mScroll + lines, 0, maxScroll); +} + +void GPUDisplayConsole::handleConsoleKey(uint8_t key, bool shift) +{ + if (key == KEY_PAGEUP || key == KEY_PAGEDOWN || (shift && (key == KEY_UP || key == KEY_DOWN))) { // Half a page / one line + const int32_t lines = key == KEY_PAGEUP || key == KEY_PAGEDOWN ? std::max(1, mVisibleLines / 2) : 1; + scroll(key == KEY_PAGEUP || key == KEY_UP ? lines : -lines); + return; + } + 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 = shownText().size(); // At the end, as when editing the entry starts + 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 = mScroll = 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, ...) +{ + va_list args, argsCopy; + va_start(args, format); + va_copy(argsCopy, args); + std::string buffer(std::max(0, vsnprintf(nullptr, 0, format, args)), '\0'); + va_end(args); + vsnprintf(buffer.data(), buffer.size() + 1, format, argsCopy); + va_end(argsCopy); + consoleAppend(" " + buffer); // Indented: output, not a command, skipped by up / down + if (mPrintToTerminal) { + GPUInfo("%s", buffer.c_str()); + } +} + +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; // Same entry, the end if it was removed + mConsoleTextPos = std::min(mConsoleTextPos, shownText().size()); + mScroll = mScroll + 1 >= size ? 0 : mScroll; // Counted from the end: same lines, the end if the lowest shown one was removed +} + +// 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 other decimal numbers (also with exponent), a string otherwise. Throws std::runtime_error for an unterminated quote / trailing backslash. + +std::tuple> GPUDisplayConsole::parseConsoleCommand(std::string_view cmd) const +{ + std::vector> tokens; // Text, plain text (quoted / escaped) + std::string token; + bool inToken = false, plainText = false, escape = false; + char quote = 0; + auto finishToken = [&]() { + if (inToken) { + tokens.emplace_back(std::move(token), plainText); + token.clear(); + inToken = plainText = false; + } + }; + for (char c : cmd) { + if (escape) { + token += c; + escape = false; + } else if (quote) { + if (c == quote) { + quote = 0; + } else { + token += c; + } + } else if (c == '\\') { + escape = inToken = plainText = true; + } else if (c == '"' || c == '\'') { + quote = c; + inToken = plainText = true; + } else if (c == ' ') { + finishToken(); + } else { + token += c; + inToken = true; + } + } + if (quote || escape) { + throw std::runtime_error(quote ? "unterminated quote" : "backslash at the end"); + } + finishToken(); + if (tokens.empty()) { + throw std::runtime_error("empty command"); + } + + const std::string& name = tokens[0].first; + const auto it = mConsoleCommands.find(name); + if (it == mConsoleCommands.end()) { + throw std::runtime_error("unknown command " + name); + } + const std::string& types = it->second.args; + const bool more = types.size() && types.back() == '+'; + const size_t nFixed = types.size() - more, nArgs = tokens.size() - 1; + if (nArgs < nFixed || (!more && nArgs > nFixed)) { + throw consoleArgumentError(name, std::to_string(nFixed) + (more ? " or more" : "") + " arguments expected, got " + std::to_string(nArgs), types); + } + auto autoType = [](const std::string& text, bool plain) -> consoleArg { // int64_t for [+-]digits, double for other decimal numbers (also with exponent, as saveSettings writes them), string otherwise or if plain text + const char *begin = text.c_str() + (text[0] == '+' || text[0] == '-'), *end = text.c_str() + text.size(); + const bool number = !plain && begin < end && ((*begin >= '0' && *begin <= '9') || *begin == '.') && std::all_of(begin, end, [](char c) { return (c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-'; }); + if (!number) { + return text; + } + int64_t intValue = 0; + double doubleValue = 0.; + if (const auto rInt = std::from_chars(begin, end, intValue); rInt.ec == std::errc() && rInt.ptr == end) { + return text[0] == '-' ? -intValue : intValue; + } else if (const auto rDouble = std::from_chars(begin, end, doubleValue); rDouble.ec == std::errc() && rDouble.ptr == end) { + return text[0] == '-' ? -doubleValue : doubleValue; // 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 consoleArgumentError(name, "argument " + std::to_string(i + 1) + " (" + text + ") must be an integer", types); + } else if (type == 'd') { + if (std::holds_alternative(value)) { + value = (double)std::get(value); + } else if (!std::holds_alternative(value)) { + throw consoleArgumentError(name, "argument " + std::to_string(i + 1) + " (" + text + ") must be a number", types); + } + } else if (type != 's' && type != 'i' && type != 'a' && type != '+') { + throw std::runtime_error(name + ": invalid argument types " + types); + } + args.emplace_back(std::move(value)); + } + return {it->second, std::move(args)}; +} + +void GPUDisplayConsole::handleConsoleCommand(std::string_view cmd) +{ + if (cmd.find_first_not_of(' ') == std::string_view::npos) { // Empty line + return; + } + const GPUDisplayConsoleCommand* command = nullptr; + std::vector args; + try { + auto parsed = parseConsoleCommand(cmd); + command = &std::get<0>(parsed); + args = std::move(std::get<1>(parsed)); + } catch (const consoleArgumentError& e) { + consolePrint("Invalid arguments for %s: %s %s", e.command.c_str(), e.what(), e.usage.c_str()); + return; + } catch (const std::runtime_error& e) { + consolePrint("Invalid command: %s", e.what()); + return; + } + try { // A failing command must not end the display + command->func(args); + } catch (const std::exception& e) { + consolePrint("Error in command %.*s: %s", (int32_t)cmd.size(), cmd.data(), e.what()); + } +} + +void GPUDisplayConsole::runScript(std::string_view script) +{ + if (mScriptDepth >= 16) { + consolePrint("Scripts nested too deeply"); + return; + } + std::vector lines; + for (size_t pos = 0; pos < script.size();) { + const size_t end = std::min(script.find_first_of("\r\n", pos), script.size()); + if (end > pos) { // Empty lines (consecutive \r / \n) are ignored + lines.push_back({std::string(script.substr(pos, end - pos)), mScriptDepth + 1}); + } + pos = end + 1; + } + mScriptQueue.insert(mScriptQueue.begin(), lines.begin(), lines.end()); // Before the remaining lines of a script running this one + if (mScriptDepth == 0) { // From a script, its processScripts loop continues with these lines + processScripts(false); + } +} + +void GPUDisplayConsole::processScripts(bool newFrame) +{ + if (newFrame) { + mWaitFrames -= mWaitFrames > 0; + if (mBenchmarkLeft && --mBenchmarkLeft == 0) { + const double time = std::chrono::duration(std::chrono::steady_clock::now() - mBenchmarkStart).count(); + consolePrint("benchmark: %u frames in %.3f s, %.2f fps, %.3f ms per frame", mBenchmarkFrames, time, mBenchmarkFrames / time, time * 1000. / mBenchmarkFrames); + } + } + if (mWaitCondition && mWaitCondition()) { + mWaitCondition = nullptr; + } + const uint32_t outerDepth = mScriptDepth; + for (uint32_t n = 0; n < 100000 && mScriptQueue.size() && !mWaitFrames && !mBenchmarkLeft && !mWaitCondition && std::chrono::steady_clock::now() >= mWaitUntil; n++) { // Limit per call: a script must not block the display + ScriptLine line = std::move(mScriptQueue.front()); + mScriptQueue.pop_front(); + mScriptDepth = line.depth; + handleConsoleCommand(line.text); + } + mScriptDepth = outerDepth; +} + +void GPUDisplayConsole::startBenchmark(uint32_t n) +{ + mBenchmarkFrames = mBenchmarkLeft = n; + mBenchmarkStart = std::chrono::steady_clock::now(); +} + +void GPUDisplayConsole::runScriptFile(const std::string& filename) +{ + std::ifstream file(filename, std::ios::binary); + if (!file) { + consolePrint("Cannot open script file %s", filename.c_str()); + return; + } + std::stringstream content; + content << file.rdbuf(); + runScript(content.str()); +} + +void GPUDisplayConsole::printCommands(const std::function& print) const +{ + std::vector*> commands; + for (const auto& entry : mConsoleCommands) { + commands.emplace_back(&entry); + } + std::sort(commands.begin(), commands.end(), [](const auto* a, const auto* b) { return a->first < b->first; }); + print("Console commands (argument types: s string, i integer, d number, a any, + further arguments):"); + for (const auto* c : commands) { + print(" " + c->first + (c->second.args.empty() ? "" : " ") + c->second.args + ": " + c->second.description); + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h new file mode 100644 index 0000000000000..d696e30bd4b91 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h @@ -0,0 +1,86 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayConsole.h +/// \author David Rohr + +#ifndef GPUDISPLAYCONSOLE_H +#define GPUDISPLAYCONSOLE_H + +#include "GPUDisplayFrontendKeyVars.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::gpu +{ +class GPUDisplayConsole : public GPUDisplayFrontendKeyVars // Text console of the event display (^ key): typed text, history with the output, commands (filled by GPUDisplayFrontend::buildConsoleCommands) +{ + public: + using consoleArg = std::variant; // Argument of a console command + struct GPUDisplayConsoleCommand { + std::function)> func; + std::string args; // One character per argument: s string, i int64_t, d double (also from an integer), a any (type from the text), + 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, bool shift); // Key typed while the console is shown + void scroll(int32_t lines); // Scrolls the history up (> 0) or down, limited to its lines + void handleConsoleCommand(std::string_view cmd); // Runs the command named by the first token, error if unknown + void runScript(std::string_view script); // Queues the lines (separated by \n / \r) as console commands before the remaining script lines, runs them up to a wait + void runScriptFile(const std::string& filename); // runScript with the content of the file + void processScripts(bool newFrame); // Runs queued script lines until a wait; newFrame: called once per frame, counts the frames of wait / benchmark + void waitFrames(uint32_t n) { mWaitFrames = n; } // The following script lines run after n frames + void waitSeconds(double t) { mWaitUntil = std::chrono::steady_clock::now() + std::chrono::duration_cast(std::chrono::duration(t)); } + void waitFor(std::function condition) { mWaitCondition = std::move(condition); } // The following script lines run once the condition is true + void startBenchmark(uint32_t n); // Measures the next n frames, the following script lines run afterwards + void printCommands(const std::function& print) const; // List of the commands with argument types and description, one line per call + std::tuple> parseConsoleCommand(std::string_view cmd) const; // Command and its arguments with the types it requests, throws std::runtime_error for syntax errors, unknown commands, wrong arguments + void consolePrint(const char* format, ...); // printf-like output to the console, indented by 2 spaces in mConsoleHistory + void consoleInfo(const char* format, const char* text); // Output of SetInfo, updates the last line instead for the same message within 2 s + void consoleAppend(std::string line); // Every other new line of the console + void consoleLimitHistory(uint32_t historySize); // Once per frame: keeps historySize lines when the history reaches twice that + const std::string& shownText() const { return mConsoleTextCurrent ? mConsoleHistory[mConsoleTextCurrent] : mConsoleText; } + + std::unordered_map mConsoleCommands; // Console commands by name, filled by GPUDisplayFrontend::buildConsoleCommands + struct ScriptLine { + std::string text; + uint32_t depth; // Nesting of runScript + }; + std::deque mScriptQueue; // Script lines not yet run + uint32_t mScriptDepth = 0; // Nesting of the script line running now + uint32_t mWaitFrames = 0; + std::chrono::steady_clock::time_point mWaitUntil; + std::function mWaitCondition; + uint32_t mBenchmarkFrames = 0, mBenchmarkLeft = 0; + std::chrono::steady_clock::time_point mBenchmarkStart; + bool mPrintToTerminal = false; // consolePrint also with GPUInfo (consolePrintToTerminal) + 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 mScroll = 0; // History lines scrolled up (the lowest ones not shown), reset by enter + uint32_t mVisibleLines = 1; // History lines fitting into the console, set when it is drawn + uint32_t mConsoleTextPos = 0; // Cursor position in the shown text (left / right), the character there is the current one +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index b48d7f6c59d5c..316bc42af5d98 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -13,13 +13,14 @@ /// \author David Rohr #include "GPUDisplayFrontend.h" +#include "GPUDisplayConsole.h" #include "GPUDisplay.h" #ifdef _WIN32 #include "GPUDisplayFrontendWindows.h" -#else -#include #endif +#include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include "GPUDisplayFrontendX11.h" @@ -44,7 +45,6 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include #include #include -#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -60,48 +60,69 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; -GPUDisplayFrontend::GPUDisplayFrontend() = default; -GPUDisplayFrontend::~GPUDisplayFrontend() = default; +GPUDisplayFrontend::GPUDisplayFrontend() : mConsole(std::make_unique(std::string(DISPLAY_WINDOW_NAME) + " Console")) {} +GPUDisplayFrontend::~GPUDisplayFrontend() +{ + if (mFrontendThread.joinable()) { // Started but not stopped, as the detached thread before + mFrontendThread.detach(); + } +} -void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) +void GPUDisplayFrontend::FrontendThreadWrapper() { - GPUDisplayFrontend* me = reinterpret_cast(ptr); int32_t retVal; try { - retVal = me->FrontendMain(); + retVal = FrontendMain(); } catch (const std::exception& e) { GPUError("Error in display frontend: %s", e.what()); retVal = -1; } - if (retVal && me->mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever - me->InitDisplay(true); + std::unique_lock lock(mExitStateMutex); + mDisplayRunning = false; // Also after an error / exception in the main loop, StopDisplay waits for it + mDisplayStoppedCV.notify_all(); + if (retVal) { + if (mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever + lock.unlock(); + InitDisplay(true); + } else { + mDisplay->mInitResult = -1; // Error after the initialization: StopDisplay does not reset the state for a new start + } } - return ((void*)(size_t)retVal); } void GPUDisplayFrontend::waitForFPSLimit() { - mNeedUpdate = 0; const auto now = std::chrono::steady_clock::now(); const auto& cfg = mDisplay->cfgR(); if (mBackend->fpsLimitMode() != GPUDisplayBackend::FPS_LIMIT_RATE || cfg.fpsRate <= 0) { mNextFrameTime = now; return; } - const auto period = std::chrono::duration_cast(std::chrono::duration(1. / cfg.fpsRate)); // TODO: wake up on HasExternalGUISignals + const auto period = std::chrono::duration_cast(std::chrono::duration(1. / cfg.fpsRate)); mNextFrameTime += period; if (mNextFrameTime < now - period) { // More than one frame behind (slow rendering, first frame), do not try to catch up mNextFrameTime = now; } - std::this_thread::sleep_until(mNextFrameTime); + std::unique_lock lock(mConfigUpdateMutex); + mConfigUpdateCV.wait_until(lock, mNextFrameTime, [this] { return mConfigUpdatePending; }); +} + +void GPUDisplayFrontend::signalConfigUpdate() +{ + { + std::lock_guard lock(mConfigUpdateMutex); + mConfigUpdatePending = true; + } + mConfigUpdateCV.notify_one(); } void GPUDisplayFrontend::HandleExternalGUISignals() { - const GPUSettingsDisplayHeavy oldCfgH = mDisplay->mCfgH; - const GPUSettingsDisplayLight oldCfgL = mDisplay->mCfgL; - const GPUSettingsDisplayRenderer oldCfgR = mDisplay->mCfgR; - bool changes = HandleExternalGUIKeys(); + { + std::lock_guard lock(mConfigUpdateMutex); + mConfigUpdatePending = false; // Before fetching the updates, so that updates sent in the meantime signal again + } + HandleExternalGUIKeys(); const bool gui = isGUIRunning(); std::optional update; @@ -114,19 +135,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->mCfgO.guiScale = *update->guiScale; + } + if (update->guiDarkMode) { + mDisplay->mCfgO.guiDarkMode = *update->guiDarkMode; } - changes = true; } - if (changes) { - mDisplay->checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } if (gui) { + GPUDisplayGUIChannel::Settings cfg{mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR, mDisplay->mCfgO, 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->mCfgO.printInfoTextGUI ? mDisplay->mGUIInfoStatus : ""; + mGUIChannel->state.infoText = mDisplay->mCfgO.printInfoTextGUI ? mDisplay->mGUIInfoText : ""; + mGUIChannel->state.infoTextCounter = mDisplay->mGUIInfoTextCounter; + if (mDisplay->mUpdateGUIOptions) { + mGUIChannel->state.optionsRevision++; + mDisplay->mUpdateGUIOptions = false; + } if (update) { mGUIChannel->state.appliedUpdate = update->id; } @@ -134,31 +163,49 @@ void GPUDisplayFrontend::HandleExternalGUISignals() mGUIChannel->displayProcessingCurrentUpdate = false; } mGUIChannel->stateUpdated.notify_all(); + std::unique_lock lock(mGUIChannel->mutex); + if (mGUIChannel->optionsReadRevision != mGUIChannel->state.optionsRevision) { // The GUI calls the dynamic option lambdas (reading display data) only while we wait here, short timeout, retried in the next frame + mGUIChannel->displayWaitingForOptions = true; + mGUIChannel->optionsRead.wait_for(lock, std::chrono::milliseconds(20), [this] { return mGUIChannel->optionsReadRevision == mGUIChannel->state.optionsRevision || mGUIChannel->stop; }); + mGUIChannel->displayWaitingForOptions = false; + } } } -bool GPUDisplayFrontend::HasExternalGUISignals() const +void GPUDisplayFrontend::sendExternalGUIKey(int32_t v, uint32_t translationMode, uint64_t extendedKey) { - return mExternalGUIKeys.size(); -} - -bool GPUDisplayFrontend::HandleExternalGUIKeys() -{ - std::lock_guard lock(mExternalGUIKeyMutex); - for (auto key : mExternalGUIKeys) { - mDisplay->HandleExternalGUIKey(key); + if (translationMode && (v = translateExternalKey(translationMode, extendedKey)) < 0) { + return; + } + { + std::lock_guard lock(mExternalGUIKeyMutex); + mExternalGUIKeys.emplace_back(v); } - return !std::exchange(mExternalGUIKeys, {}).empty(); + signalConfigUpdate(); } -void GPUDisplayFrontend::sendExternalGUIKey(int32_t v) -{ - std::lock_guard lock(mExternalGUIKeyMutex); - mExternalGUIKeys.emplace_back(v); -} +bool GPUDisplayFrontend::consoleShown() const { return mDisplay->mCfgC.showConsole; } +bool GPUDisplayFrontend::keyMapDown(uint32_t id) const { return mKeyDownMap[id] && mKeys[mKeyDownMap[id]]; } // Not mKeys[0] for unbound mappings: frontends may set it for unknown keys + +int32_t GPUDisplayFrontend::getDisplayControl() const { return mDisplay->mCfgC.displayControl.load(); } +void GPUDisplayFrontend::setDisplayControl(int32_t v) { mDisplay->mCfgC.displayControl.store(v); } void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } -int32_t GPUDisplayFrontend::DrawGLScene() { return mDisplay->DrawGLScene(); } +int32_t GPUDisplayFrontend::HandleUpdatesAndDrawGLScene(bool suspended) +{ + HandleExternalGUISignals(); + mDisplay->checkAndApplyConfigChanges(); + const bool startupScript = !mStartupScriptDone && !mDisplay->cfg().runScript.empty(); + const int32_t retVal = mDisplay->DrawGLScene(suspended || startupScript); // Before the startup script, the first frame only loads the event, it is not rendered + mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + if (startupScript) { + mStartupScriptDone = true; + mConsole->runScriptFile(mDisplay->cfg().runScript); // Settings it changes are applied with the next frame + } else { + mConsole->processScripts(true); // Waiting script lines, wait / benchmark + } + return retVal; +} void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) { @@ -189,6 +236,7 @@ int32_t GPUDisplayFrontend::startGUI() #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT if (!mGUI) { mGUIChannel = std::make_unique(); + mGUIChannel->updatePosted = [this]() { signalConfigUpdate(); }; mGUI = std::make_unique(*mGUIChannel); } if (!mGUI->isRunning()) { @@ -196,10 +244,14 @@ int32_t GPUDisplayFrontend::startGUI() std::lock_guard lock(mGUIChannel->mutex); mGUIChannel->update.reset(); mGUIChannel->state = {}; - mGUIChannel->guiSleeping = mGUIChannel->displayProcessingCurrentUpdate = mGUIChannel->stop = false; - mGUIChannel->replyTimeout = std::chrono::milliseconds(mDisplay->cfg().guiReplyTimeout); - mGUIChannel->maxUpdatesAhead = std::max(mDisplay->cfg().guiMaxUpdatesAhead, 1); // 0 would block all updates + mGUIChannel->guiSleeping = mGUIChannel->displayProcessingCurrentUpdate = mGUIChannel->stop = mGUIChannel->displayWaitingForOptions = false; + mGUIChannel->optionsReadRevision = 0; + mDisplay->mUpdateGUIOptions = true; + mGUIChannel->replyTimeout = std::chrono::milliseconds(mDisplay->cfg().internal.guiReplyTimeout); + mGUIChannel->maxUpdatesAhead = std::max(mDisplay->cfg().internal.guiMaxUpdatesAhead, 1); // 0 would block all updates + mGUIChannel->infoTextTime = std::chrono::milliseconds(std::lround((mDisplay->cfg().internal.setInfoTime + mDisplay->cfg().internal.setInfoTransition) * 1000.f)); mGUIChannel->windowName = DISPLAY_WINDOW_NAME; + setGUILayout(*mGUIChannel); } mGUI->start(); } else { @@ -264,23 +316,36 @@ GPUDisplayBackend* GPUDisplayFrontend::backend() void GPUDisplayFrontend::setWindowState(bool fullScreen, bool maximized) { - mDisplay->mCfgR.fullScreen = fullScreen; - mDisplay->mCfgR.maximized = maximized; + mDisplay->mCfgR.fullScreen = mWindowFullScreen = fullScreen; + mDisplay->mCfgR.maximized = mWindowMaximized = maximized; +} + +void GPUDisplayFrontend::requestFullScreen(bool set) +{ + if (set != std::exchange(mWindowFullScreen, set)) { + SwitchFullscreen(set); + } +} + +void GPUDisplayFrontend::requestMaximized(bool set) +{ + if (set != std::exchange(mWindowMaximized, set)) { + ToggleMaximized(set); + } } void GPUDisplayFrontend::focusLost() { std::fill(std::begin(mKeys), std::end(mKeys), false); - std::fill(std::begin(mKeysShift), std::end(mKeysShift), false); mMouseDn = mMouseDnR = mMouseDnM = false; } void GPUDisplayFrontend::applyInitialWindowState() { if (mDisplay->cfgR().fullScreen) { - SwitchFullscreen(true); + requestFullScreen(true); } else if (mDisplay->cfgR().maximized) { - ToggleMaximized(true); + requestMaximized(true); } } @@ -296,34 +361,37 @@ int32_t& GPUDisplayFrontend::drawTextFontSize() int32_t GPUDisplayFrontend::StartDisplay() { -#ifndef _WIN32 - static pthread_t hThread; - if (pthread_create(&hThread, nullptr, FrontendThreadWrapper, this)) { - GPUError("Coult not Create frontend Thread..."); - return (1); - } -#else - HANDLE hThread; - if ((hThread = CreateThread(nullptr, nullptr, &OpenGLWrapper, this, nullptr, nullptr)) == nullptr) { - GPUError("Coult not Create GL Thread..."); + try { + mFrontendThread = std::thread(&GPUDisplayFrontend::FrontendThreadWrapper, this); + } catch (const std::system_error& e) { + GPUError("Could not create frontend thread: %s", e.what()); return (1); } -#endif - return (0); + std::unique_lock lock(mExitStateMutex); + mStartDisplayDone = true; + mInitCV.notify_all(); + mInitCV.wait(lock, [this] { return mDisplay->mInitResult != 0; }); // Both threads have finished their initialization + return (mDisplay->mInitResult != 1); } void GPUDisplayFrontend::StopDisplay() { { - std::lock_guard lock(mExitStateMutex); + std::unique_lock lock(mExitStateMutex); if (mDisplayRunning) { - mDisplayControl = 2; + mDisplay->mCfgC.displayControl.store(2); StopDisplayA(); mDisplay->UnblockDisplay(); } + mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); + } + if (mFrontendThread.joinable()) { // Also the wrapper has finished (it sets mInitResult = -1 for errors after the initialization), not holding mExitStateMutex, which the wrapper uses + mFrontendThread.join(); } - while (mDisplayRunning) { - std::this_thread::sleep_for(std::chrono::microseconds(10000)); // TODO: Signal by semaphore + std::lock_guard lock(mExitStateMutex); + if (mDisplay->mInitResult == 1) { // For a new StartDisplay, not after a failed initialization or an error (undefined state) + mStartDisplayDone = mStartupScriptDone = false; + mDisplay->mInitResult = 0; } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 1bfbc36883f41..1648003414916 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -17,23 +17,31 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" +#include "GPUDisplayFrontendKeyVars.h" +#include #include #include #include +#include +#include +#include +#include +#include #include namespace o2::gpu { +struct GPUDisplayGUIUpdate; class GPUReconstruction; class GPUDisplay; class GPUDisplayBackend; class GPUDisplayGUIWrapper; +class GPUDisplayConsole; struct GPUDisplayGUIChannel; -class GPUDisplayFrontend : public GPUDisplayFrontendInterface +class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplayFrontendKeyVars { friend class GPUDisplay; - friend class GPUDisplayGUI; // Key codes public: GPUDisplayFrontend(); @@ -53,13 +61,12 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface static constexpr int32_t GL_MIN_VERSION_MAJOR = 4; static constexpr int32_t GL_MIN_VERSION_MINOR = 6; - int32_t StartDisplay(); // Start the display. This function returns, and should spawn a thread that runs the display, and calls InitDisplay - void StopDisplay() override; // Stop the display. Display thread should call ExitDisplay. Functions waits for display thread to terminate - virtual void SwitchFullscreen(bool set) = 0; // Toggle full-screen mode - virtual void ToggleMaximized(bool set) = 0; // Maximize window - virtual void SetVSync(bool enable) = 0; // Enable / disable vsync - virtual bool canSetVSync() { return false; } // OpenGL: swap interval can be set - virtual void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) = 0; // Print text on the display (needs the backend to build the font) + int32_t StartDisplay(); // Start the display: spawns the frontend thread (FrontendThreadWrapper, calls InitDisplay), returns after both threads are initialized + void StopDisplay() override; // Stop the display. Display thread should call ExitDisplay. Functions waits for display thread to terminate + virtual void SwitchFullscreen(bool set) = 0; // Toggle full-screen mode + virtual void ToggleMaximized(bool set) = 0; // Maximize window + virtual void SetVSync(bool enable) = 0; // Enable / disable vsync + virtual bool canSetVSync() { return false; } // OpenGL: swap interval can be set GPUDisplayBackend* backend(); static GPUDisplayFrontend* getFrontend(const char* type); virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; } @@ -68,9 +75,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual int32_t getVulkanSurface(void* instance, void* surface) { return 1; } virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; }; - int32_t getDisplayControl() const override { return mDisplayControl; } - void setDisplayControl(int32_t v) override { mDisplayControl = v; } - void sendExternalGUIKey(int32_t v) override; + int32_t getDisplayControl() const override; + void setDisplayControl(int32_t v) override; + void sendExternalGUIKey(int32_t v, uint32_t translationMode = 0, uint64_t extendedKey = 0) override; frontendTypes frontendType() const { return mFrontendType; } const char* frontendName() const override { return mFrontendName; } @@ -82,62 +89,40 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface protected: virtual int32_t FrontendMain() = 0; virtual void StopDisplayA(); - static void* FrontendThreadWrapper(void*); - void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode) + void FrontendThreadWrapper(); // Runs FrontendMain in mFrontendThread + void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode), or until an external config update is signalled + void signalConfigUpdate(); // Signal a config update from an external source (console keys, Qt GUI), wakes up waitForFPSLimit static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing static constexpr int32_t MIN_WIDTH = 200, MIN_HEIGHT = 150; // Minimum window size static constexpr const char* DISPLAY_WINDOW_NAME = "GPU CA Standalone Event Display"; // Title of event display set by backend - // Constant key codes for special mKeys (to unify different treatment in X11 / Windows / GLUT / etc.) - static constexpr int32_t KEY_UP = 1; - static constexpr int32_t KEY_DOWN = 2; - static constexpr int32_t KEY_LEFT = 3; - static constexpr int32_t KEY_RIGHT = 4; - static constexpr int32_t KEY_PAGEUP = 5; - static constexpr int32_t KEY_PAGEDOWN = 6; - static constexpr int32_t KEY_SHIFT = 8; - static constexpr int32_t KEY_ALT = 9; - static constexpr int32_t KEY_RALT = 10; - static constexpr int32_t KEY_CTRL = 11; - static constexpr int32_t KEY_RCTRL = 12; - static constexpr int32_t KEY_ENTER = 13; // fixed at 13 - static constexpr int32_t KEY_F1 = 14; - static constexpr int32_t KEY_F2 = 15; - static constexpr int32_t KEY_F3 = 16; - static constexpr int32_t KEY_F4 = 17; - static constexpr int32_t KEY_F5 = 18; - static constexpr int32_t KEY_F6 = 19; - static constexpr int32_t KEY_F7 = 20; - static constexpr int32_t KEY_F8 = 21; - static constexpr int32_t KEY_F9 = 22; - static constexpr int32_t KEY_F10 = 23; - static constexpr int32_t KEY_F11 = 24; - static constexpr int32_t KEY_F12 = 25; - static constexpr int32_t KEY_INSERT = 26; - static constexpr int32_t KEY_ESCAPE = 27; // fixed at 27 - static constexpr int32_t KEY_HOME = 28; - static constexpr int32_t KEY_END = 29; - static constexpr int32_t KEY_SPACE = 32; // fixed at 32 - - // volatile variables to exchange control informations between display and backend - volatile int32_t mDisplayControl = 0; // Control for next event (=1) or quit (=2) - volatile int32_t mNeedUpdate = 0; // flag that backend shall update the GL window, and call DrawGLScene + static constexpr uint32_t N_SPECIAL_KEYS = 32; + static constexpr uint32_t SPECIAL_KEYS_SHIFTED = 255; + static constexpr uint32_t N_TOTAL_KEYS = 256 + N_SPECIAL_KEYS; + std::mutex mExternalGUIKeyMutex; std::vector mExternalGUIKeys; // Keys sent by external entity (usually console), may be ignored by backend. + std::mutex mConfigUpdateMutex; + std::condition_variable mConfigUpdateCV; // Notified by signalConfigUpdate + bool mConfigUpdatePending = false; // Protected by mConfigUpdateMutex, set by signalConfigUpdate, cleared by HandleExternalGUISignals std::mutex mExitStateMutex; + std::condition_variable mDisplayStoppedCV; // Notified with mExitStateMutex held when the frontend sets mDisplayRunning to false, StopDisplay waits for it + std::condition_variable mInitCV; // With mExitStateMutex: InitDisplay (frontend thread) and StartDisplay (main thread) wait for each other at their end + std::thread mFrontendThread; // Started by StartDisplay, joined by StopDisplay + bool mStartDisplayDone = false; // Protected by mExitStateMutex + bool mStartupScriptDone = false; // GPUSettingsDisplay::runScript runs after the first (suspended) frame + std::array mKeyDownMap = {}; // Keyboard / Mouse actions - bool mMouseDn = false; // Mouse button down - bool mMouseDnR = false; // Right mouse button down - bool mMouseDnM = false; // Middle mouse button down (not used yet) - float mMouseDnX, mMouseDnY; // X/Y position where mouse button was pressed - float mMouseMvX, mMouseMvY; // Current mouse pointer position - int32_t mMouseWheel = 0; // Incremental value of mouse wheel, ca +/- 100 per wheel tick - bool mKeys[256] = {false}; // Array of mKeys currently pressed - bool mKeysShift[256] = {false}; // Array whether shift was held during key-press + bool mMouseDn = false; // Mouse button down + bool mMouseDnR = false; // Right mouse button down + bool mMouseDnM = false; // Middle mouse button down, as left + right + float mMouseDnX, mMouseDnY; // X/Y position where mouse button was pressed + float mMouseMvX, mMouseMvY; // Current mouse pointer position + int32_t mMouseWheel = 0; // Incremental value of mouse wheel, ca +/- 100 per wheel tick + bool mKeys[256] = {false}; // Array of mKeys currently pressed (not while the console is shown) int32_t mDisplayHeight = INIT_HEIGHT; int32_t mDisplayWidth = INIT_WIDTH; - int32_t mCanDrawText = 0; // 1 = in compat mode, 2 = with shader std::chrono::steady_clock::time_point mNextFrameTime; @@ -146,15 +131,20 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface frontendTypes mFrontendType = TYPE_INVALID; const char* mFrontendName = nullptr; - volatile bool mDisplayRunning = false; + std::atomic mDisplayRunning = false; // Set at the end of a successful InitDisplay, StopDisplay waits for it to become false again std::unique_ptr mGUIChannel; // Must outlive mGUI std::unique_ptr mGUI; + int32_t HandleUpdatesAndDrawGLScene(bool suspended = false); // Handle external GUI signals and config changes, then draw the scene (suspended: only update, no drawing, e.g. for a hidden window) + void HandleKey(uint8_t key); // Callback for handling key presses - int32_t DrawGLScene(); // Callback to draw the GL scene + bool consoleShown() const; // Key presses go to the console, frontends do not set mKeys + bool keyMapDown(uint32_t id) const; // Key of the KEY_MAP_* mapping held, never for unbound ones 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 + uint32_t shiftedKey(uint32_t key) const; // Special keys with SHIFT held at code + SPECIAL_KEYS_SHIFTED, as in configKeys + void showConfigInfo(uint32_t index); // Info for a changed option (index in the config RTTI): its info callback, or "struct.option changed to value" void ResizeScene(int32_t width, int32_t height); // Callback when GL window is resized int32_t InitDisplay(bool initFailure = false); // Callback to initialize the GL Display (to be called in StartDisplay) void ExitDisplay(); // Callback to clean up the GL Display @@ -163,8 +153,28 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void setWindowState(bool fullScreen, bool maximized); // Sync the settings if the window manager changed the window state void focusLost(); // Release all keys and mouse buttons when the window loses focus, since the release events go to another window void applyInitialWindowState(); // Switch to fullscreen / maximize at startup according to the settings + void requestFullScreen(bool set); // Call SwitchFullscreen / ToggleMaximized only if the state differs from the last requested or reported (setWindowState) one + void requestMaximized(bool set); + private: - bool HandleExternalGUIKeys(); // Returns whether keys were handled, config changes are not handled + struct GPUDisplayConfiguration; + struct configRTTIExt; + std::unique_ptr mConfiguration{nullptr, nullptr}; // Deleter set in GPUDisplayFrontendConfig.cxx, the only place where GPUDisplayConfiguration is defined + + void HandleExternalGUIKeys(); + static int32_t translateExternalKey(uint32_t translationMode, uint64_t extendedKey); // Key code of an encoded key of sendExternalGUIKey (+ SPECIAL_KEYS_SHIFTED with SHIFT), -1 if unknown + void buildConsoleCommands(); // Fills mConsole->mConsoleCommands, GPUDisplayFrontendConfigConsole.inc (commands need the config RTTI) + std::unique_ptr mConsole; + bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system + template + void add_key_binding(Args&&... args); // KEY_LAMBDA(key, ...) entries, the help text, and optionally the keys to show in the help text, only used in GPUDisplayFrontendConfig.cxx + template + void add_gui_binding(Args&&... args); // GUI_LAMBDA(name, ...) entries shown in one frame of the Qt GUI, and optionally its description and a flag to place the elements horizontally (default vertically), only used in GPUDisplayFrontendConfig.cxx + void setGUILayout(GPUDisplayGUIChannel& channel) const; // Copy the GUI elements of the add_gui_binding calls to the channel + void applyGUIValues(const GPUDisplayGUIUpdate& update); // Call the setter of the GUI elements changed in the GUI (element id, value), and show the info of their options + void listConfig(); + const std::vector& getHelpText(); // Help text of all key bindings, one line per binding, rebuilt after key bindings changed + void listKeys(const std::function& print); // Bound keys with action / key down mapping and option, then unbound actions and mappings, then the key down mappings with their keys, one line per call }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx new file mode 100644 index 0000000000000..8ec2ff6f11924 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -0,0 +1,869 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfig.cxx +/// \author David Rohr + +#include "GPUDisplayFrontend.h" +#include "GPUDisplay.h" +#include "frontend/GPUDisplayInfo.inc" +#include "GPULogging.h" +#include "GPUCommonMath.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 +#include +#include +#include +#include +#include + +using namespace o2::gpu; +struct GPUDisplayFrontend::configRTTIExt : public qConfig::qConfigRTTIentry { +}; + +using configKeyCallback = std::function; + +struct GPUDisplayFrontend::GPUDisplayConfiguration { + static constexpr std::tuple configStructs{&GPUDisplay::mCfgL, &GPUDisplay::mCfgH, &GPUDisplay::mCfgR, &GPUDisplay::mCfgO, &GPUDisplay::mCfgC}; // Config structs with RTTI, as members of GPUDisplay + + std::vector rttiEntries; // qconfig RTTI of all options of configStructs (struct, name, type, offset, default, min / max, description, message), the index identifies an option + 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(); // No entry: option index (rttiEntries) or, for keyAction / guiElements, no option + static constexpr int32_t KEY_UNBOUND = -1; // configKeys entry of a key without binding + static constexpr int32_t KEY_MAP_INDEX = 1 << 24; // configKeys entry of keys mapped to mKeyDownMap[entry - KEY_MAP_INDEX], entries below are indices in keyActions + static bool isKeyMap(int32_t binding) { return binding >= KEY_MAP_INDEX; } // configKeys entry of a key down mapping + using keyDownMapEntry = std::tuple; // KEY_DOWN_MAP: key, KEY_MAP_ id + using keyLambdaEntry = std::tuple; // KEY_LAMBDA / KEY_ACTION: key, actionName, index in rttiEntries (INVALID_INDEX for KEY_ACTION), callback + struct keyAction { + std::string actionName; // Unique, UPPER_SNAKE, used by bindKey + uint32_t rttiIndex; // Option changed by the action, INVALID_INDEX if none + configKeyCallback callback; // Lambda of KEY_LAMBDA / KEY_ACTION + }; + std::vector keyActions; // Key functions of KEY_LAMBDA / KEY_ACTION, keys can be bound to them by name (bindKey) + std::unordered_map keyActionsNames; // actionName -> index in keyActions + std::vector configStructNames; // Names of the configStructs, by configStructIndex + std::vector> currentActionKeys; // Reverse lookup of configKeys: keys bound to each action, by index in keyActions, in the order of binding + std::vector currentMappedKeys; // Reverse lookup of configKeys: key of each key down mapping, by id, -1 if none + std::array configKeys; // By key code, special keys with SHIFT at code + SPECIAL_KEYS_SHIFTED: index in keyActions, KEY_MAP_INDEX + id, or KEY_UNBOUND (filled in buildConfig) + std::vector keyNames; // Names of special keys (KEY_*) by key code < N_SPECIAL_KEYS, empty for other codes; for getKeyName and key names in console commands + std::vector keyMapNames; // Names of the key down mapping ids (KEY_MAP_*) + std::vector>> helpTextEntries; // Keys, text, and bindings (configKeys entries) of each add_key_binding call + std::vector helpText; // Aligned lines from helpTextEntries, by buildHelpText; use getHelpText, which rebuilds them if needed + bool helpTextUpToDate = false; // Set by buildHelpText, reset when a key binding changes: getHelpText calls rebuildHelpText + struct guiElementCallbacks { + uint32_t rttiIndex; // Option of the element + std::function set; // GUI_LAMBDA setter, applies a value from the GUI + }; + std::vector guiElements; // By element id + std::vector guiLayout; // Frames of the add_gui_binding calls + static constexpr std::string_view possibleKeys = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#.:;>'^_"; // Bindable keys >= 32 (space), special keys < 32 are those with a name in keyNames + + template + static constexpr uint32_t configStructIndex() // Index of struct T in configStructs, compile-time error if not present + { + if constexpr (std::is_same_v>, T GPUDisplay::*>) { + return I; + } else { + return configStructIndex(); + } + } + template + static T& configStruct(GPUDisplay& display) + { + return display.*std::get()>(configStructs); + } + static char* configStructPtr(GPUDisplay& display, uint32_t index) // Runtime version of configStruct by index + { + char* retVal = nullptr; + uint32_t i = 0; + std::apply([&](auto... s) { ((retVal = i++ == index ? reinterpret_cast(&(display.*s)) : retVal), ...); }, configStructs); + return retVal; + } + std::string setValue(GPUDisplay& display, uint32_t index, const GPUDisplayConsole::consoleArg& value); // Converted to the type of the option, empty string on success, otherwise the error + std::optional valueString(GPUDisplay& display, uint32_t index, bool precise = false) const; // Current value of an option, printed with print_type as in qconfig.cxx, nullopt for unsupported types; precise: floats with all digits, to read them back exactly + 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& [keys, text, bindings] : helpTextEntries) { + width = std::max(width, keys.size()); + } + helpText.clear(); + for (const auto& [keys, text, bindings] : helpTextEntries) { + helpText.emplace_back(keys + std::string(width - keys.size(), ' ') + ": " + text); + } + helpTextUpToDate = true; + } + void rebuildHelpText() // Keys of each entry from the current bindings, in the order of add_key_binding: [N/A] if unbound, [a|b] for several keys; entries without bindings (mouse) keep their text + { + for (auto& [keys, text, bindings] : helpTextEntries) { + if (bindings.empty()) { + continue; + } + keys.clear(); + for (int32_t binding : bindings) { + std::string bound; + std::vector keyCodes = boundKeys(binding); + std::sort(keyCodes.begin(), keyCodes.end()); // By key code, independent of the order of binding + for (int32_t k : keyCodes) { + bound += (bound.empty() ? "" : "|") + getKeyName(k); + } + keys += (keys.empty() ? "[" : " [") + (bound.empty() ? "N/A" : bound) + "]"; + } + } + buildHelpText(); + } + std::vector boundKeys(int32_t binding) const // Keys bound to the action or key down mapping + { + if (isKeyMap(binding)) { + const int32_t key = currentMappedKeys[binding - KEY_MAP_INDEX]; + return key < 0 ? std::vector() : std::vector{key}; + } + return currentActionKeys[binding]; + } + void setKeyBinding(uint32_t key, int32_t binding) // Binds the unbound key, also in the reverse lookups (mKeyDownMap is set by the caller) + { + configKeys[key] = binding; + if (isKeyMap(binding)) { + currentMappedKeys[binding - KEY_MAP_INDEX] = key; + } else { + currentActionKeys[binding].emplace_back(key); + } + helpTextUpToDate = false; + } + int32_t removeKeyBinding(uint32_t key) // Unbinds the key, also in the reverse lookups (mKeyDownMap is reset by the caller), returns the previous binding + { + const int32_t binding = configKeys[key]; + if (isKeyMap(binding)) { + currentMappedKeys[binding - KEY_MAP_INDEX] = -1; + } else if (binding != KEY_UNBOUND) { + std::erase(currentActionKeys[binding], key); + } + configKeys[key] = KEY_UNBOUND; + helpTextUpToDate = false; + return binding; + } + void unbindAllKeys() // Unbinds all keys, also in the reverse lookups (mKeyDownMap is reset by the caller) + { + configKeys.fill(KEY_UNBOUND); + currentMappedKeys.assign(KEY_MAP_MAX, -1); + for (auto& keys : currentActionKeys) { + keys.clear(); + } + helpTextUpToDate = false; + } + std::string bindingName(int32_t binding) const // Name of the action or key down mapping, empty if unbound + { + return isKeyMap(binding) ? keyMapNames[binding - KEY_MAP_INDEX] : (binding >= 0 ? keyActions[binding].actionName : ""); + } + uint32_t keyRTTIIndex(uint32_t key) const // Option changed by the action bound to the key, INVALID_INDEX if none + { + const int32_t binding = configKeys[key]; + return binding != KEY_UNBOUND && !isKeyMap(binding) ? keyActions[binding].rttiIndex : INVALID_INDEX; + } + std::string getKeyName(uint16_t key) const // Name of a special key (KEY_*), the character otherwise, the code if neither, SHIFT_ for shifted special keys + { + if (key >= 256) { + return "SHIFT_" + getKeyName(key - SPECIAL_KEYS_SHIFTED); + } + return key < keyNames.size() && !keyNames[key].empty() ? keyNames[key] : (key < 32 ? std::to_string(key) : std::string(1, (char)key)); + } +}; + +namespace +{ +#include "utils/qconfig_genrtti.inc" +} // namespace + +void GPUDisplay::checkAndApplyConfigChanges() +{ + const auto& [oldCfgH, oldCfgL, oldCfgR] = mOldCfg; + const bool changedH = memcmp((void*)&oldCfgH, (void*)&mCfgH, sizeof(mCfgH)) != 0, changedL = memcmp((void*)&oldCfgL, (void*)&mCfgL, sizeof(mCfgL)) != 0, changedR = memcmp((void*)&oldCfgR, (void*)&mCfgR, sizeof(mCfgR)) != 0; + if (mAnimate && (changedH || changedL || changedR || mCfgC.resetScene.load())) { + StopAnimation(); + } + if (changedH) { + mUpdateEventData = true; + } + if (mCfgL.propagateTracks != 0 && !mTracksArePropagated) { + mUpdateVertexLists = true; + } + if (changedL || changedR) { + mUpdateDrawCommands = true; + } + if (oldCfgR.drawQualityMSAA != mCfgR.drawQualityMSAA || oldCfgR.drawQualityDownsampleFSAA != mCfgR.drawQualityDownsampleFSAA || oldCfgL.depthBuffer != mCfgL.depthBuffer || oldCfgR.screenshotScaleFactor != mCfgR.screenshotScaleFactor) { + mUpdateRenderPipeline = true; + } + if (oldCfgL.depthBuffer != mCfgL.depthBuffer) { // Also for set / scripts, not only key and GUI + mBackend->setDepthBuffer(); + } + + if (oldCfgR.drawQualityVSync != mCfgR.drawQualityVSync) { + mFrontend->SetVSync(mCfgR.drawQualityVSync); + mBackend->SetVSync(mCfgR.drawQualityVSync); + } + if (oldCfgR.fullScreen != mCfgR.fullScreen) { + mFrontend->requestFullScreen(mCfgR.fullScreen); + } + if (oldCfgR.maximized != mCfgR.maximized) { + mFrontend->requestMaximized(mCfgR.maximized); + } + if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { + mUpdateEventData = true; + } + if (oldCfgH.trackFilter != mCfgH.trackFilter) { + mUpdateTrackFilter = true; + } + mCfgO.rotateModel |= mCfgO.rotateAroundZ; + if (mCfgC.showConsole != mConsoleShown) { + mConsoleShown = mCfgC.showConsole; + const double start = mConsoleTimer.IsRunning() ? std::max(0., mConfig.internal.consoleDelay - mConsoleTimer.GetCurrentElapsedTime()) : 0.; // Reverse a running animation from its current position + mConsoleTimer.Reset(); + mConsoleTimer.AddTime(start); + mConsoleTimer.Start(); + if (mCfgC.showConsole) { + const bool shift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; // Kept while the console is shown (scrolling, shifted keys), frontends still update it + std::fill(std::begin(mFrontend->mKeys), std::end(mFrontend->mKeys), false); + mFrontend->mKeys[mFrontend->KEY_SHIFT] = shift; + } + } + if (mCfgO.applyToAllPoints && oldCfgL.animationMode != mCfgL.animationMode) { // Also for keys; the animation's own changes are in mOldCfg, so this is only user input + for (auto& p : mAnimationVectors) { + p.cfgL.animationMode = mCfgL.animationMode; + } + } + if (mCfgC.animationPoints.selected >= 0 && mCfgC.animationPoints.selected < (int32_t)mAnimationVectors.size()) { // Update animation settings of selected point + mAnimationVectors[mCfgC.animationPoints.selected].cfgL = mCfgL; + } + updateAnimationPointsGUI(); +} + +void GPUDisplay::HandleExternalGUIKey(int32_t key, bool bypassConsole) +{ + bool shifted = key >= 'A' && key <= 'Z'; + if (key >= 256) { // Special key with SHIFT, from translateExternalKey + shifted = true; + key -= mFrontend->SPECIAL_KEYS_SHIFTED; + } + bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; + mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; + HandleKey(key, true, bypassConsole); + mFrontend->mKeys[mFrontend->KEY_SHIFT] = oldShift; +} + +void GPUDisplayFrontend::HandleExternalGUIKeys() +{ + std::lock_guard lock(mExternalGUIKeyMutex); + for (auto key : mExternalGUIKeys) { + mDisplay->HandleExternalGUIKey(key); + } + mExternalGUIKeys.clear(); +} + +template +void GPUDisplayFrontend::add_key_binding(Args&&... args) +{ + constexpr size_t nKeys = (0 + ... + (std::is_same_v, GPUDisplayConfiguration::keyLambdaEntry> || std::is_same_v, GPUDisplayConfiguration::keyDownMapEntry>)); + 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; + std::vector bindings; + auto addKey = [&](auto& key) { + const uint16_t character = std::get<0>(key); + const std::string name = mConfiguration->getKeyName(character); + int32_t binding; + if (mConfiguration->configKeys[character] != GPUDisplayConfiguration::KEY_UNBOUND) { + throw std::runtime_error("Key [" + name + "] assigned twice in display frontend"); + } + if constexpr (std::is_same_v, GPUDisplayConfiguration::keyDownMapEntry>) { + const uint8_t id = std::get<1>(key); + if (mKeyDownMap[id]) { + throw std::runtime_error("Key down mapping " + mConfiguration->keyMapNames[id] + " assigned twice in display frontend"); + } + mKeyDownMap[id] = character >= 'a' && character <= 'z' ? character + 'A' - 'a' : character; // mKeys stores letters in upper case + binding = GPUDisplayConfiguration::KEY_MAP_INDEX + id; + } else if constexpr (std::is_same_v, GPUDisplayConfiguration::keyLambdaEntry>) { + const std::string& actionName = std::get<1>(key); + const auto& mapNames = mConfiguration->keyMapNames; + if (mConfiguration->keyActionsNames.contains(actionName) || std::find(mapNames.begin(), mapNames.end(), actionName) != mapNames.end()) { + throw std::runtime_error("Key action " + actionName + " defined twice in display frontend, or name of a key down mapping"); + } + binding = mConfiguration->keyActions.size(); + mConfiguration->keyActionsNames.emplace(actionName, binding); + mConfiguration->keyActions.push_back({actionName, std::get<2>(key), std::move(std::get<3>(key))}); + mConfiguration->currentActionKeys.emplace_back(); + } else { + static_assert(false, "invalid code path"); + } + mConfiguration->setKeyBinding(character, binding); + bindings.emplace_back(binding); + 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); + } + mConfiguration->helpTextEntries.emplace_back(std::move(keys), std::get(argTuple), std::move(bindings)); +} + +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, double s = 0) : type(t), choices(std::move(c)), options(std::move(o)), lines(l), bitMask(b), step(s) {} + 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 + double step = 0; // GUI_SLIDER_STEP / GUI_SLIDER_DYNAMIC_STEP / GUI_MULTI_SLIDER +}; + +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 if constexpr (std::is_same_v, GPUDisplayMultiSliderValue>) { + return ""; + } 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) { + dst = v; + } else if constexpr (std::is_same_v || std::is_same_v) { + throw std::runtime_error("GUI value of a multi slider for another element type, or vice versa"); + } else 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; + } +} + +static GPUDisplayGUIValue toGUIValue(const GPUDisplayMultiSliderValue& v) { return 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->mCfgO, 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 = mConfiguration->guiElements.size(); + auto& el = frame.elements.emplace_back(GPUDisplayGUIChannel::Element{.name = element.name, .description = element.rttiIndex < mConfiguration->rttiEntries.size() ? mConfiguration->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, .step = element.type.step, .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 >= mConfiguration->rttiEntries.size()) { + throw std::runtime_error("GUI slider " + element.name + " needs an option with RTTI for min and max values"); + } + const auto& e = mConfiguration->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 < mConfiguration->rttiEntries.size() && !mConfiguration->rttiEntries[element.rttiIndex].min.empty() && !mConfiguration->rttiEntries[element.rttiIndex].max.empty()) { + el.min = std::stod(mConfiguration->rttiEntries[element.rttiIndex].min); + el.max = std::stod(mConfiguration->rttiEntries[element.rttiIndex].max); + el.hasRange = true; + } + if (el.step > 0 && !(el.type == Type::MULTI_SLIDER || (el.type == Type::SLIDER && el.choices.empty()))) { + throw std::runtime_error("GUI element " + element.name + ": a step only for sliders with a range and multi sliders"); + } + if (el.type == Type::MULTI_SLIDER && (!std::holds_alternative(element.get(defaults)) || el.step <= 0)) { + throw std::runtime_error("GUI multi slider " + element.name + " needs an option of type GPUDisplayMultiSliderValue and a step > 0"); + } + 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; + mConfiguration->guiElements.emplace_back(GPUDisplayConfiguration::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), ...); + mConfiguration->guiLayout.emplace_back(std::move(frame)); +} + +void GPUDisplayFrontend::setGUILayout(GPUDisplayGUIChannel& channel) const { channel.layout = mConfiguration->guiLayout; } + +void GPUDisplayFrontend::applyGUIValues(const GPUDisplayGUIUpdate& update) +{ + for (const auto& [id, value] : update.values) { + if (id >= mConfiguration->guiElements.size()) { + continue; + } + const auto& e = mConfiguration->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->mCfgO.guiPrintInfo && e.rttiIndex < mConfiguration->configInfos.size()) { + showConfigInfo(e.rttiIndex); + } + } +} + +const std::vector& GPUDisplayFrontend::getHelpText() +{ + if (!mConfiguration->helpTextUpToDate) { + mConfiguration->rebuildHelpText(); + } + return mConfiguration->helpText; +} + +static std::pair keyOrder(uint16_t k) // 0-9, a-z, A-Z, other characters, special keys, special keys with SHIFT +{ + return {k >= '0' && k <= '9' ? 0 + : k >= 'a' && k <= 'z' ? 1 + : k >= 'A' && k <= 'Z' ? 2 + : k >= 256 ? 5 + : k > 32 ? 3 + : 4, + k}; +} + +void GPUDisplayFrontend::listKeys(const std::function& print) +{ + const auto& rtti = *mConfiguration; + std::vector boundKeys; + for (uint32_t k = 1; k < rtti.configKeys.size(); k++) { + if (rtti.configKeys[k] != GPUDisplayConfiguration::KEY_UNBOUND) { + boundKeys.emplace_back(k); + } + } + std::sort(boundKeys.begin(), boundKeys.end(), [](uint16_t a, uint16_t b) { return keyOrder(a) < keyOrder(b); }); + std::vector> rows; // Key, binding + for (uint16_t k : boundKeys) { + rows.emplace_back("[" + rtti.getKeyName(k) + "]", rtti.configKeys[k]); + } + auto addUnbound = [&](int32_t binding) { + if (rtti.boundKeys(binding).empty()) { + rows.emplace_back("[N/A]", binding); + } + }; + for (uint32_t i = 0; i < rtti.keyActions.size(); i++) { + addUnbound(i); + } + for (uint32_t i = 0; i < KEY_MAP_MAX; i++) { + addUnbound(GPUDisplayConfiguration::KEY_MAP_INDEX + i); + } + size_t keyWidth = 0, nameWidth = 0; + for (const auto& [key, binding] : rows) { + keyWidth = std::max(keyWidth, key.size()); + nameWidth = std::max(nameWidth, rtti.bindingName(binding).size()); + } + print("Key bindings (key, action or key down mapping, option):"); + for (const auto& [key, binding] : rows) { + const std::string name = rtti.bindingName(binding); + const uint32_t index = GPUDisplayConfiguration::isKeyMap(binding) ? GPUDisplayConfiguration::INVALID_INDEX : rtti.keyActions[binding].rttiIndex; + const std::string option = GPUDisplayConfiguration::isKeyMap(binding) ? "(key down map)" : (index == GPUDisplayConfiguration::INVALID_INDEX ? "(no config option)" : rtti.rttiEntries[index].configStruct + " " + rtti.rttiEntries[index].name); + print(" " + key + std::string(keyWidth - key.size() + 1, ' ') + name + std::string(nameWidth - name.size() + 1, ' ') + option); + } + print(""); + print("Key down mappings (id, name, key):"); + for (uint32_t i = 0; i < KEY_MAP_MAX; i++) { + char line[128]; + snprintf(line, sizeof(line), " %2u %-28s %s", i, rtti.keyMapNames[i].c_str(), rtti.currentMappedKeys[i] < 0 ? "[N/A]" : ("[" + rtti.getKeyName(rtti.currentMappedKeys[i]) + "]").c_str()); + print(line); + } +} + +void GPUDisplayFrontend::listConfig() +{ + const auto& rtti = *mConfiguration; + std::vector boundKeys, availableKeys; + for (uint32_t k = 1; k < rtti.configKeys.size(); k++) { + const uint32_t base = k >= 256 ? k - SPECIAL_KEYS_SHIFTED : k; // A shifted special key is possible if the special key is, SHIFT itself is the modifier + const bool possible = base != KEY_SHIFT && ((base < N_SPECIAL_KEYS && !rtti.keyNames[base].empty()) || (k < 256 && rtti.possibleKeys.find((char)k) != std::string_view::npos)); + if (rtti.configKeys[k] != GPUDisplayConfiguration::KEY_UNBOUND) { + boundKeys.emplace_back(k); + } else if (possible) { + availableKeys.emplace_back(k); + } + } + std::sort(boundKeys.begin(), boundKeys.end(), [&](uint16_t a, uint16_t b) { return keyOrder(a) < keyOrder(b); }); + std::sort(availableKeys.begin(), availableKeys.end(), [&](uint16_t a, uint16_t b) { return keyOrder(a) < keyOrder(b); }); + + GPUInfo(""); + GPUInfo("Display config options with RTTI (struct, option, type, default, in Qt GUI, keys):"); + uint32_t nOptionsWithKey = 0, nOptionsInGUI = 0; + int32_t structWidth = 0, nameWidth = 0, typeWidth = 0, defaultWidth = 0; // Columns aligned to the longest entry + for (const auto& e : rtti.rttiEntries) { + structWidth = std::max(structWidth, e.configStruct.size()); + nameWidth = std::max(nameWidth, e.name.size()); + typeWidth = std::max(typeWidth, e.type.size()); + defaultWidth = std::max(defaultWidth, e.defaultValue.size()); + } + for (uint32_t i = 0; i < rtti.rttiEntries.size(); i++) { + const auto& e = rtti.rttiEntries[i]; + std::string keys; + for (uint16_t k : boundKeys) { + if (rtti.keyRTTIIndex(k) == i) { + keys += (keys.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + } + } + const bool inGUI = std::any_of(rtti.guiElements.begin(), rtti.guiElements.end(), [i](const auto& g) { return g.rttiIndex == i; }); + nOptionsWithKey += !keys.empty(); + nOptionsInGUI += inGUI; + GPUInfo(" %-*s %-*s %-*s %-*s %-3s %s", structWidth, e.configStruct.c_str(), nameWidth, e.name.c_str(), typeWidth, e.type.c_str(), defaultWidth, e.defaultValue.c_str(), inGUI ? "GUI" : "---", keys.c_str()); + } + GPUInfo(""); + listKeys([](const std::string& line) { GPUInfo("%s", line.c_str()); }); + GPUInfo(""); + static constexpr std::string_view availablePrefix = "Available keys: "; + static constexpr size_t MAX_LINE_LENGTH = 240; + std::string available(availablePrefix); + for (uint16_t k : availableKeys) { + const std::string key = "[" + rtti.getKeyName(k) + "]"; + if (available.size() > availablePrefix.size() && available.size() + 1 + key.size() > MAX_LINE_LENGTH) { // Wrap, without splitting a key + GPUInfo("%s", available.c_str()); + available.assign(availablePrefix.size(), ' '); + } else if (available.size() > availablePrefix.size()) { + available += ' '; + } + available += key; + } + GPUInfo("%s", available.c_str()); + GPUInfo(""); + GPUInfo("Help text:"); + for (const auto& line : getHelpText()) { + 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, %zu console commands", rtti.rttiEntries.size(), nOptionsWithKey, nOptionsInGUI, boundKeys.size(), availableKeys.size(), mConsole->mConsoleCommands.size()); + GPUInfo(""); +} + +#include "GPUDisplayFrontendConfigKeys.inc" +#include "GPUDisplayFrontendConfigInfo.inc" +#include "GPUDisplayFrontendConfigQtGUI.inc" +#include "GPUDisplayFrontendConfigConsole.inc" + +void GPUDisplay::buildConfig() +{ + auto& rtti = mFrontend->mConfiguration; + rtti = {new GPUDisplayFrontend::GPUDisplayConfiguration, [](GPUDisplayFrontend::GPUDisplayConfiguration* p) { delete p; }}; + + rtti->unbindAllKeys(); + mFrontend->mKeyDownMap.fill(0); // Set again by buildConfigKeys, also when the display is started again + rtti->keyNames.resize(rtti->configKeys.size()); + rtti->keyMapNames.resize(GPUDisplayFrontend::KEY_MAP_MAX + 1); // Including MAX +#define GPUDisplayFrontendAddKey(code, key) rtti->keyNames[code] = #key +#define GPUDisplayFrontendKeyDownMap(code, key) rtti->keyMapNames[code] = #key +#include "frontend/GPUDisplayFrontendKeys.h" + + std::tie(rtti->rttiEntries, rtti->configStructNames) = std::apply([this](auto... s) { return qConfigGenRTTI*s)>...>(); }, rtti->configStructs); + std::apply([&](auto... s) { (rtti->rttiReverseId.emplace_back(sizeof(this->*s), GPUDisplayFrontend::GPUDisplayConfiguration::INVALID_INDEX), ...); }, rtti->configStructs); + for (uint32_t i = 0; i < rtti->rttiEntries.size(); i++) { + const auto& entry = rtti->rttiEntries[i]; + rtti->rttiReverseId[entry.configStructIndex][entry.offset] = i; + } + + buildConfigKeys(); + + rtti->configInfos.resize(rtti->rttiEntries.size()); + for (uint32_t i = 0; i < rtti->rttiEntries.size(); i++) { // Info from the qConfig message, with the value as %s as in qconfig.cxx + const auto& e = rtti->rttiEntries[i]; + if (e.message.empty()) { + continue; + } + if (!rtti->valueString(*this, i)) { + GPUError("No info message support for type %s of %s", e.type.c_str(), e.name.c_str()); + continue; + } + rtti->configInfos[i] = [this, i, message = e.message]() { + char buffer[256]; + const int32_t n = snprintf(buffer, sizeof(buffer), message.c_str(), mFrontend->mConfiguration->valueString(*this, i)->c_str()); + if (n < 0 || n >= (int32_t)sizeof(buffer)) { + GPUError("Info message too long: %s", message.c_str()); + return; + } + SetInfo("%s", buffer); + }; + } + buildConfigInfo(); + buildConfigQtGUI(); + mFrontend->buildConsoleCommands(); + mFrontend->mConsole->mPrintToTerminal = mConfig.internal.consolePrintToTerminal; + rtti->buildHelpText(); +} + +uint32_t GPUDisplayFrontend::shiftedKey(uint32_t key) const { return key < N_SPECIAL_KEYS && mKeys[KEY_SHIFT] ? key + SPECIAL_KEYS_SHIFTED : key; } + +bool GPUDisplayFrontend::processKeyLambda(uint32_t key) +{ + key = shiftedKey(key); + const int32_t binding = key < mConfiguration->configKeys.size() ? mConfiguration->configKeys[key] : GPUDisplayConfiguration::KEY_UNBOUND; + if (binding != GPUDisplayConfiguration::KEY_UNBOUND && !GPUDisplayConfiguration::isKeyMap(binding)) { + const auto& action = mConfiguration->keyActions[binding]; + action.callback(); + if (action.rttiIndex < mConfiguration->configInfos.size()) { + showConfigInfo(action.rttiIndex); + } + return true; + } + return false; +} + +void GPUDisplayFrontend::showConfigInfo(uint32_t index) +{ + if (mConfiguration->configInfos[index]) { + mConfiguration->configInfos[index](); + } else if (const auto value = mConfiguration->valueString(*mDisplay, index)) { // Generic info for options without info callback + const auto& e = mConfiguration->rttiEntries[index]; + mDisplay->SetInfo("%s.%s changed to %s", mConfiguration->configStructNames[e.configStructIndex].c_str(), e.name.c_str(), value->c_str()); + } +} + +void GPUDisplay::HandleKey(uint8_t key, bool external, bool bypassConsole) +{ + if (mCfgC.showConsole && !bypassConsole && mFrontend->mConfiguration->keyRTTIIndex(mFrontend->shiftedKey(key)) != mFrontend->mConfiguration->getRTTIIndex(*this, &GPUDisplayControl::showConsole)) { // All keys except the one toggling the console go to the console + if (!external) { + mFrontend->mConsole->handleConsoleKey(key, mFrontend->mKeys[mFrontend->KEY_SHIFT]); + } + return; + } + const uint8_t originKey = mFrontend->mKeyDownMap[mFrontend->KEY_MAP_FOCUS_ORIGIN], upKey = mFrontend->mKeyDownMap[mFrontend->KEY_MAP_FOCUS_ORIENT_UP]; + if (originKey && upKey && (key >= 'a' && key <= 'z' ? key + 'A' - 'a' : key) == originKey) { // mKeyDownMap has letters in upper case + mFrontend->mKeys[upKey] = false; // Release the orient up key (CTRL) with the focus origin key (ALT), to avoid orienting along y automatically! + } + mFrontend->processKeyLambda(key); +} + +std::string GPUDisplayFrontend::GPUDisplayConfiguration::setValue(GPUDisplay& display, uint32_t index, const GPUDisplayConsole::consoleArg& value) +{ + const auto& e = rttiEntries[index]; + char* ptr = configStructPtr(display, e.configStructIndex) + e.offset; + if (e.type == "std::string") { + const std::string* str = std::get_if(&value); + if (!str) { + return "string expected"; + } + *(std::string*)ptr = *str; + return ""; + } + if (e.isVector || e.arraySize) { + return "unsupported type " + e.type; + } + if (e.type == "float") { // Floating point: also from an integer + const double v = std::holds_alternative(value) ? (double)std::get(value) : (std::holds_alternative(value) ? std::get(value) : std::nan("")); + if (std::isnan(v)) { + return "number expected"; + } else if ((e.min.size() && v < std::stod(e.min)) || (e.max.size() && v > std::stod(e.max))) { + return "value out of range [" + e.min + ", " + e.max + "]"; + } + *(float*)ptr = v; + return ""; + } + int64_t v; // Integer types and bool, exactly, not through double + if (const std::string* str = std::get_if(&value)) { + if (e.type != "bool" || (*str != "true" && *str != "false")) { + return "number expected"; + } + v = *str == "true"; + } else if (const int64_t* i = std::get_if(&value)) { + v = *i; + } else { + return "integer expected"; + } + if ((e.min.size() && v < std::stoll(e.min)) || (e.max.size() && v > std::stoll(e.max))) { + return "value out of range [" + e.min + ", " + e.max + "]"; + } + auto store = [&](auto&& assign) -> std::string { // Within the range of the type T + if (v < (int64_t)std::numeric_limits::min() || (v > 0 && (uint64_t)v > (uint64_t)std::numeric_limits::max())) { + return "value out of range of " + e.type; + } + assign((T)v); + return ""; + }; + if (e.type == "bool") { + return store.template operator()([&](bool x) { *(bool*)ptr = x; }); + } else if (e.type == "int8_t") { + return store.template operator()([&](int8_t x) { *(int8_t*)ptr = x; }); + } else if (e.type == "uint8_t") { + return store.template operator()([&](uint8_t x) { *(uint8_t*)ptr = x; }); + } else if (e.type == "int32_t") { + return store.template operator()([&](int32_t x) { *(int32_t*)ptr = x; }); + } else if (e.type == "uint32_t") { + return store.template operator()([&](uint32_t x) { *(uint32_t*)ptr = x; }); + } else if (e.type == "int64_t") { + return store.template operator()([&](int64_t x) { *(int64_t*)ptr = x; }); + } else if (e.type == "uint64_t") { + return store.template operator()([&](uint64_t x) { *(uint64_t*)ptr = x; }); + } else if (e.type == "GPUDisplayCopyableAtomic") { + return store.template operator()([&](int32_t x) { ((GPUDisplayCopyableAtomic*)ptr)->store(x); }); + } else if (e.type == "GPUDisplayCopyableAtomic") { + return store.template operator()([&](bool x) { ((GPUDisplayCopyableAtomic*)ptr)->store(x); }); + } + return "unsupported type " + e.type; +} + +std::optional GPUDisplayFrontend::GPUDisplayConfiguration::valueString(GPUDisplay& display, uint32_t index, bool precise) const +{ + 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") { + if (precise) { + std::ostringstream s; + s << std::setprecision(std::numeric_limits::max_digits10) << *(const float*)ptr; + return s.str(); + } + 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") { + 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 == "int64_t") { + return qConfig::print_type(*(const int64_t*)ptr); + } else if (e.type == "uint64_t") { + return qConfig::print_type(*(const uint64_t*)ptr); + } else if (e.type == "GPUDisplayCopyableAtomic") { + return qConfig::print_type(((const GPUDisplayCopyableAtomic*)ptr)->load()); + } else if (e.type == "GPUDisplayCopyableAtomic") { + return qConfig::print_type(((const GPUDisplayCopyableAtomic*)ptr)->load()); + } else if (e.type == "std::string") { + return *(const std::string*)ptr; + } + return std::nullopt; +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc new file mode 100644 index 0000000000000..6f03b8bbbfab1 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc @@ -0,0 +1,431 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigConsole.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGCONSOLE_INC +#define GPUDISPLAYFRONTENDCONFIGCONSOLE_INC + +void GPUDisplayFrontend::buildConsoleCommands() +{ + using args = std::span; + auto& commands = mConsole->mConsoleCommands; + commands["help"] = {[this](args) { mConsole->printCommands([this](const std::string& line) { mConsole->consolePrint("%s", line.c_str()); }); }, "", "List available console commands"}; + commands["clearHistory"] = {[this](args) { + mConsole->mConsoleHistory.resize(1); // Keep the title + mConsole->mConsoleTextCurrent = mConsole->mScroll = 0; + mConsole->mConsoleTextPos = std::min(mConsole->mConsoleTextPos, mConsole->mConsoleText.size()); // Shown text is mConsoleText again + mConsole->mConsoleInfoLastMessage.clear(); + }, + "", "Clear the console history"}; + commands["runScript"] = {[this](args a) { mConsole->runScriptFile(std::get(a[0])); }, "s", "Execute script file"}; + + auto writeFile = [this](const char* command, const std::string& file, const char* success, const std::function& write) { // Writes the file with write, reports errors opening / writing, or success (" ") + std::ofstream out(file); + if (!out) { + mConsole->consolePrint("%s: cannot open file %s", command, file.c_str()); + return; + } + write(out); + out.close(); + mConsole->consolePrint(out ? "%s %s" : "%s: error writing file %s", out ? success : command, file.c_str()); + }; + auto floatNumber = [](const GPUDisplayConsole::consoleArg& a) { return (float)std::get(a); }; + auto parseKey = [this](const GPUDisplayConsole::consoleArg& a) -> int32_t { // Key code (+ SPECIAL_KEYS_SHIFTED with SHIFT) from a code, a character or a key name (SHIFT_...), -1 if invalid + if (const int64_t* code = std::get_if(&a)) { + return *code > 0 && *code < (int64_t)N_TOTAL_KEYS ? (int32_t)*code : -1; + } + const std::string* str = std::get_if(&a); + if (!str) { // Floating point number + return -1; + } + const std::string& name = *str; + if (name.size() == 1) { + return (uint8_t)name[0]; + } + const bool shift = name.starts_with("SHIFT_"); + const std::string keyName = shift ? name.substr(6) : name; + if (keyName.empty()) { // Would match the codes without name + return -1; + } + const auto& names = mConfiguration->keyNames; + const auto it = std::find(names.begin(), names.end(), keyName); + const int32_t key = it == names.end() ? -1 : it - names.begin(); + return key < 0 || !shift ? key : (key < (int32_t)N_SPECIAL_KEYS ? key + (int32_t)SPECIAL_KEYS_SHIFTED : -1); + }; + commands["setXYZ"] = {[this, floatNumber](args a) { + const float xyz[3] = {floatNumber(a[0]), floatNumber(a[1]), floatNumber(a[2])}; + const float angles[3] = {mDisplay->mAngle[0], mDisplay->mAngle[1], mDisplay->mAngle[2]}; + mDisplay->setCamera(xyz, angles); + }, + "ddd", "Set the camera position x y z"}; + commands["setCameraAngles"] = {[this, floatNumber](args a) { + const float toRad = CAMath::Pi() / 180.f; + const float xyz[3] = {mDisplay->mXYZ[0], mDisplay->mXYZ[1], mDisplay->mXYZ[2]}; + const float angles[3] = {floatNumber(a[1]) * toRad, floatNumber(a[0]) * toRad, floatNumber(a[2]) * toRad}; // As mAngle: pitch, yaw, roll + mDisplay->setCamera(xyz, angles); + }, + "ddd", "Set the camera direction: yaw pitch roll in degrees"}; + commands["injectKey"] = {[this, parseKey](args a) { + const int32_t key = parseKey(a[0]); + if (key < 0) { + mConsole->consolePrint("injectKey: invalid key, expected a key code, a character, or a key name"); + return; + } + mDisplay->HandleExternalGUIKey(key, true); // Handled as key also while the console is shown + }, + "a", "Press a key: key code, character (\"a\"), or key name (UP, F1, SHIFT_F1, ...)"}; + commands["exit"] = {[this](args) { + mDisplay->mCfgC.displayControl.store(2); + mConsole->mScriptQueue.clear(); // No further script lines + showConfigInfo(mConfiguration->getRTTIIndex(*mDisplay, &GPUDisplayControl::displayControl)); + }, + "", "Exit"}; + commands["nextEvent"] = {[this](args) { + mDisplay->mCfgC.displayControl.store(1); + mConsole->waitFor([this, n = mDisplay->mNEventsShown] { return mDisplay->mNEventsShown != n; }); + }, + "", "Show the next event, a script continues when it is shown"}; + commands["addAnimationPoint"] = {[this, floatNumber](args a) { + const float toRad = CAMath::Pi() / 180.f; + const float xyz[3] = {floatNumber(a[0]), floatNumber(a[1]), floatNumber(a[2])}; + const float angles[3] = {floatNumber(a[4]) * toRad, floatNumber(a[3]) * toRad, floatNumber(a[5]) * toRad}; // As mAngle: pitch, yaw, roll + mDisplay->addAnimationPoint(xyz, angles); + mDisplay->SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mDisplay->mAnimationVectors.size(), mDisplay->mCfgC.animationPoints.values.back()); + }, + "dddddd", "Add an animation point: x y z yaw pitch roll (degrees)"}; + commands["startAnimation"] = {[this](args) { mDisplay->runAnimation(); }, "", "Run the animation"}; + commands["takeScreenshot"] = {[this](args a) { + mDisplay->takeScreenshot(); + mDisplay->mScreenshotFile = std::get(a[0]); + mConsole->waitFor([this] { return !mDisplay->mCfgC.requestScreenshot; }); // Otherwise further script lines would change the settings or the file before the screenshot is taken + showConfigInfo(mConfiguration->getRTTIIndex(*mDisplay, &GPUDisplayControl::requestScreenshot)); + }, + "s", "Take a screenshot to the given file, a script continues after it is taken"}; + auto unbind = [this](int32_t key) { // Removes the binding of the key, returns the name of the previous binding, empty if none + const int32_t binding = mConfiguration->removeKeyBinding(key); + if (GPUDisplayConfiguration::isKeyMap(binding)) { + mKeyDownMap[binding - GPUDisplayConfiguration::KEY_MAP_INDEX] = 0; + } + return mConfiguration->bindingName(binding); + }; + commands["bindKey"] = {[this, parseKey, unbind](args a) { + auto& rtti = *mConfiguration; + const std::string& name = std::get(a[1]); + const uint32_t mapId = std::find(rtti.keyMapNames.begin(), rtti.keyMapNames.begin() + KEY_MAP_MAX, name) - rtti.keyMapNames.begin(); + const auto action = rtti.keyActionsNames.find(name); + const int32_t key = parseKey(a[0]); + if (key < 0 || (mapId < KEY_MAP_MAX && key >= 256)) { + mConsole->consolePrint("bindKey: invalid key, expected a key code, a character, or a key name (SHIFT_ only for actions)"); + return; + } else if (mapId < KEY_MAP_MAX && key >= 'A' && key <= 'Z') { // Held letters use the entry of the lower case letter, as add_key_binding + mConsole->consolePrint("bindKey: key down mappings need lower case letters, the upper case letter would be SHIFT + key"); + return; + } else if (mapId >= KEY_MAP_MAX && action == rtti.keyActionsNames.end()) { + mConsole->consolePrint("bindKey: unknown action or key down mapping %s (listKeys)", name.c_str()); + return; + } + if (const std::string previous = unbind(key); previous.size() && previous != name) { + mConsole->consolePrint("bindKey: key [%s] was bound to %s, now unbound from it", rtti.getKeyName(key).c_str(), previous.c_str()); + } + if (mapId < KEY_MAP_MAX) { + if (const int32_t previousKey = rtti.currentMappedKeys[mapId]; previousKey >= 0) { // The previous key of this mapping + rtti.removeKeyBinding(previousKey); + } + rtti.setKeyBinding(key, GPUDisplayConfiguration::KEY_MAP_INDEX + mapId); + mKeyDownMap[mapId] = key >= 'a' && key <= 'z' ? key + 'A' - 'a' : key; // mKeys stores letters in upper case, as add_key_binding + } else { + rtti.setKeyBinding(key, action->second); + } + }, + "as", "Bind a key (code, character, or name, SHIFT_ for special keys with SHIFT) to an action or a key down mapping (listKeys), replacing the binding of the key; a key down mapping has one key (letters in lower case), an action keeps its other keys"}; + commands["clearKeyBindings"] = {[this](args) { + for (int32_t key : mConfiguration->currentMappedKeys) { + if (key >= 0) { + mConfiguration->removeKeyBinding(key); + } + } + mKeyDownMap.fill(0); + }, + "", "Remove all key down mappings (bindKey)"}; + commands["resetAnimation"] = {[this](args) { + mDisplay->resetAnimation(); + mDisplay->SetInfo("Reset Animation points", 1); + }, + "", "Remove all animation points"}; + auto pointIndex = [this](const char* command, const GPUDisplayConsole::consoleArg& a) -> std::optional { // Index of an animation point or -1, error message otherwise + const int64_t i = std::get(a); + if (i < -1 || i >= (int64_t)mDisplay->mAnimationVectors.size()) { + mConsole->consolePrint("%s: no animation point %lld (%zu points)", command, (long long)i, mDisplay->mAnimationVectors.size()); + return std::nullopt; + } + return i; + }; + commands["removeAnimationPoint"] = {[this, pointIndex](args a) { + if (const auto i = pointIndex("removeAnimationPoint", a[0])) { + mDisplay->removeAnimationPoint(*i); + } + }, + "i", "Remove the animation point with this index, -1: the selected one, or the last one if none is selected"}; + commands["selectAnimationPoint"] = {[this, pointIndex](args a) { + if (const auto i = pointIndex("selectAnimationPoint", a[0])) { + mDisplay->selectAnimationPoint(*i); + } + }, + "i", "Select an animation point (-1: none): loads its settings and camera, later changes of the light settings are stored in it"}; + auto fileCommand = [this](std::string GPUDisplayOptions::* file, void (GPUDisplay::*function)()) { // Sets the file name option (as in the GUI) and runs the function + return [this, file, function](args a) { + mDisplay->mCfgO.*file = std::get(a[0]); + (mDisplay->*function)(); + }; + }; + commands["saveCamera"] = {fileCommand(&GPUDisplayOptions::glPosFile, &GPUDisplay::SaveGLPos), "s", "Save the camera position to the file"}; + commands["loadCamera"] = {fileCommand(&GPUDisplayOptions::glPosFile, &GPUDisplay::LoadGLPos), "s", "Load the camera position from the file"}; + commands["saveAnimation"] = {fileCommand(&GPUDisplayOptions::glAnimationFile, &GPUDisplay::SaveGLAnimation), "s", "Save the animation path to the file"}; + commands["loadAnimation"] = {fileCommand(&GPUDisplayOptions::glAnimationFile, &GPUDisplay::LoadGLAnimation), "s", "Load the animation path from the file"}; + commands["moveCamera"] = {[this, floatNumber](args a) { + const float xyz[3] = {mDisplay->mXYZ[0] + floatNumber(a[0]), mDisplay->mXYZ[1] + floatNumber(a[1]), mDisplay->mXYZ[2] + floatNumber(a[2])}; + const float angles[3] = {mDisplay->mAngle[0], mDisplay->mAngle[1], mDisplay->mAngle[2]}; + mDisplay->setCamera(xyz, angles); + }, + "ddd", "Move the camera by dx dy dz (global coordinates)"}; + commands["rotateCamera"] = {[this, floatNumber](args a) { + const float angles[3] = {floatNumber(a[0]), floatNumber(a[1]), floatNumber(a[2])}; + mDisplay->rotateCamera(angles); + }, + "ddd", "Rotate the camera relative to the view, as the keyboard: yaw (right), pitch (down), roll (left) in degrees"}; + commands["lookAt"] = {[this, floatNumber](args a) { + const float target[3] = {floatNumber(a[0]), floatNumber(a[1]), floatNumber(a[2])}; + if (!mDisplay->lookAt(target)) { + mConsole->consolePrint("lookAt: the target is the camera position"); + } + }, + "ddd", "Turn the camera to the point x y z, keeping its position"}; + static constexpr std::string_view groups = "lhroc"; // Order of GPUDisplayConfiguration::configStructs + auto optionName = [this](uint32_t i) { return std::string(1, groups[mConfiguration->rttiEntries[i].configStructIndex]) + "." + mConfiguration->rttiEntries[i].name; }; + auto findOptions = [this](const std::string& name, bool allowGroups) -> std::vector { // Indices of the option l.name, with allowGroups also of the group l / h / r / o / c or all; empty if not found + const size_t group = name.size() && (name.size() == 1 ? allowGroups : name[1] == '.') ? groups.find(name[0]) : std::string_view::npos; + std::vector retVal; + for (uint32_t i = 0; i < mConfiguration->rttiEntries.size(); i++) { + const auto& e = mConfiguration->rttiEntries[i]; + if ((allowGroups && name == "all") || (e.configStructIndex == group && (name.size() == 1 || e.name == name.substr(2)))) { + retVal.emplace_back(i); + } + } + return retVal; + }; + auto controlState = [this](uint32_t i) { return mConfiguration->rttiEntries[i].configStructIndex == GPUDisplayConfiguration::configStructIndex(); }; // Control state and actions (mCfgC): not saved, not reset with a group + + auto defaultValue = [this](uint32_t i) -> std::optional { // Default from the RTTI, as setValue expects it, nullopt if it is not a number (e.g. GPUDisplayMultiSliderValue) + const auto& e = mConfiguration->rttiEntries[i]; + if (e.type == "std::string" || e.type == "bool") { + return e.defaultValue; + } + int64_t intValue = 0; + const char* end = e.defaultValue.c_str() + e.defaultValue.size(); + if (std::from_chars(e.defaultValue.c_str(), end, intValue).ptr == end) { // Integers exactly, not through double + return intValue; + } + double doubleValue = 0.; + if (const auto r = std::from_chars(e.defaultValue.c_str(), end, doubleValue); r.ec == std::errc() && r.ptr == end) { + return doubleValue; + } + return std::nullopt; + }; + auto toText = [](const GPUDisplayConsole::consoleArg& a) { // Argument as text + return std::visit([](const auto& v) { + if constexpr (std::is_same_v, std::string>) { + return v; + } else { + return qConfig::print_type(v); + } + }, + a); + }; + + commands["set"] = {[this, findOptions](args a) { + const std::string& name = std::get(a[0]); + const auto index = findOptions(name, false); + if (index.empty()) { + mConsole->consolePrint("set: unknown option %s, expected l. / h. / r. / o. / c. and the name", name.c_str()); + return; + } + if (const std::string error = mConfiguration->setValue(*mDisplay, index[0], a[1]); error.size()) { + mConsole->consolePrint("set %s: %s", name.c_str(), error.c_str()); + } // Quiet: no info text, no GUI setter + }, + "sa", "Set an option: l. / h. / r. / o. / c. (light, heavy, renderer, options, control) and its name, value"}; + commands["print"] = {[this, findOptions, optionName](args a) { + const std::string& name = std::get(a[0]); + const auto index = findOptions(name, true); + if (index.empty()) { + mConsole->consolePrint("print: unknown option %s, expected all, l / h / r / o / c, or l. / h. / r. / o. / c. and an option name", name.c_str()); + return; + } + for (uint32_t i : index) { + const auto value = mConfiguration->valueString(*mDisplay, i); + mConsole->consolePrint("%s = %s", optionName(i).c_str(), value ? value->c_str() : "(type not supported)"); + } + }, + "s", "Print an option (l. / h. / r. / o. / c. and its name), a group (l / h / r / o / c), or all"}; + commands["reset"] = {[this, findOptions, controlState, defaultValue, optionName](args a) { + const std::string& name = std::get(a[0]); + const auto index = findOptions(name, true); + if (index.empty()) { + mConsole->consolePrint("reset: unknown option %s, expected all, l / h / r / o / c, or l. / h. / r. / o. / c. and an option name", name.c_str()); + return; + } + for (uint32_t i : index) { + if (controlState(i)) { // Transient, no settings + if (index.size() == 1) { + mConsole->consolePrint("reset %s: control state, not resettable", optionName(i).c_str()); + } + continue; + } + const auto value = defaultValue(i); + if (const std::string error = value ? mConfiguration->setValue(*mDisplay, i, *value) : "default is not a number, not resettable"; error.size()) { + mConsole->consolePrint("reset %s: %s", optionName(i).c_str(), error.c_str()); + } // Quiet as set + } + }, + "s", "Reset an option, a group (l / h / r / o), or all to the defaults, not the control state c"}; + commands["describe"] = {[this, findOptions, optionName](args a) { + const auto index = findOptions(std::get(a[0]), false); + if (index.empty()) { + mConsole->consolePrint("describe: unknown option %s", std::get(a[0]).c_str()); + return; + } + const auto& e = mConfiguration->rttiEntries[index[0]]; + const auto value = mConfiguration->valueString(*mDisplay, index[0]); + std::string keys; + for (uint32_t k = 0; k < mConfiguration->configKeys.size(); k++) { + if (mConfiguration->keyRTTIIndex(k) == index[0]) { + keys += (keys.empty() ? "[" : " [") + mConfiguration->getKeyName(k) + "]"; + } + } + const bool inGUI = std::any_of(mConfiguration->guiElements.begin(), mConfiguration->guiElements.end(), [&](const auto& g) { return g.rttiIndex == index[0]; }); + mConsole->consolePrint("%s (%s): %s", optionName(index[0]).c_str(), e.type.c_str(), e.description.empty() ? "no description" : e.description.c_str()); + mConsole->consolePrint(" value %s, default %s%s%s%s%s", value ? value->c_str() : "(type not supported)", e.defaultValue.c_str(), e.min.size() ? ", min " : "", e.min.c_str(), e.max.size() ? ", max " : "", e.max.c_str()); + mConsole->consolePrint(" keys %s, %s", keys.empty() ? "none" : keys.c_str(), inGUI ? "in the Qt GUI" : "not in the Qt GUI"); + }, + "s", "Describe an option (l. / h. / r. / o. / c. and its name): type, description, value, default, range, keys, GUI"}; + commands["find"] = {[this, optionName](args a) { + auto lower = [](std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + return s; + }; + const std::string text = lower(std::get(a[0])); + uint32_t n = 0; + for (uint32_t i = 0; i < mConfiguration->rttiEntries.size(); i++) { + const auto& e = mConfiguration->rttiEntries[i]; + if (lower(e.name).find(text) != std::string::npos || lower(e.description).find(text) != std::string::npos) { + mConsole->consolePrint("%s: %s", optionName(i).c_str(), e.description.c_str()); + n++; + } + } + mConsole->consolePrint("find: %u options", n); + }, + "s", "Find options whose name or description contains the text (case insensitive)"}; + commands["saveSettings"] = {[this, controlState, optionName, writeFile](args a) { + writeFile("saveSettings", std::get(a[0]), "Settings and camera saved to", [&](std::ostream& out) { + for (uint32_t i = 0; i < mConfiguration->rttiEntries.size(); i++) { + const auto value = mConfiguration->valueString(*mDisplay, i, true); + if (!value || controlState(i)) { + continue; + } else if (value->find_first_of("\r\n") != std::string::npos) { + mConsole->consolePrint("saveSettings: %s not saved, scripts cannot contain line breaks", optionName(i).c_str()); + continue; + } + const bool isString = mConfiguration->rttiEntries[i].type == "std::string"; + std::string text = value->empty() ? "\"\"" : ""; + for (size_t k = 0; k < value->size(); k++) { // Escaped: a single argument, and plain text (a string) for string options also if it looks like a number + const char c = (*value)[k]; + text += (c == ' ' || c == '"' || c == '\'' || c == '\\' || (isString && k == 0) ? "\\" : "") + std::string(1, c); + } + out << "set " << optionName(i) << " " << text << "\n"; + } + const float toDeg = 180.f / CAMath::Pi(); + out << std::setprecision(std::numeric_limits::max_digits10); // Camera position exact, angles up to the conversion to degrees + out << "setXYZ " << mDisplay->mXYZ[0] << " " << mDisplay->mXYZ[1] << " " << mDisplay->mXYZ[2] << "\n"; + out << "setCameraAngles " << mDisplay->mAngle[1] * toDeg << " " << mDisplay->mAngle[0] * toDeg << " " << mDisplay->mAngle[2] * toDeg << "\n"; + }); + }, + "s", "Save all settings and the camera as console script (run it with runScript)"}; + commands["waitFrames"] = {[this](args a) { mConsole->waitFrames(std::max(std::get(a[0]), 0)); }, "i", "In a script: run the following lines after n frames"}; + commands["waitSeconds"] = {[this](args a) { mConsole->waitSeconds(std::get(a[0])); }, "d", "In a script: run the following lines after t seconds"}; + commands["benchmark"] = {[this](args a) { mConsole->startBenchmark(std::max(std::get(a[0]), 1)); }, "i", "Measure the frame rate of the next n frames (FPS limit / VSync apply), a script continues afterwards"}; + commands["echo"] = {[this, toText](args a) { + std::string text; + for (const auto& arg : a) { + text += (text.empty() ? "" : " ") + toText(arg); + } + mConsole->consolePrint("%s", text.c_str()); + }, + "+", "Print the arguments"}; + commands["printCamera"] = {[this](args) { + const float toDeg = 180.f / CAMath::Pi(); + mConsole->consolePrint("Camera: x %f y %f z %f, yaw %f pitch %f roll %f", mDisplay->mXYZ[0], mDisplay->mXYZ[1], mDisplay->mXYZ[2], mDisplay->mAngle[1] * toDeg, mDisplay->mAngle[0] * toDeg, mDisplay->mAngle[2] * toDeg); + }, + "", "Print the camera position and direction (degrees)"}; + commands["printStatus"] = {[this](args) { mConsole->consolePrint("%s", mDisplay->mGUIInfoStatus.empty() ? "No status yet (o.printInfoText / o.printInfoTextGUI)" : mDisplay->mGUIInfoStatus.c_str()); }, "", "Print the status line (FPS, counts, camera), updated every second"}; + commands["unbindKey"] = {[this, parseKey, unbind](args a) { + const int32_t key = parseKey(a[0]); + if (key < 0) { + mConsole->consolePrint("unbindKey: invalid key, expected a key code, a character, or a key name"); + return; + } else if (mConfiguration->configKeys[key] == GPUDisplayConfiguration::KEY_UNBOUND) { + mConsole->consolePrint("unbindKey: key [%s] is not bound", mConfiguration->getKeyName(key).c_str()); + return; + } + unbind(key); + }, + "a", "Remove the binding of a key (action or key down mapping)"}; + commands["listKeys"] = {[this](args) { listKeys([this](const std::string& line) { mConsole->consolePrint("%s", line.c_str()); }); }, "", "List the key bindings, and actions / key down mappings without key"}; + commands["printHelpText"] = {[this](args) { + for (const auto& line : getHelpText()) { + mConsole->consolePrint("%s", line.c_str()); + } + }, + "", "Print the help text of the keys (as h), with the current key bindings"}; + commands["unbindAll"] = {[this](args) { + mConfiguration->unbindAllKeys(); + mKeyDownMap.fill(0); + }, + "", "Remove all key bindings (actions and key down mappings, also the key of the console)"}; + commands["saveKeyBindings"] = {[this, writeFile](args a) { + writeFile("saveKeyBindings", std::get(a[0]), "Key bindings saved to", [&](std::ostream& out) { + out << "unbindAll\n"; + for (uint32_t k = 1; k < mConfiguration->configKeys.size(); k++) { + if (const int32_t binding = mConfiguration->configKeys[k]; binding != GPUDisplayConfiguration::KEY_UNBOUND) { + const std::string key = mConfiguration->getKeyName(k); + out << "bindKey " << (key.size() == 1 ? "\\" : "") << key << " " << mConfiguration->bindingName(binding) << "\n"; // Characters escaped: plain text, also digits, space, quotes + } + } + }); + }, + "s", "Save all key bindings as console script: unbindAll, then bindKey for each key (run it with runScript)"}; + commands["resetView"] = {[this](args) { + mDisplay->mCfgC.resetScene.store(true); + showConfigInfo(mConfiguration->getRTTIIndex(*mDisplay, &GPUDisplayControl::resetScene)); + }, + "", "Reset the view"}; + commands["history"] = {[this, writeFile](args a) { + writeFile("history", std::get(a[0]), "History saved to", [&](std::ostream& out) { + for (const auto& line : mConsole->mConsoleHistory) { // Title, commands, and their output (indented) + out << line << "\n"; + } + }); + }, + "s", "Save the console history (commands and output) to a file"}; +} + +#endif // GPUDISPLAYFRONTENDCONFIGCONSOLE_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc new file mode 100644 index 0000000000000..a5f84063409e2 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -0,0 +1,126 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigInfo.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGINFO_INC +#define GPUDISPLAYFRONTENDCONFIGINFO_INC + +#define INFO_LAMBDA(obj, value, ...) std::pair(mFrontend->mConfiguration->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->mConfiguration->configInfos[info.first] = std::move(info.second); }; + static constexpr std::tuple noInfo{&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->mConfiguration->configInfos[mFrontend->mConfiguration->getRTTIIndex(*this, member)] = []() {}), ...); }, noInfo); + addInfo(INFO_LAMBDA(mCfgC, displayControl, if (val == 2) { SetInfo("Exiting", 1); })); + addInfo(INFO_LAMBDA(mCfgC, resetScene, SetInfo("View reset", 1);)); + addInfo(INFO_LAMBDA(mCfgC, requestScreenshot, SetInfo("Taking screenshot (%s)", mScreenshotFile.c_str());)); + addInfo(INFO_LAMBDA(mCfgO, animateScreenshot, SetInfo("Writing screenshots from animation set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgO, animationChangeConfig, SetInfo("Interpolating visualization settings during Animation %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgO, printInfoTextAlways, SetInfo(val ? "Showing help text until disabled" : (mInfoHelpTimer.IsRunning() ? "Showing help text" : "Showing help text disabled"), 1);)); + addInfo(INFO_LAMBDA(mCfgO, printInfoText, SetInfo("Info text display - console: %s, onscreen %s", (val & 2) ? "enabled" : "disabled", (val & 1) ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, colorClusters, SetInfo("Color coding for seed / trrack attachmend %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, drawSector, { + if (val == -1) { + SetInfo("Showing all sectors", 1); + } else { + SetInfo("Showing sector %d", val); + } + })); + addInfo(INFO_LAMBDA(mCfgL, drawRelatedSectors, SetInfo("Drawing of related sectors %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, xzAdd, SetInfo("TPC sector separation: %f", mCfgH.xzAdd);)); + addInfo(INFO_LAMBDA(mCfgL, animationMode, SetInfo("Animation mode set to %s", ANIMATION_MODE_NAMES_LONG[val].c_str());)); + addInfo(INFO_LAMBDA(mCfgL, drawGrid, SetInfo("Fast Cluster Search Grid %s", val ? "shown" : "hidden");)); + addInfo(INFO_LAMBDA(mCfgH, projectXY, SetInfo("Projection onto xy plane %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, excludeClusters, SetInfo(val ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", val);)); + addInfo(INFO_LAMBDA(mCfgH, hideRejectedTracks, SetInfo("Rejected tracks are %s", val ? "hidden" : "shown");)); + addInfo(INFO_LAMBDA(mCfgH, markClusters, { + SetInfo("Cluster flag highlight mask set to %d (%s)", val, val == 0 ? "off" : (val == 1 ? "split pad" : (val == 2 ? "split time" : (val == 4 ? "edge" : (val == 8 ? "singlePad" : (val == 0x10 ? "reject distance" : "reject error")))))); + })); + addInfo(INFO_LAMBDA(mCfgH, markFakeClusters, SetInfo("Marking fake clusters: %s", val ? "on" : "off");)); + addInfo(INFO_LAMBDA(mCfgH, markAdjacentClusters, { + if (val == 33) { + SetInfo("Marking protected clusters (%d)", val); + } else if (val == 32) { + SetInfo("Marking removable clusters (%d)", val); + } else { + SetInfo("Marking adjacent clusters (%d): rejected %s, tube %s, looper leg %s, low Pt %s, high incl %s", val, (val & 1) ? "yes" : " no", (val & 2) ? "yes" : " no", (val & 4) ? "yes" : " no", (val & 8) ? "yes" : " no", (val & 16) ? "yes" : " no"); + } + })); + addInfo(INFO_LAMBDA(mCfgL, showCollision, { + if (val == -1) { + SetInfo("Showing all collisions", 1); + } else { + SetInfo("Showing collision %d / %d", val, mNCollissions); + } + })); + addInfo(INFO_LAMBDA(mCfgL, colorCollisions, SetInfo("Color coding of collisions %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, hideRejectedClusters, SetInfo("Rejected clusters are %s", val ? "hidden" : "shown");)); + addInfo(INFO_LAMBDA(mCfgH, separateExtrapolatedTracks, SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", val ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgH, trackFilter, SetInfo("Track filter: %s", val == 0 ? "None" : trackFilterName().c_str());)); + addInfo(INFO_LAMBDA(mCfgL, propagateTracks, { + const char* infoText[] = {"Hits connected", "Hits connected and propagated to vertex", "Reconstructed track propagated inwards and outwards", "Monte Carlo track"}; + SetInfo("Display of propagated tracks: %s", infoText[val]); + })); + addInfo(INFO_LAMBDA(mCfgH, propagateLoopers, SetInfo("Propagation of loopers %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, screenshotScaleFactor, SetInfo("Screenshot scaling factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgL, smoothPoints, SetInfo("Smoothing of points %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, smoothLines, SetInfo("Smoothing of lines %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, depthBuffer, SetInfo("Depth buffer (z-buffer) %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, drawQualityMSAA, SetInfo("Multisampling anti-aliasing factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgR, drawQualityDownsampleFSAA, SetInfo("Downsampling anti-aliasing factor set to %d", val);)); + addInfo(INFO_LAMBDA(mCfgR, drawQualityVSync, SetInfo("VSync: %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, fpsLimit, { + if (val == GPUDisplayBackend::FPS_LIMIT_RATE) { + SetInfo("FPS limit: %d FPS", mCfgR.fpsRate); + } else if (val == GPUDisplayBackend::FPS_LIMIT_VSYNC) { + char rate[32] = "unknown rate"; + if (mFrontend->getRefreshRate() > 0.f) { + snprintf(rate, sizeof(rate), "%.1f Hz", mFrontend->getRefreshRate()); + } + const int32_t mode = mBackend->fpsLimitMode(); + if (mode == GPUDisplayBackend::FPS_LIMIT_NONE) { + SetInfo("FPS limit: refresh rate (%s) - VSync disabled, no limit", rate); + } else if (mode == GPUDisplayBackend::FPS_LIMIT_RATE) { + SetInfo("FPS limit: refresh rate (%s) - not supported by the backend, fallback to %d FPS", rate, mCfgR.fpsRate); + } else { + SetInfo("FPS limit: refresh rate (%s)", rate); + } + } else { + SetInfo("FPS limit: none"); + } + })); + addInfo(INFO_LAMBDA(mCfgR, fullScreen, SetInfo("Toggling full screen (%d)", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgR, maximized, SetInfo("Toggling Maximized window (%d)", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgR, fov, SetInfo("Set FOV to %f", val);)); + addInfo(INFO_LAMBDA(mCfgR, useGLIndirectDraw, SetInfo("OpenGL Indirect Draw %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgL, drawTPC, SetInfo("Showing TPC Clusters: %d", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgL, drawTRD, SetInfo("Showing TRD Tracklets: %d", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgL, drawTOF, SetInfo("Showing TOF Hits: %d", (int32_t)val);)); + addInfo(INFO_LAMBDA(mCfgL, drawITS, SetInfo("Showing ITS Clusters: %d", (int32_t)val);)); + auto trackFilterMaskInfo = [this]() { SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); }; + addInfo(INFO_LAMBDA(mCfgH, drawTPCTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawTRDTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawTOFTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawITSTracks, trackFilterMaskInfo();)); + addInfo(INFO_LAMBDA(mCfgH, drawTracksAndFilter, SetInfo("Track filter: %s", val ? "AND" : "OR");)); + addInfo(INFO_LAMBDA(mCfgR, camLookOrigin, SetInfo("Camera locked on origin: %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, camYUp, SetInfo("Camera locked on y-axis facing upwards: %s", val ? "enabled" : "disabled");)); + addInfo(INFO_LAMBDA(mCfgR, cameraMode, { + const char* modeText[] = {"Descent (free movement)", "Focus locked on origin (y-axis forced upwards)", "Spectator (y-axis forced upwards)", "Focus locked on origin (with free rotation)"}; + SetInfo("Camera mode %d: %s", val, modeText[val]); + })); +} + +#endif // GPUDISPLAYFRONTENDCONFIGINFO_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc new file mode 100644 index 0000000000000..964b50f194511 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -0,0 +1,315 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigKeys.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGKEYS_INC +#define GPUDISPLAYFRONTENDCONFIGKEYS_INC + +#define KEY_DOWN_MAP(character, id) GPUDisplayFrontend::GPUDisplayConfiguration::keyDownMapEntry(character, GPUDisplayFrontend::KEY_MAP_##id) // Key queried as held key: GPUDisplay uses mKeys[mKeyDownMap[KEY_MAP_...]] +#define KEY_LAMBDA(character, actionName, obj, value, ...) GPUDisplayFrontend::GPUDisplayConfiguration::keyLambdaEntry(character, actionName, mFrontend->mConfiguration->getRTTIIndex(*this, &std::remove_cvref_t::value), configKeyCallback([this]() { [[maybe_unused]] auto& val = obj.value; __VA_ARGS__ })) +#define KEY_ACTION(character, actionName, ...) GPUDisplayFrontend::GPUDisplayConfiguration::keyLambdaEntry(character, actionName, GPUDisplayFrontend::GPUDisplayConfiguration::INVALID_INDEX, configKeyCallback([this]() { __VA_ARGS__ })) // Key function without config option + +void GPUDisplay::buildConfigKeys() +{ + mFrontend->mConfiguration->helpTextEntries.emplace_back("[MOUSE 1] / [MOUSE 2] / [MOUSE 1+2]", "Look around / Strafe camera / Zoom / Rotate", std::vector()); + mFrontend->mConfiguration->helpTextEntries.emplace_back("[SHIFT]", "Slow Mouse Zoom / Move / Rotate", std::vector()); + + mFrontend->add_key_binding( + KEY_DOWN_MAP('w', FORWARD), + KEY_DOWN_MAP('s', BACKWARD), + KEY_DOWN_MAP('a', STRAFE_LEFT), + KEY_DOWN_MAP('d', STRAFE_RIGHT), // + "Zoom / Strafe Left and Right"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_PAGEUP, STRAFE_UP), + KEY_DOWN_MAP(mFrontend->KEY_PAGEDOWN, STRAFE_DOWN), + KEY_DOWN_MAP('e', ROTATE_LEFT), + KEY_DOWN_MAP('f', ROTATE_RIGHT), // + "Strafe up / down / Rotate left / right"); + + mFrontend->add_key_binding( + KEY_LAMBDA(mFrontend->KEY_ESCAPE, "QUIT", mCfgC, displayControl, val.store(2);), + KEY_LAMBDA('n', "NEXT_EVENT", mCfgC, displayControl, val.store(1);), + KEY_LAMBDA('r', "RESET_SCENE", mCfgC, resetScene, val.store(true);), // + KEY_ACTION('q', "TOGGLE_GUI", { + if (mFrontend->isGUIRunning()) { + SetInfo("Stopping GUI", 1); + mFrontend->stopGUI(); + } else { + SetInfo("Starting GUI", 1); + mFrontend->startGUI(); + } + }), // + "Quit / Next event / Reset Display Settings / Start / Stop Qt GUI"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_LEFT, ROTATE_YAW_LEFT), + KEY_DOWN_MAP(mFrontend->KEY_RIGHT, ROTATE_YAW_RIGHT), + KEY_DOWN_MAP(mFrontend->KEY_UP, ROTATE_PITCH_UP), + KEY_DOWN_MAP(mFrontend->KEY_DOWN, ROTATE_PITCH_DOWN), // + "Turn camera left / right / up / down"); + + mFrontend->add_key_binding( + KEY_DOWN_MAP(mFrontend->KEY_ALT, FOCUS_ORIGIN), + KEY_DOWN_MAP(mFrontend->KEY_CTRL, FOCUS_ORIENT_UP), // + KEY_LAMBDA(mFrontend->KEY_ALT + mFrontend->SPECIAL_KEYS_SHIFTED, "CAM_LOOK_ORIGIN", mCfgR, camLookOrigin, { + val ^= 1; + mCfgR.cameraMode = val + 2 * mCfgR.camYUp; + }), + KEY_LAMBDA(mFrontend->KEY_CTRL + mFrontend->SPECIAL_KEYS_SHIFTED, "CAM_YUP", mCfgR, camYUp, { + val ^= 1; + mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * val; + }), + KEY_LAMBDA(mFrontend->KEY_ENTER, "CAMERA_MODE", 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', "FULL_SCREEN", mCfgR, fullScreen, val ^= 1;), // + KEY_LAMBDA('_', "MAXIMIZED", mCfgR, maximized, val ^= 1;), // + KEY_LAMBDA('b', "FOV", mCfgR, fov, { + if ((val += 5) > 175) { + val = 5; + } + }), + KEY_LAMBDA('I', "USE_GLINDIRECT_DRAW", mCfgR, useGLIndirectDraw, val ^= true;), // + "Switch Full Screen / Maximized window / Change FOV (field of view) / Enable / disable GL indirect draw"); + + mFrontend->add_key_binding( + KEY_LAMBDA('S', "SMOOTH_POINTS", mCfgL, smoothPoints, val ^= true;), // + KEY_LAMBDA('A', "SMOOTH_LINES", mCfgL, smoothLines, val ^= true;), // + KEY_LAMBDA('D', "DEPTH_BUFFER", mCfgL, depthBuffer, val ^= true;), // + "Enable or disable smoothing of points / smoothing of lines / depth buffer"); + + mFrontend->add_key_binding( + KEY_LAMBDA('W', "DRAW_QUALITY_MSAA", mCfgR, drawQualityMSAA, { + val *= 2; + if (val < 2) { + val = 2; + } + if (val > 16 || val > mBackend->getMaxMSAA()) { + val = 0; + } + }), + KEY_LAMBDA('U', "DRAW_QUALITY_DOWNSAMPLE_FSAA", mCfgR, drawQualityDownsampleFSAA, { + val++; + if (val == 1) { + val = 2; + } + if (val == 5) { + val = 0; + } + }), + KEY_LAMBDA('V', "DRAW_QUALITY_VSYNC", mCfgR, drawQualityVSync, val ^= true;), // + KEY_LAMBDA('R', "FPS_LIMIT", 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('#', "INVERT_COLORS", mCfgL, invertColors, val ^= 1;), + KEY_LAMBDA('g', "DRAW_GRID", mCfgL, drawGrid, val ^= 1;), + KEY_LAMBDA('i', "PROJECT_XY", 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', "DRAW_SECTOR_NEXT", mCfgL, drawSector, { + if (val >= (mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1))) { + val = -1; + } else { + val++; + } + }), + KEY_LAMBDA('k', "DRAW_SECTOR_PREVIOUS", mCfgL, drawSector, { + if (val <= -1) { + val = mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1); + } else { + val--; + } + }), + KEY_LAMBDA('J', "DRAW_RELATED_SECTORS", mCfgL, drawRelatedSectors, val ^= 1;), // + KEY_LAMBDA(';', "XZ_ADD", mCfgH, xzAdd, val = std::min(val + 60, 300.f);), // Up to the option maximum, reset with the scene + "Draw single sector (next / previous sector), draw related sectors (same plane in phi) / Show splitting of TPC in sectors by extruding volume"); + + mFrontend->add_key_binding( + KEY_LAMBDA('x', "EXCLUDE_CLUSTERS", mCfgL, excludeClusters, val = (val + 1) % 3;), + KEY_LAMBDA('.', "HIDE_REJECTED_TRACKS", mCfgH, hideRejectedTracks, val ^= 1;), // + "Exclude Clusters used in the tracking steps enabled for visualization ([1]-[8]) / Exclude rejected tracks"); + + mFrontend->add_key_binding( + KEY_LAMBDA('c', "MARK_CLUSTERS", mCfgH, markClusters, { + if (val == 0) { + val = 1; + } else if (val >= 0x20) { + val = 0; + } else { + val <<= 1; + } + }), + KEY_LAMBDA('z', "MARK_FAKE_CLUSTERS", mCfgH, markFakeClusters, val ^= 1;), // + KEY_LAMBDA('B', "MARK_ADJACENT_CLUSTERS", mCfgH, markAdjacentClusters, { + val++; + if (val == 5) { + val = 7; + } + if (val == 9) { + val = 15; + } + if (val == 17) { + val = 31; + } + if (val == 34) { + val = 0; + } + }), // + "Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20 / Mark fake attached clusters / Mark adjacent clusters"); + + mFrontend->add_key_binding( + KEY_LAMBDA('v', "HIDE_REJECTED_CLUSTERS", mCfgH, hideRejectedClusters, val ^= 1;), + KEY_LAMBDA('j', "SEPARATE_EXTRAPOLATED_TRACKS", mCfgH, separateExtrapolatedTracks, val ^= 1;), + KEY_LAMBDA('m', "SPLIT_CETRACKS", mCfgH, splitCETracks, val ^= 1;), + KEY_LAMBDA('u', "TRACK_FILTER", mCfgH, trackFilter, val = (val + 1) % (mConfig.filterMacros.size() + 1);), // + "Hide rejected clusters from tracks / Show tracks segments propagated to adjacent sector in different color / splt CE tracks / Cycle through track filter"); + + mFrontend->add_key_binding( + KEY_LAMBDA('E', "PROPAGATE_TRACKS", mCfgL, propagateTracks, { + val += 1; + if (val == 4) { + val = 0; + } + }), + KEY_LAMBDA('G', "PROPAGATE_LOOPERS", mCfgH, propagateLoopers, val ^= 1;), // + "Extrapolate tracks / loopers"); + + mFrontend->add_key_binding( + KEY_LAMBDA('L', "SHOW_COLLISION_NEXT", mCfgL, showCollision, { + if (val >= mNCollissions - 1) { + val = -1; + } else { + val++; + } + }), + KEY_LAMBDA('K', "SHOW_COLLISION_PREVIOUS", mCfgL, showCollision, { + if (val <= -1) { + val = mNCollissions - 1; + } else { + val--; + } + }), + KEY_LAMBDA('C', "COLOR_COLLISIONS", mCfgL, colorCollisions, val ^= 1;), // + "Draw single collisions (next / previous) / Colorcode clusters of different collisions"); + + mFrontend->add_key_binding( + KEY_LAMBDA('t', "REQUEST_SCREENSHOT", mCfgC, requestScreenshot, takeScreenshot();), + KEY_LAMBDA('T', "ANIMATE_SCREENSHOT", mCfgO, animateScreenshot, val ^= 1;), // + KEY_LAMBDA('Z', "SCREENSHOT_SCALE_FACTOR", mCfgR, screenshotScaleFactor, { + val += 1; + if (val > 4) { + val = 1; + } + }), // + "Take Screenshot / Record Animation to pictures / Change screenshot resolution (scaling factor)"); + + mFrontend->add_key_binding( + KEY_ACTION('y', "START_ANIMATION", runAnimation();), + KEY_ACTION('Y', "ADD_ANIMATION_POINT", setAnimationPoint(); SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimationVectors.size(), mCfgC.animationPoints.values.back());), // + KEY_ACTION('\'', "REMOVE_ANIMATION_POINT", removeAnimationPoint(); SetInfo("Removed Animation point", 1);), // + KEY_ACTION('X', "RESET_ANIMATION", resetAnimation(); SetInfo("Reset Animation points", 1);), // + KEY_LAMBDA('M', "ANIMATION_MODE", mCfgL, animationMode, { + val++; + if (val == 7) { + val = 0; + } + }), // + "Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode"); + + mFrontend->add_key_binding( + KEY_LAMBDA('>', "ANIMATION_CHANGE_CONFIG", mCfgO, animationChangeConfig, val ^= 1;), + KEY_DOWN_MAP('<', CHANGE_ANIMATION_INTERVAL), // + "Toggle config interpolation during Animation / change Animation interval (via movement)"); + + mFrontend->add_key_binding( + KEY_ACTION('o', "SAVE_CAMERA", SaveGLPos();), + KEY_ACTION('p', "LOAD_CAMERA", LoadGLPos();), + KEY_ACTION('O', "SAVE_ANIMATION", SaveGLAnimation();), + KEY_ACTION('P', "LOAD_ANIMATION", LoadGLAnimation();), + "Save / restore current camera position / Animation path"); + + mFrontend->add_key_binding( + KEY_LAMBDA('1', "DRAW_CLUSTERS", mCfgL, drawClusters, val ^= 1;), + KEY_LAMBDA('2', "DRAW_INIT_LINKS", mCfgL, drawInitLinks, val ^= 1;), + KEY_LAMBDA('3', "DRAW_LINKS", mCfgL, drawLinks, val ^= 1;), + KEY_LAMBDA('4', "DRAW_SEEDS", mCfgL, drawSeeds, val ^= 1;), + KEY_LAMBDA('5', "DRAW_TRACKLETS", mCfgL, drawTracklets, val ^= 1;), + KEY_LAMBDA('6', "DRAW_TRACKS", mCfgL, drawTracks, val ^= 1;), + KEY_LAMBDA('7', "DRAW_EXTRAPOLATED_TRACKS", mCfgL, drawExtrapolatedTracks, val ^= 1;), + KEY_LAMBDA('8', "DRAW_FINAL", mCfgL, drawFinal, val ^= 1;), + KEY_LAMBDA('N', "COLOR_CLUSTERS", 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, "DRAW_TPC", mCfgL, drawTPC, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F2, "DRAW_TRD", mCfgL, drawTRD, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F3, "DRAW_TOF", mCfgL, drawTOF, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F4, "DRAW_ITS", 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, "DRAW_TPCTRACKS", mCfgH, drawTPCTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F2 + mFrontend->SPECIAL_KEYS_SHIFTED, "DRAW_TRDTRACKS", mCfgH, drawTRDTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F3 + mFrontend->SPECIAL_KEYS_SHIFTED, "DRAW_TOFTRACKS", mCfgH, drawTOFTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F4 + mFrontend->SPECIAL_KEYS_SHIFTED, "DRAW_ITSTRACKS", mCfgH, drawITSTracks, val ^= 1;), + KEY_LAMBDA(mFrontend->KEY_F12 + mFrontend->SPECIAL_KEYS_SHIFTED, "DRAW_TRACKS_AND_FILTER", mCfgH, drawTracksAndFilter, val ^= 1;), // + "Enable / disable track detector filter / SHIFT F12: Switch track detector filter between AND and OR mode", + "[SHIFT] + [F1] to [F4] / [F12]"); + + mFrontend->add_key_binding( + KEY_LAMBDA('h', "PRINT_INFO_TEXT_ALWAYS", mCfgO, printInfoTextAlways, { + if (val) { + val = false; + } else if (mInfoHelpTimer.IsRunning()) { + val = true; + mInfoHelpTimer.Reset(); + } else { + PrintHelp(); + } + }), + KEY_LAMBDA('H', "PRINT_INFO_TEXT", mCfgO, printInfoText, { + val += 1; + val &= 3; + }), // + "Print Help / Show info texts"); + + mFrontend->add_key_binding( + KEY_LAMBDA('^', "SHOW_CONSOLE", mCfgC, showConsole, val ^= 1;), // + "Show / hide the console"); +} + +#endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc new file mode 100644 index 0000000000000..70231f53514a8 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigQtGUI.inc @@ -0,0 +1,202 @@ +// 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_STEP(step) guiType(GPUDisplayGUIChannel::ElementType::SLIDER, {}, {}, 0, 0, step) // Range from the min / max of the option, positions multiples of step from min +#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_SLIDER_DYNAMIC_STEP(step, ...) guiType(GPUDisplayGUIChannel::ElementType::SLIDER, {}, __VA_ARGS__, 0, 0, step) // As GUI_SLIDER_DYNAMIC, positions multiples of step from min +#define GUI_DROPDOWN_DYNAMIC(...) guiType(GPUDisplayGUIChannel::ElementType::DROPDOWN, {}, __VA_ARGS__) // Choices from a lambda returning GPUDisplayGUIChannel::Options{.choices = ...}, as GUI_SLIDER_DYNAMIC +#define GUI_MULTI_SLIDER(step) guiType(GPUDisplayGUIChannel::ElementType::MULTI_SLIDER, {}, {}, 0, 0, step) // Several handles for a GPUDisplayMultiSliderValue: positions multiples of step, at least step apart, range 0 to its max; sent when a handle is released or clicked (selection) +#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, std::string, or GPUDisplayMultiSliderValue). Without body, guiVal is assigned to val. +#define GUI_LAMBDA(name, obj, value, type, ...) guiElement(std::string(name), mFrontend->mConfiguration->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); }) +#define GUI_ACTION(name, ...) guiElement(std::string(name), GPUDisplayFrontend::GPUDisplayConfiguration::INVALID_INDEX, GUI_BUTTON, [this](const GPUDisplayGUIValue&) { __VA_ARGS__ }, [](const GPUDisplayGUIChannel::Settings&) { return GPUDisplayGUIValue((int64_t)0); }) // Button without config option: runs the body + +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", mCfgO, rotateModel, GUI_CHECKBOX), + GUI_LAMBDA("Rotate around Beamline", mCfgO, rotateAroundZ, 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, 16)), + 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), + 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", mCfgO, filterMacroCustom, 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_ACTION("Save Camera", SaveGLPos();), + GUI_ACTION("Load Camera", LoadGLPos();), + GUI_ACTION("Save Animation", SaveGLAnimation();), + GUI_ACTION("Load Animation", LoadGLAnimation();), + GUI_LAMBDA("Camera File", mCfgO, glPosFile, GUI_TEXTBOX), + GUI_LAMBDA("Animation File", mCfgO, glAnimationFile, GUI_TEXTBOX), + "Load / Save", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Screenshot", mCfgC, requestScreenshot, GUI_BUTTON, takeScreenshot();), + GUI_ACTION("Animation", runAnimation();), + GUI_LAMBDA("Record Animation", mCfgO, animateScreenshot, GUI_DROPDOWN({"no", 0}, {"to screenshots", 1})), + GUI_LAMBDA("Filename", mCfgO, mscreenShotName, GUI_TEXTBOX), + guiVertical{true}, + GUI_LAMBDA("Scale Factor", mCfgR, screenshotScaleFactor, GUI_SLIDER), + GUI_LAMBDA("Animation Delay", mCfgO, animationDelay, GUI_SLIDER_DYNAMIC_STEP(0.05, []() { return guiRange(0.05f, 20.f); })), + guiVertical{false}, + GUI_ACTION("Add Point", setAnimationPoint(); SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimationVectors.size(), mCfgC.animationPoints.values.back());), + GUI_ACTION("Remove Point", removeAnimationPoint(); SetInfo("Removed Animation point", 1);), + GUI_ACTION("Reset Animation", resetAnimation(); SetInfo("Reset Animation points", 1);), + GUI_LAMBDA("Animation Mode", mCfgL, animationMode, GUI_DROPDOWN_NAMES(ANIMATION_MODE_NAMES_SHORT), guiAssign(val, guiVal); if (!mCfgO.guiPrintInfo) { SetInfo("Animation mode set to %s", ANIMATION_MODE_NAMES_LONG[val].c_str()); }), // clang-format off + GUI_LAMBDA("Apply to all", mCfgO, applyToAllPoints, GUI_CHECKBOX), + guiVertical{true}, + GUI_LAMBDA("Points", mCfgC, animationPoints, GUI_MULTI_SLIDER(0.05), setAnimationPointsFromGUI(std::get(guiVal));), // clang-format on + "Screenshots / Animation", true); + + mFrontend->add_gui_binding( + GUI_LAMBDA("Next Event", mCfgC, displayControl, GUI_BUTTON, val.store(1);), + GUI_LAMBDA("Exit", mCfgC, displayControl, GUI_BUTTON, val.store(2);), + GUI_LAMBDA("Reset", mCfgC, resetScene, GUI_BUTTON, val.store(true);), + GUI_LAMBDA("Reset GUI", mCfgO, guiScale, GUI_BUTTON, val = 1.f;), + GUI_ACTION("Close GUI", mFrontend->stopGUI();), + GUI_LAMBDA("SendInfoText", mCfgO, guiPrintInfo, GUI_CHECKBOX), + GUI_LAMBDA("GUIInfo", mCfgO, printInfoTextGUI, GUI_CHECKBOX), + GUI_LAMBDA("HelpText", mCfgO, printInfoTextAlways, GUI_CHECKBOX), + GUI_LAMBDA("StatusText", mCfgO, printInfoText, GUI_CHECKBOX_BIT(1)), // Bit 0: status line on screen (bit 1: in the console) + GUI_LAMBDA("DarkMode", mCfgO, guiDarkMode, GUI_CHECKBOX), + true); +} + +#endif // GPUDISPLAYFRONTENDCONFIGQTGUI_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index ac61ddacdd0f6..e7ad8150aec55 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -92,6 +92,15 @@ int32_t GPUDisplayFrontendGlfw::GetKey(int32_t key) if (key == GLFW_KEY_INSERT) { return (KEY_INSERT); } + if (key == GLFW_KEY_BACKSPACE) { + return (KEY_BACKSPACE); + } + if (key == GLFW_KEY_DELETE) { + return (KEY_DELETE); + } + if (key == GLFW_KEY_GRAVE_ACCENT) { // Key left of 1 (named after the US layout): console key (^), independent of the layout + return ('^'); + } if (key == GLFW_KEY_ENTER) { return (KEY_ENTER); } @@ -161,21 +170,22 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 int32_t handleKey = 0, keyPress = 0; GetKey(key, scancode, mods, handleKey, keyPress); if (handleKey < 32) { - if (action == GLFW_PRESS) { - me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = mods & GLFW_MOD_SHIFT; + if (action == GLFW_PRESS || action == GLFW_REPEAT) { // Repeat as the other frontends (e.g. backspace in the console), characters are repeated by char_callback + me->mKeys[keyPress] = keyPress == KEY_SHIFT || !me->consoleShown(); // Held keys only without console, SHIFT also with it (scrolling) me->HandleKey(handleKey); } else if (action == GLFW_RELEASE) { me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; } } else if (handleKey < 256) { if (action == GLFW_PRESS) { me->mLastKeyDown = handleKey; + if (key == GLFW_KEY_GRAVE_ACCENT) { // Console key, its character (if not a dead key) follows in the same glfwPollEvents + me->mConsoleKeyPressed = true; + me->HandleKey('^'); + } } else if (action == GLFW_RELEASE) { - keyPress = (uint8_t)me->mKeyDownMap[handleKey]; + keyPress = (uint8_t)me->mGLFWKeyDownMap[handleKey]; me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; } } } @@ -183,14 +193,17 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 void GPUDisplayFrontendGlfw::char_callback(GLFWwindow* window, uint32_t codepoint) { // GPUInfo("Key (char callback): %d %c - key: %d", codepoint, (char)codepoint, (int32_t)me->mLastKeyDown); + if (me->mConsoleKeyPressed) { // Character of the console key, handled in key_callback + me->mConsoleKeyPressed = false; + return; + } if (codepoint < 256) { uint8_t keyPress = codepoint; if (keyPress >= 'a' && keyPress <= 'z') { keyPress += 'A' - 'a'; } - me->mKeyDownMap[me->mLastKeyDown] = keyPress; - me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; + me->mGLFWKeyDownMap[me->mLastKeyDown] = keyPress; + me->mKeys[keyPress] = keyPress == KEY_SHIFT || !me->consoleShown(); // Held keys only without console, SHIFT also with it (scrolling) me->HandleKey(codepoint); } } @@ -275,7 +288,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_MIN_VERSION_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_MIN_VERSION_MINOR); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 0); - glfwWindowHint(GLFW_OPENGL_PROFILE, mBackend->CoreProfile() ? GLFW_OPENGL_CORE_PROFILE : GLFW_OPENGL_COMPAT_PROFILE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); } glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Initial size scaled by the content scale on X11 / Win32 mWindow = glfwCreateWindow(INIT_WIDTH, INIT_HEIGHT, DISPLAY_WINDOW_NAME, nullptr, nullptr); @@ -319,15 +332,9 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; // Only after successful initialization, StopDisplay waits for it to become false again - } - int32_t retVal = 0; while (!glfwWindowShouldClose(mWindow)) { - HandleExternalGUISignals(); - if (DrawGLScene()) { + if (HandleUpdatesAndDrawGLScene()) { fprintf(stderr, "Error drawing GL scene\n"); retVal = 1; break; // Cleanup below @@ -337,6 +344,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } waitForFPSLimit(); glfwPollEvents(); + mConsoleKeyPressed = false; // No character followed (dead key) if (mRestoreWindowedSize) { mRestoreWindowedSize = false; if (mWindowedWidth > 0 && mWindowedHeight > 0 && !glfwGetWindowAttrib(mWindow, GLFW_MAXIMIZED)) { @@ -348,10 +356,11 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() ExitDisplay(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); glfwDestroyWindow(mWindow); glfwTerminate(); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return retVal; @@ -362,10 +371,6 @@ void GPUDisplayFrontendGlfw::StopDisplayA() glfwSetWindowShouldClose(mWindow, true); } -void GPUDisplayFrontendGlfw::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -} - void GPUDisplayFrontendGlfw::SwitchFullscreen(bool set) { GPUInfo("Setting Full Screen %d", (int32_t)set); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index f079c87852e13..87a166c27f61f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -31,7 +31,6 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } float getRefreshRate() override; @@ -67,8 +66,9 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend int32_t mWindowY = 0; int32_t mWindowWidth = INIT_WIDTH; int32_t mWindowHeight = INIT_HEIGHT; - uint8_t mKeyDownMap[256] = {0}; + uint8_t mGLFWKeyDownMap[256] = {0}; uint8_t mLastKeyDown = 0; + bool mConsoleKeyPressed = false; // Console key pressed in this glfwPollEvents, skip its character in char_callback }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 368a86413194c..2dae9039d8d78 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -18,17 +18,18 @@ #include "GPUDisplayGUIWrapper.h" #include "GPULogging.h" #include +#include #include #include +#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include #include +#include #endif -#include using namespace o2::gpu; -static GPUDisplayFrontendGlut* me = nullptr; GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() { @@ -36,98 +37,84 @@ GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() mFrontendName = "GLUT"; } -void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, to get rid of the static me variable! +void GPUDisplayFrontendGlut::displayFunc() { - me->DrawGLScene(); + mRedisplayPending = false; + HandleUpdatesAndDrawGLScene(); glutSwapBuffers(); - me->waitForFPSLimit(); + waitForFPSLimit(); } void GPUDisplayFrontendGlut::glutLoopFunc() { - me->HandleExternalGUISignals(); - displayFunc(); + if (mRedisplayPending) { // Last redisplay not served (window not visible): handle updates without drawing + HandleUpdatesAndDrawGLScene(true); + std::unique_lock lock(mConfigUpdateMutex); // No swap waiting for VSync: block instead of spinning, woken by GUI updates + mConfigUpdateCV.wait_for(lock, std::chrono::milliseconds(100), [this] { return mConfigUpdatePending; }); + } + mRedisplayPending = true; + glutPostRedisplay(); // Drawing only in displayFunc, so that expose events do not draw twice } int32_t GPUDisplayFrontendGlut::GetKey(int32_t key) { - if (key == GLUT_KEY_UP) { - return KEY_UP; - } - if (key == GLUT_KEY_DOWN) { - return KEY_DOWN; - } - if (key == GLUT_KEY_LEFT) { - return KEY_LEFT; - } - if (key == GLUT_KEY_RIGHT) { - return KEY_RIGHT; - } - if (key == GLUT_KEY_PAGE_UP) { - return KEY_PAGEUP; - } - if (key == GLUT_KEY_PAGE_DOWN) { - return KEY_PAGEDOWN; - } - if (key == GLUT_KEY_HOME) { - return KEY_HOME; - } - if (key == GLUT_KEY_END) { - return KEY_END; - } - if (key == GLUT_KEY_INSERT) { - return KEY_INSERT; - } - if (key == GLUT_KEY_F1) { - return KEY_F1; - } - if (key == GLUT_KEY_F2) { - return KEY_F2; - } - if (key == GLUT_KEY_F3) { - return KEY_F3; - } - if (key == GLUT_KEY_F4) { - return KEY_F4; - } - if (key == GLUT_KEY_F5) { - return KEY_F5; - } - if (key == GLUT_KEY_F6) { - return KEY_F6; - } - if (key == GLUT_KEY_F7) { - return KEY_F7; + switch (key) { + case GLUT_KEY_UP: + return KEY_UP; + case GLUT_KEY_DOWN: + return KEY_DOWN; + case GLUT_KEY_LEFT: + return KEY_LEFT; + case GLUT_KEY_RIGHT: + return KEY_RIGHT; + case GLUT_KEY_PAGE_UP: + return KEY_PAGEUP; + case GLUT_KEY_PAGE_DOWN: + return KEY_PAGEDOWN; + case GLUT_KEY_HOME: + return KEY_HOME; + case GLUT_KEY_END: + return KEY_END; + case GLUT_KEY_INSERT: + return KEY_INSERT; + case GLUT_KEY_F1: + return KEY_F1; + case GLUT_KEY_F2: + return KEY_F2; + case GLUT_KEY_F3: + return KEY_F3; + case GLUT_KEY_F4: + return KEY_F4; + case GLUT_KEY_F5: + return KEY_F5; + case GLUT_KEY_F6: + return KEY_F6; + case GLUT_KEY_F7: + return KEY_F7; + case GLUT_KEY_F8: + return KEY_F8; + case GLUT_KEY_F9: + return KEY_F9; + case GLUT_KEY_F10: + return KEY_F10; + case GLUT_KEY_F11: + return KEY_F11; + case GLUT_KEY_F12: + return KEY_F12; + case GLUT_KEY_SHIFT_L: + case GLUT_KEY_SHIFT_R: + return KEY_SHIFT; + case GLUT_KEY_CTRL_L: + return KEY_CTRL; + case GLUT_KEY_CTRL_R: + return KEY_RCTRL; + case GLUT_KEY_ALT_L: + return KEY_ALT; + case GLUT_KEY_ALT_R: + return KEY_RALT; + default: + return 0; } - if (key == GLUT_KEY_F8) { - return KEY_F8; - } - if (key == GLUT_KEY_F9) { - return KEY_F9; - } - if (key == GLUT_KEY_F10) { - return KEY_F10; - } - if (key == GLUT_KEY_F11) { - return KEY_F11; - } - if (key == GLUT_KEY_F12) { - return KEY_F12; - } - if (key == 112 || key == 113) { - return KEY_SHIFT; - } - if (key == 114) { - return KEY_CTRL; - } - if (key == 115) { - return KEY_RCTRL; - } - if (key == 116) { - return KEY_ALT; - } - - return (0); } void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPressOut, bool special) @@ -138,90 +125,84 @@ void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPr if (specialKey) { keyOut = keyPressOut = specialKey; } else { - keyOut = keyPressOut = key; + keyOut = keyPressOut = key == 8 ? KEY_BACKSPACE : (key == 127 ? KEY_DELETE : key); // Otherwise 8 = KEY_SHIFT if (keyPressOut >= 'a' && keyPressOut <= 'z') { keyPressOut += 'A' - 'a'; } } } -void GPUDisplayFrontendGlut::keyboardDownFunc(uint8_t key, int32_t x, int32_t y) +void GPUDisplayFrontendGlut::keyboardFunc(int32_t key, bool special, bool down) { int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, false); - me->mKeysShift[keyPress] = glutGetModifiers() & GLUT_ACTIVE_SHIFT; - me->mKeys[keyPress] = true; - me->HandleKey(handleKey); -} - -void GPUDisplayFrontendGlut::keyboardUpFunc(uint8_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, false); - me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; -} - -void GPUDisplayFrontendGlut::specialDownFunc(int32_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, true); - me->mKeysShift[keyPress] = glutGetModifiers() & GLUT_ACTIVE_SHIFT; - me->mKeys[keyPress] = true; - me->HandleKey(handleKey); -} - -void GPUDisplayFrontendGlut::specialUpFunc(int32_t key, int32_t x, int32_t y) -{ - int32_t handleKey = 0, keyPress = 0; - GetKey(key, handleKey, keyPress, true); - me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; + GetKey(key, handleKey, keyPress, special); + mKeys[keyPress] = down && (keyPress == KEY_SHIFT || !consoleShown()); // Held keys only without console, SHIFT also with it (scrolling) + if (down) { + HandleKey(handleKey); + } } void GPUDisplayFrontendGlut::ResizeSceneWrapper(int32_t width, int32_t height) { - if (!me->mFullScreen) { - me->mWidth = width; - me->mHeight = height; + if (!mFullScreen) { + mWidth = width; + mHeight = height; } - me->ResizeScene(width, height); + ResizeScene(width, height); +#ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 + Display* display = glXGetCurrentDisplay(); // Maximizing / unmaximizing by the window manager changes the size, read the state as freeglut does not report it (fullscreen stays as requested, freeglut tracks it itself) + Atom type; + int32_t format; + unsigned long nItems, bytesAfter; + unsigned char* data = nullptr; + if (!display || XGetWindowProperty(display, glXGetCurrentDrawable(), XInternAtom(display, "_NET_WM_STATE", False), 0, 1024, False, XA_ATOM, &type, &format, &nItems, &bytesAfter, &data) != Success || data == nullptr) { + return; + } + const Atom maxHorz = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", False), maxVert = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + bool isMaxHorz = false, isMaxVert = false; + for (unsigned long i = 0; i < nItems; i++) { + isMaxHorz |= ((Atom*)data)[i] == maxHorz; + isMaxVert |= ((Atom*)data)[i] == maxVert; + } + XFree(data); + setWindowState(mFullScreen, isMaxHorz && isMaxVert); +#endif } void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y) { if (button == 3) { - me->mMouseWheel += 100; + mMouseWheel += 100; } else if (button == 4) { - me->mMouseWheel -= 100; + mMouseWheel -= 100; } else if (state == GLUT_DOWN) { if (button == GLUT_LEFT_BUTTON) { - me->mMouseDn = true; + mMouseDn = true; } else if (button == GLUT_RIGHT_BUTTON) { - me->mMouseDnR = true; + mMouseDnR = true; } else if (button == GLUT_MIDDLE_BUTTON) { - me->mMouseDnM = true; + mMouseDnM = true; } - me->mMouseDnX = x; - me->mMouseDnY = y; + mMouseDnX = x; + mMouseDnY = y; } else if (state == GLUT_UP) { if (button == GLUT_LEFT_BUTTON) { - me->mMouseDn = false; + mMouseDn = false; } else if (button == GLUT_RIGHT_BUTTON) { - me->mMouseDnR = false; + mMouseDnR = false; } else if (button == GLUT_MIDDLE_BUTTON) { - me->mMouseDnM = false; + mMouseDnM = false; } } } void GPUDisplayFrontendGlut::mouseMoveFunc(int32_t x, int32_t y) { - me->mMouseMvX = x; - me->mMouseMvY = y; + mMouseMvX = x; + mMouseMvY = y; } -void GPUDisplayFrontendGlut::mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y) { me->mMouseWheel += dir; } +void GPUDisplayFrontendGlut::mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y) { mMouseWheel += dir; } int32_t GPUDisplayFrontendGlut::FrontendMain() { @@ -229,8 +210,6 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Only OpenGL backend supported\n"); return 1; } - me = this; - mCanDrawText = 1; if (drawTextFontSize() == 0) { drawTextFontSize() = 12; } @@ -241,7 +220,7 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() char* opts[] = {opt1, opt2}; glutInit(&nopts, opts); glutInitContextVersion(GL_MIN_VERSION_MAJOR, GL_MIN_VERSION_MINOR); - glutInitContextProfile(mBackend->CoreProfile() ? GLUT_CORE_PROFILE : GLUT_COMPATIBILITY_PROFILE); + glutInitContextProfile(GLUT_CORE_PROFILE); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(INIT_WIDTH, INIT_HEIGHT); glutCreateWindow(DISPLAY_WINDOW_NAME); @@ -251,34 +230,31 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Error initializing GL extension wrapper\n"); return (-1); } - if (InitDisplay()) { - fprintf(stderr, "Error in OpenGL initialization\n"); - return (1); - } - - glutDisplayFunc(displayFunc); - glutIdleFunc(glutLoopFunc); - glutReshapeFunc(ResizeSceneWrapper); - glutKeyboardFunc(keyboardDownFunc); - glutKeyboardUpFunc(keyboardUpFunc); - glutSpecialFunc(specialDownFunc); - glutSpecialUpFunc(specialUpFunc); - glutMouseFunc(mouseFunc); - glutMotionFunc(mouseMoveFunc); - glutPassiveMotionFunc(mouseMoveFunc); - glutMouseWheelFunc(mMouseWheelFunc); + auto mouseMoveFunc = [](int32_t x, int32_t y, void* p) { self(p)->mouseMoveFunc(x, y); }; + glutDisplayFuncUcall([](void* p) { self(p)->displayFunc(); }, this); // Callbacks with this as user data, freeglut >= 3.2 + glutIdleFuncUcall([](void* p) { self(p)->glutLoopFunc(); }, this); + glutReshapeFuncUcall([](int32_t width, int32_t height, void* p) { self(p)->ResizeSceneWrapper(width, height); }, this); + glutKeyboardFuncUcall([](uint8_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, false, true); }, this); + glutKeyboardUpFuncUcall([](uint8_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, false, false); }, this); + glutSpecialFuncUcall([](int32_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, true, true); }, this); + glutSpecialUpFuncUcall([](int32_t key, int32_t x, int32_t y, void* p) { self(p)->keyboardFunc(key, true, false); }, this); + glutMouseFuncUcall([](int32_t button, int32_t state, int32_t x, int32_t y, void* p) { self(p)->mouseFunc(button, state, x, y); }, this); + glutMotionFuncUcall(mouseMoveFunc, this); + glutPassiveMotionFuncUcall(mouseMoveFunc, this); + glutMouseWheelFuncUcall([](int32_t button, int32_t dir, int32_t x, int32_t y, void* p) { self(p)->mMouseWheelFunc(button, dir, x, y); }, this); applyInitialWindowState(); SetVSync(vsyncEnabled()); - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; + if (InitDisplay()) { + fprintf(stderr, "Error in OpenGL initialization\n"); + return (1); } glutMainLoop(); { std::lock_guard lock(mExitStateMutex); - mDisplayControl = 2; + setDisplayControl(2); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } ExitDisplay(); @@ -290,18 +266,6 @@ void GPUDisplayFrontendGlut::StopDisplayA() glutLeaveMainLoop(); } -void GPUDisplayFrontendGlut::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!fromBotton) { - y = mDisplayHeight - y; - } - glColor4f(r, g, b, a); - glRasterPos2f(x, y); - glutBitmapString(GLUT_BITMAP_HELVETICA_12, (const uint8_t*)s); -#endif -} - void GPUDisplayFrontendGlut::SwitchFullscreen(bool set) { mFullScreen = set; @@ -312,7 +276,27 @@ void GPUDisplayFrontendGlut::SwitchFullscreen(bool set) } } -void GPUDisplayFrontendGlut::ToggleMaximized(bool set) {} +void GPUDisplayFrontendGlut::ToggleMaximized(bool set) // GLUT has no API for maximizing, request it from the window manager via GLX / X11 directly (no-op if freeglut does not use GLX) +{ +#ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 + Display* display = glXGetCurrentDisplay(); + if (!display) { + return; + } + XEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = glXGetCurrentDrawable(); // freeglut renders directly to the X window + xev.xclient.message_type = XInternAtom(display, "_NET_WM_STATE", False); + xev.xclient.format = 32; + xev.xclient.data.l[0] = set ? 1 : 0; // _NET_WM_STATE_ADD / _NET_WM_STATE_REMOVE + xev.xclient.data.l[1] = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + xev.xclient.data.l[2] = XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", False); + XSendEvent(display, DefaultRootWindow(display), False, SubstructureNotifyMask | SubstructureRedirectMask, &xev); + XFlush(display); +#endif +} + void GPUDisplayFrontendGlut::SetVSync(bool enable) // GLUT has no swap interval API, set it via GLX directly (no-op if freeglut does not use GLX) { #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index 9fe8e7b44d018..9d3efac3996d0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -30,27 +30,25 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; bool canSetVSync() override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; private: int32_t FrontendMain() override; - static void displayFunc(); - static void glutLoopFunc(); - static void keyboardUpFunc(uint8_t key, int32_t x, int32_t y); - static void keyboardDownFunc(uint8_t key, int32_t x, int32_t y); - static void specialUpFunc(int32_t key, int32_t x, int32_t y); - static void specialDownFunc(int32_t key, int32_t x, int32_t y); - static void mouseMoveFunc(int32_t x, int32_t y); - static void mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y); - static void mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y); - static void ResizeSceneWrapper(int32_t width, int32_t height); + static GPUDisplayFrontendGlut* self(void* p) { return static_cast(p); } // GLUT callback user data + void displayFunc(); + void glutLoopFunc(); + void keyboardFunc(int32_t key, bool special, bool down); + void mouseMoveFunc(int32_t x, int32_t y); + void mMouseWheelFunc(int32_t button, int32_t dir, int32_t x, int32_t y); + void mouseFunc(int32_t button, int32_t state, int32_t x, int32_t y); + void ResizeSceneWrapper(int32_t width, int32_t height); static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t& keyOut, int32_t& keyPressOut, bool special); int32_t mWidth = INIT_WIDTH; int32_t mHeight = INIT_HEIGHT; bool mFullScreen = false; + bool mRedisplayPending = false; // glutPostRedisplay called in glutLoopFunc, not yet served by displayFunc }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx new file mode 100644 index 0000000000000..31ab4705fae6c --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx @@ -0,0 +1,115 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendKeyTranslation.cxx +/// \author David Rohr + +#include "GPUDisplayFrontend.h" +#include + +using namespace o2::gpu; + +static constexpr uint64_t encodeSequence(const char* s) // As encoded by the console: first character in the lowest byte +{ + uint64_t retVal = 0; + for (uint32_t i = 0; i < 8 && s[i]; i++) { + retVal |= (uint64_t)(uint8_t)s[i] << (8 * i); + } + return retVal; +} + +int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint64_t extendedKey) +{ + if (translationMode == 1) { + // Console escape sequences, characters after the ESC: VT100 / xterm, cursor keys also in application mode (O instead of [), F1 to F5 also of rxvt ([11~) and the Linux console ([[A), with SHIFT of xterm (;2) and rxvt ([a, $) + static constexpr std::pair sequences[] = { + {"[A", KEY_UP}, + {"[B", KEY_DOWN}, + {"[C", KEY_RIGHT}, + {"[D", KEY_LEFT}, + {"OA", KEY_UP}, + {"OB", KEY_DOWN}, + {"OC", KEY_RIGHT}, + {"OD", KEY_LEFT}, + {"[H", KEY_HOME}, + {"[F", KEY_END}, + {"OH", KEY_HOME}, + {"OF", KEY_END}, + {"[1~", KEY_HOME}, + {"[4~", KEY_END}, + {"[7~", KEY_HOME}, + {"[8~", KEY_END}, + {"[2~", KEY_INSERT}, + {"[3~", KEY_DELETE}, + {"[5~", KEY_PAGEUP}, + {"[6~", KEY_PAGEDOWN}, + {"OP", KEY_F1}, + {"OQ", KEY_F2}, + {"OR", KEY_F3}, + {"OS", KEY_F4}, + {"[11~", KEY_F1}, + {"[12~", KEY_F2}, + {"[13~", KEY_F3}, + {"[14~", KEY_F4}, + {"[[A", KEY_F1}, + {"[[B", KEY_F2}, + {"[[C", KEY_F3}, + {"[[D", KEY_F4}, + {"[[E", KEY_F5}, + {"[15~", KEY_F5}, + {"[17~", KEY_F6}, + {"[18~", KEY_F7}, + {"[19~", KEY_F8}, + {"[20~", KEY_F9}, + {"[21~", KEY_F10}, + {"[23~", KEY_F11}, + {"[24~", KEY_F12}, + {"[1;2A", KEY_UP + SPECIAL_KEYS_SHIFTED}, + {"[1;2B", KEY_DOWN + SPECIAL_KEYS_SHIFTED}, + {"[1;2C", KEY_RIGHT + SPECIAL_KEYS_SHIFTED}, + {"[1;2D", KEY_LEFT + SPECIAL_KEYS_SHIFTED}, + {"[1;2H", KEY_HOME + SPECIAL_KEYS_SHIFTED}, + {"[1;2F", KEY_END + SPECIAL_KEYS_SHIFTED}, + {"[2;2~", KEY_INSERT + SPECIAL_KEYS_SHIFTED}, + {"[3;2~", KEY_DELETE + SPECIAL_KEYS_SHIFTED}, + {"[5;2~", KEY_PAGEUP + SPECIAL_KEYS_SHIFTED}, + {"[6;2~", KEY_PAGEDOWN + SPECIAL_KEYS_SHIFTED}, + {"[a", KEY_UP + SPECIAL_KEYS_SHIFTED}, + {"[b", KEY_DOWN + SPECIAL_KEYS_SHIFTED}, + {"[c", KEY_RIGHT + SPECIAL_KEYS_SHIFTED}, + {"[d", KEY_LEFT + SPECIAL_KEYS_SHIFTED}, + {"[7$", KEY_HOME + SPECIAL_KEYS_SHIFTED}, + {"[8$", KEY_END + SPECIAL_KEYS_SHIFTED}, + {"[2$", KEY_INSERT + SPECIAL_KEYS_SHIFTED}, + {"[3$", KEY_DELETE + SPECIAL_KEYS_SHIFTED}, + {"[5$", KEY_PAGEUP + SPECIAL_KEYS_SHIFTED}, + {"[6$", KEY_PAGEDOWN + SPECIAL_KEYS_SHIFTED}, + {"[1;2P", KEY_F1 + SPECIAL_KEYS_SHIFTED}, + {"[1;2Q", KEY_F2 + SPECIAL_KEYS_SHIFTED}, + {"[1;2R", KEY_F3 + SPECIAL_KEYS_SHIFTED}, + {"[1;2S", KEY_F4 + SPECIAL_KEYS_SHIFTED}, + {"[15;2~", KEY_F5 + SPECIAL_KEYS_SHIFTED}, + {"[17;2~", KEY_F6 + SPECIAL_KEYS_SHIFTED}, + {"[18;2~", KEY_F7 + SPECIAL_KEYS_SHIFTED}, + {"[19;2~", KEY_F8 + SPECIAL_KEYS_SHIFTED}, + {"[20;2~", KEY_F9 + SPECIAL_KEYS_SHIFTED}, + {"[21;2~", KEY_F10 + SPECIAL_KEYS_SHIFTED}, + {"[23;2~", KEY_F11 + SPECIAL_KEYS_SHIFTED}, + {"[24;2~", KEY_F12 + SPECIAL_KEYS_SHIFTED}, + }; + for (const auto& [sequence, key] : sequences) { + if (encodeSequence(sequence) == extendedKey) { + return key; + } + } + } + return -1; +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h new file mode 100644 index 0000000000000..499d4dac5e200 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyVars.h @@ -0,0 +1,32 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendKeyVars.h +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDKEYVARS_H +#define GPUDISPLAYFRONTENDKEYVARS_H + +#include + +namespace o2::gpu +{ +class GPUDisplayFrontendKeyVars +{ + public: + // Constant key codes for special mKeys (to unify different treatment in X11 / Windows / GLUT / etc.) +#define GPUDisplayFrontendAddKey(code, key) static constexpr int32_t KEY_##key = code +#define GPUDisplayFrontendKeyDownMap(code, key) static constexpr int32_t KEY_MAP_##key = code +#include "GPUDisplayFrontendKeys.h" +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h new file mode 100644 index 0000000000000..ad8db27502761 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h @@ -0,0 +1,76 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendKeys.h +/// \author David Rohr + +// Key codes for special keys (to unify different treatment in X11 / Windows / GLUT / etc.), as GPUDisplayFrontendAddKey(code, name) X-macros. +// No include guard: included multiple times with different definitions of GPUDisplayFrontendAddKey. + +GPUDisplayFrontendAddKey(1, UP); +GPUDisplayFrontendAddKey(2, DOWN); +GPUDisplayFrontendAddKey(3, LEFT); +GPUDisplayFrontendAddKey(4, RIGHT); +GPUDisplayFrontendAddKey(5, PAGEUP); +GPUDisplayFrontendAddKey(6, PAGEDOWN); +GPUDisplayFrontendAddKey(8, SHIFT); +GPUDisplayFrontendAddKey(9, ALT); +GPUDisplayFrontendAddKey(10, RALT); +GPUDisplayFrontendAddKey(11, CTRL); +GPUDisplayFrontendAddKey(12, RCTRL); +GPUDisplayFrontendAddKey(13, ENTER); // fixed at 13 +GPUDisplayFrontendAddKey(14, F1); +GPUDisplayFrontendAddKey(15, F2); +GPUDisplayFrontendAddKey(16, F3); +GPUDisplayFrontendAddKey(17, F4); +GPUDisplayFrontendAddKey(18, F5); +GPUDisplayFrontendAddKey(19, F6); +GPUDisplayFrontendAddKey(20, F7); +GPUDisplayFrontendAddKey(21, F8); +GPUDisplayFrontendAddKey(22, F9); +GPUDisplayFrontendAddKey(23, F10); +GPUDisplayFrontendAddKey(24, F11); +GPUDisplayFrontendAddKey(25, F12); +GPUDisplayFrontendAddKey(26, INSERT); +GPUDisplayFrontendAddKey(27, ESCAPE); // fixed at 27 +GPUDisplayFrontendAddKey(28, HOME); +GPUDisplayFrontendAddKey(29, END); +GPUDisplayFrontendAddKey(30, BACKSPACE); +GPUDisplayFrontendAddKey(31, DELETE); +GPUDisplayFrontendAddKey(32, SPACE); // fixed at 32 + +#ifndef GPUDisplayFrontendKeyDownMap +#define GPUDisplayFrontendKeyDownMap(...) +#endif + +GPUDisplayFrontendKeyDownMap(0, FORWARD); +GPUDisplayFrontendKeyDownMap(1, BACKWARD); +GPUDisplayFrontendKeyDownMap(2, STRAFE_LEFT); +GPUDisplayFrontendKeyDownMap(3, STRAFE_RIGHT); +GPUDisplayFrontendKeyDownMap(4, STRAFE_UP); +GPUDisplayFrontendKeyDownMap(5, STRAFE_DOWN); +GPUDisplayFrontendKeyDownMap(6, ROTATE_LEFT); +GPUDisplayFrontendKeyDownMap(7, ROTATE_RIGHT); +GPUDisplayFrontendKeyDownMap(8, INCREASE_WIDTH); +GPUDisplayFrontendKeyDownMap(9, DECREASE_WIDTH); +GPUDisplayFrontendKeyDownMap(10, FOCUS_ORIGIN); +GPUDisplayFrontendKeyDownMap(11, FOCUS_ORIENT_UP); +GPUDisplayFrontendKeyDownMap(12, ROTATE_MODEL); +GPUDisplayFrontendKeyDownMap(13, ROTATE_AROUND_Z); +GPUDisplayFrontendKeyDownMap(14, CHANGE_ANIMATION_INTERVAL); +GPUDisplayFrontendKeyDownMap(15, ROTATE_YAW_LEFT); +GPUDisplayFrontendKeyDownMap(16, ROTATE_YAW_RIGHT); +GPUDisplayFrontendKeyDownMap(17, ROTATE_PITCH_UP); +GPUDisplayFrontendKeyDownMap(18, ROTATE_PITCH_DOWN); +GPUDisplayFrontendKeyDownMap(19, MAX); // Must be the maximum value! + +#undef GPUDisplayFrontendAddKey +#undef GPUDisplayFrontendKeyDownMap diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index 5d5748e13349a..98a5654dcdbcd 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -28,9 +28,14 @@ int32_t GPUDisplayFrontendNone::FrontendMain() return 1; } do { - HandleExternalGUISignals(); - DrawGLScene(); + HandleUpdatesAndDrawGLScene(); waitForFPSLimit(); - } while (mDisplayControl != 2); + } while (getDisplayControl() != 2); + ExitDisplay(); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); + } return 0; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h index c6eab47c60441..47080c821542b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h @@ -28,7 +28,6 @@ class GPUDisplayFrontendNone : public GPUDisplayFrontend void SwitchFullscreen(bool set) override {} void ToggleMaximized(bool set) override {} void SetVSync(bool enable) override {} - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override {} private: int32_t FrontendMain() override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 66701557a8068..a3a14f7d7f114 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -38,7 +38,6 @@ #include "viewporter-client-protocol.h" #include "cursor-shape-client-protocol.h" #include -#include using namespace o2::gpu; @@ -50,13 +49,12 @@ GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() mFrontendName = "Wayland"; } -void GPUDisplayFrontendWayland::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -} - int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) { int32_t retVal = 0; + if (key == 41) { // evdev KEY_GRAVE, key left of 1: console key (^), independent of the layout (linux/input-event-codes.h must not be included here) + return ('^'); + } if (mXKBkeymap) { xkb_keysym_t sym = xkb_state_key_get_one_sym(mXKBstate, key + 8); if (sym == XKB_KEY_KP_Subtract) { @@ -95,6 +93,10 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) return (KEY_HOME); } else if (sym == XKB_KEY_Insert) { return (KEY_INSERT); + } else if (sym == XKB_KEY_BackSpace) { + return (KEY_BACKSPACE); + } else if (sym == XKB_KEY_Delete) { + return (KEY_DELETE); } else if (sym == XKB_KEY_F1) { return (KEY_F1); } else if (sym == XKB_KEY_F2) { @@ -174,24 +176,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto pointer_button = [](void* data, wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; - if (state == WL_POINTER_BUTTON_STATE_PRESSED) { - if (button == BTN_RIGHT) { - me->mMouseDnR = true; - } else if (button == BTN_LEFT) { - me->mMouseDn = true; - } else if (button == BTN_MIDDLE) { - me->mMouseDnM = true; - } - me->mMouseDnX = me->mMouseMvX; - me->mMouseDnY = me->mMouseMvY; - } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { - if (button == BTN_RIGHT) { - me->mMouseDnR = false; - } else if (button == BTN_LEFT) { - me->mMouseDn = false; - } else if (button == BTN_MIDDLE) { - me->mMouseDnM = false; - } + if (state == WL_POINTER_BUTTON_STATE_PRESSED || state == WL_POINTER_BUTTON_STATE_RELEASED) { + me->handlePointerButton(button, state == WL_POINTER_BUTTON_STATE_PRESSED); } }; auto pointer_frame = [](void* data, wl_pointer* wl_pointer) { ((GPUDisplayFrontendWayland*)data)->applyScroll(); }; @@ -258,8 +244,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() int32_t symbol = me->GetKey(key, state); int32_t keyPress = (symbol >= 'a' && symbol <= 'z') ? symbol + 'A' - 'a' : symbol; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - me->mKeys[keyPress] = true; - me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; + me->mKeys[keyPress] = keyPress == KEY_SHIFT || !me->consoleShown(); // Held keys only without console, SHIFT also with it (scrolling) me->HandleKey(symbol); if (symbol && me->mRepeatRate > 0 && me->mXKBkeymap && xkb_keymap_key_repeats(me->mXKBkeymap, key + 8)) { // Wayland compositors do not repeat keys, the client must do it me->mRepeatKeyCode = key; @@ -268,7 +253,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } } else { me->mKeys[keyPress] = false; - me->mKeysShift[keyPress] = false; if ((int64_t)key == me->mRepeatKeyCode) { me->mRepeatKeyCode = -1; } @@ -511,7 +495,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto xdg_toplevel_handle_close = [](void* data, xdg_toplevel* toplevel) { - ((GPUDisplayFrontendWayland*)data)->mDisplayControl = 2; + ((GPUDisplayFrontendWayland*)data)->mDisplay->mCfgC.displayControl.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) { @@ -568,8 +552,11 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() throw std::runtime_error("Wayland initialization failed"); } if (initialFullScreen || initialMaximized) { // Only after the windowed initial configure, so that the compositor has a normal (un-maximized) placement, then wait for the new configure before creating the swapchain - setWindowState(initialFullScreen, initialMaximized); - applyInitialWindowState(); + if (initialFullScreen) { // Directly: the settings were reset by the configure, the next configure syncs them to the actual state + requestFullScreen(true); + } else { + requestMaximized(true); + } if (wl_display_roundtrip(mWayland) == -1) { reportError("initial window state roundtrip"); throw std::runtime_error("Wayland initialization failed"); @@ -580,12 +567,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - - while (mDisplayControl != 2) { + while (mDisplay->mCfgC.displayControl.load() != 2) { bool error = false; while (!error && wl_display_prepare_read(mWayland) != 0) { error = wl_display_dispatch_pending(mWayland) == -1; @@ -608,14 +590,11 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() reportError("dispatching events"); break; } - if (mDisplayControl == 2) { + if (mDisplay->mCfgC.displayControl.load() == 2) { break; } handleKeyRepeat(); - HandleExternalGUISignals(); - if (!mSuspended) { // Window not visible, compositor would not release swapchain images - DrawGLScene(); - } + HandleUpdatesAndDrawGLScene(mSuspended); // Window not visible, compositor would not release swapchain images waitForFPSLimit(); } @@ -631,6 +610,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } if (mCursorShapeDevice) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index bd55aa53f4b55..b6083e66e0711 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -43,7 +43,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } float getRefreshRate() override; @@ -52,6 +51,7 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend private: int32_t FrontendMain() override; + void handlePointerButton(uint32_t button, bool pressed); // In GPUDisplayFrontendWaylandEv.cxx, which needs the evdev button codes int32_t GetKey(uint32_t key, uint32_t state); void updateScale(); // Scale from wp_fractional_scale_v1, else integer wl_surface.preferred_buffer_scale, or max scale of the outputs the surface is on (older compositors) void updateSize(); // Physical size = logical size * scale, resizes the scene if changed diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx new file mode 100644 index 0000000000000..4e5e68be566b1 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWaylandEv.cxx @@ -0,0 +1,37 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendWaylandEv.cxx +/// \author David Rohr + +// Functions of the Wayland frontend that need the evdev codes from linux/input-event-codes.h. +// That header defines macros KEY_UP, KEY_F1, ..., which replace GPUDisplayFrontend::KEY_* (e.g. KEY_F1 becomes 59 = ';'), so it must not be included in GPUDisplayFrontendWayland.cxx, +// and here only after all other headers, and the functions in this file must not use GPUDisplayFrontend::KEY_*. + +#include "GPUDisplayFrontendWayland.h" +#include + +using namespace o2::gpu; + +void GPUDisplayFrontendWayland::handlePointerButton(uint32_t button, bool pressed) +{ + if (button == BTN_RIGHT) { + mMouseDnR = pressed; + } else if (button == BTN_LEFT) { + mMouseDn = pressed; + } else if (button == BTN_MIDDLE) { + mMouseDnM = pressed; + } + if (pressed) { + mMouseDnX = mMouseMvX; + mMouseDnY = mMouseMvY; + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 80537c15c4700..8e34fed79a877 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -219,6 +219,12 @@ BOOL CreateGLWindow(char* title, int32_t width, int32_t height, int32_t bits, bo int32_t GetKey(int32_t key) { + if (key == VK_BACK) { + return (GPUDisplayFrontendKeyVars::KEY_BACKSPACE); + } + if (key == VK_DELETE) { + return (GPUDisplayFrontendKeyVars::KEY_DELETE); + } if (key == 107 || key == 187) { return ('+'); } @@ -257,16 +263,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) PostQuitMessage(0); return 0; - case WM_KEYDOWN: // Is A Key Being Held Down? - wParam = GetKey(wParam); - mKeys[wParam] = TRUE; - mKeysShift[wParam] = mKeys[KEY_SHIFT]; + case WM_KEYDOWN: // Is A Key Being Held Down? + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); // Scan code of the key left of 1: console key, independent of the layout + mKeys[wParam] = wParam == KEY_SHIFT || !consoleShown(); // Held keys only without console, SHIFT also with it (scrolling) HandleKey(wParam); return 0; case WM_KEYUP: - wParam = GetKey(wParam); - mKeysShift[wParam] = false; + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); mKeys[wParam] = false; GPUInfo("Key: %d", wParam); return 0; @@ -297,6 +301,17 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) mMouseDnR = false; return 0; + case WM_MBUTTONDOWN: + mMouseDnX = GET_X_LPARAM(lParam); + mMouseDnY = GET_Y_LPARAM(lParam); + mMouseDnM = true; + GetCursorPos(&mouseCursorPos); + return 0; + + case WM_MBUTTONUP: + mMouseDnM = false; + return 0; + case WM_MOUSEMOVE: if (mouseReset) { mMouseDnX = GET_X_LPARAM(lParam); @@ -352,13 +367,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 (getDisplayControl() == 2) { // Quit key / StopDisplay + done = TRUE; + } else { + HandleUpdatesAndDrawGLScene(!active); // Only draw if the program is active + if (active) { SwapBuffers(hDC); // Swap Buffers (Double Buffering) waitForFPSLimit(); } @@ -372,7 +385,6 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() return (0); } -void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) {} void SwitchFullscreen(bool set) {} void ToggleMaximized(bool set) {} void SetVSync(bool enable) {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h index e28a9f4ced75b..ac38427367dc5 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h @@ -28,7 +28,6 @@ class GPUDisplayFrontendWindows : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; private: int32_t FrontendMain() override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index a06f75d094cda..99df81e4a3c0e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -97,6 +97,12 @@ int32_t GPUDisplayFrontendX11::GetKey(int32_t key) if (key == 65379) { return (KEY_INSERT); } + if (key == 65288) { + return (KEY_BACKSPACE); + } + if (key == 65535) { + return (KEY_DELETE); + } if (key == 65470) { return (KEY_F1); } @@ -144,6 +150,10 @@ int32_t GPUDisplayFrontendX11::GetKey(int32_t key) void GPUDisplayFrontendX11::GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut) { + if (event.xkey.keycode == 49) { // Key left of 1: console key (^), independent of the layout + keyOut = keyPressOut = '^'; + return; + } char tmpString[9]; KeySym sym; if (XLookupString(&event.xkey, tmpString, 8, &sym, nullptr) == 0) { @@ -163,28 +173,6 @@ void GPUDisplayFrontendX11::GetKey(XEvent& event, int32_t& keyOut, int32_t& keyP } } -void GPUDisplayFrontendX11::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) -{ -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - if (!fromBotton) { - y = mDisplayHeight - y; - } - glColor4f(r, g, b, a); - glRasterPos2f(x, y); - if (!glIsList(mFontBase)) { - GPUError("print string: Bad display list."); - exit(1); - } else if (s && strlen(s)) { - glPushAttrib(GL_LIST_BIT); - glListBase(mFontBase); - glCallLists(strlen(s), GL_UNSIGNED_BYTE, (GLubyte*)s); - glPopAttrib(); - } - } -#endif -} - int32_t GPUDisplayFrontendX11::FrontendMain() { XSetWindowAttributes windowAttributes; @@ -261,7 +249,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, GL_MIN_VERSION_MAJOR, GLX_CONTEXT_MINOR_VERSION_ARB, GL_MIN_VERSION_MINOR, - GLX_CONTEXT_PROFILE_MASK_ARB, mBackend->CoreProfile() ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, None}; glxContext = glXCreateContextAttribsARB(mXDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); } else { @@ -300,31 +288,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() // Receive signal when window closed Atom WM_DELETE_WINDOW = XInternAtom(mXDisplay, "WM_DELETE_WINDOW", False); XSetWMProtocols(mXDisplay, mWindow, &WM_DELETE_WINDOW, 1); -#ifndef GPUCA_DISPLAY_OPENGL_CORE - XFontStruct* font_info = nullptr; - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - // Prepare fonts - mFontBase = glGenLists(256); - if (!glIsList(mFontBase)) { - GPUError("Out of display lists."); - return (-1); - } - const char* f = "fixed"; - font_info = XLoadQueryFont(mXDisplay, f); - if (!font_info) { - GPUError("XLoadQueryFont failed."); - return (-1); - } else { - int32_t first = font_info->min_char_or_byte2; - int32_t last = font_info->max_char_or_byte2; - glXUseXFont(font_info->fid, first, last - first + 1, mFontBase + first); - } - } - mCanDrawText = 1; - if (drawTextFontSize() == 0) { - drawTextFontSize() = 12; - } -#endif if (mBackend->ExtInit()) { fprintf(stderr, "Error initializing backend\n"); @@ -348,14 +311,9 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - while (1) { do { - if (mDisplayControl == 2) { + if (getDisplayControl() == 2) { break; } if (!XPending(mXDisplay)) { @@ -399,8 +357,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() case KeyPress: { int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); - mKeysShift[keyPress] = mKeys[KEY_SHIFT]; - mKeys[keyPress] = true; + mKeys[keyPress] = keyPress == KEY_SHIFT || !consoleShown(); // Held keys only without console, SHIFT also with it (scrolling) HandleKey(handleKey); break; } @@ -409,7 +366,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); mKeys[keyPress] = false; - mKeysShift[keyPress] = false; break; } @@ -444,30 +400,23 @@ int32_t GPUDisplayFrontendX11::FrontendMain() if (event.xclient.message_type == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { XFlush(mXDisplay); } else { - mDisplayControl = 2; + setDisplayControl(2); } break; } } } while (XPending(mXDisplay)); // Loop to compress events - if (mDisplayControl == 2) { + if (getDisplayControl() == 2) { break; } - HandleExternalGUISignals(); - DrawGLScene(); + HandleUpdatesAndDrawGLScene(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXSwapBuffers(mXDisplay, mWindow); } waitForFPSLimit(); } -#ifndef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glDeleteLists(mFontBase, 256); - XUnloadFont(mXDisplay, font_info->fid); - } -#endif ExitDisplay(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXDestroyContext(mXDisplay, glxContext); @@ -479,6 +428,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() { std::lock_guard lock(mExitStateMutex); mDisplayRunning = false; + mDisplayStoppedCV.notify_all(); } return (0); @@ -513,8 +463,8 @@ void GPUDisplayFrontendX11::SwitchFullscreen(bool set) xev.xclient.window = mWindow; xev.xclient.message_type = XInternAtom(mXDisplay, "_NET_WM_STATE", False); xev.xclient.format = 32; - xev.xclient.data.l[0] = 2; // _NET_WM_STATE_TOGGLE - xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", True); + xev.xclient.data.l[0] = set ? 1 : 0; // _NET_WM_STATE_ADD / _NET_WM_STATE_REMOVE + xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", False); xev.xclient.data.l[2] = 0; XSendEvent(mXDisplay, DefaultRootWindow(mXDisplay), False, SubstructureNotifyMask, &xev); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index 41032debeb9f5..abceeaaa670f0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -32,7 +32,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; - void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; void getSize(int32_t& width, int32_t& height) override; float getDisplayScale() const override { return mScale; } int32_t getVulkanSurface(void* instance, void* surface) override; @@ -43,8 +42,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend int32_t GetKey(int32_t key); void GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut); - GLuint mFontBase; - Display* mXDisplay = nullptr; Window mWindow; void updateWindowState(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 93a5caa2fa4dd..aec293ecbac3f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -13,34 +13,523 @@ /// \author David Rohr #include "GPUDisplayGUI.h" -#include "GPUDisplayFrontend.h" -#include "ui_GPUDisplayGUI.h" +#include "GPUDisplayFrontendKeyVars.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include using namespace o2::gpu; -GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mUi(std::make_unique()), mChannel(channel) +namespace { - mUi->setupUi(this); +class FlowLayout : public QLayout // Places the items in a row with their preferred width, and wraps into further rows at the right border (Qt has no such layout) +{ + public: + FlowLayout(QWidget* parent, int hSpacingFactor, bool equalRowHeight) : QLayout(parent), mHSpacingFactor(hSpacingFactor), mEqualRowHeight(equalRowHeight) {} + ~FlowLayout() override + { + while (QLayoutItem* item = takeAt(0)) { + delete item; + } + } + void addItem(QLayoutItem* item) override { mItems.append(item); } + int count() const override { return mItems.size(); } + QLayoutItem* itemAt(int index) const override { return mItems.value(index); } + QLayoutItem* takeAt(int index) override { return index >= 0 && index < mItems.size() ? mItems.takeAt(index) : nullptr; } + Qt::Orientations expandingDirections() const override { return {}; } + bool hasHeightForWidth() const override { return true; } + int heightForWidth(int width) const override { return doLayout(QRect(0, 0, width, 0), true); } + void setGeometry(const QRect& rect) override + { + QLayout::setGeometry(rect); + doLayout(rect, false); + } + QSize sizeHint() const override // All items in one row + { + QSize size(0, 0); + for (const QLayoutItem* item : mItems) { + size = QSize(size.width() + (size.width() ? hSpacing() : 0) + itemWidth(item), std::max(size.height(), item->sizeHint().height())); + } + const QMargins m = contentsMargins(); + return size + QSize(m.left() + m.right(), m.top() + m.bottom()); + } + QSize minimumSize() const override // Widest item, height for that width is given by heightForWidth + { + QSize size; + for (const QLayoutItem* item : mItems) { + size = size.expandedTo(item->minimumSize()); + } + const QMargins m = contentsMargins(); + return size + QSize(m.left() + m.right(), m.top() + m.bottom()); + } + void setPreferredWidth(QWidget* widget, int width) { mPreferredWidth[widget] = width; } // Instead of the size hint of the widget + + private: + int hSpacing() const { return mHSpacingFactor * spacing(); } + int itemWidth(const QLayoutItem* item) const { return mPreferredWidth.value(item->widget(), item->sizeHint().width()); } + int doLayout(const QRect& rect, bool testOnly) const // Returns the height needed + { + const QRect r = rect.marginsRemoved(contentsMargins()); + int x = r.x(), y = r.y(), lineHeight = 0; + std::vector> line; // Items of the current row, placed when the row is complete (height of the row known) + auto placeLine = [&]() { + if (!testOnly) { + for (auto& [item, geometry] : line) { + item->setGeometry(mEqualRowHeight ? QRect(geometry.x(), geometry.y(), geometry.width(), lineHeight) : geometry.translated(0, (lineHeight - geometry.height()) / 2)); // Otherwise vertically centered in the row + } + } + line.clear(); + }; + for (QLayoutItem* item : mItems) { + const int width = std::max(std::min(itemWidth(item), r.width()), item->minimumSize().width()); // Items wider than the layout get its width (at least their minimum), and wrap internally if they can + const int height = item->hasHeightForWidth() ? item->heightForWidth(width) : item->sizeHint().height(); + if (x > r.x() && x + width > r.right() + 1) { + placeLine(); + x = r.x(); + y += lineHeight + spacing(); + lineHeight = 0; + } + line.emplace_back(item, QRect(x, y, width, height)); + x += width + hSpacing(); + lineHeight = std::max(lineHeight, height); + } + placeLine(); + return y + lineHeight - rect.y() + contentsMargins().bottom(); + } + QList mItems; + QHash mPreferredWidth; + int mHSpacingFactor; // Horizontal spacing in units of spacing() + bool mEqualRowHeight; // All items of a row get the height of the highest one +}; +class GUIStyle : public QProxyStyle // Tooltips only after hovering for 1.5 seconds, also directly after another tooltip +{ + public: + int styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const override + { + return hint == SH_ToolTip_WakeUpDelay ? 1500 : (hint == SH_ToolTip_FallAsleepDelay ? 0 : QProxyStyle::styleHint(hint, option, widget, returnData)); + } +}; + +class ResizeGrip : public QWidget // Size grip in the bottom right corner of its parent, reports the new size while dragged (QSizeGrip resizes only windows) +{ + public: + ResizeGrip(QWidget* parent, std::function resize) : QWidget(parent), mResize(std::move(resize)) + { + setCursor(Qt::SizeFDiagCursor); + setFixedSize(16, 16); + } + + protected: + void paintEvent(QPaintEvent*) override + { + QStylePainter painter(this); + QStyleOptionSizeGrip opt; + opt.initFrom(this); + opt.corner = Qt::BottomRightCorner; + painter.drawControl(QStyle::CE_SizeGrip, opt); + } + void mousePressEvent(QMouseEvent* e) override + { + mStartPos = e->globalPosition().toPoint(); + mStartSize = parentWidget()->size(); + } + void mouseMoveEvent(QMouseEvent* e) override + { + if (e->buttons() & Qt::LeftButton) { + const QPoint d = e->globalPosition().toPoint() - mStartPos; + mResize(mStartSize + QSize(d.x(), d.y())); + } + } + + private: + std::function mResize; + QPoint mStartPos; + QSize mStartSize; +}; + +class TextEdit : public QPlainTextEdit // Text edit with a resize grip, reports a changed text when it loses the focus +{ + public: + TextEdit(uint32_t lines, std::function changed) : mChanged(std::move(changed)), mGrip(new ResizeGrip(this, [this](QSize size) { + mUnscaledSize = size.expandedTo(QSize(60, 30) * mScale) / mScale; + setScale(mScale); + })) + { + const int margins = 2 * (frameWidth() + std::lround(document()->documentMargin())) + 6; // Few more pixels, otherwise a scroll bar appears + mUnscaledSize = QSize(400, std::max(lines, 1) * fontMetrics().lineSpacing() + margins); + } + QSize sizeHint() const override { return mPreferredSize; } + void setScale(float scale) // Fixed height, but narrower than preferred if the window is too small, so that it does not determine the width of the other elements + { + mScale = scale; + mPreferredSize = mUnscaledSize * scale; + setFixedHeight(mPreferredSize.height()); + setMinimumWidth(std::min(mPreferredSize.width(), 100)); + setMaximumWidth(mPreferredSize.width()); + updateGeometry(); + } + void showText(const QString& text) // Text from the display, not shown while the user edits + { + mCurrent = text; + if (!hasFocus() && toPlainText() != text) { + setPlainText(text); + document()->setModified(false); + } + } + + protected: + void focusOutEvent(QFocusEvent* e) override + { + QPlainTextEdit::focusOutEvent(e); + if (document()->isModified()) { + document()->setModified(false); + mCurrent = toPlainText(); + mChanged(mCurrent); + } else { + showText(mCurrent); + } + } + void resizeEvent(QResizeEvent* e) override + { + QPlainTextEdit::resizeEvent(e); + mGrip->move(width() - mGrip->width(), height() - mGrip->height()); + mGrip->raise(); + } + + private: + std::function mChanged; + QString mCurrent; + QSize mUnscaledSize; // At GUI scale 1, changed by the resize grip + QSize mPreferredSize; + float mScale = 1.f; + ResizeGrip* mGrip; +}; + +struct ValueEdit { // Text box for a value from the display, copied into the lambdas of the element + QLineEdit* edit = new QLineEdit; + std::shared_ptr current = std::make_shared(); // Restored when the text box is left without a valid new value + + void show(const QString& text) const // Value from the display, not shown while the user edits + { + *current = text; + if (!edit->hasFocus()) { + edit->setText(text); + } + } + void commit(const QString& text) const // New value entered by the user, possibly corrected + { + *current = text; + edit->setText(text); + } + std::optional entered() const // Text if changed by the user, otherwise restores the current value + { + if (edit->isModified()) { + return edit->text(); + } + edit->setText(*current); + return std::nullopt; + } + std::optional number() const // Number if entered by the user, otherwise restores the current value + { + bool ok = false; + const double v = edit->text().toDouble(&ok); + if (edit->isModified() && ok && std::isfinite(v)) { + return v; + } + edit->setText(*current); + return std::nullopt; + } +}; + +class MultiSlider : public QWidget // Handles on one range [0, value.max] with ticks below: dragged with the mouse (multiples of step, never onto another handle), a click without moving (de)selects +{ + public: + explicit MultiSlider(double step) : mStep(step) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setFocusPolicy(Qt::ClickFocus); // A click finishes the editing in the text box first, for the previously focused handle + } + std::function changed; // Positions or selection changed by the user, value in the order received from the display + std::function focusChanged; // Other focused handle or its position, for the text box + GPUDisplayMultiSliderValue value; // Positions in the order of the display, selected handle + int32_t focused = -1; // Last clicked / dragged handle, shown in the text box + + void setFromDisplay(const GPUDisplayMultiSliderValue& v) + { + value = v; + mDragging = -1; // A drag is cancelled by changes from the display + focused = focused < (int32_t)value.values.size() ? focused : -1; + update(); + focusChanged(); + } + void setPosition(int32_t i, double v) // From the text box, rounded to the step, ignored if the position does not change or another handle is there + { + v = snap(v); + if (isFree(v, -1)) { // Also the own position + value.values[i] = v; + sendAndNormalize(); + } + } + QSize sizeHint() const override { return {300, handleHeight() + 6 + fontMetrics().height() + 8}; } + QSize minimumSizeHint() const override { return {100, sizeHint().height()}; } + + protected: + void paintEvent(QPaintEvent*) override + { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + const QPalette& pal = palette(); + const int32_t trackY = handleHeight() / 2 + 1, tickY = handleHeight() + 3; + p.setPen(QPen(pal.color(QPalette::Mid), 2)); + p.drawLine(margin(), trackY, width() - margin(), trackY); + if (value.progress && value.values.size()) { // Up to the progress of the last position, e.g. the running animation + p.setPen(QPen(pal.color(QPalette::Highlight), 4)); + p.drawLine(margin(), trackY, toX(value.progress / 65535. * value.values.back()), trackY); + } + p.setPen(pal.color(QPalette::WindowText)); + const double tick = tickStep(); + for (int32_t k = 0; k * tick <= maxValue() * (1 + 1e-9); k++) { + const int32_t x = toX(k * tick); + p.drawLine(x, tickY, x, tickY + 4); + p.drawText(QRect(x - 50, tickY + 5, 100, fontMetrics().height()), Qt::AlignHCenter | Qt::AlignTop, QString::number(k * tick)); + } + for (uint32_t i = 0; i < value.values.size(); i++) { + const QRect r(toX(value.values[i]) - handleWidth() / 2, 1, handleWidth(), handleHeight()); + p.setBrush((int32_t)i == value.selected ? pal.color(QPalette::Highlight) : pal.color(QPalette::Button)); + p.setPen(QPen((int32_t)i == focused ? pal.color(QPalette::WindowText) : pal.color(QPalette::Mid), (int32_t)i == focused ? 2 : 1)); + p.drawRoundedRect(r, 2, 2); + } + } + void mousePressEvent(QMouseEvent* e) override + { + if (e->button() != Qt::LeftButton) { + return; + } + int32_t best = -1, bestDistance = handleWidth(); + for (uint32_t i = 0; i < value.values.size(); i++) { // Nearest handle under the mouse + if (const int32_t d = std::abs(toX(value.values[i]) - (int32_t)e->position().x()); d <= bestDistance) { + best = i; + bestDistance = d; + } + } + if (best >= 0) { + mDragging = focused = best; + mMoved = false; + update(); + focusChanged(); + } + } + void mouseMoveEvent(QMouseEvent* e) override + { + if (mDragging < 0) { + return; + } + const float v = snap(toValue(e->position().x())); // Compared to the stored float + if (isFree(v, mDragging) && v != value.values[mDragging]) { // Never onto another handle, it can be passed + value.values[mDragging] = v; + mMoved = true; + update(); + focusChanged(); + } + } + void mouseReleaseEvent(QMouseEvent* e) override + { + if (e->button() != Qt::LeftButton || mDragging < 0) { + return; + } + if (!mMoved) { + value.selected = value.selected == mDragging ? -1 : mDragging; + } + mDragging = -1; + sendAndNormalize(); + } + + private: + void sendAndNormalize() // Sends the value in the order of the display, then sorts it as the display does (first position 0), until its reply arrives + { + changed(); + std::vector order(value.values.size()); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), [this](uint32_t a, uint32_t b) { return value.values[a] < value.values[b]; }); + GPUDisplayMultiSliderValue sorted = value; + int32_t newFocused = -1; + for (uint32_t i = 0; i < order.size(); i++) { + sorted.values[i] = value.values[order[i]] - value.values[order[0]]; + sorted.selected = (int32_t)order[i] == value.selected ? i : sorted.selected; + newFocused = (int32_t)order[i] == focused ? i : newFocused; + } + value = std::move(sorted); + focused = newFocused; + update(); + focusChanged(); + } + double maxValue() const { return std::max(value.max, mStep); } + double snap(double v) const { return std::clamp(std::round(v / mStep) * mStep, 0., maxValue()); } // Positions are multiples of the step + bool isFree(double v, int32_t except) const // No handle other than except at v (positions are snapped: closer than half a step is the same position) + { + return std::none_of(value.values.begin(), value.values.end(), [&](const float& x) { return &x - value.values.data() != except && std::abs(x - v) < mStep / 2; }); + } + int32_t handleHeight() const { return fontMetrics().height(); } + int32_t handleWidth() const { return std::max(6, fontMetrics().height() / 2); } + int32_t margin() const { return std::max(handleWidth() / 2 + 1, fontMetrics().horizontalAdvance("00.00") / 2); } // Room for the handles and tick labels at the ends + int32_t toX(double v) const { return margin() + std::lround(v / maxValue() * (width() - 2 * margin())); } + double toValue(double x) const { return (x - margin()) / std::max(1, width() - 2 * margin()) * maxValue(); } + double tickStep() const // 1, 2, 5 times a power of 10, labels not overlapping + { + const double minStep = maxValue() * 2 * fontMetrics().horizontalAdvance("00.00") / std::max(1, width() - 2 * margin()); + const double base = std::pow(10., std::floor(std::log10(std::max(minStep, 1e-9)))); + for (double m : {1., 2., 5.}) { + if (m * base >= minStep) { + return std::max(m * base, mStep); + } + } + return std::max(10 * base, mStep); + } + + double mStep; + int32_t mDragging = -1; + bool mMoved = false; +}; + +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, double fixedStep = 0) : min(minValue), step(fixedStep > 0 ? fixedStep : (isInteger ? 1. : (maxValue - minValue) / FLOAT_STEPS)), steps(fixedStep > 0 || isInteger ? std::max(std::lround((maxValue - minValue) / step), 0) : FLOAT_STEPS) {} // fixedStep: Element::step + 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 float clampScale(float scale) { return std::clamp(scale, 0.25f, 4.f); } + +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 if constexpr (std::is_same_v, GPUDisplayMultiSliderValue>) { + return 0.; + } 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 if constexpr (std::is_same_v, GPUDisplayMultiSliderValue>) { + return QString(); + } 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 aNumber = std::holds_alternative(a) || std::holds_alternative(a), bNumber = std::holds_alternative(b) || std::holds_alternative(b); + return aNumber && bNumber ? toDouble(a) == toDouble(b) : a == b; +} + +GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mChannel(channel) +{ + qApp->setStyle(new GUIStyle); // Owned by the application + resize(1370, 1210); + setMinimumSize(500, 500); + mStatusLabel = new QLabel; + mStatusLabel->setWordWrap(true); + mStatusLabel->hide(); + statusBar()->addWidget(mStatusLabel, 1); + mDarkMode = darkPalette(); // System scheme, until the display tells otherwise + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &GPUDisplayGUI::colorSchemeChanged, Qt::QueuedConnection); // Queued: after the palette was updated + mInfoTextTimer.setSingleShot(true); + connect(&mInfoTextTimer, &QTimer::timeout, this, &GPUDisplayGUI::updateInfoLabels); + std::vector layout; { std::lock_guard lock(mChannel.mutex); setWindowTitle(QString::fromStdString(mChannel.windowName + " GUI")); + layout = mChannel.layout; } - connect(mUi->lineWidthSlider, &QSlider::valueChanged, this, [this](int32_t value) { - mUpdate.lineWidth = value * 0.1f; - mUi->lineWidthValue->setNum(*mUpdate.lineWidth); - }); + mBaseFontSize = font().pointSizeF() > 0 ? font().pointSizeF() : 10.f; // Fonts with pixel size have no point size + auto* content = new QWidget; + auto* contentLayout = new QVBoxLayout(content); + contentLayout->addWidget(buildElements(layout)); + contentLayout->addStretch(); + auto* scroll = new QScrollArea; // Vertical scroll bar if the frames do not fit into the window, they wrap to the window width + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setWidget(content); + setCentralWidget(scroll); + colorSchemeChanged(); // Style sheets, with the GUI scale, which comes from the display with the first state + qApp->installEventFilter(this); // Sliders / dropdowns would consume Ctrl + mouse wheel themselves connect(&mTimer, &QTimer::timeout, this, &GPUDisplayGUI::timerTick); mTimer.start(std::chrono::milliseconds(10)); } -GPUDisplayGUI::~GPUDisplayGUI() = default; - void GPUDisplayGUI::timerTick() { std::unique_lock lock(mChannel.mutex); @@ -64,14 +553,21 @@ void GPUDisplayGUI::timerTick() mLastAppliedId = mLastSentId; } } + std::vector> options; // Read while the display waits, applied to the widgets after unlocking + if (mChannel.displayWaitingForOptions && mChannel.state.optionsRevision != mOptionsRevision) { + for (uint32_t id = 0; id < mElements.size(); id++) { + if (mElements[id].getOptions) { + options.emplace_back(id, mElements[id].getOptions()); + } + } + mOptionsRevision = mChannel.optionsReadRevision = mChannel.state.optionsRevision; + mChannel.optionsRead.notify_all(); + } const bool apply = newState && mUpdate.empty() && mState.appliedUpdate == mLastSentId; // State contains all our updates, and we have no unsent changes - if (!mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead) { + const bool post = !mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead; + if (post) { if (mChannel.update) { // Previous update not yet picked up by the display, merge - auto& update = *mChannel.update; - update.keys.insert(update.keys.end(), mUpdate.keys.begin(), mUpdate.keys.end()); - if (mUpdate.lineWidth) { - update.lineWidth = mUpdate.lineWidth; - } + mChannel.update->merge(mUpdate); } else { mChannel.update = std::move(mUpdate); } @@ -81,16 +577,421 @@ void GPUDisplayGUI::timerTick() } lock.unlock(); + if (post) { + mChannel.updatePosted(); + } + if (newState) { + if (mState.cfg.mCfgO.guiDarkMode < 0) { + mUpdate.guiDarkMode = mDarkMode; // Report the system scheme, also shows up in the DarkMode checkbox + } else if ((mState.cfg.mCfgO.guiDarkMode != 0) != mDarkMode) { + setDarkMode(mState.cfg.mCfgO.guiDarkMode != 0); + } + if (mState.infoTextCounter != mInfoTextCounter) { + mInfoTextCounter = mState.infoTextCounter; + mInfoTextTimer.start(mChannel.infoTextTime); + } + updateInfoLabels(); + } + for (const auto& [id, o] : options) { // New range / choices, then the current value again + mElements[id].setOptions(o); + mElements[id].applied.reset(); + applyElement(mElements[id]); + } if (apply) { applySettings(); } } +bool GPUDisplayGUI::darkPalette() const { return palette().window().color().lightness() < 128; } + +void GPUDisplayGUI::setDarkMode(bool dark) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + QGuiApplication::styleHints()->setColorScheme(dark ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light); // No effect if the platform theme does not support it +#endif + mDarkMode = dark; +} + +void GPUDisplayGUI::colorSchemeChanged() +{ + setStyleSheet(QString()); // Otherwise the widgets keep their old palette + setScale(mScale); // Sets the style sheet again + updateInfoLabels(); +} + +void GPUDisplayGUI::resizeEvent(QResizeEvent* event) +{ + QMainWindow::resizeEvent(event); + updateInfoLabels(); // Shows the GUI size +} + +void GPUDisplayGUI::fitStatusLabel() +{ + mStatusLabel->setMinimumHeight(0); // heightForWidth of QLabel does not return less than the minimum height + mStatusLabel->setFixedHeight(mStatusLabel->heightForWidth(mStatusLabel->width())); +} + +void GPUDisplayGUI::updateInfoLabels() +{ + const QString status = QString::fromStdString(mState.infoStatus) + QString(" GUI %1x%2").arg(width()).arg(height()); + if (mInfoTextTimer.isActive() && !mState.infoText.empty()) { // Info message for some seconds, in bold and color, in the same line + const char* color = darkPalette() ? "lightblue" : "darkblue"; + mStatusLabel->setTextFormat(Qt::RichText); + mStatusLabel->setText("" + status.toHtmlEscaped() + QString(" - %2").arg(color, QString::fromStdString(mState.infoText).toHtmlEscaped())); + } else { + mStatusLabel->setTextFormat(Qt::PlainText); + mStatusLabel->setText(status); + } + mStatusLabel->setVisible(!mState.infoStatus.empty()); + fitStatusLabel(); +} + void GPUDisplayGUI::applySettings() { - const QSignalBlocker blocker(mUi->lineWidthSlider); // Not a user change - mUi->lineWidthSlider->setValue(std::lround(mState.cfg.cfgL.lineWidth * 10.f)); - mUi->lineWidthValue->setNum(mState.cfg.cfgL.lineWidth); + if (clampScale(mState.cfg.mCfgO.guiScale) != mScale) { // Changed by the display (Reset GUI button, console) or from a previous GUI session, clamped as by setScale + setScale(mState.cfg.mCfgO.guiScale); + } + 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)); }, {}}; +} + +QHBoxLayout* GPUDisplayGUI::createSliderRow(ElementWidget& retVal, QWidget* slider, QLineEdit* edit, Qt::Alignment editAlignment) +{ + retVal.widget = new QWidget; + auto* row = new QHBoxLayout(retVal.widget); + row->setContentsMargins(0, 0, 0, 0); + mScaleSetters.emplace_back([edit](float scale) { edit->setFixedWidth(std::lround(60 * scale)); }); + row->addWidget(slider); + row->addWidget(edit, 0, editAlignment); + return row; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createSlider(const GPUDisplayGUIChannel::Element& el) +{ + ElementWidget retVal; + auto* slider = new QSlider(Qt::Horizontal); + ValueEdit text; + auto* row = createSliderRow(retVal, slider, text.edit); + 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)); + } + }); + 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, el.step); // 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, step = el.step]() { + if (const auto entered = text.number()) { + const double clamped = std::clamp(*entered, range->min, range->value(range->steps)); + const GPUDisplayGUIValue v = toValue(step > 0 ? range->value(range->position(clamped)) : clamped, isInteger); // Rounded to the step if set, otherwise 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, step = el.step](const GPUDisplayGUIChannel::Options& o) { + *range = SliderRange(o.min, o.max, isInteger, step); + const QSignalBlocker blocker(slider); + slider->setRange(0, range->steps); + }; + } + return retVal; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createMultiSlider(const GPUDisplayGUIChannel::Element& el) +{ + ElementWidget retVal; + auto* slider = new MultiSlider(el.step); + ValueEdit text; + createSliderRow(retVal, slider, text.edit, Qt::AlignTop); + mScaleSetters.emplace_back([slider](float scale) { slider->setMinimumWidth(std::lround(300 * scale)); }); + slider->changed = [this, slider, id = el.id]() { setValue(id, slider->value); }; + auto focusedText = [slider]() { return slider->focused >= 0 ? QString::number(slider->value.values[slider->focused]) : QString(); }; + slider->focusChanged = [text, focusedText]() { text.show(focusedText()); }; + connect(text.edit, &QLineEdit::editingFinished, this, [slider, text, focusedText]() { + if (const auto entered = text.number(); entered && slider->focused >= 0) { + slider->setPosition(slider->focused, *entered); + } + text.commit(focusedText()); // The position actually set, also after the normalization + }); + retVal.set = [slider](const GPUDisplayGUIValue& v) { slider->setFromDisplay(std::get(v)); }; + return retVal; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createRadioButtons(const GPUDisplayGUIChannel::Element& el) +{ + auto* widget = new QWidget; + auto* row = new QHBoxLayout(widget); + row->setContentsMargins(0, 0, 0, 0); + auto* group = new QButtonGroup(widget); + for (uint32_t i = 0; i < el.choices.size(); i++) { + auto* button = new QRadioButton(QString::fromStdString(el.choices[i].first)); + group->addButton(button, i); + row->addWidget(button); + } + connect(group, &QButtonGroup::idClicked, this, [this, id = el.id, choices = el.choices](int32_t i) { setValue(id, choices[i].second); }); // idClicked only on user clicks + return {widget, [group, choices = el.choices](const GPUDisplayGUIValue& v) { + for (uint32_t i = 0; i < choices.size(); i++) { + if (sameValue(choices[i].second, v)) { + group->button(i)->setChecked(true); + } + } + }, + {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createTextEdit(const GPUDisplayGUIChannel::Element& el) +{ + auto* w = new TextEdit(el.lines, [this, id = el.id](const QString& text) { setValue(id, text.toStdString()); }); + mScaleSetters.emplace_back([w](float scale) { w->setScale(scale); }); + return {w, [w](const GPUDisplayGUIValue& v) { w->showText(toQString(v)); }, {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createButton(const GPUDisplayGUIChannel::Element& el) // No value from the display +{ + auto* w = new QPushButton(QString::fromStdString(el.name)); + connect(w, &QPushButton::clicked, this, [this, id = el.id]() { setValue(id, (int64_t)1); }); + return {w, {}, {}}; +} + +GPUDisplayGUI::ElementWidget GPUDisplayGUI::createDropdown(const GPUDisplayGUIChannel::Element& el) +{ + auto* w = new QComboBox; + auto choices = std::make_shared>>(el.choices); // Changed by dynamic options + for (const auto& choice : *choices) { + w->addItem(QString::fromStdString(choice.first)); + } + connect(w, &QComboBox::currentIndexChanged, this, [this, id = el.id, choices](int32_t i) { + if (i >= 0 && i < (int32_t)choices->size()) { + setValue(id, (*choices)[i].second); + } + }); + return {w, [w, choices](const GPUDisplayGUIValue& v) { + const QSignalBlocker blocker(w); + for (uint32_t i = 0; i < choices->size(); i++) { + if (sameValue((*choices)[i].second, v)) { + w->setCurrentIndex(i); + } + } }, + [w, choices](const GPUDisplayGUIChannel::Options& o) { + *choices = o.choices; + const QSignalBlocker blocker(w); + w->clear(); + for (const auto& choice : *choices) { + w->addItem(QString::fromStdString(choice.first)); + } + }}; +} + +QWidget* GPUDisplayGUI::buildElements(const std::vector& layout) +{ + using Type = GPUDisplayGUIChannel::ElementType; + auto* frames = new QWidget; // Frames with their natural width, next to each other while they fit into the window + auto* framesLayout = new FlowLayout(frames, 1, true); + framesLayout->setContentsMargins(0, 0, 0, 0); + for (const auto& frame : layout) { + auto* box = new QGroupBox(QString::fromStdString(frame.description)); + if (frame.description.empty()) { + box->setStyleSheet("QGroupBox { margin-top: 0; }"); // No space for a title + } + auto* boxLayout = new QVBoxLayout(box); + QLayout* section = nullptr; // Consecutive elements with the same alignment + bool sectionHorizontal = false; + for (const auto& el : frame.elements) { + ElementWidget created; + switch (el.type) { + case Type::CHECKBOX: + created = createCheckbox(el); + break; + case Type::TEXTBOX: + created = createTextbox(el); + break; + case Type::SLIDER: + created = createSlider(el); + break; + case Type::RADIO_BUTTONS: + created = createRadioButtons(el); + break; + case Type::TEXTEDIT: + created = createTextEdit(el); + break; + case Type::BUTTON: + created = createButton(el); + break; + case Type::DROPDOWN: + created = createDropdown(el); + break; + case Type::MULTI_SLIDER: + created = createMultiSlider(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)) || el.type == Type::MULTI_SLIDER; })) { // Sliders range to the frame border, frame limited to the window width by the layout + mScaleSetters.emplace_back([this, framesLayout, box](float scale) { framesLayout->setPreferredWidth(box, std::lround(screen()->availableGeometry().width() / 3 * scale)); }); + } + } + mScaleSetters.emplace_back([framesLayout](float) { framesLayout->invalidate(); }); + return frames; +} + +void GPUDisplayGUI::setScale(float scale) +{ + mScale = clampScale(scale); + for (const auto& setter : mScaleSetters) { + setter(mScale); + } + const int indicator = std::lround(style()->pixelMetric(QStyle::PM_IndicatorWidth) * mScale); + setStyleSheet(QString("QGroupBox { border: 1px solid palette(mid); border-radius: 3px; margin-top: 0.6em; padding-top: 0.3em; }" + "QGroupBox::title { subcontrol-origin: margin; left: 8px; padding: 0 3px; }" // Title on the frame border instead of in an extra line above + "QCheckBox::indicator, QRadioButton::indicator { width: %1px; height: %1px; }" + "QWidget { font-size: %2pt; }") // Style sheet, since setFont() does not override the fonts the platform theme sets for some classes (QLabel, QPushButton), sizes derived from the text follow + .arg(indicator) + .arg(mBaseFontSize * mScale, 0, 'f', 1)); +} + +bool GPUDisplayGUI::eventFilter(QObject* watched, QEvent* event) +{ + const auto* w = qobject_cast(watched); + if (!w || w->window() != this) { + return false; + } + if (watched == mStatusLabel && event->type() == QEvent::Resize) { + fitStatusLabel(); + return false; + } + if (event->type() == QEvent::Wheel && (static_cast(event)->modifiers() & Qt::ControlModifier)) { + const int delta = static_cast(event)->angleDelta().y(); // 120 per notch + if (delta) { + setScale(mScale * std::pow(1.1f, delta / 120.f)); + mUpdate.guiScale = mScale; + } + return true; + } + if (event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_0 && (static_cast(event)->modifiers() & Qt::ControlModifier)) { + setScale(1.f); + mUpdate.guiScale = mScale; + return true; + } + return false; } void GPUDisplayGUI::bringToFront() @@ -113,36 +1014,47 @@ void GPUDisplayGUI::keyPressEvent(QKeyEvent* event) int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event) { +#ifdef Q_OS_WIN + if (event->nativeScanCode() == 0x29) { // Key left of 1: console key (^), independent of the layout +#else + if (event->nativeScanCode() == 49) { // Key left of 1 (XKB keycode): console key (^), independent of the layout +#endif + return '^'; + } switch (event->key()) { case Qt::Key_Up: - return GPUDisplayFrontend::KEY_UP; + return GPUDisplayFrontendKeyVars::KEY_UP; case Qt::Key_Down: - return GPUDisplayFrontend::KEY_DOWN; + return GPUDisplayFrontendKeyVars::KEY_DOWN; case Qt::Key_Left: - return GPUDisplayFrontend::KEY_LEFT; + return GPUDisplayFrontendKeyVars::KEY_LEFT; case Qt::Key_Right: - return GPUDisplayFrontend::KEY_RIGHT; + return GPUDisplayFrontendKeyVars::KEY_RIGHT; case Qt::Key_PageUp: - return GPUDisplayFrontend::KEY_PAGEUP; + return GPUDisplayFrontendKeyVars::KEY_PAGEUP; case Qt::Key_PageDown: - return GPUDisplayFrontend::KEY_PAGEDOWN; + return GPUDisplayFrontendKeyVars::KEY_PAGEDOWN; case Qt::Key_Home: - return GPUDisplayFrontend::KEY_HOME; + return GPUDisplayFrontendKeyVars::KEY_HOME; case Qt::Key_End: - return GPUDisplayFrontend::KEY_END; + return GPUDisplayFrontendKeyVars::KEY_END; case Qt::Key_Insert: - return GPUDisplayFrontend::KEY_INSERT; + return GPUDisplayFrontendKeyVars::KEY_INSERT; case Qt::Key_Escape: - return GPUDisplayFrontend::KEY_ESCAPE; + return GPUDisplayFrontendKeyVars::KEY_ESCAPE; + case Qt::Key_Backspace: + return GPUDisplayFrontendKeyVars::KEY_BACKSPACE; + case Qt::Key_Delete: + return GPUDisplayFrontendKeyVars::KEY_DELETE; case Qt::Key_Return: case Qt::Key_Enter: - return GPUDisplayFrontend::KEY_ENTER; + return GPUDisplayFrontendKeyVars::KEY_ENTER; default: break; } if (event->key() >= Qt::Key_F1 && event->key() <= Qt::Key_F12) { - static_assert(GPUDisplayFrontend::KEY_F12 - GPUDisplayFrontend::KEY_F1 == Qt::Key_F12 - Qt::Key_F1); - return GPUDisplayFrontend::KEY_F1 + (event->key() - Qt::Key_F1); + static_assert(GPUDisplayFrontendKeyVars::KEY_F12 - GPUDisplayFrontendKeyVars::KEY_F1 == Qt::Key_F12 - Qt::Key_F1); + return GPUDisplayFrontendKeyVars::KEY_F1 + (event->key() - Qt::Key_F1); } const QString text = event->text(); if (text.size() == 1 && text[0].unicode() >= 32 && text[0].unicode() < 127) { // Printable ASCII, with shift applied diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h index e0da6423e94d2..2c01135ee15b9 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.h @@ -15,24 +15,18 @@ #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 QHBoxLayout; class QKeyEvent; - -QT_BEGIN_NAMESPACE -namespace Ui -{ -class GPUDisplayGUI; -} -QT_END_NAMESPACE +class QLabel; +class QLineEdit; namespace o2::gpu { @@ -42,24 +36,64 @@ class GPUDisplayGUI : public QMainWindow public: explicit GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent = nullptr); - ~GPUDisplayGUI() override; void bringToFront(); protected: void keyPressEvent(QKeyEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + bool eventFilter(QObject* watched, QEvent* event) override; // Ctrl + mouse wheel / Ctrl + 0 for all widgets of the window private: + struct ElementWidget { // Result of the create* functions for a GUI element + QWidget* widget = nullptr; + std::function set; // Set the element to a value from the display, signals are blocked + std::function setOptions; // Update range / choices of elements with dynamic options + }; + struct GUIElement { // By element id + std::function set; + std::function setOptions; + std::function get; // Value of the element from mState.cfg + std::function getOptions; // Called only while the display waits for it + std::optional applied; // Value last set from the display, empty after a change by the user: the display might not accept it + }; + void timerTick(); // Exchange updates / settings with the display void applySettings(); - static int32_t translateKey(const QKeyEvent* event); // Qt key → display key code, -1 if not forwarded + void applyElement(GUIElement& element); // Set the element from mState.cfg if the value changed + void updateInfoLabels(); + void fitStatusLabel(); // Height needed for the wrapped text, otherwise the size hint of the label makes the status bar too high + bool darkPalette() const; + void setDarkMode(bool dark); // Requests the color scheme from the platform theme + void colorSchemeChanged(); // Style sheets and colors depending on the color scheme + QWidget* buildElements(const std::vector& layout); // One frame per add_gui_binding call, with a labeled element per option + ElementWidget createCheckbox(const GPUDisplayGUIChannel::Element& el); + ElementWidget createTextbox(const GPUDisplayGUIChannel::Element& el); + ElementWidget createSlider(const GPUDisplayGUIChannel::Element& el); + ElementWidget createRadioButtons(const GPUDisplayGUIChannel::Element& el); + ElementWidget createTextEdit(const GPUDisplayGUIChannel::Element& el); + ElementWidget createButton(const GPUDisplayGUIChannel::Element& el); + ElementWidget createDropdown(const GPUDisplayGUIChannel::Element& el); + ElementWidget createMultiSlider(const GPUDisplayGUIChannel::Element& el); + QHBoxLayout* createSliderRow(ElementWidget& retVal, QWidget* slider, QLineEdit* edit, Qt::Alignment editAlignment = {}); + void setValue(uint32_t id, GPUDisplayGUIValue value); // Element changed by the user, sent with the next update + void setScale(float scale); // Font size, fixed element sizes, and check / radio indicators (not sent to the display) + static int32_t translateKey(const QKeyEvent* event); // Qt key → display key code, -1 if not forwarded - std::unique_ptr mUi; QTimer mTimer; GPUDisplayGUIChannel& mChannel; GPUDisplayGUIChannel::Update mUpdate; // Changes collected since the last tick GPUDisplayGUIChannel::State mState; // Last state received from the display uint64_t mLastSentId = 0; // Id of the last update sent uint64_t mLastAppliedId = 0; // Id of the last update for which the display state was received + std::vector mElements; + uint64_t mOptionsRevision = 0; // optionsRevision for which the option lambdas were called + std::vector> mScaleSetters; // Sizes of widgets and layouts that depend on the GUI scale + QLabel* mStatusLabel = nullptr; // Status line and, for infoTextTime of the channel, info message from the display, at the bottom of the window + QTimer mInfoTextTimer; + uint64_t mInfoTextCounter = 0; + bool mDarkMode = false; + float mScale = 1.f; + float mBaseFontSize = 0.f; // Point size of the font at scale 1 }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui deleted file mode 100644 index 77b2a7401be11..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.ui +++ /dev/null @@ -1,66 +0,0 @@ - - - GPUDisplayGUI - - - - 0 - 0 - 899 - 679 - - - - GPUDisplayGUI - - - - - - - - - Line width - - - - - - - 1 - - - 50 - - - Qt::Orientation::Horizontal - - - - - - - - 40 - 0 - - - - - - - - - - Qt::Orientation::Vertical - - - - - - - - - - - diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h index cf24951477d0a..c22ef970b694e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUIChannel.h @@ -15,48 +15,125 @@ #ifndef GPUDISPLAYGUICHANNEL_H #define GPUDISPLAYGUICHANNEL_H +#define GPUSETTINGS_DISPLAY +#define GPUSETTINGS_DISPLAYCONTROL #include "GPUSettings.h" +#include #include #include #include +#include #include #include #include +#include +#include #include namespace o2::gpu { +using GPUDisplayGUIValue = std::variant; // Value of a Qt GUI element +static_assert(sizeof(GPUDisplayGUIValue) == sizeof(std::variant), "GPUDisplayMultiSliderValue must not enlarge GPUDisplayGUIValue"); + +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; + GPUDisplayOptions mCfgO; + GPUDisplayControl mCfgC; + }; + enum class ElementType : uint8_t { CHECKBOX, + TEXTBOX, + SLIDER, + RADIO_BUTTONS, + DROPDOWN, + BUTTON, + TEXTEDIT, + MULTI_SLIDER }; + + 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 + double step = 0; // Slider with range / multi slider: positions are multiples of it (if > 0, required for the multi slider), multi slider handles at least that far apart + 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::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 - bool displayProcessingCurrentUpdate = false; // Display processes the update the GUI went to sleep with, GUI waits for the reply - bool stop = false; // GUI shall close - 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::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 + bool displayProcessingCurrentUpdate = false; // Display processes the update the GUI went to sleep with, GUI waits for the reply + bool stop = false; // GUI shall close + 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::chrono::milliseconds infoTextTime{5000}; // How long the GUI shows an info message: setInfoTime + setInfoTransition of the display + std::string windowName; // Name of the display window, set when starting the GUI + std::vector layout; // GUI elements, set when starting the GUI + std::function updatePosted; // Set by the display, called by the GUI (without holding mutex) after posting an update, wakes up the display }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx new file mode 100644 index 0000000000000..73b11a253c0d4 --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayHelp.cxx @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayHelp.cxx +/// \author David Rohr + +#include "GPUDisplay.h" +#include "GPULogging.h" + +#include + +using namespace o2::gpu; + +void GPUDisplay::PrintHelp() +{ + mInfoHelpTimer.ResetStart(); + for (const auto& line : mFrontend->getHelpText()) { + GPUInfo("%s", line.c_str()); + } +} + +void GPUDisplay::PrintGLHelpText(float colorValue) +{ + const auto& lines = mFrontend->getHelpText(); + const int32_t maxChars = mBackend->mFontCharWidth > 0.f ? (mBackend->mScreenWidth / mFrontend->getDisplayScale() - 80.f) / mBackend->mFontCharWidth : 0; // Characters fitting between the margins, 0 = unknown (no Freetype font), no wrapping + uint32_t row = 0; + auto print = [&](const char* s, uint32_t nCharacters, uint32_t indent) { GLPrintText(s, 40.f + indent * mBackend->mFontCharWidth, 35 + std::max(20, mDrawTextFontSize + 4) * (1 + row++), colorValue, colorValue, colorValue, mInfoHelpTimer.GetCurrentElapsedTime() >= 5 ? (6 - mInfoHelpTimer.GetCurrentElapsedTime()) : 1, nCharacters, false); }; + for (const auto& line : lines) { + if (maxChars <= 0 || line.size() <= (size_t)maxChars) { + print(line.c_str(), 0, 0); + continue; + } + const size_t textStart = line.find(": "); + const size_t indent = textStart != std::string::npos && textStart + 2 < (size_t)maxChars / 2 ? textStart + 2 : 0; // Continuation lines aligned with the text after the keys, if that leaves enough space + size_t pos = 0; // Start of the part not yet printed + for (bool first = true;; first = false) { // Wrap at the last space that fits, or hard if there is none (also if it is only in the keys) + const size_t lineIndent = first ? 0 : indent, available = maxChars - lineIndent; + if (line.size() - pos <= available) { + print(line.c_str() + pos, line.size() - pos, lineIndent); + break; + } + size_t cut = line.rfind(' ', pos + available); + if (cut == std::string::npos || cut <= pos + (first ? indent : 0)) { + cut = pos + available; + } + print(line.c_str() + pos, cut - pos, lineIndent); + pos = line[cut] == ' ' ? cut + 1 : cut; + } + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc index b6ac78b31f315..584c3578684d8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc @@ -17,6 +17,7 @@ #include "GPUDisplay.h" #include "GPULogging.h" +#include "frontend/GPUDisplayConsole.h" namespace o2::gpu { @@ -29,6 +30,9 @@ void GPUDisplay::SetInfo(Args... args) snprintf(mInfoText2, 1024, args...); #pragma GCC diagnostic pop GPUInfo("%s", mInfoText2); + mFrontend->mConsole->consoleInfo(std::get<0>(std::tuple(args...)), mInfoText2); + mGUIInfoText = mInfoText2; + mGUIInfoTextCounter++; mInfoText2Timer.ResetStart(); } } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx deleted file mode 100644 index b7c5c55cfdf22..0000000000000 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ /dev/null @@ -1,564 +0,0 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. -// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. -// All rights not expressly granted are reserved. -// -// This software is distributed under the terms of the GNU General Public -// License v3 (GPL Version 3), copied verbatim in the file "COPYING". -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// \file GPUDisplayKeys.cxx -/// \author David Rohr - -#include "GPUDisplay.h" -#include "frontend/GPUDisplayInfo.inc" - -using namespace o2::gpu; - -const char* HelpText[] = { - "[ESC] Quit", - "[n] Next event", - "[r] Reset Display Settings", - "[l] / [k] / [J] Draw single sector (next / previous sector), draw related sectors (same plane in phi)", - "[;] / [:] Show splitting of TPC in sectors by extruding volume, [:] resets", - "[#] Invert colors", - "[y] / [Y] / ['] / [X] / [M] Start Animation, Add / remove Animation point, Reset Points, Cycle animation camera mode (resets)", - "[>] / [<] Toggle config interpolation during Animation / change Animation interval (via movement)", - "[g] Draw Grid", - "[i] Project onto XY-plane", - "[x] Exclude Clusters used in the tracking steps enabled for visualization ([1]-[8])", - "[.] Exclude rejected tracks", - "[c] Mark flagged clusters (splitPad = 0x1, splitTime = 0x2, edge = 0x4, singlePad = 0x8, rejectDistance = 0x10, rejectErr = 0x20", - "[z] Mark fake attached clusters", - "[B] Mark clusters attached as adjacent", - "[L] / [K] Draw single collisions (next / previous)", - "[C] Colorcode clusters of different collisions", - "[v] Hide rejected clusters from tracks", - "[j] Show tracks segments propagated to adjacent sector in different color / splt CE tracks", - "[u] Cycle through track filter", - "[E] / [G] Extrapolate tracks / loopers", - "[t] / [T] Take Screenshot / Record Animation to pictures", - "[Z] Change screenshot resolution (scaling factor)", - "[S] / [A] / [D] Enable or disable smoothing of points / smoothing of lines / depth buffer", - "[W] / [U] / [V] Toggle anti-aliasing (MSAA at raster level / change downsampling FSAA factor) / toggle VSync", - "[F] / [_] / [R] Switch mFullScreen / Maximized window / FPS limit (none, refresh rate, fpsRate)", - "[I] Enable / disable GL indirect draw", - "[o] / [p] / [O] / [P] Save / restore current camera position / Animation path", - "[h] Print Help", - "[H] Show info texts", - "[q] Start / Stop Qt GUI", - "[w] / [s] / [a] / [d] Zoom / Strafe Left and Right", - "[pgup] / [pgdn] Strafe up / down", - "[e] / [f] Rotate left / right", - "[+] / [-] Increase / decrease point size (Hold SHIFT for lines)", - "[b] Change FOV (field of view)", - "[m] Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines)", - "[MOUSE 1] Look around", - "[MOUSE 2] Strafe camera", - "[MOUSE 1+2] Zoom / Rotate", - "[SHIFT] Slow Zoom / Move / Rotate", - "[ALT] / [CTRL] / [ENTER] Focus camera on origin / orient y-axis upwards (combine with [SHIFT] to lock) / Cycle through modes", - "[RCTRL] / [RALT] Rotate model instead of camera / rotate TPC around beamline", - "[1] ... [8] / [N] Enable display of clusters, preseeds, seeds, starthits, tracklets, tracks, extrapolated tracks, merged tracks / Show assigned clusters in colors", - "[F1] / [F2] / [F3] / [F4] Enable / disable drawing of TPC / TRD / TOF / ITS", - "[SHIFT] + [F1] to [F4] Enable / disable track detector filter", - "[SHIFT] + [F12] Switch track detector filter between AND and OR mode" - // FREE: [SPACE] [Q] - // Test setting: ^ -}; - -void GPUDisplay::PrintHelp() -{ - mInfoHelpTimer.ResetStart(); - for (uint32_t i = 0; i < sizeof(HelpText) / sizeof(HelpText[0]); i++) { - GPUInfo("%s", HelpText[i]); - } -} - -void GPUDisplay::HandleKey(uint8_t key, bool doNotHandleConfigChanges) -{ - const GPUSettingsDisplayHeavy oldCfgH = mCfgH; - const GPUSettingsDisplayLight oldCfgL = mCfgL; - const GPUSettingsDisplayRenderer oldCfgR = mCfgR; - if (key == 'n') { - mFrontend->mDisplayControl = 1; - SetInfo("Showing next event", 1); - } else if (key == 27 || key == mFrontend->KEY_ESCAPE) { - mFrontend->mDisplayControl = 2; - SetInfo("Exiting", 1); - } else if (key == 'r') { - mResetScene = true; - SetInfo("View reset", 1); - } else if (key == mFrontend->KEY_ALT && mFrontend->mKeysShift[mFrontend->KEY_ALT]) { - mCfgR.camLookOrigin ^= 1; - mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * mCfgR.camYUp; - SetInfo("Camera locked on origin: %s", mCfgR.camLookOrigin ? "enabled" : "disabled"); - } else if (key == mFrontend->KEY_CTRL && mFrontend->mKeysShift[mFrontend->KEY_CTRL]) { - mCfgR.camYUp ^= 1; - mCfgR.cameraMode = mCfgR.camLookOrigin + 2 * mCfgR.camYUp; - SetInfo("Camera locked on y-axis facing upwards: %s", mCfgR.camYUp ? "enabled" : "disabled"); - } else if (key == mFrontend->KEY_ENTER) { - mCfgR.cameraMode++; - if (mCfgR.cameraMode == 4) { - mCfgR.cameraMode = 0; - } - mCfgR.camLookOrigin = mCfgR.cameraMode & 1; - mCfgR.camYUp = mCfgR.cameraMode & 2; - const char* modeText[] = {"Descent (free movement)", "Focus locked on origin (y-axis forced upwards)", "Spectator (y-axis forced upwards)", "Focus locked on origin (with free rotation)"}; - SetInfo("Camera mode %d: %s", mCfgR.cameraMode, modeText[mCfgR.cameraMode]); - } else if (key == mFrontend->KEY_ALT) { - mFrontend->mKeys[mFrontend->KEY_CTRL] = false; // Release CTRL with alt, to avoid orienting along y automatically! - } else if (key == 'l') { - if (mCfgL.drawSector >= (mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1))) { - mCfgL.drawSector = -1; - SetInfo("Showing all sectors", 1); - } else { - mCfgL.drawSector++; - SetInfo("Showing sector %d", mCfgL.drawSector); - } - } else if (key == 'k') { - if (mCfgL.drawSector <= -1) { - mCfgL.drawSector = mCfgL.drawRelatedSectors ? (NSECTORS / 4 - 1) : (NSECTORS - 1); - } else { - mCfgL.drawSector--; - } - if (mCfgL.drawSector == -1) { - SetInfo("Showing all sectors", 1); - } else { - SetInfo("Showing sector %d", mCfgL.drawSector); - } - } else if (key == 'J') { - mCfgL.drawRelatedSectors ^= 1; - SetInfo("Drawing of related sectors %s", mCfgL.drawRelatedSectors ? "enabled" : "disabled"); - } else if (key == 'L') { - if (mCfgL.showCollision >= mNCollissions - 1) { - mCfgL.showCollision = -1; - SetInfo("Showing all collisions", 1); - } else { - mCfgL.showCollision++; - SetInfo("Showing collision %d / %d", mCfgL.showCollision, mNCollissions); - } - } else if (key == 'K') { - if (mCfgL.showCollision <= -1) { - mCfgL.showCollision = mNCollissions - 1; - } else { - mCfgL.showCollision--; - } - if (mCfgL.showCollision == -1) { - SetInfo("Showing all collisions", 1); - } else { - SetInfo("Showing collision %d / %d", mCfgL.showCollision, mNCollissions); - } - } else if (key == 'F') { - mCfgR.fullScreen ^= 1; - SetInfo("Toggling full screen (%d)", (int32_t)mCfgR.fullScreen); - } else if (key == '_') { - mCfgR.maximized ^= 1; - SetInfo("Toggling Maximized window (%d)", (int32_t)mCfgR.maximized); - } else if (key == 'R') { - mCfgR.fpsLimit = (mCfgR.fpsLimit + 1) % 3; - if (mCfgR.fpsLimit == GPUDisplayBackend::FPS_LIMIT_RATE) { - SetInfo("FPS limit: %d FPS", mCfgR.fpsRate); - } else if (mCfgR.fpsLimit == GPUDisplayBackend::FPS_LIMIT_VSYNC) { - char rate[32] = "unknown rate"; - if (mFrontend->getRefreshRate() > 0.f) { - snprintf(rate, sizeof(rate), "%.1f Hz", mFrontend->getRefreshRate()); - } - const int32_t mode = mBackend->fpsLimitMode(); - if (mode == GPUDisplayBackend::FPS_LIMIT_NONE) { - SetInfo("FPS limit: refresh rate (%s) - VSync disabled, no limit", rate); - } else if (mode == GPUDisplayBackend::FPS_LIMIT_RATE) { - SetInfo("FPS limit: refresh rate (%s) - not supported by the backend, fallback to %d FPS", rate, mCfgR.fpsRate); - } else { - SetInfo("FPS limit: refresh rate (%s)", rate); - } - } else { - SetInfo("FPS limit: none"); - } - } else if (key == 'H') { - mPrintInfoText += 1; - mPrintInfoText &= 3; - SetInfo("Info text display - console: %s, onscreen %s", (mPrintInfoText & 2) ? "enabled" : "disabled", (mPrintInfoText & 1) ? "enabled" : "disabled"); - } else if (key == 'j') { - if (mCfgH.separateExtrapolatedTracks) { - mCfgH.splitCETracks ^= 1; - } - mCfgH.separateExtrapolatedTracks ^= 1; - SetInfo("Seperated display of tracks propagated to adjacent sectors %s / of CE tracks %s", mCfgH.separateExtrapolatedTracks ? "enabled" : "disabled", mCfgH.splitCETracks ? "enabled" : "disabled"); - } else if (key == 'c') { - if (mCfgH.markClusters == 0) { - mCfgH.markClusters = 1; - } else if (mCfgH.markClusters >= 0x20) { - mCfgH.markClusters = 0; - } else { - mCfgH.markClusters <<= 1; - } - SetInfo("Cluster flag highlight mask set to %d (%s)", mCfgH.markClusters, - mCfgH.markClusters == 0 ? "off" : mCfgH.markClusters == 1 ? "split pad" : mCfgH.markClusters == 2 ? "split time" : mCfgH.markClusters == 4 ? "edge" : mCfgH.markClusters == 8 ? "singlePad" : mCfgH.markClusters == 0x10 ? "reject distance" : "reject error"); - } else if (key == 'z') { - mCfgH.markFakeClusters ^= 1; - SetInfo("Marking fake clusters: %s", mCfgH.markFakeClusters ? "on" : "off"); - } else if (key == 'b') { - if ((mCfgR.fov += 5) > 175) { - mCfgR.fov = 5; - } - SetInfo("Set FOV to %f", mCfgR.fov); - } else if (key == 'm') { -#ifdef GPUCA_DISPLAY_OPENGL_CORE - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - SetInfo("OpenGL compat profile not available, using core profile", 1); - } else -#endif - { - mCfgR.rendererCompatSmoothing ^= 1; - SetInfo("Using renderer path for %s profile", mCfgR.rendererCompatSmoothing ? "compat" : "core (shader)"); - } - } else if (key == 'B') { - mCfgH.markAdjacentClusters++; - if (mCfgH.markAdjacentClusters == 5) { - mCfgH.markAdjacentClusters = 7; - } - if (mCfgH.markAdjacentClusters == 9) { - mCfgH.markAdjacentClusters = 15; - } - if (mCfgH.markAdjacentClusters == 17) { - mCfgH.markAdjacentClusters = 31; - } - if (mCfgH.markAdjacentClusters == 34) { - mCfgH.markAdjacentClusters = 0; - } - if (mCfgH.markAdjacentClusters == 33) { - SetInfo("Marking protected clusters (%d)", mCfgH.markAdjacentClusters); - } else if (mCfgH.markAdjacentClusters == 32) { - SetInfo("Marking removable clusters (%d)", mCfgH.markAdjacentClusters); - } else { - SetInfo("Marking adjacent clusters (%d): rejected %s, tube %s, looper leg %s, low Pt %s, high incl %s", mCfgH.markAdjacentClusters, (mCfgH.markAdjacentClusters & 1) ? "yes" : " no", (mCfgH.markAdjacentClusters & 2) ? "yes" : " no", (mCfgH.markAdjacentClusters & 4) ? "yes" : " no", (mCfgH.markAdjacentClusters & 8) ? "yes" : " no", (mCfgH.markAdjacentClusters & 16) ? "yes" : " no"); - } - } else if (key == 'C') { - mCfgL.colorCollisions ^= 1; - SetInfo("Color coding of collisions %s", mCfgL.colorCollisions ? "enabled" : "disabled"); - } else if (key == 'N') { - mCfgL.colorClusters ^= 1; - SetInfo("Color coding for seed / trrack attachmend %s", mCfgL.colorClusters ? "enabled" : "disabled"); - } else if (key == 'E') { - mCfgL.propagateTracks += 1; - if (mCfgL.propagateTracks == 4) { - mCfgL.propagateTracks = 0; - } - const char* infoText[] = {"Hits connected", "Hits connected and propagated to vertex", "Reconstructed track propagated inwards and outwards", "Monte Carlo track"}; - SetInfo("Display of propagated tracks: %s", infoText[mCfgL.propagateTracks]); - } else if (key == 'G') { - mCfgH.propagateLoopers ^= 1; - SetInfo("Propagation of loopers %s", mCfgH.propagateLoopers ? "enabled" : "disabled"); - } else if (key == 'v') { - mCfgH.hideRejectedClusters ^= 1; - SetInfo("Rejected clusters are %s", mCfgH.hideRejectedClusters ? "hidden" : "shown"); - } else if (key == 'i') { - mCfgH.projectXY ^= 1; - SetInfo("Projection onto xy plane %s", mCfgH.projectXY ? "enabled" : "disabled"); - } else if (key == 'S') { - mCfgL.smoothPoints ^= true; - SetInfo("Smoothing of points %s", mCfgL.smoothPoints ? "enabled" : "disabled"); - } else if (key == 'A') { - mCfgL.smoothLines ^= true; - SetInfo("Smoothing of lines %s", mCfgL.smoothLines ? "enabled" : "disabled"); - } else if (key == 'D') { - mCfgL.depthBuffer ^= true; - SetInfo("Depth buffer (z-buffer, %u bits) %s", mBackend->DepthBits(), mCfgL.depthBuffer ? "enabled" : "disabled"); - mBackend->setDepthBuffer(); - } else if (key == 'W') { - mCfgR.drawQualityMSAA *= 2; - if (mCfgR.drawQualityMSAA < 2) { - mCfgR.drawQualityMSAA = 2; - } - if (mCfgR.drawQualityMSAA > 16 || mCfgR.drawQualityMSAA > mBackend->getMaxMSAA()) { - mCfgR.drawQualityMSAA = 0; - } - SetInfo("Multisampling anti-aliasing factor set to %d", mCfgR.drawQualityMSAA); - } else if (key == 'U') { - mCfgR.drawQualityDownsampleFSAA++; - if (mCfgR.drawQualityDownsampleFSAA == 1) { - mCfgR.drawQualityDownsampleFSAA = 2; - } - if (mCfgR.drawQualityDownsampleFSAA == 5) { - mCfgR.drawQualityDownsampleFSAA = 0; - } - SetInfo("Downsampling anti-aliasing factor set to %d", mCfgR.drawQualityDownsampleFSAA); - } else if (key == 'V') { - mCfgR.drawQualityVSync ^= true; - SetInfo("VSync: %s", mCfgR.drawQualityVSync ? "enabled" : "disabled"); - } else if (key == 'I') { - mCfgR.useGLIndirectDraw ^= true; - SetInfo("OpenGL Indirect Draw %s", mCfgR.useGLIndirectDraw ? "enabled" : "disabled"); - } else if (key == ';') { - mCfgH.xAdd += 60; - mCfgH.zAdd += 60; - SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); - } else if (key == ':') { - mCfgH.xAdd -= 60; - mCfgH.zAdd -= 60; - if (mCfgH.zAdd < 0 || mCfgH.xAdd < 0) { - mCfgH.zAdd = mCfgH.xAdd = 0; - } - SetInfo("TPC sector separation: %f %f", mCfgH.xAdd, mCfgH.zAdd); - } else if (key == '#') { - mCfgL.invertColors ^= 1; - } else if (key == 'g') { - mCfgL.drawGrid ^= 1; - SetInfo("Fast Cluster Search Grid %s", mCfgL.drawGrid ? "shown" : "hidden"); - } else if (key == 'x') { - mCfgL.excludeClusters = (mCfgL.excludeClusters + 1) % 3; - SetInfo(mCfgL.excludeClusters ? "Clusters of selected category are excluded from display (%d)" : "Clusters are shown", mCfgL.excludeClusters); - } else if (key == '.') { - mCfgH.hideRejectedTracks ^= 1; - SetInfo("Rejected tracks are %s", mCfgH.hideRejectedTracks ? "hidden" : "shown"); - } else if (key == '1') { - mCfgL.drawClusters ^= 1; - } else if (key == '2') { - mCfgL.drawInitLinks ^= 1; - } else if (key == '3') { - mCfgL.drawLinks ^= 1; - } else if (key == '4') { - mCfgL.drawSeeds ^= 1; - } else if (key == '5') { - mCfgL.drawTracklets ^= 1; - } else if (key == '6') { - mCfgL.drawTracks ^= 1; - } else if (key == '7') { - mCfgL.drawExtrapolatedTracks ^= 1; - } else if (key == '8') { - mCfgL.drawFinal ^= 1; - } else if (key == mFrontend->KEY_F1) { - if (mFrontend->mKeysShift[mFrontend->KEY_F1]) { - mCfgH.drawTPCTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawTPC ^= 1; - SetInfo("Showing TPC Clusters: %d", (int32_t)mCfgL.drawTPC); - } - } else if (key == mFrontend->KEY_F2) { - if (mFrontend->mKeysShift[mFrontend->KEY_F2]) { - mCfgH.drawTRDTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawTRD ^= 1; - SetInfo("Showing TRD Tracklets: %d", (int32_t)mCfgL.drawTRD); - } - } else if (key == mFrontend->KEY_F3) { - if (mFrontend->mKeysShift[mFrontend->KEY_F3]) { - mCfgH.drawTOFTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawTOF ^= 1; - SetInfo("Showing TOF Hits: %d", (int32_t)mCfgL.drawTOF); - } - } else if (key == mFrontend->KEY_F4) { - if (mFrontend->mKeysShift[mFrontend->KEY_F4]) { - mCfgH.drawITSTracks ^= 1; - SetInfo("Track Filter Mask: TPC:%d TRD:%d TOF:%d ITS:%d", (int32_t)mCfgH.drawTPCTracks, (int32_t)mCfgH.drawTRDTracks, (int32_t)mCfgH.drawTOFTracks, (int32_t)mCfgH.drawITSTracks); - } else { - mCfgL.drawITS ^= 1; - SetInfo("Showing ITS Clusters: %d", (int32_t)mCfgL.drawITS); - } - } else if (key == mFrontend->KEY_F12 && mFrontend->mKeysShift[mFrontend->KEY_F12]) { - mCfgH.drawTracksAndFilter ^= 1; - SetInfo("Track filter: %s", mCfgH.drawTracksAndFilter ? "AND" : "OR"); - } else if (key == 't') { - static int32_t nScreenshot = 1; - char fname[32]; - snprintf(fname, 32, "screenshot%d.bmp", nScreenshot++); - mRequestScreenshot = true; - mScreenshotFile = fname; - SetInfo("Taking screenshot (%s)", fname); - } else if (key == 'Z') { - mCfgR.screenshotScaleFactor += 1; - if (mCfgR.screenshotScaleFactor == 5) { - mCfgR.screenshotScaleFactor = 1; - } - SetInfo("Screenshot scaling factor set to %d", mCfgR.screenshotScaleFactor); - } else if (key == 'y' || key == 'T') { - if (mAnimateVectors[0].size() > 1) { - if ((mAnimateScreenshot = (key == 'T'))) { - mAnimationExport++; - } - startAnimation(); - SetInfo("Starting Animation", 1); - } else { - SetInfo("Insufficient Animation points to start Animation", 1); - } - } else if (key == '>') { - mAnimationChangeConfig ^= 1; - SetInfo("Interpolating visualization settings during Animation %s", mAnimationChangeConfig ? "enabled" : "disabled"); - } else if (key == 'Y') { - setAnimationPoint(); - SetInfo("Added Animation point (%d points, %6.2f seconds)", (int32_t)mAnimateVectors[0].size(), mAnimateVectors[0].back()); - } else if (key == 'X') { - resetAnimation(); - SetInfo("Reset Animation points", 1); - } else if (key == '\'') { - removeAnimationPoint(); - SetInfo("Removed Animation point", 1); - } else if (key == 'M') { - mCfgL.animationMode++; - if (mCfgL.animationMode == 7) { - mCfgL.animationMode = 0; - } - resetAnimation(); - if (mCfgL.animationMode == 6) { - SetInfo("Animation mode %d - Centered on origin", mCfgL.animationMode); - } else { - SetInfo("Animation mode %d - Position: %s, Direction: %s", mCfgL.animationMode, (mCfgL.animationMode & 2) ? "Spherical (spherical rotation)" : (mCfgL.animationMode & 4) ? "Spherical (Euler angles)" : "Cartesian", (mCfgL.animationMode & 1) ? "Euler angles" : "Quaternion"); - } - } else if (key == 'u') { - mCfgH.trackFilter = (mCfgH.trackFilter + 1) % (mConfig.filterMacros.size() + 1); - mUpdateTrackFilter = true; - SetInfo("Track filter: %s", mCfgH.trackFilter == 0 ? "None" : mConfig.filterMacros[mCfgH.trackFilter - 1].c_str()); - } else if (key == 'o') { - FILE* ftmp = fopen("glpos.tmp", "w+b"); - if (ftmp) { - int32_t retval = fwrite(&mViewMatrix, sizeof(mViewMatrix), 1, ftmp); - if (retval != 1) { - GPUError("Error writing position to file"); - } else { - GPUInfo("Position stored to file"); - } - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Camera position stored to file", 1); - } else if (key == 'p') { - FILE* ftmp = fopen("glpos.tmp", "rb"); - if (ftmp) { - int32_t retval = fread(&mViewMatrix, 1, sizeof(mViewMatrix), ftmp); - if (retval == sizeof(mViewMatrix)) { - GPUInfo("Position read from file"); - } else { - GPUError("Error reading position from file"); - } - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Camera position loaded from file", 1); - } else if (key == 'O') { - FILE* ftmp = fopen("glanimation.tmp", "w+b"); - if (ftmp) { - fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp); - int32_t size = mAnimateVectors[0].size(); - fwrite(&size, sizeof(size), 1, ftmp); - for (int32_t i = 0; i < 9; i++) { - fwrite(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); - } - fwrite(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); - fclose(ftmp); - } else { - GPUError("Error opening file"); - } - SetInfo("Animation path stored to file %s", "glanimation.tmp"); - } else if (key == 'P') { - FILE* ftmp = fopen("glanimation.tmp", "rb"); - if (ftmp) { - int32_t retval = fread(&mCfgL, sizeof(mCfgL), 1, ftmp); - int32_t size; - retval += fread(&size, sizeof(size), 1, ftmp); - for (int32_t i = 0; i < 9; i++) { - mAnimateVectors[i].resize(size); - retval += fread(mAnimateVectors[i].data(), sizeof(mAnimateVectors[i][0]), size, ftmp); - } - mAnimateConfig.resize(size); - retval += fread(mAnimateConfig.data(), sizeof(mAnimateConfig[0]), size, ftmp); - (void)retval; // disable unused warning - fclose(ftmp); - updateConfig(); - } else { - GPUError("Error opening file"); - } - SetInfo("Animation path loaded from file %s", "glanimation.tmp"); - } else if (key == 'h') { - if (mPrintInfoTextAlways) { - mPrintInfoTextAlways = false; - SetInfo("Showing help text disabled", 1); - } else if (mInfoHelpTimer.IsRunning()) { - mPrintInfoTextAlways = true; - mInfoHelpTimer.Reset(); - SetInfo("Showing help text until disabled", 1); - } else { - PrintHelp(); - SetInfo("Showing help text", 1); - } - } else if (key == 'q') { - if (mFrontend->isGUIRunning()) { - SetInfo("Stopping GUI", 1); - mFrontend->stopGUI(); - } else { - SetInfo("Starting GUI", 1); - mFrontend->startGUI(); - } - } - /* - else if (key == '^') - { - mTestSetting++; - SetInfo("Debug test variable set to %d", mTestSetting); - } - */ - - if (!doNotHandleConfigChanges) { - checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR); - } -} - -void GPUDisplay::checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCfgH, const GPUSettingsDisplayLight& oldCfgL, const GPUSettingsDisplayRenderer& oldCfgR) -{ - if (memcmp((void*)&oldCfgH, (void*)&mCfgH, sizeof(mCfgH)) != 0) { - mUpdateEventData = true; - } - if (mCfgL.propagateTracks != 0 && !mTracksArePropagated) { - mUpdateVertexLists = true; - } - if (memcmp((void*)&oldCfgL, (void*)&mCfgL, sizeof(mCfgL)) != 0 || memcmp((void*)&oldCfgR, (void*)&mCfgR, sizeof(mCfgR)) != 0) { - mUpdateDrawCommands = true; - } - if (oldCfgR.drawQualityMSAA != mCfgR.drawQualityMSAA || oldCfgR.drawQualityDownsampleFSAA != mCfgR.drawQualityDownsampleFSAA || oldCfgL.depthBuffer != mCfgL.depthBuffer || oldCfgR.screenshotScaleFactor != mCfgR.screenshotScaleFactor) { - mUpdateRenderPipeline = true; - } - - if (oldCfgR.drawQualityVSync != mCfgR.drawQualityVSync) { - mFrontend->SetVSync(mCfgR.drawQualityVSync); - mBackend->SetVSync(mCfgR.drawQualityVSync); - } - if (oldCfgR.fullScreen != mCfgR.fullScreen) { - mFrontend->SwitchFullscreen(mCfgR.fullScreen); - } - if (oldCfgR.maximized != mCfgR.maximized) { - mFrontend->ToggleMaximized(mCfgR.maximized); - } - if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { - mUpdateEventData = true; - } -} - -void GPUDisplay::HandleExternalGUIKey(int32_t key) -{ - // GPUError("key %d '%c'", key, (char) key); - - bool shifted = key >= 'A' && key <= 'Z'; - int32_t press = key; - if (press >= 'a' && press <= 'z') { - press += 'A' - 'a'; - } - bool oldShift = mFrontend->mKeysShift[press]; - mFrontend->mKeysShift[press] = shifted; - HandleKey(key, true); - mFrontend->mKeysShift[press] = oldShift; -} - -void GPUDisplay::PrintGLHelpText(float colorValue) -{ - for (uint32_t i = 0; i < sizeof(HelpText) / sizeof(HelpText[0]); i++) { - OpenGLPrint(HelpText[i], 40.f, 35 + std::max(20, mDrawTextFontSize + 4) * (1 + i), colorValue, colorValue, colorValue, mInfoHelpTimer.GetCurrentElapsedTime() >= 5 ? (6 - mInfoHelpTimer.GetCurrentElapsedTime()) : 1, false); - } -} diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index bb2bacfc08434..866988aae669e 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -17,108 +17,265 @@ #include "GPUCommonMath.h" #include "HandMadeMath.h" +#include +#include + 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]}}}; } +static hmm_mat4 cameraViewMatrix(const float (&xyz)[3], const float (&angles)[3]) // Inverse of calcXYZ +{ + const float toDeg = 180.f / CAMath::Pi(); + return HMM_Rotate(-angles[2] * toDeg, {{0, 0, 1}}) * HMM_Rotate(-angles[0] * toDeg, {{1, 0, 0}}) * HMM_Rotate(angles[1] * toDeg, {{0, 1, 0}}) * HMM_Translate({{-xyz[0], -xyz[1], -xyz[2]}}); +} + void GPUDisplay::setAnimationPoint() { - if (mCfgL.animationMode & 4) // Spherical + StopAnimation(); + auto& times = mCfgC.animationPoints.values; + times.emplace_back(times.size() ? times.back() + ((int32_t)(mCfgO.animationDelay * 20)) / 20.f : 0.f); + mAnimationVectors.push_back({{mXYZ[0], mXYZ[1], mXYZ[2], mAngle[0], mAngle[1], mAngle[2]}, mCfgL}); + mCfgC.animationPoints.selected = -1; +} + +void GPUDisplay::animationPointValues(const animationVector& p, int32_t mode, const float* last, float (&vals)[8]) +{ + const float xyz[3] = {p.v[0], p.v[1], p.v[2]}, angles[3] = {p.v[3], p.v[4], p.v[5]}; + if (mode & 4) // Spherical { - float rxy = sqrtf(mXYZ[0] * mXYZ[0] + mXYZ[2] * mXYZ[2]); - float anglePhi = atan2f(mXYZ[0], mXYZ[2]); - float angleTheta = atan2f(mXYZ[1], rxy); - if (mAnimateVectors[0].size()) { - mAnimationCloseAngle(anglePhi, mAnimateVectors[2].back()); - } - if (mAnimateVectors[0].size()) { - mAnimationCloseAngle(angleTheta, mAnimateVectors[3].back()); - } - mAnimateVectors[1].emplace_back(0); - mAnimateVectors[2].emplace_back(anglePhi); - mAnimateVectors[3].emplace_back(angleTheta); + float rxy = sqrtf(xyz[0] * xyz[0] + xyz[2] * xyz[2]); + float anglePhi = atan2f(xyz[0], xyz[2]); + float angleTheta = atan2f(xyz[1], rxy); + if (last) { + mAnimationCloseAngle(anglePhi, last[1]); + mAnimationCloseAngle(angleTheta, last[2]); + } + vals[0] = 0; + vals[1] = anglePhi; + vals[2] = angleTheta; } else { for (int32_t i = 0; i < 3; i++) { - mAnimateVectors[i + 1].emplace_back(mXYZ[i]); + vals[i] = xyz[i]; } // Cartesian } - float r = sqrtf(mXYZ[0] * mXYZ[0] + mXYZ[1] * mXYZ[1] + mXYZ[2] * mXYZ[2]); - mAnimateVectors[4].emplace_back(r); - if (mCfgL.animationMode & 1) // Euler-angles + vals[3] = sqrtf(xyz[0] * xyz[0] + xyz[1] * xyz[1] + xyz[2] * xyz[2]); + if (mode & 1) // Euler-angles { for (int32_t i = 0; i < 3; i++) { - float newangle = mAngle[i]; - if (mAnimateVectors[0].size()) { - mAnimationCloseAngle(newangle, mAnimateVectors[i + 5].back()); + float newangle = angles[i]; + if (last) { + mAnimationCloseAngle(newangle, last[i + 4]); } - mAnimateVectors[i + 5].emplace_back(newangle); + vals[i + 4] = newangle; } - mAnimateVectors[8].emplace_back(0); + vals[7] = 0; } else { // Quaternions float v[4]; - createQuaternionFromMatrix(v, &mViewMatrix->Elements[0][0]); - if (mAnimateVectors[0].size()) { - mAnimateCloseQuaternion(v, mAnimateVectors[5].back(), mAnimateVectors[6].back(), mAnimateVectors[7].back(), mAnimateVectors[8].back()); + const hmm_mat4 viewMatrix = cameraViewMatrix(xyz, angles); + createQuaternionFromMatrix(v, &viewMatrix.Elements[0][0]); + if (last) { + mAnimateCloseQuaternion(v, last[4], last[5], last[6], last[7]); } for (int32_t i = 0; i < 4; i++) { - mAnimateVectors[i + 5].emplace_back(v[i]); + vals[i + 4] = v[i]; } } - float delay = 0.f; - if (mAnimateVectors[0].size()) { - delay = mAnimateVectors[0].back() + ((int32_t)(mAnimationDelay * 20)) / 20.f; +} + +static hmm_mat4 lookAtMatrix(const float (&eye)[3], const float (&target)[3]) // HMM_LookAt with y up, z up when looking along y (the cross product with y would be 0) +{ + const float d[3] = {target[0] - eye[0], target[1] - eye[1], target[2] - eye[2]}; + const bool alongY = d[0] * d[0] + d[2] * d[2] <= 1e-10f * (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + return HMM_LookAt({{eye[0], eye[1], eye[2]}}, {{target[0], target[1], target[2]}}, alongY ? hmm_vec3{{0, 0, 1}} : hmm_vec3{{0, 1, 0}}); +} + +void GPUDisplay::setCamera(const float (&xyz)[3], const float (&angles)[3]) +{ + *mViewMatrix = cameraViewMatrix(xyz, angles); + calcXYZ(&mViewMatrix->Elements[0][0]); +} + +bool GPUDisplay::lookAt(const float (&target)[3]) +{ + if (target[0] == mXYZ[0] && target[1] == mXYZ[1] && target[2] == mXYZ[2]) { + return false; } - mAnimateVectors[0].emplace_back(delay); - mAnimateConfig.emplace_back(mCfgL); + *mViewMatrix = lookAtMatrix(mXYZ, target); + calcXYZ(&mViewMatrix->Elements[0][0]); + return true; +} + +void GPUDisplay::rotateCamera(const float (&angles)[3]) +{ + *mViewMatrix = cameraRotation(angles) * *mViewMatrix; + calcXYZ(&mViewMatrix->Elements[0][0]); +} + +hmm_mat4 GPUDisplay::cameraRotation(const float (&angles)[3]) const +{ + return HMM_Rotate(angles[0], {{0, 1, 0}}) * HMM_Rotate(angles[1] * mYFactor, {{1, 0, 0}}) * HMM_Rotate(angles[2] * mYFactor, {{0, 0, 1}}); +} + +void GPUDisplay::addAnimationPoint(const float (&xyz)[3], const float (&angles)[3]) +{ + const hmm_mat4 oldViewMatrix = *mViewMatrix; + setCamera(xyz, angles); + setAnimationPoint(); // From mXYZ / mAngle set by setCamera, as for the current camera + *mViewMatrix = oldViewMatrix; + calcXYZ(&mViewMatrix->Elements[0][0]); } void GPUDisplay::resetAnimation() { - for (int32_t i = 0; i < 9; i++) { - mAnimateVectors[i].clear(); + mAnimationVectors.clear(); + mCfgC.animationPoints.values.clear(); + mCfgC.animationPoints.selected = -1; + StopAnimation(); +} + +void GPUDisplay::removeAnimationPoint(int32_t i) +{ + StopAnimation(); + if (i < 0) { + i = mCfgC.animationPoints.selected >= 0 ? mCfgC.animationPoints.selected : (int32_t)mAnimationVectors.size() - 1; + } + if (i < 0 || i >= (int32_t)mAnimationVectors.size()) { + return; + } + auto& times = mCfgC.animationPoints.values; + mAnimationVectors.erase(mAnimationVectors.begin() + i); + times.erase(times.begin() + i); + if (i == 0 && times.size()) { // The first point stays at time 0 + const float t0 = times[0]; + for (float& t : times) { + t -= t0; + } } - mAnimateConfig.clear(); - mAnimate = 0; + mCfgC.animationPoints.selected = -1; } -void GPUDisplay::removeAnimationPoint() +void GPUDisplay::selectAnimationPoint(int32_t i) { - if (mAnimateVectors[0].size() == 0) { + StopAnimation(); + if (i < 0 || i >= (int32_t)mAnimationVectors.size()) { + mCfgC.animationPoints.selected = -1; return; } - for (int32_t i = 0; i < 9; i++) { - mAnimateVectors[i].pop_back(); + mCfgC.animationPoints.selected = i; + mCfgL = mAnimationVectors[i].cfgL; + const auto& v = mAnimationVectors[i].v; + setCamera({v[0], v[1], v[2]}, {v[3], v[4], v[5]}); +} + +void GPUDisplay::StopAnimation() +{ + if (!mAnimate) { + return; + } + mAnimate = mCfgO.animateScreenshot = 0; + mAnimationMixBase = -1; // Camera and settings stay as they are + mUpdateDrawCommands = true; + SetInfo("Animation stopped", 1); +} + +void GPUDisplay::setAnimationPointsFromGUI(const GPUDisplayMultiSliderValue& points) +{ + if (mAnimate || points.values.size() != mAnimationVectors.size()) { // Not while the animation runs; the GUI may not yet know a changed number of points + return; + } + std::vector order(points.values.size()); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), [&](uint32_t a, uint32_t b) { return points.values[a] < points.values[b]; }); + for (uint32_t i = 1; i < order.size(); i++) { + if (!(points.values[order[i]] > points.values[order[i - 1]])) { // Points must have different times, the GUI prevents this + return; + } + } + vecpod sorted(order.size()); + vecpod sortedTimes(order.size()); + int32_t selected = -1; + for (uint32_t i = 0; i < order.size(); i++) { + sorted[i] = mAnimationVectors[order[i]]; + sortedTimes[i] = points.values[order[i]] - points.values[order[0]]; // The first point at time 0 + selected = (int32_t)order[i] == points.selected ? i : selected; + } + const bool selectionChanged = points.selected != mCfgC.animationPoints.selected; + mAnimationVectors = std::move(sorted); + mCfgC.animationPoints.values = std::move(sortedTimes); + if (selectionChanged) { + selectAnimationPoint(selected); + } else { + mCfgC.animationPoints.selected = selected; + } +} + +void GPUDisplay::updateAnimationPointsGUI() +{ + auto& gui = mCfgC.animationPoints; + gui.progress = mAnimate ? gui.progress : 0; // Set by animateCamera while the animation runs + gui.max = (gui.values.size() ? gui.values.back() : 0.) + ((int32_t)(mCfgO.animationDelay * 20)) / 20.f; // Room for the next point +} + +void GPUDisplay::runAnimation() +{ + if (mAnimationVectors.size() > 1) { + if (mCfgO.animateScreenshot) { + mAnimationExport++; + } + startAnimation(); + SetInfo("Starting Animation", 1); + } else { + SetInfo("Insufficient Animation points to start Animation", 1); } - mAnimateConfig.pop_back(); } void GPUDisplay::startAnimation() { - for (int32_t i = 0; i < 8; i++) { - mAnimationSplines[i].create(mAnimateVectors[0], mAnimateVectors[i + 1]); + mAnimationSplines.resize(ANIMATION_MODE_NAMES_SHORT.size()); + vecpod y[8]; + for (auto& yi : y) { + yi.resize(mAnimationVectors.size()); + } + for (uint32_t mode = 0; mode < mAnimationSplines.size(); mode++) { // Splines for all modes, points may have different modes + float vals[8], previous[8]; + for (uint32_t k = 0; k < mAnimationVectors.size(); k++) { + animationPointValues(mAnimationVectors[k], mode, k ? previous : nullptr, vals); // Continuous to the previous point in time order + for (int32_t i = 0; i < 8; i++) { + y[i][k] = previous[i] = vals[i]; + } + } + for (int32_t i = 0; i < 8; i++) { + mAnimationSplines[mode][i].create(mCfgC.animationPoints.values, y[i]); + } } mAnimationTimer.ResetStart(); mAnimationFrame = 0; mAnimate = 1; + mCfgC.animationPoints.selected = -1; // The animation changes mCfgL, which would otherwise be stored in the selected point when it ends mAnimationLastBase = 0; mAnimationMixBase = -1; } -int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix) +int32_t GPUDisplay::animateCamera(bool suspended, float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix) { float time = animateTime; if (mAnimate && time < 0) { - if (mAnimateScreenshot) { + if (mCfgO.animateScreenshot) { time = mAnimationFrame / 30.f; } else { time = mAnimationTimer.GetCurrentElapsedTime(); } - float maxTime = mAnimateVectors[0].back(); + float maxTime = mCfgC.animationPoints.values.back(); + mCfgC.animationPoints.progress = std::lround(std::clamp(time / maxTime, 0.f, 1.f) * 65535); if (time >= maxTime) { time = maxTime; - mAnimate = mAnimateScreenshot = 0; + mAnimate = mCfgO.animateScreenshot = 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); @@ -128,44 +285,82 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ if (!mAnimate) { return 0; } - float vals[8]; - for (int32_t i = 0; i < 8; i++) { - vals[i] = mAnimationSplines[i].evaluate(time); - } - if (mAnimationChangeConfig && animateTime < 0) { + if (mCfgO.animationChangeConfig && animateTime < 0) { int32_t base = 0; - int32_t k = mAnimateVectors[0].size() - 1; - while (base < k && time > mAnimateVectors[0][base]) { + const auto& times = mCfgC.animationPoints.values; + int32_t k = mAnimationVectors.size() - 1; + while (base < k && time > times[base]) { base++; } if (base > mAnimationLastBase + 1) { mAnimationLastBase = base - 1; } - const bool mixing = base != mAnimationLastBase && mAnimateVectors[0][mAnimationLastBase] != mAnimateVectors[0][base] && memcmp(&mAnimateConfig[base], &mAnimateConfig[mAnimationLastBase], sizeof(mAnimateConfig[base])); + const bool mixing = base != mAnimationLastBase && times[mAnimationLastBase] != times[base] && memcmp(&mAnimationVectors[base].cfgL, &mAnimationVectors[mAnimationLastBase].cfgL, sizeof(mCfgL)); if ((mixing ? base : -1) != mAnimationMixBase) { // Blended configs changed: cached (Vulkan) command buffers must be re-recorded, otherwise they are kept while mixing mAnimationMixBase = mixing ? base : -1; mUpdateDrawCommands = true; } if (mixing) { - mixSlaveImage = 1.f - (time - mAnimateVectors[0][mAnimationLastBase]) / (mAnimateVectors[0][base] - mAnimateVectors[0][mAnimationLastBase]); + mixSlaveImage = 1.f - (time - times[mAnimationLastBase]) / (times[base] - times[mAnimationLastBase]); if (mixSlaveImage > 0) { - mCfgL = mAnimateConfig[mAnimationLastBase]; + mCfgL = mAnimationVectors[mAnimationLastBase].cfgL; updateConfig(); - DrawGLScene_internal(time, true); + DrawGLScene_internal(suspended, time, true); } } - if (memcmp(&mAnimateConfig[base], &mCfgL, sizeof(mCfgL))) { - mCfgL = mAnimateConfig[base]; + if (memcmp(&mAnimationVectors[base].cfgL, &mCfgL, sizeof(mCfgL))) { + mCfgL = mAnimationVectors[base].cfgL; updateConfig(); + mUpdateDrawCommands |= !mixing; // mOldCfg is taken after drawing, so checkAndApplyConfigChanges does not see this change: cached (Vulkan) command buffers would keep the previous settings; while mixing, a change of mAnimationMixBase re-records them + } + } + + const auto& times = mCfgC.animationPoints.values; + uint32_t k = 0; // Interval [times[k], times[k + 1]] of the time + while (k + 2 < times.size() && time > times[k + 1]) { + k++; + } + const int32_t maxMode = mAnimationSplines.size() - 1; // Modes from a loaded file are not checked + const int32_t modeA = std::clamp(mAnimationVectors[k].cfgL.animationMode, 0, maxMode), modeB = std::clamp(mAnimationVectors[k + 1].cfgL.animationMode, 0, maxMode); + auto cameraOfMode = [&](int32_t mode, hmm_mat4& viewMatrix) { + float vals[8]; + for (int32_t i = 0; i < 8; i++) { + vals[i] = mAnimationSplines[mode][i].evaluate(time); + } + animationViewMatrix(vals, mode, viewMatrix); + }; + if (modeA == modeB) { + cameraOfMode(modeA, nextViewMatrix); + } else { // Modes of the points differ: blend the cameras of both modes, position linearly, rotation as quaternion, with sin^2 for a smooth start and end + hmm_mat4 viewA = nextViewMatrix, viewB = nextViewMatrix; + cameraOfMode(modeA, viewA); + cameraOfMode(modeB, viewB); + const float s = sinf(std::clamp((time - times[k]) / (times[k + 1] - times[k]), 0.f, 1.f) * CAMath::Pi() / 2), w = s * s; + float qA[4], qB[4], vals[8]; + createQuaternionFromMatrix(qA, &viewA.Elements[0][0]); + createQuaternionFromMatrix(qB, &viewB.Elements[0][0]); + const float sign = qA[0] * qB[0] + qA[1] * qB[1] + qA[2] * qB[2] + qA[3] * qB[3] < 0 ? -1.f : 1.f; // Shorter way + auto position = [](const hmm_mat4& m, int32_t i) { return -(m.Elements[i][0] * m.Elements[3][0] + m.Elements[i][1] * m.Elements[3][1] + m.Elements[i][2] * m.Elements[3][2]); }; // Camera position, as calcXYZ + for (int32_t i = 0; i < 3; i++) { + vals[i] = (1 - w) * position(viewA, i) + w * position(viewB, i); + } + vals[3] = 0; + for (int32_t i = 0; i < 4; i++) { + vals[4 + i] = (1 - w) * qA[i] + w * sign * qB[i]; // Normalized by animationViewMatrix } + animationViewMatrix(vals, 0, nextViewMatrix); // Mode 0: Cartesian position, quaternion } + return 1; +} - if (mCfgL.animationMode != 6) { - if (mCfgL.animationMode & 1) // Rotation from euler angles +void GPUDisplay::animationViewMatrix(float (&vals)[8], int32_t mode, hmm_mat4& nextViewMatrix) const +{ + if (mode != 6) { + if (mode & 1) // Rotation from euler angles, same order as setCamera (inverse of calcXYZ) { - nextViewMatrix = nextViewMatrix * HMM_Rotate(-vals[4] * 180.f / CAMath::Pi(), {{1, 0, 0}}) * HMM_Rotate(vals[5] * 180.f / CAMath::Pi(), {{0, 1, 0}}) * HMM_Rotate(-vals[6] * 180.f / CAMath::Pi(), {{0, 0, 1}}); + nextViewMatrix = nextViewMatrix * HMM_Rotate(-vals[6] * 180.f / CAMath::Pi(), {{0, 0, 1}}) * HMM_Rotate(-vals[4] * 180.f / CAMath::Pi(), {{1, 0, 0}}) * HMM_Rotate(vals[5] * 180.f / CAMath::Pi(), {{0, 1, 0}}); } else { // Rotation from quaternion const float mag = sqrtf(vals[4] * vals[4] + vals[5] * vals[5] + vals[6] * vals[6] + vals[7] * vals[7]); if (mag < 0.0001f) { @@ -181,13 +376,13 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ nextViewMatrix = nextViewMatrix * MY_HMM_FROM(mat); } } - if (mCfgL.animationMode & 4) // Compute cartesian translation from sperical coordinates (euler angles) + if (mode & 4) // Compute cartesian translation from sperical coordinates (euler angles) { const float r = vals[3], phi = vals[1], theta = vals[2]; vals[2] = r * cosf(phi) * cosf(theta); vals[0] = r * sinf(phi) * cosf(theta); vals[1] = r * sinf(theta); - } else if (mCfgL.animationMode & 2) { // Scale cartesion translation to interpolated radius + } else if (mode & 2) { // Scale cartesion translation to interpolated radius float r = sqrtf(vals[0] * vals[0] + vals[1] * vals[1] + vals[2] * vals[2]); if (fabsf(r) < 0.0001f) { r = 1; @@ -197,10 +392,9 @@ int32_t GPUDisplay::animateCamera(float& animateTime, float& mixSlaveImage, hmm_ vals[i] *= r; } } - if (mCfgL.animationMode == 6) { - nextViewMatrix = HMM_LookAt({{vals[0], vals[1], vals[2]}}, {{0, 0, 0}}, {{0, 1, 0}}); + if (mode == 6) { + nextViewMatrix = lookAtMatrix({vals[0], vals[1], vals[2]}, {0.f, 0.f, 0.f}); } else { nextViewMatrix = nextViewMatrix * HMM_Translate({{-vals[0], -vals[1], -vals[2]}}); } - return 1; } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index d0a6685efc742..9920ee0289be6 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,102 @@ void GPUDisplay::disableUnsupportedOptions() } } +void GPUDisplay::takeScreenshot() +{ + static int32_t nScreenshot = 1; + mCfgC.requestScreenshot = true; + mScreenshotFile = mCfgO.mscreenShotName + std::to_string(nScreenshot++) + ".bmp"; +} + +void GPUDisplay::SaveGLPos() +{ + const char* file = mCfgO.glPosFile.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 = mCfgO.glPosFile.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 = mCfgO.glAnimationFile.c_str(); + FILE* ftmp = fopen(file, "w+b"); + if (!ftmp) { + SetInfo("Error opening file %s", file); + return; + } + const int32_t size = mAnimationVectors.size(); + bool ok = fwrite(&mCfgL, sizeof(mCfgL), 1, ftmp) == 1 && fwrite(&size, sizeof(size), 1, ftmp) == 1; + ok = ok && fwrite(mCfgC.animationPoints.values.data(), sizeof(float), size, ftmp) == (size_t)size; + ok = ok && fwrite(mAnimationVectors.data(), sizeof(mAnimationVectors[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() +{ + StopAnimation(); + const char* file = mCfgO.glAnimationFile.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 * (sizeof(float) + sizeof(animationVector)); + fseek(ftmp, start, SEEK_SET); + } + if (ok) { + mCfgC.animationPoints.values.resize(size); + mAnimationVectors.resize(size); + ok = ok && fread(mCfgC.animationPoints.values.data(), sizeof(float), size, ftmp) == (size_t)size; + ok = ok && fread(mAnimationVectors.data(), sizeof(mAnimationVectors[0]), size, ftmp) == (size_t)size; + mCfgC.animationPoints.selected = -1; + if (ok) { + mCfgL = cfg; + updateConfig(); + } else { + resetAnimation(); // Read error after the size check: no incomplete path + } + } + fclose(ftmp); + SetInfo(ok ? "Animation path loaded from file %s" : "Error reading animation path from file %s", file); +} + +std::string GPUDisplay::trackFilterName() const +{ + if (mCfgH.trackFilter <= 0) { + return ""; + } + return (uint32_t)mCfgH.trackFilter <= mConfig.filterMacros.size() ? "displayTrackFilter/" + mConfig.filterMacros[mCfgH.trackFilter - 1] : "Custom Macro"; +} + void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, float animateTime) { size_t screenshot_x = mBackend->mScreenWidth * mCfgR.screenshotScaleFactor; @@ -99,28 +197,46 @@ void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, f void GPUDisplay::showInfo(const char* info) { - mBackend->updateFontScale(mFrontend->getDisplayScale()); - mBackend->prepareText(); + if (mCfgO.printInfoTextGUI) { + mGUIInfoStatus = info; + } + if (!(mCfgO.printInfoText & 1)) { + return; + } float colorValue = mCfgL.invertColors ? 0.f : 1.f; - OpenGLPrint(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); + GLPrintText(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); if (mInfoText2Timer.IsRunning()) { - if (mInfoText2Timer.GetCurrentElapsedTime() >= 6) { + const float time = mInfoText2Timer.GetCurrentElapsedTime(), shown = mConfig.internal.setInfoTime, transition = mConfig.internal.setInfoTransition; + if (time >= shown + transition) { mInfoText2Timer.Reset(); } else { - OpenGLPrint(mInfoText2, 40.f, 20.f, colorValue, colorValue, colorValue, 6 - mInfoText2Timer.GetCurrentElapsedTime()); + GLPrintText(mInfoText2, 40.f, 20.f, colorValue, colorValue, colorValue, time <= shown ? 1.f : 1.f - (time - shown) / transition); // Opaque, then fading out } } - if (mInfoHelpTimer.IsRunning() || mPrintInfoTextAlways) { + if (mInfoHelpTimer.IsRunning() || mCfgO.printInfoTextAlways) { if (mInfoHelpTimer.GetCurrentElapsedTime() >= 6) { mInfoHelpTimer.Reset(); } else { PrintGLHelpText(colorValue); } } - mBackend->finishText(); } -void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) +float GPUDisplay::consoleVerticalFraction() +{ + const bool running = mConsoleTimer.IsRunning(); + if (!mCfgC.showConsole && !running) { + return 0.f; + } + const double time = mConsoleTimer.GetCurrentElapsedTime(); + if (running && !mCfgC.showConsole && time >= mConfig.internal.consoleDelay) { // Keeps running while open, for the blinking cursor + mConsoleTimer.Stop(); + } + const float progress = running && mConfig.internal.consoleDelay > 0.f ? std::min(1.f, (float)(time / mConfig.internal.consoleDelay)) : 1.f; + return mConfig.internal.consoleVertical * (mCfgC.showConsole ? progress : 1.f - progress); +} + +void GPUDisplay::GLPrintText(const char* s, float x, float y, float r, float g, float b, float a, uint32_t nCharacters, bool fromBotton) { if (mBackend->mFreetypeInitialized) { const float scale = mFrontend->getDisplayScale(); @@ -130,8 +246,6 @@ void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, y = mBackend->mScreenHeight - y; } float color[4] = {r, g, b, a}; - mBackend->OpenGLPrint(s, x, y, color, scale / mBackend->mFontRasterScale); - } else if (mFrontend->mCanDrawText) { - mFrontend->OpenGLPrint(s, x, y, r, g, b, a, fromBotton); + mBackend->GLPrintText(s, x, y, color, scale / mBackend->mFontRasterScale, nCharacters); } } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx index 7108386edccf4..58da1216bcfe2 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 (mCfgO.filterMacroCustom.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) << mCfgO.filterMacroCustom << "\n"; + } + if (!mTrackFilterMacroLoaded.empty()) { // All macros define gpuDisplayTrackFilter, the previous one must be unloaded + const bool recreate = !std::filesystem::exists(mTrackFilterMacroLoaded); // ROOT cannot unload a macro if its file no longer exists, the content does not matter + if (recreate) { + std::ofstream{mTrackFilterMacroLoaded}; + } + gInterpreter->UnloadFile(mTrackFilterMacroLoaded.c_str()); + if (recreate) { + std::filesystem::remove(mTrackFilterMacroLoaded); + } + mTrackFilterMacroLoaded.clear(); + } gROOT->Reset(); - if (gROOT->LoadMacro(name.c_str())) { + const int32_t loadError = gROOT->LoadMacro(name.c_str()); + if (custom) { + std::filesystem::remove(name); + } + if (loadError) { GPUError("Error loading trackFilter macro %s", name.c_str()); return 1; } + mTrackFilterMacroLoaded = name; } TMethodCall call; call.InitWithPrototype("gpuDisplayTrackFilter", "std::vector*, const o2::gpu::GPUTrackingInOutPointers*, const o2::gpu::GPUConstantMem*"); diff --git a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx index a18b3e3fe2140..7e85a14674319 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx @@ -654,7 +654,7 @@ void GPUDisplay::DrawFinal(int32_t iSector, int32_t /*iCol*/, const GPUTPCGMProp break; } float sa = sinf(alpha), ca = cosf(alpha); - float drawX = trkParam.X() + mCfgH.xAdd; + float drawX = trkParam.X() + mCfgH.xzAdd; useBuffer.emplace_back((ca * drawX - sa * trkParam.Y()) * GL_SCALE_FACTOR, (ca * trkParam.Y() + sa * drawX) * mYFactor * GL_SCALE_FACTOR, mCfgH.projectXY ? 0 : (trkParam.Z() + ZOffset) * GL_SCALE_FACTOR); x += inFlyDirection ? 1 : -1; } @@ -685,17 +685,17 @@ GPUDisplay::vboList GPUDisplay::DrawGrid(const GPUTPCTracker& tracker) for (int32_t j = 0; j <= (signed)row.Grid().Ny(); j++) { float z1 = row.Grid().ZMin(); float z2 = row.Grid().ZMax(); - float x = row.X() + mCfgH.xAdd; + float x = row.X() + mCfgH.xzAdd; float y = row.Grid().YMin() + (float)j / row.Grid().StepYInv(); float zz1, zz2, yy1, yy2, xx1, xx2; mParam->Sector2Global(tracker.ISector(), x, y, z1, &xx1, &yy1, &zz1); mParam->Sector2Global(tracker.ISector(), x, y, z2, &xx2, &yy2, &zz2); if (iSector < 18) { - zz1 += mCfgH.zAdd; - zz2 += mCfgH.zAdd; + zz1 += mCfgH.xzAdd; + zz2 += mCfgH.xzAdd; } else { - zz1 -= mCfgH.zAdd; - zz2 -= mCfgH.zAdd; + zz1 -= mCfgH.xzAdd; + zz2 -= mCfgH.xzAdd; } mVertexBuffer[iSector].emplace_back(xx1 * GL_SCALE_FACTOR, yy1 * GL_SCALE_FACTOR * mYFactor, zz1 * GL_SCALE_FACTOR); mVertexBuffer[iSector].emplace_back(xx2 * GL_SCALE_FACTOR, yy2 * GL_SCALE_FACTOR * mYFactor, zz2 * GL_SCALE_FACTOR); @@ -703,17 +703,17 @@ GPUDisplay::vboList GPUDisplay::DrawGrid(const GPUTPCTracker& tracker) for (int32_t j = 0; j <= (signed)row.Grid().Nz(); j++) { float y1 = row.Grid().YMin(); float y2 = row.Grid().YMax(); - float x = row.X() + mCfgH.xAdd; + float x = row.X() + mCfgH.xzAdd; float z = row.Grid().ZMin() + (float)j / row.Grid().StepZInv(); float zz1, zz2, yy1, yy2, xx1, xx2; mParam->Sector2Global(tracker.ISector(), x, y1, z, &xx1, &yy1, &zz1); mParam->Sector2Global(tracker.ISector(), x, y2, z, &xx2, &yy2, &zz2); if (iSector < 18) { - zz1 += mCfgH.zAdd; - zz2 += mCfgH.zAdd; + zz1 += mCfgH.xzAdd; + zz2 += mCfgH.xzAdd; } else { - zz1 -= mCfgH.zAdd; - zz2 -= mCfgH.zAdd; + zz1 -= mCfgH.xzAdd; + zz2 -= mCfgH.xzAdd; } mVertexBuffer[iSector].emplace_back(xx1 * GL_SCALE_FACTOR, yy1 * GL_SCALE_FACTOR * mYFactor, zz1 * GL_SCALE_FACTOR); mVertexBuffer[iSector].emplace_back(xx2 * GL_SCALE_FACTOR, yy2 * GL_SCALE_FACTOR * mYFactor, zz2 * GL_SCALE_FACTOR); diff --git a/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx b/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx index 27c9f98f01fa6..5209dc9262819 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayImportEvent.cxx @@ -40,6 +40,7 @@ using namespace o2::gpu; void GPUDisplay::DrawGLScene_updateEventData() { + mUpdateGUIOptions = true; // E.g. number of collisions HighResTimer timer(mChain->GetProcessingSettings().debugLevel >= 2); if (mIOPtrs->clustersNative) { mCurrentClusters = mIOPtrs->clustersNative->nClustersTotal; @@ -119,14 +120,14 @@ void GPUDisplay::DrawGLScene_updateEventData() mTrackFilter.resize(nTracks); std::fill(mTrackFilter.begin(), mTrackFilter.end(), true); if (buildTrackFilter()) { - SetInfo("Error running track filter from %s", mConfig.filterMacros[mCfgH.trackFilter - 1].c_str()); + SetInfo("Error running track filter from %s", trackFilterName().c_str()); } else { uint32_t nFiltered = 0; for (uint32_t i = 0; i < mTrackFilter.size(); i++) { nFiltered += !mTrackFilter[i]; } if (mUpdateTrackFilter) { - SetInfo("Applied track filter %s - filtered %u / %u", mConfig.filterMacros[mCfgH.trackFilter - 1].c_str(), nFiltered, (uint32_t)mTrackFilter.size()); + SetInfo("Applied track filter %s - filtered %u / %u", trackFilterName().c_str(), nFiltered, (uint32_t)mTrackFilter.size()); } } } @@ -155,12 +156,12 @@ void GPUDisplay::DrawGLScene_updateEventData() if (mCfgH.clustersOnNominalRow) { x = GPUTPCGeometry::Row2X(row); } - mParam->Sector2Global(iSector, x + mCfgH.xAdd, y, z, &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(iSector, x + mCfgH.xzAdd, y, z, &ptr->x, &ptr->y, &ptr->z); if (fabsf(ptr->z) > maxClusterZ) { maxClusterZ = fabsf(ptr->z); } - ptr->z += iSector < 18 ? mCfgH.zAdd : -mCfgH.zAdd; + ptr->z += iSector < 18 ? mCfgH.xzAdd : -mCfgH.xzAdd; ptr->x *= GL_SCALE_FACTOR; ptr->y *= GL_SCALE_FACTOR; ptr->z *= GL_SCALE_FACTOR; @@ -185,7 +186,7 @@ void GPUDisplay::DrawGLScene_updateEventData() const auto& sp = mIOPtrs->trdSpacePoints[i]; int32_t iSec = trdGeometry()->GetSector(mIOPtrs->trdTracklets[i].GetDetector()); float4* ptr = &mGlobalPosTRD[i]; - mParam->Sector2Global(iSec, sp.getX() + mCfgH.xAdd, sp.getY(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(iSec, sp.getX() + mCfgH.xzAdd, sp.getY(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); ptr->z += ptr->z > 0 ? trdZoffset : -trdZoffset; if (fabsf(ptr->z) > maxClusterZ) { maxClusterZ = fabsf(ptr->z); @@ -195,7 +196,7 @@ void GPUDisplay::DrawGLScene_updateEventData() ptr->z *= GL_SCALE_FACTOR; ptr->w = tTRDCLUSTER; ptr = &mGlobalPosTRD2[i]; - mParam->Sector2Global(iSec, sp.getX() + mCfgH.xAdd + 4.5f, sp.getY() + 1.5f * sp.getDy(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(iSec, sp.getX() + mCfgH.xzAdd + 4.5f, sp.getY() + 1.5f * sp.getDy(), sp.getZ(), &ptr->x, &ptr->y, &ptr->z); ptr->z += ptr->z > 0 ? trdZoffset : -trdZoffset; if (fabsf(ptr->z) > maxClusterZ) { maxClusterZ = fabsf(ptr->z); @@ -214,7 +215,7 @@ void GPUDisplay::DrawGLScene_updateEventData() mMaxClusterZ = tbb::parallel_reduce(tbb::blocked_range(0, mCurrentClustersTOF, 32), float(mMaxClusterZ), [&](const tbb::blocked_range& r, float maxClusterZ) { for (int32_t i = r.begin(); i < r.end(); i++) { float4* ptr = &mGlobalPosTOF[i]; - mParam->Sector2Global(mIOPtrs->tofClusters[i].getSector(), mIOPtrs->tofClusters[i].getX() + mCfgH.xAdd, mIOPtrs->tofClusters[i].getY(), mIOPtrs->tofClusters[i].getZ(), &ptr->x, &ptr->y, &ptr->z); + mParam->Sector2Global(mIOPtrs->tofClusters[i].getSector(), mIOPtrs->tofClusters[i].getX() + mCfgH.xzAdd, mIOPtrs->tofClusters[i].getY(), mIOPtrs->tofClusters[i].getZ(), &ptr->x, &ptr->y, &ptr->z); float ZOffset = 0; if (mParam->par.continuousTracking) { float tofTime = mIOPtrs->tofClusters[i].getTime() * 1e-3 / o2::constants::lhc::LHCBunchSpacingNS / o2::tpc::constants::LHCBCPERTIMEBIN; diff --git a/GPU/GPUTracking/qa/GPUQA.cxx b/GPU/GPUTracking/qa/GPUQA.cxx index 0ef2ce6ac9d84..8a2610606994d 100644 --- a/GPU/GPUTracking/qa/GPUQA.cxx +++ b/GPU/GPUTracking/qa/GPUQA.cxx @@ -518,8 +518,8 @@ int32_t GPUQA::InitQACreateHistograms() // Create Cluster Histograms if (mQATasks & taskClusterAttach) { for (int32_t i = 0; i < N_CLS_TYPE * N_CLS_HIST - 1; i++) { - int32_t ioffset = i >= (2 * N_CLS_HIST - 1) ? (2 * N_CLS_HIST - 1) : i >= N_CLS_HIST ? N_CLS_HIST : 0; - int32_t itype = i >= (2 * N_CLS_HIST - 1) ? 2 : i >= N_CLS_HIST ? 1 : 0; + int32_t ioffset = i >= (2 * N_CLS_HIST - 1) ? (2 * N_CLS_HIST - 1) : (i >= N_CLS_HIST ? N_CLS_HIST : 0); + int32_t itype = i >= (2 * N_CLS_HIST - 1) ? 2 : (i >= N_CLS_HIST ? 1 : 0); snprintf(name, 2048, "clusters%s%s", CLUSTER_NAMES_SHORT[i - ioffset], CLUSTER_TYPES[itype]); std::unique_ptr binsPt{CreateLogAxis(AXIS_BINS[4], PT_MIN_CLUST, PT_MAX)}; createHist(mClusters[i], name, name, AXIS_BINS[4], binsPt.get()); @@ -1330,7 +1330,7 @@ void GPUQA::RunQA(bool matchOnly, const std::vector* tracksEx continue; } - int32_t val = (j == 0) ? (mRecTracks[iCol][i] ? 1 : 0) : (j == 1) ? (mRecTracks[iCol][i] ? mRecTracks[iCol][i] - 1 : 0) : (j == 2) ? mFakeTracks[iCol][i] : 1; + int32_t val = (j == 0) ? (mRecTracks[iCol][i] ? 1 : 0) : ((j == 1) ? (mRecTracks[iCol][i] ? mRecTracks[iCol][i] - 1 : 0) : ((j == 2) ? mFakeTracks[iCol][i] : 1)); if (val == 0) { continue; } @@ -1346,7 +1346,7 @@ void GPUQA::RunQA(bool matchOnly, const std::vector* tracksEx continue; } - float pos = l == 0 ? localY : l == 1 ? info.z : l == 2 ? mcphi : l == 3 ? mceta : mcpt; + float pos = l == 0 ? localY : (l == 1 ? info.z : (l == 2 ? mcphi : (l == 3 ? mceta : mcpt))); mEff[j][k][!info.prim][l]->Fill(pos, val); } @@ -2512,7 +2512,7 @@ int32_t GPUQA::DrawQAHistograms(TObjArray* qcout) proj->GetXaxis()->SetRangeUser(std::max(proj->GetXaxis()->GetXmin(), proj->GetMean() - 3. * proj->GetRMS()), std::min(proj->GetXaxis()->GetXmax(), proj->GetMean() + 3. * proj->GetRMS())); bool forceLogLike = proj->GetMaximum() < 20; for (int32_t k = forceLogLike ? 2 : 0; k < 3; k++) { - proj->Fit("gaus", forceLogLike || k == 2 ? "sQl" : k ? "sQww" : "sQ"); + proj->Fit("gaus", forceLogLike || k == 2 ? "sQl" : (k ? "sQww" : "sQ")); TF1* fitFunc = proj->GetFunction("gaus"); if (k && !forceLogLike) { @@ -2528,7 +2528,7 @@ int32_t GPUQA::DrawQAHistograms(TObjArray* qcout) dst[1]->SetBinError(bin, fitFunc->GetParError(1)); const bool fail1 = sigma <= 0.f; - const bool fail2 = fabs(proj->GetMean() - dst[1]->GetBinContent(bin)) > std::min(p ? PULL_AXIS : mConfig.nativeFitResolutions ? RES_AXES_NATIVE[j] : RES_AXES[j], 3.f * proj->GetRMS()); + const bool fail2 = fabs(proj->GetMean() - dst[1]->GetBinContent(bin)) > std::min(p ? PULL_AXIS : (mConfig.nativeFitResolutions ? RES_AXES_NATIVE[j] : RES_AXES[j]), 3.f * proj->GetRMS()); const bool fail3 = dst[0]->GetBinContent(bin) > 3.f * proj->GetRMS() || dst[0]->GetBinError(bin) > 1 || dst[1]->GetBinError(bin) > 1; const bool fail4 = fitFunc->GetParameter(0) < proj->GetMaximum() / 5.; const bool fail = fail1 || fail2 || fail3 || fail4; @@ -2633,7 +2633,7 @@ int32_t GPUQA::DrawQAHistograms(TObjArray* qcout) e->SetLineWidth(1); e->SetLineStyle(CONFIG_DASHED_MARKERS ? k + 1 : 1); SetAxisSize(e); - e->GetYaxis()->SetTitle(p ? AXIS_TITLES_PULL[j] : mConfig.nativeFitResolutions ? AXIS_TITLES_NATIVE[j] : AXIS_TITLES[j]); + e->GetYaxis()->SetTitle(p ? AXIS_TITLES_PULL[j] : (mConfig.nativeFitResolutions ? AXIS_TITLES_NATIVE[j] : AXIS_TITLES[j])); e->GetXaxis()->SetTitle(XAXIS_TITLES[i]); if (LOG_PT_MIN > 0 && ii == 5) { e->GetXaxis()->SetRangeUser(LOG_PT_MIN, PT_MAX); @@ -2841,8 +2841,8 @@ int32_t GPUQA::DrawQAHistograms(TObjArray* qcout) mPClust[i]->cd(); mPClust[i]->SetLogx(); } - int32_t begin = i == 2 ? (2 * N_CLS_HIST - 1) : i == 1 ? N_CLS_HIST : 0; - int32_t end = i == 2 ? (3 * N_CLS_HIST - 1) : i == 1 ? (2 * N_CLS_HIST - 1) : N_CLS_HIST; + int32_t begin = i == 2 ? (2 * N_CLS_HIST - 1) : (i == 1 ? N_CLS_HIST : 0); + int32_t end = i == 2 ? (3 * N_CLS_HIST - 1) : (i == 1 ? (2 * N_CLS_HIST - 1) : N_CLS_HIST); int32_t numColor = 0; for (int32_t k = 0; k < ConfigNumInputs; k++) { for (int32_t j = end - 1; j >= begin; j--) { @@ -2852,7 +2852,7 @@ int32_t GPUQA::DrawQAHistograms(TObjArray* qcout) } e->SetTitle(mConfig.plotsNoTitle ? "" : CLUSTER_TITLES[i]); - e->GetYaxis()->SetTitle(i == 0 ? "Number of TPC clusters" : i == 1 ? "Fraction of TPC clusters" : CLUST_HIST_INT_SUM ? "Total TPC clusters (integrated)" : "Fraction of TPC clusters (integrated)"); + e->GetYaxis()->SetTitle(i == 0 ? "Number of TPC clusters" : (i == 1 ? "Fraction of TPC clusters" : (CLUST_HIST_INT_SUM ? "Total TPC clusters (integrated)" : "Fraction of TPC clusters (integrated)"))); e->GetXaxis()->SetTitle("#it{p}_{Tmc} (GeV/#it{c})"); e->GetXaxis()->SetTitleOffset(1.1); e->GetXaxis()->SetLabelOffset(-0.005); @@ -2907,9 +2907,9 @@ int32_t GPUQA::DrawQAHistograms(TObjArray* qcout) } doPerfFigure(i == 0 ? 0.37 : (i == 1 ? 0.34 : 0.6), 0.295, 0.030); mCClust[i]->cd(); - mCClust[i]->Print(Form(i == 2 ? "%s/clusters_integral.pdf" : i == 1 ? "%s/clusters_relative.pdf" : "%s/clusters.pdf", mConfig.plotsDir.c_str())); + mCClust[i]->Print(Form(i == 2 ? "%s/clusters_integral.pdf" : (i == 1 ? "%s/clusters_relative.pdf" : "%s/clusters.pdf"), mConfig.plotsDir.c_str())); if (mConfig.writeFileExt != "") { - mCClust[i]->Print(Form(i == 2 ? "%s/clusters_integral.%s" : i == 1 ? "%s/clusters_relative.%s" : "%s/clusters.%s", mConfig.plotsDir.c_str(), mConfig.writeFileExt.c_str())); + mCClust[i]->Print(Form(i == 2 ? "%s/clusters_integral.%s" : (i == 1 ? "%s/clusters_relative.%s" : "%s/clusters.%s"), mConfig.plotsDir.c_str(), mConfig.writeFileExt.c_str())); } } diff --git a/GPU/GPUTracking/qa/genEvents.cxx b/GPU/GPUTracking/qa/genEvents.cxx index 5af7698fdb4dc..c589dc32674e0 100644 --- a/GPU/GPUTracking/qa/genEvents.cxx +++ b/GPU/GPUTracking/qa/genEvents.cxx @@ -38,6 +38,8 @@ #include "GPUTPCGMMerger.h" #include "GPUChainTracking.h" #include "GPUConstantMem.h" +#define GPUSETTINGS_STANDALONE +#define GPUSETTINGS_DISPLAY #include "GPUSettings.h" using namespace o2::gpu; @@ -274,7 +276,7 @@ int32_t genEvents::GenerateEvent(const GPUParam& param, const char* filename) GenCluster c; float sigmaY = 0.3; float sigmaZ = 0.5; - const int32_t rowType = iRow < 64 ? 0 : iRow < 128 ? 2 : 1; + const int32_t rowType = iRow < 64 ? 0 : (iRow < 128 ? 2 : 1); t.UpdateValues(); param.GetClusterErrors2(iSector, rowType, t.GetZ(), t.GetSinPhi(), t.GetDzDs(), -1.f, 0.f, 0.f, sigmaY, sigmaZ); sigmaY = std::sqrt(sigmaY); diff --git a/GPU/GPUTracking/qconfigoptions.h b/GPU/GPUTracking/qconfigoptions.h index 2a43c3f59cc3f..b199157546b43 100644 --- a/GPU/GPUTracking/qconfigoptions.h +++ b/GPU/GPUTracking/qconfigoptions.h @@ -12,6 +12,28 @@ /// \file qconfigoptions.h /// \author David Rohr -#include "GPUSettings.h" // Usually we are included from GPUSettings.h, so this doesn't do anything. - // But ROOT or qconfig.cpp will include us directly, so load the settings first. +// clang-format off +#if !defined(QCONFIGOPTIONS_H_LIST) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_LIST #include "GPUSettingsList.h" +#endif + +#if ((defined(GPUSETTINGS_DISPLAY) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_DISPLAY)) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_DISPLAY +#include "GPUSettingsListDisplay.h" +#endif + +#if ((defined(GPUSETTINGS_PARAM) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_PARAM)) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_PARAM +#include "GPUSettingsParam.h" +#endif + +#if ((defined(GPUSETTINGS_DISPLAYCONTROL) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_DISPLAYCONTROL)) || (defined(QCONFIG_INTERNAL) && !defined(QCONFIG_PRINT_RTC)) +#define QCONFIGOPTIONS_H_DISPLAYCONTROL +#include "display/GPUDisplayControl.h" +#endif + +#if ((defined(GPUSETTINGS_STANDALONE) || defined(GPUSETTINGS_ALL)) && !defined(QCONFIGOPTIONS_H_STANDALONE)) || defined(QCONFIG_INTERNAL) +#define QCONFIGOPTIONS_H_STANDALONE +#include "GPUSettingsListStandalone.h" +#endif diff --git a/GPU/GPUTracking/utils/linux_helpers.h b/GPU/GPUTracking/utils/linux_helpers.h index 9de9f49d8207d..7f3be49dcd97b 100644 --- a/GPU/GPUTracking/utils/linux_helpers.h +++ b/GPU/GPUTracking/utils/linux_helpers.h @@ -26,7 +26,8 @@ static inline int32_t getch() newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); - int32_t retVal = getchar(); + unsigned char c; + int32_t retVal = read(STDIN_FILENO, &c, 1) == 1 ? c : -1; // Not getchar(): its buffer would hide the remaining bytes from kbhit() tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return (retVal); } diff --git a/GPU/GPUTracking/utils/qconfig.cxx b/GPU/GPUTracking/utils/qconfig.cxx index 8a15a6ecdd9ef..63212e33085ac 100644 --- a/GPU/GPUTracking/utils/qconfig.cxx +++ b/GPU/GPUTracking/utils/qconfig.cxx @@ -17,11 +17,19 @@ #include #include #include +#include +#include #include #include +#include +#include +#include #include +#define QCONFIG_INTERNAL #include "qconfig.h" +#undef QCONFIG_INTERNAL #include "qconfig_helpers.h" +#include "qconfig_rtti.h" #include "qconfig_internal_helpers.h" // Create config instances @@ -41,19 +49,6 @@ static inline const char* getOptName(const char** argv, int32_t i) return (argv[i]); } -template -struct qConfigSettings { - qConfigSettings() : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(nullptr), allowEmpty(false) {}; - template - qConfigSettings(const qConfigSettings v) : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(v.message), allowEmpty(v.allowEmpty){}; - bool checkMin, checkMax; - bool doSet, doDefault; - T min, max; - T set; - const char* message; - bool allowEmpty; -}; - template static int32_t qAddOptionType(qConfigSettings& settings, T& ref, int32_t& i, const char** argv, const int argc, T def); template @@ -61,15 +56,6 @@ static void qAddOptionMessage(qConfigSettings& settings, T& ref); template static int32_t qAddOptionMinMax(qConfigSettings& settings, T& ref, const char* arg); -template -struct qSettingsType { - typedef T settingsType; -}; -template -struct qSettingsType> { - typedef int32_t settingsType; -}; - template struct qConfigTypeSpecialized { static inline int32_t qAddOptionMain(qConfigSettings& settings, T& ref, int32_t& i, const char** argv, const int argc, T def); @@ -130,41 +116,8 @@ inline int32_t qConfigTypeSpecialized>::qAddOptionMain(qConfigS } template -struct qConfigType { - // Recursive handling of additional settings - static inline void qProcessSetting(qConfigSettings& settings, qmin_t minval) - { - static_assert(!std::is_same_v, "min option not supported for boolean settings"); - settings.checkMin = true; - settings.min = minval.v; - } - static inline void qProcessSetting(qConfigSettings& settings, qmax_t maxval) - { - static_assert(!std::is_same_v, "max option not supported for boolean settings"); - settings.checkMax = true; - settings.max = maxval.v; - } - static inline void qProcessSetting(qConfigSettings& settings, qmessage_t msg) { settings.message = msg.v; } - template - static inline void qProcessSetting(qConfigSettings& settings, qset_t set) - { - settings.doSet = true; - settings.set = set.v; - } - template - static inline void qProcessSetting(qConfigSettings& settings, qdef_t set) - { - settings.doDefault = true; - settings.set = set.v; - } - - static inline void qAddOptionSettings(qConfigSettings& /*settings*/) {} - template - static inline void qAddOptionSettings(qConfigSettings& settings, Arg1&& arg1, Args&&... args) - { - qProcessSetting(settings, arg1); - qAddOptionSettings(settings, args...); - } +struct qConfigType : public qConfigSettingsParser { + using qConfigSettingsParser::qConfigGetSettings; template static inline int32_t qAddOption(T& ref, int32_t& i, const char** argv, const int argc, T def, const char* /*help*/, Args&&... args) @@ -222,7 +175,7 @@ struct qConfigType { { auto settings = qConfigGetSettings(args...); const bool boolType = optionType != 1 && std::is_same_v; - const char* arguments = settings.doSet ? " (" : (settings.doDefault || optionType == 1 || boolType) ? " [arg] (" : optionType == 2 ? " [...] (" : " arg ("; + const char* arguments = settings.doSet ? " (" : ((settings.doDefault || optionType == 1 || boolType) ? " [arg] (" : (optionType == 2 ? " [...] (" : " arg (")); char argBuffer[4] = {0}; uint32_t argBufferPos = 0; if (optnameshort && preoptshort) { @@ -264,14 +217,6 @@ struct qConfigType { } std::cout << "\n"; } - - template - static inline auto qConfigGetSettings(Args&... args) - { - qConfigSettings::settingsType> settings; - qConfigType::settingsType>::qAddOptionSettings(settings, args...); - return (settings); - } }; static inline const char* getArg(int32_t& i, const char** argv, const int argc, bool allowOption = false) @@ -480,10 +425,7 @@ int32_t qConfigParse(int argc, const char** argv, const char* /*filename*/) return (0); } -std::vector> qprint_global; -#define QCONFIG_PRINT -#include "qconfig_internal.h" -#undef QCONFIG_PRINT +#include "qconfig_print.inc" void qConfigPrint() { @@ -491,4 +433,10 @@ void qConfigPrint() f(); } } +namespace internal +{ +#include "qconfig_genrtti.inc" +} // namespace internal + +std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs) { return internal::qConfigGenRTTI(configStructs); } } // end namespace qConfig diff --git a/GPU/GPUTracking/utils/qconfig.h b/GPU/GPUTracking/utils/qconfig.h index d510cd727d12c..21261cd23ff3e 100644 --- a/GPU/GPUTracking/utils/qconfig.h +++ b/GPU/GPUTracking/utils/qconfig.h @@ -13,13 +13,7 @@ /// \author David Rohr #include "qconfig_general.h" - -#if !defined(QCONFIG_HEADER_GUARD_NO_INCLUDE) || defined(QCONFIG_GENRTC) - -#if defined(QCONFIG_HEADER_GUARD) && !defined(QCONFIG_GENRTC) -#define QCONFIG_HEADER_GUARD_NO_INCLUDE -#else -#define QCONFIG_HEADER_GUARD +#define QCONFIG_H #ifndef BeginNamespace #define BeginNamespace(name) \ @@ -66,8 +60,4 @@ }; // clang-format on -#endif - #include "qconfig_wrapinclude.h" - -#endif // QCONFIG_HEADER_GUARD_NO_INCLUDE diff --git a/GPU/GPUTracking/utils/qconfig_general.h b/GPU/GPUTracking/utils/qconfig_general.h index 225808107a4cf..6804a91666ef0 100644 --- a/GPU/GPUTracking/utils/qconfig_general.h +++ b/GPU/GPUTracking/utils/qconfig_general.h @@ -17,16 +17,21 @@ #ifndef GPUCA_GPUCODE_DEVICE #include #include +#include +#include #endif namespace qConfig { +struct qConfigRTTIentry; #ifndef GPUCA_GPUCODE_DEVICE -int32_t qConfigParse(int argc, const char** argv, const char* filename = nullptr); -void qConfigPrint(); // Print all settings +int32_t qConfigParse(int argc, const char** argv, const char* filename = nullptr); // Parse qConfig options +void qConfigPrint(); // Print all settings template -void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName = ""); // Print one sub config, specializations created in qconfig.cxx +void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName = ""); // Print one sub config, specializations created in qconfig.cxx +std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs = nullptr); // Entries and list of config structs, for the given config structs (all if nullptr) #endif + enum qConfigRetVal { qcrOK = 0, qcrError = 1, qcrMinFailure = 2, diff --git a/GPU/GPUTracking/utils/qconfig_genrtti.inc b/GPU/GPUTracking/utils/qconfig_genrtti.inc new file mode 100644 index 0000000000000..b309727799f9e --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_genrtti.inc @@ -0,0 +1,96 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file qconfig_genrtti.inc +/// \author David Rohr + +// Provides static inline qConfigGenRTTI() in the namespace it is included in. +// Requires , , , , , "qconfig_rtti.h" (brings , , ), "qconfig_internal_helpers.h" to be included before (outside of the namespace), and the config structs to be visible unqualified. + +#ifndef QCONFIG_GENRTTI_INC +#define QCONFIG_GENRTTI_INC + +template +static inline T qConfigRTTICheckedCast(size_t v, const char* what) +{ + if (v > std::numeric_limits::max()) { + throw std::runtime_error(std::string("qConfig RTTI: ") + what + " " + std::to_string(v) + " exceeds the range of the entry"); + } + return static_cast(v); +} + +template +constexpr bool qConfigRTTISelected = sizeof...(Structs) == 0 || (std::is_same_v || ...); // Compile-time filter, all structs if none given + +template +static inline std::optional qConfigRTTIStructIndex(std::vector& configStructs, const char* name, bool filter) // Index of struct S, position in Structs if given, else of name in configStructs (appended if not filtering, nullopt if filtered out) +{ + if constexpr (sizeof...(Structs) > 0) { + constexpr size_t index = [] { size_t i = 0; ((std::is_same_v ? false : (++i, true)) && ...); return i; }(); + configStructs[index] = name; + return index; + } + if (auto it = std::find(configStructs.begin(), configStructs.end(), name); it != configStructs.end()) { + return it - configStructs.begin(); + } + if (filter) { + return std::nullopt; + } + configStructs.emplace_back(name); + return configStructs.size() - 1; +} + +template +concept qConfigRTTIentryType = std::derived_from && std::is_aggregate_v; // Aggregate, so it can be initialized from the base entry; extra members need default initializers + +template +static inline void qConfigRTTIEntry(std::vector& retVal, R entry, size_t arraySize, size_t configStructIndex, std::string defaultValue, const char* help, Args&&... args) // Complete the entry, check the narrow members for overflow +{ + auto settings = ::qConfig::qConfigSettingsParser::qConfigGetSettings(args...); + entry.size = qConfigRTTICheckedCast(sizeof(T), "size"); + entry.arraySize = qConfigRTTICheckedCast(arraySize, "arraySize"); + entry.configStructIndex = qConfigRTTICheckedCast(configStructIndex, "configStructIndex"); + entry.defaultValue = std::move(defaultValue); + entry.description = help; + if (settings.checkMin) { + entry.min = ::qConfig::print_type(settings.min); + } + if (settings.checkMax) { + entry.max = ::qConfig::print_type(settings.max); + } + if (settings.message) { + entry.message = settings.message; + } + retVal.emplace_back(std::move(entry)); +} + +template +static inline std::tuple, std::vector> qConfigGenRTTI(const std::vector* configStructs = nullptr) // Entries (of type T, derived from qConfigRTTIentry) and list of config structs. Filter: struct types Structs at compile time (code only generated for those), else the names in configStructs at runtime (all if nullptr) +{ + using namespace ::qConfig; // For the settings helpers (min, max, ...) and print_type used in GPUSettingsList.h / qconfig_internal.h + std::vector retVal; + std::vector retStructs; + if constexpr (sizeof...(Structs) > 0) { + retStructs.resize(sizeof...(Structs)); + } else if (configStructs) { + retStructs = *configStructs; + } +#define QCONFIG_GEN_RTTI +#include "qconfig_internal.h" +#undef QCONFIG_GEN_RTTI +#undef QCONFIG_RTTI + if (std::find(retStructs.begin(), retStructs.end(), "") != retStructs.end()) { + throw std::runtime_error("qConfigGenRTTI: requested type is not a config struct"); + } + return {std::move(retVal), std::move(retStructs)}; +} + +#endif // QCONFIG_GENRTTI_INC diff --git a/GPU/GPUTracking/utils/qconfig_internal.h b/GPU/GPUTracking/utils/qconfig_internal.h index 04ecced0cb34a..7e71bcedc226b 100644 --- a/GPU/GPUTracking/utils/qconfig_internal.h +++ b/GPU/GPUTracking/utils/qconfig_internal.h @@ -18,8 +18,6 @@ #include "qconfig_general.h" #include "qconfig_helpers.h" -#if !defined(QCONFIG_HEADER_GUARD_NO_INCLUDE) || defined(QCONFIG_GENRTC) - #ifdef QCONFIG_PARSE #define QCONFIG_COMPARE(name, optname, optnameshort) \ @@ -210,6 +208,7 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // // End QCONFIG_PRINT #elif defined(QCONFIG_INSTANCE) #define BeginNamespace(name) \ + using namespace name; \ namespace name \ { #define EndNamespace() } @@ -254,7 +253,7 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // out << "struct " << qon_mxstr(name) << " {\n"; #define BeginSubConfig(name, instance, parent, preoptname, preoptnameshort, descr, ...) BeginConfig(name, instance) #define EndConfig() \ - out << "};"; \ + out << "};\n"; \ } #define BeginNamespace(name) out << "namespace " << qon_mxstr(name) << " {\n"; #define EndNamespace() out << "}\n"; @@ -262,12 +261,37 @@ void qConfigPrint(const T& qconfig_tmp_object, const std::string& blockName); // #define AddOptionSet(...) // End QCONFIG_PRINT_RTC +#elif defined(QCONFIG_GEN_RTTI) +#define QCONFIG_RTTI(vName, vType, rtc, variable, vector, count, defaultValue, ...) \ + qConfigRTTIEntry(retVal, \ + T{::qConfig::qConfigRTTIentry{.name = qon_mxstr(vName), .type = qon_mxstr(vType), .isRTC = rtc, .isVariable = variable, .isVector = vector, .offset = static_cast(reinterpret_cast(&qConfigObject.vName) - reinterpret_cast(&qConfigObject)), .configStruct = qConfigStruct}}, \ + count, *qConfigStructIndex, defaultValue, __VA_ARGS__); +#define AddOption(name, type, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, false, false, false, 0, print_type(static_cast(default)), __VA_ARGS__) +#define AddOptionRTC(name, type, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, true, false, false, 0, print_type(static_cast(default)), __VA_ARGS__) +#define AddVariable(name, type, default) QCONFIG_RTTI(name, type, false, true, false, 0, print_type(static_cast(default)), "") +#define AddVariableRTC(name, type, default) QCONFIG_RTTI(name, type, true, true, false, 0, print_type(static_cast(default)), "") +#define AddOptionVec(name, type, optname, optnameshort, ...) QCONFIG_RTTI(name, type, false, false, true, 0, "", __VA_ARGS__) +#define AddOptionArray(name, type, count, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, false, false, false, count, qon_mxstr(default), __VA_ARGS__) +#define AddOptionArrayRTC(name, type, count, default, optname, optnameshort, ...) QCONFIG_RTTI(name, type, true, false, false, count, qon_mxstr(default), __VA_ARGS__) +#define AddOptionSet(...) +#define AddSubConfig(name, instance) +#define BeginConfig(name, instance) \ + if constexpr (qConfigRTTISelected) { \ + if (const auto qConfigStructIndex = qConfigRTTIStructIndex(retStructs, qon_mxstr(name), configStructs != nullptr)) { \ + [[maybe_unused]] constexpr const char* qConfigStruct = qon_mxstr(name); \ + [[maybe_unused]] const name qConfigObject{}; // Default-constructed instance to compute the member offsets +#define BeginSubConfig(name, instance, parent, preoptname, preoptnameshort, descr, ...) BeginConfig(name, instance) +#define EndConfig() \ + } \ + } + +// End QCONFIG_GEN_RTTI #else // Define structures #error Internal error, qconfig_internal called with action type #endif +#define QCONFIG_INTERNAL #include "qconfig_wrapinclude.h" - -#endif // QCONFIG_HEADER_GUARD_NO_INCLUDE +#undef QCONFIG_INTERNAL diff --git a/GPU/GPUTracking/utils/qconfig_internal_helpers.h b/GPU/GPUTracking/utils/qconfig_internal_helpers.h index c3702e819256b..0eb16d22d60f0 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 // clang-format off + qConfigSettings(const qConfigSettings v) : checkMin(false), checkMax(false), doSet(false), doDefault(false), min(), max(), set(), message(v.message), allowEmpty(v.allowEmpty) {}; // clang-format on + bool checkMin, checkMax; + bool doSet, doDefault; + T min, max; + T set; + const char* message; + bool allowEmpty; +}; + +template +struct qSettingsType { + typedef T settingsType; +}; +template +struct qSettingsType> { + typedef int32_t settingsType; +}; + +template +struct qConfigSettingsParser { // Parse the additional settings (min, max, set, def, message) of an option + // Recursive handling of additional settings + static inline void qProcessSetting(qConfigSettings& settings, qmin_t minval) + { + static_assert(!std::is_same_v, "min option not supported for boolean settings"); + settings.checkMin = true; + settings.min = minval.v; + } + static inline void qProcessSetting(qConfigSettings& settings, qmax_t maxval) + { + static_assert(!std::is_same_v, "max option not supported for boolean settings"); + settings.checkMax = true; + settings.max = maxval.v; + } + static inline void qProcessSetting(qConfigSettings& settings, qmessage_t msg) { settings.message = msg.v; } + template + static inline void qProcessSetting(qConfigSettings& settings, qset_t set) + { + settings.doSet = true; + settings.set = set.v; + } + template + static inline void qProcessSetting(qConfigSettings& settings, qdef_t set) + { + settings.doDefault = true; + settings.set = set.v; + } + + static inline void qAddOptionSettings(qConfigSettings& /*settings*/) {} + template + static inline void qAddOptionSettings(qConfigSettings& settings, Arg1&& arg1, Args&&... args) + { + qProcessSetting(settings, arg1); + qAddOptionSettings(settings, args...); + } + + template + static inline auto qConfigGetSettings(Args&... args) + { + qConfigSettings::settingsType> settings; + qConfigSettingsParser::settingsType>::qAddOptionSettings(settings, args...); + return (settings); + } +}; } // namespace qConfig #endif diff --git a/GPU/GPUTracking/utils/qconfig_print.inc b/GPU/GPUTracking/utils/qconfig_print.inc new file mode 100644 index 0000000000000..0928bae1a1592 --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_print.inc @@ -0,0 +1,26 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file qconfig_print.inc +/// \author David Rohr + +// Generates qConfigPrint for all config structs in the namespace it is included in, after the instances of the top-level configs (e.g. configStandalone). +// Requires , , , , "qconfig_helpers.h" to be included before (outside of the namespace). + +#ifndef QCONFIG_PRINT_INC +#define QCONFIG_PRINT_INC + +std::vector> qprint_global; +#define QCONFIG_PRINT +#include "qconfig_internal.h" +#undef QCONFIG_PRINT + +#endif // QCONFIG_PRINT_INC diff --git a/GPU/GPUTracking/utils/qconfig_rtc.h b/GPU/GPUTracking/utils/qconfig_rtc.h index 7e8090004a8bd..c98f1f8cf309a 100644 --- a/GPU/GPUTracking/utils/qconfig_rtc.h +++ b/GPU/GPUTracking/utils/qconfig_rtc.h @@ -18,15 +18,6 @@ #include "qconfig.h" #include "qconfig_helpers.h" -#ifndef qon_mxstr -#define qon_mstr(a) #a -#define qon_mxstr(a) qon_mstr(a) -#endif -#ifndef qon_mxcat -#define qon_mcat(a, b) a##b -#define qon_mxcat(a, b) qon_mcat(a, b) -#endif - template static std::string qConfigPrintRtc(const T& tSrc, bool useConstexpr) { diff --git a/GPU/GPUTracking/utils/qconfig_rtti.h b/GPU/GPUTracking/utils/qconfig_rtti.h new file mode 100644 index 0000000000000..63e58bd96341c --- /dev/null +++ b/GPU/GPUTracking/utils/qconfig_rtti.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file qconfig_rtti.h +/// \author David Rohr + +#ifndef QCONFIG_RTTI_H +#define QCONFIG_RTTI_H + +#include "qconfig.h" +#include "qconfig_helpers.h" +#include + +namespace qConfig +{ +struct qConfigRTTIentry { + std::string name = ""; // Name of option + std::string type = ""; // Data type of option + uint8_t size = 0; // sizeof(type) + bool isRTC = false; // enable RTC constexpr optimization + bool isVariable = false; // is a variable, not settable at command line + bool isVector = false; // is std::vector + uint16_t arraySize = 0; // size of array if std::array, 0 = no array + uint16_t configStructIndex = 0; // Index in configStructVector + size_t offset = 0; // Offset address in configStruct + std::string configStruct = ""; // Name of config struct that stores the option + std::string max = ""; // Maximum Value + std::string min = ""; // Minimum Value + std::string defaultValue = ""; // Default Value + std::string description = ""; // Description of option + std::string message = ""; // printf format string, printed with the value when the option is set +}; +} // namespace qConfig + +#endif // QCONFIG_RTTI_H diff --git a/GPU/GPUTracking/utils/qconfig_wrapinclude.h b/GPU/GPUTracking/utils/qconfig_wrapinclude.h index ae6350ebd5f43..40a52b45948f7 100644 --- a/GPU/GPUTracking/utils/qconfig_wrapinclude.h +++ b/GPU/GPUTracking/utils/qconfig_wrapinclude.h @@ -12,7 +12,10 @@ /// \file qconfig_wrapinclude.h /// \author David Rohr -#ifndef QCONFIG_HEADER_GUARD_NO_INCLUDE +#ifndef QCONFIG_H +#error Must include qconfig.h before using qconfig_internal.h, etc... +#endif + #ifndef AddHelp #define AddHelp(cmd, cmdshort) #endif @@ -53,6 +56,10 @@ #define BeginHiddenConfig(name, instance) BeginSubConfig(name, instance, , , , ) #endif +#ifdef QCONFIG_GENRTC +#define QCONFIG_INTERNAL +#endif + #include "qconfigoptions.h" #undef AddOption @@ -79,7 +86,6 @@ #ifdef QCONFIG_COMPARE #undef QCONFIG_COMPARE #endif - -#else -#undef QCONFIG_HEADER_GUARD_NO_INCLUDE +#ifdef QCONFIG_INTERNAL +#undef QCONFIG_INTERNAL #endif diff --git a/GPU/GPUTracking/utils/threadserver.h b/GPU/GPUTracking/utils/threadserver.h index c8dfe831fd578..606531f46f201 100644 --- a/GPU/GPUTracking/utils/threadserver.h +++ b/GPU/GPUTracking/utils/threadserver.h @@ -98,7 +98,7 @@ class qThreadCls qThreadParamCls& XthreadParam = *((qThreadParamCls*)&this->threadParam); XthreadParam.pCls = pCls; - XthreadParam.pFunc = (void(S::*)(void*))pFunc; + XthreadParam.pFunc = (void (S::*)(void*))pFunc; XthreadParam.threadNum = threadNum; XthreadParam.pinCPU = pinCPU; pthread_t thr; @@ -150,7 +150,7 @@ void* qThreadCls::qThreadWrapperCls(T* arg) sched_setaffinity(0, sizeof(tmp_mask), &tmp_mask); } - void (S::*pFunc)(T*) = (void(S::*)(T*))arg_A->pFunc; + void (S::*pFunc)(T*) = (void (S::*)(T*))arg_A->pFunc; (arg_A->pCls->*pFunc)(arg); arg_A->threadMutex[1].Unlock(); diff --git a/GPU/GPUTracking/utils/vecpod.h b/GPU/GPUTracking/utils/vecpod.h index 4aa4f9ba744b2..e0c1e90c8641c 100644 --- a/GPU/GPUTracking/utils/vecpod.h +++ b/GPU/GPUTracking/utils/vecpod.h @@ -20,11 +20,11 @@ template struct vecpod_allocator { typedef T value_type; - vecpod_allocator() noexcept : stdalloc() {} - T* allocate(std::size_t n) { return stdalloc.allocate(n); } - void deallocate(T* p, std::size_t n) { stdalloc.deallocate(p, n); } + vecpod_allocator() noexcept = default; + T* allocate(std::size_t n) { return std::allocator().allocate(n); } + void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } static void construct(T*) {} - std::allocator stdalloc; + bool operator==(const vecpod_allocator&) const { return true; } // Stateless (no member, so std::vector does not store it), needed e.g. for the move assignment of std::vector }; template