Skip to content

Commit dc10792

Browse files
committed
GPU Display GUI: First version of protocol to send updates between frontend and GUI
1 parent c0dc347 commit dc10792

12 files changed

Lines changed: 394 additions & 131 deletions

‎GPU/GPUTracking/Definitions/GPUSettingsList.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@ AddOption(noFreetype, bool, false, "", 0, "Do not use Freetype for font renderin
532532
AddOption(displayRenderer, std::string, "auto", "renderer", 0, "Renderer for event display: opengl | vulkan | auto")
533533
AddOption(displayFrontend, std::string, "auto", "displayFrontend", 0, "Frontend to use for event display: auto | win32 | x11 | glut | glfw | wayland")
534534
AddOptionVec(filterMacros, std::string, "", 0, "ROOT macros used as track filter")
535+
AddOption(guiReplyTimeout, uint32_t, 100, "", 0, "Time in ms the Qt GUI waits at most for the display to process its update")
536+
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")
535537
AddSubConfig(GPUSettingsDisplayLight, light)
536538
AddSubConfig(GPUSettingsDisplayHeavy, heavy)
537539
AddSubConfig(GPUSettingsDisplayRenderer, renderer)

‎GPU/GPUTracking/display/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ if(GPUCA_EVENT_DISPLAY_QT)
174174

175175
set(SRCS ${SRCS} frontend/GPUDisplayGUI.cxx frontend/GPUDisplayGUIWrapper.cxx)
176176
set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS} ${GPUCA_QT_UI_HDRS})
177+
set(HDRS_INSTALL ${HDRS_INSTALL} frontend/GPUDisplayGUIChannel.h)
177178
endif()
178179

179180
string(REPLACE ".cxx" ".h" HDRS "${SRCS}")

‎GPU/GPUTracking/display/GPUDisplay.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ class GPUDisplay : public GPUDisplayInterface
5858
void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; }
5959
void UpdateParam(const GPUParam* param) override { mParam = param; }
6060

61-
void HandleKey(uint8_t key);
61+
void HandleKey(uint8_t key, bool doNotHandleConfigChanges = false); // With doNotHandleConfigChanges, the caller must call checkAndApplyConfigChanges
6262
int32_t DrawGLScene();
63-
void HandleExternalGUIKey(int32_t key);
63+
void HandleExternalGUIKey(int32_t key); // Does not handle config changes, see HandleKey
6464
int32_t InitDisplay(bool initFailure = false);
6565
void ResizeScene(int32_t width, int32_t height, bool init = false);
66+
void checkAndApplyConfigChanges(const GPUSettingsDisplayHeavy& oldCfgH, const GPUSettingsDisplayLight& oldCfgL, const GPUSettingsDisplayRenderer& oldCfgR); // Set update flags / apply the settings that changed compared to the old copies
6667

6768
const GPUSettingsDisplayRenderer& cfgR() const { return mCfgR; }
6869
const GPUSettingsDisplayLight& cfgL() const { return mCfgL; }

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx‎

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland();
3838
#endif
3939
#include "GPUDisplayFrontendNone.h"
4040

41+
#include "GPUDisplayGUIChannel.h"
4142
#include "GPULogging.h"
4243
#include <cstring>
4344
#include <exception>
4445
#include <algorithm>
4546
#include <iterator>
4647
#include <thread>
48+
#include <utility>
4749

4850
#ifdef GPUCA_BUILD_EVENT_DISPLAY_QT
4951
#include "GPUDisplayGUIWrapper.h"
@@ -58,6 +60,7 @@ class GPUDisplayGUIWrapper
5860

5961
using namespace o2::gpu;
6062

63+
GPUDisplayFrontend::GPUDisplayFrontend() = default;
6164
GPUDisplayFrontend::~GPUDisplayFrontend() = default;
6265

6366
void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr)
@@ -95,21 +98,57 @@ void GPUDisplayFrontend::waitForFPSLimit()
9598

9699
void GPUDisplayFrontend::HandleExternalGUISignals()
97100
{
98-
HandleExternalGUIKeys();
101+
const GPUSettingsDisplayHeavy oldCfgH = mDisplay->mCfgH;
102+
const GPUSettingsDisplayLight oldCfgL = mDisplay->mCfgL;
103+
const GPUSettingsDisplayRenderer oldCfgR = mDisplay->mCfgR;
104+
bool changes = HandleExternalGUIKeys();
105+
106+
const bool gui = isGUIRunning();
107+
std::optional<GPUDisplayGUIChannel::Update> update;
108+
if (gui) {
109+
std::lock_guard lock(mGUIChannel->mutex);
110+
update = std::exchange(mGUIChannel->update, std::nullopt);
111+
mGUIChannel->displayProcessingCurrentUpdate = update && mGUIChannel->guiSleeping; // GUI waits for the reply when it wakes up
112+
}
113+
if (update) {
114+
for (int32_t key : update->keys) {
115+
mDisplay->HandleExternalGUIKey(key);
116+
}
117+
if (update->lineWidth) {
118+
mDisplay->mCfgL.lineWidth = *update->lineWidth;
119+
}
120+
changes = true;
121+
}
122+
123+
if (changes) {
124+
mDisplay->checkAndApplyConfigChanges(oldCfgH, oldCfgL, oldCfgR);
125+
}
126+
if (gui) {
127+
{
128+
std::lock_guard lock(mGUIChannel->mutex);
129+
mGUIChannel->state.cfg = {mDisplay->mCfgH, mDisplay->mCfgL, mDisplay->mCfgR};
130+
if (update) {
131+
mGUIChannel->state.appliedUpdate = update->id;
132+
}
133+
mGUIChannel->state.counter++;
134+
mGUIChannel->displayProcessingCurrentUpdate = false;
135+
}
136+
mGUIChannel->stateUpdated.notify_all();
137+
}
99138
}
100139

101140
bool GPUDisplayFrontend::HasExternalGUISignals() const
102141
{
103-
142+
return mExternalGUIKeys.size();
104143
}
105144

106-
void GPUDisplayFrontend::HandleExternalGUIKeys()
145+
bool GPUDisplayFrontend::HandleExternalGUIKeys()
107146
{
108147
std::lock_guard lock(mExternalGUIKeyMutex);
109148
for (auto key : mExternalGUIKeys) {
110149
mDisplay->HandleExternalGUIKey(key);
111150
}
112-
mExternalGUIKeys.clear();
151+
return !std::exchange(mExternalGUIKeys, {}).empty();
113152
}
114153

115154
void GPUDisplayFrontend::sendExternalGUIKey(int32_t v)
@@ -149,9 +188,19 @@ int32_t GPUDisplayFrontend::startGUI()
149188
int32_t retVal = 1;
150189
#ifdef GPUCA_BUILD_EVENT_DISPLAY_QT
151190
if (!mGUI) {
152-
mGUI.reset(new GPUDisplayGUIWrapper);
191+
mGUIChannel = std::make_unique<GPUDisplayGUIChannel>();
192+
mGUI = std::make_unique<GPUDisplayGUIWrapper>(*mGUIChannel);
153193
}
154194
if (!mGUI->isRunning()) {
195+
{
196+
std::lock_guard lock(mGUIChannel->mutex);
197+
mGUIChannel->update.reset();
198+
mGUIChannel->state = {};
199+
mGUIChannel->guiSleeping = mGUIChannel->displayProcessingCurrentUpdate = mGUIChannel->stop = false;
200+
mGUIChannel->replyTimeout = std::chrono::milliseconds(mDisplay->cfg().guiReplyTimeout);
201+
mGUIChannel->maxUpdatesAhead = std::max<uint32_t>(mDisplay->cfg().guiMaxUpdatesAhead, 1); // 0 would block all updates
202+
mGUIChannel->windowName = DISPLAY_WINDOW_NAME;
203+
}
155204
mGUI->start();
156205
} else {
157206
mGUI->focus();

‎GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ class GPUReconstruction;
2828
class GPUDisplay;
2929
class GPUDisplayBackend;
3030
class GPUDisplayGUIWrapper;
31+
struct GPUDisplayGUIChannel;
3132

3233
class GPUDisplayFrontend : public GPUDisplayFrontendInterface
3334
{
3435
friend class GPUDisplay;
36+
friend class GPUDisplayGUI; // Key codes
3537

3638
public:
37-
GPUDisplayFrontend() = default;
39+
GPUDisplayFrontend();
3840
~GPUDisplayFrontend() override;
3941

4042
enum frontendTypes {
@@ -101,7 +103,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface
101103
static constexpr int32_t KEY_ENTER = 13; // fixed at 13
102104
static constexpr int32_t KEY_F1 = 14;
103105
static constexpr int32_t KEY_F2 = 15;
104-
static constexpr int32_t KEY_F3 = 26;
106+
static constexpr int32_t KEY_F3 = 16;
105107
static constexpr int32_t KEY_F4 = 17;
106108
static constexpr int32_t KEY_F5 = 18;
107109
static constexpr int32_t KEY_F6 = 19;
@@ -146,6 +148,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface
146148
const char* mFrontendName = nullptr;
147149
volatile bool mDisplayRunning = false;
148150

151+
std::unique_ptr<GPUDisplayGUIChannel> mGUIChannel; // Must outlive mGUI
149152
std::unique_ptr<GPUDisplayGUIWrapper> mGUI;
150153

151154
void HandleKey(uint8_t key); // Callback for handling key presses
@@ -161,7 +164,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface
161164
void focusLost(); // Release all keys and mouse buttons when the window loses focus, since the release events go to another window
162165
void applyInitialWindowState(); // Switch to fullscreen / maximize at startup according to the settings
163166
private:
164-
void HandleExternalGUIKeys();
167+
bool HandleExternalGUIKeys(); // Returns whether keys were handled, config changes are not handled
165168
};
166169
} // namespace o2::gpu
167170

‎GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx‎

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,140 @@
1313
/// \author David Rohr
1414

1515
#include "GPUDisplayGUI.h"
16-
#include "GPUDisplayGUIWrapper.h"
16+
#include "GPUDisplayFrontend.h"
1717
#include "ui_GPUDisplayGUI.h"
1818

19+
#include <QKeyEvent>
20+
#include <QSignalBlocker>
21+
22+
#include <chrono>
23+
#include <cmath>
24+
1925
using namespace o2::gpu;
2026

21-
GPUDisplayGUI::GPUDisplayGUI(QWidget* parent) : QMainWindow(parent), ui(new Ui::GPUDisplayGUI)
27+
GPUDisplayGUI::GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent) : QMainWindow(parent), mUi(std::make_unique<Ui::GPUDisplayGUI>()), mChannel(channel)
28+
{
29+
mUi->setupUi(this);
30+
{
31+
std::lock_guard lock(mChannel.mutex);
32+
setWindowTitle(QString::fromStdString(mChannel.windowName + " GUI"));
33+
}
34+
connect(mUi->lineWidthSlider, &QSlider::valueChanged, this, [this](int32_t value) {
35+
mUpdate.lineWidth = value * 0.1f;
36+
mUi->lineWidthValue->setNum(*mUpdate.lineWidth);
37+
});
38+
connect(&mTimer, &QTimer::timeout, this, &GPUDisplayGUI::timerTick);
39+
mTimer.start(std::chrono::milliseconds(10));
40+
}
41+
42+
GPUDisplayGUI::~GPUDisplayGUI() = default;
43+
44+
void GPUDisplayGUI::timerTick()
45+
{
46+
std::unique_lock lock(mChannel.mutex);
47+
mChannel.guiSleeping = false;
48+
if (mUpdate.empty()) { // Display processes the update we went to sleep with, wait for the reply (with new changes we would not apply it anyway)
49+
mChannel.stateUpdated.wait_for(lock, mChannel.replyTimeout, [this] { return !mChannel.displayProcessingCurrentUpdate || mChannel.stop; });
50+
}
51+
if (!mUpdate.empty() && mLastSentId - mLastAppliedId >= mChannel.maxUpdatesAhead) { // Too far ahead of the display, wait for the reply to the last update
52+
mChannel.stateUpdated.wait_for(lock, mChannel.replyTimeout, [this] { return mChannel.state.appliedUpdate == mLastSentId || mChannel.stop; });
53+
}
54+
if (mChannel.stop) {
55+
lock.unlock();
56+
close();
57+
return;
58+
}
59+
60+
const bool newState = mChannel.state.counter != mState.counter;
61+
if (newState) {
62+
mState = mChannel.state;
63+
if (mState.appliedUpdate == mLastSentId) {
64+
mLastAppliedId = mLastSentId;
65+
}
66+
}
67+
const bool apply = newState && mUpdate.empty() && mState.appliedUpdate == mLastSentId; // State contains all our updates, and we have no unsent changes
68+
if (!mUpdate.empty() && mLastSentId - mLastAppliedId < mChannel.maxUpdatesAhead) {
69+
if (mChannel.update) { // Previous update not yet picked up by the display, merge
70+
auto& update = *mChannel.update;
71+
update.keys.insert(update.keys.end(), mUpdate.keys.begin(), mUpdate.keys.end());
72+
if (mUpdate.lineWidth) {
73+
update.lineWidth = mUpdate.lineWidth;
74+
}
75+
} else {
76+
mChannel.update = std::move(mUpdate);
77+
}
78+
mChannel.update->id = ++mLastSentId;
79+
mChannel.guiSleeping = true;
80+
mUpdate = {};
81+
}
82+
lock.unlock();
83+
84+
if (apply) {
85+
applySettings();
86+
}
87+
}
88+
89+
void GPUDisplayGUI::applySettings()
90+
{
91+
const QSignalBlocker blocker(mUi->lineWidthSlider); // Not a user change
92+
mUi->lineWidthSlider->setValue(std::lround(mState.cfg.cfgL.lineWidth * 10.f));
93+
mUi->lineWidthValue->setNum(mState.cfg.cfgL.lineWidth);
94+
}
95+
96+
void GPUDisplayGUI::bringToFront()
2297
{
23-
ui->setupUi(this);
98+
setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
99+
show();
100+
raise();
101+
activateWindow();
24102
}
25103

26-
GPUDisplayGUI::~GPUDisplayGUI()
104+
void GPUDisplayGUI::keyPressEvent(QKeyEvent* event)
27105
{
28-
delete ui;
106+
const int32_t key = translateKey(event);
107+
if (key < 0) {
108+
QMainWindow::keyPressEvent(event);
109+
return;
110+
}
111+
mUpdate.keys.emplace_back(key);
29112
}
30113

31-
void GPUDisplayGUI::UpdateTimer()
114+
int32_t GPUDisplayGUI::translateKey(const QKeyEvent* event)
32115
{
33-
mWrapper->UpdateTimer();
116+
switch (event->key()) {
117+
case Qt::Key_Up:
118+
return GPUDisplayFrontend::KEY_UP;
119+
case Qt::Key_Down:
120+
return GPUDisplayFrontend::KEY_DOWN;
121+
case Qt::Key_Left:
122+
return GPUDisplayFrontend::KEY_LEFT;
123+
case Qt::Key_Right:
124+
return GPUDisplayFrontend::KEY_RIGHT;
125+
case Qt::Key_PageUp:
126+
return GPUDisplayFrontend::KEY_PAGEUP;
127+
case Qt::Key_PageDown:
128+
return GPUDisplayFrontend::KEY_PAGEDOWN;
129+
case Qt::Key_Home:
130+
return GPUDisplayFrontend::KEY_HOME;
131+
case Qt::Key_End:
132+
return GPUDisplayFrontend::KEY_END;
133+
case Qt::Key_Insert:
134+
return GPUDisplayFrontend::KEY_INSERT;
135+
case Qt::Key_Escape:
136+
return GPUDisplayFrontend::KEY_ESCAPE;
137+
case Qt::Key_Return:
138+
case Qt::Key_Enter:
139+
return GPUDisplayFrontend::KEY_ENTER;
140+
default:
141+
break;
142+
}
143+
if (event->key() >= Qt::Key_F1 && event->key() <= Qt::Key_F12) {
144+
static_assert(GPUDisplayFrontend::KEY_F12 - GPUDisplayFrontend::KEY_F1 == Qt::Key_F12 - Qt::Key_F1);
145+
return GPUDisplayFrontend::KEY_F1 + (event->key() - Qt::Key_F1);
146+
}
147+
const QString text = event->text();
148+
if (text.size() == 1 && text[0].unicode() >= 32 && text[0].unicode() < 127) { // Printable ASCII, with shift applied
149+
return text[0].unicode();
150+
}
151+
return -1;
34152
}

‎GPU/GPUTracking/display/frontend/GPUDisplayGUI.h‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
{
2121
#define QT_END_NAMESPACE }
2222

23+
#include "GPUDisplayGUIChannel.h"
2324
#include <QMainWindow>
25+
#include <QTimer>
26+
#include <memory>
27+
28+
class QKeyEvent;
2429

2530
QT_BEGIN_NAMESPACE
2631
namespace Ui
@@ -31,22 +36,30 @@ QT_END_NAMESPACE
3136

3237
namespace o2::gpu
3338
{
34-
class GPUDisplayGUIWrapper;
3539
class GPUDisplayGUI : public QMainWindow
3640
{
3741
Q_OBJECT
3842

3943
public:
40-
GPUDisplayGUI(QWidget* parent = nullptr);
41-
~GPUDisplayGUI();
42-
void setWrapper(GPUDisplayGUIWrapper* w) { mWrapper = w; }
44+
explicit GPUDisplayGUI(GPUDisplayGUIChannel& channel, QWidget* parent = nullptr);
45+
~GPUDisplayGUI() override;
46+
void bringToFront();
4347

44-
private slots:
45-
void UpdateTimer();
48+
protected:
49+
void keyPressEvent(QKeyEvent* event) override;
4650

4751
private:
48-
Ui::GPUDisplayGUI* ui;
49-
GPUDisplayGUIWrapper* mWrapper;
52+
void timerTick(); // Exchange updates / settings with the display
53+
void applySettings();
54+
static int32_t translateKey(const QKeyEvent* event); // Qt key → display key code, -1 if not forwarded
55+
56+
std::unique_ptr<Ui::GPUDisplayGUI> mUi;
57+
QTimer mTimer;
58+
GPUDisplayGUIChannel& mChannel;
59+
GPUDisplayGUIChannel::Update mUpdate; // Changes collected since the last tick
60+
GPUDisplayGUIChannel::State mState; // Last state received from the display
61+
uint64_t mLastSentId = 0; // Id of the last update sent
62+
uint64_t mLastAppliedId = 0; // Id of the last update for which the display state was received
5063
};
5164
} // namespace o2::gpu
5265

0 commit comments

Comments
 (0)