diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index cf4d4348075bf..ee82d5c8f084c 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -526,6 +526,11 @@ AddOptionVec(filterMacros, std::string, "", 0, "ROOT macros used as track filter AddOption(guiReplyTimeout, uint32_t, 100, "", 0, "Time in ms the Qt GUI waits at most for the display to process its update") AddOption(guiMaxUpdatesAhead, uint32_t, 3, "", 0, "Number of updates the Qt GUI may send ahead of the last one the display has processed, before it waits for the display") AddOption(listFrontendOptionHandling, bool, false, "", 0, "List all assigned keys in frontend, Configurable settings in Qt GUI, and missing configuration options") +AddOption(consoleDelay, float, 0.5f, "", 0, "Time in seconds to open / close the console (^ key)") +AddOption(consoleAlpha, float, 0.8f, "", 0, "Opacity of the console background") +AddOption(consoleVertical, float, 0.5f, "", 0, "Fraction of the window height covered by the fully opened console") +AddOption(runScript, std::string, "", "", 0, "Console script file, run before the first frame of the display") +AddOption(consoleHistorySize, uint32_t, 1000000, "", 0, "Number of console lines kept, the history is shortened to this when it reaches twice the size") AddSubConfig(GPUSettingsDisplayLight, light) AddSubConfig(GPUSettingsDisplayHeavy, heavy) AddSubConfig(GPUSettingsDisplayRenderer, renderer) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 70ac9bdd4282c..682dfbe60c61c 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -117,6 +117,7 @@ set(SRCS ../utils/qsem.cxx GPUDisplay.cxx helpers/GPUDisplayMagneticField.cxx frontend/GPUDisplayFrontend.cxx + frontend/GPUDisplayConsole.cxx frontend/GPUDisplayFrontendNone.cxx backend/GPUDisplayBackend.cxx backend/GPUDisplayBackendNone.cxx) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index f6762f5850de7..9f9abda84cc21 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -171,12 +171,22 @@ int32_t GPUDisplay::InitDisplay(bool initFailure) if (!initFailure) { retVal = InitDisplay_internal(); } + if (retVal == 0) { + buildConfig(); + if (mConfig.listFrontendOptionHandling) { + mFrontend->listConfig(); + } + } } catch (const std::runtime_error& e) { GPUError("%s", e.what()); retVal = 1; } - mInitResult = retVal == 0 ? 1 : -1; mOldCfg = {mCfgH, mCfgL, mCfgR}; + std::unique_lock lock(mFrontend->mExitStateMutex); + mInitResult = retVal == 0 ? 1 : -1; + mFrontend->mDisplayRunning = retVal == 0; // Only after successful initialization, StopDisplay waits for it to become false again + mFrontend->mInitCV.notify_all(); + mFrontend->mInitCV.wait(lock, [this] { return mFrontend->mStartDisplayDone; }); // Both threads have finished their initialization return (retVal); } @@ -727,9 +737,23 @@ void GPUDisplay::DrawGLScene_internal(bool suspended, float animateTime, bool re mFramesDoneFPS = 0; } + const float consoleFraction = consoleVerticalFraction(); + const bool drawText = (mCfgC.mPrintInfoText & 1) || consoleFraction > 0.f; + if (drawText) { + mBackend->updateFontScale(mFrontend->getDisplayScale()); + mBackend->prepareText(); + } if (mCfgC.mPrintInfoText & 1 || mCfgC.mPrintInfoTextGUI) { showInfo(info); } + if (consoleFraction > 0.f) { + const bool cursor = mCfgC.showConsole && std::fmod(mConsoleTimer.GetCurrentElapsedTime(), 1.0) < 0.5; // Blinking, the timer runs while the console is open + mBackend->GLDrawConsole(consoleFraction, mFrontend->mConsole->mConsoleHistory, mFrontend->mConsole->shownText(), cursor ? (int32_t)mFrontend->mConsole->mConsoleTextPos : -1); + } + if (drawText) { + mBackend->finishText(); + } + mFrontend->mConsole->consoleLimitHistory(mConfig.consoleHistorySize); } mBackend->finishFrame(doScreenshot, renderToMixBuffer, mixSlaveImage); @@ -776,11 +800,5 @@ void GPUDisplay::WaitTillEventShown() int32_t GPUDisplay::StartDisplay() { mLoadAndShowEvent = true; - if (mFrontend->StartDisplay()) { - return (1); - } - while (mInitResult == 0) { - Sleep(10); - } - return (mInitResult != 1); + return mFrontend->StartDisplay(); } diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 55519fdd39b56..ddc239c300297 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -61,7 +61,7 @@ class GPUDisplay : public GPUDisplayInterface void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; } void UpdateParam(const GPUParam* param) override { mParam = param; } - void HandleKey(uint8_t key); + void HandleKey(uint8_t key, bool external = false); // external: from the Qt GUI or the console, not typed into the event display console int32_t DrawGLScene(bool suspended = false); void HandleExternalGUIKey(int32_t key); int32_t InitDisplay(bool initFailure = false); @@ -198,6 +198,7 @@ class GPUDisplay : public GPUDisplayInterface void startAnimation(); int32_t animateCamera(bool suspended, float& animateTime, float& mixSlaveImage, hmm_mat4& nextViewMatrix); void showInfo(const char* info); + float consoleVerticalFraction(); // Fraction of the window covered by the console (animated), 0 if hidden void ActivateColor(); void SetColorTRD(); void SetColorTOF(); @@ -321,14 +322,15 @@ class GPUDisplay : public GPUDisplayInterface opengl_spline mAnimationSplines[8]; char mInfoText2[1024]; - std::string mGUIInfoStatus; // Latest status line (FPS etc.) for the Qt GUI - std::string mGUIInfoText; // Latest info message (SetInfo) for the Qt GUI - uint64_t mGUIInfoTextCounter = 0; // Number of info messages, the GUI shows a new one for some seconds - HighResTimer mInfoText2Timer, mInfoHelpTimer; + std::string mGUIInfoStatus; // Latest status line (FPS etc.) for the Qt GUI + std::string mGUIInfoText; // Latest info message (SetInfo) for the Qt GUI + uint64_t mGUIInfoTextCounter = 0; // Number of info messages, the GUI shows a new one for some seconds + HighResTimer mInfoText2Timer, mInfoHelpTimer, mConsoleTimer; // mConsoleTimer: opening / closing animation of the console + bool mConsoleShown = false; // showConsole of the last checkAndApplyConfigChanges std::vector mThreadBuffers; std::vector, 2>, NSECTORS>>> mThreadTracks; - volatile int32_t mInitResult = 0; + int32_t mInitResult = 0; // 1 success, -1 failure, protected by mFrontend->mExitStateMutex float mFPSScale = 1, mFPSScaleadjust = 0; int32_t mFramesDone = 0, mFramesDoneFPS = 0; diff --git a/GPU/GPUTracking/display/GPUDisplayControl.h b/GPU/GPUTracking/display/GPUDisplayControl.h index 0e2585501d239..1d433a8f13751 100644 --- a/GPU/GPUTracking/display/GPUDisplayControl.h +++ b/GPU/GPUTracking/display/GPUDisplayControl.h @@ -40,6 +40,7 @@ BeginHiddenConfig(GPUDisplayControl, control) AddVariable(mGLAnimationFile, std::string, "glanimation.tmp") // File for saving / loading the animation path AddVariable(mGuiDarkMode, int8_t, -1) // Dark mode of the Qt GUI, -1 = not yet set: the GUI reports the system scheme when it starts AddVariable(mGuiScale, float, 1.f) // Scale factor of text and elements of the Qt GUI (on top of the system scaling), Ctrl + mouse wheel / Ctrl + 0 in the GUI + AddVariable(showConsole, bool, false) // Console shown (^ key): all other keys go to the console AddVariable(dummy, bool, false) // dummy value to reference for keys that do not change variables EndConfig() EndNamespace() // o2::gpu diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index 2f353ea8b60c2..400967da1c442 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -132,7 +132,7 @@ void GPUDisplayBackend::loadFont() mFontRasterScale = mDisplay->frontend()->getDisplayScale(); FT_Set_Pixel_Sizes(face, 0, (uint32_t)(fontSize * mFontRasterScale + 0.5f)); // Rasterize at physical pixel size for HiDPI - for (uint32_t i = 0; i < 128; i++) { + for (uint32_t i = 0; i < SOLID_GLYPH; i++) { if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { GPUError("Error loading freetype symbol"); FT_Done_Face(face); @@ -145,6 +145,8 @@ void GPUDisplayBackend::loadFont() } addFontSymbol(i, glyph->bitmap.width, glyph->bitmap.rows, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x, glyph->bitmap.buffer); } + std::vector solid(8 * 8, 255); + addFontSymbol(SOLID_GLYPH, 8, 8, 0, 0, 0, solid.data()); initializeTextDrawing(); FT_Done_Face(face); FT_Done_FreeType(ft); @@ -207,3 +209,42 @@ bool GPUDisplayBackend::smoothFont() { return mDisplay->cfg().smoothFont < 0 ? (mDisplay->cfg().fontSize > 12) : mDisplay->cfg().smoothFont; } + +void GPUDisplayBackend::GLDrawLine(float x0, float y0, float x1, float y1, float width, const float* color) +{ + const float dx = x1 - x0, dy = y1 - y0, length = std::sqrt(dx * dx + dy * dy); + if (length == 0.f) { + return; + } + const float nx = -dy / length * width * 0.5f, ny = dx / length * width * 0.5f; // Perpendicular, half the width + GLDrawQuad({{x0 + nx, y0 + ny}, {x0 - nx, y0 - ny}, {x1 - nx, y1 - ny}, {x1 + nx, y1 + ny}}, color); +} + +void GPUDisplayBackend::GLDrawConsole(float verticalFraction, std::span previous, const std::string& current, int32_t cursorPos) +{ + if (!mFreetypeInitialized) { + return; + } + const float scale = mDisplay->frontend()->getDisplayScale(); + const float colorValue = mDisplay->cfgL().invertColors ? 0.f : 1.f; + float textColor[4] = {colorValue, colorValue, colorValue, 1.f}; + const float backgroundColor[4] = {1.f - colorValue, 1.f - colorValue, 1.f - colorValue, mDisplay->cfg().consoleAlpha}; + const float width = mScreenWidth, top = mScreenHeight, bottom = top * (1.f - verticalFraction); + GLDrawQuad({{0.f, bottom}, {width, bottom}, {width, top}, {0.f, top}}, backgroundColor); + GLDrawLine(0.f, bottom, width, bottom, scale, textColor); + + static constexpr float MARGIN = 6.f; // Logical pixels from the text to the left border and to the line + const float lineHeight = std::max(20, mDisplay->drawTextFontSize() + 4) * scale; // As the info text of GPUDisplay + const float descent = 0.25f * mDisplay->drawTextFontSize() * scale; // Below the baseline, approximately + const size_t maxChars = mFontCharWidth > 0.f ? std::max(0.f, (width / scale - 2 * MARGIN) / mFontCharWidth) : SIZE_MAX; // Longer lines are cut + float y = bottom + (0.5f + MARGIN) * scale + descent; // Baseline of the lowest line, MARGIN above the line (half its width) + if (cursorPos >= 0 && (size_t)cursorPos < maxChars && mFontCharWidth > 0.f) { // Monospace font: position from the character width + GLPrintText("_", (MARGIN + cursorPos * mFontCharWidth) * scale, y, textColor, scale / mFontRasterScale, 0); + } + for (size_t i = 0; i <= previous.size() && y + lineHeight - descent <= top; i++, y += lineHeight) { + const std::string& text = i == 0 ? current : previous[previous.size() - i]; + if (!text.empty() && maxChars) { + GLPrintText(text.c_str(), MARGIN * scale, y, textColor, scale / mFontRasterScale, std::min(text.size(), maxChars)); + } + } +} diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 411af384221b5..9abe2859ee9c4 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -17,6 +17,8 @@ #include "GPUCommonDef.h" #include "../utils/vecpod.h" +#include +#include #include #include #include @@ -99,7 +101,10 @@ class GPUDisplayBackend const char* backendName() const { return mBackendName; } virtual void resizeScene(uint32_t width, uint32_t height) {} virtual size_t needMultiVBO() { return 0; } - virtual void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) = 0; // nCharacters > 0: at most that many characters, otherwise until the terminating 0 + virtual void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) = 0; // nCharacters > 0: at most that many characters, otherwise until the terminating 0 + virtual void GLDrawQuad(const float (&corners)[4][2], const float* color) = 0; // Filled quad with the corners counterclockwise, in pixels with y from the bottom, drawn with the text (SOLID_GLYPH) + void GLDrawLine(float x0, float y0, float x1, float y1, float width, const float* color); // As GLDrawQuad + void GLDrawConsole(float verticalFraction, std::span previous, const std::string& current, int32_t cursorPos); // Console over the top verticalFraction of the window: current text in the lowest line, previous ones above (last one lowest), cursor _ at cursorPos (< 0: none) static GPUDisplayBackend* getBackend(const char* type); std::vector getPixels(); virtual float getYFactor() const { return 1.0f; } @@ -119,8 +124,9 @@ class GPUDisplayBackend std::vector mIndirectSectorOffset; vecpod mCmdBuffer; bool mFreetypeInitialized = false; - float mFontRasterScale = 1.f; // Display scale the font was rasterized for - float mFontCharWidth = 0.f; // Advance of a character of the (monospace) font in logical pixels, 0 without Freetype font + float mFontRasterScale = 1.f; // Display scale the font was rasterized for + static constexpr int32_t SOLID_GLYPH = 128; // Fully opaque glyph after the characters (0 - 127), for quads drawn with the text + float mFontCharWidth = 0.f; // Advance of a character of the (monospace) font in logical pixels, 0 without Freetype font std::vector mScreenshotPixels; static constexpr float SMOOTH_POINT_EXTRA_SIZE = 0.6f; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h index 825fd9b353aa6..d5ec0e22b8254 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendNone.h @@ -40,6 +40,7 @@ class GPUDisplayBackendNone : public GPUDisplayBackend void pointSizeFactor(float factor) override {} void lineWidthFactor(float factor) override {} void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override {} + void GLDrawQuad(const float (&corners)[4][2], const float* color) override {} void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override {} void initializeTextDrawing() override {} }; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index e9f4248ad4503..3d91ee802c16f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -684,8 +684,8 @@ void GPUDisplayBackendOpenGL::GLPrintText(const char* s, float x, float y, float scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 } for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + if (*c < 32 || *c >= SOLID_GLYPH) { // Printable ASCII only, SOLID_GLYPH is not a character + GPUError("Trying to draw unsupported symbol: %d", (int32_t)*c); continue; } FontSymbolOpenGL sym = mFontSymbols[*c]; @@ -701,11 +701,7 @@ void GPUDisplayBackendOpenGL::GLPrintText(const char* s, float x, float y, float {xpos, ypos + h, 0.0f, 0.0f}, {xpos + w, ypos, 1.0f, 1.0f}, {xpos + w, ypos + h, 1.0f, 0.0f}}; - CHKERR(glBindTexture(GL_TEXTURE_2D, sym.texId)); - CHKERR(glBindBuffer(GL_ARRAY_BUFFER, VBO_text)); - CHKERR(glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); - CHKERR(glBindBuffer(GL_ARRAY_BUFFER, 0)); - CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); + drawTextVertices(vertices, sym.texId); } x += (sym.advance >> 6) * scale; // shift is in 1/64th of a pixel } @@ -714,6 +710,34 @@ void GPUDisplayBackendOpenGL::GLPrintText(const char* s, float x, float y, float } } +void GPUDisplayBackendOpenGL::drawTextVertices(const float (&vertices)[6][4], uint32_t texId) +{ + CHKERR(glBindTexture(GL_TEXTURE_2D, texId)); + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, VBO_text)); + CHKERR(glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, 0)); + CHKERR(glDrawArrays(GL_TRIANGLES, 0, 6)); +} + +void GPUDisplayBackendOpenGL::GLDrawQuad(const float (&corners)[4][2], const float* color) +{ + if (!mFreetypeInitialized) { + return; + } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); + static constexpr int32_t order[6] = {0, 1, 2, 0, 2, 3}; + float vertices[6][4]; + for (int32_t i = 0; i < 6; i++) { + vertices[i][0] = corners[order[i]][0]; + vertices[i][1] = corners[order[i]][1]; + vertices[i][2] = vertices[i][3] = 0.5f; // Center of the opaque glyph + } + drawTextVertices(vertices, mFontSymbols[SOLID_GLYPH].texId); + if (!mDisplay->useMultiVBO()) { + CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[0])); // As in GLPrintText + } +} + void GPUDisplayBackendOpenGL::ClearOffscreenBuffers() { if (mMixBuffer.created) { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index f6e9d8801b6d0..e7bc6480878f5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -80,6 +80,8 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void initializeTextDrawing() override; void clearFontSymbols() override; void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; + void GLDrawQuad(const float (&corners)[4][2], const float* color) override; + void drawTextVertices(const float (&vertices)[6][4], uint32_t texId); // One quad with the text shader, after GLPrintText / GLDrawQuad set the color struct FontSymbolOpenGL : public FontSymbol { uint32_t texId; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index b76fc71de66d1..df689d1fdfd0f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1900,8 +1900,8 @@ void GPUDisplayBackendVulkan::GLPrintText(const char* s, float x, float y, float } for (const unsigned char* c = (const unsigned char*)s; *c && (nCharacters == 0 || c < (const unsigned char*)s + nCharacters); c++) { - if ((int32_t)*c >= (int32_t)mFontSymbols.size()) { - GPUError("Trying to draw unsupported symbol: %d > %d\n", (int32_t)*c, (int32_t)mFontSymbols.size()); + if (*c < 32 || *c >= SOLID_GLYPH) { // Printable ASCII only, SOLID_GLYPH is not a character + GPUError("Trying to draw unsupported symbol: %d", (int32_t)*c); continue; } const FontSymbolVulkan& sym = mFontSymbols[*c]; @@ -1925,6 +1925,11 @@ void GPUDisplayBackendVulkan::GLPrintText(const char* s, float x, float y, float x += (sym.advance >> 6) * scale; // shift is in 1/64th of a pixel } + addTextDrawCommand(firstVertex, color); +} + +void GPUDisplayBackendVulkan::addTextDrawCommand(size_t firstVertex, const float* color) +{ size_t nVertices = mFontVertexBufferHost.size() / 4 - firstVertex; if (nVertices) { @@ -1937,6 +1942,21 @@ void GPUDisplayBackendVulkan::GLPrintText(const char* s, float x, float y, float } } +void GPUDisplayBackendVulkan::GLDrawQuad(const float (&corners)[4][2], const float* color) +{ + if (!mFreetypeInitialized) { + return; + } + const FontSymbolVulkan& sym = mFontSymbols[SOLID_GLYPH]; + const float u = (sym.x0 + sym.x1) * 0.5f, v = (sym.y0 + sym.y1) * 0.5f; // Center of the opaque glyph in the font atlas + const size_t firstVertex = mFontVertexBufferHost.size() / 4; + for (int32_t i : {0, 2, 1, 0, 3, 2}) { // Winding as the glyph quads of GLPrintText, the text pipeline culls back faces + mFontVertexBufferHost.insert(mFontVertexBufferHost.end(), {corners[i][0], mScreenHeight - 1 - corners[i][1], u, v}); + } + mHasDrawnText = true; + addTextDrawCommand(firstVertex, color); +} + void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageView view, vk::ImageLayout layout, std::vector& pixels) { const vk::Format format = mSurfaceFormat.format; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index d76fa357bb216..1b24f2199e3a6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -137,6 +137,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void initializeTextDrawing() override; void clearFontSymbols() override; void GLPrintText(const char* s, float x, float y, float* color, float scale, uint32_t nCharacters) override; + void GLDrawQuad(const float (&corners)[4][2], const float* color) override; + void addTextDrawCommand(size_t firstVertex, const float* color); // Draw the text vertices from firstVertex on with this color uint32_t mGraphicsFamily; SwapChainSupportDetails mSwapChainDetails; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx new file mode 100644 index 0000000000000..17134912d37af --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.cxx @@ -0,0 +1,274 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayConsole.cxx +/// \author David Rohr + +#include "GPUDisplayConsole.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::gpu; + +GPUDisplayConsole::GPUDisplayConsole(const std::string& title) : mConsoleHistory{title} {} + +void GPUDisplayConsole::handleConsoleKey(uint8_t key) +{ + const uint32_t nHistory = mConsoleHistory.size(); + auto isOutput = [this](uint32_t i) { return mConsoleHistory[i].size() && mConsoleHistory[i][0] == ' '; }; // Indented output of commands, not selectable + if (key == KEY_UP || key == KEY_DOWN) { // Entry 0 is the title, not selectable + if (key == KEY_UP) { + uint32_t i = mConsoleTextCurrent ? mConsoleTextCurrent : nHistory; + while (--i >= 1 && isOutput(i)) { + } + mConsoleTextCurrent = i >= 1 ? i : mConsoleTextCurrent; + } else if (mConsoleTextCurrent) { + uint32_t i = mConsoleTextCurrent; + while (++i < nHistory && isOutput(i)) { + } + mConsoleTextCurrent = i < nHistory ? i : 0; + } + mConsoleTextPos = 0; + return; + } else if (key == KEY_ENTER) { + std::string command = mConsoleTextCurrent ? mConsoleHistory[mConsoleTextCurrent] : mConsoleText; + command.erase(0, std::min(command.size(), command.find_first_not_of(' '))); // Leading spaces would mark it as output in the history + mConsoleText.clear(); + mConsoleTextCurrent = mConsoleTextPos = 0; + if (!command.empty()) { + consoleAppend(command); // Before its output + handleConsoleCommand(command); + } + return; + } else if (key != KEY_LEFT && key != KEY_RIGHT && key != KEY_BACKSPACE && key != KEY_DELETE && (key < 32 || key >= 127)) { // Other special keys are ignored + return; + } + if (mConsoleTextCurrent) { // Edit a copy of the history entry, with the cursor at its end (left: on its last character), delete then removes nothing + mConsoleText = mConsoleHistory[mConsoleTextCurrent]; + mConsoleTextCurrent = 0; + mConsoleTextPos = mConsoleText.size() - (key == KEY_LEFT && mConsoleText.size()); + if (key == KEY_LEFT || key == KEY_RIGHT) { + return; + } + } + mConsoleTextPos = std::min(mConsoleTextPos, mConsoleText.size()); + if (key == KEY_LEFT) { + mConsoleTextPos -= mConsoleTextPos > 0; + } else if (key == KEY_RIGHT) { + mConsoleTextPos += mConsoleTextPos < mConsoleText.size(); + } else if (key == KEY_BACKSPACE) { + if (mConsoleTextPos > 0) { + mConsoleText.erase(--mConsoleTextPos, 1); + } + } else if (key == KEY_DELETE) { + if (mConsoleTextPos < mConsoleText.size()) { + mConsoleText.erase(mConsoleTextPos, 1); + } + } else { + mConsoleText.insert(mConsoleTextPos++, 1, (char)key); + } +} + +void GPUDisplayConsole::consolePrint(const char* format, ...) +{ + char buffer[1024]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + consoleAppend(std::string(" ") + buffer); // Indented: output, not a command, skipped by up / down +} + +void GPUDisplayConsole::consoleInfo(const char* format, const char* text) +{ + const auto now = std::chrono::steady_clock::now(); + if (!mConsoleInfoLastMessage.empty() && mConsoleInfoLastMessage == format && now - mConsoleInfoLastTime < std::chrono::seconds(2)) { + mConsoleHistory.back() = std::string(" ") + text; // Same message again shortly after (animation progress, held keys): update its line + } else { + consoleAppend(std::string(" ") + text); + } + mConsoleInfoLastMessage = strcmp(format, "%s") ? format : ""; // A plain "%s" can be any message, never merged + mConsoleInfoLastTime = now; +} + +void GPUDisplayConsole::consoleAppend(std::string line) +{ + mConsoleHistory.emplace_back(std::move(line)); + mConsoleInfoLastMessage.clear(); +} + +void GPUDisplayConsole::consoleLimitHistory(uint32_t historySize) +{ + const size_t size = std::max(historySize, 2); + if (mConsoleHistory.size() < 2 * size) { + return; + } + const size_t removed = mConsoleHistory.size() - size; + std::move(mConsoleHistory.begin() + 1 + removed, mConsoleHistory.end(), mConsoleHistory.begin() + 1); // Keep the title (entry 0) + mConsoleHistory.resize(size); + mConsoleTextCurrent = mConsoleTextCurrent > removed ? mConsoleTextCurrent - removed : 0; +} + +// Tokens separated by spaces: text in single / double quotes and characters after a backslash are plain text (the token is a string), +// otherwise a token is int64_t for [+-]digits, double for [+-]digits with one '.', a string otherwise. Throws std::runtime_error for an unterminated quote / trailing backslash. + +std::tuple> GPUDisplayConsole::parseConsoleCommand(std::string_view cmd) const +{ + std::vector> tokens; // Text, plain text (quoted / escaped) + std::string token; + bool inToken = false, plainText = false, escape = false; + char quote = 0; + auto finishToken = [&]() { + if (inToken) { + tokens.emplace_back(std::move(token), plainText); + token.clear(); + inToken = plainText = false; + } + }; + for (char c : cmd) { + if (escape) { + token += c; + escape = false; + } else if (quote) { + if (c == quote) { + quote = 0; + } else { + token += c; + } + } else if (c == '\\') { + escape = inToken = plainText = true; + } else if (c == '"' || c == '\'') { + quote = c; + inToken = plainText = true; + } else if (c == ' ') { + finishToken(); + } else { + token += c; + inToken = true; + } + } + if (quote || escape) { + throw std::runtime_error(quote ? "unterminated quote" : "backslash at the end"); + } + finishToken(); + if (tokens.empty()) { + throw std::runtime_error("empty command"); + } + + const std::string& name = tokens[0].first; + const auto it = mConsoleCommands.find(name); + if (it == mConsoleCommands.end()) { + throw std::runtime_error("unknown command " + name); + } + const std::string& types = it->second.args; + const bool more = types.size() && types.back() == '+'; + const size_t nFixed = types.size() - more, nArgs = tokens.size() - 1; + if (nArgs < nFixed || (!more && nArgs > nFixed)) { + throw std::runtime_error(name + ": " + std::to_string(nFixed) + (more ? " or more" : "") + " arguments expected, got " + std::to_string(nArgs)); + } + auto autoType = [](const std::string& text, bool plain) -> consoleArg { // int64_t for [+-]digits, double for [+-]digits with one '.', string otherwise or if plain text + const char *begin = text.c_str() + (text[0] == '+' || text[0] == '-'), *end = text.c_str() + text.size(); + const size_t nDigits = std::count_if(begin, end, [](char c) { return c >= '0' && c <= '9'; }); + const size_t nDots = std::count(begin, end, '.'); + const bool number = !plain && nDigits && nDigits + nDots == (size_t)(end - begin); + int64_t intValue = 0; + if (number && nDots == 0 && std::from_chars(begin, end, intValue).ec == std::errc()) { + return text[0] == '-' ? -intValue : intValue; + } else if (number && nDots <= 1) { + return std::stod(text); // Also integers out of the int64_t range + } + return text; + }; + std::vector args; + for (size_t i = 0; i < nArgs; i++) { + const auto& [text, plain] = tokens[i + 1]; + const char type = i < nFixed ? types[i] : '+'; + consoleArg value = type == 's' ? consoleArg(text) : autoType(text, plain); + if (type == 'i' && !std::holds_alternative(value)) { + throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be an integer"); + } else if (type == 'd') { + if (std::holds_alternative(value)) { + value = (double)std::get(value); + } else if (!std::holds_alternative(value)) { + throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be a number"); + } + } else if (type != 's' && type != 'i' && type != '+') { + throw std::runtime_error(name + ": invalid argument types " + types); + } + args.emplace_back(std::move(value)); + } + return {it->second, std::move(args)}; +} + +void GPUDisplayConsole::handleConsoleCommand(std::string_view cmd) +{ + if (cmd.find_first_not_of(' ') == std::string_view::npos) { // Empty line + return; + } + const GPUDisplayConsoleCommand* command = nullptr; + std::vector args; + try { + auto parsed = parseConsoleCommand(cmd); + command = &std::get<0>(parsed); + args = std::move(std::get<1>(parsed)); + } catch (const std::runtime_error& e) { + consolePrint("Invalid command: %s", e.what()); + return; + } + command->func(args); +} + +void GPUDisplayConsole::runScript(std::string_view script) +{ + if (mScriptDepth >= 16) { + consolePrint("Scripts nested too deeply"); + return; + } + mScriptDepth++; + for (size_t pos = 0; pos < script.size();) { + const size_t end = std::min(script.find_first_of("\r\n", pos), script.size()); + handleConsoleCommand(script.substr(pos, end - pos)); // Empty lines (consecutive \r / \n) are ignored + pos = end + 1; + } + mScriptDepth--; +} + +void GPUDisplayConsole::runScriptFile(const std::string& filename) +{ + std::ifstream file(filename, std::ios::binary); + if (!file) { + consolePrint("Cannot open script file %s", filename.c_str()); + return; + } + std::stringstream content; + content << file.rdbuf(); + runScript(content.str()); +} + +void GPUDisplayConsole::printCommands(const std::function& print) +{ + std::vector names; + for (const auto& [name, command] : mConsoleCommands) { + names.emplace_back(name); + } + std::sort(names.begin(), names.end()); + print("Console commands (argument types: s string, i integer, d number, + further arguments):"); + for (const auto& name : names) { + const auto& command = mConsoleCommands[name]; + print(" " + name + (command.args.empty() ? "" : " ") + command.args + ": " + command.description); + } +} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h new file mode 100644 index 0000000000000..9529bdb33387e --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayConsole.h @@ -0,0 +1,66 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayConsole.h +/// \author David Rohr + +#ifndef GPUDISPLAYCONSOLE_H +#define GPUDISPLAYCONSOLE_H + +#include "GPUDisplayFrontendKeyVars.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::gpu +{ +class GPUDisplayConsole : public GPUDisplayFrontendKeyVars // Text console of the event display (^ key): typed text, history with the output, commands (filled by GPUDisplayFrontend::buildConsoleCommands) +{ + public: + using consoleArg = std::variant; // Argument of a console command + struct GPUDisplayConsoleCommand { + std::function)> func; + std::string args; // One character per argument: s string, i int64_t, d double (also from an integer), + at the end: any further arguments (type from the text) + std::string description; // For help + }; + GPUDisplayConsole(const std::string& title); // First history entry, not selectable + + void handleConsoleKey(uint8_t key); // Key typed while the console is shown + void handleConsoleCommand(std::string_view cmd); // Runs the command named by the first token, error if unknown + void runScript(std::string_view script); // Runs each line (separated by \n / \r) as console command + void runScriptFile(const std::string& filename); // runScript with the content of the file + void printCommands(const std::function& print); // List of the commands with argument types and description, one line per call + std::tuple> parseConsoleCommand(std::string_view cmd) const; // Command and its arguments with the types it requests, throws std::runtime_error for syntax errors, unknown commands, wrong arguments + void consolePrint(const char* format, ...); // printf-like output to the console, indented by 2 spaces in mConsoleHistory + void consoleInfo(const char* format, const char* text); // Output of SetInfo, updates the last line instead for the same message within 2 s + void consoleAppend(std::string line); // Every other new line of the console + void consoleLimitHistory(uint32_t historySize); // Once per frame: keeps historySize lines when the history reaches twice that + const std::string& shownText() const { return mConsoleTextCurrent ? mConsoleHistory[mConsoleTextCurrent] : mConsoleText; } + + std::unordered_map mConsoleCommands; // Console commands by name, filled by GPUDisplayFrontend::buildConsoleCommands + uint32_t mScriptDepth = 0; // Scripts running scripts + std::string mConsoleInfoLastMessage; // Format of the SetInfo that wrote the last console line, empty if something else came after it + std::chrono::steady_clock::time_point mConsoleInfoLastTime; // + std::vector mConsoleHistory; // Title, then commands entered in the console and their output (indented) + std::string mConsoleText; // Text being typed in the console + uint32_t mConsoleTextCurrent = 0; // Entry of mConsoleHistory shown in the console (selected with up / down, the title entry 0 is not selectable), 0 for mConsoleText + uint32_t mConsoleTextPos = 0; // Cursor position in the shown text (left / right), the character there is the current one +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index a299f7669f3ab..37d1eadeeedab 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -13,13 +13,14 @@ /// \author David Rohr #include "GPUDisplayFrontend.h" +#include "GPUDisplayConsole.h" #include "GPUDisplay.h" #ifdef _WIN32 #include "GPUDisplayFrontendWindows.h" -#else -#include #endif +#include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_X11 #include "GPUDisplayFrontendX11.h" @@ -59,23 +60,32 @@ class GPUDisplayGUIWrapper using namespace o2::gpu; -GPUDisplayFrontend::GPUDisplayFrontend() = default; -GPUDisplayFrontend::~GPUDisplayFrontend() = default; +GPUDisplayFrontend::GPUDisplayFrontend() : mConsole(std::make_unique(std::string(DISPLAY_WINDOW_NAME) + " Console")) {} +GPUDisplayFrontend::~GPUDisplayFrontend() +{ + if (mFrontendThread.joinable()) { // Started but not stopped, as the detached thread before + mFrontendThread.detach(); + } +} -void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) +void GPUDisplayFrontend::FrontendThreadWrapper() { - GPUDisplayFrontend* me = reinterpret_cast(ptr); int32_t retVal; try { - retVal = me->FrontendMain(); + retVal = FrontendMain(); } catch (const std::exception& e) { GPUError("Error in display frontend: %s", e.what()); retVal = -1; } - if (retVal && me->mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever - me->InitDisplay(true); + if (retVal) { + std::unique_lock lock(mExitStateMutex); + if (mDisplay->mInitResult == 0) { // Failed before InitDisplay was called, report failure to StartDisplay(), which otherwise waits forever + lock.unlock(); + InitDisplay(true); + } else { + mDisplay->mInitResult = -1; // Error after the initialization: StopDisplay does not reset the state for a new start + } } - return ((void*)(size_t)retVal); } void GPUDisplayFrontend::waitForFPSLimit() @@ -172,6 +182,8 @@ void GPUDisplayFrontend::sendExternalGUIKey(int32_t v, uint32_t translationMode, signalConfigUpdate(); } +bool GPUDisplayFrontend::consoleShown() const { return mDisplay->mCfgC.showConsole; } + int32_t GPUDisplayFrontend::getDisplayControl() const { return mDisplay->mCfgC.mDisplayControl.load(); } void GPUDisplayFrontend::setDisplayControl(int32_t v) { mDisplay->mCfgC.mDisplayControl.store(v); } @@ -180,8 +192,13 @@ int32_t GPUDisplayFrontend::HandleUpdatesAndDrawGLScene(bool suspended) { HandleExternalGUISignals(); mDisplay->checkAndApplyConfigChanges(); - const int32_t retVal = mDisplay->DrawGLScene(suspended); - mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + const bool startupScript = !mStartupScriptDone && !mDisplay->cfg().runScript.empty(); + const int32_t retVal = mDisplay->DrawGLScene(suspended || startupScript); // Before the startup script, the first frame only loads the event, it is not rendered + mDisplay->mOldCfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR}; // Also includes the changes during drawing (animation, scene reset, ...), which set their update flags themselves + if (startupScript) { + mStartupScriptDone = true; + mConsole->runScriptFile(mDisplay->cfg().runScript); // Settings it changes are applied with the next frame + } return retVal; } @@ -338,35 +355,38 @@ int32_t& GPUDisplayFrontend::drawTextFontSize() int32_t GPUDisplayFrontend::StartDisplay() { -#ifndef _WIN32 - static pthread_t hThread; - if (pthread_create(&hThread, nullptr, FrontendThreadWrapper, this)) { - GPUError("Coult not Create frontend Thread..."); - return (1); - } -#else - HANDLE hThread; - if ((hThread = CreateThread(nullptr, nullptr, &OpenGLWrapper, this, nullptr, nullptr)) == nullptr) { - GPUError("Coult not Create GL Thread..."); + try { + mFrontendThread = std::thread(&GPUDisplayFrontend::FrontendThreadWrapper, this); + } catch (const std::system_error& e) { + GPUError("Could not create frontend thread: %s", e.what()); return (1); } -#endif - mDisplay->buildConfig(); - if (mDisplay->cfg().listFrontendOptionHandling) { - listConfig(); - } - return (0); + std::unique_lock lock(mExitStateMutex); + mStartDisplayDone = true; + mInitCV.notify_all(); + mInitCV.wait(lock, [this] { return mDisplay->mInitResult != 0; }); // Both threads have finished their initialization + return (mDisplay->mInitResult != 1); } void GPUDisplayFrontend::StopDisplay() { - std::unique_lock lock(mExitStateMutex); - if (mDisplayRunning) { - mDisplay->mCfgC.mDisplayControl.store(2); - StopDisplayA(); - mDisplay->UnblockDisplay(); + { + std::unique_lock lock(mExitStateMutex); + if (mDisplayRunning) { + mDisplay->mCfgC.mDisplayControl.store(2); + StopDisplayA(); + mDisplay->UnblockDisplay(); + } + mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); + } + if (mFrontendThread.joinable()) { // Also the wrapper has finished (it sets mInitResult = -1 for errors after the initialization), not holding mExitStateMutex, which the wrapper uses + mFrontendThread.join(); + } + std::lock_guard lock(mExitStateMutex); + if (mDisplay->mInitResult == 1) { // For a new StartDisplay, not after a failed initialization or an error (undefined state) + mStartDisplayDone = mStartupScriptDone = false; + mDisplay->mInitResult = 0; } - mDisplayStoppedCV.wait(lock, [this] { return !mDisplayRunning; }); } void GPUDisplayFrontend::StopDisplayA() {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 7686ad9489d1b..f29f57687c35a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -18,9 +18,11 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" #include "GPUDisplayFrontendKeyVars.h" +#include #include #include #include +#include #include #include #include @@ -33,6 +35,7 @@ class GPUReconstruction; class GPUDisplay; class GPUDisplayBackend; class GPUDisplayGUIWrapper; +class GPUDisplayConsole; struct GPUDisplayGUIChannel; class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplayFrontendKeyVars @@ -85,9 +88,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay protected: virtual int32_t FrontendMain() = 0; virtual void StopDisplayA(); - static void* FrontendThreadWrapper(void*); - void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode), or until an external config update is signalled - void signalConfigUpdate(); // Signal a config update from an external source (console keys, Qt GUI), wakes up waitForFPSLimit + void FrontendThreadWrapper(); // Runs FrontendMain in mFrontendThread + void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (only in FPS_LIMIT_RATE mode, see GPUDisplayBackend::fpsLimitMode), or until an external config update is signalled + void signalConfigUpdate(); // Signal a config update from an external source (console keys, Qt GUI), wakes up waitForFPSLimit static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing static constexpr int32_t MIN_WIDTH = 200, MIN_HEIGHT = 150; // Minimum window size @@ -103,6 +106,10 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay bool mConfigUpdatePending = false; // Protected by mConfigUpdateMutex, set by signalConfigUpdate, cleared by HandleExternalGUISignals std::mutex mExitStateMutex; std::condition_variable mDisplayStoppedCV; // Notified with mExitStateMutex held when the frontend sets mDisplayRunning to false, StopDisplay waits for it + std::condition_variable mInitCV; // With mExitStateMutex: InitDisplay (frontend thread) and StartDisplay (main thread) wait for each other at their end + std::thread mFrontendThread; // Started by StartDisplay, joined by StopDisplay + bool mStartDisplayDone = false; // Protected by mExitStateMutex + bool mStartupScriptDone = false; // GPUSettingsDisplay::runScript runs after the first (suspended) frame std::array mKeyDownMap = {}; // Keyboard / Mouse actions @@ -112,7 +119,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay float mMouseDnX, mMouseDnY; // X/Y position where mouse button was pressed float mMouseMvX, mMouseMvY; // Current mouse pointer position int32_t mMouseWheel = 0; // Incremental value of mouse wheel, ca +/- 100 per wheel tick - bool mKeys[256] = {false}; // Array of mKeys currently pressed + bool mKeys[256] = {false}; // Array of mKeys currently pressed (not while the console is shown) int32_t mDisplayHeight = INIT_HEIGHT; int32_t mDisplayWidth = INIT_WIDTH; @@ -123,7 +130,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay frontendTypes mFrontendType = TYPE_INVALID; const char* mFrontendName = nullptr; - volatile bool mDisplayRunning = false; + std::atomic mDisplayRunning = false; // Set at the end of a successful InitDisplay, StopDisplay waits for it to become false again std::unique_ptr mGUIChannel; // Must outlive mGUI std::unique_ptr mGUI; @@ -131,6 +138,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay int32_t HandleUpdatesAndDrawGLScene(bool suspended = false); // Handle external GUI signals and config changes, then draw the scene (suspended: only update, no drawing, e.g. for a hidden window) void HandleKey(uint8_t key); // Callback for handling key presses + bool consoleShown() const; // Key presses go to the console, frontends do not set mKeys void HandleExternalGUISignals(); // Callback to handle signals from external GUIs bool HasExternalGUISignals() const; // Returns whether there are currently pending signals from external GUIs to be handled bool processKeyLambda(uint32_t key); // Process key lambda callback @@ -154,7 +162,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface, public GPUDisplay void HandleExternalGUIKeys(); static int32_t translateExternalKey(uint32_t translationMode, uint64_t extendedKey); // Key code of an encoded key of sendExternalGUIKey (+ SPECIAL_KEYS_SHIFTED with SHIFT), -1 if unknown - bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system + void buildConsoleCommands(); // Fills mConsole->mConsoleCommands, GPUDisplayFrontendConfigConsole.inc (commands need the config RTTI) + std::unique_ptr mConsole; + bool mWindowFullScreen = false, mWindowMaximized = false; // Window state last requested or reported by the window system template void add_key_binding(Args&&... args); // KEY_LAMBDA(key, ...) entries, the help text, and optionally the keys to show in the help text, only used in GPUDisplayFrontendConfig.cxx template diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx index ee9266e15b483..42de138145b7d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfig.cxx @@ -17,24 +17,31 @@ #include "frontend/GPUDisplayInfo.inc" #include "GPULogging.h" #include "GPUDisplayGUIChannel.h" +#include "GPUDisplayConsole.h" #define GPUSETTINGS_ALL #include "GPUSettings.h" #include "utils/qconfig_rtti.h" #include "utils/qconfig_internal_helpers.h" #include +#include #include +#include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include +#include using namespace o2::gpu; struct GPUDisplayFrontend::configRTTIExt : public qConfig::qConfigRTTIentry { @@ -180,6 +187,16 @@ void GPUDisplay::checkAndApplyConfigChanges() mUpdateTrackFilter = true; } mCfgC.mRotateModel |= mCfgC.mRotateAroundZ; + if (mCfgC.showConsole != mConsoleShown) { + mConsoleShown = mCfgC.showConsole; + const double start = mConsoleTimer.IsRunning() ? std::max(0., mConfig.consoleDelay - mConsoleTimer.GetCurrentElapsedTime()) : 0.; // Reverse a running animation from its current position + mConsoleTimer.Reset(); + mConsoleTimer.AddTime(start); + mConsoleTimer.Start(); + if (mCfgC.showConsole) { + std::fill(std::begin(mFrontend->mKeys), std::end(mFrontend->mKeys), false); + } + } } void GPUDisplay::HandleExternalGUIKey(int32_t key) @@ -193,7 +210,7 @@ void GPUDisplay::HandleExternalGUIKey(int32_t key) } bool oldShift = mFrontend->mKeys[mFrontend->KEY_SHIFT]; mFrontend->mKeys[mFrontend->KEY_SHIFT] = shifted; - HandleKey(key); + HandleKey(key, true); mFrontend->mKeys[mFrontend->KEY_SHIFT] = oldShift; } @@ -486,22 +503,34 @@ void GPUDisplayFrontend::listConfig() GPUInfo(" %2u %-28s %s", i, rtti.keyMapNames[i].c_str(), mappedKeys[i].c_str()); } GPUInfo(""); - std::string available; + static constexpr std::string_view availablePrefix = "Available keys: "; + static constexpr size_t MAX_LINE_LENGTH = 240; + std::string available(availablePrefix); for (uint16_t k : availableKeys) { - available += (available.empty() ? "[" : " [") + rtti.getKeyName(k) + "]"; + const std::string key = "[" + rtti.getKeyName(k) + "]"; + if (available.size() > availablePrefix.size() && available.size() + 1 + key.size() > MAX_LINE_LENGTH) { // Wrap, without splitting a key + GPUInfo("%s", available.c_str()); + available.assign(availablePrefix.size(), ' '); + } else if (available.size() > availablePrefix.size()) { + available += ' '; + } + available += key; } - GPUInfo("Available keys: %s", available.c_str()); + GPUInfo("%s", available.c_str()); GPUInfo("Help text:"); for (const auto& line : rtti.helpText) { GPUInfo(" %s", line.c_str()); } GPUInfo(""); + mConsole->printCommands([](const std::string& line) { GPUInfo("%s", line.c_str()); }); + GPUInfo(""); GPUInfo("%zu options with RTTI, %u of them with a key, %u in the Qt GUI, %zu bound keys, %zu available keys", rtti.rttiEntries.size(), nOptionsWithKey, nOptionsInGUI, boundKeys.size(), availableKeys.size()); } #include "GPUDisplayFrontendConfigKeys.inc" #include "GPUDisplayFrontendConfigInfo.inc" #include "GPUDisplayFrontendConfigQtGUI.inc" +#include "GPUDisplayFrontendConfigConsole.inc" void GPUDisplay::buildConfig() { @@ -545,6 +574,7 @@ void GPUDisplay::buildConfig() } buildConfigInfo(); buildConfigQtGUI(); + mFrontend->buildConsoleCommands(); rtti->buildHelpText(); } @@ -573,8 +603,14 @@ void GPUDisplayFrontend::showConfigInfo(uint32_t index) } } -void GPUDisplay::HandleKey(uint8_t key) +void GPUDisplay::HandleKey(uint8_t key, bool external) { + if (mCfgC.showConsole && mFrontend->mConfigRTTI->configKeys[key].configRTTIIndex != mFrontend->mConfigRTTI->getRTTIIndex(*this, &GPUDisplayControl::showConsole)) { // All keys except the one toggling the console go to the console + if (!external) { + mFrontend->mConsole->handleConsoleKey(key); + } + return; + } if (key == mFrontend->KEY_ALT) { mFrontend->mKeys[mFrontend->KEY_CTRL] = false; // Release CTRL with alt, to avoid orienting along y automatically! } else if (mFrontend->processKeyLambda(key)) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc new file mode 100644 index 0000000000000..5d0353dc9458e --- /dev/null +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigConsole.inc @@ -0,0 +1,32 @@ +// Copyright 2019-2026 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file GPUDisplayFrontendConfigConsole.inc +/// \author David Rohr + +#ifndef GPUDISPLAYFRONTENDCONFIGCONSOLE_INC +#define GPUDISPLAYFRONTENDCONFIGCONSOLE_INC + +void GPUDisplayFrontend::buildConsoleCommands() +{ + using args = std::span; + auto& commands = mConsole->mConsoleCommands; + commands["help"] = {[this](args) { mConsole->printCommands([this](const std::string& line) { mConsole->consolePrint("%s", line.c_str()); }); }, "", "List available console commands"}; + commands["clearHistory"] = {[this](args) { + mConsole->mConsoleHistory.resize(1); // Keep the title + mConsole->mConsoleTextCurrent = 0; + mConsole->mConsoleInfoLastMessage.clear(); + }, + "", "Clear the console history"}; + commands["runScript"] = {[this](args a) { mConsole->runScriptFile(std::get(a[0])); }, "s", "Execute script file"}; +} + +#endif // GPUDISPLAYFRONTENDCONFIGCONSOLE_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc index 3c7c742ff9a74..66710fbdbd89d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigInfo.inc @@ -20,7 +20,7 @@ void GPUDisplay::buildConfigInfo() { auto addInfo = [this](std::pair&& info) { mFrontend->mConfigRTTI->configInfos[info.first] = std::move(info.second); }; - static constexpr std::tuple noInfo{&GPUDisplayControl::dummy, &GPUSettingsDisplayLight::drawClusters, &GPUSettingsDisplayLight::drawInitLinks, &GPUSettingsDisplayLight::drawLinks, &GPUSettingsDisplayLight::drawSeeds, + static constexpr std::tuple noInfo{&GPUDisplayControl::dummy, &GPUDisplayControl::showConsole, &GPUSettingsDisplayLight::drawClusters, &GPUSettingsDisplayLight::drawInitLinks, &GPUSettingsDisplayLight::drawLinks, &GPUSettingsDisplayLight::drawSeeds, &GPUSettingsDisplayLight::drawTracklets, &GPUSettingsDisplayLight::drawTracks, &GPUSettingsDisplayLight::drawExtrapolatedTracks, &GPUSettingsDisplayLight::drawFinal}; // Options without info, not even the generic one std::apply([this](auto... member) { ((mFrontend->mConfigRTTI->configInfos[mFrontend->mConfigRTTI->getRTTIIndex(*this, member)] = []() {}), ...); }, noInfo); addInfo(INFO_LAMBDA(mCfgC, mDisplayControl, if (val == 2) { SetInfo("Exiting", 1); })); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc index b97c281896a12..fe81c6ac05449 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendConfigKeys.inc @@ -308,6 +308,10 @@ void GPUDisplay::buildConfigKeys() val &= 3; }), // "Print Help / Show info texts"); + + mFrontend->add_key_binding( + KEY_LAMBDA('^', mCfgC, showConsole, val ^= 1;), // + "Show / hide the console"); } #endif // GPUDISPLAYFRONTENDCONFIGKEYS_INC diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index e9b861abd131c..6e345ab1a9366 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -92,6 +92,15 @@ int32_t GPUDisplayFrontendGlfw::GetKey(int32_t key) if (key == GLFW_KEY_INSERT) { return (KEY_INSERT); } + if (key == GLFW_KEY_BACKSPACE) { + return (KEY_BACKSPACE); + } + if (key == GLFW_KEY_DELETE) { + return (KEY_DELETE); + } + if (key == GLFW_KEY_GRAVE_ACCENT) { // Key left of 1 (named after the US layout): console key (^), independent of the layout + return ('^'); + } if (key == GLFW_KEY_ENTER) { return (KEY_ENTER); } @@ -161,8 +170,8 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 int32_t handleKey = 0, keyPress = 0; GetKey(key, scancode, mods, handleKey, keyPress); if (handleKey < 32) { - if (action == GLFW_PRESS) { - me->mKeys[keyPress] = true; + if (action == GLFW_PRESS || action == GLFW_REPEAT) { // Repeat as the other frontends (e.g. backspace in the console), characters are repeated by char_callback + me->mKeys[keyPress] = !me->consoleShown(); me->HandleKey(handleKey); } else if (action == GLFW_RELEASE) { me->mKeys[keyPress] = false; @@ -170,6 +179,10 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 } else if (handleKey < 256) { if (action == GLFW_PRESS) { me->mLastKeyDown = handleKey; + if (key == GLFW_KEY_GRAVE_ACCENT) { // Console key, its character (if not a dead key) follows in the same glfwPollEvents + me->mConsoleKeyPressed = true; + me->HandleKey('^'); + } } else if (action == GLFW_RELEASE) { keyPress = (uint8_t)me->mGLFWKeyDownMap[handleKey]; me->mKeys[keyPress] = false; @@ -180,13 +193,17 @@ void GPUDisplayFrontendGlfw::key_callback(GLFWwindow* window, int32_t key, int32 void GPUDisplayFrontendGlfw::char_callback(GLFWwindow* window, uint32_t codepoint) { // GPUInfo("Key (char callback): %d %c - key: %d", codepoint, (char)codepoint, (int32_t)me->mLastKeyDown); + if (me->mConsoleKeyPressed) { // Character of the console key, handled in key_callback + me->mConsoleKeyPressed = false; + return; + } if (codepoint < 256) { uint8_t keyPress = codepoint; if (keyPress >= 'a' && keyPress <= 'z') { keyPress += 'A' - 'a'; } me->mGLFWKeyDownMap[me->mLastKeyDown] = keyPress; - me->mKeys[keyPress] = true; + me->mKeys[keyPress] = !me->consoleShown(); me->HandleKey(codepoint); } } @@ -315,11 +332,6 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; // Only after successful initialization, StopDisplay waits for it to become false again - } - int32_t retVal = 0; while (!glfwWindowShouldClose(mWindow)) { if (HandleUpdatesAndDrawGLScene()) { @@ -332,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)) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index fafde0ca89cc2..87a166c27f61f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -68,6 +68,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend int32_t mWindowHeight = INIT_HEIGHT; uint8_t mGLFWKeyDownMap[256] = {0}; uint8_t mLastKeyDown = 0; + bool mConsoleKeyPressed = false; // Console key pressed in this glfwPollEvents, skip its character in char_callback }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 5e79e85b6cf84..ebe63b5bc78ca 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -28,7 +28,6 @@ #include #endif -#include using namespace o2::gpu; GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() @@ -124,7 +123,8 @@ void GPUDisplayFrontendGlut::GetKey(int32_t key, int32_t& keyOut, int32_t& keyPr if (specialKey) { keyOut = keyPressOut = specialKey; } else { - keyOut = keyPressOut = key; + keyOut = keyPressOut = key == 8 ? KEY_BACKSPACE : key == 127 ? KEY_DELETE + : key; // Otherwise 8 = KEY_SHIFT if (keyPressOut >= 'a' && keyPressOut <= 'z') { keyPressOut += 'A' - 'a'; } @@ -135,7 +135,7 @@ void GPUDisplayFrontendGlut::keyboardFunc(int32_t key, bool special, bool down) { int32_t handleKey = 0, keyPress = 0; GetKey(key, handleKey, keyPress, special); - mKeys[keyPress] = down; + mKeys[keyPress] = down && !consoleShown(); if (down) { HandleKey(handleKey); } @@ -229,11 +229,6 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() fprintf(stderr, "Error initializing GL extension wrapper\n"); return (-1); } - if (InitDisplay()) { - fprintf(stderr, "Error in OpenGL initialization\n"); - return (1); - } - auto mouseMoveFunc = [](int32_t x, int32_t y, void* p) { self(p)->mouseMoveFunc(x, y); }; glutDisplayFuncUcall([](void* p) { self(p)->displayFunc(); }, this); // Callbacks with this as user data, freeglut >= 3.2 glutIdleFuncUcall([](void* p) { self(p)->glutLoopFunc(); }, this); @@ -249,9 +244,9 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() applyInitialWindowState(); SetVSync(vsyncEnabled()); - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; + if (InitDisplay()) { + fprintf(stderr, "Error in OpenGL initialization\n"); + return (1); } glutMainLoop(); { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx index 2bbc9928aa774..31ab4705fae6c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeyTranslation.cxx @@ -48,6 +48,7 @@ int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint6 {"[7~", KEY_HOME}, {"[8~", KEY_END}, {"[2~", KEY_INSERT}, + {"[3~", KEY_DELETE}, {"[5~", KEY_PAGEUP}, {"[6~", KEY_PAGEDOWN}, {"OP", KEY_F1}, @@ -78,6 +79,7 @@ int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint6 {"[1;2H", KEY_HOME + SPECIAL_KEYS_SHIFTED}, {"[1;2F", KEY_END + SPECIAL_KEYS_SHIFTED}, {"[2;2~", KEY_INSERT + SPECIAL_KEYS_SHIFTED}, + {"[3;2~", KEY_DELETE + SPECIAL_KEYS_SHIFTED}, {"[5;2~", KEY_PAGEUP + SPECIAL_KEYS_SHIFTED}, {"[6;2~", KEY_PAGEDOWN + SPECIAL_KEYS_SHIFTED}, {"[a", KEY_UP + SPECIAL_KEYS_SHIFTED}, @@ -87,6 +89,7 @@ int32_t GPUDisplayFrontend::translateExternalKey(uint32_t translationMode, uint6 {"[7$", KEY_HOME + SPECIAL_KEYS_SHIFTED}, {"[8$", KEY_END + SPECIAL_KEYS_SHIFTED}, {"[2$", KEY_INSERT + SPECIAL_KEYS_SHIFTED}, + {"[3$", KEY_DELETE + SPECIAL_KEYS_SHIFTED}, {"[5$", KEY_PAGEUP + SPECIAL_KEYS_SHIFTED}, {"[6$", KEY_PAGEDOWN + SPECIAL_KEYS_SHIFTED}, {"[1;2P", KEY_F1 + SPECIAL_KEYS_SHIFTED}, diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h index 4fbe81054c1d2..ad8db27502761 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendKeys.h @@ -43,6 +43,8 @@ GPUDisplayFrontendAddKey(26, INSERT); GPUDisplayFrontendAddKey(27, ESCAPE); // fixed at 27 GPUDisplayFrontendAddKey(28, HOME); GPUDisplayFrontendAddKey(29, END); +GPUDisplayFrontendAddKey(30, BACKSPACE); +GPUDisplayFrontendAddKey(31, DELETE); GPUDisplayFrontendAddKey(32, SPACE); // fixed at 32 #ifndef GPUDisplayFrontendKeyDownMap diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index f195fce1977ce..98a5654dcdbcd 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -27,10 +27,6 @@ int32_t GPUDisplayFrontendNone::FrontendMain() if (InitDisplay()) { return 1; } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } do { HandleUpdatesAndDrawGLScene(); waitForFPSLimit(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 68177449f06c8..54a915d25f551 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -52,6 +52,9 @@ GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) { int32_t retVal = 0; + if (key == 41) { // evdev KEY_GRAVE, key left of 1: console key (^), independent of the layout (linux/input-event-codes.h must not be included here) + return ('^'); + } if (mXKBkeymap) { xkb_keysym_t sym = xkb_state_key_get_one_sym(mXKBstate, key + 8); if (sym == XKB_KEY_KP_Subtract) { @@ -90,6 +93,10 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) return (KEY_HOME); } else if (sym == XKB_KEY_Insert) { return (KEY_INSERT); + } else if (sym == XKB_KEY_BackSpace) { + return (KEY_BACKSPACE); + } else if (sym == XKB_KEY_Delete) { + return (KEY_DELETE); } else if (sym == XKB_KEY_F1) { return (KEY_F1); } else if (sym == XKB_KEY_F2) { @@ -237,7 +244,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() int32_t symbol = me->GetKey(key, state); int32_t keyPress = (symbol >= 'a' && symbol <= 'z') ? symbol + 'A' - 'a' : symbol; if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - me->mKeys[keyPress] = true; + me->mKeys[keyPress] = !me->consoleShown(); me->HandleKey(symbol); if (symbol && me->mRepeatRate > 0 && me->mXKBkeymap && xkb_keymap_key_repeats(me->mXKBkeymap, key + 8)) { // Wayland compositors do not repeat keys, the client must do it me->mRepeatKeyCode = key; @@ -557,11 +564,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - while (mDisplay->mCfgC.mDisplayControl.load() != 2) { bool error = false; while (!error && wl_display_prepare_read(mWayland) != 0) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index d71afb3414668..229428af427bd 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -219,6 +219,12 @@ BOOL CreateGLWindow(char* title, int32_t width, int32_t height, int32_t bits, bo int32_t GetKey(int32_t key) { + if (key == VK_BACK) { + return (GPUDisplayFrontendKeyVars::KEY_BACKSPACE); + } + if (key == VK_DELETE) { + return (GPUDisplayFrontendKeyVars::KEY_DELETE); + } if (key == 107 || key == 187) { return ('+'); } @@ -257,14 +263,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) PostQuitMessage(0); return 0; - case WM_KEYDOWN: // Is A Key Being Held Down? - wParam = GetKey(wParam); - mKeys[wParam] = TRUE; + case WM_KEYDOWN: // Is A Key Being Held Down? + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); // Scan code of the key left of 1: console key, independent of the layout + mKeys[wParam] = !consoleShown(); HandleKey(wParam); return 0; case WM_KEYUP: - wParam = GetKey(wParam); + wParam = ((lParam >> 16) & 0xFF) == 0x29 ? '^' : GetKey(wParam); mKeys[wParam] = false; GPUInfo("Key: %d", wParam); return 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 6448c83303c0a..2b9f37b33efd6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -97,6 +97,12 @@ int32_t GPUDisplayFrontendX11::GetKey(int32_t key) if (key == 65379) { return (KEY_INSERT); } + if (key == 65288) { + return (KEY_BACKSPACE); + } + if (key == 65535) { + return (KEY_DELETE); + } if (key == 65470) { return (KEY_F1); } @@ -144,6 +150,10 @@ int32_t GPUDisplayFrontendX11::GetKey(int32_t key) void GPUDisplayFrontendX11::GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut) { + if (event.xkey.keycode == 49) { // Key left of 1: console key (^), independent of the layout + keyOut = keyPressOut = '^'; + return; + } char tmpString[9]; KeySym sym; if (XLookupString(&event.xkey, tmpString, 8, &sym, nullptr) == 0) { @@ -301,11 +311,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (1); } - { - std::lock_guard lock(mExitStateMutex); - mDisplayRunning = true; - } - while (1) { do { if (getDisplayControl() == 2) { @@ -352,7 +357,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() case KeyPress: { int32_t handleKey = 0, keyPress = 0; GetKey(event, handleKey, keyPress); - mKeys[keyPress] = true; + mKeys[keyPress] = !consoleShown(); HandleKey(handleKey); break; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 6c866b4b166a8..abc4d3c31bf2f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -827,6 +827,13 @@ void GPUDisplayGUI::keyPressEvent(QKeyEvent* event) int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event) { +#ifdef Q_OS_WIN + if (event->nativeScanCode() == 0x29) { // Key left of 1: console key (^), independent of the layout +#else + if (event->nativeScanCode() == 49) { // Key left of 1 (XKB keycode): console key (^), independent of the layout +#endif + return '^'; + } switch (event->key()) { case Qt::Key_Up: return GPUDisplayFrontendKeyVars::KEY_UP; @@ -848,6 +855,10 @@ int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event) return GPUDisplayFrontendKeyVars::KEY_INSERT; case Qt::Key_Escape: return GPUDisplayFrontendKeyVars::KEY_ESCAPE; + case Qt::Key_Backspace: + return GPUDisplayFrontendKeyVars::KEY_BACKSPACE; + case Qt::Key_Delete: + return GPUDisplayFrontendKeyVars::KEY_DELETE; case Qt::Key_Return: case Qt::Key_Enter: return GPUDisplayFrontendKeyVars::KEY_ENTER; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc index 9df428783a394..584c3578684d8 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc +++ b/GPU/GPUTracking/display/frontend/GPUDisplayInfo.inc @@ -17,6 +17,7 @@ #include "GPUDisplay.h" #include "GPULogging.h" +#include "frontend/GPUDisplayConsole.h" namespace o2::gpu { @@ -29,6 +30,7 @@ void GPUDisplay::SetInfo(Args... args) snprintf(mInfoText2, 1024, args...); #pragma GCC diagnostic pop GPUInfo("%s", mInfoText2); + mFrontend->mConsole->consoleInfo(std::get<0>(std::tuple(args...)), mInfoText2); mGUIInfoText = mInfoText2; mGUIInfoTextCounter++; mInfoText2Timer.ResetStart(); diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 8d8aec9c306ea..6d7d29426c10c 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -206,8 +206,6 @@ void GPUDisplay::showInfo(const char* info) if (!(mCfgC.mPrintInfoText & 1)) { return; } - mBackend->updateFontScale(mFrontend->getDisplayScale()); - mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; GLPrintText(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); if (mInfoText2Timer.IsRunning()) { @@ -224,7 +222,20 @@ void GPUDisplay::showInfo(const char* info) PrintGLHelpText(colorValue); } } - mBackend->finishText(); +} + +float GPUDisplay::consoleVerticalFraction() +{ + const bool running = mConsoleTimer.IsRunning(); + if (!mCfgC.showConsole && !running) { + return 0.f; + } + const double time = mConsoleTimer.GetCurrentElapsedTime(); + if (running && !mCfgC.showConsole && time >= mConfig.consoleDelay) { // Keeps running while open, for the blinking cursor + mConsoleTimer.Stop(); + } + const float progress = running && mConfig.consoleDelay > 0.f ? std::min(1.f, (float)(time / mConfig.consoleDelay)) : 1.f; + return mConfig.consoleVertical * (mCfgC.showConsole ? progress : 1.f - progress); } void GPUDisplay::GLPrintText(const char* s, float x, float y, float r, float g, float b, float a, uint32_t nCharacters, bool fromBotton) diff --git a/GPU/GPUTracking/utils/qconfig_internal_helpers.h b/GPU/GPUTracking/utils/qconfig_internal_helpers.h index 89ef328e9de4f..88ef724ebedb4 100644 --- a/GPU/GPUTracking/utils/qconfig_internal_helpers.h +++ b/GPU/GPUTracking/utils/qconfig_internal_helpers.h @@ -50,7 +50,7 @@ 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) {}; + 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;