From ff66c2b19875178d7144c20d3be544332caf3f11 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Mon, 12 Oct 2026 17:38:28 +0200 Subject: [PATCH 01/48] GPU Display: Better implementation of FPS limiting --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 3 +- .../Interface/GPUO2InterfaceDisplay.cxx | 3 ++ GPU/GPUTracking/display/GPUDisplayInterface.h | 3 +- .../backend/GPUDisplayBackendVulkan.cxx | 2 +- .../display/frontend/GPUDisplayFrontend.cxx | 19 ++++++++++ .../display/frontend/GPUDisplayFrontend.h | 10 +++--- .../frontend/GPUDisplayFrontendGlfw.cxx | 10 +++--- .../display/frontend/GPUDisplayFrontendGlfw.h | 1 - .../frontend/GPUDisplayFrontendGlut.cxx | 12 +++---- .../display/frontend/GPUDisplayFrontendGlut.h | 1 - .../frontend/GPUDisplayFrontendNone.cxx | 1 + .../frontend/GPUDisplayFrontendWayland.cxx | 23 +++++++++--- .../frontend/GPUDisplayFrontendWayland.h | 1 - .../frontend/GPUDisplayFrontendWindows.cxx | 1 + .../frontend/GPUDisplayFrontendX11.cxx | 35 +------------------ .../display/frontend/GPUDisplayFrontendX11.h | 1 - .../display/frontend/GPUDisplayKeys.cxx | 5 +-- 17 files changed, 63 insertions(+), 68 deletions(-) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 2d49faf287c0e..24b3119ea2da1 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -504,7 +504,8 @@ AddOption(openGLCore, bool, false, "", 0, "Use renderer path for OpenGL core pro 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(maxFPSRate, int32_t, 0, "", 0, "Do not limit FPS but run at maximum possible rate") +AddOption(maxFPSRate, bool, false, "", 0, "Do not limit FPS but run at maximum possible rate") +AddOption(fpsRate, int32_t, 100, "", 0, "Target frame rate (frames per second) if not running at maximum 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") diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx index 8ef1b1980dc0c..469b26f89bda1 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx @@ -74,6 +74,9 @@ int32_t GPUO2InterfaceDisplay::show(const GPUTrackingInOutPointers* ptrs) int32_t GPUO2InterfaceDisplay::endDisplay() { mFrontend->DisplayExit(); + while (mFrontend->IsRunning()) { + usleep(10000); + } return 0; } diff --git a/GPU/GPUTracking/display/GPUDisplayInterface.h b/GPU/GPUTracking/display/GPUDisplayInterface.h index 7caceb1699da6..5034cb7163174 100644 --- a/GPU/GPUTracking/display/GPUDisplayInterface.h +++ b/GPU/GPUTracking/display/GPUDisplayInterface.h @@ -60,10 +60,9 @@ class GPUDisplayFrontendInterface virtual bool EnableSendKey() = 0; virtual int32_t getDisplayControl() const = 0; virtual int32_t getSendKey() const = 0; - virtual int32_t getNeedUpdate() const = 0; virtual void setDisplayControl(int32_t v) = 0; + virtual bool IsRunning() const = 0; virtual void setSendKey(int32_t v) = 0; - virtual void setNeedUpdate(int32_t v) = 0; virtual const char* frontendName() const = 0; protected: diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 506cef9db7248..9b1bf1910289a 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1404,7 +1404,7 @@ void GPUDisplayBackendVulkan::clearImage(VulkanImage& image) int32_t GPUDisplayBackendVulkan::InitBackendA() { - mEnableValidationLayers = mDisplay->param() && mDisplay->GetProcessingSettings().debugLevel >= 2; + mEnableValidationLayers = (mDisplay->param() && mDisplay->GetProcessingSettings().debugLevel >= 2) || (getenv("GPUCA_VULKAN_VALIDATION") && atoi(getenv("GPUCA_VULKAN_VALIDATION"))); mFramesInFlight = 2; createDevice(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index df5c45c6beaa8..160cc9cd1f93a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -37,6 +37,7 @@ #include "GPULogging.h" #include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT #include "GPUDisplayGUIWrapper.h" @@ -63,6 +64,23 @@ void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) return ((void*)(size_t)retVal); } +void GPUDisplayFrontend::waitForFPSLimit() +{ + mNeedUpdate = 0; + const auto now = std::chrono::steady_clock::now(); + const auto& cfg = mDisplay->cfgR(); + if (cfg.maxFPSRate || cfg.fpsRate <= 0) { + mNextFrameTime = now; + return; + } + 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); +} + void GPUDisplayFrontend::HandleSendKey() { if (mSendKey) { @@ -73,6 +91,7 @@ void GPUDisplayFrontend::HandleSendKey() void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } int32_t GPUDisplayFrontend::DrawGLScene() { return mDisplay->DrawGLScene(); } + void GPUDisplayFrontend::ResizeScene(int32_t width, int32_t height) { mDisplayHeight = height; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 0abab8bb0a121..53c45e1fd658a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -18,6 +18,7 @@ #include "GPUCommonDef.h" #include "GPUDisplayInterface.h" #include +#include namespace o2::gpu { @@ -49,7 +50,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface static constexpr int32_t GL_MIN_VERSION_MINOR = 5; int32_t StartDisplay(); // Start the display. This function returns, and should spawn a thread that runs the display, and calls InitDisplay - void DisplayExit() override = 0; // Stop the display. Display thread should call ExitDisplay and the function returns after the thread has terminated + void DisplayExit() override = 0; // Stop the display. Display thread should call ExitDisplay and then wait that IsRunning() returns false + bool IsRunning() const override { return mDisplayRunning; } 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 @@ -63,10 +65,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface int32_t getDisplayControl() const override { return mDisplayControl; } int32_t getSendKey() const override { return mSendKey; } - int32_t getNeedUpdate() const override { return mNeedUpdate; } void setDisplayControl(int32_t v) override { mDisplayControl = v; } void setSendKey(int32_t v) override { mSendKey = v; } - void setNeedUpdate(int32_t v) override { mNeedUpdate = v; } frontendTypes frontendType() const { return mFrontendType; } const char* frontendName() const override { return mFrontendName; } @@ -83,6 +83,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface protected: virtual int32_t FrontendMain() = 0; static void* FrontendThreadWrapper(void*); + void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (no sleep with maxFPSRate or fpsRate <= 0) static constexpr int32_t INIT_WIDTH = 1024, INIT_HEIGHT = 768; // Initial window size, before maximizing static constexpr const char* DISPLAY_WINDOW_NAME = "GPU CA Standalone Event Display"; // Title of event display set by backend @@ -129,13 +130,14 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface int32_t mDisplayWidth = INIT_WIDTH; int32_t mCanDrawText = 0; // 1 = in compat mode, 2 = with shader - int32_t mMaxFPSRate = 0; // run at highest possible frame rate, do not sleep in between frames + std::chrono::steady_clock::time_point mNextFrameTime; GPUDisplay* mDisplay = nullptr; // Ptr to display, not owning, set by display when it connects to backend GPUDisplayBackend* mBackend = nullptr; // Ptr to backend, not owning frontendTypes mFrontendType = TYPE_INVALID; const char* mFrontendName = nullptr; + volatile bool mDisplayRunning = false; std::unique_ptr mGUI; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 232a9065355a6..f7f35fba7836c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -282,7 +282,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } pthread_mutex_lock(&mSemLockExit); - mGlfwRunning = true; + mDisplayRunning = true; pthread_mutex_unlock(&mSemLockExit); if (mBackend->ExtInit()) { @@ -304,6 +304,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapBuffers(mWindow); } + waitForFPSLimit(); glfwPollEvents(); } @@ -312,7 +313,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() pthread_mutex_lock(&mSemLockExit); glfwDestroyWindow(mWindow); glfwTerminate(); - mGlfwRunning = false; + mDisplayRunning = false; pthread_mutex_unlock(&mSemLockExit); return 0; @@ -321,13 +322,10 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() void GPUDisplayFrontendGlfw::DisplayExit() { pthread_mutex_lock(&mSemLockExit); - if (mGlfwRunning) { + if (mDisplayRunning) { glfwSetWindowShouldClose(mWindow, true); } pthread_mutex_unlock(&mSemLockExit); - while (mGlfwRunning) { - usleep(10000); - } } void GPUDisplayFrontendGlfw::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 540514a606145..42ffc5b100c98 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -54,7 +54,6 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend GLFWwindow* mWindow; - volatile bool mGlfwRunning = false; pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; int32_t mWindowX = 0; int32_t mWindowY = 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 1b2f2a21150c3..a30512d654925 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -32,10 +32,11 @@ GPUDisplayFrontendGlut::GPUDisplayFrontendGlut() mFrontendName = "GLUT"; } -void GPUDisplayFrontendGlut::displayFunc() +void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, to get rid of the static me variable! { me->DrawGLScene(); glutSwapBuffers(); + me->waitForFPSLimit(); } void GPUDisplayFrontendGlut::glutLoopFunc() @@ -261,12 +262,12 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() ToggleMaximized(true); pthread_mutex_lock(&mSemLockExit); - mGlutRunning = true; + mDisplayRunning = true; pthread_mutex_unlock(&mSemLockExit); glutMainLoop(); mDisplayControl = 2; pthread_mutex_lock(&mSemLockExit); - mGlutRunning = false; + mDisplayRunning = false; pthread_mutex_unlock(&mSemLockExit); ExitDisplay(); @@ -276,13 +277,10 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() void GPUDisplayFrontendGlut::DisplayExit() { pthread_mutex_lock(&mSemLockExit); - if (mGlutRunning) { + if (mDisplayRunning) { glutLeaveMainLoop(); } pthread_mutex_unlock(&mSemLockExit); - while (mGlutRunning) { - usleep(10000); - } } void GPUDisplayFrontendGlut::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index 9351349e2287d..acb63e863126a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -48,7 +48,6 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t& keyOut, int32_t& keyPressOut, bool special); - volatile bool mGlutRunning = false; pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; int32_t mWidth = INIT_WIDTH; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index 8a7eab7e00526..82d08015ed43c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -29,6 +29,7 @@ int32_t GPUDisplayFrontendNone::FrontendMain() } do { DrawGLScene(); + waitForFPSLimit(); HandleSendKey(); } while (mDisplayControl != 2); return 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 5a42954c90fa7..c829a8e8adedf 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -31,6 +31,7 @@ #include #include +#include #include #include #include "xdg-shell-client-protocol.h" @@ -397,11 +398,26 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } - while (wl_display_dispatch(mWayland) != -1 && mDisplayControl != 2) { + while (mDisplayControl != 2) { + while (wl_display_prepare_read(mWayland) != 0) { + wl_display_dispatch_pending(mWayland); + } + wl_display_flush(mWayland); + pollfd pfd = {wl_display_get_fd(mWayland), POLLIN, 0}; + if (poll(&pfd, 1, 0) > 0) { + if (wl_display_read_events(mWayland) == -1) { + break; + } + } else { + wl_display_cancel_read(mWayland); + } + if (wl_display_dispatch_pending(mWayland) == -1 || mDisplayControl == 2) { + break; + } HandleSendKey(); DrawGLScene(); + waitForFPSLimit(); wl_surface_damage(mSurface, 0, 0, mDisplayWidth, mDisplayHeight); - usleep(10000); } ExitDisplay(); @@ -448,9 +464,6 @@ void GPUDisplayFrontendWayland::DisplayExit() mDisplayControl = 2; } pthread_mutex_unlock(&mSemLockExit); - while (mDisplayRunning) { - usleep(10000); - } } void GPUDisplayFrontendWayland::SwitchFullscreen(bool set) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 55676c694cfef..2e7d5a7a703d4 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -52,7 +52,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend void recreateBuffer(uint32_t width, uint32_t height); pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - volatile bool mDisplayRunning = false; wl_display* mWayland = nullptr; wl_registry* mRegistry = nullptr; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 30148e0cb00ee..2188fe59a880c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -359,6 +359,7 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() } else { DrawGLScene(); // Draw The Scene SwapBuffers(hDC); // Swap Buffers (Double Buffering) + waitForFPSLimit(); } } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index be56fc8a1e546..f029c9234a65d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -21,7 +21,6 @@ #include #include #include -#include #include @@ -311,7 +310,6 @@ int32_t GPUDisplayFrontendX11::FrontendMain() XMapWindow(mDisplay, mWindow); XFlush(mDisplay); - int32_t x11_fd = ConnectionNumber(mDisplay); // Enable vsync if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL && vsync_supported) { @@ -331,36 +329,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() mDisplayRunning = true; pthread_mutex_unlock(&mSemLockExit); - std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); while (1) { - int32_t num_ready_fds; - struct timeval tv; - fd_set in_fds; - int32_t waitCount = 0; - do { - FD_ZERO(&in_fds); - FD_SET(x11_fd, &in_fds); - tv.tv_usec = 10000; - tv.tv_sec = 0; - std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); - bool allowMax = mMaxFPSRate && std::chrono::duration_cast>(t2 - t1).count() < 0.01; - t1 = t2; - num_ready_fds = allowMax || XPending(mDisplay) || select(x11_fd + 1, &in_fds, nullptr, nullptr, &tv); - if (num_ready_fds < 0) { - GPUError("Error (num_ready_fds)"); - } - if (mDisplayControl == 2) { - break; - } - if (mSendKey) { - mNeedUpdate = 1; - } - if (waitCount++ != 100) { - mNeedUpdate = 1; - } - } while (!(num_ready_fds || mNeedUpdate)); - mNeedUpdate = 0; - do { if (mDisplayControl == 2) { break; @@ -450,6 +419,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXSwapBuffers(mDisplay, mWindow); } + waitForFPSLimit(); } #ifndef GPUCA_DISPLAY_OPENGL_CORE @@ -480,9 +450,6 @@ void GPUDisplayFrontendX11::DisplayExit() mDisplayControl = 2; } pthread_mutex_unlock(&mSemLockExit); - while (mDisplayRunning) { - usleep(10000); - } } void GPUDisplayFrontendX11::SwitchFullscreen(bool set) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index 7ea38271f2ee9..c0ad7cf5f99ab 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -45,7 +45,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend void GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut); pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - volatile bool mDisplayRunning = false; GLuint mFontBase; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index 6dc09545733fe..9056edfc7c18a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -158,7 +158,7 @@ void GPUDisplay::HandleKey(uint8_t key) mCfgR.maximized ^= 1; SetInfo("Toggling Maximized window (%d)", (int32_t)mCfgR.maximized); } else if (key == 'R') { - mCfgR.maxFPSRate ^= 1; + mCfgR.maxFPSRate = !mCfgR.maxFPSRate; SetInfo("FPS rate %s", mCfgR.maxFPSRate ? "not limited" : "limited"); } else if (key == 'H') { mPrintInfoText += 1; @@ -513,9 +513,6 @@ void GPUDisplay::HandleKey(uint8_t key) if (oldCfgR.maximized != mCfgR.maximized) { mFrontend->ToggleMaximized(mCfgR.maximized); } - if (oldCfgR.maxFPSRate != mCfgR.maxFPSRate) { - mFrontend->mMaxFPSRate = mCfgR.maxFPSRate; - } if (oldCfgR.useGLIndirectDraw != mCfgR.useGLIndirectDraw) { mUpdateEventData = true; } From 8fbfc5b8cfc0daa6a00c41f5601e12e5646bcb55 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Mon, 12 Oct 2026 23:21:38 +0200 Subject: [PATCH 02/48] GPU Display: Better handling of key presses from the console --- GPU/GPUTracking/Global/GPUChainTracking.cxx | 5 +---- GPU/GPUTracking/display/GPUDisplayInterface.h | 3 +-- .../display/frontend/GPUDisplayFrontend.cxx | 15 +++++++++++---- .../display/frontend/GPUDisplayFrontend.h | 18 ++++++++++-------- .../frontend/GPUDisplayFrontendGlfw.cxx | 2 +- .../frontend/GPUDisplayFrontendGlut.cxx | 2 +- .../frontend/GPUDisplayFrontendNone.cxx | 2 +- .../frontend/GPUDisplayFrontendWayland.cxx | 2 +- .../display/frontend/GPUDisplayFrontendX11.cxx | 2 +- 9 files changed, 28 insertions(+), 23 deletions(-) diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index e54110e53aac3..74b311356c164 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -861,10 +861,7 @@ int32_t GPUChainTracking::RunChainFinalize() } else if (iKey == 'n') { break; } else if (iKey) { - while (GetProcessingSettings().eventDisplay->getSendKey() != 0) { - usleep(1000); - } - GetProcessingSettings().eventDisplay->setSendKey(iKey); + GetProcessingSettings().eventDisplay->sendKey(iKey); } } } while (GetProcessingSettings().eventDisplay->getDisplayControl() == 0); diff --git a/GPU/GPUTracking/display/GPUDisplayInterface.h b/GPU/GPUTracking/display/GPUDisplayInterface.h index 5034cb7163174..ff83b1cbaa38e 100644 --- a/GPU/GPUTracking/display/GPUDisplayInterface.h +++ b/GPU/GPUTracking/display/GPUDisplayInterface.h @@ -59,10 +59,9 @@ class GPUDisplayFrontendInterface virtual void DisplayExit() = 0; virtual bool EnableSendKey() = 0; virtual int32_t getDisplayControl() const = 0; - virtual int32_t getSendKey() const = 0; virtual void setDisplayControl(int32_t v) = 0; virtual bool IsRunning() const = 0; - virtual void setSendKey(int32_t v) = 0; + virtual void sendKey(int32_t v) = 0; virtual const char* frontendName() const = 0; protected: diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 160cc9cd1f93a..6897357d71b05 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -81,12 +81,19 @@ void GPUDisplayFrontend::waitForFPSLimit() std::this_thread::sleep_until(mNextFrameTime); } -void GPUDisplayFrontend::HandleSendKey() +void GPUDisplayFrontend::HandleSendKeys() { - if (mSendKey) { - mDisplay->HandleSendKey(mSendKey); - mSendKey = 0; + std::lock_guard lock(mSendKeyMutex); + for (auto key : mSendKeys) { + mDisplay->HandleSendKey(key); } + mSendKeys.clear(); +} + +void GPUDisplayFrontend::sendKey(int32_t v) +{ + std::lock_guard lock(mSendKeyMutex); + mSendKeys.emplace_back(v); } void GPUDisplayFrontend::HandleKey(uint8_t key) { mDisplay->HandleKey(key); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 53c45e1fd658a..21c3ceeee4d6c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -19,6 +19,8 @@ #include "GPUDisplayInterface.h" #include #include +#include +#include namespace o2::gpu { @@ -64,9 +66,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; }; int32_t getDisplayControl() const override { return mDisplayControl; } - int32_t getSendKey() const override { return mSendKey; } void setDisplayControl(int32_t v) override { mDisplayControl = v; } - void setSendKey(int32_t v) override { mSendKey = v; } + void sendKey(int32_t v) override; frontendTypes frontendType() const { return mFrontendType; } const char* frontendName() const override { return mFrontendName; } @@ -75,11 +76,6 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void stopGUI(); bool isGUIRunning(); - // 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 mSendKey = 0; // Key sent by external entity (usually console), may be ignored by backend. - volatile int32_t mNeedUpdate = 0; // flag that backend shall update the GL window, and call DrawGLScene - protected: virtual int32_t FrontendMain() = 0; static void* FrontendThreadWrapper(void*); @@ -118,6 +114,12 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 + std::mutex mSendKeyMutex; + std::vector mSendKeys; // Keys sent by external entity (usually console), may be ignored by backend. + // Keyboard / Mouse actions bool mMouseDn = false; // Mouse button down bool mMouseDnR = false; // Right mouse button down @@ -143,7 +145,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void HandleKey(uint8_t key); // Callback for handling key presses int32_t DrawGLScene(); // Callback to draw the GL scene - void HandleSendKey(); // Optional callback to handle key press from external source (e.g. stdin by default) + void HandleSendKeys(); // Optional callback to handle key press from external source (e.g. stdin by default) void ResizeScene(int32_t width, int32_t height); // Callback when GL window is resized int32_t InitDisplay(bool initFailure = false); // Callback to initialize the GL Display (to be called in StartDisplay) void ExitDisplay(); // Callback to clean up the GL Display diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index f7f35fba7836c..4b260eea32de0 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -296,7 +296,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } while (!glfwWindowShouldClose(mWindow)) { - HandleSendKey(); + HandleSendKeys(); if (DrawGLScene()) { fprintf(stderr, "Error drawing GL scene\n"); return (1); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index a30512d654925..9e40c42401b59 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -41,7 +41,7 @@ void GPUDisplayFrontendGlut::displayFunc() // TODO: Functions should be lambdas, void GPUDisplayFrontendGlut::glutLoopFunc() { - me->HandleSendKey(); + me->HandleSendKeys(); displayFunc(); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx index 82d08015ed43c..73bba8c9458aa 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.cxx @@ -30,7 +30,7 @@ int32_t GPUDisplayFrontendNone::FrontendMain() do { DrawGLScene(); waitForFPSLimit(); - HandleSendKey(); + HandleSendKeys(); } while (mDisplayControl != 2); return 0; } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index c829a8e8adedf..440f8c0f8ed6f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -414,7 +414,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() if (wl_display_dispatch_pending(mWayland) == -1 || mDisplayControl == 2) { break; } - HandleSendKey(); + HandleSendKeys(); DrawGLScene(); waitForFPSLimit(); wl_surface_damage(mSurface, 0, 0, mDisplayWidth, mDisplayHeight); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index f029c9234a65d..a821b7626bb36 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -334,7 +334,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() if (mDisplayControl == 2) { break; } - HandleSendKey(); + HandleSendKeys(); if (!XPending(mDisplay)) { event.type = Expose; } else { From 5e5017b019e9d74bb7f0a0d36b1fced9bf2e46ca Mon Sep 17 00:00:00 2001 From: David Rohr Date: Mon, 12 Oct 2026 23:24:38 +0200 Subject: [PATCH 03/48] GPU Wayland Display: Fix incorrect copy&paste when setting up the display, running state set too late. --- .../display/frontend/GPUDisplayFrontendWayland.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 440f8c0f8ed6f..7448f157f670b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -398,6 +398,10 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } + pthread_mutex_lock(&mSemLockExit); + mDisplayRunning = true; + pthread_mutex_unlock(&mSemLockExit); + while (mDisplayControl != 2) { while (wl_display_prepare_read(mWayland) != 0) { wl_display_dispatch_pending(mWayland); @@ -429,10 +433,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } xkb_context_unref(mXKBcontext); - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); - pthread_mutex_lock(&mSemLockExit); mDisplayRunning = false; pthread_mutex_unlock(&mSemLockExit); From 0d1ba80c61d46ff3622b196e1a3576a03640899f Mon Sep 17 00:00:00 2001 From: David Rohr Date: Mon, 12 Oct 2026 23:25:36 +0200 Subject: [PATCH 04/48] GPU Wayland Display: Fix Typo --- GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 7448f157f670b..5f7f0da11cc75 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -283,7 +283,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() mPointer = wl_seat_get_pointer(mSeat); wl_pointer_add_listener(mPointer, &pointer_listener, this); } - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { mKeyboard = wl_seat_get_keyboard(mSeat); wl_keyboard_add_listener(mKeyboard, &keyboard_listener, this); } From 3aca8c7f4f350a690847309b58df77fae5670350 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Mon, 12 Oct 2026 23:34:27 +0200 Subject: [PATCH 05/48] GPU Wayland Display: fix should not attach wayland SHM buffer to the swapchain of Vulkan (thx to claude) --- .../frontend/GPUDisplayFrontendWayland.cxx | 58 +++---------------- .../frontend/GPUDisplayFrontendWayland.h | 6 -- 2 files changed, 8 insertions(+), 56 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 5f7f0da11cc75..77e844a48e72a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -143,37 +143,6 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) return retVal; } -void GPUDisplayFrontendWayland::createBuffer(uint32_t width, uint32_t height) -{ - const uint32_t stride = width * 4; - const uint32_t size = stride * height; - if (ftruncate(mFd, size) < 0) { - throw std::runtime_error("Error setting waysland shm file size"); - } - void* shm_data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, mFd, 0); - if (shm_data == MAP_FAILED) { - throw std::runtime_error("wayland mmap failed"); - } - memset(shm_data, 0, size); - munmap(shm_data, size); - - mPool = wl_shm_create_pool(mShm, mFd, size); - mBuffer = wl_shm_pool_create_buffer(mPool, 0, width, height, stride, WL_SHM_FORMAT_XRGB8888); - mDisplayWidth = width; - mDisplayHeight = height; - wl_surface_attach(mSurface, mBuffer, 0, 0); - wl_surface_commit(mSurface); -} - -void GPUDisplayFrontendWayland::recreateBuffer(uint32_t width, uint32_t height) -{ - wl_surface_attach(mSurface, nullptr, 0, 0); - wl_surface_commit(mSurface); - wl_buffer_destroy(mBuffer); - wl_shm_pool_destroy(mPool); - createBuffer(width, height); -} - int32_t GPUDisplayFrontendWayland::FrontendMain() { if (backend()->backendType() != GPUDisplayBackend::TYPE_VULKAN) { @@ -195,11 +164,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() throw std::runtime_error("Could not create wayland registry"); } - mFd = memfd_create("/tmp/ca-gpu-display-wayland-memfile", 0); - if (mFd < 0) { - throw std::runtime_error("Error creating wayland shm segment file descriptor"); - } - auto pointer_enter = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { }; auto pointer_leave = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* wl_surface) { @@ -309,8 +273,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() // wl_output_add_listener(mOutput, &output_listener, this); } else if (strcmp(interface, wl_compositor_interface.name) == 0) { mCompositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, 1); - } else if (strcmp(interface, wl_shm_interface.name) == 0) { - mShm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { mXdgBase = (xdg_wm_base*)wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); xdg_wm_base_add_listener(mXdgBase, &xdg_wm_base_listener, this); @@ -329,7 +291,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() wl_registry_add_listener(mRegistry, ®istry_listener, ®istry_global_c); wl_display_roundtrip(mWayland); - if (mCompositor == nullptr || mShm == nullptr || mXdgBase == nullptr || mSeat == nullptr || mOutput == nullptr) { + if (mCompositor == nullptr || mXdgBase == nullptr || mSeat == nullptr || mOutput == nullptr) { throw std::runtime_error("Error getting wayland objects"); } @@ -356,10 +318,12 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; xdg_surface_ack_configure(me->mXdgSurface, serial); if (me->mWidthRequested && me->mHeightRequested && (me->mWidthRequested != me->mDisplayWidth || me->mHeightRequested != me->mDisplayHeight)) { - me->recreateBuffer(me->mWidthRequested, me->mHeightRequested); - me->ResizeScene(me->mDisplayWidth, me->mDisplayHeight); - } else { - wl_surface_commit(((GPUDisplayFrontendWayland*)data)->mSurface); + if (me->mDisplayRunning) { + me->ResizeScene(me->mWidthRequested, me->mHeightRequested); // Swapchain is recreated by the backend, Vulkan WSI commits the new buffer + } else { + me->mDisplayWidth = me->mWidthRequested; // Initial size, used by InitDisplay via getSize() + me->mDisplayHeight = me->mHeightRequested; + } } me->mWidthRequested = me->mHeightRequested = 0; }; @@ -389,11 +353,9 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() printf("Enabling decoration\n"); } - wl_surface_commit(mSurface); + wl_surface_commit(mSurface); // Initial commit without buffer, the compositor answers with the first configure wl_display_roundtrip(mWayland); - createBuffer(INIT_WIDTH, INIT_HEIGHT); - if (InitDisplay()) { return (1); } @@ -421,7 +383,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() HandleSendKeys(); DrawGLScene(); waitForFPSLimit(); - wl_surface_damage(mSurface, 0, 0, mDisplayWidth, mDisplayHeight); } ExitDisplay(); @@ -448,11 +409,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() xdg_toplevel_destroy(mXdgToplevel); xdg_surface_destroy(mXdgSurface); wl_surface_destroy(mSurface); - wl_buffer_destroy(mBuffer); - wl_shm_pool_destroy(mPool); wl_registry_destroy(mRegistry); wl_display_disconnect(mWayland); - close(mFd); return (0); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 2e7d5a7a703d4..d8c9277fe7246 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -48,8 +48,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend private: int32_t FrontendMain() override; int32_t GetKey(uint32_t key, uint32_t state); - void createBuffer(uint32_t width, uint32_t height); - void recreateBuffer(uint32_t width, uint32_t height); pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; @@ -61,7 +59,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend wl_pointer* mPointer = nullptr; wl_keyboard* mKeyboard = nullptr; xdg_wm_base* mXdgBase = nullptr; - wl_shm* mShm = nullptr; wl_surface* mSurface = nullptr; xdg_surface* mXdgSurface = nullptr; @@ -69,9 +66,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend wl_output* mOutput = nullptr; - int32_t mFd = 0; - wl_shm_pool* mPool; - wl_buffer* mBuffer; xkb_context* mXKBcontext = nullptr; xkb_keymap* mXKBkeymap = nullptr; From 5bf4ef99c92361fcaadd782f187b725ebbb4f789 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Mon, 12 Oct 2026 23:41:04 +0200 Subject: [PATCH 06/48] GPU Wayland Display: Use server side decoration, now that they are more widely available --- .../frontend/GPUDisplayFrontendWayland.cxx | 21 ++++++++++++++----- .../frontend/GPUDisplayFrontendWayland.h | 3 +-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 77e844a48e72a..c9261f7fe5b3e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -279,8 +279,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } else if (strcmp(interface, wl_seat_interface.name) == 0) { mSeat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1); wl_seat_add_listener(mSeat, &seat_listener, &seat_capabilities_c); - } else if (strcmp(interface, zxdg_toplevel_decoration_v1_interface.name) == 0) { - mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_toplevel_decoration_v1_interface, 1); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); } }; @@ -321,7 +321,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() if (me->mDisplayRunning) { me->ResizeScene(me->mWidthRequested, me->mHeightRequested); // Swapchain is recreated by the backend, Vulkan WSI commits the new buffer } else { - me->mDisplayWidth = me->mWidthRequested; // Initial size, used by InitDisplay via getSize() + me->mDisplayWidth = me->mWidthRequested; me->mDisplayHeight = me->mHeightRequested; } } @@ -349,8 +349,11 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() xdg_toplevel_set_title(mXdgToplevel, DISPLAY_WINDOW_NAME); - if (mDecManager) { - printf("Enabling decoration\n"); + if (mDecManager) { // Must be requested before the first commit. Without the protocol (e.g. GNOME), the window has no decorations + mXdgDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(mDecManager, mXdgToplevel); + zxdg_toplevel_decoration_v1_set_mode(mXdgDecoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); + } else if (mDisplay->GetProcessingSettings().debugLevel >= 0) { + GPUInfo("Wayland compositor does not support server-side decorations"); } wl_surface_commit(mSurface); // Initial commit without buffer, the compositor answers with the first configure @@ -406,9 +409,17 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() wl_keyboard_release(mKeyboard); mKeyboard = nullptr; } + if (mXdgDecoration) { + zxdg_toplevel_decoration_v1_destroy(mXdgDecoration); + mXdgDecoration = nullptr; + } xdg_toplevel_destroy(mXdgToplevel); xdg_surface_destroy(mXdgSurface); wl_surface_destroy(mSurface); + if (mDecManager) { + zxdg_decoration_manager_v1_destroy(mDecManager); + mDecManager = nullptr; + } wl_registry_destroy(mRegistry); wl_display_disconnect(mWayland); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index d8c9277fe7246..7f6301d090b11 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -66,13 +66,12 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend wl_output* mOutput = nullptr; - xkb_context* mXKBcontext = nullptr; xkb_keymap* mXKBkeymap = nullptr; xkb_state* mXKBstate = nullptr; zxdg_decoration_manager_v1* mDecManager = nullptr; - // zxdg_toplevel_decoration_v1* mXdgDecoration = nullptr; + zxdg_toplevel_decoration_v1* mXdgDecoration = nullptr; int32_t mWidthRequested = 0; int32_t mHeightRequested = 0; From 213cb3f7fb9093ebc2456e12774bcbfb513b86ac Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 00:53:09 +0200 Subject: [PATCH 07/48] GPU Wayland Display: Refactoring for correct windows / pixel sizes with HighDPI screen and scaling, fixed maximized windows, several other fixes by Claude --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 2 +- GPU/GPUTracking/display/GPUDisplay.h | 2 + .../display/frontend/GPUDisplayFrontend.cxx | 22 +- .../display/frontend/GPUDisplayFrontend.h | 3 + .../frontend/GPUDisplayFrontendGlfw.cxx | 5 +- .../display/frontend/GPUDisplayFrontendGlfw.h | 1 + .../frontend/GPUDisplayFrontendGlut.cxx | 2 +- .../frontend/GPUDisplayFrontendWayland.cxx | 344 ++++++++++++++---- .../frontend/GPUDisplayFrontendWayland.h | 50 ++- .../frontend/GPUDisplayFrontendX11.cxx | 114 +++--- .../display/frontend/GPUDisplayFrontendX11.h | 3 +- .../display/helpers/GPUDisplayHelpers.cxx | 5 +- 12 files changed, 429 insertions(+), 124 deletions(-) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 24b3119ea2da1..343cda302305b 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -499,7 +499,7 @@ 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, false, "", 0, "Full Screen") +AddOption(maximized, bool, true, "", 0, "Maximized window") AddOption(openGLCore, bool, false, "", 0, "Use renderer path for OpenGL core profile") AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing") AddOption(drawQualityDownsampleFSAA, int32_t, 0, "", 0, "Downsampling Anti Aliasing") diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index b450ac1c18060..006db4bbe5b06 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -41,6 +41,8 @@ class GPUTPCGMPropagator; class GPUDisplay : public GPUDisplayInterface { + friend class GPUDisplayFrontend; + public: GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GPUQA* qa, const GPUParam* param = nullptr, const GPUCalibObjectsConst* calib = nullptr, const GPUSettingsDisplay* config = nullptr, const GPUSettingsProcessing* proc = nullptr); GPUDisplay(const GPUDisplay&) = delete; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 6897357d71b05..57fb46a26c9ef 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -31,7 +31,10 @@ #include "GPUDisplayFrontendGlut.h" #endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_WAYLAND -#include "GPUDisplayFrontendWayland.h" +namespace o2::gpu +{ +GPUDisplayFrontend* createGPUDisplayFrontendWayland(); +} // namespace o2::gpu #endif #include "GPUDisplayFrontendNone.h" @@ -171,7 +174,7 @@ GPUDisplayFrontend* GPUDisplayFrontend::getFrontend(const char* type) #endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_WAYLAND if (strcmp(type, "wayland") == 0 || (strcmp(type, "auto") == 0 && getenv("XDG_SESSION_TYPE") && strcmp(getenv("XDG_SESSION_TYPE"), "wayland") == 0)) { - return new GPUDisplayFrontendWayland; + return createGPUDisplayFrontendWayland(); } else #endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_GLUT @@ -192,6 +195,21 @@ GPUDisplayBackend* GPUDisplayFrontend::backend() return mDisplay->backend(); } +void GPUDisplayFrontend::setWindowState(bool fullScreen, bool maximized) +{ + mDisplay->mCfgR.fullScreen = fullScreen; + mDisplay->mCfgR.maximized = maximized; +} + +void GPUDisplayFrontend::applyInitialWindowState() +{ + if (mDisplay->cfgR().fullScreen) { + SwitchFullscreen(true); + } else if (mDisplay->cfgR().maximized) { + ToggleMaximized(true); + } +} + int32_t& GPUDisplayFrontend::drawTextFontSize() { return mDisplay->drawTextFontSize(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 21c3ceeee4d6c..e2744e9036e43 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -62,6 +62,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface GPUDisplayBackend* backend(); static GPUDisplayFrontend* getFrontend(const char* type); virtual void getSize(int32_t& width, int32_t& height) { width = height = 0; } + virtual float getDisplayScale() const { return 1.f; } // HiDPI scale factor: physical pixels per logical pixel, text positions and sizes are scaled by it virtual int32_t getVulkanSurface(void* instance, void* surface) { return 1; } virtual uint32_t getReqVulkanExtensions(const char**& p) { return 0; }; @@ -150,6 +151,8 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface 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 int32_t& drawTextFontSize(); + void setWindowState(bool fullScreen, bool maximized); // Sync the settings if the window manager changed the window state + void applyInitialWindowState(); // Switch to fullscreen / maximize at startup according to the settings }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 4b260eea32de0..c4d60e4733e9a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -229,6 +229,8 @@ void GPUDisplayFrontendGlfw::cursorPos_callback(GLFWwindow* window, double x, do void GPUDisplayFrontendGlfw::resize_callback(GLFWwindow* window, int32_t width, int32_t height) { me->ResizeScene(width, height); } +void GPUDisplayFrontendGlfw::maximize_callback(GLFWwindow* window, int32_t maximized) { me->setWindowState(glfwGetWindowMonitor(window) != nullptr, maximized); } + #ifndef GPUCA_STANDALONE void GPUDisplayFrontendGlfw::DisplayLoop() { @@ -251,7 +253,6 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } glfwSetErrorCallback(error_callback); - glfwWindowHint(GLFW_MAXIMIZED, 1); if (backend()->backendType() == GPUDisplayBackend::TYPE_VULKAN) { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); } @@ -277,6 +278,8 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwSetScrollCallback(mWindow, scroll_callback); glfwSetCursorPosCallback(mWindow, cursorPos_callback); glfwSetWindowSizeCallback(mWindow, resize_callback); + glfwSetWindowMaximizeCallback(mWindow, maximize_callback); + applyInitialWindowState(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapInterval(1); } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 42ffc5b100c98..41bbb4ab3e219 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -49,6 +49,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend static void scroll_callback(GLFWwindow* mWindow, double x, double y); static void cursorPos_callback(GLFWwindow* mWindow, double x, double y); static void resize_callback(GLFWwindow* mWindow, int32_t width, int32_t height); + static void maximize_callback(GLFWwindow* mWindow, int32_t maximized); static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t scancode, int32_t mods, int32_t& keyOut, int32_t& keyPressOut); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 9e40c42401b59..82b1c9448708d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -259,7 +259,7 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() glutMotionFunc(mouseMoveFunc); glutPassiveMotionFunc(mouseMoveFunc); glutMouseWheelFunc(mMouseWheelFunc); - ToggleMaximized(true); + applyInitialWindowState(); pthread_mutex_lock(&mSemLockExit); mDisplayRunning = true; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index c9261f7fe5b3e..d176c42cbf694 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -41,18 +41,7 @@ using namespace o2::gpu; -namespace o2::gpu::internal -{ -template -struct CCallWrapper { - std::function func; - static T callback(void* context, Args... args) - { - const CCallWrapper* funcwrap = reinterpret_cast(context); - return funcwrap->func(std::forward(args)...); - } -}; -} // namespace o2::gpu::internal +GPUDisplayFrontend* o2::gpu::createGPUDisplayFrontendWayland() { return new GPUDisplayFrontendWayland; } GPUDisplayFrontendWayland::GPUDisplayFrontendWayland() { @@ -170,8 +159,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto pointer_motion = [](void* data, wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; - me->mMouseMvX = wl_fixed_to_double(surface_x); - me->mMouseMvY = wl_fixed_to_double(surface_y); + me->mMouseMvX = wl_fixed_to_double(surface_x) * me->mScale; + me->mMouseMvY = wl_fixed_to_double(surface_y) * me->mScale; }; 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; @@ -191,16 +180,36 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } } }; + auto pointer_frame = [](void* data, wl_pointer* wl_pointer) { ((GPUDisplayFrontendWayland*)data)->applyScroll(); }; auto pointer_axis = [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { - if (axis == 0) { - ((GPUDisplayFrontendWayland*)data)->mMouseWheel += wl_fixed_to_double(value) * (-100.f / 15.f); + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + me->mAxisValue += wl_fixed_to_double(value); + if (me->mSeatVersion < WL_POINTER_FRAME_SINCE_VERSION) { + me->applyScroll(); + } + } + }; + auto pointer_axis_source = [](void* data, wl_pointer* wl_pointer, uint32_t axis_source) {}; + auto pointer_axis_stop = [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis) {}; + auto pointer_axis_discrete = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) {}; // Only sent for seat versions 5 to 7, superseded by axis_value120 +#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION + auto pointer_axis_value120 = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t value120) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + me->mAxisValue120 += value120; + me->mHasAxisValue120 = true; } }; +#endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" - const wl_pointer_listener pointer_listener = {.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = nullptr, .axis_source = nullptr, .axis_stop = nullptr, .axis_discrete = nullptr}; + mPointerListener = wl_pointer_listener{.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion, .button = pointer_button, .axis = pointer_axis, .frame = pointer_frame, .axis_source = pointer_axis_source, .axis_stop = pointer_axis_stop, .axis_discrete = pointer_axis_discrete, +#ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION + .axis_value120 = pointer_axis_value120 +#endif + }; #pragma GCC diagnostic pop - auto keyboard_keymap = [](void* data, wl_keyboard* wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; if (me->mXKBkeymap) { @@ -233,65 +242,115 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() xkb_state_update_mask(me->mXKBstate, mods_depressed, mods_latched, mods_locked, 0, 0, group); }; auto keyboard_repat = [](void* data, wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) {}; - const wl_keyboard_listener keyboard_listener = {.keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repat}; + mKeyboardListener = wl_keyboard_listener{.keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repat}; auto xdg_wm_base_ping = [](void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); }; - const xdg_wm_base_listener xdg_wm_base_listener = { + mXdgWmBaseListener = xdg_wm_base_listener{ .ping = xdg_wm_base_ping, }; - auto seat_capabilities = [&](struct wl_seat* seat, uint32_t capabilities) { - if (capabilities & WL_SEAT_CAPABILITY_POINTER) { - mPointer = wl_seat_get_pointer(mSeat); - wl_pointer_add_listener(mPointer, &pointer_listener, this); + auto seat_capabilities = [](void* data, struct wl_seat* seat, uint32_t capabilities) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !me->mPointer) { + me->mPointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(me->mPointer, &me->mPointerListener, me); } - if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - mKeyboard = wl_seat_get_keyboard(mSeat); - wl_keyboard_add_listener(mKeyboard, &keyboard_listener, this); + if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && !me->mKeyboard) { + me->mKeyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(me->mKeyboard, &me->mKeyboardListener, me); } }; - auto seat_capabilities_c = internal::CCallWrapper{[seat_capabilities](wl_seat* seat, uint32_t capabilities) { seat_capabilities(seat, capabilities); }}; - auto seat_name = [](void* data, struct wl_seat* seat, const char* name) { if (((GPUDisplayFrontendWayland*)data)->mDisplay->GetProcessingSettings().debugLevel >= 2) { GPUInfo("Wayland seat: %s", name); } }; - const wl_seat_listener seat_listener = { - .capabilities = seat_capabilities_c.callback, + mSeatListener = wl_seat_listener{ + .capabilities = seat_capabilities, .name = seat_name, }; - auto registry_global = [&](wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - if (mDisplay->GetProcessingSettings().debugLevel >= 3) { - GPUInfo("Available interface %s", interface); + auto output_geometry = [](void* data, wl_output* output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, const char* model, int32_t transform) {}; + auto output_mode = [](void* data, wl_output* output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {}; + auto output_done = [](void* data, wl_output* output) {}; + auto output_scale = [](void* data, wl_output* output, int32_t factor) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (auto* o = me->findOutput(output)) { + o->scale = factor; + me->updateScale(); + } + }; +#ifdef WL_OUTPUT_NAME_SINCE_VERSION + auto output_name = [](void* data, wl_output* output, const char* name) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (auto* o = me->findOutput(output)) { + o->name = name; + } + if (me->mDisplay->GetProcessingSettings().debugLevel >= 2) { + GPUInfo("Wayland output: %s", name); + } + }; + auto output_description = [](void* data, wl_output* output, const char* description) {}; +#endif + mOutputListener = wl_output_listener{ + .geometry = output_geometry, + .mode = output_mode, + .done = output_done, + .scale = output_scale, +#ifdef WL_OUTPUT_NAME_SINCE_VERSION + .name = output_name, + .description = output_description, +#endif + }; + + auto registry_global = [](void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (me->mDisplay->GetProcessingSettings().debugLevel >= 3) { + GPUInfo("Available interface %s (version %u)", interface, version); } + auto bindVersion = [version](const wl_interface& iface, uint32_t maxVersion) { return std::min({version, (uint32_t)iface.version, maxVersion}); }; // Highest version supported by compositor, generated headers, and this code if (strcmp(interface, wl_output_interface.name) == 0) { - mOutput = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 1); - // wl_output_add_listener(mOutput, &output_listener, this); + uint32_t v = bindVersion(wl_output_interface, 4); + wl_output* output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, v); + me->mOutputs.emplace_back(OutputInfo{output, name, v, 1, ""}); + wl_output_add_listener(output, &me->mOutputListener, me); } else if (strcmp(interface, wl_compositor_interface.name) == 0) { - mCompositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, 1); + me->mCompositor = (wl_compositor*)wl_registry_bind(registry, name, &wl_compositor_interface, bindVersion(wl_compositor_interface, 6)); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - mXdgBase = (xdg_wm_base*)wl_registry_bind(registry, name, &xdg_wm_base_interface, 1); - xdg_wm_base_add_listener(mXdgBase, &xdg_wm_base_listener, this); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - mSeat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1); - wl_seat_add_listener(mSeat, &seat_listener, &seat_capabilities_c); + me->mXdgBase = (xdg_wm_base*)wl_registry_bind(registry, name, &xdg_wm_base_interface, bindVersion(xdg_wm_base_interface, 6)); + xdg_wm_base_add_listener(me->mXdgBase, &me->mXdgWmBaseListener, me); + } else if (strcmp(interface, wl_seat_interface.name) == 0 && !me->mSeat) { + me->mSeatVersion = bindVersion(wl_seat_interface, 8); + me->mSeat = (wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, me->mSeatVersion); + wl_seat_add_listener(me->mSeat, &me->mSeatListener, me); } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); + me->mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); } }; + auto registry_global_remove = [](void* data, wl_registry* registry, uint32_t name) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + for (auto it = me->mOutputs.begin(); it != me->mOutputs.end(); it++) { + if (it->globalName == name) { + std::erase(me->mSurfaceOutputs, it->output); + if (it->version >= WL_OUTPUT_RELEASE_SINCE_VERSION) { + wl_output_release(it->output); + } else { + wl_output_destroy(it->output); + } + me->mOutputs.erase(it); + me->updateScale(); + break; + } + } + }; + mRegistryListener = wl_registry_listener{.global = registry_global, .global_remove = registry_global_remove}; - auto registry_global_c = internal::CCallWrapper{[registry_global](wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { registry_global(registry, name, interface, version); }}; - auto registry_global_remove = [](void* a, wl_registry* b, uint32_t c) {}; - const wl_registry_listener registry_listener = {.global = ®istry_global_c.callback, .global_remove = registry_global_remove}; - - wl_registry_add_listener(mRegistry, ®istry_listener, ®istry_global_c); + wl_registry_add_listener(mRegistry, &mRegistryListener, this); wl_display_roundtrip(mWayland); - if (mCompositor == nullptr || mXdgBase == nullptr || mSeat == nullptr || mOutput == nullptr) { + if (mCompositor == nullptr || mXdgBase == nullptr || mSeat == nullptr) { throw std::runtime_error("Error getting wayland objects"); } @@ -299,6 +358,35 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() if (mSurface == nullptr) { throw std::runtime_error("Error creating wayland surface"); } + + auto surface_enter = [](void* data, wl_surface* surface, wl_output* output) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mSurfaceOutputs.emplace_back(output); + me->updateScale(); + }; + auto surface_leave = [](void* data, wl_surface* surface, wl_output* output) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + std::erase(me->mSurfaceOutputs, output); + me->updateScale(); + }; +#ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION + auto surface_preferred_buffer_scale = [](void* data, wl_surface* surface, int32_t factor) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mPreferredScale = factor; + me->updateScale(); + }; + auto surface_preferred_buffer_transform = [](void* data, wl_surface* surface, uint32_t transform) {}; +#endif + mSurfaceListener = wl_surface_listener{ + .enter = surface_enter, + .leave = surface_leave, +#ifdef WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION + .preferred_buffer_scale = surface_preferred_buffer_scale, + .preferred_buffer_transform = surface_preferred_buffer_transform, +#endif + }; + wl_surface_add_listener(mSurface, &mSurfaceListener, this); + mXdgSurface = xdg_wm_base_get_xdg_surface(mXdgBase, mSurface); if (mXdgSurface == nullptr) { throw std::runtime_error("Error creating wayland xdg surface"); @@ -312,42 +400,88 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } me->mWidthRequested = width; me->mHeightRequested = height; + bool fullScreen = false, maximized = false; + me->mSuspended = false; + const uint32_t* state = (const uint32_t*)states->data; + for (size_t i = 0; i < states->size / sizeof(uint32_t); i++) { + if (state[i] == XDG_TOPLEVEL_STATE_FULLSCREEN) { + fullScreen = true; + } else if (state[i] == XDG_TOPLEVEL_STATE_MAXIMIZED) { + maximized = true; +#ifdef XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION + } else if (state[i] == XDG_TOPLEVEL_STATE_SUSPENDED) { + me->mSuspended = true; +#endif + } + } + me->setWindowState(fullScreen, maximized); + me->mStateFullScreenOrMaximized = fullScreen || maximized; }; auto xdg_surface_handle_configure = [](void* data, xdg_surface* surface, uint32_t serial) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; xdg_surface_ack_configure(me->mXdgSurface, serial); - if (me->mWidthRequested && me->mHeightRequested && (me->mWidthRequested != me->mDisplayWidth || me->mHeightRequested != me->mDisplayHeight)) { - if (me->mDisplayRunning) { - me->ResizeScene(me->mWidthRequested, me->mHeightRequested); // Swapchain is recreated by the backend, Vulkan WSI commits the new buffer - } else { - me->mDisplayWidth = me->mWidthRequested; - me->mDisplayHeight = me->mHeightRequested; + if (me->mWidthRequested && me->mHeightRequested) { + me->mWidthLogical = me->mWidthRequested; + me->mHeightLogical = me->mHeightRequested; + if (!me->mStateFullScreenOrMaximized) { + me->mWidthWindowed = me->mWidthRequested; + me->mHeightWindowed = me->mHeightRequested; + } + } else if (!me->mStateFullScreenOrMaximized) { + me->mWidthLogical = me->mWidthWindowed; + me->mHeightLogical = me->mHeightWindowed; + if (me->mBoundsWidth && me->mBoundsHeight) { + me->mWidthLogical = std::min(me->mWidthLogical, me->mBoundsWidth); + me->mHeightLogical = std::min(me->mHeightLogical, me->mBoundsHeight); } } + me->updateSize(); me->mWidthRequested = me->mHeightRequested = 0; }; auto xdg_toplevel_handle_close = [](void* data, xdg_toplevel* toplevel) { ((GPUDisplayFrontendWayland*)data)->mDisplayControl = 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) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mBoundsWidth = width; + me->mBoundsHeight = height; + }; +#endif +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + auto xdg_toplevel_handle_wm_capabilities = [](void* data, xdg_toplevel* toplevel, wl_array* capabilities) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mCanFullscreen = me->mCanMaximize = false; + const uint32_t* cap = (const uint32_t*)capabilities->data; + for (size_t i = 0; i < capabilities->size / sizeof(uint32_t); i++) { + me->mCanFullscreen |= cap[i] == XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN; + me->mCanMaximize |= cap[i] == XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE; + } + }; +#endif -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-field-initializers" - const xdg_surface_listener xdg_surface_listener = { + mXdgSurfaceListener = xdg_surface_listener{ .configure = xdg_surface_handle_configure, }; - const xdg_toplevel_listener xdg_toplevel_listener = { + mXdgToplevelListener = xdg_toplevel_listener{ .configure = xdg_toplevel_handle_configure, .close = xdg_toplevel_handle_close, - .configure_bounds = nullptr}; -#pragma GCC diagnostic pop +#ifdef XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION + .configure_bounds = xdg_toplevel_handle_configure_bounds, +#endif +#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION + .wm_capabilities = xdg_toplevel_handle_wm_capabilities, +#endif + }; - xdg_surface_add_listener(mXdgSurface, &xdg_surface_listener, this); - xdg_toplevel_add_listener(mXdgToplevel, &xdg_toplevel_listener, this); + xdg_surface_add_listener(mXdgSurface, &mXdgSurfaceListener, this); + xdg_toplevel_add_listener(mXdgToplevel, &mXdgToplevelListener, this); xdg_toplevel_set_title(mXdgToplevel, DISPLAY_WINDOW_NAME); + applyInitialWindowState(); // Must be requested before the first commit if (mDecManager) { // Must be requested before the first commit. Without the protocol (e.g. GNOME), the window has no decorations mXdgDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(mDecManager, mXdgToplevel); @@ -384,7 +518,9 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() break; } HandleSendKeys(); - DrawGLScene(); + if (!mSuspended) { // Window not visible, compositor would not release swapchain images + DrawGLScene(); + } waitForFPSLimit(); } @@ -402,11 +538,11 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() pthread_mutex_unlock(&mSemLockExit); if (mPointer) { - wl_pointer_release(mPointer); + mSeatVersion >= WL_POINTER_RELEASE_SINCE_VERSION ? wl_pointer_release(mPointer) : wl_pointer_destroy(mPointer); mPointer = nullptr; } if (mKeyboard) { - wl_keyboard_release(mKeyboard); + mSeatVersion >= WL_KEYBOARD_RELEASE_SINCE_VERSION ? wl_keyboard_release(mKeyboard) : wl_keyboard_destroy(mKeyboard); mKeyboard = nullptr; } if (mXdgDecoration) { @@ -420,6 +556,25 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() zxdg_decoration_manager_v1_destroy(mDecManager); mDecManager = nullptr; } + for (auto& o : mOutputs) { + if (o.version >= WL_OUTPUT_RELEASE_SINCE_VERSION) { + wl_output_release(o.output); + } else { + wl_output_destroy(o.output); + } + } + mOutputs.clear(); + mSurfaceOutputs.clear(); + if (mSeat) { + if (mSeatVersion >= WL_SEAT_RELEASE_SINCE_VERSION) { + wl_seat_release(mSeat); + } else { + wl_seat_destroy(mSeat); + } + mSeat = nullptr; + } + xdg_wm_base_destroy(mXdgBase); + wl_compositor_destroy(mCompositor); wl_registry_destroy(mRegistry); wl_display_disconnect(mWayland); @@ -437,8 +592,10 @@ void GPUDisplayFrontendWayland::DisplayExit() void GPUDisplayFrontendWayland::SwitchFullscreen(bool set) { - if (set) { - xdg_toplevel_set_fullscreen(mXdgToplevel, mOutput); + if (!mCanFullscreen) { + GPUWarning("Wayland compositor does not support fullscreen"); + } else if (set) { + xdg_toplevel_set_fullscreen(mXdgToplevel, mSurfaceOutputs.size() ? mSurfaceOutputs.back() : nullptr); } else { xdg_toplevel_unset_fullscreen(mXdgToplevel); } @@ -446,13 +603,60 @@ void GPUDisplayFrontendWayland::SwitchFullscreen(bool set) void GPUDisplayFrontendWayland::ToggleMaximized(bool set) { - if (set) { + if (!mCanMaximize) { + GPUWarning("Wayland compositor does not support maximizing windows"); + } else if (set) { xdg_toplevel_set_maximized(mXdgToplevel); } else { xdg_toplevel_unset_maximized(mXdgToplevel); } } +GPUDisplayFrontendWayland::OutputInfo* GPUDisplayFrontendWayland::findOutput(wl_output* output) +{ + auto it = std::find_if(mOutputs.begin(), mOutputs.end(), [output](const auto& o) { return o.output == output; }); + return it == mOutputs.end() ? nullptr : &*it; +} + +void GPUDisplayFrontendWayland::updateScale() +{ + int32_t scale = mPreferredScale; + if (scale == 0) { + scale = 1; + for (auto* output : mSurfaceOutputs) { + if (auto* o = findOutput(output)) { + scale = std::max(scale, o->scale); + } + } + } + if (scale != mScale && mSurface) { + mScale = scale; + wl_surface_set_buffer_scale(mSurface, mScale); // Applied with the next commit, i.e. the next present, which happens after the swapchain was recreated with the new size by ResizeScene + updateSize(); + } +} + +void GPUDisplayFrontendWayland::updateSize() +{ + const int32_t width = mWidthLogical * mScale, height = mHeightLogical * mScale; + if (width != mDisplayWidth || height != mDisplayHeight) { + if (mDisplayRunning) { + ResizeScene(width, height); // Swapchain is recreated by the backend, Vulkan WSI commits the new buffer + } else { + mDisplayWidth = width; // Initial size, used by InitDisplay via getSize() + mDisplayHeight = height; + } + } +} + +void GPUDisplayFrontendWayland::applyScroll() +{ + mMouseWheel += mHasAxisValue120 ? (mAxisValue120 * -100 / 120) : (int32_t)(mAxisValue * (-100. / 15.)); + mAxisValue = 0; + mAxisValue120 = 0; + mHasAxisValue120 = false; +} + void GPUDisplayFrontendWayland::SetVSync(bool enable) { } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 7f6301d090b11..68d1352bc0984 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -18,10 +18,10 @@ #include "GPUDisplayFrontend.h" #include #include +#include "xdg-shell-client-protocol.h" +#include +#include -struct xdg_wm_base; -struct xdg_toplevel; -struct xdg_surface; struct zxdg_decoration_manager_v1; struct zxdg_toplevel_decoration_v1; struct xkb_context; @@ -42,12 +42,25 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend 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; uint32_t getReqVulkanExtensions(const char**& p) override; private: int32_t FrontendMain() override; int32_t GetKey(uint32_t key, uint32_t state); + void updateScale(); // Buffer scale from 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 + void applyScroll(); // Apply scroll events of one wl_pointer.frame: high-resolution wheel (120 = 1 notch = 100 mMouseWheel units) if available, otherwise continuous axis (touchpad, ~15 units per notch) + + struct OutputInfo { + wl_output* output; + uint32_t globalName; + uint32_t version; + int32_t scale = 1; + std::string name; + }; + OutputInfo* findOutput(wl_output* output); pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; @@ -64,7 +77,19 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend xdg_surface* mXdgSurface = nullptr; xdg_toplevel* mXdgToplevel = nullptr; - wl_output* mOutput = nullptr; + wl_registry_listener mRegistryListener{}; + wl_seat_listener mSeatListener{}; + wl_pointer_listener mPointerListener{}; + wl_keyboard_listener mKeyboardListener{}; + wl_output_listener mOutputListener{}; + wl_surface_listener mSurfaceListener{}; + xdg_wm_base_listener mXdgWmBaseListener{}; + xdg_surface_listener mXdgSurfaceListener{}; + xdg_toplevel_listener mXdgToplevelListener{}; + + std::vector mOutputs; + std::vector mSurfaceOutputs; // Outputs the surface is on, most recently entered last + uint32_t mSeatVersion = 0; xkb_context* mXKBcontext = nullptr; xkb_keymap* mXKBkeymap = nullptr; @@ -75,7 +100,24 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend int32_t mWidthRequested = 0; int32_t mHeightRequested = 0; + int32_t mWidthLogical = INIT_WIDTH; + int32_t mHeightLogical = INIT_HEIGHT; + int32_t mWidthWindowed = INIT_WIDTH; // Last size when neither maximized nor fullscreen + int32_t mHeightWindowed = INIT_HEIGHT; + bool mStateFullScreenOrMaximized = false; + int32_t mBoundsWidth = 0; + int32_t mBoundsHeight = 0; + int32_t mScale = 1; + int32_t mPreferredScale = 0; // 0 = not sent by compositor + bool mSuspended = false; + bool mCanFullscreen = true; + bool mCanMaximize = true; + + double mAxisValue = 0; // Accumulated scroll values of one wl_pointer.frame + int32_t mAxisValue120 = 0; + bool mHasAxisValue120 = false; }; +GPUDisplayFrontend* createGPUDisplayFrontendWayland(); } // namespace o2::gpu #endif diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index a821b7626bb36..a961904b3d3f7 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -23,6 +23,7 @@ #include #include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN #include @@ -194,20 +195,20 @@ int32_t GPUDisplayFrontendX11::FrontendMain() int32_t eventBase; // Open a connection to the X server - mDisplay = XOpenDisplay(nullptr); + mXDisplay = XOpenDisplay(nullptr); - if (mDisplay == nullptr) { + if (mXDisplay == nullptr) { GPUError("could not open display"); return (-1); } // Make sure OpenGL's GLX extension supported - if (!glXQueryExtension(mDisplay, &errorBase, &eventBase)) { + if (!glXQueryExtension(mXDisplay, &errorBase, &eventBase)) { GPUError("X server has no OpenGL GLX extension"); return (-1); } - const char* glxExt = glXQueryExtensionsString(mDisplay, DefaultScreen(mDisplay)); + const char* glxExt = glXQueryExtensionsString(mXDisplay, DefaultScreen(mXDisplay)); if (strstr(glxExt, "GLX_EXT_swap_control") == nullptr) { GPUError("No vsync support!"); vsync_supported = false; @@ -223,14 +224,14 @@ int32_t GPUDisplayFrontendX11::FrontendMain() GLXFBConfig fbconfig = nullptr; int32_t fbcount; - GLXFBConfig* fbc = glXChooseFBConfig(mDisplay, DefaultScreen(mDisplay), attribs, &fbcount); + GLXFBConfig* fbc = glXChooseFBConfig(mXDisplay, DefaultScreen(mXDisplay), attribs, &fbcount); if (fbc == nullptr || fbcount == 0) { GPUError("Failed to get MSAA GLXFBConfig"); return (-1); } fbconfig = fbc[0]; XFree(fbc); - visualInfo = glXGetVisualFromFBConfig(mDisplay, fbconfig); + visualInfo = glXGetVisualFromFBConfig(mXDisplay, fbconfig); if (visualInfo == nullptr) { GPUError("no RGB visual with depth buffer"); @@ -246,9 +247,9 @@ int32_t GPUDisplayFrontendX11::FrontendMain() 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, None}; - glxContext = glXCreateContextAttribsARB(mDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); + glxContext = glXCreateContextAttribsARB(mXDisplay, fbconfig, nullptr, GL_TRUE, context_attribs); } else { - glxContext = glXCreateContext(mDisplay, visualInfo, nullptr, GL_TRUE); + glxContext = glXCreateContext(mXDisplay, visualInfo, nullptr, GL_TRUE); } if (glxContext == nullptr) { GPUError("could not create rendering context"); @@ -256,27 +257,26 @@ int32_t GPUDisplayFrontendX11::FrontendMain() } } - Window win = RootWindow(mDisplay, visualInfo->screen); - colorMap = XCreateColormap(mDisplay, win, visualInfo->visual, AllocNone); + Window win = RootWindow(mXDisplay, visualInfo->screen); + colorMap = XCreateColormap(mXDisplay, win, visualInfo->visual, AllocNone); windowAttributes.colormap = colorMap; windowAttributes.border_pixel = 0; - windowAttributes.event_mask = ExposureMask | VisibilityChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | SubstructureNotifyMask | FocusChangeMask; + windowAttributes.event_mask = ExposureMask | VisibilityChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | SubstructureNotifyMask | FocusChangeMask | PropertyChangeMask; // Create an X window with the selected visual - mWindow = XCreateWindow(mDisplay, win, 50, 50, INIT_WIDTH, INIT_HEIGHT, // Position / Width and height of window + mWindow = XCreateWindow(mXDisplay, win, 50, 50, INIT_WIDTH, INIT_HEIGHT, // Position / Width and height of window 0, visualInfo->depth, InputOutput, visualInfo->visual, CWBorderPixel | CWColormap | CWEventMask, &windowAttributes); - XSetStandardProperties(mDisplay, mWindow, DISPLAY_WINDOW_NAME, DISPLAY_WINDOW_NAME, None, nullptr, 0, nullptr); + XSetStandardProperties(mXDisplay, mWindow, DISPLAY_WINDOW_NAME, DISPLAY_WINDOW_NAME, None, nullptr, 0, nullptr); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glXMakeCurrent(mDisplay, mWindow, glxContext); + glXMakeCurrent(mXDisplay, mWindow, glxContext); } - XMapWindow(mDisplay, mWindow); + XMapWindow(mXDisplay, mWindow); - // Maximize window - ToggleMaximized(true); + applyInitialWindowState(); // Receive signal when window closed - Atom WM_DELETE_WINDOW = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False); - XSetWMProtocols(mDisplay, mWindow, &WM_DELETE_WINDOW, 1); + 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) { @@ -287,7 +287,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (-1); } const char* f = "fixed"; - font_info = XLoadQueryFont(mDisplay, f); + font_info = XLoadQueryFont(mXDisplay, f); if (!font_info) { GPUError("XLoadQueryFont failed."); return (-1); @@ -308,8 +308,8 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (-1); } - XMapWindow(mDisplay, mWindow); - XFlush(mDisplay); + XMapWindow(mXDisplay, mWindow); + XFlush(mXDisplay); // Enable vsync if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL && vsync_supported) { @@ -318,7 +318,7 @@ int32_t GPUDisplayFrontendX11::FrontendMain() GPUError("Cannot enable vsync"); return (-1); } - mGlXSwapIntervalEXT(mDisplay, glXGetCurrentDrawable(), 1); + mGlXSwapIntervalEXT(mXDisplay, glXGetCurrentDrawable(), 1); } if (InitDisplay()) { @@ -335,10 +335,10 @@ int32_t GPUDisplayFrontendX11::FrontendMain() break; } HandleSendKeys(); - if (!XPending(mDisplay)) { + if (!XPending(mXDisplay)) { event.type = Expose; } else { - XNextEvent(mDisplay, &event); + XNextEvent(mXDisplay, &event); } switch (event.type) { case ButtonPress: { @@ -392,6 +392,13 @@ int32_t GPUDisplayFrontendX11::FrontendMain() break; } + case PropertyNotify: { + if (event.xproperty.atom == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { + updateWindowState(); + } + break; + } + case Expose: { break; } @@ -402,22 +409,22 @@ int32_t GPUDisplayFrontendX11::FrontendMain() } case ClientMessage: { - if (event.xclient.message_type == XInternAtom(mDisplay, "_NET_WM_STATE", False)) { - XFlush(mDisplay); + if (event.xclient.message_type == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { + XFlush(mXDisplay); } else { mDisplayControl = 2; } break; } } - } while (XPending(mDisplay)); // Loop to compress events + } while (XPending(mXDisplay)); // Loop to compress events if (mDisplayControl == 2) { break; } DrawGLScene(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glXSwapBuffers(mDisplay, mWindow); + glXSwapBuffers(mXDisplay, mWindow); } waitForFPSLimit(); } @@ -425,16 +432,16 @@ int32_t GPUDisplayFrontendX11::FrontendMain() #ifndef GPUCA_DISPLAY_OPENGL_CORE if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glDeleteLists(mFontBase, 256); - XUnloadFont(mDisplay, font_info->fid); + XUnloadFont(mXDisplay, font_info->fid); } #endif ExitDisplay(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { - glXDestroyContext(mDisplay, glxContext); + glXDestroyContext(mXDisplay, glxContext); } XFree(visualInfo); - XDestroyWindow(mDisplay, mWindow); - XCloseDisplay(mDisplay); + XDestroyWindow(mXDisplay, mWindow); + XCloseDisplay(mXDisplay); pthread_mutex_lock(&mSemLockExit); mDisplayRunning = false; @@ -452,18 +459,39 @@ void GPUDisplayFrontendX11::DisplayExit() pthread_mutex_unlock(&mSemLockExit); } +void GPUDisplayFrontendX11::updateWindowState() +{ + Atom type; + int32_t format; + unsigned long nItems, bytesAfter; + unsigned char* data = nullptr; + if (XGetWindowProperty(mXDisplay, mWindow, XInternAtom(mXDisplay, "_NET_WM_STATE", False), 0, 1024, False, XA_ATOM, &type, &format, &nItems, &bytesAfter, &data) != Success || data == nullptr) { + return; + } + const Atom fullScreen = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", False), maxHorz = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False), maxVert = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False); + bool isFullScreen = false, isMaxHorz = false, isMaxVert = false; + for (unsigned long i = 0; i < nItems; i++) { + const Atom a = ((Atom*)data)[i]; + isFullScreen |= a == fullScreen; + isMaxHorz |= a == maxHorz; + isMaxVert |= a == maxVert; + } + XFree(data); + setWindowState(isFullScreen, isMaxHorz && isMaxVert); +} + void GPUDisplayFrontendX11::SwitchFullscreen(bool set) { XEvent xev; memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; xev.xclient.window = mWindow; - xev.xclient.message_type = XInternAtom(mDisplay, "_NET_WM_STATE", False); + 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(mDisplay, "_NET_WM_STATE_FULLSCREEN", True); + xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_FULLSCREEN", True); xev.xclient.data.l[2] = 0; - XSendEvent(mDisplay, DefaultRootWindow(mDisplay), False, SubstructureNotifyMask, &xev); + XSendEvent(mXDisplay, DefaultRootWindow(mXDisplay), False, SubstructureNotifyMask, &xev); } void GPUDisplayFrontendX11::ToggleMaximized(bool set) @@ -472,18 +500,18 @@ void GPUDisplayFrontendX11::ToggleMaximized(bool set) memset(&xev, 0, sizeof(xev)); xev.type = ClientMessage; xev.xclient.window = mWindow; - xev.xclient.message_type = XInternAtom(mDisplay, "_NET_WM_STATE", False); + xev.xclient.message_type = XInternAtom(mXDisplay, "_NET_WM_STATE", False); xev.xclient.format = 32; xev.xclient.data.l[0] = set ? 1 : 2; //_NET_WM_STATE_ADD - xev.xclient.data.l[1] = XInternAtom(mDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - xev.xclient.data.l[2] = XInternAtom(mDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False); - XSendEvent(mDisplay, DefaultRootWindow(mDisplay), False, SubstructureNotifyMask, &xev); + xev.xclient.data.l[1] = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + xev.xclient.data.l[2] = XInternAtom(mXDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False); + XSendEvent(mXDisplay, DefaultRootWindow(mXDisplay), False, SubstructureNotifyMask, &xev); } void GPUDisplayFrontendX11::SetVSync(bool enable) { if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL && vsync_supported) { - mGlXSwapIntervalEXT(mDisplay, glXGetCurrentDrawable(), (int32_t)enable); + mGlXSwapIntervalEXT(mXDisplay, glXGetCurrentDrawable(), (int32_t)enable); } } @@ -492,7 +520,7 @@ void GPUDisplayFrontendX11::getSize(int32_t& width, int32_t& height) Window root_return; int32_t x_return, y_return; uint32_t width_return, height_return, border_width_return, depth_return; - if (XGetGeometry(mDisplay, mWindow, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return) == 0) { + if (XGetGeometry(mXDisplay, mWindow, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return) == 0) { throw std::runtime_error("Cannot query X11 window geometry"); } width = width_return; @@ -505,7 +533,7 @@ int32_t GPUDisplayFrontendX11::getVulkanSurface(void* instance, void* surface) VkXlibSurfaceCreateInfoKHR info{}; info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; info.flags = 0; - info.dpy = mDisplay; + info.dpy = mXDisplay; info.window = mWindow; return vkCreateXlibSurfaceKHR(*(VkInstance*)instance, &info, nullptr, (VkSurfaceKHR*)surface) != VK_SUCCESS; #else diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index c0ad7cf5f99ab..35a9f12a7b5a3 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -48,8 +48,9 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend GLuint mFontBase; - Display* mDisplay = nullptr; + Display* mXDisplay = nullptr; Window mWindow; + void updateWindowState(); PFNGLXSWAPINTERVALEXTPROC mGlXSwapIntervalEXT = nullptr; bool vsync_supported = false; diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 8726563c0ec39..2fbbb8a9d1cab 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -122,11 +122,14 @@ void GPUDisplay::showInfo(const char* info) void GPUDisplay::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) { if (mBackend->mFreetypeInitialized) { + const float scale = mFrontend->getDisplayScale(); + x *= scale; + y *= scale; if (!fromBotton) { y = mBackend->mScreenHeight - y; } float color[4] = {r, g, b, a}; - mBackend->OpenGLPrint(s, x, y, color, 1.0f); + mBackend->OpenGLPrint(s, x, y, color, scale); } else if (mFrontend->mCanDrawText) { mFrontend->OpenGLPrint(s, x, y, r, g, b, a, fromBotton); } From ffa67268a980dab8838c9a059af0ae19b005373c Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 00:55:14 +0200 Subject: [PATCH 08/48] GPU Wayland Display: Update keymap with correct defines for +/- --- .../display/frontend/GPUDisplayFrontendWayland.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index d176c42cbf694..a591c068b9717 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -58,9 +58,9 @@ int32_t GPUDisplayFrontendWayland::GetKey(uint32_t key, uint32_t state) int32_t retVal = 0; if (mXKBkeymap) { xkb_keysym_t sym = xkb_state_key_get_one_sym(mXKBstate, key + 8); - if (sym == 65453) { + if (sym == XKB_KEY_KP_Subtract) { return ('-'); - } else if (sym == 65451) { + } else if (sym == XKB_KEY_KP_Add) { return ('+'); } else if (sym == XKB_KEY_Shift_L || sym == XKB_KEY_Shift_R) { return (KEY_SHIFT); From 49c55eda5706edd9bea4bc339c405e7080d423bf Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:11:21 +0200 Subject: [PATCH 09/48] GPU Display: Release keys / mouse buttons when focus lost --- .../display/frontend/GPUDisplayFrontend.cxx | 9 +++++++++ GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h | 1 + .../display/frontend/GPUDisplayFrontendGlfw.cxx | 8 ++++++++ .../display/frontend/GPUDisplayFrontendGlfw.h | 1 + .../display/frontend/GPUDisplayFrontendWayland.cxx | 10 ++++++++-- .../display/frontend/GPUDisplayFrontendX11.cxx | 5 +++++ 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 57fb46a26c9ef..e223f453e233a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -40,6 +40,8 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include "GPULogging.h" #include +#include +#include #include #ifdef GPUCA_BUILD_EVENT_DISPLAY_QT @@ -201,6 +203,13 @@ void GPUDisplayFrontend::setWindowState(bool fullScreen, bool maximized) mDisplay->mCfgR.maximized = maximized; } +void GPUDisplayFrontend::focusLost() +{ + std::fill(std::begin(mKeys), std::end(mKeys), false); + std::fill(std::begin(mKeysShift), std::end(mKeysShift), false); + mMouseDn = mMouseDnR = false; +} + void GPUDisplayFrontend::applyInitialWindowState() { if (mDisplay->cfgR().fullScreen) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index e2744e9036e43..3de02c5928fa6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -152,6 +152,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void ExitDisplay(); // Callback to clean up the GL Display int32_t& drawTextFontSize(); 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 }; } // namespace o2::gpu diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index c4d60e4733e9a..629f5b1e1765b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -229,6 +229,13 @@ void GPUDisplayFrontendGlfw::cursorPos_callback(GLFWwindow* window, double x, do void GPUDisplayFrontendGlfw::resize_callback(GLFWwindow* window, int32_t width, int32_t height) { me->ResizeScene(width, height); } +void GPUDisplayFrontendGlfw::focus_callback(GLFWwindow* window, int32_t focused) +{ + if (!focused) { + me->focusLost(); + } +} + void GPUDisplayFrontendGlfw::maximize_callback(GLFWwindow* window, int32_t maximized) { me->setWindowState(glfwGetWindowMonitor(window) != nullptr, maximized); } #ifndef GPUCA_STANDALONE @@ -279,6 +286,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwSetCursorPosCallback(mWindow, cursorPos_callback); glfwSetWindowSizeCallback(mWindow, resize_callback); glfwSetWindowMaximizeCallback(mWindow, maximize_callback); + glfwSetWindowFocusCallback(mWindow, focus_callback); applyInitialWindowState(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapInterval(1); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 41bbb4ab3e219..922b0166404d6 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -50,6 +50,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend static void cursorPos_callback(GLFWwindow* mWindow, double x, double y); static void resize_callback(GLFWwindow* mWindow, int32_t width, int32_t height); static void maximize_callback(GLFWwindow* mWindow, int32_t maximized); + static void focus_callback(GLFWwindow* mWindow, int32_t focused); static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t scancode, int32_t mods, int32_t& keyOut, int32_t& keyPressOut); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index a591c068b9717..0493d33180861 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -156,6 +156,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() auto pointer_enter = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { }; auto pointer_leave = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* wl_surface) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mMouseDn = me->mMouseDnR = false; }; auto pointer_motion = [](void* data, wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; @@ -192,7 +194,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto pointer_axis_source = [](void* data, wl_pointer* wl_pointer, uint32_t axis_source) {}; auto pointer_axis_stop = [](void* data, wl_pointer* wl_pointer, uint32_t time, uint32_t axis) {}; - auto pointer_axis_discrete = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) {}; // Only sent for seat versions 5 to 7, superseded by axis_value120 + auto pointer_axis_discrete = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) {}; #ifdef WL_POINTER_AXIS_VALUE120_SINCE_VERSION auto pointer_axis_value120 = [](void* data, wl_pointer* wl_pointer, uint32_t axis, int32_t value120) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; @@ -223,7 +225,11 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() close(fd); }; auto keyboard_enter = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) {}; - auto keyboard_leave = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface) {}; + auto keyboard_leave = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, wl_surface* surface) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->focusLost(); + me->mRepeatKeyCode = -1; + }; auto keyboard_key = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; int32_t symbol = me->GetKey(key, state); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index a961904b3d3f7..703ece544d1d4 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -392,6 +392,11 @@ int32_t GPUDisplayFrontendX11::FrontendMain() break; } + case FocusOut: { + focusLost(); + break; + } + case PropertyNotify: { if (event.xproperty.atom == XInternAtom(mXDisplay, "_NET_WM_STATE", False)) { updateWindowState(); From 561d6d71dabfda05cf1fed4a92d214ac2ee29c3e Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:11:30 +0200 Subject: [PATCH 10/48] GPU Wayland Display: Handle Key Repeats --- .../frontend/GPUDisplayFrontendWayland.cxx | 32 +++++++++++++++++-- .../frontend/GPUDisplayFrontendWayland.h | 13 ++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 0493d33180861..f4ed836facb7a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -234,20 +234,35 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; int32_t symbol = me->GetKey(key, state); int32_t keyPress = (symbol >= 'a' && symbol <= 'z') ? symbol + 'A' - 'a' : symbol; - if (state == XKB_KEY_DOWN) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { me->mKeys[keyPress] = true; me->mKeysShift[keyPress] = me->mKeys[KEY_SHIFT]; me->HandleKey(symbol); + if (symbol && me->mRepeatRate > 0 && me->mXKBkeymap && xkb_keymap_key_repeats(me->mXKBkeymap, key + 8)) { // Wayland compositors do not repeat keys, the client must do it + me->mRepeatKeyCode = key; + me->mRepeatSymbol = symbol; + me->mRepeatNext = std::chrono::steady_clock::now() + std::chrono::milliseconds(me->mRepeatDelay); + } } else { me->mKeys[keyPress] = false; me->mKeysShift[keyPress] = false; + if ((int64_t)key == me->mRepeatKeyCode) { + me->mRepeatKeyCode = -1; + } } }; auto keyboard_modifiers = [](void* data, wl_keyboard* wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; xkb_state_update_mask(me->mXKBstate, mods_depressed, mods_latched, mods_locked, 0, 0, group); }; - auto keyboard_repat = [](void* data, wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) {}; + auto keyboard_repat = [](void* data, wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mRepeatRate = rate; + me->mRepeatDelay = delay; + if (rate <= 0) { + me->mRepeatKeyCode = -1; + } + }; mKeyboardListener = wl_keyboard_listener{.keymap = keyboard_keymap, .enter = keyboard_enter, .leave = keyboard_leave, .key = keyboard_key, .modifiers = keyboard_modifiers, .repeat_info = keyboard_repat}; auto xdg_wm_base_ping = [](void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial) { @@ -524,6 +539,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() break; } HandleSendKeys(); + handleKeyRepeat(); if (!mSuspended) { // Window not visible, compositor would not release swapchain images DrawGLScene(); } @@ -655,6 +671,18 @@ void GPUDisplayFrontendWayland::updateSize() } } +void GPUDisplayFrontendWayland::handleKeyRepeat() +{ + if (mRepeatKeyCode < 0) { + return; + } + const auto now = std::chrono::steady_clock::now(); + while (now >= mRepeatNext && mRepeatKeyCode >= 0) { + HandleKey(mRepeatSymbol); + mRepeatNext += std::chrono::microseconds(1000000 / mRepeatRate); + } +} + void GPUDisplayFrontendWayland::applyScroll() { mMouseWheel += mHasAxisValue120 ? (mAxisValue120 * -100 / 120) : (int32_t)(mAxisValue * (-100. / 15.)); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 68d1352bc0984..a12c55989f955 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -49,9 +49,10 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend private: int32_t FrontendMain() override; int32_t GetKey(uint32_t key, uint32_t state); - void updateScale(); // Buffer scale from 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 - void applyScroll(); // Apply scroll events of one wl_pointer.frame: high-resolution wheel (120 = 1 notch = 100 mMouseWheel units) if available, otherwise continuous axis (touchpad, ~15 units per notch) + void updateScale(); // Buffer scale from 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 + void handleKeyRepeat(); // Call HandleKey for the held key according to wl_keyboard.repeat_info + void applyScroll(); // Apply scroll events of one wl_pointer.frame: high-resolution wheel (120 = 1 notch = 100 mMouseWheel units) if available, otherwise continuous axis (touchpad, ~15 units per notch) struct OutputInfo { wl_output* output; @@ -113,6 +114,12 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend bool mCanFullscreen = true; bool mCanMaximize = true; + int32_t mRepeatRate = 0; // Keys per second, 0 = no repeat + int32_t mRepeatDelay = 0; // ms before the first repeat + int64_t mRepeatKeyCode = -1; // Evdev key code of the held key to repeat, -1 = none + int32_t mRepeatSymbol = 0; + std::chrono::steady_clock::time_point mRepeatNext; + double mAxisValue = 0; // Accumulated scroll values of one wl_pointer.frame int32_t mAxisValue120 = 0; bool mHasAxisValue120 = false; From 750d7e2727058e12174bb727d84c317bc6561ded Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:16:26 +0200 Subject: [PATCH 11/48] GPU Wayland Display: Add error handling / reporting --- .../display/frontend/GPUDisplayFrontend.cxx | 11 ++++- .../frontend/GPUDisplayFrontendWayland.cxx | 43 ++++++++++++++++--- .../frontend/GPUDisplayFrontendWayland.h | 9 ++-- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index e223f453e233a..286dc7ddedc4c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -40,6 +40,7 @@ GPUDisplayFrontend* createGPUDisplayFrontendWayland(); #include "GPULogging.h" #include +#include #include #include #include @@ -62,8 +63,14 @@ GPUDisplayFrontend::~GPUDisplayFrontend() = default; void* GPUDisplayFrontend::FrontendThreadWrapper(void* ptr) { GPUDisplayFrontend* me = reinterpret_cast(ptr); - int32_t retVal = me->FrontendMain(); - if (retVal == -1) { + int32_t retVal; + try { + retVal = me->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); } return ((void*)(size_t)retVal); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index f4ed836facb7a..b55c8e12aa9ff 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -369,7 +370,10 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() mRegistryListener = wl_registry_listener{.global = registry_global, .global_remove = registry_global_remove}; wl_registry_add_listener(mRegistry, &mRegistryListener, this); - wl_display_roundtrip(mWayland); + if (wl_display_roundtrip(mWayland) == -1) { + reportError("initial roundtrip"); + throw std::runtime_error("Wayland initialization failed"); + } if (mCompositor == nullptr || mXdgBase == nullptr || mSeat == nullptr) { throw std::runtime_error("Error getting wayland objects"); @@ -502,7 +506,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() xdg_toplevel_add_listener(mXdgToplevel, &mXdgToplevelListener, this); xdg_toplevel_set_title(mXdgToplevel, DISPLAY_WINDOW_NAME); - applyInitialWindowState(); // Must be requested before the first commit + xdg_toplevel_set_app_id(mXdgToplevel, "o2-gpu-event-display"); // Used by the compositor for window grouping, window rules, and matching a .desktop file + applyInitialWindowState(); // Must be requested before the first commit if (mDecManager) { // Must be requested before the first commit. Without the protocol (e.g. GNOME), the window has no decorations mXdgDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(mDecManager, mXdgToplevel); @@ -512,7 +517,10 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } wl_surface_commit(mSurface); // Initial commit without buffer, the compositor answers with the first configure - wl_display_roundtrip(mWayland); + if (wl_display_roundtrip(mWayland) == -1) { + reportError("initial roundtrip"); + throw std::runtime_error("Wayland initialization failed"); + } if (InitDisplay()) { return (1); @@ -523,19 +531,29 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() pthread_mutex_unlock(&mSemLockExit); while (mDisplayControl != 2) { - while (wl_display_prepare_read(mWayland) != 0) { - wl_display_dispatch_pending(mWayland); + bool error = false; + while (!error && wl_display_prepare_read(mWayland) != 0) { + error = wl_display_dispatch_pending(mWayland) == -1; + } + if (error) { + reportError("dispatching events"); + break; } wl_display_flush(mWayland); pollfd pfd = {wl_display_get_fd(mWayland), POLLIN, 0}; if (poll(&pfd, 1, 0) > 0) { if (wl_display_read_events(mWayland) == -1) { + reportError("reading events"); break; } } else { wl_display_cancel_read(mWayland); } - if (wl_display_dispatch_pending(mWayland) == -1 || mDisplayControl == 2) { + if (wl_display_dispatch_pending(mWayland) == -1) { + reportError("dispatching events"); + break; + } + if (mDisplayControl == 2) { break; } HandleSendKeys(); @@ -671,6 +689,19 @@ void GPUDisplayFrontendWayland::updateSize() } } +void GPUDisplayFrontendWayland::reportError(const char* where) +{ + const int32_t err = wl_display_get_error(mWayland); + if (err == EPROTO) { + const wl_interface* iface = nullptr; + uint32_t id = 0; + const uint32_t code = wl_display_get_protocol_error(mWayland, &iface, &id); + GPUError("Wayland protocol error %u on %s@%u while %s", code, iface ? iface->name : "unknown interface", id, where); + } else { + GPUError("Wayland error while %s: %s", where, strerror(err)); + } +} + void GPUDisplayFrontendWayland::handleKeyRepeat() { if (mRepeatKeyCode < 0) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index a12c55989f955..5c4668da3369e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -49,10 +49,11 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend private: int32_t FrontendMain() override; int32_t GetKey(uint32_t key, uint32_t state); - void updateScale(); // Buffer scale from 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 - void handleKeyRepeat(); // Call HandleKey for the held key according to wl_keyboard.repeat_info - void applyScroll(); // Apply scroll events of one wl_pointer.frame: high-resolution wheel (120 = 1 notch = 100 mMouseWheel units) if available, otherwise continuous axis (touchpad, ~15 units per notch) + void updateScale(); // Buffer scale from 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 + void reportError(const char* where); // Print the Wayland display error (protocol error with interface and object, or errno) after a call returned -1 + void handleKeyRepeat(); // Call HandleKey for the held key according to wl_keyboard.repeat_info + void applyScroll(); // Apply scroll events of one wl_pointer.frame: high-resolution wheel (120 = 1 notch = 100 mMouseWheel units) if available, otherwise continuous axis (touchpad, ~15 units per notch) struct OutputInfo { wl_output* output; From eb80cc0b5a54cf9b5e7f6f102a42d0506fbe13fb Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:39:50 +0200 Subject: [PATCH 12/48] GPU Display: Fix missing return value --- GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx index 07a05e585d422..7108386edccf4 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayROOT.cxx @@ -59,6 +59,7 @@ int32_t GPUDisplay::buildTrackFilter() int32_t GPUDisplay::buildTrackFilter() { + return 0; } #endif From 347cbaa3f6c96a9035404e0390fd640e2769709f Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:43:20 +0200 Subject: [PATCH 13/48] GPU Display CMake: Simplify generation of wayland protocols --- GPU/GPUTracking/display/CMakeLists.txt | 52 +++++++++++++------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index eb7a0dc6d6400..ab15cd429963a 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -76,35 +76,33 @@ set(HDRS_INSTALL shaders/GPUDisplayShaders.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) if(GPUCA_EVENT_DISPLAY_WAYLAND) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-client-protocol.h - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-client-protocol.h - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml - COMMENT "Preparing Wayland xdg-shell-client-protocol.h") - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml - COMMENT "Preparing Wayland xdg-shell-protocol.c") - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-client-protocol.h - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-client-protocol.h - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml - COMMENT "Preparing Wayland xdg-decoration-client-protocol.h") - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-protocol.c - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-protocol.c - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml - COMMENT "Preparing Wayland xdg-decoration-protocol.c") - - set_property(SOURCE frontend/GPUDisplayFrontendWayland.cxx ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-client-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-client-protocol.h) + set(WAYLAND_PROTOCOL_HEADERS) + set(WAYLAND_PROTOCOL_SOURCES) + foreach(xml stable/xdg-shell/xdg-shell.xml unstable/xdg-decoration/xdg-decoration-unstable-v1.xml) + get_filename_component(name ${xml} NAME_WE) + string(REPLACE "-unstable-v1" "" name ${name}) + set(header ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-client-protocol.h) + set(source ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-protocol.c) + add_custom_command( + OUTPUT ${header} + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ + COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${header} + MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} + COMMENT "Preparing Wayland ${name}-client-protocol.h") + add_custom_command( + OUTPUT ${source} + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ + COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${source} + MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} + COMMENT "Preparing Wayland ${name}-protocol.c") + list(APPEND WAYLAND_PROTOCOL_HEADERS ${header}) + 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(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-shell-protocol.c ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/xdg-decoration-protocol.c) + set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() endif() From 202fe0935ed5ce403130c84d4c1bd19908b2640a Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:44:14 +0200 Subject: [PATCH 14/48] GPU Display: Support middle mouse button --- GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx | 2 +- GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h | 1 + GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx | 4 ++++ GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx | 4 ++++ .../display/frontend/GPUDisplayFrontendWayland.cxx | 6 +++++- GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx | 4 ++++ 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index 286dc7ddedc4c..f5bbeef3cf76e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -214,7 +214,7 @@ void GPUDisplayFrontend::focusLost() { std::fill(std::begin(mKeys), std::end(mKeys), false); std::fill(std::begin(mKeysShift), std::end(mKeysShift), false); - mMouseDn = mMouseDnR = false; + mMouseDn = mMouseDnR = mMouseDnM = false; } void GPUDisplayFrontend::applyInitialWindowState() diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 3de02c5928fa6..98af4808008bc 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -124,6 +124,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface // 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 diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 629f5b1e1765b..43e91017ba80d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -207,6 +207,8 @@ void GPUDisplayFrontendGlfw::mouseButton_callback(GLFWwindow* window, int32_t bu me->mMouseDn = true; } else if (button == 1) { me->mMouseDnR = true; + } else if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + me->mMouseDnM = true; } me->mMouseDnX = me->mMouseMvX; me->mMouseDnY = me->mMouseMvY; @@ -215,6 +217,8 @@ void GPUDisplayFrontendGlfw::mouseButton_callback(GLFWwindow* window, int32_t bu me->mMouseDn = false; } else if (button == 1) { me->mMouseDnR = false; + } else if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + me->mMouseDnM = false; } } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 82b1c9448708d..6adfd0091bb65 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -195,6 +195,8 @@ void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, me->mMouseDn = true; } else if (button == GLUT_RIGHT_BUTTON) { me->mMouseDnR = true; + } else if (button == GLUT_MIDDLE_BUTTON) { + me->mMouseDnM = true; } me->mMouseDnX = x; me->mMouseDnY = y; @@ -203,6 +205,8 @@ void GPUDisplayFrontendGlut::mouseFunc(int32_t button, int32_t state, int32_t x, me->mMouseDn = false; } else if (button == GLUT_RIGHT_BUTTON) { me->mMouseDnR = false; + } else if (button == GLUT_MIDDLE_BUTTON) { + me->mMouseDnM = false; } } } diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index b55c8e12aa9ff..4a0dcb0a6067a 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -158,7 +158,7 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; auto pointer_leave = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* wl_surface) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; - me->mMouseDn = me->mMouseDnR = false; + me->mMouseDn = me->mMouseDnR = me->mMouseDnM = false; }; auto pointer_motion = [](void* data, wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; @@ -172,6 +172,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() 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; @@ -180,6 +182,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() me->mMouseDnR = false; } else if (button == BTN_LEFT) { me->mMouseDn = false; + } else if (button == BTN_MIDDLE) { + me->mMouseDnM = false; } } }; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 703ece544d1d4..d83baf6dfb41d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -349,6 +349,8 @@ int32_t GPUDisplayFrontendX11::FrontendMain() } else { if (event.xbutton.button == 1) { mMouseDn = true; + } else if (event.xbutton.button == 2) { + mMouseDnM = true; } else { mMouseDnR = true; } @@ -362,6 +364,8 @@ int32_t GPUDisplayFrontendX11::FrontendMain() if (event.xbutton.button != 4 && event.xbutton.button != 5) { if (event.xbutton.button == 1) { mMouseDn = false; + } else if (event.xbutton.button == 2) { + mMouseDnM = false; } else { mMouseDnR = false; } From 37f0223b14682b5ae62783dd26ace243f86a1dc2 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:45:52 +0200 Subject: [PATCH 15/48] GPU Display: Raster font at correct pixel size taking into accound fractional scaling --- .../display/backend/GPUDisplayBackend.cxx | 28 ++++++++++++++----- .../display/backend/GPUDisplayBackend.h | 4 +++ .../backend/GPUDisplayBackendOpenGL.cxx | 11 ++++++++ .../display/backend/GPUDisplayBackendOpenGL.h | 1 + .../backend/GPUDisplayBackendVulkan.cxx | 10 +++++++ .../display/backend/GPUDisplayBackendVulkan.h | 1 + .../display/helpers/GPUDisplayHelpers.cxx | 3 +- 7 files changed, 50 insertions(+), 8 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index a459a8e290ef6..77c1c0a49a4a5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -66,9 +66,23 @@ int32_t GPUDisplayBackend::InitBackend() if (retVal) { return retVal; } - if (mDisplay->cfg().noFreetype) { - return retVal; + if (!mDisplay->cfg().noFreetype) { + loadFont(); + } + return retVal; +} + +void GPUDisplayBackend::updateFontScale(float scale) +{ + if (mFreetypeInitialized && scale != mFontRasterScale) { + clearFontSymbols(); + mFreetypeInitialized = false; + loadFont(); } +} + +void GPUDisplayBackend::loadFont() +{ std::string fontName = mDisplay->cfg().font; #ifdef GPUCA_BUILD_EVENT_DISPLAY_FONTCONFIG FcInit(); @@ -97,11 +111,11 @@ int32_t GPUDisplayBackend::InitBackend() FT_Face face; if (FT_Init_FreeType(&ft)) { GPUError("Error initializing freetype"); - return 0; + return; } if (FT_New_Face(ft, fontName.c_str(), 0, &face)) { GPUError("Error loading freetypoe font"); - return 0; + return; } int32_t fontSize = mDisplay->cfg().fontSize; @@ -109,12 +123,13 @@ int32_t GPUDisplayBackend::InitBackend() if (smoothFont()) { fontSize *= 4; // Font size scaled by 4, can be downsampled } - FT_Set_Pixel_Sizes(face, 0, fontSize); + 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++) { if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { GPUError("Error loading freetype symbol"); - return 0; + return; } const auto& glyph = face->glyph; addFontSymbol(i, glyph->bitmap.width, glyph->bitmap.rows, glyph->bitmap_left, glyph->bitmap_top, glyph->advance.x, glyph->bitmap.buffer); @@ -124,7 +139,6 @@ int32_t GPUDisplayBackend::InitBackend() FT_Done_FreeType(ft); mFreetypeInitialized = true; #endif // GPUCA_BUILD_EVENT_DISPLAY_FREETYPE - return retVal; } void GPUDisplayBackend::ExitBackend() diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 546c53e1e63ff..0b222d25cb126 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -86,6 +86,7 @@ class GPUDisplayBackend virtual int32_t InitBackendA() = 0; virtual void ExitBackendA() = 0; int32_t InitBackend(); + void updateFontScale(float scale); // Rasterize the font again if the display (HiDPI) scale changed void ExitBackend(); virtual void loadDataToGPU(size_t totalVertizes) = 0; virtual void prepareDraw(const hmm_mat4& proj, const hmm_mat4& view, bool requestScreenshot = false, bool toMixBuffer = false, float includeMixImage = 0.f) = 0; @@ -108,6 +109,8 @@ class GPUDisplayBackend protected: virtual void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) = 0; virtual void initializeTextDrawing() = 0; + virtual void clearFontSymbols() {} // Release glyphs / font atlas before loading the font again + void loadFont(); float getDownsampleFactor(bool screenshot = false); void fillIndirectCmdBuffer(); @@ -117,6 +120,7 @@ class GPUDisplayBackend std::vector mIndirectSectorOffset; vecpod mCmdBuffer; bool mFreetypeInitialized = false; + float mFontRasterScale = 1.f; // Display scale the font was rasterized for bool mFrontendCompatTextDraw = false; std::vector mScreenshotPixels; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index effe59588da24..9d9a413d5bb04 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -709,6 +709,16 @@ void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32 CHKERR(glPixelStorei(GL_UNPACK_ALIGNMENT, oldAlign)); } +void GPUDisplayBackendOpenGL::clearFontSymbols() +{ + CHKERR(glDeleteBuffers(1, &VBO_text)); + CHKERR(glDeleteVertexArrays(1, &VAO_text)); + for (auto& symbol : mFontSymbols) { + CHKERR(glDeleteTextures(1, &symbol.texId)); + } + mFontSymbols.clear(); +} + void GPUDisplayBackendOpenGL::initializeTextDrawing() { CHKERR(glGenVertexArrays(1, &VAO_text)); @@ -840,5 +850,6 @@ void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) {} void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) {} void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) {} void GPUDisplayBackendOpenGL::initializeTextDrawing() {} +void GPUDisplayBackendOpenGL::clearFontSymbols() {} void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float* color, float scale) {} #endif // GPUCA_BUILD_EVENT_DISPLAY_OPENGL diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index d9bb50d7bead8..51c0a30c2a856 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -71,6 +71,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; + void clearFontSymbols() override; void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; struct FontSymbolOpenGL : public FontSymbol { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 9b1bf1910289a..0f968c9c539f4 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1854,6 +1854,16 @@ void GPUDisplayBackendVulkan::initializeTextDrawing() updateFontTextureDescriptor(); } +void GPUDisplayBackendVulkan::clearFontSymbols() +{ + mDevice.waitIdle(); + if (mFontImage.sizex && mFontImage.sizey) { + clearImage(mFontImage); + mFontImage.sizex = mFontImage.sizey = 0; + } + mFontSymbols.clear(); +} + void GPUDisplayBackendVulkan::updateFontTextureDescriptor() { vk::DescriptorImageInfo imageInfo{}; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index caefdd25388c1..b4bff0b2852af 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -126,6 +126,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) override; void initializeTextDrawing() override; + void clearFontSymbols() override; void OpenGLPrint(const char* s, float x, float y, float* color, float scale) override; uint32_t mGraphicsFamily; diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx index 2fbbb8a9d1cab..d0a6685efc742 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayHelpers.cxx @@ -99,6 +99,7 @@ void GPUDisplay::DoScreenshot(const char* filename, std::vector& pixels, f void GPUDisplay::showInfo(const char* info) { + mBackend->updateFontScale(mFrontend->getDisplayScale()); mBackend->prepareText(); float colorValue = mCfgL.invertColors ? 0.f : 1.f; OpenGLPrint(info, 40.f, 20.f + std::max(20, mDrawTextFontSize + 4), colorValue, colorValue, colorValue, 1); @@ -129,7 +130,7 @@ 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->OpenGLPrint(s, x, y, color, scale / mBackend->mFontRasterScale); } else if (mFrontend->mCanDrawText) { mFrontend->OpenGLPrint(s, x, y, r, g, b, a, fromBotton); } From fa719daaf74ba3ccd51b56eb92eb40f2700c1b1a Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 01:49:21 +0200 Subject: [PATCH 16/48] GPU Display: Add minimum window size --- GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h | 1 + GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx | 1 + .../display/frontend/GPUDisplayFrontendWayland.cxx | 5 +++-- GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx | 5 +++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 98af4808008bc..56fe18c9c223e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -83,6 +83,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface void waitForFPSLimit(); // Sleep until the next frame is due according to fpsRate (no sleep with maxFPSRate or fpsRate <= 0) 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; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 43e91017ba80d..7a14369e7395f 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -291,6 +291,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwSetWindowSizeCallback(mWindow, resize_callback); glfwSetWindowMaximizeCallback(mWindow, maximize_callback); glfwSetWindowFocusCallback(mWindow, focus_callback); + glfwSetWindowSizeLimits(mWindow, MIN_WIDTH, MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE); applyInitialWindowState(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapInterval(1); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 4a0dcb0a6067a..3094fe87284af 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -510,8 +510,9 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() xdg_toplevel_add_listener(mXdgToplevel, &mXdgToplevelListener, this); xdg_toplevel_set_title(mXdgToplevel, DISPLAY_WINDOW_NAME); - xdg_toplevel_set_app_id(mXdgToplevel, "o2-gpu-event-display"); // Used by the compositor for window grouping, window rules, and matching a .desktop file - applyInitialWindowState(); // Must be requested before the first commit + xdg_toplevel_set_min_size(mXdgToplevel, MIN_WIDTH, MIN_HEIGHT); // Logical size, avoids degenerate swapchains / projections + xdg_toplevel_set_app_id(mXdgToplevel, "o2-gpu-event-display"); // Used by the compositor for window grouping, window rules, and matching a .desktop file + applyInitialWindowState(); // Must be requested before the first commit if (mDecManager) { // Must be requested before the first commit. Without the protocol (e.g. GNOME), the window has no decorations mXdgDecoration = zxdg_decoration_manager_v1_get_toplevel_decoration(mDecManager, mXdgToplevel); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index d83baf6dfb41d..616311094f649 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -267,6 +267,11 @@ int32_t GPUDisplayFrontendX11::FrontendMain() mWindow = XCreateWindow(mXDisplay, win, 50, 50, INIT_WIDTH, INIT_HEIGHT, // Position / Width and height of window 0, visualInfo->depth, InputOutput, visualInfo->visual, CWBorderPixel | CWColormap | CWEventMask, &windowAttributes); XSetStandardProperties(mXDisplay, mWindow, DISPLAY_WINDOW_NAME, DISPLAY_WINDOW_NAME, None, nullptr, 0, nullptr); + XSizeHints sizeHints{}; + sizeHints.flags = PMinSize; + sizeHints.min_width = MIN_WIDTH; + sizeHints.min_height = MIN_HEIGHT; + XSetWMNormalHints(mXDisplay, mWindow, &sizeHints); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXMakeCurrent(mXDisplay, mWindow, glxContext); } From ff5c3b9e0a37876a6f11f5704d725eb4440928c0 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 02:01:23 +0200 Subject: [PATCH 17/48] GPU Wayland Display: Support pixel-precise rendering --- GPU/GPUTracking/display/CMakeLists.txt | 4 +- .../frontend/GPUDisplayFrontendWayland.cxx | 62 ++++++++++++++++--- .../frontend/GPUDisplayFrontendWayland.h | 16 ++++- dependencies/FindO2GPUWayland.cmake | 2 +- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index ab15cd429963a..98ebfe075b432 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -78,9 +78,9 @@ if(GPUCA_EVENT_DISPLAY_VULKAN) if(GPUCA_EVENT_DISPLAY_WAYLAND) set(WAYLAND_PROTOCOL_HEADERS) set(WAYLAND_PROTOCOL_SOURCES) - foreach(xml stable/xdg-shell/xdg-shell.xml unstable/xdg-decoration/xdg-decoration-unstable-v1.xml) + foreach(xml stable/xdg-shell/xdg-shell.xml stable/viewporter/viewporter.xml unstable/xdg-decoration/xdg-decoration-unstable-v1.xml staging/fractional-scale/fractional-scale-v1.xml) get_filename_component(name ${xml} NAME_WE) - string(REPLACE "-unstable-v1" "" name ${name}) + string(REGEX REPLACE "(-unstable)?-v[0-9]+$" "" name ${name}) set(header ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-client-protocol.h) set(source ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-protocol.c) add_custom_command( diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 3094fe87284af..3c13b0cd60c46 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include "xdg-shell-client-protocol.h" #include "xdg-decoration-client-protocol.h" +#include "viewporter-client-protocol.h" #include #include @@ -353,6 +355,10 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() wl_seat_add_listener(me->mSeat, &me->mSeatListener, me); } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { me->mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { + me->mViewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, 1); + } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { + me->mFractionalScaleManager = (wp_fractional_scale_manager_v1*)wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1); } }; auto registry_global_remove = [](void* data, wl_registry* registry, uint32_t name) { @@ -416,6 +422,18 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() }; wl_surface_add_listener(mSurface, &mSurfaceListener, this); + if (mViewporter && mFractionalScaleManager) { + auto fractional_scale_preferred_scale = [](void* data, wp_fractional_scale_v1* fractional_scale, uint32_t scale) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + me->mFractionalScale120 = scale; + me->updateScale(); + }; + mFractionalScaleListener = wp_fractional_scale_v1_listener{.preferred_scale = fractional_scale_preferred_scale}; + mViewport = wp_viewporter_get_viewport(mViewporter, mSurface); + mFractionalScale = wp_fractional_scale_manager_v1_get_fractional_scale(mFractionalScaleManager, mSurface); + wp_fractional_scale_v1_add_listener(mFractionalScale, &mFractionalScaleListener, this); + } + mXdgSurface = xdg_wm_base_get_xdg_surface(mXdgBase, mSurface); if (mXdgSurface == nullptr) { throw std::runtime_error("Error creating wayland xdg surface"); @@ -594,9 +612,25 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() zxdg_toplevel_decoration_v1_destroy(mXdgDecoration); mXdgDecoration = nullptr; } + if (mFractionalScale) { + wp_fractional_scale_v1_destroy(mFractionalScale); + mFractionalScale = nullptr; + } + if (mViewport) { + wp_viewport_destroy(mViewport); + mViewport = nullptr; + } xdg_toplevel_destroy(mXdgToplevel); xdg_surface_destroy(mXdgSurface); wl_surface_destroy(mSurface); + if (mFractionalScaleManager) { + wp_fractional_scale_manager_v1_destroy(mFractionalScaleManager); + mFractionalScaleManager = nullptr; + } + if (mViewporter) { + wp_viewporter_destroy(mViewporter); + mViewporter = nullptr; + } if (mDecManager) { zxdg_decoration_manager_v1_destroy(mDecManager); mDecManager = nullptr; @@ -665,25 +699,37 @@ GPUDisplayFrontendWayland::OutputInfo* GPUDisplayFrontendWayland::findOutput(wl_ void GPUDisplayFrontendWayland::updateScale() { - int32_t scale = mPreferredScale; - if (scale == 0) { - scale = 1; - for (auto* output : mSurfaceOutputs) { - if (auto* o = findOutput(output)) { - scale = std::max(scale, o->scale); + double scale; + if (mFractionalScale) { + scale = mFractionalScale120 ? mFractionalScale120 / 120. : 1.; + } else { + int32_t intScale = mPreferredScale; + if (intScale == 0) { + intScale = 1; + for (auto* output : mSurfaceOutputs) { + if (auto* o = findOutput(output)) { + intScale = std::max(intScale, o->scale); + } } } + scale = intScale; } if (scale != mScale && mSurface) { mScale = scale; - wl_surface_set_buffer_scale(mSurface, mScale); // Applied with the next commit, i.e. the next present, which happens after the swapchain was recreated with the new size by ResizeScene + if (!mFractionalScale) { + wl_surface_set_buffer_scale(mSurface, (int32_t)mScale); // Applied with the next commit, i.e. the next present, which happens after the swapchain was recreated with the new size by ResizeScene + } updateSize(); } } void GPUDisplayFrontendWayland::updateSize() { - const int32_t width = mWidthLogical * mScale, height = mHeightLogical * mScale; + if (mViewport) { + wp_viewport_set_destination(mViewport, mWidthLogical, mHeightLogical); // Surface size in logical coordinates, independent of the buffer size, applied with the next present + } + const int32_t width = mFractionalScale ? (int32_t)std::lround(mWidthLogical * mScale) : (mWidthLogical * (int32_t)mScale); + const int32_t height = mFractionalScale ? (int32_t)std::lround(mHeightLogical * mScale) : (mHeightLogical * (int32_t)mScale); if (width != mDisplayWidth || height != mDisplayHeight) { if (mDisplayRunning) { ResizeScene(width, height); // Swapchain is recreated by the backend, Vulkan WSI commits the new buffer diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 5c4668da3369e..03fc40869a01e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -19,10 +19,13 @@ #include #include #include "xdg-shell-client-protocol.h" +#include "fractional-scale-client-protocol.h" #include #include struct zxdg_decoration_manager_v1; +struct wp_viewporter; +struct wp_viewport; struct zxdg_toplevel_decoration_v1; struct xkb_context; struct xkb_keymap; @@ -49,7 +52,7 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend private: int32_t FrontendMain() override; int32_t GetKey(uint32_t key, uint32_t state); - void updateScale(); // Buffer scale from wl_surface.preferred_buffer_scale, or max scale of the outputs the surface is on (older compositors) + 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 void reportError(const char* where); // Print the Wayland display error (protocol error with interface and object, or errno) after a call returned -1 void handleKeyRepeat(); // Call HandleKey for the held key according to wl_keyboard.repeat_info @@ -88,6 +91,7 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend xdg_wm_base_listener mXdgWmBaseListener{}; xdg_surface_listener mXdgSurfaceListener{}; xdg_toplevel_listener mXdgToplevelListener{}; + wp_fractional_scale_v1_listener mFractionalScaleListener{}; std::vector mOutputs; std::vector mSurfaceOutputs; // Outputs the surface is on, most recently entered last @@ -100,6 +104,11 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend zxdg_decoration_manager_v1* mDecManager = nullptr; zxdg_toplevel_decoration_v1* mXdgDecoration = nullptr; + wp_viewporter* mViewporter = nullptr; // Fractional scaling (if both are available): buffer scale 1, buffer at physical size, viewport destination = logical size + wp_fractional_scale_manager_v1* mFractionalScaleManager = nullptr; + wp_viewport* mViewport = nullptr; + wp_fractional_scale_v1* mFractionalScale = nullptr; + int32_t mWidthRequested = 0; int32_t mHeightRequested = 0; int32_t mWidthLogical = INIT_WIDTH; @@ -109,8 +118,9 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend bool mStateFullScreenOrMaximized = false; int32_t mBoundsWidth = 0; int32_t mBoundsHeight = 0; - int32_t mScale = 1; - int32_t mPreferredScale = 0; // 0 = not sent by compositor + double mScale = 1; // Physical pixels per logical pixel + int32_t mPreferredScale = 0; // Integer scale from wl_surface.preferred_buffer_scale, 0 = not sent by compositor + uint32_t mFractionalScale120 = 0; // Scale * 120 from wp_fractional_scale_v1, 0 = not sent yet bool mSuspended = false; bool mCanFullscreen = true; bool mCanMaximize = true; diff --git a/dependencies/FindO2GPUWayland.cmake b/dependencies/FindO2GPUWayland.cmake index cc13848df9616..b9d5de52f7552 100644 --- a/dependencies/FindO2GPUWayland.cmake +++ b/dependencies/FindO2GPUWayland.cmake @@ -25,7 +25,7 @@ find_program(${PKGNAME}_SCANNER wayland-scanner) find_package(PkgConfig) if(PkgConfig_FOUND) pkg_get_variable(${PKGNAME}_PROTOCOLS_DIR wayland-protocols pkgdatadir) - if(NOT ${PKGNAME}_PROTOCOLS_DIR OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") + if(NOT ${PKGNAME}_PROTOCOLS_DIR OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/stable/viewporter/viewporter.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/staging/fractional-scale/fractional-scale-v1.xml") message(STATUS "wayland xdg protocol missing in ${${PKGNAME}_PROTOCOLS_DIR}") unset(${PKGNAME}_PROTOCOLS_DIR) endif() From 87763e68ec2fcf33917c84c0e92dc560ccea27d1 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 02:17:46 +0200 Subject: [PATCH 18/48] GPU Display: Render also text in X11 and GLFW at correct size taking into accound HiDPI settings --- .../frontend/GPUDisplayFrontendGlfw.cxx | 49 ++++++++++++++++--- .../display/frontend/GPUDisplayFrontendGlfw.h | 7 +++ .../frontend/GPUDisplayFrontendX11.cxx | 20 ++++++-- .../display/frontend/GPUDisplayFrontendX11.h | 2 + 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 7a14369e7395f..836ab53bedb84 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -227,11 +227,17 @@ void GPUDisplayFrontendGlfw::scroll_callback(GLFWwindow* window, double x, doubl void GPUDisplayFrontendGlfw::cursorPos_callback(GLFWwindow* window, double x, double y) { - me->mMouseMvX = x; - me->mMouseMvY = y; + me->mMouseMvX = x * me->mPixelRatio; // Window coordinates to framebuffer pixels + me->mMouseMvY = y * me->mPixelRatio; } -void GPUDisplayFrontendGlfw::resize_callback(GLFWwindow* window, int32_t width, int32_t height) { me->ResizeScene(width, height); } +void GPUDisplayFrontendGlfw::resize_callback(GLFWwindow* window, int32_t width, int32_t height) // Framebuffer size in pixels +{ + int32_t windowWidth, windowHeight; + glfwGetWindowSize(window, &windowWidth, &windowHeight); + me->mPixelRatio = windowWidth ? (double)width / windowWidth : 1.; + me->ResizeScene(width, height); +} void GPUDisplayFrontendGlfw::focus_callback(GLFWwindow* window, int32_t focused) { @@ -240,7 +246,22 @@ void GPUDisplayFrontendGlfw::focus_callback(GLFWwindow* window, int32_t focused) } } -void GPUDisplayFrontendGlfw::maximize_callback(GLFWwindow* window, int32_t maximized) { me->setWindowState(glfwGetWindowMonitor(window) != nullptr, maximized); } +void GPUDisplayFrontendGlfw::contentScale_callback(GLFWwindow* window, float xscale, float yscale) +{ + me->mScale = xscale; +} + +void GPUDisplayFrontendGlfw::maximize_callback(GLFWwindow* window, int32_t maximized) +{ + me->setWindowState(glfwGetWindowMonitor(window) != nullptr, maximized); + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { // Called before the resize. GLFW keeps the maximized size if the compositor leaves the size to the client when unmaximizing, so restore the last windowed size + if (maximized) { + glfwGetWindowSize(window, &me->mWindowedWidth, &me->mWindowedHeight); + } else { + me->mRestoreWindowedSize = true; // Must be applied after GLFW has processed the configure event, otherwise GLFW resizes to the pending maximized size again + } + } +} #ifndef GPUCA_STANDALONE void GPUDisplayFrontendGlfw::DisplayLoop() @@ -273,6 +294,7 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, mBackend->CoreProfile() ? GLFW_OPENGL_CORE_PROFILE : GLFW_OPENGL_COMPAT_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); if (!mWindow) { fprintf(stderr, "Error creating glfw window\n"); @@ -288,10 +310,19 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwSetMouseButtonCallback(mWindow, mouseButton_callback); glfwSetScrollCallback(mWindow, scroll_callback); glfwSetCursorPosCallback(mWindow, cursorPos_callback); - glfwSetWindowSizeCallback(mWindow, resize_callback); + glfwSetFramebufferSizeCallback(mWindow, resize_callback); glfwSetWindowMaximizeCallback(mWindow, maximize_callback); glfwSetWindowFocusCallback(mWindow, focus_callback); - glfwSetWindowSizeLimits(mWindow, MIN_WIDTH, MIN_HEIGHT, GLFW_DONT_CARE, GLFW_DONT_CARE); + float xscale, yscale; + glfwGetWindowContentScale(mWindow, &xscale, &yscale); + contentScale_callback(mWindow, xscale, yscale); + glfwSetWindowContentScaleCallback(mWindow, contentScale_callback); + const float limitScale = (glfwGetPlatform() == GLFW_PLATFORM_X11 || glfwGetPlatform() == GLFW_PLATFORM_WIN32) ? mScale : 1.f; // Window coordinates are pixels on X11 / Win32, logical on Wayland / macOS + glfwSetWindowSizeLimits(mWindow, MIN_WIDTH * limitScale, MIN_HEIGHT * limitScale, GLFW_DONT_CARE, GLFW_DONT_CARE); + int32_t fbWidth, fbHeight, windowWidth, windowHeight; + glfwGetFramebufferSize(mWindow, &fbWidth, &fbHeight); + glfwGetWindowSize(mWindow, &windowWidth, &windowHeight); + mPixelRatio = windowWidth ? (double)fbWidth / windowWidth : 1.; // Updated in resize_callback, which must not be called before InitDisplay applyInitialWindowState(); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glfwSwapInterval(1); @@ -322,6 +353,12 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } waitForFPSLimit(); glfwPollEvents(); + if (mRestoreWindowedSize) { + mRestoreWindowedSize = false; + if (mWindowedWidth > 0 && mWindowedHeight > 0 && !glfwGetWindowAttrib(mWindow, GLFW_MAXIMIZED)) { + glfwSetWindowSize(mWindow, mWindowedWidth, mWindowedHeight); + } + } } ExitDisplay(); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 922b0166404d6..77b27f48c32a5 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -35,6 +35,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend void OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton = true) override; bool EnableSendKey() override; void getSize(int32_t& width, int32_t& height) override; + float getDisplayScale() const override { return mScale; } int32_t getVulkanSurface(void* instance, void* surface) override; uint32_t getReqVulkanExtensions(const char**& p) override; @@ -51,10 +52,16 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend static void resize_callback(GLFWwindow* mWindow, int32_t width, int32_t height); static void maximize_callback(GLFWwindow* mWindow, int32_t maximized); static void focus_callback(GLFWwindow* mWindow, int32_t focused); + static void contentScale_callback(GLFWwindow* mWindow, float xscale, float yscale); static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t scancode, int32_t mods, int32_t& keyOut, int32_t& keyPressOut); GLFWwindow* mWindow; + float mScale = 1.f; // Content scale for text / UI + int32_t mWindowedWidth = 0; // Window size before maximizing (Wayland) + int32_t mWindowedHeight = 0; + bool mRestoreWindowedSize = false; + double mPixelRatio = 1.; // Framebuffer pixels per window coordinate (1 on X11 / Win32, scale on Wayland / macOS) pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; int32_t mWindowX = 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 616311094f649..6bdce427bf518 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -24,6 +24,7 @@ #include #include +#include #ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN #include @@ -202,6 +203,19 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (-1); } + XrmInitialize(); + if (const char* resources = XResourceManagerString(mXDisplay)) { + XrmDatabase db = XrmGetStringDatabase(resources); + char* type = nullptr; + XrmValue value; + if (db && XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && value.addr && atof(value.addr) > 0) { + mScale = atof(value.addr) / 96.; + } + if (db) { + XrmDestroyDatabase(db); + } + } + // Make sure OpenGL's GLX extension supported if (!glXQueryExtension(mXDisplay, &errorBase, &eventBase)) { GPUError("X server has no OpenGL GLX extension"); @@ -264,13 +278,13 @@ int32_t GPUDisplayFrontendX11::FrontendMain() windowAttributes.event_mask = ExposureMask | VisibilityChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | SubstructureNotifyMask | FocusChangeMask | PropertyChangeMask; // Create an X window with the selected visual - mWindow = XCreateWindow(mXDisplay, win, 50, 50, INIT_WIDTH, INIT_HEIGHT, // Position / Width and height of window + mWindow = XCreateWindow(mXDisplay, win, 50, 50, INIT_WIDTH * mScale, INIT_HEIGHT * mScale, // Position / Width and height of window 0, visualInfo->depth, InputOutput, visualInfo->visual, CWBorderPixel | CWColormap | CWEventMask, &windowAttributes); XSetStandardProperties(mXDisplay, mWindow, DISPLAY_WINDOW_NAME, DISPLAY_WINDOW_NAME, None, nullptr, 0, nullptr); XSizeHints sizeHints{}; sizeHints.flags = PMinSize; - sizeHints.min_width = MIN_WIDTH; - sizeHints.min_height = MIN_HEIGHT; + sizeHints.min_width = MIN_WIDTH * mScale; + sizeHints.min_height = MIN_HEIGHT * mScale; XSetWMNormalHints(mXDisplay, mWindow, &sizeHints); if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { glXMakeCurrent(mXDisplay, mWindow, glxContext); diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index 35a9f12a7b5a3..294e1b7174221 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -36,6 +36,7 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend 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; uint32_t getReqVulkanExtensions(const char**& p) override; @@ -51,6 +52,7 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend Display* mXDisplay = nullptr; Window mWindow; void updateWindowState(); + float mScale = 1.f; // From the Xft.dpi X resource (desktop scaling, e.g. KDE with "Apply scaling themselves" for X11 apps) PFNGLXSWAPINTERVALEXTPROC mGlXSwapIntervalEXT = nullptr; bool vsync_supported = false; From 4b3fdc58f1887e4257cb71ffe36c9cff611a734d Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 10:51:40 +0200 Subject: [PATCH 19/48] GPU Display: Fix a data race and clean up lambda captures --- .../display/render/GPUDisplayDraw.cxx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx index 523a8d8b71dd7..9a6283abc5b8d 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx @@ -398,7 +398,7 @@ void GPUDisplay::DrawFinal(int32_t iSector, int32_t /*iCol*/, const GPUTPCGMProp } // Print TRD part of track - auto tmpDoTRDTracklets = [&](const auto& trk) { + auto tmpDoTRDTracklets = [this, &drawing, &lastSide, iSector](const auto& trk) { for (int32_t k = 5; k >= 0; k--) { int32_t cid = trk.getTrackletIndex(k); if (cid < 0) { @@ -817,9 +817,9 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() } int32_t numThreads = getNumThreads(); - tbb::task_arena(numThreads).execute([&] { + tbb::task_arena(numThreads).execute([this, &timer, numThreads] { if (mChain && (mChain->GetRecoSteps() & gpudatatypes::RecoStep::TPCSectorTracking)) { - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { GPUTPCTracker& tracker = (GPUTPCTracker&)sectorTracker(iSector); tracker.SetPointersDataLinks(tracker.LinkTmpMemory()); mGlDLLines[iSector][tINITLINK] = DrawLinks(tracker, tINITLINK, true); @@ -829,7 +829,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Links:\t\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { const GPUTPCTracker& tracker = sectorTracker(iSector); mGlDLLines[iSector][tLINK] = DrawLinks(tracker, tLINK); @@ -845,7 +845,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Seeds:\t\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { const GPUTPCTracker& tracker = sectorTracker(iSector); mGlDLLines[iSector][tEXTRAPOLATEDTRACK] = DrawTracks(tracker, 1); // clang-format off }, tbb::simple_partitioner()); // clang-format on @@ -853,7 +853,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Sector Tracks:\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } } - tbb::parallel_for(0, numThreads, [&](int32_t iThread) { + tbb::parallel_for(0, numThreads, [this](int32_t iThread) { mThreadTracks[iThread].resize(mNCollissions); for (int32_t i = 0; i < mNCollissions; i++) { for (int32_t j = 0; j < NSECTORS; j++) { @@ -865,9 +865,9 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() }, tbb::simple_partitioner()); // clang-format on if (mConfig.showTPCTracksFromO2Format) { #ifndef GPUCA_RUN2 - uint32_t col = 0; - tbb::parallel_for(0, mIOPtrs->nOutputTracksTPCO2, [&](auto i) { + tbb::parallel_for(0, mIOPtrs->nOutputTracksTPCO2, [this](auto i) { uint8_t sector, row; + uint32_t col = 0; if (mIOPtrs->clustersNative) { mIOPtrs->outputTracksTPCO2[i].getCluster(mIOPtrs->outputClusRefsTPCO2, 0, *mIOPtrs->clustersNative, sector, row); } else { @@ -880,7 +880,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() }); #endif } else { - tbb::parallel_for(0, mIOPtrs->nMergedTracks, [&](auto i) { + tbb::parallel_for(0, mIOPtrs->nMergedTracks, [this](auto i) { const GPUTPCGMMergedTrack* track = &mIOPtrs->mergedTracks[i]; if (track->NClusters() == 0) { return; @@ -904,7 +904,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() }); } for (uint32_t col = 0; col < mIOPtrs->nMCInfosTPCCol; col++) { - tbb::parallel_for(mIOPtrs->mcInfosTPCCol[col].first, mIOPtrs->mcInfosTPCCol[col].first + mIOPtrs->mcInfosTPCCol[col].num, [&](uint32_t i) { + tbb::parallel_for(mIOPtrs->mcInfosTPCCol[col].first, mIOPtrs->mcInfosTPCCol[col].first + mIOPtrs->mcInfosTPCCol[col].num, [this, col](uint32_t i) { const GPUTPCMCInfo& mc = mIOPtrs->mcInfosTPC[i]; if (mc.charge == 0.f) { return; @@ -933,7 +933,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() prop.SetMaterialTPC(); prop.SetPolynomialField(&mParam->polynomialField); - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this, numThreads, prop](int32_t iSector) { int32_t numThread = GPUReconstruction::getHostThreadIndex(); for (int32_t iCol = 0; iCol < mNCollissions; iCol++) { mThreadBuffers[numThread].clear(); @@ -959,14 +959,14 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() GPUInfo("Display Time: Vertex Merged Tracks:\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } - tbb::parallel_for(0, NSECTORS, [&](int32_t iSector) { + tbb::parallel_for(0, NSECTORS, [this](int32_t iSector) { DrawClusters(iSector); // clang-format off }, tbb::simple_partitioner()); // clang-format on if (timer.IsRunning()) { GPUInfo("Display Time: Vertex Clusters:\t\t\t%6.0f us", timer.GetCurrentElapsedTime(true) * 1e6); } }); - // End omp parallel + // End tbb task arena mGlDLFinalITS = DrawFinalITS(); for (int32_t iSector = 0; iSector < NSECTORS; iSector++) { From 0d775b108959deab3646ac3e585dd8968e1580c1 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 14:03:47 +0200 Subject: [PATCH 20/48] GPU Display: Don't draw TOF and ITS hits multiple times --- GPU/GPUTracking/display/GPUDisplay.cxx | 6 ++++-- GPU/GPUTracking/display/render/GPUDisplayDraw.cxx | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 7cad25916940a..2d7e43ec2b891 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -465,12 +465,14 @@ void GPUDisplay::DrawGLScene_drawCommands() if (mCfgL.drawTOF) { SetColorTOF(); mBackend->pointSizeFactor(2); - LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[0][tTOFCLUSTER][0], GPUDisplayBackend::POINTS)); + // LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[iSector][tTOFCLUSTER][iCol], GPUDisplayBackend::POINTS)); + drawVertices(mGlDLPoints[0][tTOFCLUSTER][0], GPUDisplayBackend::POINTS); mBackend->pointSizeFactor(1); } if (mCfgL.drawITS) { SetColorITS(); - LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[0][tITSCLUSTER][0], GPUDisplayBackend::POINTS)); + // LOOP_SECTOR LOOP_COLLISION_COL(drawVertices(mGlDLPoints[iSector][tITSCLUSTER][iCol], GPUDisplayBackend::POINTS)); + drawVertices(mGlDLPoints[0][tITSCLUSTER][0], GPUDisplayBackend::POINTS); } if (mCfgL.drawTPC) { SetColorClusters(); diff --git a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx index 9a6283abc5b8d..9b9c922db02b2 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx @@ -973,6 +973,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() for (int32_t i = N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD + N_POINTS_TYPE_TOF; i < N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD + N_POINTS_TYPE_TOF + N_POINTS_TYPE_ITS; i++) { for (int32_t iCol = 0; iCol < mNCollissions; iCol++) { mGlDLPoints[iSector][i][iCol] = DrawSpacePointsITS(iSector, i, iCol); + break; // TODO: Only collision 0 filled for now } } break; // TODO: Only sector 0 filled for now @@ -997,6 +998,7 @@ size_t GPUDisplay::DrawGLScene_updateVertexList() for (int32_t i = N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD; i < N_POINTS_TYPE_TPC + N_POINTS_TYPE_TRD + N_POINTS_TYPE_TOF; i++) { for (int32_t iCol = 0; iCol < mNCollissions; iCol++) { mGlDLPoints[iSector][i][iCol] = DrawSpacePointsTOF(iSector, i, iCol); + break; // TODO: Only collision 0 filled for now } } break; // TODO: Only sector 0 filled for now From 7e2daa462204c631e04553bc64cca2764dd5f016 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 14:11:22 +0200 Subject: [PATCH 21/48] GPU Wayland Display: set mouse cursor to default instead of being undefined --- GPU/GPUTracking/display/CMakeLists.txt | 2 +- .../frontend/GPUDisplayFrontendWayland.cxx | 18 ++++++++++++++++++ .../frontend/GPUDisplayFrontendWayland.h | 4 ++++ dependencies/FindO2GPUWayland.cmake | 16 ++++++++++++---- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 98ebfe075b432..6c617a5b75e03 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -78,7 +78,7 @@ if(GPUCA_EVENT_DISPLAY_VULKAN) if(GPUCA_EVENT_DISPLAY_WAYLAND) set(WAYLAND_PROTOCOL_HEADERS) set(WAYLAND_PROTOCOL_SOURCES) - foreach(xml stable/xdg-shell/xdg-shell.xml stable/viewporter/viewporter.xml unstable/xdg-decoration/xdg-decoration-unstable-v1.xml staging/fractional-scale/fractional-scale-v1.xml) + foreach(xml ${O2GPUWayland_PROTOCOLS}) # List and existence check in dependencies/FindO2GPUWayland.cmake get_filename_component(name ${xml} NAME_WE) string(REGEX REPLACE "(-unstable)?-v[0-9]+$" "" name ${name}) set(header ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-client-protocol.h) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 3c13b0cd60c46..2af8513b7c4c4 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -39,6 +39,7 @@ #include "xdg-shell-client-protocol.h" #include "xdg-decoration-client-protocol.h" #include "viewporter-client-protocol.h" +#include "cursor-shape-client-protocol.h" #include #include @@ -157,6 +158,13 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } auto pointer_enter = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; + if (!me->mCursorShapeDevice && me->mCursorShapeManager) { + me->mCursorShapeDevice = wp_cursor_shape_manager_v1_get_pointer(me->mCursorShapeManager, wl_pointer); + } + if (me->mCursorShapeDevice) { + wp_cursor_shape_device_v1_set_shape(me->mCursorShapeDevice, serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + } }; auto pointer_leave = [](void* data, wl_pointer* wl_pointer, uint32_t serial, wl_surface* wl_surface) { GPUDisplayFrontendWayland* me = (GPUDisplayFrontendWayland*)data; @@ -355,6 +363,8 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() wl_seat_add_listener(me->mSeat, &me->mSeatListener, me); } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { me->mDecManager = (zxdg_decoration_manager_v1*)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + me->mCursorShapeManager = (wp_cursor_shape_manager_v1*)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1); } else if (strcmp(interface, wp_viewporter_interface.name) == 0) { me->mViewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, 1); } else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) { @@ -600,6 +610,14 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() mDisplayRunning = false; pthread_mutex_unlock(&mSemLockExit); + if (mCursorShapeDevice) { + wp_cursor_shape_device_v1_destroy(mCursorShapeDevice); + mCursorShapeDevice = nullptr; + } + if (mCursorShapeManager) { + wp_cursor_shape_manager_v1_destroy(mCursorShapeManager); + mCursorShapeManager = nullptr; + } if (mPointer) { mSeatVersion >= WL_POINTER_RELEASE_SINCE_VERSION ? wl_pointer_release(mPointer) : wl_pointer_destroy(mPointer); mPointer = nullptr; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 03fc40869a01e..01af3dc49631c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -26,6 +26,8 @@ struct zxdg_decoration_manager_v1; struct wp_viewporter; struct wp_viewport; +struct wp_cursor_shape_manager_v1; +struct wp_cursor_shape_device_v1; struct zxdg_toplevel_decoration_v1; struct xkb_context; struct xkb_keymap; @@ -106,6 +108,8 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend wp_viewporter* mViewporter = nullptr; // Fractional scaling (if both are available): buffer scale 1, buffer at physical size, viewport destination = logical size wp_fractional_scale_manager_v1* mFractionalScaleManager = nullptr; + wp_cursor_shape_manager_v1* mCursorShapeManager = nullptr; // Sets the default cursor on pointer enter, otherwise the cursor image is undefined + wp_cursor_shape_device_v1* mCursorShapeDevice = nullptr; wp_viewport* mViewport = nullptr; wp_fractional_scale_v1* mFractionalScale = nullptr; diff --git a/dependencies/FindO2GPUWayland.cmake b/dependencies/FindO2GPUWayland.cmake index b9d5de52f7552..7ada679c97ce1 100644 --- a/dependencies/FindO2GPUWayland.cmake +++ b/dependencies/FindO2GPUWayland.cmake @@ -25,10 +25,18 @@ find_program(${PKGNAME}_SCANNER wayland-scanner) find_package(PkgConfig) if(PkgConfig_FOUND) pkg_get_variable(${PKGNAME}_PROTOCOLS_DIR wayland-protocols pkgdatadir) - if(NOT ${PKGNAME}_PROTOCOLS_DIR OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/stable/viewporter/viewporter.xml" OR NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/staging/fractional-scale/fractional-scale-v1.xml") - message(STATUS "wayland xdg protocol missing in ${${PKGNAME}_PROTOCOLS_DIR}") - unset(${PKGNAME}_PROTOCOLS_DIR) - endif() + set(${PKGNAME}_PROTOCOLS stable/xdg-shell/xdg-shell.xml + stable/viewporter/viewporter.xml + stable/tablet/tablet-v2.xml + unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + staging/fractional-scale/fractional-scale-v1.xml + staging/cursor-shape/cursor-shape-v1.xml) + foreach(xml ${${PKGNAME}_PROTOCOLS}) + if(${PKGNAME}_PROTOCOLS_DIR AND NOT EXISTS "${${PKGNAME}_PROTOCOLS_DIR}/${xml}") + message(STATUS "wayland protocol ${xml} missing in ${${PKGNAME}_PROTOCOLS_DIR}") + unset(${PKGNAME}_PROTOCOLS_DIR) + endif() + endforeach() endif() include(FindPackageHandleStandardArgs) From c4190e21ee8560f41e34b5f90f8699f9fedbc221 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 15:08:37 +0200 Subject: [PATCH 22/48] GPU Vulkan Display: Improve device initialization --- .../backend/GPUDisplayBackendVulkan.cxx | 29 ++++++++++++------- .../display/backend/GPUDisplayBackendVulkan.h | 3 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 0f968c9c539f4..fe4eeb6dd26c2 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -273,7 +273,7 @@ static inline void clearVector(T& v, S func, bool downsize = true) // ---------------------------- VULKAN DEVICE MANAGEMENT ---------------------------- -double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions) +double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, bool silent) { double score = -1.; vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); @@ -287,18 +287,20 @@ double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std bool found = false; for (uint32_t i = 0; i < queueFamilies.size(); i++) { if (!(queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics)) { - return (-1); + continue; } vk::Bool32 presentSupport = device.getSurfaceSupportKHR(i, mSurface); if (!presentSupport) { - return (-1); + continue; } mGraphicsFamily = i; found = true; break; } if (!found) { - GPUInfo("%s ignored due to missing queue properties", &deviceProperties.deviceName[0]); + if (!silent) { + GPUInfo("%s ignored due to missing queue properties", &deviceProperties.deviceName[0]); + } return (-1); } @@ -313,13 +315,17 @@ double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std } } if (extensionsFound < reqDeviceExtensions.size()) { - GPUInfo("%s ignored due to missing extensions", &deviceProperties.deviceName[0]); + if (!silent) { + GPUInfo("%s ignored due to missing extensions", &deviceProperties.deviceName[0]); + } return (-1); } updateSwapChainDetails(device); if (mSwapChainDetails.formats.empty() || mSwapChainDetails.presentModes.empty()) { - GPUInfo("%s ignored due to incompatible swap chain", &deviceProperties.deviceName[0]); + if (!silent) { + GPUInfo("%s ignored due to incompatible swap chain", &deviceProperties.deviceName[0]); + } return (-1); } @@ -454,21 +460,22 @@ void GPUDisplayBackendVulkan::createDevice() if (!mPhysicalDevice) { throw std::runtime_error("All available Vulkan devices unsuited"); } + if (checkDevice(mPhysicalDevice, reqDeviceExtensions, true) <= 0) { + throw std::runtime_error("Selected vulkan device is incompatible"); + } updateSwapChainDetails(mPhysicalDevice); vk::PhysicalDeviceProperties deviceProperties = mPhysicalDevice.getProperties(); vk::PhysicalDeviceFeatures deviceFeatures = mPhysicalDevice.getFeatures(); vk::FormatProperties depth32FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32Sfloat); vk::FormatProperties depth64FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32SfloatS8Uint); - vk::FormatProperties formatProperties = mPhysicalDevice.getFormatProperties(mSurfaceFormat.format); GPUInfo("Using physical Vulkan device %s", &deviceProperties.deviceName[0]); mMaxMSAAsupported = getMaxUsableSampleCount(deviceProperties); mZSupported = (bool)(depth32FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); mStencilSupported = (bool)(depth64FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); - mCubicFilterSupported = (bool)(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterCubicEXT); 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, Cubic Filtering %d, Mailbox present mode %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mCubicFilterSupported, (int32_t)mailboxSupported); + GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported); } vk::DeviceQueueCreateInfo queueCreateInfo{}; @@ -1893,8 +1900,8 @@ 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 char* c = s; *c; c++) { - if ((int32_t)*c > (int32_t)mFontSymbols.size()) { + 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()); continue; } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index b4bff0b2852af..fc98c540f6910 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -83,7 +83,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend float getYFactor() const override { return -1.0f; } int32_t getMaxMSAA() const override { return mMaxMSAAsupported; } - double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions); + double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, bool silent = false); vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities); void transitionImageLayout(vk::CommandBuffer commandBuffer, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout); VulkanBuffer createBuffer(size_t size, const void* srcData = nullptr, vk::BufferUsageFlags type = vk::BufferUsageFlagBits::eVertexBuffer, int32_t deviceMemory = 1); @@ -208,7 +208,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mZActive = false; bool mZSupported = false; bool mStencilSupported = false; - bool mCubicFilterSupported = false; bool mDownsampleFSAA = false; bool mMixingSupported = 0; From c5b4eb1ae2632d1c29cebdf86478cbf253c4a0da Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 16:10:15 +0200 Subject: [PATCH 23/48] GPU Vulkan Display: Improve requesting device features --- .../backend/GPUDisplayBackendVulkan.cxx | 32 ++++++++++++++----- .../display/backend/GPUDisplayBackendVulkan.h | 2 +- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index fe4eeb6dd26c2..80bb794a6d9d1 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -273,13 +273,29 @@ static inline void clearVector(T& v, S func, bool downsize = true) // ---------------------------- VULKAN DEVICE MANAGEMENT ---------------------------- -double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, bool silent) +static bool hasFeatures(const vk::PhysicalDeviceFeatures& available, const vk::PhysicalDeviceFeatures& required) // The struct consists only of VkBool32 members +{ + static_assert(sizeof(vk::PhysicalDeviceFeatures) % sizeof(vk::Bool32) == 0); + const vk::Bool32* a = reinterpret_cast(&available); + const vk::Bool32* r = reinterpret_cast(&required); + for (size_t i = 0; i < sizeof(vk::PhysicalDeviceFeatures) / sizeof(vk::Bool32); i++) { + if (r[i] && !a[i]) { + return false; + } + } + return true; +} + +double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const vk::PhysicalDeviceFeatures& reqDeviceFeatures, bool silent) { double score = -1.; vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); vk::PhysicalDeviceFeatures deviceFeatures = device.getFeatures(); vk::PhysicalDeviceMemoryProperties memoryProperties = device.getMemoryProperties(); - if (!deviceFeatures.geometryShader || !deviceFeatures.wideLines || !deviceFeatures.largePoints) { + if (!hasFeatures(deviceFeatures, reqDeviceFeatures)) { + if (!silent) { + GPUInfo("%s ignored due to missing features", &deviceProperties.deviceName[0]); + } return (-1); } @@ -431,8 +447,9 @@ void GPUDisplayBackendVulkan::createDevice() throw std::runtime_error("Frontend does not provide Vulkan surface"); } - const std::vector reqDeviceExtensions = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + const std::vector reqDeviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + vk::PhysicalDeviceFeatures reqDeviceFeatures{}; + reqDeviceFeatures.geometryShader = reqDeviceFeatures.wideLines = reqDeviceFeatures.largePoints = VK_TRUE; mPhysicalDevice = VkPhysicalDevice(VK_NULL_HANDLE); std::vector devices = mInstance.enumeratePhysicalDevices(); @@ -441,7 +458,7 @@ void GPUDisplayBackendVulkan::createDevice() } double bestScore = -1.; for (uint32_t i = 0; i < devices.size(); i++) { - double score = checkDevice(devices[i], reqDeviceExtensions); + double score = checkDevice(devices[i], reqDeviceExtensions, reqDeviceFeatures); if (mDisplay->GetProcessingSettings().debugLevel >= 2) { vk::PhysicalDeviceProperties deviceProperties = devices[i].getProperties(); GPUInfo("Available Vulkan device %d: %s - Score %f", i, &deviceProperties.deviceName[0], score); @@ -460,13 +477,12 @@ void GPUDisplayBackendVulkan::createDevice() if (!mPhysicalDevice) { throw std::runtime_error("All available Vulkan devices unsuited"); } - if (checkDevice(mPhysicalDevice, reqDeviceExtensions, true) <= 0) { + if (checkDevice(mPhysicalDevice, reqDeviceExtensions, reqDeviceFeatures, true) <= 0) { throw std::runtime_error("Selected vulkan device is incompatible"); } updateSwapChainDetails(mPhysicalDevice); vk::PhysicalDeviceProperties deviceProperties = mPhysicalDevice.getProperties(); - vk::PhysicalDeviceFeatures deviceFeatures = mPhysicalDevice.getFeatures(); vk::FormatProperties depth32FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32Sfloat); vk::FormatProperties depth64FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32SfloatS8Uint); GPUInfo("Using physical Vulkan device %s", &deviceProperties.deviceName[0]); @@ -486,7 +502,7 @@ void GPUDisplayBackendVulkan::createDevice() vk::DeviceCreateInfo deviceCreateInfo{}; deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; deviceCreateInfo.queueCreateInfoCount = 1; - deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + deviceCreateInfo.pEnabledFeatures = &reqDeviceFeatures; deviceCreateInfo.enabledExtensionCount = static_cast(reqDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = reqDeviceExtensions.data(); mDevice = mPhysicalDevice.createDevice(deviceCreateInfo, nullptr); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index fc98c540f6910..b36a46f3170a1 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -83,7 +83,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend float getYFactor() const override { return -1.0f; } int32_t getMaxMSAA() const override { return mMaxMSAAsupported; } - double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, bool silent = false); + double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const vk::PhysicalDeviceFeatures& reqDeviceFeatures, bool silent = false); vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities); void transitionImageLayout(vk::CommandBuffer commandBuffer, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout); VulkanBuffer createBuffer(size_t size, const void* srcData = nullptr, vk::BufferUsageFlags type = vk::BufferUsageFlagBits::eVertexBuffer, int32_t deviceMemory = 1); From 9665b53e0ffd63e372a58e71a3ba6e699ceae26b Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 20:40:03 +0200 Subject: [PATCH 24/48] GPU Display: Fix memcpy must not be called with nullptr / size=0 input by definition --- GPU/GPUTracking/display/render/GPUDisplayDraw.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx index 9b9c922db02b2..a18b3e3fe2140 100644 --- a/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx +++ b/GPU/GPUTracking/display/render/GPUDisplayDraw.cxx @@ -195,7 +195,9 @@ void GPUDisplay::DrawClusters(int32_t iSector) for (int32_t i = 0; i < N_POINTS_TYPE_TPC; i++) { uint32_t count = vertexCache[iCol][i].size(); mClusterBufferSizeCache[iSector][iCol][i] = std::max(mClusterBufferSizeCache[iSector][iCol][i], count); - memcpy((void*)(mVertexBuffer[iSector].data() + startCountInner), (const void*)vertexCache[iCol][i].data(), count * sizeof(vertexCache[iCol][i][0])); + if (count) { + memcpy((void*)(mVertexBuffer[iSector].data() + startCountInner), (const void*)vertexCache[iCol][i].data(), count * sizeof(vertexCache[iCol][i][0])); + } size_t startCount = mVertexBufferStart[iSector].size(); insertVertexList(iSector, startCountInner, startCountInner + count); startCountInner += count; From f20b6723fb68728f7d077f6bd5c75929b6adf372 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 22:18:55 +0200 Subject: [PATCH 25/48] GPU Display: Fix wait when stopping display, rename some misleading functions, switch from some pthread to std --- GPU/GPUTracking/Global/GPUChainTracking.cxx | 9 ++++-- .../Interface/GPUO2InterfaceDisplay.cxx | 9 ++---- .../Interface/GPUO2InterfaceDisplay.h | 4 +-- GPU/GPUTracking/display/GPUDisplay.cxx | 6 ++++ GPU/GPUTracking/display/GPUDisplay.h | 3 +- GPU/GPUTracking/display/GPUDisplayInterface.h | 3 +- .../display/frontend/GPUDisplayFrontend.cxx | 17 +++++++++++ .../display/frontend/GPUDisplayFrontend.h | 5 ++-- .../frontend/GPUDisplayFrontendGlfw.cxx | 28 +++++++++---------- .../display/frontend/GPUDisplayFrontendGlfw.h | 4 +-- .../frontend/GPUDisplayFrontendGlut.cxx | 24 ++++++++-------- .../display/frontend/GPUDisplayFrontendGlut.h | 5 +--- .../display/frontend/GPUDisplayFrontendNone.h | 1 - .../frontend/GPUDisplayFrontendWayland.cxx | 23 ++++++--------- .../frontend/GPUDisplayFrontendWayland.h | 4 --- .../frontend/GPUDisplayFrontendWindows.cxx | 1 - .../frontend/GPUDisplayFrontendWindows.h | 1 - .../frontend/GPUDisplayFrontendX11.cxx | 23 ++++++--------- .../display/frontend/GPUDisplayFrontendX11.h | 4 --- GPU/Workflow/src/O2GPUDPLDisplay.cxx | 6 ++-- 20 files changed, 86 insertions(+), 94 deletions(-) diff --git a/GPU/GPUTracking/Global/GPUChainTracking.cxx b/GPU/GPUTracking/Global/GPUChainTracking.cxx index 74b311356c164..63bec89446d4e 100644 --- a/GPU/GPUTracking/Global/GPUChainTracking.cxx +++ b/GPU/GPUTracking/Global/GPUChainTracking.cxx @@ -68,7 +68,12 @@ GPUChainTracking::GPUChainTracking(GPUReconstruction* rec, uint32_t maxTPCHits, mFlatObjectsDevice.mChainTracking = this; } -GPUChainTracking::~GPUChainTracking() = default; +GPUChainTracking::~GPUChainTracking() +{ + if (mDisplayRunning) { + GetProcessingSettings().eventDisplay->StopDisplay(); + } +} void GPUChainTracking::RegisterPermanentMemoryAndProcessors() { @@ -867,7 +872,7 @@ int32_t GPUChainTracking::RunChainFinalize() } while (GetProcessingSettings().eventDisplay->getDisplayControl() == 0); if (GetProcessingSettings().eventDisplay->getDisplayControl() == 2) { mDisplayRunning = false; - GetProcessingSettings().eventDisplay->DisplayExit(); + GetProcessingSettings().eventDisplay->StopDisplay(); const_cast(GetProcessingSettings()).eventDisplay = nullptr; // TODO: fixme - eventDisplay should probably not be put into ProcessingSettings in the first place return GPUReconstruction::retValValue::retDoExit; } diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx index 469b26f89bda1..008bb2a1f1079 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.cxx @@ -39,7 +39,7 @@ GPUO2InterfaceDisplay::GPUO2InterfaceDisplay(const GPUO2InterfaceConfiguration* GPUO2InterfaceDisplay::~GPUO2InterfaceDisplay() = default; -int32_t GPUO2InterfaceDisplay::startDisplay() +int32_t GPUO2InterfaceDisplay::StartUnmanagedDisplay() { int32_t retVal = mDisplay->StartDisplay(); if (retVal) { @@ -71,12 +71,9 @@ int32_t GPUO2InterfaceDisplay::show(const GPUTrackingInOutPointers* ptrs) return 0; } -int32_t GPUO2InterfaceDisplay::endDisplay() +int32_t GPUO2InterfaceDisplay::StopUnmanagedDisplay() { - mFrontend->DisplayExit(); - while (mFrontend->IsRunning()) { - usleep(10000); - } + mFrontend->StopDisplay(); return 0; } diff --git a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h index c0946bab0076d..87990adce5ff1 100644 --- a/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h +++ b/GPU/GPUTracking/Interface/GPUO2InterfaceDisplay.h @@ -37,9 +37,9 @@ class GPUO2InterfaceDisplay void UpdateCalib(const GPUCalibObjectsConst* calib); void UpdateGRP(const GPUSettingsGRP* grp); - int32_t startDisplay(); + int32_t StartUnmanagedDisplay(); int32_t show(const GPUTrackingInOutPointers* ptrs); - int32_t endDisplay(); + int32_t StopUnmanagedDisplay(); private: std::unique_ptr mDisplay; diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 2d7e43ec2b891..e7f5580e3f79f 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -734,6 +734,12 @@ void GPUDisplay::ShowNextEvent(const GPUTrackingInOutPointers* ptrs) void GPUDisplay::BlockTillNextEvent() { mSemLockDisplay.Lock(); } +void GPUDisplay::UnblockDisplay() +{ + mSemLockDisplay.Trylock(); + mSemLockDisplay.Unlock(); +} + void GPUDisplay::WaitTillEventShown() { std::unique_lock lock(mMutexLoadAndShowEvent); diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index 006db4bbe5b06..bae898cf38674 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -49,8 +49,10 @@ class GPUDisplay : public GPUDisplayInterface ~GPUDisplay() override = default; int32_t StartDisplay() override; + void ExitDisplay(); void ShowNextEvent(const GPUTrackingInOutPointers* ptrs = nullptr) override; void BlockTillNextEvent() override; + void UnblockDisplay(); void WaitTillEventShown() override; void SetCollisionFirstCluster(uint32_t collision, int32_t sector, int32_t cluster) override; void UpdateCalib(const GPUCalibObjectsConst* calib) override { mCalib = calib; } @@ -60,7 +62,6 @@ class GPUDisplay : public GPUDisplayInterface int32_t DrawGLScene(); void HandleSendKey(int32_t key); int32_t InitDisplay(bool initFailure = false); - void ExitDisplay(); void ResizeScene(int32_t width, int32_t height, bool init = false); const GPUSettingsDisplayRenderer& cfgR() const { return mCfgR; } diff --git a/GPU/GPUTracking/display/GPUDisplayInterface.h b/GPU/GPUTracking/display/GPUDisplayInterface.h index ff83b1cbaa38e..5f5a101233191 100644 --- a/GPU/GPUTracking/display/GPUDisplayInterface.h +++ b/GPU/GPUTracking/display/GPUDisplayInterface.h @@ -56,11 +56,10 @@ class GPUDisplayFrontendInterface public: virtual ~GPUDisplayFrontendInterface(); static GPUDisplayFrontendInterface* getFrontend(const char* type); - virtual void DisplayExit() = 0; + virtual void StopDisplay() = 0; virtual bool EnableSendKey() = 0; virtual int32_t getDisplayControl() const = 0; virtual void setDisplayControl(int32_t v) = 0; - virtual bool IsRunning() const = 0; virtual void sendKey(int32_t v) = 0; virtual const char* frontendName() const = 0; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx index f5bbeef3cf76e..ab6d8162c6d0c 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.cxx @@ -248,3 +248,20 @@ int32_t GPUDisplayFrontend::StartDisplay() #endif return (0); } + +void GPUDisplayFrontend::StopDisplay() +{ + { + std::lock_guard lock(mExitStateMutex); + if (mDisplayRunning) { + mDisplayControl = 2; + StopDisplayA(); + mDisplay->UnblockDisplay(); + } + } + while (mDisplayRunning) { + std::this_thread::sleep_for(std::chrono::microseconds(10000)); // TODO: Signal by semaphore + } +} + +void GPUDisplayFrontend::StopDisplayA() {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index 56fe18c9c223e..d53cd5f1f621e 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -52,8 +52,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface static constexpr int32_t GL_MIN_VERSION_MINOR = 5; int32_t StartDisplay(); // Start the display. This function returns, and should spawn a thread that runs the display, and calls InitDisplay - void DisplayExit() override = 0; // Stop the display. Display thread should call ExitDisplay and then wait that IsRunning() returns false - bool IsRunning() const override { return mDisplayRunning; } + 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 @@ -79,6 +78,7 @@ 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 (no sleep with maxFPSRate or fpsRate <= 0) @@ -121,6 +121,7 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface volatile int32_t mNeedUpdate = 0; // flag that backend shall update the GL window, and call DrawGLScene std::mutex mSendKeyMutex; std::vector mSendKeys; // Keys sent by external entity (usually console), may be ignored by backend. + std::mutex mExitStateMutex; // Keyboard / Mouse actions bool mMouseDn = false; // Mouse button down diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index 836ab53bedb84..ad19130aa47ee 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -328,9 +328,10 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() glfwSwapInterval(1); } - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } if (mBackend->ExtInit()) { fprintf(stderr, "Error initializing GL extension wrapper\n"); @@ -362,23 +363,20 @@ int32_t GPUDisplayFrontendGlfw::FrontendMain() } ExitDisplay(); - mDisplayControl = 2; - pthread_mutex_lock(&mSemLockExit); - glfwDestroyWindow(mWindow); - glfwTerminate(); - mDisplayRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayControl = 2; + glfwDestroyWindow(mWindow); + glfwTerminate(); + mDisplayRunning = false; + } return 0; } -void GPUDisplayFrontendGlfw::DisplayExit() +void GPUDisplayFrontendGlfw::StopDisplayA() { - pthread_mutex_lock(&mSemLockExit); - if (mDisplayRunning) { - glfwSetWindowShouldClose(mWindow, true); - } - pthread_mutex_unlock(&mSemLockExit); + glfwSetWindowShouldClose(mWindow, true); } void GPUDisplayFrontendGlfw::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h index 77b27f48c32a5..31b9a8b5211d7 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDGLFW_H #include "GPUDisplayFrontend.h" -#include struct GLFWwindow; @@ -28,7 +27,7 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend GPUDisplayFrontendGlfw(); ~GPUDisplayFrontendGlfw() override = default; - void DisplayExit() override; + void StopDisplayA() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; @@ -63,7 +62,6 @@ class GPUDisplayFrontendGlfw : public GPUDisplayFrontend bool mRestoreWindowedSize = false; double mPixelRatio = 1.; // Framebuffer pixels per window coordinate (1 on X11 / Win32, scale on Wayland / macOS) - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; int32_t mWindowX = 0; int32_t mWindowY = 0; int32_t mWindowWidth = INIT_WIDTH; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx index 6adfd0091bb65..6b75eb7493627 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.cxx @@ -265,26 +265,24 @@ int32_t GPUDisplayFrontendGlut::FrontendMain() glutMouseWheelFunc(mMouseWheelFunc); applyInitialWindowState(); - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } glutMainLoop(); - mDisplayControl = 2; - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayControl = 2; + mDisplayRunning = false; + } ExitDisplay(); return 0; } -void GPUDisplayFrontendGlut::DisplayExit() +void GPUDisplayFrontendGlut::StopDisplayA() { - pthread_mutex_lock(&mSemLockExit); - if (mDisplayRunning) { - glutLeaveMainLoop(); - } - pthread_mutex_unlock(&mSemLockExit); + glutLeaveMainLoop(); } void GPUDisplayFrontendGlut::OpenGLPrint(const char* s, float x, float y, float r, float g, float b, float a, bool fromBotton) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h index acb63e863126a..d595fedcb16cf 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlut.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDGLUT_H #include "GPUDisplayFrontend.h" -#include namespace o2::gpu { @@ -26,7 +25,7 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend GPUDisplayFrontendGlut(); ~GPUDisplayFrontendGlut() override = default; - void DisplayExit() override; + void StopDisplayA() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; @@ -48,8 +47,6 @@ class GPUDisplayFrontendGlut : public GPUDisplayFrontend static int32_t GetKey(int32_t key); static void GetKey(int32_t keyin, int32_t& keyOut, int32_t& keyPressOut, bool special); - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - int32_t mWidth = INIT_WIDTH; int32_t mHeight = INIT_HEIGHT; bool mFullScreen = false; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h index 3c7b67c35a0ce..c6eab47c60441 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendNone.h @@ -25,7 +25,6 @@ class GPUDisplayFrontendNone : public GPUDisplayFrontend GPUDisplayFrontendNone(); ~GPUDisplayFrontendNone() override = default; - void DisplayExit() override {} void SwitchFullscreen(bool set) override {} void ToggleMaximized(bool set) override {} void SetVSync(bool enable) override {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx index 2af8513b7c4c4..987217f93f336 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.cxx @@ -559,9 +559,10 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (1); } - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } while (mDisplayControl != 2) { bool error = false; @@ -606,9 +607,10 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() } xkb_context_unref(mXKBcontext); - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + } if (mCursorShapeDevice) { wp_cursor_shape_device_v1_destroy(mCursorShapeDevice); @@ -678,15 +680,6 @@ int32_t GPUDisplayFrontendWayland::FrontendMain() return (0); } -void GPUDisplayFrontendWayland::DisplayExit() -{ - pthread_mutex_lock(&mSemLockExit); - if (mDisplayRunning) { - mDisplayControl = 2; - } - pthread_mutex_unlock(&mSemLockExit); -} - void GPUDisplayFrontendWayland::SwitchFullscreen(bool set) { if (!mCanFullscreen) { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h index 01af3dc49631c..61c95907d500d 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWayland.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDWAYLAND_H #include "GPUDisplayFrontend.h" -#include #include #include "xdg-shell-client-protocol.h" #include "fractional-scale-client-protocol.h" @@ -41,7 +40,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend GPUDisplayFrontendWayland(); ~GPUDisplayFrontendWayland() override = default; - void DisplayExit() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; @@ -69,8 +67,6 @@ class GPUDisplayFrontendWayland : public GPUDisplayFrontend }; OutputInfo* findOutput(wl_output* output); - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - wl_display* mWayland = nullptr; wl_registry* mRegistry = nullptr; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx index 2188fe59a880c..14a65e6634476 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.cxx @@ -371,7 +371,6 @@ int32_t GPUDisplayFrontendWindows::FrontendMain() return (0); } -void DisplayExit() {} 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) {} diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h index cac5b62c4cc63..e28a9f4ced75b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendWindows.h @@ -25,7 +25,6 @@ class GPUDisplayFrontendWindows : public GPUDisplayFrontend GPUDisplayFrontendWindows(); ~GPUDisplayFrontendWindows() override = default; - void DisplayExit() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx index 6bdce427bf518..e832ee88bd751 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.cxx @@ -344,9 +344,10 @@ int32_t GPUDisplayFrontendX11::FrontendMain() return (1); } - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = true; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = true; + } while (1) { do { @@ -471,22 +472,14 @@ int32_t GPUDisplayFrontendX11::FrontendMain() XDestroyWindow(mXDisplay, mWindow); XCloseDisplay(mXDisplay); - pthread_mutex_lock(&mSemLockExit); - mDisplayRunning = false; - pthread_mutex_unlock(&mSemLockExit); + { + std::lock_guard lock(mExitStateMutex); + mDisplayRunning = false; + } return (0); } -void GPUDisplayFrontendX11::DisplayExit() -{ - pthread_mutex_lock(&mSemLockExit); - if (mDisplayRunning) { - mDisplayControl = 2; - } - pthread_mutex_unlock(&mSemLockExit); -} - void GPUDisplayFrontendX11::updateWindowState() { Atom type; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h index 294e1b7174221..bca7def502aaa 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendX11.h @@ -16,7 +16,6 @@ #define GPUDISPLAYFRONTENDX11_H #include "GPUDisplayFrontend.h" -#include #include #include #include @@ -30,7 +29,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend GPUDisplayFrontendX11(); ~GPUDisplayFrontendX11() override = default; - void DisplayExit() override; void SwitchFullscreen(bool set) override; void ToggleMaximized(bool set) override; void SetVSync(bool enable) override; @@ -45,8 +43,6 @@ class GPUDisplayFrontendX11 : public GPUDisplayFrontend int32_t GetKey(int32_t key); void GetKey(XEvent& event, int32_t& keyOut, int32_t& keyPressOut); - pthread_mutex_t mSemLockExit = PTHREAD_MUTEX_INITIALIZER; - GLuint mFontBase; Display* mXDisplay = nullptr; diff --git a/GPU/Workflow/src/O2GPUDPLDisplay.cxx b/GPU/Workflow/src/O2GPUDPLDisplay.cxx index fa68289517e68..3d10a47adda4f 100644 --- a/GPU/Workflow/src/O2GPUDPLDisplay.cxx +++ b/GPU/Workflow/src/O2GPUDPLDisplay.cxx @@ -129,7 +129,7 @@ void O2GPUDPLDisplaySpec::run(ProcessingContext& pc) } if (mDisplayStarted == false) { - if (mDisplay->startDisplay()) { + if (mDisplay->StartUnmanagedDisplay()) { throw std::runtime_error("Error starting event display"); } mDisplayStarted = true; @@ -143,7 +143,7 @@ void O2GPUDPLDisplaySpec::run(ProcessingContext& pc) ptrs.settingsTF = mTFSettings.get(); if (mDisplay->show(&ptrs)) { - mDisplay->endDisplay(); + mDisplay->StopUnmanagedDisplay(); mDisplayShutDown = true; } } @@ -153,7 +153,7 @@ void O2GPUDPLDisplaySpec::endOfStream(EndOfStreamContext& ec) if (mDisplayShutDown) { return; } - mDisplay->endDisplay(); + mDisplay->StopUnmanagedDisplay(); mDisplayShutDown = true; } From 9af7793c26dfdc53d0fcb6551c376707c45f7031 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Tue, 13 Oct 2026 22:39:31 +0200 Subject: [PATCH 26/48] GPU Vulkan Display: Simplify: number of command buffers can equal number of images --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 2 +- .../backend/GPUDisplayBackendVulkan.cxx | 126 ++++++++---------- .../display/backend/GPUDisplayBackendVulkan.h | 2 - 3 files changed, 58 insertions(+), 72 deletions(-) diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index 343cda302305b..f1fdd7f70cdda 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -514,7 +514,7 @@ EndConfig() // Vulkan Display Settings BeginSubConfig(GPUSettingsDisplayVulkan, vulkan, configStandalone.display, "GLV", 0, "Vulkan display settings", display_vulkan) -AddOption(nFramesInFlight, int32_t, 0, "", 0, "Max number of render frames in flight (0 = as many as swapchain images)") +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') diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 80bb794a6d9d1..f03013d03fbab 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -532,8 +532,8 @@ void GPUDisplayBackendVulkan::createCommandBuffers() vk::CommandBufferAllocateInfo allocInfo{}; allocInfo.commandPool = mCommandPool; allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = mFramesInFlight; - mCommandBufferUpToDate.resize(mFramesInFlight, false); + allocInfo.commandBufferCount = mImageCount; + mCommandBufferUpToDate.resize(mImageCount, false); mCommandBuffers = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersText = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersTexture = mDevice.allocateCommandBuffers(allocInfo); @@ -591,9 +591,9 @@ void GPUDisplayBackendVulkan::clearSemaphoresAndFences() void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() { for (int32_t j = 0; j < 3; j++) { - mUniformBuffersMat[j].resize(mFramesInFlight); - mUniformBuffersCol[j].resize(mFramesInFlight); - for (uint32_t i = 0; i < mFramesInFlight; i++) { + mUniformBuffersMat[j].resize(mImageCount); + mUniformBuffersCol[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); mUniformBuffersCol[j][i] = createBuffer(sizeof(float) * 4, nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); } @@ -601,13 +601,13 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() std::array poolSizes{}; poolSizes[0].type = vk::DescriptorType::eUniformBuffer; - poolSizes[0].descriptorCount = (uint32_t)mFramesInFlight * (2 * 3); + poolSizes[0].descriptorCount = (uint32_t)mImageCount * (2 * 3); poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; - poolSizes[1].descriptorCount = (uint32_t)mFramesInFlight * 2; + poolSizes[1].descriptorCount = (uint32_t)mImageCount * 2; vk::DescriptorPoolCreateInfo poolInfo{}; poolInfo.poolSizeCount = poolSizes.size(); poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = (uint32_t)mFramesInFlight * 3; + poolInfo.maxSets = (uint32_t)mImageCount * 3; mDescriptorPool = mDevice.createDescriptorPool(poolInfo, nullptr); vk::DescriptorSetLayoutBinding uboLayoutBindingMat{}; @@ -634,15 +634,15 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() vk::DescriptorSetAllocateInfo allocInfo{}; allocInfo.descriptorPool = mDescriptorPool; - allocInfo.descriptorSetCount = (uint32_t)mFramesInFlight; + allocInfo.descriptorSetCount = (uint32_t)mImageCount; for (int32_t j = 0; j < 3; j++) { // 0 = Render, 1 = Text, 2 = Texture - std::vector layouts(mFramesInFlight, j ? mUniformDescriptorTexture : mUniformDescriptor); + std::vector layouts(mImageCount, j ? mUniformDescriptorTexture : mUniformDescriptor); allocInfo.pSetLayouts = layouts.data(); mDescriptorSets[j] = mDevice.allocateDescriptorSets(allocInfo); for (int32_t k = 0; k < 2; k++) { auto& mUniformBuffers = k ? mUniformBuffersCol[j] : mUniformBuffersMat[j]; - for (uint32_t i = 0; i < mFramesInFlight; i++) { + for (uint32_t i = 0; i < mImageCount; i++) { vk::DescriptorBufferInfo bufferInfo{}; bufferInfo.buffer = mUniformBuffers[i].buffer; bufferInfo.offset = 0; @@ -733,7 +733,7 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing mPresentMode = chooseSwapPresentMode(mSwapChainDetails.presentModes, mDisplay->cfgR().drawQualityVSync ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate); vk::Extent2D extent = chooseSwapExtent(mSwapChainDetails.capabilities); - uint32_t imageCount = mSwapChainDetails.capabilities.minImageCount + 1; + uint32_t imageCount = std::max(mSwapChainDetails.capabilities.minImageCount + 1, mDisplay->cfg().vulkan.nFramesInFlight + 1); if (mSwapChainDetails.capabilities.maxImageCount > 0 && imageCount > mSwapChainDetails.capabilities.maxImageCount) { imageCount = mSwapChainDetails.capabilities.maxImageCount; } @@ -769,13 +769,12 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing mSwapChainImages = mDevice.getSwapchainImagesKHR(mSwapChain); uint32_t oldFramesInFlight = mFramesInFlight; + mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; mImageCount = mSwapChainImages.size(); - mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight == 0 ? mImageCount : mDisplay->cfg().vulkan.nFramesInFlight; - mCommandBufferPerImage = mFramesInFlight == mImageCount; if (mFramesInFlight > oldFramesInFlight || !mCommandInfrastructureCreated) { if (mCommandInfrastructureCreated) { - clearUniformLayoutsAndBuffers(); + clearUniformLayoutsAndBuffers(); // TODO: Check what must actually be cleaned up if frames in flight changes clearCommandBuffers(); clearSemaphoresAndFences(); } @@ -1016,14 +1015,12 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo {(float)mRenderWidth, (float)mRenderHeight, 1.0f, 1.0f}}; mMixingTextureVertexArray = createBuffer(sizeof(vertices), &vertices[0][0], vk::BufferUsageFlagBits::eVertexBuffer, 1); - if (mCommandBufferPerImage) { - for (uint32_t i = 0; i < mFramesInFlight; i++) { - setMixDescriptor(i, i); - } + for (uint32_t i = 0; i < mImageCount; i++) { + setMixDescriptor(i, i); } } - mFontVertexBuffer.resize(mFramesInFlight); + mFontVertexBuffer.resize(mImageCount); } void GPUDisplayBackendVulkan::clearOffscreenBuffers() @@ -1253,7 +1250,7 @@ void GPUDisplayBackendVulkan::startFillCommandBuffer(vk::CommandBuffer& commandB vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mVBO.buffer, offsets); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayout, 0, 1, &mDescriptorSets[0][mCurrentBufferSet], 0, nullptr); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayout, 0, 1, &mDescriptorSets[0][mCurrentImageIndex], 0, nullptr); } void GPUDisplayBackendVulkan::endFillCommandBuffer(vk::CommandBuffer& commandBuffer) @@ -1428,7 +1425,7 @@ void GPUDisplayBackendVulkan::clearImage(VulkanImage& image) int32_t GPUDisplayBackendVulkan::InitBackendA() { mEnableValidationLayers = (mDisplay->param() && mDisplay->GetProcessingSettings().debugLevel >= 2) || (getenv("GPUCA_VULKAN_VALIDATION") && atoi(getenv("GPUCA_VULKAN_VALIDATION"))); - mFramesInFlight = 2; + mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; createDevice(); createShaders(); @@ -1496,7 +1493,7 @@ uint32_t GPUDisplayBackendVulkan::drawVertices(const vboList& v, const drawType if (count == 0) { return 0; } - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return count; } @@ -1527,12 +1524,9 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& auto getImage = [&]() { vk::Fence fen = VkFence(VK_NULL_HANDLE); vk::Semaphore sem = VkSemaphore(VK_NULL_HANDLE); - if (mCommandBufferPerImage) { - fen = mInFlightFence[mCurrentFrame]; - CHKERR(mDevice.resetFences(1, &fen)); - } else { - sem = mImageAvailableSemaphore[mCurrentFrame]; - } + fen = mInFlightFence[mCurrentFrame]; + CHKERR(mDevice.resetFences(1, &fen)); + // sem = mImageAvailableSemaphore[mCurrentFrame]; // TODO: Why not ! return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, sem, fen, &mCurrentImageIndex); }; @@ -1551,31 +1545,28 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& retVal = getImage(); } CHKERR(retVal); - if (mCommandBufferPerImage) { - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); - } + CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); CHKERR(mDevice.resetFences(1, &mInFlightFence[mCurrentFrame])); mMustUpdateSwapChain = false; mHasDrawnText = false; - mCurrentBufferSet = mCommandBufferPerImage ? mCurrentImageIndex : mCurrentFrame; const hmm_mat4 modelViewProj = proj * view; - writeToBuffer(mUniformBuffersMat[0][mCurrentBufferSet], sizeof(modelViewProj), &modelViewProj); + writeToBuffer(mUniformBuffersMat[0][mCurrentImageIndex], sizeof(modelViewProj), &modelViewProj); } - mCurrentCommandBuffer = toMixBuffer ? mCommandBuffersMix[mCurrentBufferSet] : mCommandBuffers[mCurrentBufferSet]; + mCurrentCommandBuffer = toMixBuffer ? mCommandBuffersMix[mCurrentImageIndex] : mCommandBuffers[mCurrentImageIndex]; mCurrentCommandBufferLastPipeline = -1; - if (!mCommandBufferUpToDate[mCurrentBufferSet]) { + if (!mCommandBufferUpToDate[mCurrentImageIndex]) { startFillCommandBuffer(mCurrentCommandBuffer, mCurrentImageIndex, toMixBuffer); } } void GPUDisplayBackendVulkan::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { - if (!mCommandBufferUpToDate[mCurrentBufferSet]) { + if (!mCommandBufferUpToDate[mCurrentImageIndex]) { endFillCommandBuffer(mCurrentCommandBuffer); - if (!toMixBuffer && includeMixImage == 0.f && mCommandBufferPerImage) { - mCommandBufferUpToDate[mCurrentBufferSet] = true; + if (!toMixBuffer && includeMixImage == 0.f) { + mCommandBufferUpToDate[mCurrentImageIndex] = true; } } } @@ -1587,7 +1578,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f vk::SubmitInfo submitInfo{}; vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.pWaitSemaphores = includeMixImage > 0.f ? &mRenderFinishedSemaphore[mCurrentFrame] : (!mCommandBufferPerImage ? &mImageAvailableSemaphore[mCurrentFrame] : nullptr); + submitInfo.pWaitSemaphores = includeMixImage > 0.f ? &mRenderFinishedSemaphore[mCurrentFrame] : nullptr; submitInfo.waitSemaphoreCount = submitInfo.pWaitSemaphores != nullptr ? 1 : 0; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; @@ -1597,19 +1588,19 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f CHKERR(mGraphicsQueue.submit(1, &submitInfo, includeMixImage > 0 || toMixBuffer || mHasDrawnText || mDownsampleFSAA ? noFence : mInFlightFence[mCurrentFrame])); if (!toMixBuffer) { if (includeMixImage > 0.f) { - mixImages(mCommandBuffersTexture[mCurrentBufferSet], includeMixImage); + mixImages(mCommandBuffersTexture[mCurrentImageIndex], includeMixImage); submitInfo.pWaitSemaphores = stageFinishedSemaphore; waitStages[0] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; submitInfo.waitSemaphoreCount = 1; - submitInfo.pCommandBuffers = &mCommandBuffersTexture[mCurrentBufferSet]; + submitInfo.pCommandBuffers = &mCommandBuffersTexture[mCurrentImageIndex]; stageFinishedSemaphore = &mMixFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; CHKERR(mGraphicsQueue.submit(1, &submitInfo, mHasDrawnText || mDownsampleFSAA ? noFence : mInFlightFence[mCurrentFrame])); } if (mDownsampleFSAA) { - downsampleToFramebuffer(mCommandBuffersDownsample[mCurrentBufferSet]); - submitInfo.pCommandBuffers = &mCommandBuffersDownsample[mCurrentBufferSet]; + downsampleToFramebuffer(mCommandBuffersDownsample[mCurrentImageIndex]); + submitInfo.pCommandBuffers = &mCommandBuffersDownsample[mCurrentImageIndex]; submitInfo.pWaitSemaphores = stageFinishedSemaphore; waitStages[0] = {vk::PipelineStageFlagBits::eTransfer}; submitInfo.waitSemaphoreCount = 1; @@ -1631,7 +1622,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.pWaitSemaphores = stageFinishedSemaphore; waitStages[0] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; submitInfo.waitSemaphoreCount = 1; - submitInfo.pCommandBuffers = &mCommandBuffersText[mCurrentBufferSet]; + submitInfo.pCommandBuffers = &mCommandBuffersText[mCurrentImageIndex]; stageFinishedSemaphore = &mTextFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); @@ -1690,7 +1681,7 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command void GPUDisplayBackendVulkan::prepareText() { hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); - writeToBuffer(mUniformBuffersMat[1][mCurrentBufferSet], sizeof(proj), &proj); + writeToBuffer(mUniformBuffersMat[1][mCurrentImageIndex], sizeof(proj), &proj); mFontVertexBufferHost.clear(); mTextDrawCommands.clear(); @@ -1702,11 +1693,11 @@ void GPUDisplayBackendVulkan::finishText() return; } - mCommandBuffersText[mCurrentBufferSet].reset({}); + mCommandBuffersText[mCurrentImageIndex].reset({}); vk::CommandBufferBeginInfo beginInfo{}; beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; - mCommandBuffersText[mCurrentBufferSet].begin(beginInfo); + mCommandBuffersText[mCurrentImageIndex].begin(beginInfo); vk::RenderPassBeginInfo renderPassInfo{}; renderPassInfo.renderPass = mRenderPassText; @@ -1714,33 +1705,33 @@ void GPUDisplayBackendVulkan::finishText() renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; renderPassInfo.renderArea.extent = vk::Extent2D{mScreenWidth, mScreenHeight}; renderPassInfo.clearValueCount = 0; - mCommandBuffersText[mCurrentBufferSet].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + mCommandBuffersText[mCurrentImageIndex].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); - if (mFontVertexBuffer[mCurrentBufferSet].size) { - clearBuffer(mFontVertexBuffer[mCurrentBufferSet]); + if (mFontVertexBuffer[mCurrentImageIndex].size) { + clearBuffer(mFontVertexBuffer[mCurrentImageIndex]); } - mFontVertexBuffer[mCurrentBufferSet] = createBuffer(mFontVertexBufferHost.size() * sizeof(float), mFontVertexBufferHost.data(), vk::BufferUsageFlagBits::eVertexBuffer, 0); + mFontVertexBuffer[mCurrentImageIndex] = createBuffer(mFontVertexBufferHost.size() * sizeof(float), mFontVertexBufferHost.data(), vk::BufferUsageFlagBits::eVertexBuffer, 0); - mCommandBuffersText[mCurrentBufferSet].bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[3]); - mCommandBuffersText[mCurrentBufferSet].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[1][mCurrentBufferSet], 0, nullptr); + mCommandBuffersText[mCurrentImageIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[3]); + mCommandBuffersText[mCurrentImageIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[1][mCurrentImageIndex], 0, nullptr); vk::DeviceSize offsets[] = {0}; - mCommandBuffersText[mCurrentBufferSet].bindVertexBuffers(0, 1, &mFontVertexBuffer[mCurrentBufferSet].buffer, offsets); + mCommandBuffersText[mCurrentImageIndex].bindVertexBuffers(0, 1, &mFontVertexBuffer[mCurrentImageIndex].buffer, offsets); for (const auto& cmd : mTextDrawCommands) { - mCommandBuffersText[mCurrentBufferSet].pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(cmd.color), cmd.color); - mCommandBuffersText[mCurrentBufferSet].draw(cmd.nVertices, 1, cmd.firstVertex, 0); + mCommandBuffersText[mCurrentImageIndex].pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(cmd.color), cmd.color); + mCommandBuffersText[mCurrentImageIndex].draw(cmd.nVertices, 1, cmd.firstVertex, 0); } mFontVertexBufferHost.clear(); - mCommandBuffersText[mCurrentBufferSet].endRenderPass(); - mCommandBuffersText[mCurrentBufferSet].end(); + mCommandBuffersText[mCurrentImageIndex].endRenderPass(); + mCommandBuffersText[mCurrentImageIndex].end(); } void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float mixSlaveImage) { hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1, 1); - writeToBuffer(mUniformBuffersMat[2][mCurrentBufferSet], sizeof(proj), &proj); + writeToBuffer(mUniformBuffersMat[2][mCurrentImageIndex], sizeof(proj), &proj); commandBuffer.reset({}); vk::CommandBufferBeginInfo beginInfo{}; @@ -1760,10 +1751,7 @@ void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float m commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[4]); - if (!mCommandBufferPerImage) { - setMixDescriptor(mCurrentBufferSet, mCurrentImageIndex); - } - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[2][mCurrentBufferSet], 0, nullptr); + commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[2][mCurrentImageIndex], 0, nullptr); vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mMixingTextureVertexArray.buffer, offsets); @@ -1776,7 +1764,7 @@ void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float m void GPUDisplayBackendVulkan::ActivateColor(std::array& color) { - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, sizeof(color), color.data()); @@ -1784,7 +1772,7 @@ void GPUDisplayBackendVulkan::ActivateColor(std::array& color) void GPUDisplayBackendVulkan::pointSizeFactor(float factor) { - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor; @@ -1793,7 +1781,7 @@ void GPUDisplayBackendVulkan::pointSizeFactor(float factor) void GPUDisplayBackendVulkan::lineWidthFactor(float factor) { - if (mCommandBufferUpToDate[mCurrentBufferSet]) { + if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } mCurrentCommandBuffer.setLineWidth(mDisplay->cfgL().lineWidth * mDownsampleFactor * factor); @@ -1893,7 +1881,7 @@ void GPUDisplayBackendVulkan::updateFontTextureDescriptor() imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; imageInfo.imageView = mFontImage.view; imageInfo.sampler = mTextureSampler; - for (uint32_t i = 0; i < mFramesInFlight; i++) { + for (uint32_t i = 0; i < mImageCount; i++) { vk::WriteDescriptorSet descriptorWrite{}; descriptorWrite.dstSet = mDescriptorSets[1][i]; descriptorWrite.dstBinding = 2; @@ -2024,5 +2012,5 @@ uint32_t GPUDisplayBackendVulkan::DepthBits() bool GPUDisplayBackendVulkan::backendNeedRedraw() { - return !mCommandBufferUpToDate[mCurrentBufferSet]; + return !mCommandBufferUpToDate[mCurrentImageIndex]; } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index b36a46f3170a1..2d571fc0a31f5 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -162,13 +162,11 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mFramebuffersTexture; vk::CommandPool mCommandPool; - bool mCommandBufferPerImage = false; bool mCommandInfrastructureCreated = false; uint32_t mImageCount = 0; uint32_t mFramesInFlight = 0; int32_t mCurrentFrame = 0; uint32_t mCurrentImageIndex = 0; - int32_t mCurrentBufferSet = 0; vk::CommandBuffer mCurrentCommandBuffer; int32_t mCurrentCommandBufferLastPipeline = -1; From 08422aa6ee78537bac16e94f54e2e31c37a9f80b Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 00:20:53 +0200 Subject: [PATCH 27/48] GPU Vulkan Display: Improve semaphores and fences for rendering, allow full CPU + render + display overlap --- .../backend/GPUDisplayBackendVulkan.cxx | 48 ++++++++++++------- .../display/backend/GPUDisplayBackendVulkan.h | 2 + 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index f03013d03fbab..09bd1a8c49a88 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -562,7 +562,9 @@ void GPUDisplayBackendVulkan::createSemaphoresAndFences() mTextFinishedSemaphore.resize(mFramesInFlight); mMixFinishedSemaphore.resize(mFramesInFlight); mDownsampleFinishedSemaphore.resize(mFramesInFlight); + mPresentSemaphore.resize(mImageCount); mInFlightFence.resize(mFramesInFlight); + mImageFence.resize(mImageCount); for (uint32_t i = 0; i < mFramesInFlight; i++) { mImageAvailableSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); mRenderFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); @@ -571,6 +573,10 @@ void GPUDisplayBackendVulkan::createSemaphoresAndFences() mDownsampleFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); mInFlightFence[i] = mDevice.createFence(fenceInfo, nullptr); } + for (uint32_t i = 0; i < mImageCount; i++) { + mPresentSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); + mImageFence[i] = VkFence(VK_NULL_HANDLE); + } fenceInfo.flags = {}; mSingleCommitFence = mDevice.createFence(fenceInfo, nullptr); } @@ -582,6 +588,7 @@ void GPUDisplayBackendVulkan::clearSemaphoresAndFences() clearVector(mTextFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mMixFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mDownsampleFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); + clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mInFlightFence, [&](auto& x) { mDevice.destroyFence(x, nullptr); }); mDevice.destroyFence(mSingleCommitFence, nullptr); } @@ -768,11 +775,11 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing mSwapChain = mDevice.createSwapchainKHR(swapCreateInfo, nullptr); mSwapChainImages = mDevice.getSwapchainImagesKHR(mSwapChain); - uint32_t oldFramesInFlight = mFramesInFlight; + uint32_t oldFramesInFlight = mFramesInFlight, oldImageCount = mImageCount; mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; mImageCount = mSwapChainImages.size(); - if (mFramesInFlight > oldFramesInFlight || !mCommandInfrastructureCreated) { + if (mFramesInFlight != oldFramesInFlight || mImageCount != oldImageCount || !mCommandInfrastructureCreated) { if (mCommandInfrastructureCreated) { clearUniformLayoutsAndBuffers(); // TODO: Check what must actually be cleaned up if frames in flight changes clearCommandBuffers(); @@ -1522,12 +1529,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& mCurrentFrame = (mCurrentFrame + 1) % mFramesInFlight; CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); auto getImage = [&]() { - vk::Fence fen = VkFence(VK_NULL_HANDLE); - vk::Semaphore sem = VkSemaphore(VK_NULL_HANDLE); - fen = mInFlightFence[mCurrentFrame]; - CHKERR(mDevice.resetFences(1, &fen)); - // sem = mImageAvailableSemaphore[mCurrentFrame]; // TODO: Why not ! - return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, sem, fen, &mCurrentImageIndex); + return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, mImageAvailableSemaphore[mCurrentFrame], VkFence(VK_NULL_HANDLE), &mCurrentImageIndex); }; vk::Result retVal = vk::Result::eSuccess; @@ -1545,8 +1547,12 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& retVal = getImage(); } CHKERR(retVal); - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); + if (mImageFence[mCurrentImageIndex]) { + CHKERR(mDevice.waitForFences(1, &mImageFence[mCurrentImageIndex], true, UINT64_MAX)); + } + mImageFence[mCurrentImageIndex] = mInFlightFence[mCurrentFrame]; CHKERR(mDevice.resetFences(1, &mInFlightFence[mCurrentFrame])); + mMustUpdateSwapChain = false; mHasDrawnText = false; @@ -1578,14 +1584,14 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f vk::SubmitInfo submitInfo{}; vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.pWaitSemaphores = includeMixImage > 0.f ? &mRenderFinishedSemaphore[mCurrentFrame] : nullptr; - submitInfo.waitSemaphoreCount = submitInfo.pWaitSemaphores != nullptr ? 1 : 0; + submitInfo.pWaitSemaphores = includeMixImage > 0.f ? &mRenderFinishedSemaphore[mCurrentFrame] : &mImageAvailableSemaphore[mCurrentFrame]; + submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &mCurrentCommandBuffer; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, includeMixImage > 0 || toMixBuffer || mHasDrawnText || mDownsampleFSAA ? noFence : mInFlightFence[mCurrentFrame])); + CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); if (!toMixBuffer) { if (includeMixImage > 0.f) { mixImages(mCommandBuffersTexture[mCurrentImageIndex], includeMixImage); @@ -1595,7 +1601,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.pCommandBuffers = &mCommandBuffersTexture[mCurrentImageIndex]; stageFinishedSemaphore = &mMixFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mHasDrawnText || mDownsampleFSAA ? noFence : mInFlightFence[mCurrentFrame])); + CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); } if (mDownsampleFSAA) { @@ -1606,7 +1612,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.waitSemaphoreCount = 1; stageFinishedSemaphore = &mDownsampleFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mHasDrawnText ? noFence : mInFlightFence[mCurrentFrame])); + CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); } if (doScreenshot) { @@ -1625,13 +1631,21 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.pCommandBuffers = &mCommandBuffersText[mCurrentImageIndex]; stageFinishedSemaphore = &mTextFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); + CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); } - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); // TODO: I think we need to wait for the fence, so that the image was acquired before we present. Perhaps we can present later to avoid delays + vk::PipelineStageFlags presentWaitStage = vk::PipelineStageFlagBits::eAllCommands; + submitInfo.commandBufferCount = 0; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = stageFinishedSemaphore; + submitInfo.pWaitDstStageMask = &presentWaitStage; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &mPresentSemaphore[mCurrentImageIndex]; + CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); // Empty submit to signal InFlightFence and PresentSemaphore + vk::PresentInfoKHR presentInfo{}; presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = stageFinishedSemaphore; + presentInfo.pWaitSemaphores = &mPresentSemaphore[mCurrentImageIndex]; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &mSwapChain; presentInfo.pImageIndices = &mCurrentImageIndex; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 2d571fc0a31f5..964772711d511 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -181,7 +181,9 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mTextFinishedSemaphore; std::vector mMixFinishedSemaphore; std::vector mDownsampleFinishedSemaphore; + std::vector mPresentSemaphore; std::vector mInFlightFence; + std::vector mImageFence; std::vector mUniformBuffersMat[3]; std::vector mUniformBuffersCol[3]; std::vector mDescriptorSets[3]; From 17615683070c0524aceedb610feb01be7d6dbf25 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 01:04:49 +0200 Subject: [PATCH 28/48] GPU Vulkan Display: Fix error handling when acquiring images --- .../backend/GPUDisplayBackendVulkan.cxx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 09bd1a8c49a88..f34a31901a5f2 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1533,19 +1533,18 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& }; vk::Result retVal = vk::Result::eSuccess; - bool mustUpdateRendering = mMustUpdateSwapChain; - if (mDisplay->updateRenderPipeline() || (requestScreenshot && !mSwapchainImageReadable) || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot)) { - mustUpdateRendering = true; - } else if (!mMustUpdateSwapChain) { + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (requestScreenshot && !mSwapchainImageReadable) || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot); + if (!mustUpdateSwapChain) { retVal = getImage(); - } - if (mMustUpdateSwapChain || mustUpdateRendering || retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) { - if (!mustUpdateRendering) { - GPUInfo("Pipeline out of data / suboptimal, recreating"); + if (retVal == vk::Result::eErrorOutOfDateKHR) { + mustUpdateSwapChain = mMustUpdateSwapChain = true; } + } + if (mustUpdateSwapChain) { recreateRendering(requestScreenshot, toMixBuffer); retVal = getImage(); } + mMustUpdateSwapChain = retVal == vk::Result::eSuboptimalKHR; CHKERR(retVal); if (mImageFence[mCurrentImageIndex]) { CHKERR(mDevice.waitForFences(1, &mImageFence[mCurrentImageIndex], true, UINT64_MAX)); @@ -1553,9 +1552,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& mImageFence[mCurrentImageIndex] = mInFlightFence[mCurrentFrame]; CHKERR(mDevice.resetFences(1, &mInFlightFence[mCurrentFrame])); - mMustUpdateSwapChain = false; mHasDrawnText = false; - const hmm_mat4 modelViewProj = proj * view; writeToBuffer(mUniformBuffersMat[0][mCurrentImageIndex], sizeof(modelViewProj), &modelViewProj); } @@ -1651,7 +1648,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f presentInfo.pImageIndices = &mCurrentImageIndex; presentInfo.pResults = nullptr; vk::Result retVal = mGraphicsQueue.presentKHR(&presentInfo); - if (retVal == vk::Result::eErrorOutOfDateKHR) { + if (retVal == vk::Result::eErrorOutOfDateKHR || retVal == vk::Result::eSuboptimalKHR) { mMustUpdateSwapChain = true; } else { CHKERR(retVal); From 34045c15ed13d677cd4ebb5fc89a4e0ec543d247 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 08:43:40 +0200 Subject: [PATCH 29/48] GPU Vulkan Backend: Merge all commands to a single commit, remove empty commit for fence at the end --- .../backend/GPUDisplayBackendVulkan.cxx | 30 ++++++++++--------- .../display/backend/GPUDisplayBackendVulkan.h | 1 + 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index f34a31901a5f2..84f9a4524417c 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1562,6 +1562,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& if (!mCommandBufferUpToDate[mCurrentImageIndex]) { startFillCommandBuffer(mCurrentCommandBuffer, mCurrentImageIndex, toMixBuffer); } + mSubmitQueue.clear(); } void GPUDisplayBackendVulkan::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) @@ -1577,7 +1578,6 @@ void GPUDisplayBackendVulkan::finishDraw(bool doScreenshot, bool toMixBuffer, fl void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, float includeMixImage) { vk::Semaphore* stageFinishedSemaphore = &mRenderFinishedSemaphore[mCurrentFrame]; - const vk::Fence noFence = VkFence(VK_NULL_HANDLE); vk::SubmitInfo submitInfo{}; vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; @@ -1588,7 +1588,8 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.pCommandBuffers = &mCurrentCommandBuffer; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); + mSubmitQueue.emplace_back(submitInfo); + if (!toMixBuffer) { if (includeMixImage > 0.f) { mixImages(mCommandBuffersTexture[mCurrentImageIndex], includeMixImage); @@ -1598,7 +1599,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.pCommandBuffers = &mCommandBuffersTexture[mCurrentImageIndex]; stageFinishedSemaphore = &mMixFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); + mSubmitQueue.emplace_back(submitInfo); } if (mDownsampleFSAA) { @@ -1609,16 +1610,18 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.waitSemaphoreCount = 1; stageFinishedSemaphore = &mDownsampleFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); + mSubmitQueue.emplace_back(submitInfo); } if (doScreenshot) { + CHKERR(mGraphicsQueue.submit(mSubmitQueue.size(), mSubmitQueue.data(), VkFence(VK_NULL_HANDLE))); mDevice.waitIdle(); if (mDisplay->cfgR().screenshotScaleFactor != 1) { readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); } else { readImageToPixels(mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::ePresentSrcKHR, mScreenshotPixels); } + mSubmitQueue.clear(); } if (mHasDrawnText) { @@ -1628,20 +1631,19 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f submitInfo.pCommandBuffers = &mCommandBuffersText[mCurrentImageIndex]; stageFinishedSemaphore = &mTextFinishedSemaphore[mCurrentFrame]; submitInfo.pSignalSemaphores = stageFinishedSemaphore; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, noFence)); + mSubmitQueue.emplace_back(submitInfo); } - vk::PipelineStageFlags presentWaitStage = vk::PipelineStageFlagBits::eAllCommands; - submitInfo.commandBufferCount = 0; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - submitInfo.pWaitDstStageMask = &presentWaitStage; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &mPresentSemaphore[mCurrentImageIndex]; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); // Empty submit to signal InFlightFence and PresentSemaphore + if (mSubmitQueue.size()) { + vk::SubmitInfo& submitInfoLast = mSubmitQueue.back(); + vk::PipelineStageFlags presentWaitStage = vk::PipelineStageFlagBits::eAllCommands; // Last commit signals to present + submitInfoLast.pWaitDstStageMask = &presentWaitStage; + submitInfoLast.pSignalSemaphores = &mPresentSemaphore[mCurrentImageIndex]; + CHKERR(mGraphicsQueue.submit(mSubmitQueue.size(), mSubmitQueue.data(), mInFlightFence[mCurrentFrame])); + } vk::PresentInfoKHR presentInfo{}; - presentInfo.waitSemaphoreCount = 1; + presentInfo.waitSemaphoreCount = mSubmitQueue.size() ? 1 : 0; presentInfo.pWaitSemaphores = &mPresentSemaphore[mCurrentImageIndex]; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &mSwapChain; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 964772711d511..286f51bf6a3f0 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -175,6 +175,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mCommandBuffersText; std::vector mCommandBuffersTexture; std::vector mCommandBuffersMix; + std::vector mSubmitQueue; std::vector mCommandBufferUpToDate; std::vector mImageAvailableSemaphore; std::vector mRenderFinishedSemaphore; From 9d074c1f04bb391478e8aa12f9560b45a8ce34d1 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 09:15:14 +0200 Subject: [PATCH 30/48] GPU Vulkan Backend: Remove unnecessary semaphores and rely on barriers --- .../backend/GPUDisplayBackendVulkan.cxx | 124 ++++++------------ .../display/backend/GPUDisplayBackendVulkan.h | 6 +- 2 files changed, 40 insertions(+), 90 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 84f9a4524417c..7f2d2353a9207 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -558,19 +558,11 @@ void GPUDisplayBackendVulkan::createSemaphoresAndFences() vk::FenceCreateInfo fenceInfo{}; fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; mImageAvailableSemaphore.resize(mFramesInFlight); - mRenderFinishedSemaphore.resize(mFramesInFlight); - mTextFinishedSemaphore.resize(mFramesInFlight); - mMixFinishedSemaphore.resize(mFramesInFlight); - mDownsampleFinishedSemaphore.resize(mFramesInFlight); mPresentSemaphore.resize(mImageCount); mInFlightFence.resize(mFramesInFlight); mImageFence.resize(mImageCount); for (uint32_t i = 0; i < mFramesInFlight; i++) { mImageAvailableSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mRenderFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mTextFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mMixFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mDownsampleFinishedSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); mInFlightFence[i] = mDevice.createFence(fenceInfo, nullptr); } for (uint32_t i = 0; i < mImageCount; i++) { @@ -584,10 +576,6 @@ void GPUDisplayBackendVulkan::createSemaphoresAndFences() void GPUDisplayBackendVulkan::clearSemaphoresAndFences() { clearVector(mImageAvailableSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mRenderFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mTextFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mMixFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mDownsampleFinishedSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mInFlightFence, [&](auto& x) { mDevice.destroyFence(x, nullptr); }); mDevice.destroyFence(mSingleCommitFence, nullptr); @@ -861,22 +849,25 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo colorAttachmentRef.attachment = nAttachments++; colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; vk::AttachmentReference depthAttachmentRef{}; - // depthAttachmentRef.attachment // below depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; vk::AttachmentReference colorAttachmentResolveRef{}; - // colorAttachmentResolveRef.attachment // below colorAttachmentResolveRef.layout = vk::ImageLayout::eColorAttachmentOptimal; vk::SubpassDescription subpass{}; subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; - vk::SubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests; - dependency.srcAccessMask = {}; - dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests; - dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + std::array dependencies{}; + dependencies[0].srcSubpass = dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; + dependencies[0].dstSubpass = 0; + dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader; // Includes eColorAttachmentOutput, the wait stage of the image acquire semaphore + dependencies[0].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eTransferWrite; + dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; + dependencies[0].dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + dependencies[1].srcSubpass = 0; + dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eLateFragmentTests; + dependencies[1].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader; + dependencies[1].dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eShaderRead; std::vector attachments = {colorAttachment}; if (mZActive) { @@ -895,8 +886,8 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo renderPassInfo.pAttachments = attachments.data(); renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 1; - renderPassInfo.pDependencies = &dependency; + renderPassInfo.dependencyCount = dependencies.size(); + renderPassInfo.pDependencies = dependencies.data(); mRenderPass = mDevice.createRenderPass(renderPassInfo, nullptr); const uint32_t imageCountWithMixImages = mImageCount * (mMixingSupported ? 2 : 1); @@ -928,11 +919,6 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo renderPassInfo.pAttachments = &colorAttachment; subpass.pDepthStencilAttachment = nullptr; subpass.pResolveAttachments = nullptr; - if (mFramebuffersText.size()) { - dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; // Remove early fragment test - dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; // Remove depth/stencil dependencies - } colorAttachment.loadOp = vk::AttachmentLoadOp::eLoad; // Don't clear the frame buffer colorAttachment.initialLayout = vk::ImageLayout::ePresentSrcKHR; // Initial layout is not undefined after 1st pass colorAttachment.samples = vk::SampleCountFlagBits::e1; // No MSAA for Text @@ -944,11 +930,6 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo colorAttachment.initialLayout = vk::ImageLayout::eColorAttachmentOptimal; colorAttachment.finalLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; } - if (mFramebuffersTexture.size()) { - dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; // Remove early fragment test - dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; // Remove depth/stencil dependencies - } mRenderPassTexture = mDevice.createRenderPass(renderPassInfo, nullptr); } @@ -1553,6 +1534,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& CHKERR(mDevice.resetFences(1, &mInFlightFence[mCurrentFrame])); mHasDrawnText = false; + mFrameCommandBuffers.clear(); const hmm_mat4 modelViewProj = proj * view; writeToBuffer(mUniformBuffersMat[0][mCurrentImageIndex], sizeof(modelViewProj), &modelViewProj); } @@ -1562,7 +1544,6 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& if (!mCommandBufferUpToDate[mCurrentImageIndex]) { startFillCommandBuffer(mCurrentCommandBuffer, mCurrentImageIndex, toMixBuffer); } - mSubmitQueue.clear(); } void GPUDisplayBackendVulkan::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) @@ -1577,73 +1558,46 @@ void GPUDisplayBackendVulkan::finishDraw(bool doScreenshot, bool toMixBuffer, fl void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, float includeMixImage) { - vk::Semaphore* stageFinishedSemaphore = &mRenderFinishedSemaphore[mCurrentFrame]; - - vk::SubmitInfo submitInfo{}; - vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.pWaitSemaphores = includeMixImage > 0.f ? &mRenderFinishedSemaphore[mCurrentFrame] : &mImageAvailableSemaphore[mCurrentFrame]; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitDstStageMask = waitStages; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &mCurrentCommandBuffer; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - mSubmitQueue.emplace_back(submitInfo); - + mFrameCommandBuffers.emplace_back(mCurrentCommandBuffer); if (!toMixBuffer) { if (includeMixImage > 0.f) { mixImages(mCommandBuffersTexture[mCurrentImageIndex], includeMixImage); - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - waitStages[0] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pCommandBuffers = &mCommandBuffersTexture[mCurrentImageIndex]; - stageFinishedSemaphore = &mMixFinishedSemaphore[mCurrentFrame]; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - mSubmitQueue.emplace_back(submitInfo); + mFrameCommandBuffers.emplace_back(mCommandBuffersTexture[mCurrentImageIndex]); } - if (mDownsampleFSAA) { downsampleToFramebuffer(mCommandBuffersDownsample[mCurrentImageIndex]); - submitInfo.pCommandBuffers = &mCommandBuffersDownsample[mCurrentImageIndex]; - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - waitStages[0] = {vk::PipelineStageFlagBits::eTransfer}; - submitInfo.waitSemaphoreCount = 1; - stageFinishedSemaphore = &mDownsampleFinishedSemaphore[mCurrentFrame]; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - mSubmitQueue.emplace_back(submitInfo); + mFrameCommandBuffers.emplace_back(mCommandBuffersDownsample[mCurrentImageIndex]); } + vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer; + vk::SubmitInfo submitInfo{}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = &mImageAvailableSemaphore[mCurrentFrame]; + submitInfo.pWaitDstStageMask = &waitStage; if (doScreenshot) { - CHKERR(mGraphicsQueue.submit(mSubmitQueue.size(), mSubmitQueue.data(), VkFence(VK_NULL_HANDLE))); + submitInfo.commandBufferCount = mFrameCommandBuffers.size(); + submitInfo.pCommandBuffers = mFrameCommandBuffers.data(); + CHKERR(mGraphicsQueue.submit(1, &submitInfo, VkFence(VK_NULL_HANDLE))); mDevice.waitIdle(); if (mDisplay->cfgR().screenshotScaleFactor != 1) { readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); } else { readImageToPixels(mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::ePresentSrcKHR, mScreenshotPixels); } - mSubmitQueue.clear(); + mFrameCommandBuffers.clear(); + submitInfo.waitSemaphoreCount = 0; } - if (mHasDrawnText) { - submitInfo.pWaitSemaphores = stageFinishedSemaphore; - waitStages[0] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pCommandBuffers = &mCommandBuffersText[mCurrentImageIndex]; - stageFinishedSemaphore = &mTextFinishedSemaphore[mCurrentFrame]; - submitInfo.pSignalSemaphores = stageFinishedSemaphore; - mSubmitQueue.emplace_back(submitInfo); - } - - if (mSubmitQueue.size()) { - vk::SubmitInfo& submitInfoLast = mSubmitQueue.back(); - vk::PipelineStageFlags presentWaitStage = vk::PipelineStageFlagBits::eAllCommands; // Last commit signals to present - submitInfoLast.pWaitDstStageMask = &presentWaitStage; - submitInfoLast.pSignalSemaphores = &mPresentSemaphore[mCurrentImageIndex]; - CHKERR(mGraphicsQueue.submit(mSubmitQueue.size(), mSubmitQueue.data(), mInFlightFence[mCurrentFrame])); + mFrameCommandBuffers.emplace_back(mCommandBuffersText[mCurrentImageIndex]); } + submitInfo.commandBufferCount = mFrameCommandBuffers.size(); + submitInfo.pCommandBuffers = mFrameCommandBuffers.data(); + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &mPresentSemaphore[mCurrentImageIndex]; + CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); vk::PresentInfoKHR presentInfo{}; - presentInfo.waitSemaphoreCount = mSubmitQueue.size() ? 1 : 0; + presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = &mPresentSemaphore[mCurrentImageIndex]; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &mSwapChain; @@ -1666,7 +1620,7 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command commandBuffer.begin(beginInfo); cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer); vk::Offset3D blitSizeSrc; blitSizeSrc.x = mRenderWidth; @@ -1685,8 +1639,8 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command imageBlitRegion.dstOffsets[1] = blitSizeDst; commandBuffer.blitImage(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eTransferSrcOptimal, mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); - cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput); // Next: text pass, or present (ordered by the present semaphore) + cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, {}, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput); // Next frame renders into it again commandBuffer.end(); } @@ -1753,7 +1707,7 @@ void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float m vk::Image& image = mDownsampleFSAA ? mDownsampleImages[mCurrentImageIndex + mImageCount].image : mMixImages[mCurrentImageIndex].image; vk::ImageLayout srcLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - cmdImageMemoryBarrier(commandBuffer, image, {}, vk::AccessFlagBits::eMemoryRead, srcLayout, vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eFragmentShader); + cmdImageMemoryBarrier(commandBuffer, image, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead, srcLayout, vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader); vk::RenderPassBeginInfo renderPassInfo{}; renderPassInfo.renderPass = mRenderPassTexture; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 286f51bf6a3f0..24afb12fadee4 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -175,13 +175,9 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mCommandBuffersText; std::vector mCommandBuffersTexture; std::vector mCommandBuffersMix; - std::vector mSubmitQueue; + std::vector mFrameCommandBuffers; // All command buffers of the current frame, submitted as one batch, ordered by render pass dependencies and barriers std::vector mCommandBufferUpToDate; std::vector mImageAvailableSemaphore; - std::vector mRenderFinishedSemaphore; - std::vector mTextFinishedSemaphore; - std::vector mMixFinishedSemaphore; - std::vector mDownsampleFinishedSemaphore; std::vector mPresentSemaphore; std::vector mInFlightFence; std::vector mImageFence; From fb1766037cacc804e62f77e770cba09010acf74f Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 09:43:01 +0200 Subject: [PATCH 31/48] GPU Vulkan Backend: fix taking screenshots if runtime uses non-standard row-pitch --- .../backend/GPUDisplayBackendVulkan.cxx | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 7f2d2353a9207..7d879fe4c0bcd 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -1911,19 +1911,25 @@ void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout layout, std::vector& pixels) { + const vk::Format format = mSurfaceFormat.format; + if (format != vk::Format::eB8G8R8A8Unorm && format != vk::Format::eB8G8R8A8Srgb && format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eR8G8B8A8Srgb) { + GPUError("Screenshot not supported for swapchain format %s, only 8 bit BGRA / RGBA", vk::to_string(format).c_str()); + pixels.clear(); + return; + } uint32_t width = mScreenWidth * mDisplay->cfgR().screenshotScaleFactor; uint32_t height = mScreenHeight * mDisplay->cfgR().screenshotScaleFactor; static constexpr int32_t bytesPerPixel = 4; pixels.resize(width * height * bytesPerPixel); - vk::Image dstImage, dstImage2, src2; - vk::DeviceMemory dstImageMemory, dstImageMemory2; - createImageI(mDevice, mPhysicalDevice, dstImage, dstImageMemory, width, height, vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, vk::ImageTiling::eLinear); + vk::Image dstImage, src; + vk::DeviceMemory dstImageMemory; + VulkanBuffer readBuffer = createBuffer(width * height * bytesPerPixel, nullptr, vk::BufferUsageFlagBits::eTransferDst, 0); vk::CommandBuffer cmdBuffer = getSingleTimeCommandBuffer(); cmdImageMemoryBarrier(cmdBuffer, image, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, layout, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); if (mDisplay->cfgR().screenshotScaleFactor != 1) { - createImageI(mDevice, mPhysicalDevice, dstImage2, dstImageMemory2, width, height, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); - cmdImageMemoryBarrier(cmdBuffer, dstImage2, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + createImageI(mDevice, mPhysicalDevice, dstImage, dstImageMemory, width, height, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); + cmdImageMemoryBarrier(cmdBuffer, dstImage, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); vk::Offset3D blitSizeSrc = {(int32_t)mRenderWidth, (int32_t)mRenderHeight, 1}; vk::Offset3D blitSizeDst = {(int32_t)width, (int32_t)height, 1}; vk::ImageBlit imageBlitRegion{}; @@ -1933,42 +1939,41 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout imageBlitRegion.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; imageBlitRegion.dstSubresource.layerCount = 1; imageBlitRegion.dstOffsets[1] = blitSizeDst; - cmdBuffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal, dstImage2, vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); - src2 = dstImage2; - cmdImageMemoryBarrier(cmdBuffer, dstImage2, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdBuffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); + src = dstImage; + cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); } else { - src2 = image; + src = image; } - cmdImageMemoryBarrier(cmdBuffer, dstImage, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - vk::ImageCopy imageCopyRegion{}; - imageCopyRegion.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; - imageCopyRegion.srcSubresource.layerCount = 1; - imageCopyRegion.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; - imageCopyRegion.dstSubresource.layerCount = 1; - imageCopyRegion.extent.width = width; - imageCopyRegion.extent.height = height; - imageCopyRegion.extent.depth = 1; - cmdBuffer.copyImage(src2, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, 1, &imageCopyRegion); + vk::BufferImageCopy copyRegion{}; + copyRegion.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent = vk::Extent3D{width, height, 1}; + cmdBuffer.copyImageToBuffer(src, vk::ImageLayout::eTransferSrcOptimal, readBuffer.buffer, 1, ©Region); - cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + vk::BufferMemoryBarrier hostBarrier{vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eHostRead, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, readBuffer.buffer, 0, VK_WHOLE_SIZE}; + cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &hostBarrier, 0, nullptr); cmdImageMemoryBarrier(cmdBuffer, image, vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferSrcOptimal, layout, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); submitSingleTimeCommandBuffer(cmdBuffer); - vk::ImageSubresource subResource{vk::ImageAspectFlagBits::eColor, 0, 0}; - vk::SubresourceLayout subResourceLayout = mDevice.getImageSubresourceLayout(dstImage, subResource); + const bool swapRB = format == vk::Format::eR8G8B8A8Unorm || format == vk::Format::eR8G8B8A8Srgb; // Raw copy in swapchain format, the BMP needs BGRA const char* data; - CHKERR(mDevice.mapMemory(dstImageMemory, 0, VK_WHOLE_SIZE, {}, (void**)&data)); - data += subResourceLayout.offset; - for (uint32_t i = 0; i < height; i++) { - memcpy(pixels.data() + i * width * bytesPerPixel, data + (height - i - 1) * width * bytesPerPixel, width * bytesPerPixel); - } - mDevice.unmapMemory(dstImageMemory); - mDevice.freeMemory(dstImageMemory, nullptr); - mDevice.destroyImage(dstImage, nullptr); + CHKERR(mDevice.mapMemory(readBuffer.memory, 0, VK_WHOLE_SIZE, {}, (void**)&data)); + for (uint32_t i = 0; i < height; i++) { // BMP rows are bottom-up + char* dst = pixels.data() + i * width * bytesPerPixel; + memcpy(dst, data + (height - i - 1) * width * bytesPerPixel, width * bytesPerPixel); + if (swapRB) { + for (uint32_t j = 0; j < width; j++) { + std::swap(dst[j * bytesPerPixel], dst[j * bytesPerPixel + 2]); + } + } + } + mDevice.unmapMemory(readBuffer.memory); + clearBuffer(readBuffer); if (mDisplay->cfgR().screenshotScaleFactor != 1) { - mDevice.freeMemory(dstImageMemory2, nullptr); - mDevice.destroyImage(dstImage2, nullptr); + mDevice.freeMemory(dstImageMemory, nullptr); + mDevice.destroyImage(dstImage, nullptr); } } From be638a562332925638ef18f5aafeeeb8a29561d0 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 10:45:14 +0200 Subject: [PATCH 32/48] GPU Vulkan Display: Switch to Vulkan 1.4 --- GPU/GPUTracking/Standalone/CMakeLists.txt | 2 +- GPU/GPUTracking/cmake/vulkan_display.cmake | 2 +- GPU/GPUTracking/display/CMakeLists.txt | 3 +- .../backend/GPUDisplayBackendVulkan.cxx | 111 ++++++++++++------ .../display/backend/GPUDisplayBackendVulkan.h | 3 +- 5 files changed, 79 insertions(+), 42 deletions(-) diff --git a/GPU/GPUTracking/Standalone/CMakeLists.txt b/GPU/GPUTracking/Standalone/CMakeLists.txt index a4c5817d1f7f9..ef02e1c819a0c 100644 --- a/GPU/GPUTracking/Standalone/CMakeLists.txt +++ b/GPU/GPUTracking/Standalone/CMakeLists.txt @@ -96,7 +96,7 @@ if(GPUCA_BUILD_EVENT_DISPLAY) find_package(Fontconfig REQUIRED) endif() if(GPUCA_BUILD_EVENT_DISPLAY_VULKAN) - find_package(Vulkan REQUIRED) + find_package(Vulkan 1.4.328 REQUIRED) if (Vulkan_GLSLC_EXECUTABLE STREQUAL "Vulkan_GLSLC_EXECUTABLE-NOTFOUND") message(FATAL_ERROR ${Vulkan_GLSLC_EXECUTABLE}) endif() diff --git a/GPU/GPUTracking/cmake/vulkan_display.cmake b/GPU/GPUTracking/cmake/vulkan_display.cmake index 7859742363755..59bc53da2ff76 100644 --- a/GPU/GPUTracking/cmake/vulkan_display.cmake +++ b/GPU/GPUTracking/cmake/vulkan_display.cmake @@ -25,7 +25,7 @@ function(add_glslc_shader TARGET SHADER) add_custom_command( OUTPUT ${spirv-file} - COMMAND ${Vulkan_GLSLC_EXECUTABLE} -o ${spirv-file} ${input-file-abs} -MD -MT ${spirv-file} -MF ${spirv-file}.d + COMMAND ${Vulkan_GLSLC_EXECUTABLE} --target-env=vulkan1.4 -o ${spirv-file} ${input-file-abs} -MD -MT ${spirv-file} -MF ${spirv-file}.d DEPENDS ${input-file-abs} DEPFILE ${spirv-file}.d COMMENT "Compiling GLSL to SPIRV: ${SHADER}" diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 6c617a5b75e03..0866ca5abe711 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -32,7 +32,8 @@ endif() if(Vulkan_FOUND) string(FIND "${Vulkan_VERSION}" "." vulkan_dot_index) - if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.3.0" OR vulkan_dot_index EQUAL -1 OR Vulkan_GLSLC_EXECUTABLE STREQUAL "Vulkan_GLSLC_EXECUTABLE-NOTFOUND") + if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.4.328" OR vulkan_dot_index EQUAL -1 OR Vulkan_GLSLC_EXECUTABLE STREQUAL "Vulkan_GLSLC_EXECUTABLE-NOTFOUND") + message(STATUS "Vulkan event display disabled: need Vulkan headers >= 1.4.328 (found ${Vulkan_VERSION}) and glslc") set(Vulkan_FOUND 0) endif() endif() diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 7d879fe4c0bcd..257ad6b60c9f4 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -273,12 +273,12 @@ static inline void clearVector(T& v, S func, bool downsize = true) // ---------------------------- VULKAN DEVICE MANAGEMENT ---------------------------- -static bool hasFeatures(const vk::PhysicalDeviceFeatures& available, const vk::PhysicalDeviceFeatures& required) // The struct consists only of VkBool32 members +template // Feature structs: sType, pNext, then only VkBool32 members (possibly followed by padding) +static bool hasFeatures(const T& available, const T& required) { - static_assert(sizeof(vk::PhysicalDeviceFeatures) % sizeof(vk::Bool32) == 0); - const vk::Bool32* a = reinterpret_cast(&available); - const vk::Bool32* r = reinterpret_cast(&required); - for (size_t i = 0; i < sizeof(vk::PhysicalDeviceFeatures) / sizeof(vk::Bool32); i++) { + const vk::Bool32* a = reinterpret_cast(reinterpret_cast(&available) + sizeof(VkBaseOutStructure)); + const vk::Bool32* r = reinterpret_cast(reinterpret_cast(&required) + sizeof(VkBaseOutStructure)); + for (size_t i = 0; i < (sizeof(T) - sizeof(VkBaseOutStructure)) / sizeof(vk::Bool32); i++) { if (r[i] && !a[i]) { return false; } @@ -286,13 +286,54 @@ static bool hasFeatures(const vk::PhysicalDeviceFeatures& available, const vk::P return true; } -double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const vk::PhysicalDeviceFeatures& reqDeviceFeatures, bool silent) +template +static bool hasFeatures(const vk::StructureChain& available, const vk::StructureChain& required) +{ + return (hasFeatures(available.template get(), required.template get()) && ...); +} + +template +static void clearFeatures(vk::StructureChain& chain) // Zero all feature flags including padding, which the member-wise comparison in hasFeatures reads +{ + ((memset(reinterpret_cast(&chain.template get()) + sizeof(VkBaseOutStructure), 0, sizeof(T) - sizeof(VkBaseOutStructure))), ...); +} + +template +static vk::StructureChain getFeatures(vk::PhysicalDevice device, const vk::StructureChain&) +{ + return device.getFeatures2(); +} + +double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const FeatureChain& reqDeviceFeatures, bool silent) { double score = -1.; vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); - vk::PhysicalDeviceFeatures deviceFeatures = device.getFeatures(); vk::PhysicalDeviceMemoryProperties memoryProperties = device.getMemoryProperties(); - if (!hasFeatures(deviceFeatures, reqDeviceFeatures)) { + if (deviceProperties.apiVersion < VK_API_VERSION_1_4) { + if (!silent) { + GPUInfo("%s ignored, supports only Vulkan %u.%u", &deviceProperties.deviceName[0], VK_API_VERSION_MAJOR(deviceProperties.apiVersion), VK_API_VERSION_MINOR(deviceProperties.apiVersion)); + } + return (-1); + } + + std::vector availableExtensions = device.enumerateDeviceExtensionProperties(nullptr); + uint32_t extensionsFound = 0; + for (uint32_t i = 0; i < reqDeviceExtensions.size(); i++) { + for (uint32_t j = 0; j < availableExtensions.size(); j++) { + if (strcmp(reqDeviceExtensions[i], availableExtensions[j].extensionName) == 0) { + extensionsFound++; + break; + } + } + } + if (extensionsFound < reqDeviceExtensions.size()) { + if (!silent) { + GPUInfo("%s ignored due to missing extensions", &deviceProperties.deviceName[0]); + } + return (-1); + } + + if (!hasFeatures(getFeatures(device, reqDeviceFeatures), reqDeviceFeatures)) { // Query only after the version and extension checks, the chain contains 1.4 and extension structs if (!silent) { GPUInfo("%s ignored due to missing features", &deviceProperties.deviceName[0]); } @@ -320,23 +361,6 @@ double GPUDisplayBackendVulkan::checkDevice(vk::PhysicalDevice device, const std return (-1); } - std::vector availableExtensions = device.enumerateDeviceExtensionProperties(nullptr); - uint32_t extensionsFound = 0; - for (uint32_t i = 0; i < reqDeviceExtensions.size(); i++) { - for (uint32_t j = 0; j < availableExtensions.size(); j++) { - if (strcmp(reqDeviceExtensions[i], availableExtensions[j].extensionName) == 0) { - extensionsFound++; - break; - } - } - } - if (extensionsFound < reqDeviceExtensions.size()) { - if (!silent) { - GPUInfo("%s ignored due to missing extensions", &deviceProperties.deviceName[0]); - } - return (-1); - } - updateSwapChainDetails(device); if (mSwapChainDetails.formats.empty() || mSwapChainDetails.presentModes.empty()) { if (!silent) { @@ -369,7 +393,7 @@ void GPUDisplayBackendVulkan::createDevice() appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "GPU CI Standalone Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); - appInfo.apiVersion = VK_API_VERSION_1_0; + appInfo.apiVersion = VK_API_VERSION_1_4; vk::InstanceCreateInfo instanceCreateInfo; instanceCreateInfo.pApplicationInfo = &appInfo; @@ -377,6 +401,19 @@ void GPUDisplayBackendVulkan::createDevice() const char** frontendExtensions; uint32_t frontendExtensionCount = mDisplay->frontend()->getReqVulkanExtensions(frontendExtensions); std::vector reqInstanceExtensions(frontendExtensions, frontendExtensions + frontendExtensionCount); + reqInstanceExtensions.insert(reqInstanceExtensions.end(), {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME}); // The latter two are required by VK_KHR_swapchain_maintenance1 + std::vector availableInstanceExtensions = vk::enumerateInstanceExtensionProperties(nullptr); + if (mDisplay->GetProcessingSettings().debugLevel >= 3) { + std::cout << "available instance extensions: " << availableInstanceExtensions.size() << "\n"; + for (const auto& extension : availableInstanceExtensions) { + std::cout << '\t' << extension.extensionName << '\n'; + } + } + for (const char* ext : reqInstanceExtensions) { + if (std::none_of(availableInstanceExtensions.begin(), availableInstanceExtensions.end(), [ext](const auto& e) { return strcmp(ext, e.extensionName) == 0; })) { + throw std::runtime_error(std::string("Required Vulkan instance extension not available: ") + ext); + } + } const std::vector reqValidationLayers = {"VK_LAYER_KHRONOS_validation"}; auto debugCallback = [](vk::DebugUtilsMessageSeverityFlagBitsEXT messageSeverity, vk::DebugUtilsMessageTypeFlagsEXT messageType, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) -> VkBool32 { @@ -412,7 +449,6 @@ void GPUDisplayBackendVulkan::createDevice() if (checkVulkanLayersSupported(reqValidationLayers)) { throw std::runtime_error("Requested validation layer support not available"); } - reqInstanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); instanceCreateInfo.enabledLayerCount = static_cast(reqValidationLayers.size()); instanceCreateInfo.ppEnabledLayerNames = reqValidationLayers.data(); instanceCreateInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; @@ -435,21 +471,19 @@ void GPUDisplayBackendVulkan::createDevice() GPUInfo("Enabling Vulkan Validation Layers"); mDebugMessenger = mInstance.createDebugUtilsMessengerEXT(debugCreateInfo, nullptr); } - std::vector extensions = vk::enumerateInstanceExtensionProperties(nullptr); - if (mDisplay->GetProcessingSettings().debugLevel >= 3) { - std::cout << "available instance extensions: " << extensions.size() << "\n"; - for (const auto& extension : extensions) { - std::cout << '\t' << extension.extensionName << '\n'; - } - } if (mDisplay->frontend()->getVulkanSurface(&mInstance, &mSurface)) { throw std::runtime_error("Frontend does not provide Vulkan surface"); } - const std::vector reqDeviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; - vk::PhysicalDeviceFeatures reqDeviceFeatures{}; - reqDeviceFeatures.geometryShader = reqDeviceFeatures.wideLines = reqDeviceFeatures.largePoints = VK_TRUE; + const std::vector reqDeviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME}; + FeatureChain reqDeviceFeatures; // Exactly these features are required and enabled + clearFeatures(reqDeviceFeatures); + auto& reqFeatures10 = reqDeviceFeatures.get().features; + reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = VK_TRUE; + reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; + reqDeviceFeatures.get().presentId = VK_TRUE; + reqDeviceFeatures.get().presentWait = VK_TRUE; mPhysicalDevice = VkPhysicalDevice(VK_NULL_HANDLE); std::vector devices = mInstance.enumeratePhysicalDevices(); @@ -502,7 +536,8 @@ void GPUDisplayBackendVulkan::createDevice() vk::DeviceCreateInfo deviceCreateInfo{}; deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; deviceCreateInfo.queueCreateInfoCount = 1; - deviceCreateInfo.pEnabledFeatures = &reqDeviceFeatures; + deviceCreateInfo.pNext = &reqDeviceFeatures.get(); + deviceCreateInfo.pEnabledFeatures = nullptr; deviceCreateInfo.enabledExtensionCount = static_cast(reqDeviceExtensions.size()); deviceCreateInfo.ppEnabledExtensionNames = reqDeviceExtensions.data(); mDevice = mPhysicalDevice.createDevice(deviceCreateInfo, nullptr); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 24afb12fadee4..570e31d05193f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -83,7 +83,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend float getYFactor() const override { return -1.0f; } int32_t getMaxMSAA() const override { return mMaxMSAAsupported; } - double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const vk::PhysicalDeviceFeatures& reqDeviceFeatures, bool silent = false); + using FeatureChain = vk::StructureChain; + double checkDevice(vk::PhysicalDevice device, const std::vector& reqDeviceExtensions, const FeatureChain& reqDeviceFeatures, bool silent = false); vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities); void transitionImageLayout(vk::CommandBuffer commandBuffer, vk::Image image, vk::Format format, vk::ImageLayout oldLayout, vk::ImageLayout newLayout); VulkanBuffer createBuffer(size_t size, const void* srcData = nullptr, vk::BufferUsageFlags type = vk::BufferUsageFlagBits::eVertexBuffer, int32_t deviceMemory = 1); From df422c36dc1c0e69aea2a9f691ef3acb8d5f4b50 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 13:19:19 +0200 Subject: [PATCH 33/48] GPU Vulkan Backend: We were using multi-draw-indirect all the time without requesting it --- GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 257ad6b60c9f4..a4dec3c870fc2 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -480,7 +480,7 @@ void GPUDisplayBackendVulkan::createDevice() FeatureChain reqDeviceFeatures; // Exactly these features are required and enabled clearFeatures(reqDeviceFeatures); auto& reqFeatures10 = reqDeviceFeatures.get().features; - reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = VK_TRUE; + reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = reqFeatures10.multiDrawIndirect = VK_TRUE; reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; reqDeviceFeatures.get().presentId = VK_TRUE; reqDeviceFeatures.get().presentWait = VK_TRUE; From 744df575bdfc26c79432a52f23a375b6b750bd98 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 13:15:35 +0200 Subject: [PATCH 34/48] GPU OpenGL Display: Switch to using SPIR-V shaders --- GPU/GPUTracking/cmake/vulkan_display.cmake | 36 -- GPU/GPUTracking/display/CMakeLists.txt | 66 ++- .../backend/GPUDisplayBackendOpenGL.cxx | 102 +--- .../display/backend/GPUDisplayBackendOpenGL.h | 14 +- .../backend/GPUDisplayBackendVulkan.cxx | 11 +- .../display/frontend/GPUDisplayFrontend.h | 4 +- .../GPUDisplayBackendOpenGLMagneticField.cxx | 62 +-- .../helpers/GPUDisplayMagneticField.cxx | 4 +- .../display/helpers/GPUDisplayMagneticField.h | 34 +- .../GPUDisplayMagneticFieldConstants.h | 36 ++ GPU/GPUTracking/display/shaders.cmake | 68 +++ .../display/shaders/GPUDisplayShaders.h | 471 ------------------ GPU/GPUTracking/display/shaders/field.geom | 378 ++++++++++++++ GPU/GPUTracking/display/shaders/fragment.frag | 8 +- .../display/shaders/fragmentText.frag | 11 +- .../display/shaders/fragmentTexture.frag | 9 +- .../display/shaders/fragmentUniform.frag | 8 - GPU/GPUTracking/display/shaders/vertex.vert | 8 +- .../display/shaders/vertexPassthrough.vert | 21 + .../display/shaders/vertexPoint.vert | 13 +- .../display/shaders/vertexTexture.vert | 8 +- 21 files changed, 675 insertions(+), 697 deletions(-) delete mode 100644 GPU/GPUTracking/cmake/vulkan_display.cmake create mode 100644 GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h create mode 100644 GPU/GPUTracking/display/shaders.cmake delete mode 100644 GPU/GPUTracking/display/shaders/GPUDisplayShaders.h create mode 100644 GPU/GPUTracking/display/shaders/field.geom delete mode 100644 GPU/GPUTracking/display/shaders/fragmentUniform.frag create mode 100644 GPU/GPUTracking/display/shaders/vertexPassthrough.vert diff --git a/GPU/GPUTracking/cmake/vulkan_display.cmake b/GPU/GPUTracking/cmake/vulkan_display.cmake deleted file mode 100644 index 59bc53da2ff76..0000000000000 --- a/GPU/GPUTracking/cmake/vulkan_display.cmake +++ /dev/null @@ -1,36 +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 vulkan_display.cmake -# author David Rohr - -if (GLSLC STREQUAL "GLSLC-NOTFOUND") - message(FATAL_ERROR "glslc not found") -endif() - -function(add_glslc_shader TARGET SHADER) - get_filename_component(input-file-abs ${SHADER} ABSOLUTE) - FILE(RELATIVE_PATH input-file-rel ${CMAKE_CURRENT_SOURCE_DIR} ${input-file-abs}) - set(spirv-file ${CMAKE_CURRENT_BINARY_DIR}/shaders/${input-file-rel}.spv) - get_filename_component(output-dir ${spirv-file} DIRECTORY) - file(MAKE_DIRECTORY ${output-dir}) - - add_custom_command( - OUTPUT ${spirv-file} - COMMAND ${Vulkan_GLSLC_EXECUTABLE} --target-env=vulkan1.4 -o ${spirv-file} ${input-file-abs} -MD -MT ${spirv-file} -MF ${spirv-file}.d - DEPENDS ${input-file-abs} - DEPFILE ${spirv-file}.d - COMMENT "Compiling GLSL to SPIRV: ${SHADER}" - VERBATIM - ) - - add_binary_resource(${TARGET} ${spirv-file}) -endfunction(add_glslc_shader) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 0866ca5abe711..308dfb8a6edf7 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -30,15 +30,46 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") set_package_properties(Qt6 PROPERTIES TYPE OPTIONAL) endif() +# glslc compiles the shaders to SPIR-V for both backends +if(Vulkan_GLSLC_EXECUTABLE AND NOT Vulkan_GLSLC_EXECUTABLE MATCHES "NOTFOUND") + set(GPUCA_GLSLC ${Vulkan_GLSLC_EXECUTABLE}) +else() + find_program(GPUCA_GLSLC glslc) +endif() + +# OpenGL backend: OpenGL 4.6 headers (bundled gl3w, or GLEW) for SPIR-V shaders, and glslc +if(GPUCA_GLSLC) + include(CheckSymbolExists) + if(ALIGPU_BUILD_TYPE STREQUAL "O2" OR GPUCA_CONFIG_GL3W) + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty) + check_symbol_exists(GL_VERSION_4_6 "GL/glcorearb.h" GPUCA_GL3W_HAS_GL_4_6) # Bundled header + if(GPUCA_GL3W_HAS_GL_4_6) + set(GPUCA_EVENT_DISPLAY_OPENGL ON) + endif() + else() + set(CMAKE_REQUIRED_INCLUDES ${GLEW_INCLUDE_DIRS}) + check_symbol_exists(GL_VERSION_4_6 "GL/glew.h" GPUCA_GLEW_HAS_GL_4_6) + if(GPUCA_GLEW_HAS_GL_4_6) + set(GPUCA_EVENT_DISPLAY_OPENGL ON) + endif() + endif() + unset(CMAKE_REQUIRED_INCLUDES) +endif() +if(NOT GPUCA_EVENT_DISPLAY_OPENGL) + if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") + message(FATAL_ERROR "OpenGL event display needs OpenGL 4.6 headers and glslc") + endif() + message(STATUS "OpenGL event display backend disabled: needs OpenGL 4.6 headers and glslc") +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 Vulkan_GLSLC_EXECUTABLE STREQUAL "Vulkan_GLSLC_EXECUTABLE-NOTFOUND") + if(NOT ${Vulkan_VERSION} VERSION_GREATER_EQUAL "1.4.328" OR vulkan_dot_index EQUAL -1 OR NOT GPUCA_GLSLC) message(STATUS "Vulkan event display disabled: need Vulkan headers >= 1.4.328 (found ${Vulkan_VERSION}) and glslc") set(Vulkan_FOUND 0) endif() endif() if(Vulkan_FOUND) - include(../cmake/vulkan_display.cmake) set(GPUCA_EVENT_DISPLAY_VULKAN ON) if(O2GPUWayland_FOUND) set(GPUCA_EVENT_DISPLAY_WAYLAND ON) @@ -72,7 +103,7 @@ set(SRCS_NO_H helpers/GPUDisplayLoader.cxx helpers/GPUDisplayROOT.cxx frontend/GPUDisplayKeys.cxx) -set(HDRS_INSTALL shaders/GPUDisplayShaders.h) +set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) @@ -160,7 +191,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/setinclude.sh PERMISSIONS OWNER_READ O target_compile_definitions(${targetName} PRIVATE $) -message(STATUS "Building GPU Event Display (Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${Fontconfig_FOUND}, Qt ${GPUCA_EVENT_DISPLAY_QT})") +message(STATUS "Building GPU Event Display (OpenGL ${GPUCA_EVENT_DISPLAY_OPENGL}, Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${Fontconfig_FOUND}, Qt ${GPUCA_EVENT_DISPLAY_QT})") target_link_libraries(${targetName} PUBLIC ${GLFW_LIBRARIES} OpenGL::GL) target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) @@ -193,17 +224,30 @@ if (OPENGL_GLU_FOUND) target_link_libraries(${targetName} PRIVATE OpenGL::GLU) endif() +if(GPUCA_EVENT_DISPLAY_OPENGL) + target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_OPENGL) +endif() + +if(GPUCA_EVENT_DISPLAY_VULKAN OR GPUCA_EVENT_DISPLAY_OPENGL) + include(shaders.cmake) + set(SHADERS shaders/vertex.vert + shaders/vertexPoint.vert + shaders/vertexTexture.vert + shaders/vertexPassthrough.vert + shaders/fragment.frag + shaders/fragmentText.frag + shaders/fragmentTexture.frag + shaders/field.geom) + foreach(shader ${SHADERS}) + add_glslc_shader(${targetName} ${shader}) + endforeach() + write_glslc_shader_header(${targetName} GPUDisplayLoadShaders.h) +endif() + if(GPUCA_EVENT_DISPLAY_VULKAN) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_VULKAN) target_link_libraries(${targetName} PRIVATE Vulkan::Vulkan) - add_glslc_shader(${targetName} shaders/fragment.frag) - add_glslc_shader(${targetName} shaders/vertex.vert) - add_glslc_shader(${targetName} shaders/fragmentText.frag) - add_glslc_shader(${targetName} shaders/vertexTexture.vert) - add_glslc_shader(${targetName} shaders/fragmentTexture.frag) - add_glslc_shader(${targetName} shaders/fragmentUniform.frag) - add_glslc_shader(${targetName} shaders/vertexPoint.vert) if(GPUCA_EVENT_DISPLAY_WAYLAND) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_WAYLAND) target_link_libraries(${targetName} PRIVATE GPUWayland::wayland-client) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index 9d9a413d5bb04..435664c51eacb 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -25,7 +25,6 @@ #include "GPUCommonDef.h" #include "GPUDisplayBackendOpenGL.h" -#include "shaders/GPUDisplayShaders.h" #include "GPUDisplay.h" #include "GPULogging.h" #include "GPUParam.h" @@ -35,25 +34,17 @@ using namespace o2::gpu; -#ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN -#include "utils/qGetLdBinarySymbols.h" -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertex_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragmentUniform_frag_spv); -#endif - -// Runtime minimum version defined in GPUDisplayFrontend.h, keep in sync! -#define GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#if !defined(GL_VERSION_4_5) || GL_VERSION_4_5 != 1 -#ifdef GPUCA_STANDALONE -#warning Unsupported OpenGL version < 4.5, disabling standalone event display -#error Unsupported OpenGL version < 4.5 -#else -#pragma message "Unsupported OpenGL version < 4.5, disabling standalone event display" -#endif -#undef GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#endif +#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL // Set by CMake if OpenGL 4.6 headers and glslc are available, runtime minimum version in GPUDisplayFrontend.h +#define GPUCA_DISPLAY_SHADER_TYPE_OPENGL +#include "GPUDisplayLoadShaders.h" -#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL +uint32_t GPUDisplayBackendOpenGL::loadShader(uint32_t type, const char* spirv, size_t size) +{ + uint32_t shader = glCreateShader(type); + glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, spirv, size); + glSpecializeShader(shader, "main", 0, nullptr, nullptr); + return shader; +} GPUDisplayBackendOpenGL::GPUDisplayBackendOpenGL() { @@ -225,13 +216,7 @@ void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) } else #endif { - if (mSPIRVShaders) { - CHKERR(glBindBuffer(GL_UNIFORM_BUFFER, mSPIRVColorBuffer)); - CHKERR(glBufferData(GL_UNIFORM_BUFFER, sizeof(color), &color[0], GL_STATIC_DRAW)); - CHKERR(glBindBufferBase(GL_UNIFORM_BUFFER, 1, mSPIRVColorBuffer)); - } else { - CHKERR(glUniform4fv(mColorId, 1, &color[0])); - } + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); } } @@ -322,33 +307,11 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() #endif setDepthBuffer(); setQuality(); - CHKERR(mVertexShader = glCreateShader(GL_VERTEX_SHADER)); - CHKERR(mFragmentShader = glCreateShader(GL_FRAGMENT_SHADER)); - CHKERR(mVertexShaderTexture = glCreateShader(GL_VERTEX_SHADER)); - CHKERR(mFragmentShaderTexture = glCreateShader(GL_FRAGMENT_SHADER)); - CHKERR(mFragmentShaderText = glCreateShader(GL_FRAGMENT_SHADER)); -#if defined(GL_VERSION_4_6) && GL_VERSION_4_6 == 1 && defined(GPUCA_BUILD_EVENT_DISPLAY_VULKAN) - if (getenv("USE_SPIRV_SHADERS") && atoi(getenv("USE_SPIRV_SHADERS"))) { - CHKERR(glShaderBinary(1, &mVertexShader, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB, _binary_shaders_shaders_vertex_vert_spv_start, _binary_shaders_shaders_vertex_vert_spv_len)); - CHKERR(glSpecializeShader(mVertexShader, "main", 0, nullptr, nullptr)); - CHKERR(glShaderBinary(1, &mFragmentShader, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB, _binary_shaders_shaders_fragmentUniform_frag_spv_start, _binary_shaders_shaders_fragmentUniform_frag_spv_len)); - CHKERR(glSpecializeShader(mFragmentShader, "main", 0, nullptr, nullptr)); - GPUInfo("Using SPIR-V shaders"); - mSPIRVShaders = true; - } else -#endif - { - CHKERR(glShaderSource(mVertexShader, 1, &GPUDisplayShaders::vertexShader, nullptr)); - CHKERR(glCompileShader(mVertexShader)); - CHKERR(glShaderSource(mFragmentShader, 1, &GPUDisplayShaders::fragmentShader, nullptr)); - CHKERR(glCompileShader(mFragmentShader)); - } - CHKERR(glShaderSource(mVertexShaderTexture, 1, &GPUDisplayShaders::vertexShaderTexture, nullptr)); - CHKERR(glCompileShader(mVertexShaderTexture)); - CHKERR(glShaderSource(mFragmentShaderTexture, 1, &GPUDisplayShaders::fragmentShaderTexture, nullptr)); - CHKERR(glCompileShader(mFragmentShaderTexture)); - CHKERR(glShaderSource(mFragmentShaderText, 1, &GPUDisplayShaders::fragmentShaderText, nullptr)); - CHKERR(glCompileShader(mFragmentShaderText)); + CHKERR(mVertexShader = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertex_vert_spv_start, _binary_opengl_vertex_vert_spv_len)); + CHKERR(mFragmentShader = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragment_frag_spv_start, _binary_opengl_fragment_frag_spv_len)); + CHKERR(mVertexShaderTexture = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexTexture_vert_spv_start, _binary_opengl_vertexTexture_vert_spv_len)); + CHKERR(mFragmentShaderTexture = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentTexture_frag_spv_start, _binary_opengl_fragmentTexture_frag_spv_len)); + CHKERR(mFragmentShaderText = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentText_frag_spv_start, _binary_opengl_fragmentText_frag_spv_len)); if (checkShaderStatus(mVertexShader) || checkShaderStatus(mFragmentShader) || checkShaderStatus(mVertexShaderTexture) || checkShaderStatus(mFragmentShaderTexture) || checkShaderStatus(mFragmentShaderText)) { return 1; } @@ -359,13 +322,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgram)) { return 1; } - if (mSPIRVShaders) { - CHKERR(glGenBuffers(1, &mSPIRVModelViewBuffer)); - CHKERR(glGenBuffers(1, &mSPIRVColorBuffer)); - } else { - CHKERR(mModelViewProjId = glGetUniformLocation(mShaderProgram, "ModelViewProj")); - CHKERR(mColorId = glGetUniformLocation(mShaderProgram, "color")); - } CHKERR(mShaderProgramText = glCreateProgram()); CHKERR(glAttachShader(mShaderProgramText, mVertexShaderTexture)); CHKERR(glAttachShader(mShaderProgramText, mFragmentShaderText)); @@ -373,8 +329,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgramText)) { return 1; } - CHKERR(mModelViewProjIdText = glGetUniformLocation(mShaderProgramText, "projection")); - CHKERR(mColorIdText = glGetUniformLocation(mShaderProgramText, "textColor")); CHKERR(mShaderProgramTexture = glCreateProgram()); CHKERR(glAttachShader(mShaderProgramTexture, mVertexShaderTexture)); CHKERR(glAttachShader(mShaderProgramTexture, mFragmentShaderTexture)); @@ -382,8 +336,6 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgramTexture)) { return 1; } - CHKERR(mModelViewProjIdTexture = glGetUniformLocation(mShaderProgramTexture, "projection")); - CHKERR(mAlphaIdTexture = glGetUniformLocation(mShaderProgramTexture, "alpha")); CHKERR(glGenVertexArrays(1, &mVertexArray)); CHKERR(glGenVertexArrays(1, &VAO_texture)); @@ -425,10 +377,6 @@ void GPUDisplayBackendOpenGL::ExitBackendA() CHKERR(glDeleteTextures(1, &symbol.texId)); } } - if (mSPIRVShaders) { - CHKERR(glDeleteBuffers(1, &mSPIRVModelViewBuffer)); - CHKERR(glDeleteBuffers(1, &mSPIRVColorBuffer)); - } if (mMagneticFieldVisualization) { ExitMagFieldVisualization(); } @@ -522,13 +470,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& #endif { const hmm_mat4 modelViewProj = proj * view; - if (mSPIRVShaders) { - glBindBuffer(GL_UNIFORM_BUFFER, mSPIRVModelViewBuffer); - glBufferData(GL_UNIFORM_BUFFER, sizeof(modelViewProj), &modelViewProj, GL_STATIC_DRAW); - glBindBufferBase(GL_UNIFORM_BUFFER, 0, mSPIRVModelViewBuffer); - } else { - CHKERR(glUniformMatrix4fv(mModelViewProjId, 1, GL_FALSE, &modelViewProj.Elements[0][0])); - } + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &modelViewProj.Elements[0][0])); if (mMagneticFieldVisualization) { CHKERR(glNamedBufferSubData(mFieldModelViewBuffer, 0, sizeof(modelViewProj), &modelViewProj)); } @@ -603,9 +545,9 @@ void GPUDisplayBackendOpenGL::prepareText() CHKERR(glUseProgram(mShaderProgramText)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_text)); - CHKERR(glUniformMatrix4fv(mModelViewProjIdText, 1, GL_FALSE, &proj.Elements[0][0])); + CHKERR(glUniformMatrix4fv(UNIFORM_LOCATION_MATRIX, 1, GL_FALSE, &proj.Elements[0][0])); float color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - CHKERR(glUniform4fv(mColorIdText, 1, &color[0])); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, &color[0])); } } @@ -635,8 +577,8 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) CHKERR(glUseProgram(mShaderProgramTexture)); CHKERR(glActiveTexture(GL_TEXTURE0)); CHKERR(glBindVertexArray(VAO_texture)); - CHKERR(glUniformMatrix4fv(mModelViewProjIdTexture, 1, GL_FALSE, &proj.Elements[0][0])); - CHKERR(glUniform1f(mAlphaIdTexture, mixSlaveImage)); + 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)); @@ -737,7 +679,7 @@ void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { return; } - CHKERR(glUniform4fv(mColorIdText, 1, color)); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, color)); if (smoothFont()) { scale *= 0.25f; // Font size is 48 to have nice bitmap, scale to size 12 diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index 51c0a30c2a856..60a65a4498a7f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -49,6 +49,7 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void setFrameBuffer(uint32_t newID = 0); int32_t InitBackendA() override; int32_t InitMagFieldVisualization(); + bool mMagneticFieldFailed = false; // Initialization of the field drawing failed, do not retry void ExitBackendA() override; void ExitMagFieldVisualization(); static int32_t checkShaderStatus(uint32_t shader); @@ -68,6 +69,9 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend void lineWidthFactor(float factor) override; size_t needMultiVBO() override { return 0x100000000ll; } void readImageToPixels(); + static uint32_t loadShader(uint32_t type, const char* spirv, size_t size); // Create shader from SPIR-V + static constexpr int32_t UNIFORM_LOCATION_MATRIX = 0; // Explicit uniform locations in the OpenGL variant of the shaders (shaders/*.vert, *.frag) + static constexpr int32_t UNIFORM_LOCATION_COLOR = 4; // color, textColor, alpha 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; @@ -94,14 +98,6 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mIndirectId; std::vector mVBOId; std::vector mFontSymbols; - int32_t mModelViewProjId; - int32_t mColorId; - int32_t mModelViewProjIdTexture; - int32_t mAlphaIdTexture; - int32_t mModelViewProjIdText; - int32_t mColorIdText; - uint32_t mSPIRVModelViewBuffer; - uint32_t mSPIRVColorBuffer; uint32_t mFieldModelViewBuffer; uint32_t mFieldModelConstantsBuffer; @@ -116,8 +112,6 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t VAO_field, VBO_field; - bool mSPIRVShaders = false; - GLfb mMixBuffer; GLfb mOffscreenBufferMSAA, mOffscreenBuffer; }; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index a4dec3c870fc2..bc0c8184bdccb 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -27,13 +27,8 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE using namespace o2::gpu; -#include "utils/qGetLdBinarySymbols.h" -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertex_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragment_frag_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertexPoint_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_vertexTexture_vert_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragmentTexture_frag_spv); -QGET_LD_BINARY_SYMBOLS(shaders_shaders_fragmentText_frag_spv); +#define GPUCA_DISPLAY_SHADER_TYPE_VULKAN +#include "GPUDisplayLoadShaders.h" // #define CHKERR(cmd) {cmd;} #define CHKERR(cmd) \ @@ -1292,7 +1287,7 @@ void GPUDisplayBackendVulkan::clearPipeline() // ---------------------------- VULKAN SHADERS ---------------------------- #define LOAD_SHADER(file, ext) \ - mShaders[#file] = createShaderModule(_binary_shaders_shaders_##file##_##ext##_spv_start, _binary_shaders_shaders_##file##_##ext##_spv_len, mDevice) + mShaders[#file] = createShaderModule(_binary_vulkan_##file##_##ext##_spv_start, _binary_vulkan_##file##_##ext##_spv_len, mDevice) void GPUDisplayBackendVulkan::createShaders() { diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h index d53cd5f1f621e..03f5a0524c817 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontend.h @@ -47,9 +47,9 @@ class GPUDisplayFrontend : public GPUDisplayFrontendInterface TYPE_NONE = 5 }; - // Compile time minimum version defined in GPUDisplay.h, keep in sync! + // Compile time minimum version checked in display/CMakeLists.txt (OpenGL 4.6 headers for SPIR-V shaders), keep in sync! static constexpr int32_t GL_MIN_VERSION_MAJOR = 4; - static constexpr int32_t GL_MIN_VERSION_MINOR = 5; + 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 diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx index 4472fcd087708..6d6a928824285 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx @@ -22,26 +22,19 @@ #else #define gluErrorString(err) "" #endif -#ifndef GPUCA_NO_FMT -#include -#endif #include "GPUCommonDef.h" #include "GPUDisplayMagneticField.h" #include "backend/GPUDisplayBackendOpenGL.h" -#include "shaders/GPUDisplayShaders.h" #include "GPUDisplay.h" #include "GPULogging.h" using namespace o2::gpu; -// Runtime minimum version defined in GPUDisplayFrontend.h, keep in sync! -#define GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#if !defined(GL_VERSION_4_5) || GL_VERSION_4_5 != 1 -#undef GPUCA_BUILD_EVENT_DISPLAY_OPENGL -#endif +#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL // Set by CMake, see GPUDisplayBackendOpenGL.cxx -#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL +#define GPUCA_DISPLAY_SHADER_TYPE_OPENGL +#include "GPUDisplayLoadShaders.h" #define CHKERR(cmd) \ do { \ @@ -55,40 +48,19 @@ using namespace o2::gpu; int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() { -#ifndef GPUCA_NO_FMT mMagneticFieldVisualization = std::make_unique(); + if (!mMagneticFieldVisualization->mValid) { + GPUError("Magnetic field data not available, field drawing disabled"); + mMagneticFieldVisualization.reset(); + return 1; + } mMagneticFieldVisualization->generateSeedPoints(mDisplay->cfgL().bFieldLinesCount); - CHKERR(mVertexShaderPassthrough = glCreateShader(GL_VERTEX_SHADER)); - CHKERR(mGeometryShader = glCreateShader(GL_GEOMETRY_SHADER)); - - CHKERR(glShaderSource(mVertexShaderPassthrough, 1, &GPUDisplayShaders::vertexShaderPassthrough, nullptr)); - CHKERR(glCompileShader(mVertexShaderPassthrough)); - - const auto constantsExpanded = fmt::format(GPUDisplayShaders::fieldModelShaderConstants, - fmt::arg("dimensions", GPUDisplayMagneticField::DIMENSIONS), - fmt::arg("solZSegs", GPUDisplayMagneticField::MAX_SOLENOID_Z_SEGMENTS), - fmt::arg("solPSegs", GPUDisplayMagneticField::MAX_SOLENOID_P_SEGMENTS), - fmt::arg("solRSegs", GPUDisplayMagneticField::MAX_SOLENOID_R_SEGMENTS), - fmt::arg("solParams", GPUDisplayMagneticField::MAX_SOLENOID_PARAMETERIZATIONS), - fmt::arg("solRows", GPUDisplayMagneticField::MAX_SOLENOID_ROWS), - fmt::arg("solColumns", GPUDisplayMagneticField::MAX_SOLENOID_COLUMNS), - fmt::arg("solCoeffs", GPUDisplayMagneticField::MAX_SOLENOID_COEFFICIENTS), - fmt::arg("dipZSegs", GPUDisplayMagneticField::MAX_DIPOLE_Z_SEGMENTS), - fmt::arg("dipYSegs", GPUDisplayMagneticField::MAX_DIPOLE_Y_SEGMENTS), - fmt::arg("dipXSegs", GPUDisplayMagneticField::MAX_DIPOLE_X_SEGMENTS), - fmt::arg("dipParams", GPUDisplayMagneticField::MAX_DIPOLE_PARAMETERIZATIONS), - fmt::arg("dipRows", GPUDisplayMagneticField::MAX_DIPOLE_ROWS), - fmt::arg("dipColumns", GPUDisplayMagneticField::MAX_DIPOLE_COLUMNS), - fmt::arg("dipCoeffs", GPUDisplayMagneticField::MAX_DIPOLE_COEFFICIENTS), - fmt::arg("maxChebOrder", GPUDisplayMagneticField::MAX_CHEBYSHEV_ORDER)); - - std::array geomShaderSource = {GPUDisplayShaders::geometryShaderP1, constantsExpanded.c_str(), GPUDisplayShaders::fieldModelShaderCode, GPUDisplayShaders::geometryShaderP2}; - - CHKERR(glShaderSource(mGeometryShader, geomShaderSource.size(), geomShaderSource.data(), nullptr)); - CHKERR(glCompileShader(mGeometryShader)); + CHKERR(mVertexShaderPassthrough = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexPassthrough_vert_spv_start, _binary_opengl_vertexPassthrough_vert_spv_len)); + CHKERR(mGeometryShader = loadShader(GL_GEOMETRY_SHADER, _binary_opengl_field_geom_spv_start, _binary_opengl_field_geom_spv_len)); if (checkShaderStatus(mVertexShaderPassthrough) || checkShaderStatus(mGeometryShader)) { + mMagneticFieldVisualization.reset(); return 1; } @@ -99,6 +71,7 @@ int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() CHKERR(glLinkProgram(mShaderProgramField)); if (checkProgramStatus(mShaderProgramField)) { + mMagneticFieldVisualization.reset(); return 1; } @@ -131,9 +104,6 @@ int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() CHKERR(glNamedBufferData(mDipoleSegmentsBuffer, sizeof(GPUDisplayMagneticField::DipoleSegmentsUniform), mMagneticFieldVisualization->mDipoleSegments.get(), GL_STREAM_DRAW)); CHKERR(glCreateBuffers(1, &mDipoleParameterizationBuffer)); CHKERR(glNamedBufferData(mDipoleParameterizationBuffer, sizeof(GPUDisplayMagneticField::DipoleParameterizationUniform), mMagneticFieldVisualization->mDipoleParameterization.get(), GL_STREAM_DRAW)); -#else - throw std::runtime_error("Magnetic field needs fmt"); -#endif return 0; } @@ -141,7 +111,10 @@ int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() uint32_t GPUDisplayBackendOpenGL::drawField() { if (!mMagneticFieldVisualization) { - return InitMagFieldVisualization(); // next frame will fill MVP matrix + if (!mMagneticFieldFailed && InitMagFieldVisualization()) { + mMagneticFieldFailed = true; + } + return 0; // next frame will fill MVP matrix } if (mMagneticFieldVisualization->mFieldLineSeedPoints.size() != (uint32_t)mDisplay->cfgL().bFieldLinesCount) { @@ -157,8 +130,7 @@ uint32_t GPUDisplayBackendOpenGL::drawField() CHKERR(glUseProgram(mShaderProgramField)); CHKERR(glBindBufferBase(GL_UNIFORM_BUFFER, 0, mFieldModelViewBuffer)); const std::array drawColor = {1.f, 0.f, 0.f, 1.f}; - const auto color = glGetUniformLocation(mShaderProgramField, "color"); - CHKERR(glUniform4fv(color, 1, drawColor.data())); + CHKERR(glUniform4fv(UNIFORM_LOCATION_COLOR, 1, drawColor.data())); CHKERR(glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, mFieldModelConstantsBuffer)); CHKERR(glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, mSolenoidSegmentsBuffer)); diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx index adfbc62ec47e1..683f2780aa8da 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.cxx @@ -32,13 +32,13 @@ using namespace o2::gpu; GPUDisplayMagneticField::GPUDisplayMagneticField() { - initializeUniforms(); + mValid = initializeUniforms() == 0; } #ifndef GPUCA_STANDALONE GPUDisplayMagneticField::GPUDisplayMagneticField(o2::field::MagneticField* field) { - initializeUniformsFromField(field); + mValid = initializeUniformsFromField(field) == 0; } #endif diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h index 72ac83280d29c..85d6d639e9c43 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h +++ b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticField.h @@ -16,6 +16,7 @@ #define GPUDISPLAYMAGNETICFIELD_H #include "GPUCommonDef.h" +#include "GPUDisplayMagneticFieldConstants.h" // Shared with the field shader #include #include @@ -33,22 +34,22 @@ class GPUDisplayMagneticField GPUDisplayMagneticField(o2::field::MagneticField* field); #endif - static constexpr std::size_t DIMENSIONS = 3; - static constexpr std::size_t MAX_SOLENOID_Z_SEGMENTS = 32; - static constexpr std::size_t MAX_SOLENOID_P_SEGMENTS = 512; - static constexpr std::size_t MAX_SOLENOID_R_SEGMENTS = 4096; - static constexpr std::size_t MAX_DIPOLE_Z_SEGMENTS = 128; - static constexpr std::size_t MAX_DIPOLE_Y_SEGMENTS = 2048; - static constexpr std::size_t MAX_DIPOLE_X_SEGMENTS = 16384; - static constexpr std::size_t MAX_SOLENOID_PARAMETERIZATIONS = 2048; - static constexpr std::size_t MAX_SOLENOID_ROWS = 16384; - static constexpr std::size_t MAX_SOLENOID_COLUMNS = 65536; - static constexpr std::size_t MAX_SOLENOID_COEFFICIENTS = 131072; - static constexpr std::size_t MAX_DIPOLE_PARAMETERIZATIONS = 2048; - static constexpr std::size_t MAX_DIPOLE_ROWS = 16384; - static constexpr std::size_t MAX_DIPOLE_COLUMNS = 65536; - static constexpr std::size_t MAX_DIPOLE_COEFFICIENTS = 262144; - static constexpr std::size_t MAX_CHEBYSHEV_ORDER = 32; + static constexpr std::size_t DIMENSIONS = GPUCA_DISPLAY_FIELD_DIMENSIONS; + static constexpr std::size_t MAX_SOLENOID_Z_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_Z_SEGMENTS; + static constexpr std::size_t MAX_SOLENOID_P_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_P_SEGMENTS; + static constexpr std::size_t MAX_SOLENOID_R_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_R_SEGMENTS; + static constexpr std::size_t MAX_DIPOLE_Z_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Z_SEGMENTS; + static constexpr std::size_t MAX_DIPOLE_Y_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Y_SEGMENTS; + static constexpr std::size_t MAX_DIPOLE_X_SEGMENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_X_SEGMENTS; + static constexpr std::size_t MAX_SOLENOID_PARAMETERIZATIONS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_PARAMETERIZATIONS; + static constexpr std::size_t MAX_SOLENOID_ROWS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_ROWS; + static constexpr std::size_t MAX_SOLENOID_COLUMNS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COLUMNS; + static constexpr std::size_t MAX_SOLENOID_COEFFICIENTS = GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COEFFICIENTS; + static constexpr std::size_t MAX_DIPOLE_PARAMETERIZATIONS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_PARAMETERIZATIONS; + static constexpr std::size_t MAX_DIPOLE_ROWS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_ROWS; + static constexpr std::size_t MAX_DIPOLE_COLUMNS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COLUMNS; + static constexpr std::size_t MAX_DIPOLE_COEFFICIENTS = GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COEFFICIENTS; + static constexpr std::size_t MAX_CHEBYSHEV_ORDER = GPUCA_DISPLAY_FIELD_MAX_CHEBYSHEV_ORDER; struct RenderConstantsUniform { uint32_t StepCount; @@ -134,6 +135,7 @@ class GPUDisplayMagneticField std::size_t mDipColumns; std::size_t mDipCoefficients; + bool mValid = false; // Field data loaded, buffers below filled std::unique_ptr mRenderConstantsUniform; std::unique_ptr mSolenoidSegments; std::unique_ptr mDipoleSegments; diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h new file mode 100644 index 0000000000000..5bc1c52562862 --- /dev/null +++ b/GPU/GPUTracking/display/helpers/GPUDisplayMagneticFieldConstants.h @@ -0,0 +1,36 @@ +// 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 GPUDisplayMagneticFieldConstants.h +/// \author David Rohr +/// \brief Array sizes of the magnetic field parameterization, shared by GPUDisplayMagneticField (C++) and the field shader (GLSL), so only plain #defines + +#ifndef GPUDISPLAYMAGNETICFIELDCONSTANTS_H +#define GPUDISPLAYMAGNETICFIELDCONSTANTS_H + +#define GPUCA_DISPLAY_FIELD_DIMENSIONS 3 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_Z_SEGMENTS 32 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_P_SEGMENTS 512 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_R_SEGMENTS 4096 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Z_SEGMENTS 128 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Y_SEGMENTS 2048 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_X_SEGMENTS 16384 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_PARAMETERIZATIONS 2048 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_ROWS 16384 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COLUMNS 65536 +#define GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COEFFICIENTS 131072 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_PARAMETERIZATIONS 2048 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_ROWS 16384 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COLUMNS 65536 +#define GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COEFFICIENTS 262144 +#define GPUCA_DISPLAY_FIELD_MAX_CHEBYSHEV_ORDER 32 + +#endif diff --git a/GPU/GPUTracking/display/shaders.cmake b/GPU/GPUTracking/display/shaders.cmake new file mode 100644 index 0000000000000..2c232672115ef --- /dev/null +++ b/GPU/GPUTracking/display/shaders.cmake @@ -0,0 +1,68 @@ +# 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 shaders.cmake +# author David Rohr + +# Compiles a GLSL shader with glslc to SPIR-V for Vulkan (${CMAKE_CURRENT_BINARY_DIR}/vulkan/) and / or OpenGL (.../opengl/), depending on GPUCA_EVENT_DISPLAY_VULKAN / _OPENGL, and embeds it as binary resource. +# The shader sources use #ifdef VULKAN (defined by glslc for Vulkan targets) for API differences. The resource symbols are collected for write_glslc_shader_header. +function(add_glslc_shader TARGET SHADER) + get_filename_component(input-file-abs ${SHADER} ABSOLUTE) + get_filename_component(input-file-name ${SHADER} NAME) + foreach(api vulkan opengl) + string(TOUPPER ${api} api-upper) + if(NOT GPUCA_EVENT_DISPLAY_${api-upper}) + continue() + endif() + if(api STREQUAL "vulkan") + set(target-env vulkan1.4) + else() + set(target-env opengl4.5) # SPIR-V 1.0 for OpenGL 4.6 (ARB_gl_spirv) + endif() + set(spirv-file ${CMAKE_CURRENT_BINARY_DIR}/${api}/${input-file-name}.spv) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${api}) + add_custom_command( + OUTPUT ${spirv-file} + COMMAND ${GPUCA_GLSLC} --target-env=${target-env} -o ${spirv-file} ${input-file-abs} -MD -MT ${spirv-file} -MF ${spirv-file}.d + DEPENDS ${input-file-abs} + DEPFILE ${spirv-file}.d + COMMENT "Compiling GLSL to SPIR-V (${api}): ${SHADER}" + VERBATIM + ) + add_binary_resource(${TARGET} ${spirv-file}) + string(MAKE_C_IDENTIFIER "${api}/${input-file-name}.spv" symbol) # Same name as the symbol created by ld --format binary + set_property(GLOBAL APPEND PROPERTY GPUCA_DISPLAY_SHADER_SYMBOLS_${api} ${symbol}) + endforeach() +endfunction(add_glslc_shader) + +# Generates the header declaring the symbols of all shaders added with add_glslc_shader (_binary___spv_start / _len), for the API selected by GPUCA_DISPLAY_SHADER_TYPE_VULKAN / _OPENGL, which the including file defines +function(write_glslc_shader_header TARGET HEADER) + set(content "// Generated by GPU/GPUTracking/display/shaders.cmake, do not edit\n\n#include \"utils/qGetLdBinarySymbols.h\"\n\n") + set(directive "#if") + foreach(api vulkan opengl) + get_property(symbols GLOBAL PROPERTY GPUCA_DISPLAY_SHADER_SYMBOLS_${api}) + if(NOT symbols) + continue() # Not compiled for this API, falls through to the #error + endif() + string(TOUPPER ${api} api-upper) + string(APPEND content "${directive} defined(GPUCA_DISPLAY_SHADER_TYPE_${api-upper})\n") + set(directive "#elif") + foreach(symbol ${symbols}) + string(APPEND content "QGET_LD_BINARY_SYMBOLS(${symbol});\n") + endforeach() + endforeach() + if(directive STREQUAL "#if") + string(APPEND content "#if 0\n") + endif() + string(APPEND content "#else\n#error GPUCA_DISPLAY_SHADER_TYPE_[VULKAN|OPENGL] undefined, or shaders not compiled for this type\n#endif\n") + file(CONFIGURE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${HEADER} CONTENT "${content}" @ONLY) # Only rewritten if changed + target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +endfunction(write_glslc_shader_header) diff --git a/GPU/GPUTracking/display/shaders/GPUDisplayShaders.h b/GPU/GPUTracking/display/shaders/GPUDisplayShaders.h deleted file mode 100644 index 88162ef29fda3..0000000000000 --- a/GPU/GPUTracking/display/shaders/GPUDisplayShaders.h +++ /dev/null @@ -1,471 +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 GPUDisplayShaders.h -/// \author David Rohr - -#ifndef GPUDISPLAYSHADERS_H -#define GPUDISPLAYSHADERS_H - -#include "GPUCommonDef.h" -namespace o2::gpu -{ - -struct GPUDisplayShaders { - static constexpr const char* vertexShader = R"( -#version 450 core -layout (location = 0) in vec3 pos; -uniform mat4 ModelViewProj; - -void main() -{ - gl_Position = ModelViewProj * vec4(pos.x, pos.y, pos.z, 1.0); -} -)"; - - static constexpr const char* fragmentShader = R"( -#version 450 core -out vec4 fragColor; -uniform vec4 color; - -void main() -{ - fragColor = vec4(color.x, color.y, color.z, 1.f); -} -)"; - - static constexpr const char* vertexShaderTexture = R"( -#version 450 core -layout (location = 0) in vec4 vertex; -out vec2 TexCoords; - -uniform mat4 projection; - -void main() -{ - gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); - TexCoords = vertex.zw; -} -)"; - - static constexpr const char* fragmentShaderTexture = R"( -#version 450 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D tex; -uniform float alpha; - -void main() -{ - color = vec4(texture(tex, TexCoords).rgb, alpha); -} -)"; - - static constexpr const char* fragmentShaderText = R"( -#version 450 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D text; -uniform vec4 textColor; - -void main() -{ - vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); - color = textColor * sampled; -} -)"; - - static constexpr const char* vertexShaderPassthrough = R"( -#version 450 core -layout (location = 0) in vec3 pos; -void main() -{ - gl_Position = vec4(pos, 1.0); -} -)"; - - static constexpr const char* geometryShaderP1 = R"( - #version 450 core -)"; - - static constexpr const char* fieldModelShaderConstants = R"( -#define DIMENSIONS {dimensions} - -#define SOL_Z_SEGS {solZSegs} -#define SOL_P_SEGS {solPSegs} -#define SOL_R_SEGS {solRSegs} - -#define SOL_PARAMS {solParams} -#define SOL_ROWS {solRows} -#define SOL_COLUMNS {solColumns} -#define SOL_COEFFS {solCoeffs} - -#define DIP_Z_SEGS {dipZSegs} -#define DIP_Y_SEGS {dipYSegs} -#define DIP_X_SEGS {dipXSegs} - -#define DIP_PARAMS {dipParams} -#define DIP_ROWS {dipRows} -#define DIP_COLUMNS {dipColumns} -#define DIP_COEFFS {dipCoeffs} - -#define MAX_CHEB_ORDER {maxChebOrder} -)"; - - static constexpr const char* fieldModelShaderCode = R"( -layout(std430, binding = 0) restrict readonly buffer field_config_ssbo { - uint32_t StepCount; - float StepSize; -} field_config; - -layout(std430, binding = 1) restrict readonly buffer sol_segment_ssbo { - float MinZ; - float MaxZ; - float MultiplicativeFactor; - - int32_t ZSegments; - - float SegZSol[SOL_Z_SEGS]; - - int32_t BegSegPSol[SOL_Z_SEGS]; - int32_t NSegPSol[SOL_Z_SEGS]; - - float SegPSol[SOL_P_SEGS]; - int32_t BegSegRSol[SOL_P_SEGS]; - int32_t NSegRSol[SOL_P_SEGS]; - - float SegRSol[SOL_R_SEGS]; - int32_t SegIDSol[SOL_R_SEGS]; -} sol_segment; - -layout(std430, binding = 2) restrict readonly buffer dip_segment_ssbo { - float MinZ; - float MaxZ; - float MultiplicativeFactor; - - int32_t ZSegments; - - float SegZDip[DIP_Z_SEGS]; - - int32_t BegSegYDip[DIP_Z_SEGS]; - int32_t NSegYDip[DIP_Z_SEGS]; - - float SegYDip[DIP_Y_SEGS]; - int32_t BegSegXDip[DIP_Y_SEGS]; - int32_t NSegXDip[DIP_Y_SEGS]; - - float SegXDip[DIP_X_SEGS]; - int32_t SegIDDip[DIP_X_SEGS]; -} dip_segment; - -layout(std430, binding = 3) restrict readonly buffer sol_params_ssbo { - float BOffsets[DIMENSIONS*SOL_PARAMS]; - float BScales[DIMENSIONS*SOL_PARAMS]; - float BMin[DIMENSIONS*SOL_PARAMS]; - float BMax[DIMENSIONS*SOL_PARAMS]; - int32_t NRows[DIMENSIONS*SOL_PARAMS]; - int32_t ColsAtRowOffset[DIMENSIONS*SOL_PARAMS]; - int32_t CofsAtRowOffset[DIMENSIONS*SOL_PARAMS]; - - int32_t NColsAtRow[SOL_ROWS]; - int32_t CofsAtColOffset[SOL_ROWS]; - - int32_t NCofsAtCol[SOL_COLUMNS]; - int32_t AtColCoefOffset[SOL_COLUMNS]; - - float Coeffs[SOL_COEFFS]; -} sol_params; - -layout(std430, binding = 4) restrict readonly buffer dip_params_ssbo { - float BOffsets[DIMENSIONS*DIP_PARAMS]; - float BScales[DIMENSIONS*DIP_PARAMS]; - float BMin[DIMENSIONS*DIP_PARAMS]; - float BMax[DIMENSIONS*DIP_PARAMS]; - int32_t NRows[DIMENSIONS*DIP_PARAMS]; - int32_t ColsAtRowOffset[DIMENSIONS*DIP_PARAMS]; - int32_t CofsAtRowOffset[DIMENSIONS*DIP_PARAMS]; - - int32_t NColsAtRow[DIP_ROWS]; - int32_t CofsAtColOffset[DIP_ROWS]; - - int32_t NCofsAtCol[DIP_COLUMNS]; - int32_t AtColCoefOffset[DIP_COLUMNS]; - - float Coeffs[DIP_COEFFS]; -} dip_params; - -float tmpCfs1[MAX_CHEB_ORDER]; -float tmpCfs0[MAX_CHEB_ORDER]; - -vec3 CarttoCyl(vec3 pos) { - return vec3(length(pos.xy), atan(pos.y, pos.x), pos.z); -} - -int32_t findSolSegment(vec3 pos) { - int32_t rid,pid,zid; - - for(zid=0; zid < sol_segment.ZSegments; zid++) if(pos.z=0;i--) { - b.zy = b.yx; - b.x = arr[i]; - b.x = dot(t1, b); - } - - const vec3 t = vec3(1, -x, 0); - return dot(t, b); -} - -float cheb1DParamsSol(float x, int32_t coeff_offset, int32_t ncf) { - if(ncf <= 0) return 0.0f; - - const float x2 = 2*x; - - vec3 b = vec3(sol_params.Coeffs[coeff_offset + (--ncf)], 0, 0); - --ncf; - - const vec3 t1 = vec3(1, x2, -1); - - for (int32_t i=ncf;i>=0;i--) { - b.zy = b.yx; - b.x = sol_params.Coeffs[coeff_offset + i]; - b.x = dot(t1, b); - } - - const vec3 t = vec3(1, -x, 0); - return dot(t, b); -} - -float cheb1DParamsDip(float x, int32_t coeff_offset, int32_t ncf) { - if(ncf <= 0) return 0.0f; - - const float x2 = 2*x; - - vec3 b = vec3(dip_params.Coeffs[coeff_offset + (--ncf)], 0, 0); - --ncf; - - const vec3 t1 = vec3(1, x2, -1); - - for (int32_t i=ncf;i>=0;i--) { - b.zy = b.yx; - b.x = dip_params.Coeffs[coeff_offset + i]; - b.x = dot(t1, b); - } - - const vec3 t = vec3(1, -x, 0); - return dot(t, b); -} - -bool IsBetween(vec3 sMin, vec3 val, vec3 sMax) { - return all(lessThanEqual(sMin, val)) && all(lessThanEqual(val, sMax)); -} - -bool IsInsideSol(int32_t segID, vec3 rphiz) { - const int32_t index = DIMENSIONS*segID; - - const vec3 seg_min = vec3(sol_params.BMin[index+0], sol_params.BMin[index+1], sol_params.BMin[index+2]); - const vec3 seg_max = vec3(sol_params.BMax[index+0], sol_params.BMax[index+1], sol_params.BMax[index+2]); - - return IsBetween(seg_min, rphiz, seg_max); -} - -bool IsInsideDip(int32_t segID, vec3 pos) { - const int32_t index = DIMENSIONS*segID; - - const vec3 seg_min = vec3(dip_params.BMin[index+0], dip_params.BMin[index+1], dip_params.BMin[index+2]); - const vec3 seg_max = vec3(dip_params.BMax[index+0], dip_params.BMax[index+1], dip_params.BMax[index+2]); - - return IsBetween(seg_min, pos, seg_max); -} - -float Eval3DSol(int32_t segID, int32_t dim, vec3 internal) { - const int32_t index = DIMENSIONS*segID; - const int32_t n_rows = sol_params.NRows[index+dim]; - const int32_t cols_at_row_offset = sol_params.ColsAtRowOffset[index+dim]; - const int32_t coeffs_at_row_offset = sol_params.CofsAtRowOffset[index+dim]; - - for(int32_t row = 0; row < n_rows; row++) { - const int32_t n_cols = sol_params.NColsAtRow[cols_at_row_offset+row]; - const int32_t coeff_at_col_offset = sol_params.CofsAtColOffset[cols_at_row_offset+row]; - - for(int32_t col = 0; col < n_cols; col++) { - const int32_t n_coeffs = sol_params.NCofsAtCol[coeff_at_col_offset+col]; - const int32_t per_col_coeff_offset = sol_params.AtColCoefOffset[coeff_at_col_offset+col]; - - const int32_t coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; - - tmpCfs1[col] = cheb1DParamsSol(internal.z, coeffs_offset,n_coeffs); - } - tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); - } - - return cheb1DArray(internal.x, tmpCfs0, n_rows); -} - -vec3 EvalSol(int32_t segID, vec3 rphiz) { - const vec3 internal = mapToInternalSol(segID, rphiz); - return vec3(Eval3DSol(segID, 0, internal), Eval3DSol(segID, 1, internal), Eval3DSol(segID, 2, internal)); -} - -float Eval3DDip(int32_t segID, int32_t dim, vec3 internal) { - const int32_t index = DIMENSIONS*segID; - const int32_t n_rows = dip_params.NRows[index+dim]; - const int32_t cols_at_row_offset = dip_params.ColsAtRowOffset[index+dim]; - const int32_t coeffs_at_row_offset = dip_params.CofsAtRowOffset[index+dim]; - - for(int32_t row = 0; row < n_rows; row++) { - const int32_t n_cols = dip_params.NColsAtRow[cols_at_row_offset+row]; - const int32_t coeff_at_col_offset = dip_params.CofsAtColOffset[cols_at_row_offset+row]; - - for(int32_t col = 0; col < n_cols; col++) { - const int32_t n_coeffs = dip_params.NCofsAtCol[coeff_at_col_offset+col]; - const int32_t per_col_coeff_offset = dip_params.AtColCoefOffset[coeff_at_col_offset+col]; - - const int32_t coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; - - tmpCfs1[col] = cheb1DParamsDip(internal.z, coeffs_offset, n_coeffs); - } - tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); - } - - return cheb1DArray(internal.x, tmpCfs0, n_rows); -} - -vec3 EvalDip(int32_t segID, vec3 pos) { - const vec3 internal = mapToInternalDip(segID, pos); - return vec3(Eval3DDip(segID, 0, internal), Eval3DDip(segID, 1, internal), Eval3DDip(segID, 2, internal)); -} - -vec3 CyltoCartCylB(vec3 rphiz, vec3 brphiz) { - const float btr = length(brphiz.xy); - const float psiPLUSphi = atan(brphiz.y, brphiz.x) + rphiz.y; - - return vec3(btr*cos(psiPLUSphi), btr*sin(psiPLUSphi), brphiz.z); -} - -vec3 MachineField(vec3 pos) { - return vec3(0); -} - -vec3 SolDipField(vec3 pos) { - if(pos.z > sol_segment.MinZ) { - const vec3 rphiz = CarttoCyl(pos); - const int32_t segID = findSolSegment(rphiz); - if(segID >=0 && IsInsideSol(segID, rphiz)) { - const vec3 brphiz = EvalSol(segID, rphiz); - return CyltoCartCylB(rphiz, brphiz) * sol_segment.MultiplicativeFactor; - } - } - - const int32_t segID = findDipSegment(pos); - if(segID >= 0 && IsInsideDip(segID, pos)) { - return EvalDip(segID, pos) * dip_segment.MultiplicativeFactor; - } - - return vec3(0); -} - -const float MinZ = dip_segment.MinZ; -const float MaxZ = sol_segment.MaxZ; - -vec3 Field(vec3 pos) { - if(pos.z > MinZ && pos.z < MaxZ) { - return SolDipField(pos); - } - return vec3(0); -} -)"; - - static constexpr const char* geometryShaderP2 = R"( -layout (points) in; -layout (line_strip, max_vertices = 256) out; - -layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; - -const float positionScale = 100.0f; - -void main() { - vec3 position = gl_in[0].gl_Position.xyz; - - for(uint32_t i = 0; i < field_config.StepCount; i++) { - gl_Position = um.ModelViewProj * vec4(position/positionScale, 1.0f); - EmitVertex(); - const vec3 b_vec = Field(position); - position -= b_vec * field_config.StepSize; - } - EndPrimitive(); -} -)"; -}; -} // namespace o2::gpu - -#endif diff --git a/GPU/GPUTracking/display/shaders/field.geom b/GPU/GPUTracking/display/shaders/field.geom new file mode 100644 index 0000000000000..40a129777f5ae --- /dev/null +++ b/GPU/GPUTracking/display/shaders/field.geom @@ -0,0 +1,378 @@ +// 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 field.geom +/// \author David Rohr +/// \brief Magnetic field lines: integrates the field from each seed point (input point) and emits the line strip + +#version 460 core +#extension GL_GOOGLE_include_directive : require +#include "../helpers/GPUDisplayMagneticFieldConstants.h" + +#define DIMENSIONS GPUCA_DISPLAY_FIELD_DIMENSIONS +#define SOL_Z_SEGS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_Z_SEGMENTS +#define SOL_P_SEGS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_P_SEGMENTS +#define SOL_R_SEGS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_R_SEGMENTS +#define SOL_PARAMS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_PARAMETERIZATIONS +#define SOL_ROWS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_ROWS +#define SOL_COLUMNS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COLUMNS +#define SOL_COEFFS GPUCA_DISPLAY_FIELD_MAX_SOLENOID_COEFFICIENTS +#define DIP_Z_SEGS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Z_SEGMENTS +#define DIP_Y_SEGS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_Y_SEGMENTS +#define DIP_X_SEGS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_X_SEGMENTS +#define DIP_PARAMS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_PARAMETERIZATIONS +#define DIP_ROWS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_ROWS +#define DIP_COLUMNS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COLUMNS +#define DIP_COEFFS GPUCA_DISPLAY_FIELD_MAX_DIPOLE_COEFFICIENTS +#define MAX_CHEB_ORDER GPUCA_DISPLAY_FIELD_MAX_CHEBYSHEV_ORDER + +#ifdef VULKAN +#define FIELD_SSBO_SET set = 1, // Vulkan: UBO and SSBOs share the binding numbers of a descriptor set +#else +#define FIELD_SSBO_SET +#endif + +layout(std430, FIELD_SSBO_SET binding = 0) restrict readonly buffer field_config_ssbo { + uint StepCount; + float StepSize; +} field_config; + +layout(std430, FIELD_SSBO_SET binding = 1) restrict readonly buffer sol_segment_ssbo { + float MinZ; + float MaxZ; + float MultiplicativeFactor; + + int ZSegments; + + float SegZSol[SOL_Z_SEGS]; + + int BegSegPSol[SOL_Z_SEGS]; + int NSegPSol[SOL_Z_SEGS]; + + float SegPSol[SOL_P_SEGS]; + int BegSegRSol[SOL_P_SEGS]; + int NSegRSol[SOL_P_SEGS]; + + float SegRSol[SOL_R_SEGS]; + int SegIDSol[SOL_R_SEGS]; +} sol_segment; + +layout(std430, FIELD_SSBO_SET binding = 2) restrict readonly buffer dip_segment_ssbo { + float MinZ; + float MaxZ; + float MultiplicativeFactor; + + int ZSegments; + + float SegZDip[DIP_Z_SEGS]; + + int BegSegYDip[DIP_Z_SEGS]; + int NSegYDip[DIP_Z_SEGS]; + + float SegYDip[DIP_Y_SEGS]; + int BegSegXDip[DIP_Y_SEGS]; + int NSegXDip[DIP_Y_SEGS]; + + float SegXDip[DIP_X_SEGS]; + int SegIDDip[DIP_X_SEGS]; +} dip_segment; + +layout(std430, FIELD_SSBO_SET binding = 3) restrict readonly buffer sol_params_ssbo { + float BOffsets[DIMENSIONS*SOL_PARAMS]; + float BScales[DIMENSIONS*SOL_PARAMS]; + float BMin[DIMENSIONS*SOL_PARAMS]; + float BMax[DIMENSIONS*SOL_PARAMS]; + int NRows[DIMENSIONS*SOL_PARAMS]; + int ColsAtRowOffset[DIMENSIONS*SOL_PARAMS]; + int CofsAtRowOffset[DIMENSIONS*SOL_PARAMS]; + + int NColsAtRow[SOL_ROWS]; + int CofsAtColOffset[SOL_ROWS]; + + int NCofsAtCol[SOL_COLUMNS]; + int AtColCoefOffset[SOL_COLUMNS]; + + float Coeffs[SOL_COEFFS]; +} sol_params; + +layout(std430, FIELD_SSBO_SET binding = 4) restrict readonly buffer dip_params_ssbo { + float BOffsets[DIMENSIONS*DIP_PARAMS]; + float BScales[DIMENSIONS*DIP_PARAMS]; + float BMin[DIMENSIONS*DIP_PARAMS]; + float BMax[DIMENSIONS*DIP_PARAMS]; + int NRows[DIMENSIONS*DIP_PARAMS]; + int ColsAtRowOffset[DIMENSIONS*DIP_PARAMS]; + int CofsAtRowOffset[DIMENSIONS*DIP_PARAMS]; + + int NColsAtRow[DIP_ROWS]; + int CofsAtColOffset[DIP_ROWS]; + + int NCofsAtCol[DIP_COLUMNS]; + int AtColCoefOffset[DIP_COLUMNS]; + + float Coeffs[DIP_COEFFS]; +} dip_params; + +float tmpCfs1[MAX_CHEB_ORDER]; +float tmpCfs0[MAX_CHEB_ORDER]; + +vec3 CarttoCyl(vec3 pos) { + return vec3(length(pos.xy), atan(pos.y, pos.x), pos.z); +} + +int findSolSegment(vec3 pos) { + int rid,pid,zid; + + for(zid=0; zid < sol_segment.ZSegments; zid++) if(pos.z=0;i--) { + b.zy = b.yx; + b.x = arr[i]; + b.x = dot(t1, b); + } + + const vec3 t = vec3(1, -x, 0); + return dot(t, b); +} + +float cheb1DParamsSol(float x, int coeff_offset, int ncf) { + if(ncf <= 0) return 0.0f; + + const float x2 = 2*x; + + vec3 b = vec3(sol_params.Coeffs[coeff_offset + (--ncf)], 0, 0); + --ncf; + + const vec3 t1 = vec3(1, x2, -1); + + for (int i=ncf;i>=0;i--) { + b.zy = b.yx; + b.x = sol_params.Coeffs[coeff_offset + i]; + b.x = dot(t1, b); + } + + const vec3 t = vec3(1, -x, 0); + return dot(t, b); +} + +float cheb1DParamsDip(float x, int coeff_offset, int ncf) { + if(ncf <= 0) return 0.0f; + + const float x2 = 2*x; + + vec3 b = vec3(dip_params.Coeffs[coeff_offset + (--ncf)], 0, 0); + --ncf; + + const vec3 t1 = vec3(1, x2, -1); + + for (int i=ncf;i>=0;i--) { + b.zy = b.yx; + b.x = dip_params.Coeffs[coeff_offset + i]; + b.x = dot(t1, b); + } + + const vec3 t = vec3(1, -x, 0); + return dot(t, b); +} + +bool IsBetween(vec3 sMin, vec3 val, vec3 sMax) { + return all(lessThanEqual(sMin, val)) && all(lessThanEqual(val, sMax)); +} + +bool IsInsideSol(int segID, vec3 rphiz) { + const int index = DIMENSIONS*segID; + + const vec3 seg_min = vec3(sol_params.BMin[index+0], sol_params.BMin[index+1], sol_params.BMin[index+2]); + const vec3 seg_max = vec3(sol_params.BMax[index+0], sol_params.BMax[index+1], sol_params.BMax[index+2]); + + return IsBetween(seg_min, rphiz, seg_max); +} + +bool IsInsideDip(int segID, vec3 pos) { + const int index = DIMENSIONS*segID; + + const vec3 seg_min = vec3(dip_params.BMin[index+0], dip_params.BMin[index+1], dip_params.BMin[index+2]); + const vec3 seg_max = vec3(dip_params.BMax[index+0], dip_params.BMax[index+1], dip_params.BMax[index+2]); + + return IsBetween(seg_min, pos, seg_max); +} + +float Eval3DSol(int segID, int dim, vec3 internal) { + const int index = DIMENSIONS*segID; + const int n_rows = sol_params.NRows[index+dim]; + const int cols_at_row_offset = sol_params.ColsAtRowOffset[index+dim]; + const int coeffs_at_row_offset = sol_params.CofsAtRowOffset[index+dim]; + + for(int row = 0; row < n_rows; row++) { + const int n_cols = sol_params.NColsAtRow[cols_at_row_offset+row]; + const int coeff_at_col_offset = sol_params.CofsAtColOffset[cols_at_row_offset+row]; + + for(int col = 0; col < n_cols; col++) { + const int n_coeffs = sol_params.NCofsAtCol[coeff_at_col_offset+col]; + const int per_col_coeff_offset = sol_params.AtColCoefOffset[coeff_at_col_offset+col]; + + const int coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; + + tmpCfs1[col] = cheb1DParamsSol(internal.z, coeffs_offset,n_coeffs); + } + tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); + } + + return cheb1DArray(internal.x, tmpCfs0, n_rows); +} + +vec3 EvalSol(int segID, vec3 rphiz) { + const vec3 internal = mapToInternalSol(segID, rphiz); + return vec3(Eval3DSol(segID, 0, internal), Eval3DSol(segID, 1, internal), Eval3DSol(segID, 2, internal)); +} + +float Eval3DDip(int segID, int dim, vec3 internal) { + const int index = DIMENSIONS*segID; + const int n_rows = dip_params.NRows[index+dim]; + const int cols_at_row_offset = dip_params.ColsAtRowOffset[index+dim]; + const int coeffs_at_row_offset = dip_params.CofsAtRowOffset[index+dim]; + + for(int row = 0; row < n_rows; row++) { + const int n_cols = dip_params.NColsAtRow[cols_at_row_offset+row]; + const int coeff_at_col_offset = dip_params.CofsAtColOffset[cols_at_row_offset+row]; + + for(int col = 0; col < n_cols; col++) { + const int n_coeffs = dip_params.NCofsAtCol[coeff_at_col_offset+col]; + const int per_col_coeff_offset = dip_params.AtColCoefOffset[coeff_at_col_offset+col]; + + const int coeffs_offset = coeffs_at_row_offset + per_col_coeff_offset; + + tmpCfs1[col] = cheb1DParamsDip(internal.z, coeffs_offset, n_coeffs); + } + tmpCfs0[row] = cheb1DArray(internal.y, tmpCfs1, n_cols); + } + + return cheb1DArray(internal.x, tmpCfs0, n_rows); +} + +vec3 EvalDip(int segID, vec3 pos) { + const vec3 internal = mapToInternalDip(segID, pos); + return vec3(Eval3DDip(segID, 0, internal), Eval3DDip(segID, 1, internal), Eval3DDip(segID, 2, internal)); +} + +vec3 CyltoCartCylB(vec3 rphiz, vec3 brphiz) { + const float btr = length(brphiz.xy); + const float psiPLUSphi = atan(brphiz.y, brphiz.x) + rphiz.y; + + return vec3(btr*cos(psiPLUSphi), btr*sin(psiPLUSphi), brphiz.z); +} + +vec3 MachineField(vec3 pos) { + return vec3(0); +} + +vec3 SolDipField(vec3 pos) { + if(pos.z > sol_segment.MinZ) { + const vec3 rphiz = CarttoCyl(pos); + const int segID = findSolSegment(rphiz); + if(segID >=0 && IsInsideSol(segID, rphiz)) { + const vec3 brphiz = EvalSol(segID, rphiz); + return CyltoCartCylB(rphiz, brphiz) * sol_segment.MultiplicativeFactor; + } + } + + const int segID = findDipSegment(pos); + if(segID >= 0 && IsInsideDip(segID, pos)) { + return EvalDip(segID, pos) * dip_segment.MultiplicativeFactor; + } + + return vec3(0); +} + +vec3 Field(vec3 pos) { + if(pos.z > dip_segment.MinZ && pos.z < sol_segment.MaxZ) { + return SolDipField(pos); + } + return vec3(0); +} + +layout (points) in; +layout (line_strip, max_vertices = 256) out; + +layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; + +const float positionScale = 100.0f; + +void main() { + vec3 position = gl_in[0].gl_Position.xyz; + + for(uint i = 0; i < field_config.StepCount; i++) { + gl_Position = um.ModelViewProj * vec4(position/positionScale, 1.0f); + EmitVertex(); + const vec3 b_vec = Field(position); + position -= b_vec * field_config.StepSize; + } + EndPrimitive(); +} diff --git a/GPU/GPUTracking/display/shaders/fragment.frag b/GPU/GPUTracking/display/shaders/fragment.frag index e6fe7c28a54ba..7245834ded276 100644 --- a/GPU/GPUTracking/display/shaders/fragment.frag +++ b/GPU/GPUTracking/display/shaders/fragment.frag @@ -1,8 +1,14 @@ #version 460 core layout (location = 0) out vec4 outColor; +#ifdef VULKAN layout (push_constant) uniform pushColor { vec4 color; } pc; +#define COLOR pc.color +#else +layout (location = 4) uniform vec4 color; +#define COLOR color +#endif void main() { - outColor = vec4(pc.color.x, pc.color.y, pc.color.z, 1.f); + outColor = vec4(COLOR.x, COLOR.y, COLOR.z, 1.f); } diff --git a/GPU/GPUTracking/display/shaders/fragmentText.frag b/GPU/GPUTracking/display/shaders/fragmentText.frag index 37098586281ae..70f5017933960 100644 --- a/GPU/GPUTracking/display/shaders/fragmentText.frag +++ b/GPU/GPUTracking/display/shaders/fragmentText.frag @@ -1,11 +1,18 @@ #version 460 core layout (location = 0) out vec4 outColor; layout (location = 2) in vec2 TexCoords; +#ifdef VULKAN layout (binding = 2) uniform sampler2D text; layout (push_constant) uniform pushColor { vec4 textColor; } pc; +#define TEXT_COLOR pc.textColor +#else +layout (binding = 0) uniform sampler2D text; +layout (location = 4) uniform vec4 textColor; +#define TEXT_COLOR textColor +#endif void main() { - vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); - outColor = pc.textColor * sampled; + vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); + outColor = TEXT_COLOR * sampled; } diff --git a/GPU/GPUTracking/display/shaders/fragmentTexture.frag b/GPU/GPUTracking/display/shaders/fragmentTexture.frag index 6c92d9b2b27b7..786fcc8b40c91 100644 --- a/GPU/GPUTracking/display/shaders/fragmentTexture.frag +++ b/GPU/GPUTracking/display/shaders/fragmentTexture.frag @@ -1,10 +1,17 @@ #version 460 core layout (location = 0) out vec4 outColor; layout (location = 2) in vec2 TexCoords; +#ifdef VULKAN layout (binding = 2) uniform sampler2D tex; layout (push_constant) uniform pushAlpha { float alpha; } pa; +#define ALPHA pa.alpha +#else +layout (binding = 0) uniform sampler2D tex; +layout (location = 4) uniform float alpha; +#define ALPHA alpha +#endif void main() { - outColor = vec4(texture(tex, TexCoords).rgb, pa.alpha); + outColor = vec4(texture(tex, TexCoords).rgb, ALPHA); } diff --git a/GPU/GPUTracking/display/shaders/fragmentUniform.frag b/GPU/GPUTracking/display/shaders/fragmentUniform.frag deleted file mode 100644 index 4299af3daedb7..0000000000000 --- a/GPU/GPUTracking/display/shaders/fragmentUniform.frag +++ /dev/null @@ -1,8 +0,0 @@ -#version 460 core -layout (location = 0) out vec4 outColor; -layout (binding = 1) uniform uniformColor { vec4 color; } uc; - -void main() -{ - outColor = vec4(uc.color.x, uc.color.y, uc.color.z, 1.f); -} diff --git a/GPU/GPUTracking/display/shaders/vertex.vert b/GPU/GPUTracking/display/shaders/vertex.vert index 5bb1475c05856..55e2acdeee45f 100644 --- a/GPU/GPUTracking/display/shaders/vertex.vert +++ b/GPU/GPUTracking/display/shaders/vertex.vert @@ -1,8 +1,14 @@ #version 460 core layout (location = 0) in vec3 pos; +#ifdef VULKAN layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; +#define MODEL_VIEW_PROJ um.ModelViewProj +#else +layout (location = 0) uniform mat4 ModelViewProj; +#define MODEL_VIEW_PROJ ModelViewProj +#endif void main() { - gl_Position = um.ModelViewProj * vec4(pos.x, pos.y, pos.z, 1.0); + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); } diff --git a/GPU/GPUTracking/display/shaders/vertexPassthrough.vert b/GPU/GPUTracking/display/shaders/vertexPassthrough.vert new file mode 100644 index 0000000000000..7f6ed6a8e55aa --- /dev/null +++ b/GPU/GPUTracking/display/shaders/vertexPassthrough.vert @@ -0,0 +1,21 @@ +// 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 vertexPassthrough.vert +/// \author David Rohr + +#version 460 core +layout (location = 0) in vec3 pos; + +void main() +{ + gl_Position = vec4(pos, 1.0); +} diff --git a/GPU/GPUTracking/display/shaders/vertexPoint.vert b/GPU/GPUTracking/display/shaders/vertexPoint.vert index 8b87073407298..c8b572c61044a 100644 --- a/GPU/GPUTracking/display/shaders/vertexPoint.vert +++ b/GPU/GPUTracking/display/shaders/vertexPoint.vert @@ -1,10 +1,19 @@ #version 460 core layout (location = 0) in vec3 pos; +#ifdef VULKAN layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; layout (push_constant) uniform pushSize { layout(offset = 16) float size; } ps; +#define MODEL_VIEW_PROJ um.ModelViewProj +#define POINT_SIZE ps.size +#else +layout (location = 0) uniform mat4 ModelViewProj; +layout (location = 5) uniform float size; +#define MODEL_VIEW_PROJ ModelViewProj +#define POINT_SIZE size +#endif void main() { - gl_Position = um.ModelViewProj * vec4(pos.x, pos.y, pos.z, 1.0); - gl_PointSize = ps.size; + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); + gl_PointSize = POINT_SIZE; } diff --git a/GPU/GPUTracking/display/shaders/vertexTexture.vert b/GPU/GPUTracking/display/shaders/vertexTexture.vert index 62442c0131efb..984e05601dc94 100644 --- a/GPU/GPUTracking/display/shaders/vertexTexture.vert +++ b/GPU/GPUTracking/display/shaders/vertexTexture.vert @@ -1,10 +1,16 @@ #version 460 core layout (location = 0) in vec4 vertex; layout (location = 2) out vec2 TexCoords; +#ifdef VULKAN layout (binding = 0) uniform uniformMatrix { mat4 projection; } um; +#define PROJECTION um.projection +#else +layout (location = 0) uniform mat4 projection; +#define PROJECTION projection +#endif void main() { - gl_Position = um.projection * vec4(vertex.xy, 0.0, 1.0); + gl_Position = PROJECTION * vec4(vertex.xy, 0.0, 1.0); TexCoords = vertex.zw; } From 012ebbaa3ee140e4b8a2e6cd50d70bd48314db58 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 15:49:34 +0200 Subject: [PATCH 35/48] GPU Display: Stop using DebugGUI, just pull in the stuff directly, and hide HandMadeMath in GPUDisplay.h --- GPU/GPUTracking/display/GPUDisplay.cxx | 28 +++++++++++-------- GPU/GPUTracking/display/GPUDisplay.h | 5 ++-- .../backend/GPUDisplayBackendOpenGL.cxx | 1 + .../backend/GPUDisplayBackendVulkan.cxx | 1 + .../frontend/GPUDisplayFrontendGlfw.cxx | 21 -------------- .../display/helpers/GPUDisplayAnimation.cxx | 3 +- .../GPUDisplayBackendOpenGLMagneticField.cxx | 1 + 7 files changed, 23 insertions(+), 37 deletions(-) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index e7f5580e3f79f..3aa6ed07b59a3 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -16,6 +16,7 @@ #include "frontend/GPUDisplayInfo.inc" #include "GPUTPCDef.h" +#include "HandMadeMath.h" #include #include @@ -60,6 +61,8 @@ const GPUSettingsProcessing& GPUDisplay::GetProcessingConfig(GPUChainTracking* c GPUDisplay::GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GPUQA* qa, const GPUParam* param, const GPUCalibObjectsConst* calib, const GPUSettingsDisplay* config, const GPUSettingsProcessing* proc) : GPUDisplayInterface(), mFrontend(frontend), mChain(chain), mConfig(config ? *config : GetConfig(chain)), mProcessingSettings(proc ? *proc : GetProcessingConfig(chain)), mQA(qa) { + mViewMatrix = std::make_unique(); + mModelMatrix = std::make_unique(); mParam = param ? param : &mChain->GetParam(); mCalib = calib; mCfgL = mConfig.light; @@ -146,8 +149,8 @@ void GPUDisplay::ResizeScene(int32_t width, int32_t height, bool init) if (init) { mResetScene = true; - mViewMatrix = MY_HMM_IDENTITY; - mModelMatrix = MY_HMM_IDENTITY; + *mViewMatrix = MY_HMM_IDENTITY; + *mModelMatrix = MY_HMM_IDENTITY; } } @@ -251,6 +254,7 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla if (mCfgL.drawSector != -1) { scalefactor *= 0.2f; } + float* const mViewMatrixP = &mViewMatrix->Elements[0][0]; float sqrdist = sqrtf(sqrtf(mViewMatrixP[12] * mViewMatrixP[12] + mViewMatrixP[13] * mViewMatrixP[13] + mViewMatrixP[14] * mViewMatrixP[14]) * GL_SCALE_FACTOR) * 0.8f; if (sqrdist < 0.2f) { sqrdist = 0.2f; @@ -267,8 +271,8 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla } else if (mResetScene) { 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; - mModelMatrix = MY_HMM_IDENTITY; + *mViewMatrix = MY_HMM_IDENTITY; + *mModelMatrix = MY_HMM_IDENTITY; mCfgL.pointSize = 2.0f; mCfgL.lineWidth = 1.4f; @@ -367,23 +371,23 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla nextViewMatrix = nextViewMatrix * HMM_Rotate(rotRoll * mYFactor, {{0, 0, 1}}); } } - nextViewMatrix = nextViewMatrix * mViewMatrix; // Apply previous translation / rotation + nextViewMatrix = nextViewMatrix * *mViewMatrix; // Apply previous translation / rotation if (yUp) { calcXYZ(&nextViewMatrix.Elements[0][0]); nextViewMatrix = HMM_Rotate(mAngle[2] * 180.f / CAMath::Pi(), {{0, 0, 1}}) * nextViewMatrix; } if (rotateModel) { if (rotYaw != 0.f) { - mModelMatrix = HMM_Rotate(rotYaw, {{nextViewMatrix.Elements[0][1], nextViewMatrix.Elements[1][1], nextViewMatrix.Elements[2][1]}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(rotYaw, {{nextViewMatrix.Elements[0][1], nextViewMatrix.Elements[1][1], nextViewMatrix.Elements[2][1]}}) * *mModelMatrix; } if (rotPitch != 0.f) { - mModelMatrix = HMM_Rotate(rotPitch, {{nextViewMatrix.Elements[0][0], nextViewMatrix.Elements[1][0], nextViewMatrix.Elements[2][0]}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(rotPitch, {{nextViewMatrix.Elements[0][0], nextViewMatrix.Elements[1][0], nextViewMatrix.Elements[2][0]}}) * *mModelMatrix; } if (rotRoll != 0.f) { if (rotateModelTPC) { - mModelMatrix = HMM_Rotate(-rotRoll, {{0, 0, 1}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(-rotRoll, {{0, 0, 1}}) * *mModelMatrix; } else { - mModelMatrix = HMM_Rotate(-rotRoll, {{nextViewMatrix.Elements[0][2], nextViewMatrix.Elements[1][2], nextViewMatrix.Elements[2][2]}}) * mModelMatrix; + *mModelMatrix = HMM_Rotate(-rotRoll, {{nextViewMatrix.Elements[0][2], nextViewMatrix.Elements[1][2], nextViewMatrix.Elements[2][2]}}) * *mModelMatrix; } } } @@ -414,8 +418,8 @@ void GPUDisplay::DrawGLScene_cameraAndAnimation(float animateTime, float& mixSla // Store position if (animateTime < 0) { - mViewMatrix = nextViewMatrix; - calcXYZ(mViewMatrixP); + *mViewMatrix = nextViewMatrix; + calcXYZ(&mViewMatrix->Elements[0][0]); } if (mFrontend->mMouseDn || mFrontend->mMouseDnR) { @@ -659,7 +663,7 @@ void GPUDisplay::DrawGLScene_internal(float animateTime, bool renderToMixBuffer) } // Draw Event - nextViewMatrix = nextViewMatrix * mModelMatrix; + 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); mBackend->prepareDraw(proj, nextViewMatrix, doScreenshot || mRequestScreenshot, renderToMixBuffer, mixSlaveImage); diff --git a/GPU/GPUTracking/display/GPUDisplay.h b/GPU/GPUTracking/display/GPUDisplay.h index bae898cf38674..541d722e8eaaf 100644 --- a/GPU/GPUTracking/display/GPUDisplay.h +++ b/GPU/GPUTracking/display/GPUDisplay.h @@ -25,7 +25,7 @@ #include #include -#include "HandMadeMath.h" +union hmm_mat4; // From HandMadeMath #include "utils/timer.h" #include "utils/vecpod.h" @@ -248,8 +248,7 @@ class GPUDisplay : public GPUDisplayInterface float mAngleRollOrigin = -1e9; float mMaxClusterZ = -1; - hmm_mat4 mViewMatrix, mModelMatrix; - float* const mViewMatrixP = &mViewMatrix.Elements[0][0]; + std::unique_ptr mViewMatrix, mModelMatrix; float mXYZ[3]; float mAngle[3]; float mRPhiTheta[3]; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index 435664c51eacb..0fb4846ad9c2e 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -22,6 +22,7 @@ #else #define gluErrorString(err) "" #endif +#include "HandMadeMath.h" #include "GPUCommonDef.h" #include "GPUDisplayBackendOpenGL.h" diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index bc0c8184bdccb..6bc4748aef8b3 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -15,6 +15,7 @@ #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #include VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE +#include "HandMadeMath.h" #include "GPUCommonDef.h" #include "GPUDisplayBackendVulkan.h" diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx index ad19130aa47ee..789c1c2f5f451 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayFrontendGlfw.cxx @@ -26,11 +26,6 @@ #include #include -#ifndef GPUCA_STANDALONE -#include -#include -#endif - using namespace o2::gpu; GPUDisplayFrontendGlfw::GPUDisplayFrontendGlfw() @@ -263,18 +258,6 @@ void GPUDisplayFrontendGlfw::maximize_callback(GLFWwindow* window, int32_t maxim } } -#ifndef GPUCA_STANDALONE -void GPUDisplayFrontendGlfw::DisplayLoop() -{ - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(me->mDisplayWidth, me->mDisplayHeight)); - ImGui::SetNextWindowBgAlpha(0.f); - ImGui::Begin("Console", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); - me->DrawGLScene(); - ImGui::End(); -} -#endif - int32_t GPUDisplayFrontendGlfw::FrontendMain() { me = this; @@ -410,11 +393,7 @@ void GPUDisplayFrontendGlfw::SetVSync(bool enable) { glfwSwapInterval(enable); } bool GPUDisplayFrontendGlfw::EnableSendKey() { -#ifdef GPUCA_STANDALONE return true; -#else - return false; -#endif } void GPUDisplayFrontendGlfw::getSize(int32_t& width, int32_t& height) diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx index cc9ec2e766c4d..1d35e2c3f714e 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayAnimation.cxx @@ -15,6 +15,7 @@ #include "GPUDisplay.h" #include "frontend/GPUDisplayInfo.inc" #include "GPUCommonMath.h" +#include "HandMadeMath.h" using namespace o2::gpu; @@ -56,7 +57,7 @@ void GPUDisplay::setAnimationPoint() mAnimateVectors[8].emplace_back(0); } else { // Quaternions float v[4]; - createQuaternionFromMatrix(v, mViewMatrixP); + createQuaternionFromMatrix(v, &mViewMatrix->Elements[0][0]); if (mAnimateVectors[0].size()) { mAnimateCloseQuaternion(v, mAnimateVectors[5].back(), mAnimateVectors[6].back(), mAnimateVectors[7].back(), mAnimateVectors[8].back()); } diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx index 6d6a928824285..4eebf7bcf227f 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx @@ -22,6 +22,7 @@ #else #define gluErrorString(err) "" #endif +#include "HandMadeMath.h" #include "GPUCommonDef.h" #include "GPUDisplayMagneticField.h" From 030b150b7dfea0cba9b431b0a68e1fd12f0086ee Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 14:26:43 +0200 Subject: [PATCH 36/48] GPU Display CMake: No need to require OpenGL, we can build with Vulkan only --- GPU/GPUTracking/CMakeLists.txt | 2 +- GPU/GPUTracking/display/CMakeLists.txt | 130 ++++++++++-------- .../display/backend/GPUDisplayBackend.cxx | 11 +- .../display/backend/GPUDisplayBackend.h | 4 - .../backend/GPUDisplayBackendOpenGL.cxx | 31 ----- .../GPUDisplayBackendOpenGLMagneticField.cxx | 11 -- 6 files changed, 83 insertions(+), 106 deletions(-) diff --git a/GPU/GPUTracking/CMakeLists.txt b/GPU/GPUTracking/CMakeLists.txt index 0467b060ead4f..f1541e17b467f 100644 --- a/GPU/GPUTracking/CMakeLists.txt +++ b/GPU/GPUTracking/CMakeLists.txt @@ -27,7 +27,7 @@ set(CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER} "${CMAKE_CXX_FLAGS_${CMAKE_BUILD_T include(cmake/helpers.cmake) -if((ALIGPU_BUILD_TYPE STREQUAL "Standalone" AND GPUCA_BUILD_EVENT_DISPLAY) OR (ALIGPU_BUILD_TYPE STREQUAL "O2" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND TARGET AliceO2::DebugGUI AND OPENGL_FOUND AND GLFW_FOUND AND (NOT DEFINED GPUCA_BUILD_EVENT_DISPLAY OR NOT GPUCA_BUILD_EVENT_DISPLAY))) +if((ALIGPU_BUILD_TYPE STREQUAL "Standalone" AND GPUCA_BUILD_EVENT_DISPLAY) OR (ALIGPU_BUILD_TYPE STREQUAL "O2" AND NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND TARGET AliceO2::DebugGUI AND (NOT DEFINED GPUCA_BUILD_EVENT_DISPLAY OR GPUCA_BUILD_EVENT_DISPLAY))) set(GPUCA_EVENT_DISPLAY ON) endif() if(ROOT_FOUND) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index 308dfb8a6edf7..e9a0633f657ab 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -12,8 +12,6 @@ set(MODULE GPUTrackingDisplay) if(ALIGPU_BUILD_TYPE STREQUAL "O2") - find_package(GLEW) - set_package_properties(GLEW PROPERTIES TYPE OPTIONAL) find_package(X11) set_package_properties(X11 PROPERTIES TYPE OPTIONAL) find_package(GLUT) @@ -28,6 +26,7 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") set_package_properties(O2GPUWayland PROPERTIES TYPE OPTIONAL) find_package(Qt6 COMPONENTS Widgets) set_package_properties(Qt6 PROPERTIES TYPE OPTIONAL) + set(GPUCA_CONFIG_GL3W 1) endif() # glslc compiles the shaders to SPIR-V for both backends @@ -37,12 +36,16 @@ else() find_program(GPUCA_GLSLC glslc) endif() -# OpenGL backend: OpenGL 4.6 headers (bundled gl3w, or GLEW) for SPIR-V shaders, and glslc -if(GPUCA_GLSLC) +# OpenGL backend: OpenGL library, OpenGL 4.6 headers (gl3w: from DebugGUI in O2, bundled in standalone; or GLEW) for SPIR-V shaders, and glslc +if(GPUCA_GLSLC AND TARGET OpenGL::GL) include(CheckSymbolExists) - if(ALIGPU_BUILD_TYPE STREQUAL "O2" OR GPUCA_CONFIG_GL3W) - set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty) - check_symbol_exists(GL_VERSION_4_6 "GL/glcorearb.h" GPUCA_GL3W_HAS_GL_4_6) # Bundled header + if(GPUCA_CONFIG_GL3W) + if(ALIGPU_BUILD_TYPE STREQUAL "O2") + get_target_property(CMAKE_REQUIRED_INCLUDES AliceO2::DebugGUI INTERFACE_INCLUDE_DIRECTORIES) + else() + set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty) + endif() + check_symbol_exists(GL_VERSION_4_6 "GL/glcorearb.h" GPUCA_GL3W_HAS_GL_4_6) if(GPUCA_GL3W_HAS_GL_4_6) set(GPUCA_EVENT_DISPLAY_OPENGL ON) endif() @@ -57,9 +60,13 @@ if(GPUCA_GLSLC) endif() if(NOT GPUCA_EVENT_DISPLAY_OPENGL) if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") - message(FATAL_ERROR "OpenGL event display needs OpenGL 4.6 headers and glslc") + message(FATAL_ERROR "OpenGL event display needs OpenGL, OpenGL 4.6 headers and glslc") endif() - message(STATUS "OpenGL event display backend disabled: needs OpenGL 4.6 headers and glslc") + message(STATUS "OpenGL event display backend disabled: needs OpenGL, OpenGL 4.6 headers and glslc") + set(GLUT_FOUND 0) + set(OPENGL_GLU_FOUND 0) + set(GLEW_FOUND 0) + set(X11_FOUND 0) endif() if(Vulkan_FOUND) @@ -86,18 +93,15 @@ set(SRCS ../utils/qsem.cxx GPUDisplay.cxx helpers/GPUDisplayMagneticField.cxx frontend/GPUDisplayFrontend.cxx - frontend/GPUDisplayFrontendGlfw.cxx frontend/GPUDisplayFrontendNone.cxx backend/GPUDisplayBackend.cxx - backend/GPUDisplayBackendNone.cxx - backend/GPUDisplayBackendOpenGL.cxx) + backend/GPUDisplayBackendNone.cxx) set(SRCS_NO_H helpers/GPUDisplayLoader.cxx render/GPUDisplayDraw.cxx render/GPUDisplayImportEvent.cxx helpers/GPUDisplayHelpers.cxx helpers/GPUDisplayAnimation.cxx - helpers/GPUDisplayBackendOpenGLMagneticField.cxx helpers/GPUDisplayQuaternion.cxx helpers/GPUDisplayInterpolation.cxx helpers/GPUDisplayROOT.cxx @@ -107,35 +111,41 @@ set(HDRS_INSTALL helpers/GPUDisplayMagneticFieldConstants.h) if(GPUCA_EVENT_DISPLAY_VULKAN) set(SRCS ${SRCS} backend/GPUDisplayBackendVulkan.cxx) - if(GPUCA_EVENT_DISPLAY_WAYLAND) - set(WAYLAND_PROTOCOL_HEADERS) - set(WAYLAND_PROTOCOL_SOURCES) - foreach(xml ${O2GPUWayland_PROTOCOLS}) # List and existence check in dependencies/FindO2GPUWayland.cmake - get_filename_component(name ${xml} NAME_WE) - string(REGEX REPLACE "(-unstable)?-v[0-9]+$" "" name ${name}) - set(header ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-client-protocol.h) - set(source ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-protocol.c) - add_custom_command( - OUTPUT ${header} - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${header} - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} - COMMENT "Preparing Wayland ${name}-client-protocol.h") - add_custom_command( - OUTPUT ${source} - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ - COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${source} - MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} - COMMENT "Preparing Wayland ${name}-protocol.c") - list(APPEND WAYLAND_PROTOCOL_HEADERS ${header}) - 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(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) - endif() +endif() + +if(GPUCA_EVENT_DISPLAY_OPENGL) + set(SRCS ${SRCS} backend/GPUDisplayBackendOpenGL.cxx) + set(SRCS_NO_H ${SRCS_NO_H} helpers/GPUDisplayBackendOpenGLMagneticField.cxx) +endif() + +if(GPUCA_EVENT_DISPLAY_WAYLAND) + set(WAYLAND_PROTOCOL_HEADERS) + set(WAYLAND_PROTOCOL_SOURCES) + foreach(xml ${O2GPUWayland_PROTOCOLS}) # List and existence check in dependencies/FindO2GPUWayland.cmake + get_filename_component(name ${xml} NAME_WE) + string(REGEX REPLACE "(-unstable)?-v[0-9]+$" "" name ${name}) + set(header ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-client-protocol.h) + set(source ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/${name}-protocol.c) + add_custom_command( + OUTPUT ${header} + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ + COMMAND ${O2GPUWayland_SCANNER} client-header ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${header} + MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} + COMMENT "Preparing Wayland ${name}-client-protocol.h") + add_custom_command( + OUTPUT ${source} + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/xdg-gen/ + COMMAND ${O2GPUWayland_SCANNER} private-code ${O2GPUWayland_PROTOCOLS_DIR}/${xml} ${source} + MAIN_DEPENDENCY ${O2GPUWayland_PROTOCOLS_DIR}/${xml} + COMMENT "Preparing Wayland ${name}-protocol.c") + list(APPEND WAYLAND_PROTOCOL_HEADERS ${header}) + 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(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayFrontendWayland.cxx ${WAYLAND_PROTOCOL_SOURCES}) endif() if(X11_FOUND) @@ -146,6 +156,10 @@ if(GLUT_FOUND) set(SRCS ${SRCS} frontend/GPUDisplayFrontendGlut.cxx) endif() +if(GLFW_FOUND) + set(SRCS ${SRCS} frontend/GPUDisplayFrontendGlfw.cxx) +endif() + if(GPUCA_EVENT_DISPLAY_QT) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -160,12 +174,10 @@ string(REPLACE ".cxx" ".h" HDRS "${SRCS}") if(ALIGPU_BUILD_TYPE STREQUAL "O2") o2_add_library(${MODULE} TARGETVARNAME targetName - PUBLIC_LINK_LIBRARIES O2::GPUTracking AliceO2::DebugGUI + PUBLIC_LINK_LIBRARIES O2::GPUTracking PUBLIC_INCLUDE_DIRECTORIES . SOURCES ${SRCS} ${SRCS_NO_H}) - target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLFW GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) - o2_add_executable(field-uniform-exporter TARGETVARNAME exporterName COMPONENT_NAME gpu @@ -192,7 +204,6 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/setinclude.sh PERMISSIONS OWNER_READ O target_compile_definitions(${targetName} PRIVATE $) message(STATUS "Building GPU Event Display (OpenGL ${GPUCA_EVENT_DISPLAY_OPENGL}, Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${Fontconfig_FOUND}, Qt ${GPUCA_EVENT_DISPLAY_QT})") -target_link_libraries(${targetName} PUBLIC ${GLFW_LIBRARIES} OpenGL::GL) target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) if(X11_FOUND) @@ -205,13 +216,23 @@ if(GLUT_FOUND) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLUT) endif() -if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") - if(GLFW_FOUND) - target_link_libraries(${targetName} PRIVATE glfw) - target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLFW) - endif() +if(GLFW_FOUND) + target_link_libraries(${targetName} PRIVATE ${GLFW_LIBRARIES} glfw) + target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_GLFW) +endif() + +if (OPENGL_GLU_FOUND) + target_link_libraries(${targetName} PRIVATE OpenGL::GLU) +endif() + +if(GPUCA_CONFIG_GL3W) + target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W GPUCA_DISPLAY_OPENGL_CORE) +endif() + +if(ALIGPU_BUILD_TYPE STREQUAL "O2") + target_link_libraries(${targetName} PRIVATE AliceO2::DebugGUI) +elseif(ALIGPU_BUILD_TYPE STREQUAL "Standalone") if(GPUCA_CONFIG_GL3W) - target_compile_definitions(${targetName} PRIVATE GPUCA_DISPLAY_GL3W) target_sources(${targetName} PRIVATE ${GPUTRACKING_DIR}/display/3rdparty/gl3w.c) else() target_link_libraries(${targetName} PRIVATE ${GLEW_LIBRARIES}) @@ -220,12 +241,9 @@ if(ALIGPU_BUILD_TYPE STREQUAL "Standalone") target_include_directories(${targetName} SYSTEM PUBLIC ${GPUTRACKING_DIR}/display/3rdparty) endif() -if (OPENGL_GLU_FOUND) - target_link_libraries(${targetName} PRIVATE OpenGL::GLU) -endif() - if(GPUCA_EVENT_DISPLAY_OPENGL) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_OPENGL) + target_link_libraries(${targetName} PRIVATE OpenGL::GL) endif() if(GPUCA_EVENT_DISPLAY_VULKAN OR GPUCA_EVENT_DISPLAY_OPENGL) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx index 77c1c0a49a4a5..dd39cb071efc8 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.cxx @@ -15,8 +15,10 @@ #include "GPUDisplayBackend.h" #include "helpers/GPUDisplayMagneticField.h" -#include "GPUDisplayBackendOpenGL.h" #include "GPUDisplayBackendNone.h" +#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL +#include "GPUDisplayBackendOpenGL.h" +#endif #ifdef GPUCA_BUILD_EVENT_DISPLAY_VULKAN #include "GPUDisplayBackendVulkan.h" #endif @@ -50,9 +52,12 @@ GPUDisplayBackend* GPUDisplayBackend::getBackend(const char* type) return new GPUDisplayBackendVulkan; } else #endif - if (strcmp(type, "opengl") == 0 || strcmp(type, "auto") == 0) { +#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL + if (strcmp(type, "opengl") == 0 || strcmp(type, "auto") == 0) { return new GPUDisplayBackendOpenGL; - } else if (strcmp(type, "none") == 0) { + } else +#endif + if (strcmp(type, "none") == 0) { return new GPUDisplayBackendNone; } else { GPUError("Requested renderer not available"); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 0b222d25cb126..5d2a85cafbc1f 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -22,10 +22,6 @@ #include #include -#if defined(GPUCA_DISPLAY_GL3W) && !defined(GPUCA_DISPLAY_OPENGL_CORE) -#define GPUCA_DISPLAY_OPENGL_CORE -#endif - union hmm_mat4; namespace o2::gpu diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index 0fb4846ad9c2e..335b7ef6ddd69 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -35,7 +35,6 @@ using namespace o2::gpu; -#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL // Set by CMake if OpenGL 4.6 headers and glslc are available, runtime minimum version in GPUDisplayFrontend.h #define GPUCA_DISPLAY_SHADER_TYPE_OPENGL #include "GPUDisplayLoadShaders.h" @@ -766,33 +765,3 @@ void GPUDisplayBackendOpenGL::updateRenderer(bool withScreenshot) createFB(mMixBuffer, true, true, false, mRenderWidth, mRenderHeight); setQuality(); } - -#else // GPUCA_BUILD_EVENT_DISPLAY_OPENGL -GPUDisplayBackendOpenGL::GPUDisplayBackendOpenGL() -{ -} -int32_t GPUDisplayBackendOpenGL::checkShaderStatus(uint32_t shader) { return 0; } -int32_t GPUDisplayBackendOpenGL::checkProgramStatus(uint32_t program) { return 0; } -int32_t GPUDisplayBackendOpenGL::ExtInit() { throw std::runtime_error("Insufficnet OpenGL version"); } -bool GPUDisplayBackendOpenGL::CoreProfile() { return false; } -uint32_t GPUDisplayBackendOpenGL::DepthBits() { return 0; } -uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType t) { return 0; } -void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) {} -void GPUDisplayBackendOpenGL::setQuality() {} -void GPUDisplayBackendOpenGL::setDepthBuffer() {} -int32_t GPUDisplayBackendOpenGL::InitBackendA() { throw std::runtime_error("Insufficnet OpenGL version"); } -void GPUDisplayBackendOpenGL::ExitBackendA() {} -void GPUDisplayBackendOpenGL::loadDataToGPU(size_t totalVertizes) {} -void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& view, bool requestScreenshot, bool toMixBuffer, float includeMixImage) {} -void GPUDisplayBackendOpenGL::resizeScene(uint32_t width, uint32_t height) {} -void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) {} -void GPUDisplayBackendOpenGL::finishFrame(bool doScreenshot, bool toMixBuffer, float includeMixImage) {} -void GPUDisplayBackendOpenGL::prepareText() {} -void GPUDisplayBackendOpenGL::finishText() {} -void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) {} -void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) {} -void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) {} -void GPUDisplayBackendOpenGL::initializeTextDrawing() {} -void GPUDisplayBackendOpenGL::clearFontSymbols() {} -void GPUDisplayBackendOpenGL::OpenGLPrint(const char* s, float x, float y, float* color, float scale) {} -#endif // GPUCA_BUILD_EVENT_DISPLAY_OPENGL diff --git a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx index 4eebf7bcf227f..00dbb35dd5221 100644 --- a/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx +++ b/GPU/GPUTracking/display/helpers/GPUDisplayBackendOpenGLMagneticField.cxx @@ -32,8 +32,6 @@ using namespace o2::gpu; -#ifdef GPUCA_BUILD_EVENT_DISPLAY_OPENGL // Set by CMake, see GPUDisplayBackendOpenGL.cxx - #define GPUCA_DISPLAY_SHADER_TYPE_OPENGL #include "GPUDisplayLoadShaders.h" @@ -161,12 +159,3 @@ void GPUDisplayBackendOpenGL::ExitMagFieldVisualization() CHKERR(glDeleteBuffers(1, &VBO_field)); CHKERR(glDeleteVertexArrays(1, &VAO_field)); } - -#else // GPUCA_BUILD_EVENT_DISPLAY_OPENGL -int32_t GPUDisplayBackendOpenGL::InitMagFieldVisualization() -{ - return 0; -} -uint32_t GPUDisplayBackendOpenGL::drawField() { return 0; } -void GPUDisplayBackendOpenGL::ExitMagFieldVisualization() {} -#endif // GPUCA_BUILD_EVENT_DISPLAY_OPENGL From 3702d32ac309935c85317f72c4ddbbc4892c1b0b Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 16:32:09 +0200 Subject: [PATCH 37/48] GPU Vulkan Display: Switch to timeline semaphore (Vulkan 1.2) --- .../backend/GPUDisplayBackendVulkan.cxx | 84 ++++++++++++------- .../display/backend/GPUDisplayBackendVulkan.h | 17 ++-- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 6bc4748aef8b3..626296e558123 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -163,22 +163,42 @@ vk::CommandBuffer GPUDisplayBackendVulkan::getSingleTimeCommandBuffer() return commandBuffer; } +template +uint64_t GPUDisplayBackendVulkan::queueSubmit(uint64_t* timelineSignalValue, Args&&... args) +{ + std::lock_guard guard(mQueueMutex); + if (timelineSignalValue) { + *timelineSignalValue = ++mTimelineValue; + } + CHKERR(mGraphicsQueue.submit(std::forward(args)...)); + return mTimelineValue; +} + void GPUDisplayBackendVulkan::submitSingleTimeCommandBuffer(vk::CommandBuffer commandBuffer) { commandBuffer.end(); vk::SubmitInfo submitInfo{}; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; - static std::mutex fenceMutex; - { - std::lock_guard guard(fenceMutex); - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mSingleCommitFence)); - CHKERR(mDevice.waitForFences(1, &mSingleCommitFence, true, UINT64_MAX)); - CHKERR(mDevice.resetFences(1, &mSingleCommitFence)); - } + uint64_t signalValue; + vk::TimelineSemaphoreSubmitInfo timelineInfo{}; + timelineInfo.signalSemaphoreValueCount = 1; + timelineInfo.pSignalSemaphoreValues = &signalValue; + submitInfo.pNext = &timelineInfo; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &mTimelineSemaphore; + waitTimeline(queueSubmit(&signalValue, 1, &submitInfo, VkFence(VK_NULL_HANDLE))); mDevice.freeCommandBuffers(mCommandPool, 1, &commandBuffer); } +void GPUDisplayBackendVulkan::waitTimeline(uint64_t value) +{ + if (value) { + vk::SemaphoreWaitInfo waitInfo{{}, 1, &mTimelineSemaphore, &value}; + CHKERR(mDevice.waitSemaphores(&waitInfo, UINT64_MAX)); + } +} + static vk::ImageView createImageViewI(vk::Device device, vk::Image image, vk::Format format, vk::ImageAspectFlags aspectFlags = vk::ImageAspectFlagBits::eColor, uint32_t mipLevels = 1) { vk::ImageViewCreateInfo viewInfo{}; @@ -477,6 +497,7 @@ void GPUDisplayBackendVulkan::createDevice() clearFeatures(reqDeviceFeatures); auto& reqFeatures10 = reqDeviceFeatures.get().features; reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = reqFeatures10.multiDrawIndirect = VK_TRUE; + reqDeviceFeatures.get().timelineSemaphore = VK_TRUE; reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; reqDeviceFeatures.get().presentId = VK_TRUE; reqDeviceFeatures.get().presentWait = VK_TRUE; @@ -583,33 +604,30 @@ void GPUDisplayBackendVulkan::clearCommandBuffers() // ---------------------------- VULKAN SEMAPHORES AND FENCES ---------------------------- -void GPUDisplayBackendVulkan::createSemaphoresAndFences() +void GPUDisplayBackendVulkan::createSemaphores() { vk::SemaphoreCreateInfo semaphoreInfo{}; - vk::FenceCreateInfo fenceInfo{}; - fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; mImageAvailableSemaphore.resize(mFramesInFlight); mPresentSemaphore.resize(mImageCount); - mInFlightFence.resize(mFramesInFlight); - mImageFence.resize(mImageCount); for (uint32_t i = 0; i < mFramesInFlight; i++) { mImageAvailableSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mInFlightFence[i] = mDevice.createFence(fenceInfo, nullptr); } for (uint32_t i = 0; i < mImageCount; i++) { mPresentSemaphore[i] = mDevice.createSemaphore(semaphoreInfo, nullptr); - mImageFence[i] = VkFence(VK_NULL_HANDLE); } - fenceInfo.flags = {}; - mSingleCommitFence = mDevice.createFence(fenceInfo, nullptr); + vk::SemaphoreTypeCreateInfo timelineTypeInfo{vk::SemaphoreType::eTimeline, 0}; + semaphoreInfo.pNext = &timelineTypeInfo; + mTimelineSemaphore = mDevice.createSemaphore(semaphoreInfo, nullptr); + mTimelineValue = 0; + mFrameTimelineValue.assign(mFramesInFlight, 0); + mImageTimelineValue.assign(mImageCount, 0); } -void GPUDisplayBackendVulkan::clearSemaphoresAndFences() +void GPUDisplayBackendVulkan::clearSemaphores() { clearVector(mImageAvailableSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); clearVector(mPresentSemaphore, [&](auto& x) { mDevice.destroySemaphore(x, nullptr); }); - clearVector(mInFlightFence, [&](auto& x) { mDevice.destroyFence(x, nullptr); }); - mDevice.destroyFence(mSingleCommitFence, nullptr); + mDevice.destroySemaphore(mTimelineSemaphore, nullptr); } // ---------------------------- VULKAN UNIFORM LAYOUTS AND BUFFERS ---------------------------- @@ -802,11 +820,11 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing if (mCommandInfrastructureCreated) { clearUniformLayoutsAndBuffers(); // TODO: Check what must actually be cleaned up if frames in flight changes clearCommandBuffers(); - clearSemaphoresAndFences(); + clearSemaphores(); } createUniformLayoutsAndBuffers(); createCommandBuffers(); - createSemaphoresAndFences(); + createSemaphores(); mCommandInfrastructureCreated = true; } @@ -1468,7 +1486,7 @@ void GPUDisplayBackendVulkan::ExitBackendA() clearOffscreenBuffers(); clearSwapChain(); if (mCommandInfrastructureCreated) { - clearSemaphoresAndFences(); + clearSemaphores(); clearCommandBuffers(); clearUniformLayoutsAndBuffers(); } @@ -1539,7 +1557,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& if (includeMixImage == 0.f) { mCurrentFrame = (mCurrentFrame + 1) % mFramesInFlight; - CHKERR(mDevice.waitForFences(1, &mInFlightFence[mCurrentFrame], true, UINT64_MAX)); + waitTimeline(mFrameTimelineValue[mCurrentFrame]); auto getImage = [&]() { return mDevice.acquireNextImageKHR(mSwapChain, UINT64_MAX, mImageAvailableSemaphore[mCurrentFrame], VkFence(VK_NULL_HANDLE), &mCurrentImageIndex); }; @@ -1558,11 +1576,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& } mMustUpdateSwapChain = retVal == vk::Result::eSuboptimalKHR; CHKERR(retVal); - if (mImageFence[mCurrentImageIndex]) { - CHKERR(mDevice.waitForFences(1, &mImageFence[mCurrentImageIndex], true, UINT64_MAX)); - } - mImageFence[mCurrentImageIndex] = mInFlightFence[mCurrentFrame]; - CHKERR(mDevice.resetFences(1, &mInFlightFence[mCurrentFrame])); + waitTimeline(mImageTimelineValue[mCurrentImageIndex]); // Previous frame using this image's resources (command buffers, uniform buffer, ...) finished mHasDrawnText = false; mFrameCommandBuffers.clear(); @@ -1608,7 +1622,7 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f if (doScreenshot) { submitInfo.commandBufferCount = mFrameCommandBuffers.size(); submitInfo.pCommandBuffers = mFrameCommandBuffers.data(); - CHKERR(mGraphicsQueue.submit(1, &submitInfo, VkFence(VK_NULL_HANDLE))); + queueSubmit(nullptr, 1, &submitInfo, VkFence(VK_NULL_HANDLE)); mDevice.waitIdle(); if (mDisplay->cfgR().screenshotScaleFactor != 1) { readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); @@ -1623,9 +1637,15 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f } submitInfo.commandBufferCount = mFrameCommandBuffers.size(); submitInfo.pCommandBuffers = mFrameCommandBuffers.data(); - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &mPresentSemaphore[mCurrentImageIndex]; - CHKERR(mGraphicsQueue.submit(1, &submitInfo, mInFlightFence[mCurrentFrame])); + const vk::Semaphore signalSemaphores[2] = {mPresentSemaphore[mCurrentImageIndex], mTimelineSemaphore}; + uint64_t signalValues[2] = {0, 0}; // First value ignored for the binary present semaphore + vk::TimelineSemaphoreSubmitInfo timelineInfo{}; + timelineInfo.signalSemaphoreValueCount = 2; + timelineInfo.pSignalSemaphoreValues = signalValues; + submitInfo.pNext = &timelineInfo; + submitInfo.signalSemaphoreCount = 2; + submitInfo.pSignalSemaphores = signalSemaphores; + mFrameTimelineValue[mCurrentFrame] = mImageTimelineValue[mCurrentImageIndex] = queueSubmit(&signalValues[1], 1, &submitInfo, VkFence(VK_NULL_HANDLE)); vk::PresentInfoKHR presentInfo{}; presentInfo.waitSemaphoreCount = 1; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 570e31d05193f..cd6e23043e8da 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -20,6 +20,7 @@ #include #include +#include #include namespace o2::gpu @@ -98,6 +99,9 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void startFillCommandBuffer(vk::CommandBuffer& commandBuffer, uint32_t imageIndex, bool toMixBuffer = false); void endFillCommandBuffer(vk::CommandBuffer& commandBuffer); vk::CommandBuffer getSingleTimeCommandBuffer(); + void waitTimeline(uint64_t value); // Wait until the timeline semaphore reached value, 0 = nothing to wait for + template + uint64_t queueSubmit(uint64_t* timelineSignalValue, Args&&... args); // Submit under mQueueMutex; if timelineSignalValue (the entry in pSignalSemaphoreValues) is set, write the next timeline value there first. Returns the current timeline value void submitSingleTimeCommandBuffer(vk::CommandBuffer commandBuffer); void readImageToPixels(vk::Image image, vk::ImageLayout layout, std::vector& pixels); void downsampleToFramebuffer(vk::CommandBuffer& commandBuffer); @@ -108,7 +112,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void createShaders(); void createTextureSampler(); void createCommandBuffers(); - void createSemaphoresAndFences(); + void createSemaphores(); void createUniformLayoutsAndBuffers(); void createSwapChain(bool forScreenshot = false, bool forMixing = false); void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); @@ -117,7 +121,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void clearShaders(); void clearTextureSampler(); void clearCommandBuffers(); - void clearSemaphoresAndFences(); + void clearSemaphores(); void clearUniformLayoutsAndBuffers(); void clearOffscreenBuffers(); void clearSwapChain(); @@ -180,8 +184,11 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mCommandBufferUpToDate; std::vector mImageAvailableSemaphore; std::vector mPresentSemaphore; - std::vector mInFlightFence; - std::vector mImageFence; + vk::Semaphore mTimelineSemaphore; // Signaled with an increasing value by every queue submit, replaces fences + uint64_t mTimelineValue = 0; // Last value used for a submit + std::vector mFrameTimelineValue; // Per frame slot: value of its last submit, slot (and its acquire semaphore) free once reached + std::vector mImageTimelineValue; // Per swapchain image: value of the last submit using its resources + std::mutex mQueueMutex; // Queue submissions must be externally synchronized std::vector mUniformBuffersMat[3]; std::vector mUniformBuffersCol[3]; std::vector mDescriptorSets[3]; @@ -210,8 +217,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mMixingSupported = 0; VulkanBuffer mMixingTextureVertexArray; - - vk::Fence mSingleCommitFence; }; } // namespace o2::gpu From bad14727c24679ff8c8e544d139ce1d0dbab8068 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 16:35:24 +0200 Subject: [PATCH 38/48] GPU Vulkan Display: provide fallback in case no device-local host-visible memory is available --- GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 626296e558123..3f7fcbe461fa6 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -79,6 +79,9 @@ static uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags prop return i; } } + if ((properties & vk::MemoryPropertyFlagBits::eDeviceLocal) && (properties & vk::MemoryPropertyFlagBits::eHostVisible)) { // No host-visible device memory (e.g. no ReBAR), fall back to host memory + return findMemoryType(typeFilter, properties & ~vk::MemoryPropertyFlags(vk::MemoryPropertyFlagBits::eDeviceLocal), physDev); + } throw std::runtime_error("failed to find suitable memory type!"); } From 9df7324e15c821fc8d005f1797e0cc3635e807d7 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 18:37:45 +0200 Subject: [PATCH 39/48] GPU Display Qt GUI: Avoid Qt CMake AUTOMOC --- GPU/GPUTracking/display/CMakeLists.txt | 20 +++++++++++++------ .../display/frontend/GPUDisplayGUI.cxx | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index e9a0633f657ab..ae27bd1395dd5 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -84,10 +84,18 @@ if(Vulkan_FOUND) endif() if(Freetype_FOUND) set(GPUCA_EVENT_DISPLAY_FREETYPE ON) + if(Fontconfig_FOUND) + set(GPUCA_EVENT_DISPLAY_FONTCONFIG ON) + endif() endif() if(Qt6_FOUND) set(GPUCA_EVENT_DISPLAY_QT ON) endif() +foreach(feature OPENGL VULKAN WAYLAND FREETYPE FONTCONFIG QT) + if(NOT GPUCA_EVENT_DISPLAY_${feature}) + set(GPUCA_EVENT_DISPLAY_${feature} OFF) + endif() +endforeach() set(SRCS ../utils/qsem.cxx GPUDisplay.cxx @@ -161,12 +169,11 @@ if(GLFW_FOUND) endif() if(GPUCA_EVENT_DISPLAY_QT) - set(CMAKE_AUTOMOC ON) - set(CMAKE_AUTORCC ON) - set(CMAKE_AUTOUIC ON) + 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) set(SRCS ${SRCS} frontend/GPUDisplayGUI.cxx frontend/GPUDisplayGUIWrapper.cxx) - set(SRCS_NO_H ${SRCS_NO_H} frontend/GPUDisplayGUI.ui) + set(SRCS_NO_H ${SRCS_NO_H} ${GPUCA_QT_MOC_SRCS} ${GPUCA_QT_UI_HDRS}) endif() string(REPLACE ".cxx" ".h" HDRS "${SRCS}") @@ -203,7 +210,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/setinclude.sh PERMISSIONS OWNER_READ O target_compile_definitions(${targetName} PRIVATE $) -message(STATUS "Building GPU Event Display (OpenGL ${GPUCA_EVENT_DISPLAY_OPENGL}, Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${Fontconfig_FOUND}, Qt ${GPUCA_EVENT_DISPLAY_QT})") +message(STATUS "Building GPU Event Display (OpenGL ${GPUCA_EVENT_DISPLAY_OPENGL}, Vulkan ${GPUCA_EVENT_DISPLAY_VULKAN} ${Vulkan_VERSION}, Wayland ${GPUCA_EVENT_DISPLAY_WAYLAND}, Freetype ${GPUCA_EVENT_DISPLAY_FREETYPE}, Fontconfig ${GPUCA_EVENT_DISPLAY_FONTCONFIG}, Qt ${GPUCA_EVENT_DISPLAY_QT})") target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) if(X11_FOUND) @@ -275,7 +282,7 @@ endif() if(GPUCA_EVENT_DISPLAY_FREETYPE) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_FREETYPE) target_link_libraries(${targetName} PRIVATE Freetype::Freetype) - if(Fontconfig_FOUND) + if(GPUCA_EVENT_DISPLAY_FONTCONFIG) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_FONTCONFIG) target_link_libraries(${targetName} PRIVATE Fontconfig::Fontconfig) endif() @@ -284,6 +291,7 @@ endif() if(GPUCA_EVENT_DISPLAY_QT) target_compile_definitions(${targetName} PRIVATE GPUCA_BUILD_EVENT_DISPLAY_QT) target_link_libraries(${targetName} PRIVATE Qt6::Widgets) + target_include_directories(${targetName} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # ui_*.h endif() target_link_libraries(${targetName} PRIVATE TBB::tbb) diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx index 019292228b157..f02fd7914de0b 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayGUI.cxx @@ -14,7 +14,7 @@ #include "GPUDisplayGUI.h" #include "GPUDisplayGUIWrapper.h" -#include "./ui_GPUDisplayGUI.h" +#include "ui_GPUDisplayGUI.h" using namespace o2::gpu; From bc75a9a429991ef0b08cecbb3c92ea716e165dfd Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 18:54:06 +0200 Subject: [PATCH 40/48] GPU Vulkan Backend: Switch to Dynamic Rendering / Synchronization2 --- .../backend/GPUDisplayBackendVulkan.cxx | 329 +++++------------- .../display/backend/GPUDisplayBackendVulkan.h | 10 +- 2 files changed, 96 insertions(+), 243 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 3f7fcbe461fa6..fb9f6fab83924 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -133,17 +133,21 @@ static vk::ShaderModule createShaderModule(const char* code, size_t size, vk::De return device.createShaderModule(createInfo, nullptr); } -static void cmdImageMemoryBarrier(vk::CommandBuffer cmdbuffer, vk::Image image, vk::AccessFlags srcAccessMask, vk::AccessFlags dstAccessMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::PipelineStageFlags srcStageMask, vk::PipelineStageFlags dstStageMask) +static void cmdImageMemoryBarrier(vk::CommandBuffer cmdbuffer, vk::Image image, vk::PipelineStageFlags2 srcStageMask, vk::AccessFlags2 srcAccessMask, vk::PipelineStageFlags2 dstStageMask, vk::AccessFlags2 dstAccessMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor) { - vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}; - vk::ImageMemoryBarrier barrier{}; - barrier.srcAccessMask = srcAccessMask; - barrier.dstAccessMask = dstAccessMask; - barrier.oldLayout = oldLayout; - barrier.newLayout = newLayout; - barrier.image = image; - barrier.subresourceRange = range; - cmdbuffer.pipelineBarrier(srcStageMask, dstStageMask, {}, 0, nullptr, 0, nullptr, 1, &barrier); + vk::ImageMemoryBarrier2 barrier{srcStageMask, srcAccessMask, dstStageMask, dstAccessMask, oldLayout, newLayout, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, image, {aspect, 0, 1, 0, 1}}; + cmdbuffer.pipelineBarrier2(vk::DependencyInfo{{}, 0, nullptr, 0, nullptr, 1, &barrier}); +} + +static void cmdBeginRendering(vk::CommandBuffer cmdbuffer, uint32_t width, uint32_t height, const vk::RenderingAttachmentInfo& color, const vk::RenderingAttachmentInfo* depth = nullptr) +{ + cmdbuffer.beginRendering(vk::RenderingInfo{{}, vk::Rect2D{{0, 0}, {width, height}}, 1, 0, 1, &color, depth}); +} + +static void cmdBeginRenderingLoad(vk::CommandBuffer cmdbuffer, vk::Image image, vk::ImageView view, uint32_t width, uint32_t height) // Render on top of the content of a color attachment written by a previous pass +{ + cmdImageMemoryBarrier(cmdbuffer, image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eColorAttachmentOptimal); + cmdBeginRendering(cmdbuffer, width, height, vk::RenderingAttachmentInfo{view, vk::ImageLayout::eColorAttachmentOptimal}); // loadOp eLoad, storeOp eStore } void GPUDisplayBackendVulkan::updateSwapChainDetails(const vk::PhysicalDevice& device) @@ -173,24 +177,17 @@ uint64_t GPUDisplayBackendVulkan::queueSubmit(uint64_t* timelineSignalValue, Arg if (timelineSignalValue) { *timelineSignalValue = ++mTimelineValue; } - CHKERR(mGraphicsQueue.submit(std::forward(args)...)); + CHKERR(mGraphicsQueue.submit2(std::forward(args)...)); return mTimelineValue; } void GPUDisplayBackendVulkan::submitSingleTimeCommandBuffer(vk::CommandBuffer commandBuffer) { commandBuffer.end(); - vk::SubmitInfo submitInfo{}; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &commandBuffer; - uint64_t signalValue; - vk::TimelineSemaphoreSubmitInfo timelineInfo{}; - timelineInfo.signalSemaphoreValueCount = 1; - timelineInfo.pSignalSemaphoreValues = &signalValue; - submitInfo.pNext = &timelineInfo; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &mTimelineSemaphore; - waitTimeline(queueSubmit(&signalValue, 1, &submitInfo, VkFence(VK_NULL_HANDLE))); + vk::CommandBufferSubmitInfo commandBufferInfo{commandBuffer}; + vk::SemaphoreSubmitInfo signalInfo{mTimelineSemaphore, 0, vk::PipelineStageFlagBits2::eAllCommands}; + vk::SubmitInfo2 submitInfo{{}, 0, nullptr, 1, &commandBufferInfo, 1, &signalInfo}; + waitTimeline(queueSubmit(&signalInfo.value, 1, &submitInfo, VkFence(VK_NULL_HANDLE))); mDevice.freeCommandBuffers(mCommandPool, 1, &commandBuffer); } @@ -501,6 +498,7 @@ void GPUDisplayBackendVulkan::createDevice() auto& reqFeatures10 = reqDeviceFeatures.get().features; reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = reqFeatures10.multiDrawIndirect = VK_TRUE; reqDeviceFeatures.get().timelineSemaphore = VK_TRUE; + reqDeviceFeatures.get().dynamicRendering = reqDeviceFeatures.get().synchronization2 = VK_TRUE; reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; reqDeviceFeatures.get().presentId = VK_TRUE; reqDeviceFeatures.get().presentWait = VK_TRUE; @@ -594,6 +592,7 @@ void GPUDisplayBackendVulkan::createCommandBuffers() mCommandBuffersTexture = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersDownsample = mDevice.allocateCommandBuffers(allocInfo); mCommandBuffersMix = mDevice.allocateCommandBuffers(allocInfo); + mCommandBuffersPresent = mDevice.allocateCommandBuffers(allocInfo); } void GPUDisplayBackendVulkan::clearCommandBuffers() @@ -603,6 +602,7 @@ void GPUDisplayBackendVulkan::clearCommandBuffers() mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersTexture.size(), mCommandBuffersTexture.data()); mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersDownsample.size(), mCommandBuffersDownsample.data()); mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersMix.size(), mCommandBuffersMix.data()); + mDevice.freeCommandBuffers(mCommandPool, mCommandBuffersPresent.size(), mCommandBuffersPresent.data()); } // ---------------------------- VULKAN SEMAPHORES AND FENCES ---------------------------- @@ -834,6 +834,10 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing mSwapChainImageViews.resize(mImageCount); for (uint32_t i = 0; i < mImageCount; i++) { mSwapChainImageViews[i] = createImageViewI(mDevice, mSwapChainImages[i], mSurfaceFormat.format); + mCommandBuffersPresent[i].reset({}); + mCommandBuffersPresent[i].begin(vk::CommandBufferBeginInfo{}); + cmdImageMemoryBarrier(mCommandBuffersPresent[i], mSwapChainImages[i], vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eNone, vk::AccessFlagBits2::eNone, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::ePresentSrcKHR); + mCommandBuffersPresent[i].end(); } } @@ -869,82 +873,9 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo mZActive = mZSupported && mDisplay->cfgL().depthBuffer; mMixingSupported = forMixing; - vk::AttachmentDescription colorAttachment{}; - colorAttachment.format = mSurfaceFormat.format; - colorAttachment.samples = mMSAASampleCount; - colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; - colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - colorAttachment.initialLayout = vk::ImageLayout::eUndefined; - colorAttachment.finalLayout = (mMSAASampleCount != vk::SampleCountFlagBits::e1 || mDownsampleFSAA) ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - vk::AttachmentDescription depthAttachment{}; - depthAttachment.format = vk::Format::eD32Sfloat; - depthAttachment.samples = mMSAASampleCount; - depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; - depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare; - depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - depthAttachment.initialLayout = vk::ImageLayout::eUndefined; - depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - vk::AttachmentDescription colorAttachmentResolve{}; - colorAttachmentResolve.format = mSurfaceFormat.format; - colorAttachmentResolve.samples = vk::SampleCountFlagBits::e1; - colorAttachmentResolve.loadOp = vk::AttachmentLoadOp::eDontCare; - colorAttachmentResolve.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachmentResolve.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; - colorAttachmentResolve.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; - colorAttachmentResolve.initialLayout = vk::ImageLayout::eUndefined; - colorAttachmentResolve.finalLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - int32_t nAttachments = 0; - vk::AttachmentReference colorAttachmentRef{}; - colorAttachmentRef.attachment = nAttachments++; - colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; - vk::AttachmentReference depthAttachmentRef{}; - depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal; - vk::AttachmentReference colorAttachmentResolveRef{}; - colorAttachmentResolveRef.layout = vk::ImageLayout::eColorAttachmentOptimal; - vk::SubpassDescription subpass{}; - subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - std::array dependencies{}; - dependencies[0].srcSubpass = dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; - dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader; // Includes eColorAttachmentOutput, the wait stage of the image acquire semaphore - dependencies[0].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits::eTransferWrite; - dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests; - dependencies[0].dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite; - dependencies[1].srcSubpass = 0; - dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eLateFragmentTests; - dependencies[1].srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite; - dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader; - dependencies[1].dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eShaderRead; - - std::vector attachments = {colorAttachment}; - if (mZActive) { - attachments.emplace_back(depthAttachment); - depthAttachmentRef.attachment = nAttachments++; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { - attachments.emplace_back(colorAttachmentResolve); - colorAttachmentResolveRef.attachment = nAttachments++; - subpass.pResolveAttachments = &colorAttachmentResolveRef; - } - - vk::RenderPassCreateInfo renderPassInfo{}; - renderPassInfo.attachmentCount = attachments.size(); - renderPassInfo.pAttachments = attachments.data(); - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = dependencies.size(); - renderPassInfo.pDependencies = dependencies.data(); - mRenderPass = mDevice.createRenderPass(renderPassInfo, nullptr); - const uint32_t imageCountWithMixImages = mImageCount * (mMixingSupported ? 2 : 1); mRenderTargetView.resize(imageCountWithMixImages); - mFramebuffers.resize(imageCountWithMixImages); + mRenderTargetImage.resize(imageCountWithMixImages); if (mDownsampleFSAA) { mDownsampleImages.resize(imageCountWithMixImages); } @@ -954,35 +885,8 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo if (mZActive) { mZImages.resize(imageCountWithMixImages); } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1 || mZActive || mDownsampleFSAA) { - mFramebuffersText.resize(mImageCount); - } - if (mMixingSupported) { - if (mMSAASampleCount != vk::SampleCountFlagBits::e1 || mZActive || mDownsampleFSAA) { - mFramebuffersTexture.resize(mImageCount); - } - if (!mDownsampleFSAA) { - mMixImages.resize(mImageCount); - } - } - - // Text overlay goes as extra rendering path - renderPassInfo.attachmentCount = 1; // Remove depth and MSAA attachments - renderPassInfo.pAttachments = &colorAttachment; - subpass.pDepthStencilAttachment = nullptr; - subpass.pResolveAttachments = nullptr; - colorAttachment.loadOp = vk::AttachmentLoadOp::eLoad; // Don't clear the frame buffer - colorAttachment.initialLayout = vk::ImageLayout::ePresentSrcKHR; // Initial layout is not undefined after 1st pass - colorAttachment.samples = vk::SampleCountFlagBits::e1; // No MSAA for Text - colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; // Might have been overwritten above for 1st pass in case of MSAA - mRenderPassText = mDevice.createRenderPass(renderPassInfo, nullptr); - - if (mMixingSupported) { - if (mDownsampleFSAA) { - colorAttachment.initialLayout = vk::ImageLayout::eColorAttachmentOptimal; - colorAttachment.finalLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - } - mRenderPassTexture = mDevice.createRenderPass(renderPassInfo, nullptr); + if (mMixingSupported && !mDownsampleFSAA) { + mMixImages.resize(mImageCount); } for (uint32_t i = 0; i < imageCountWithMixImages; i++) { @@ -992,56 +896,23 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo createImageI(mDevice, mPhysicalDevice, mMixImages[i - mImageCount].image, mMixImages[i - mImageCount].memory, mRenderWidth, mRenderHeight, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); mMixImages[i - mImageCount].view = createImageViewI(mDevice, mMixImages[i - mImageCount].image, mSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1); } - std::vector att; if (mDownsampleFSAA) { vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | (i >= mImageCount ? vk::ImageUsageFlagBits::eSampled : vk::ImageUsageFlagBits::eTransferSrc); createImageI(mDevice, mPhysicalDevice, mDownsampleImages[i].image, mDownsampleImages[i].memory, mRenderWidth, mRenderHeight, mSurfaceFormat.format, usage, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); mDownsampleImages[i].view = createImageViewI(mDevice, mDownsampleImages[i].image, mSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1); mRenderTargetView[i] = &mDownsampleImages[i].view; + mRenderTargetImage[i] = mDownsampleImages[i].image; } else { mRenderTargetView[i] = i < mImageCount ? &mSwapChainImageViews[i] : &mMixImages[i - mImageCount].view; + mRenderTargetImage[i] = i < mImageCount ? mSwapChainImages[i] : mMixImages[i - mImageCount].image; } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { // First attachment is the render target, either the MSAA buffer or the framebuffer + if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { createImageI(mDevice, mPhysicalDevice, mMSAAImages[i].image, mMSAAImages[i].memory, mRenderWidth, mRenderHeight, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal, mMSAASampleCount); mMSAAImages[i].view = createImageViewI(mDevice, mMSAAImages[i].image, mSurfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1); - att.emplace_back(mMSAAImages[i].view); - } else { - att.emplace_back(*mRenderTargetView[i]); } if (mZActive) { createImageI(mDevice, mPhysicalDevice, mZImages[i].image, mZImages[i].memory, mRenderWidth, mRenderHeight, vk::Format::eD32Sfloat, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal, mMSAASampleCount); mZImages[i].view = createImageViewI(mDevice, mZImages[i].image, vk::Format::eD32Sfloat, vk::ImageAspectFlagBits::eDepth, 1); - att.emplace_back(mZImages[i].view); - } - if (mMSAASampleCount != vk::SampleCountFlagBits::e1) { // If we use MSAA, we have to resolve to the framebuffer as the last target - att.emplace_back(*mRenderTargetView[i]); - } - - vk::FramebufferCreateInfo framebufferInfo{}; - framebufferInfo.renderPass = mRenderPass; - framebufferInfo.attachmentCount = att.size(); - framebufferInfo.pAttachments = att.data(); - framebufferInfo.width = mRenderWidth; - framebufferInfo.height = mRenderHeight; - framebufferInfo.layers = 1; - mFramebuffers[i] = mDevice.createFramebuffer(framebufferInfo, nullptr); - - if (i < mImageCount && mFramebuffersText.size()) { - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = &mSwapChainImageViews[i]; - framebufferInfo.renderPass = mRenderPassText; - framebufferInfo.width = mScreenWidth; - framebufferInfo.height = mScreenHeight; - mFramebuffersText[i] = mDevice.createFramebuffer(framebufferInfo, nullptr); - } - - if (i >= mImageCount && mFramebuffersTexture.size()) { - framebufferInfo.attachmentCount = 1; - framebufferInfo.pAttachments = mRenderTargetView[i - mImageCount]; - framebufferInfo.renderPass = mRenderPassTexture; - framebufferInfo.width = mRenderWidth; - framebufferInfo.height = mRenderHeight; - mFramebuffersTexture[i - mImageCount] = mDevice.createFramebuffer(framebufferInfo, nullptr); } } @@ -1065,17 +936,11 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo void GPUDisplayBackendVulkan::clearOffscreenBuffers() { - clearVector(mFramebuffers, [&](auto& x) { mDevice.destroyFramebuffer(x, nullptr); }); clearVector(mMSAAImages, [&](auto& x) { clearImage(x); }); clearVector(mDownsampleImages, [&](auto& x) { clearImage(x); }); clearVector(mZImages, [&](auto& x) { clearImage(x); }); clearVector(mMixImages, [&](auto& x) { clearImage(x); }); - clearVector(mFramebuffersText, [&](auto& x) { mDevice.destroyFramebuffer(x, nullptr); }); - clearVector(mFramebuffersTexture, [&](auto& x) { mDevice.destroyFramebuffer(x, nullptr); }); - mDevice.destroyRenderPass(mRenderPass, nullptr); - mDevice.destroyRenderPass(mRenderPassText, nullptr); if (mMixingSupported) { - mDevice.destroyRenderPass(mRenderPassTexture, nullptr); clearBuffer(mMixingTextureVertexArray); } } @@ -1213,8 +1078,8 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.pDynamicState = &dynamicState; // pipelineInfo.layout // below - // pipelineInfo.renderPass // below - pipelineInfo.subpass = 0; + vk::PipelineRenderingCreateInfo renderingInfo{0, 1, &mSurfaceFormat.format}; + pipelineInfo.pNext = &renderingInfo; pipelineInfo.pStages = shaderStages; pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional @@ -1229,7 +1094,7 @@ void GPUDisplayBackendVulkan::createPipeline() vertShaderStageInfo.module = mShaders["vertexTexture"]; fragShaderStageInfo.module = mShaders["fragmentTexture"]; pipelineInfo.layout = mPipelineLayoutTexture; - pipelineInfo.renderPass = mRenderPassTexture; + renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; pipelineInfo.pDepthStencilState = nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; @@ -1242,7 +1107,7 @@ void GPUDisplayBackendVulkan::createPipeline() vertShaderStageInfo.module = mShaders["vertexTexture"]; fragShaderStageInfo.module = mShaders["fragmentText"]; pipelineInfo.layout = mPipelineLayoutTexture; - pipelineInfo.renderPass = mRenderPassText; + renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; pipelineInfo.pDepthStencilState = nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; @@ -1255,7 +1120,7 @@ void GPUDisplayBackendVulkan::createPipeline() vertShaderStageInfo.module = mShaders[types[i] == vk::PrimitiveTopology::ePointList ? "vertexPoint" : "vertex"]; fragShaderStageInfo.module = mShaders["fragment"]; pipelineInfo.layout = mPipelineLayout; - pipelineInfo.renderPass = mRenderPass; + renderingInfo.depthAttachmentFormat = mZActive ? vk::Format::eD32Sfloat : vk::Format::eUndefined; pipelineInfo.pDepthStencilState = mZActive ? &depthStencil : nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = mMSAASampleCount; @@ -1275,18 +1140,32 @@ void GPUDisplayBackendVulkan::startFillCommandBuffer(vk::CommandBuffer& commandB beginInfo.flags = {}; commandBuffer.begin(beginInfo); - vk::ClearValue clearValues[2]; - clearValues[0].color = mDisplay->cfgL().invertColors ? vk::ClearColorValue{std::array{1.0f, 1.0f, 1.0f, 1.0f}} : vk::ClearColorValue{std::array{0.0f, 0.0f, 0.0f, 1.0f}}; - clearValues[1].depthStencil = vk::ClearDepthStencilValue{{1.0f, 0}}; + const uint32_t idx = toMixBuffer ? imageIndex + mImageCount : imageIndex; + const bool msaa = mMSAASampleCount != vk::SampleCountFlagBits::e1; + const vk::PipelineStageFlags2 prevStages = vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer | vk::PipelineStageFlagBits2::eFragmentShader; // Previous use of the render target (rendering, blit, mix texture), and the wait stage of the acquire semaphore + cmdImageMemoryBarrier(commandBuffer, mRenderTargetImage[idx], prevStages, vk::AccessFlagBits2::eColorAttachmentWrite | vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal); + if (msaa) { + cmdImageMemoryBarrier(commandBuffer, mMSAAImages[idx].image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal); + } + if (mZActive) { + const vk::PipelineStageFlags2 depthStages = vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests; + cmdImageMemoryBarrier(commandBuffer, mZImages[idx].image, depthStages, vk::AccessFlagBits2::eDepthStencilAttachmentWrite, depthStages, vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal, vk::ImageAspectFlagBits::eDepth); + } - vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.renderPass = mRenderPass; - renderPassInfo.framebuffer = toMixBuffer ? mFramebuffers[imageIndex + mImageCount] : mFramebuffers[imageIndex]; - renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderPassInfo.renderArea.extent = vk::Extent2D{mRenderWidth, mRenderHeight}; - renderPassInfo.clearValueCount = mZActive ? 2 : 1; - renderPassInfo.pClearValues = clearValues; - commandBuffer.beginRenderPass(&renderPassInfo, vk::SubpassContents::eInline); + vk::RenderingAttachmentInfo colorAttachment{msaa ? mMSAAImages[idx].view : *mRenderTargetView[idx], vk::ImageLayout::eColorAttachmentOptimal}; + colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; + colorAttachment.storeOp = msaa ? vk::AttachmentStoreOp::eDontCare : vk::AttachmentStoreOp::eStore; + colorAttachment.clearValue.color = mDisplay->cfgL().invertColors ? vk::ClearColorValue{std::array{1.0f, 1.0f, 1.0f, 1.0f}} : vk::ClearColorValue{std::array{0.0f, 0.0f, 0.0f, 1.0f}}; + if (msaa) { + colorAttachment.resolveMode = vk::ResolveModeFlagBits::eAverage; + colorAttachment.resolveImageView = *mRenderTargetView[idx]; + colorAttachment.resolveImageLayout = vk::ImageLayout::eColorAttachmentOptimal; + } + vk::RenderingAttachmentInfo depthAttachment{mZActive ? mZImages[idx].view : vk::ImageView{}, vk::ImageLayout::eDepthStencilAttachmentOptimal}; + depthAttachment.loadOp = vk::AttachmentLoadOp::eClear; + depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare; + depthAttachment.clearValue.depthStencil = vk::ClearDepthStencilValue{1.0f, 0}; + cmdBeginRendering(commandBuffer, mRenderWidth, mRenderHeight, colorAttachment, mZActive ? &depthAttachment : nullptr); vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mVBO.buffer, offsets); @@ -1295,7 +1174,7 @@ void GPUDisplayBackendVulkan::startFillCommandBuffer(vk::CommandBuffer& commandB void GPUDisplayBackendVulkan::endFillCommandBuffer(vk::CommandBuffer& commandBuffer) { - commandBuffer.endRenderPass(); + commandBuffer.endRendering(); commandBuffer.end(); } @@ -1418,7 +1297,7 @@ void GPUDisplayBackendVulkan::writeToImage(VulkanImage& image, const void* srcDa auto tmp = createBuffer(srcSize, srcData, vk::BufferUsageFlagBits::eTransferSrc, 0); vk::CommandBuffer commandBuffer = getSingleTimeCommandBuffer(); - cmdImageMemoryBarrier(commandBuffer, image.image, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, image.image, vk::PipelineStageFlagBits2::eNone, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); vk::BufferImageCopy region{}; region.bufferOffset = 0; region.bufferRowLength = 0; @@ -1430,7 +1309,7 @@ void GPUDisplayBackendVulkan::writeToImage(VulkanImage& image, const void* srcDa region.imageOffset = vk::Offset3D{0, 0, 0}; region.imageExtent = vk::Extent3D{image.sizex, image.sizey, 1}; commandBuffer.copyBufferToImage(tmp.buffer, image.image, vk::ImageLayout::eTransferDstOptimal, 1, ®ion); - cmdImageMemoryBarrier(commandBuffer, image.image, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader); + cmdImageMemoryBarrier(commandBuffer, image.image, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eFragmentShader, vk::AccessFlagBits2::eShaderSampledRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); submitSingleTimeCommandBuffer(commandBuffer); clearBuffer(tmp); @@ -1617,38 +1496,31 @@ void GPUDisplayBackendVulkan::finishFrame(bool doScreenshot, bool toMixBuffer, f mFrameCommandBuffers.emplace_back(mCommandBuffersDownsample[mCurrentImageIndex]); } - vk::PipelineStageFlags waitStage = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer; - vk::SubmitInfo submitInfo{}; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &mImageAvailableSemaphore[mCurrentFrame]; - submitInfo.pWaitDstStageMask = &waitStage; + vk::SemaphoreSubmitInfo waitInfo{mImageAvailableSemaphore[mCurrentFrame], 0, vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer}; + vk::SubmitInfo2 submitInfo{{}, 1, &waitInfo}; if (doScreenshot) { - submitInfo.commandBufferCount = mFrameCommandBuffers.size(); - submitInfo.pCommandBuffers = mFrameCommandBuffers.data(); + submitInfo.commandBufferInfoCount = mFrameCommandBuffers.size(); + submitInfo.pCommandBufferInfos = mFrameCommandBuffers.data(); queueSubmit(nullptr, 1, &submitInfo, VkFence(VK_NULL_HANDLE)); mDevice.waitIdle(); if (mDisplay->cfgR().screenshotScaleFactor != 1) { - readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); + readImageToPixels(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eTransferSrcOptimal, mScreenshotPixels); } else { - readImageToPixels(mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::ePresentSrcKHR, mScreenshotPixels); + readImageToPixels(mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::eColorAttachmentOptimal, mScreenshotPixels); } mFrameCommandBuffers.clear(); - submitInfo.waitSemaphoreCount = 0; + submitInfo.waitSemaphoreInfoCount = 0; } if (mHasDrawnText) { mFrameCommandBuffers.emplace_back(mCommandBuffersText[mCurrentImageIndex]); } - submitInfo.commandBufferCount = mFrameCommandBuffers.size(); - submitInfo.pCommandBuffers = mFrameCommandBuffers.data(); - const vk::Semaphore signalSemaphores[2] = {mPresentSemaphore[mCurrentImageIndex], mTimelineSemaphore}; - uint64_t signalValues[2] = {0, 0}; // First value ignored for the binary present semaphore - vk::TimelineSemaphoreSubmitInfo timelineInfo{}; - timelineInfo.signalSemaphoreValueCount = 2; - timelineInfo.pSignalSemaphoreValues = signalValues; - submitInfo.pNext = &timelineInfo; - submitInfo.signalSemaphoreCount = 2; - submitInfo.pSignalSemaphores = signalSemaphores; - mFrameTimelineValue[mCurrentFrame] = mImageTimelineValue[mCurrentImageIndex] = queueSubmit(&signalValues[1], 1, &submitInfo, VkFence(VK_NULL_HANDLE)); + mFrameCommandBuffers.emplace_back(mCommandBuffersPresent[mCurrentImageIndex]); + submitInfo.commandBufferInfoCount = mFrameCommandBuffers.size(); + submitInfo.pCommandBufferInfos = mFrameCommandBuffers.data(); + vk::SemaphoreSubmitInfo signalInfos[2] = {{mPresentSemaphore[mCurrentImageIndex], 0, vk::PipelineStageFlagBits2::eAllCommands}, {mTimelineSemaphore, 0, vk::PipelineStageFlagBits2::eAllCommands}}; + submitInfo.signalSemaphoreInfoCount = 2; + submitInfo.pSignalSemaphoreInfos = signalInfos; + mFrameTimelineValue[mCurrentFrame] = mImageTimelineValue[mCurrentImageIndex] = queueSubmit(&signalInfos[1].value, 1, &submitInfo, VkFence(VK_NULL_HANDLE)); vk::PresentInfoKHR presentInfo{}; presentInfo.waitSemaphoreCount = 1; @@ -1673,8 +1545,8 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; commandBuffer.begin(beginInfo); - cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); - cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); // Src stage chains with the acquire semaphore wait + cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eTransferSrcOptimal); vk::Offset3D blitSizeSrc; blitSizeSrc.x = mRenderWidth; @@ -1693,8 +1565,7 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command imageBlitRegion.dstOffsets[1] = blitSizeDst; commandBuffer.blitImage(mDownsampleImages[mCurrentImageIndex].image, vk::ImageLayout::eTransferSrcOptimal, mSwapChainImages[mCurrentImageIndex], vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); - cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput); // Next: text pass, or present (ordered by the present semaphore) - cmdImageMemoryBarrier(commandBuffer, mDownsampleImages[mCurrentImageIndex].image, {}, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput); // Next frame renders into it again + cmdImageMemoryBarrier(commandBuffer, mSwapChainImages[mCurrentImageIndex], vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eColorAttachmentOptimal); // Next: text pass and / or present transition commandBuffer.end(); } @@ -1720,13 +1591,7 @@ void GPUDisplayBackendVulkan::finishText() beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; mCommandBuffersText[mCurrentImageIndex].begin(beginInfo); - vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.renderPass = mRenderPassText; - renderPassInfo.framebuffer = mFramebuffersText.size() ? mFramebuffersText[mCurrentImageIndex] : mFramebuffers[mCurrentImageIndex]; - renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderPassInfo.renderArea.extent = vk::Extent2D{mScreenWidth, mScreenHeight}; - renderPassInfo.clearValueCount = 0; - mCommandBuffersText[mCurrentImageIndex].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + cmdBeginRenderingLoad(mCommandBuffersText[mCurrentImageIndex], mSwapChainImages[mCurrentImageIndex], mSwapChainImageViews[mCurrentImageIndex], mScreenWidth, mScreenHeight); if (mFontVertexBuffer[mCurrentImageIndex].size) { clearBuffer(mFontVertexBuffer[mCurrentImageIndex]); @@ -1745,7 +1610,7 @@ void GPUDisplayBackendVulkan::finishText() mFontVertexBufferHost.clear(); - mCommandBuffersText[mCurrentImageIndex].endRenderPass(); + mCommandBuffersText[mCurrentImageIndex].endRendering(); mCommandBuffersText[mCurrentImageIndex].end(); } @@ -1760,16 +1625,8 @@ void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float m commandBuffer.begin(beginInfo); vk::Image& image = mDownsampleFSAA ? mDownsampleImages[mCurrentImageIndex + mImageCount].image : mMixImages[mCurrentImageIndex].image; - vk::ImageLayout srcLayout = mDownsampleFSAA ? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::ePresentSrcKHR; - cmdImageMemoryBarrier(commandBuffer, image, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead, srcLayout, vk::ImageLayout::eShaderReadOnlyOptimal, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader); - - vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.renderPass = mRenderPassTexture; - renderPassInfo.framebuffer = mFramebuffersTexture.size() ? mFramebuffersTexture[mCurrentImageIndex] : mFramebuffers[mCurrentImageIndex]; - renderPassInfo.renderArea.offset = vk::Offset2D{0, 0}; - renderPassInfo.renderArea.extent = vk::Extent2D{mRenderWidth, mRenderHeight}; - renderPassInfo.clearValueCount = 0; - commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline); + cmdImageMemoryBarrier(commandBuffer, image, vk::PipelineStageFlagBits2::eColorAttachmentOutput, vk::AccessFlagBits2::eColorAttachmentWrite, vk::PipelineStageFlagBits2::eFragmentShader, vk::AccessFlagBits2::eShaderSampledRead, vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + cmdBeginRenderingLoad(commandBuffer, mRenderTargetImage[mCurrentImageIndex], *mRenderTargetView[mCurrentImageIndex], mRenderWidth, mRenderHeight); commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[4]); commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[2][mCurrentImageIndex], 0, nullptr); @@ -1779,7 +1636,7 @@ void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float m commandBuffer.pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(mixSlaveImage), &mixSlaveImage); commandBuffer.draw(6, 1, 0, 0); - commandBuffer.endRenderPass(); + commandBuffer.endRendering(); commandBuffer.end(); } @@ -1980,10 +1837,10 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout vk::DeviceMemory dstImageMemory; VulkanBuffer readBuffer = createBuffer(width * height * bytesPerPixel, nullptr, vk::BufferUsageFlagBits::eTransferDst, 0); vk::CommandBuffer cmdBuffer = getSingleTimeCommandBuffer(); - cmdImageMemoryBarrier(cmdBuffer, image, vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferRead, layout, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(cmdBuffer, image, vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eColorAttachmentWrite | vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead, layout, vk::ImageLayout::eTransferSrcOptimal); if (mDisplay->cfgR().screenshotScaleFactor != 1) { createImageI(mDevice, mPhysicalDevice, dstImage, dstImageMemory, width, height, mSurfaceFormat.format, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal, vk::ImageTiling::eOptimal); - cmdImageMemoryBarrier(cmdBuffer, dstImage, {}, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::PipelineStageFlagBits2::eNone, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); vk::Offset3D blitSizeSrc = {(int32_t)mRenderWidth, (int32_t)mRenderHeight, 1}; vk::Offset3D blitSizeDst = {(int32_t)width, (int32_t)height, 1}; vk::ImageBlit imageBlitRegion{}; @@ -1995,7 +1852,7 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout imageBlitRegion.dstOffsets[1] = blitSizeDst; cmdBuffer.blitImage(image, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, 1, &imageBlitRegion, vk::Filter::eLinear); src = dstImage; - cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + cmdImageMemoryBarrier(cmdBuffer, dstImage, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eTransferSrcOptimal); } else { src = image; } @@ -2006,9 +1863,9 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout copyRegion.imageExtent = vk::Extent3D{width, height, 1}; cmdBuffer.copyImageToBuffer(src, vk::ImageLayout::eTransferSrcOptimal, readBuffer.buffer, 1, ©Region); - vk::BufferMemoryBarrier hostBarrier{vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eHostRead, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, readBuffer.buffer, 0, VK_WHOLE_SIZE}; - cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &hostBarrier, 0, nullptr); - cmdImageMemoryBarrier(cmdBuffer, image, vk::AccessFlagBits::eTransferRead, vk::AccessFlagBits::eMemoryRead, vk::ImageLayout::eTransferSrcOptimal, layout, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer); + vk::BufferMemoryBarrier2 hostBarrier{vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite, vk::PipelineStageFlagBits2::eHost, vk::AccessFlagBits2::eHostRead, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, readBuffer.buffer, 0, VK_WHOLE_SIZE}; + cmdBuffer.pipelineBarrier2(vk::DependencyInfo{{}, 0, nullptr, 1, &hostBarrier, 0, nullptr}); + cmdImageMemoryBarrier(cmdBuffer, image, vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eNone, vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eNone, vk::ImageLayout::eTransferSrcOptimal, layout); // Back to the layout the following passes expect submitSingleTimeCommandBuffer(cmdBuffer); const bool swapRB = format == vk::Format::eR8G8B8A8Unorm || format == vk::Format::eR8G8B8A8Srgb; // Raw copy in swapchain format, the BMP needs BGRA diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index cd6e23043e8da..f5f4723890fce 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -151,6 +151,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mSwapChainImages; std::vector mSwapChainImageViews; std::vector mRenderTargetView; + std::vector mRenderTargetImage; std::vector mMSAAImages; std::vector mDownsampleImages; std::vector mZImages; @@ -158,13 +159,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::unordered_map mShaders; vk::PipelineLayout mPipelineLayout; vk::PipelineLayout mPipelineLayoutTexture; - vk::RenderPass mRenderPass; - vk::RenderPass mRenderPassText; - vk::RenderPass mRenderPassTexture; std::vector mPipelines; - std::vector mFramebuffers; - std::vector mFramebuffersText; - std::vector mFramebuffersTexture; vk::CommandPool mCommandPool; bool mCommandInfrastructureCreated = false; @@ -180,7 +175,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mCommandBuffersText; std::vector mCommandBuffersTexture; std::vector mCommandBuffersMix; - std::vector mFrameCommandBuffers; // All command buffers of the current frame, submitted as one batch, ordered by render pass dependencies and barriers + std::vector mCommandBuffersPresent; // Pre-recorded per swapchain image: transition to ePresentSrcKHR, last in every frame + std::vector mFrameCommandBuffers; // All command buffers of the current frame, submitted as one batch, ordered by barriers std::vector mCommandBufferUpToDate; std::vector mImageAvailableSemaphore; std::vector mPresentSemaphore; From cebbdcd3374f5b54d16537c3da41d30ac759560f Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 19:16:16 +0200 Subject: [PATCH 41/48] GPU Vulkan Display: minor optimization: reuse resources from old swapChain when creating new one --- .../display/backend/GPUDisplayBackendVulkan.cxx | 17 +++++++++++------ .../display/backend/GPUDisplayBackendVulkan.h | 8 ++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index fb9f6fab83924..451a214dc286a 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -769,7 +769,7 @@ void GPUDisplayBackendVulkan::clearTextureSampler() // ---------------------------- VULKAN SWAPCHAIN MANAGEMENT ---------------------------- -void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing) +void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing, vk::SwapchainKHR oldSwapChain) { mDownsampleFactor = getDownsampleFactor(forScreenshot); mDownsampleFSAA = mDownsampleFactor != 1; @@ -805,7 +805,7 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing swapCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; swapCreateInfo.presentMode = mPresentMode; swapCreateInfo.clipped = true; - swapCreateInfo.oldSwapchain = VkSwapchainKHR(VK_NULL_HANDLE); + swapCreateInfo.oldSwapchain = oldSwapChain; if (mSwapchainImageReadable) { swapCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferSrc; } @@ -813,6 +813,9 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing swapCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferDst; } mSwapChain = mDevice.createSwapchainKHR(swapCreateInfo, nullptr); + if (oldSwapChain) { + mDevice.destroySwapchainKHR(oldSwapChain, nullptr); + } mSwapChainImages = mDevice.getSwapchainImagesKHR(mSwapChain); uint32_t oldFramesInFlight = mFramesInFlight, oldImageCount = mImageCount; @@ -841,10 +844,12 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing } } -void GPUDisplayBackendVulkan::clearSwapChain() +void GPUDisplayBackendVulkan::clearSwapChain(bool keepSwapChain) { clearVector(mSwapChainImageViews, [&](auto& x) { mDevice.destroyImageView(x, nullptr); }); - mDevice.destroySwapchainKHR(mSwapChain, nullptr); + if (!keepSwapChain) { + mDevice.destroySwapchainKHR(mSwapChain, nullptr); + } } void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixing) @@ -856,8 +861,8 @@ void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixi if (needUpdateOffscreenBuffers) { clearOffscreenBuffers(); if (needUpdateSwapChain) { - clearSwapChain(); - createSwapChain(forScreenshot, forMixing); + clearSwapChain(true); + createSwapChain(forScreenshot, forMixing, mSwapChain); } createOffscreenBuffers(forScreenshot, forMixing); } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index f5f4723890fce..75423b91c9262 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -99,7 +99,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void startFillCommandBuffer(vk::CommandBuffer& commandBuffer, uint32_t imageIndex, bool toMixBuffer = false); void endFillCommandBuffer(vk::CommandBuffer& commandBuffer); vk::CommandBuffer getSingleTimeCommandBuffer(); - void waitTimeline(uint64_t value); // Wait until the timeline semaphore reached value, 0 = nothing to wait for + void waitTimeline(uint64_t value); template uint64_t queueSubmit(uint64_t* timelineSignalValue, Args&&... args); // Submit under mQueueMutex; if timelineSignalValue (the entry in pSignalSemaphoreValues) is set, write the next timeline value there first. Returns the current timeline value void submitSingleTimeCommandBuffer(vk::CommandBuffer commandBuffer); @@ -114,7 +114,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void createCommandBuffers(); void createSemaphores(); void createUniformLayoutsAndBuffers(); - void createSwapChain(bool forScreenshot = false, bool forMixing = false); + void createSwapChain(bool forScreenshot = false, bool forMixing = false, vk::SwapchainKHR oldSwapChain = {}); void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); void createPipeline(); void clearDevice(); @@ -124,7 +124,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void clearSemaphores(); void clearUniformLayoutsAndBuffers(); void clearOffscreenBuffers(); - void clearSwapChain(); + void clearSwapChain(bool keepSwapChain = false); void clearPipeline(); void recreateRendering(bool forScreenshot = false, bool forMixing = false); void needRecordCommandBuffers(); @@ -175,7 +175,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mCommandBuffersText; std::vector mCommandBuffersTexture; std::vector mCommandBuffersMix; - std::vector mCommandBuffersPresent; // Pre-recorded per swapchain image: transition to ePresentSrcKHR, last in every frame + std::vector mCommandBuffersPresent; std::vector mFrameCommandBuffers; // All command buffers of the current frame, submitted as one batch, ordered by barriers std::vector mCommandBufferUpToDate; std::vector mImageAvailableSemaphore; From 13bff4a5db5ef76bfc4609ba93f66d62d5c0b9d7 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 19:40:03 +0200 Subject: [PATCH 42/48] GPU Vulkan Backend: Write UBOs and map/unmap/recreate buffers only on demand --- .../backend/GPUDisplayBackendVulkan.cxx | 116 +++++++++--------- .../display/backend/GPUDisplayBackendVulkan.h | 6 +- 2 files changed, 66 insertions(+), 56 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 451a214dc286a..809fb146b9827 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -639,16 +639,14 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() { for (int32_t j = 0; j < 3; j++) { mUniformBuffersMat[j].resize(mImageCount); - mUniformBuffersCol[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); - mUniformBuffersCol[j][i] = createBuffer(sizeof(float) * 4, nullptr, vk::BufferUsageFlagBits::eUniformBuffer, mDisplay->cfg().vulkan.uniformBuffersInDeviceMemory ? 2 : 0); } } std::array poolSizes{}; poolSizes[0].type = vk::DescriptorType::eUniformBuffer; - poolSizes[0].descriptorCount = (uint32_t)mImageCount * (2 * 3); + poolSizes[0].descriptorCount = (uint32_t)mImageCount * 3; poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; poolSizes[1].descriptorCount = (uint32_t)mImageCount * 2; vk::DescriptorPoolCreateInfo poolInfo{}; @@ -662,21 +660,18 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() uboLayoutBindingMat.descriptorType = vk::DescriptorType::eUniformBuffer; uboLayoutBindingMat.descriptorCount = 1; uboLayoutBindingMat.stageFlags = vk::ShaderStageFlagBits::eVertex; - vk::DescriptorSetLayoutBinding uboLayoutBindingCol = uboLayoutBindingMat; - uboLayoutBindingCol.binding = 1; - uboLayoutBindingCol.stageFlags = vk::ShaderStageFlagBits::eFragment; vk::DescriptorSetLayoutBinding samplerLayoutBinding{}; samplerLayoutBinding.binding = 2; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = vk::DescriptorType::eCombinedImageSampler; samplerLayoutBinding.stageFlags = vk::ShaderStageFlagBits::eFragment; - vk::DescriptorSetLayoutBinding bindings[3] = {uboLayoutBindingMat, uboLayoutBindingCol, samplerLayoutBinding}; + vk::DescriptorSetLayoutBinding bindings[2] = {uboLayoutBindingMat, samplerLayoutBinding}; vk::DescriptorSetLayoutCreateInfo layoutInfo{}; - layoutInfo.bindingCount = 2; + layoutInfo.bindingCount = 1; layoutInfo.pBindings = bindings; mUniformDescriptor = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); - layoutInfo.bindingCount = 3; + layoutInfo.bindingCount = 2; mUniformDescriptorTexture = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); vk::DescriptorSetAllocateInfo allocInfo{}; @@ -687,27 +682,25 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() allocInfo.pSetLayouts = layouts.data(); mDescriptorSets[j] = mDevice.allocateDescriptorSets(allocInfo); - for (int32_t k = 0; k < 2; k++) { - auto& mUniformBuffers = k ? mUniformBuffersCol[j] : mUniformBuffersMat[j]; - for (uint32_t i = 0; i < mImageCount; i++) { - vk::DescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = mUniformBuffers[i].buffer; - bufferInfo.offset = 0; - bufferInfo.range = mUniformBuffers[i].size; - - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[j][i]; - descriptorWrite.dstBinding = k; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eUniformBuffer; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pBufferInfo = &bufferInfo; - descriptorWrite.pImageInfo = nullptr; - descriptorWrite.pTexelBufferView = nullptr; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); - } + for (uint32_t i = 0; i < mImageCount; i++) { + vk::DescriptorBufferInfo bufferInfo{}; + bufferInfo.buffer = mUniformBuffersMat[j][i].buffer; + bufferInfo.offset = 0; + bufferInfo.range = mUniformBuffersMat[j][i].size; + + vk::WriteDescriptorSet descriptorWrite{}; + descriptorWrite.dstSet = mDescriptorSets[j][i]; + descriptorWrite.dstBinding = 0; + descriptorWrite.dstArrayElement = 0; + descriptorWrite.descriptorType = vk::DescriptorType::eUniformBuffer; + descriptorWrite.descriptorCount = 1; + descriptorWrite.pBufferInfo = &bufferInfo; + descriptorWrite.pImageInfo = nullptr; + descriptorWrite.pTexelBufferView = nullptr; + mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); } } + mUpdateUBOdrawMatrix = mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; if (mFontImage.sizex && mFontImage.sizey) { updateFontTextureDescriptor(); @@ -721,7 +714,6 @@ void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffers() mDevice.destroyDescriptorPool(mDescriptorPool, nullptr); for (int32_t j = 0; j < 3; j++) { clearVector(mUniformBuffersMat[j], [&](auto& x) { clearBuffer(x); }); - clearVector(mUniformBuffersCol[j], [&](auto& x) { clearBuffer(x); }); } } @@ -821,6 +813,9 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing uint32_t oldFramesInFlight = mFramesInFlight, oldImageCount = mImageCount; mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; mImageCount = mSwapChainImages.size(); + if (mImageCount > sizeof(mUpdateUBOdrawMatrix) * 8) { + throw std::runtime_error("Too many swapchain images"); + } if (mFramesInFlight != oldFramesInFlight || mImageCount != oldImageCount || !mCommandInfrastructureCreated) { if (mCommandInfrastructureCreated) { @@ -868,6 +863,7 @@ void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixi } createPipeline(); needRecordCommandBuffers(); + mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed } // ---------------------------- VULKAN OFFSCREEN BUFFERS ---------------------------- @@ -945,6 +941,7 @@ void GPUDisplayBackendVulkan::clearOffscreenBuffers() clearVector(mDownsampleImages, [&](auto& x) { clearImage(x); }); clearVector(mZImages, [&](auto& x) { clearImage(x); }); clearVector(mMixImages, [&](auto& x) { clearImage(x); }); + clearVector(mFontVertexBuffer, [&](auto& x) { if (x.size) { clearBuffer(x); } }); if (mMixingSupported) { clearBuffer(mMixingTextureVertexArray); } @@ -1214,11 +1211,8 @@ void GPUDisplayBackendVulkan::clearShaders() void GPUDisplayBackendVulkan::writeToBuffer(VulkanBuffer& buffer, size_t size, const void* srcData) { - if (buffer.deviceMemory != 1) { - void* dstData; - CHKERR(mDevice.mapMemory(buffer.memory, 0, buffer.size, {}, &dstData)); - memcpy(dstData, srcData, size); - mDevice.unmapMemory(buffer.memory); + if (buffer.mapped) { + memcpy(buffer.mapped, srcData, size); } else { auto tmp = createBuffer(size, srcData, vk::BufferUsageFlagBits::eTransferSrc, 0); @@ -1263,6 +1257,9 @@ GPUDisplayBackendVulkan::VulkanBuffer GPUDisplayBackendVulkan::createBuffer(size buffer.size = size; buffer.deviceMemory = deviceMemory; + if (deviceMemory != 1) { + CHKERR(mDevice.mapMemory(buffer.memory, 0, VK_WHOLE_SIZE, {}, &buffer.mapped)); + } if (srcData != nullptr) { writeToBuffer(buffer, size, srcData); @@ -1274,7 +1271,7 @@ GPUDisplayBackendVulkan::VulkanBuffer GPUDisplayBackendVulkan::createBuffer(size void GPUDisplayBackendVulkan::clearBuffer(VulkanBuffer& buffer) { mDevice.destroyBuffer(buffer.buffer, nullptr); - mDevice.freeMemory(buffer.memory, nullptr); + mDevice.freeMemory(buffer.memory, nullptr); // Implicitly unmaps } void GPUDisplayBackendVulkan::clearVertexBuffers() @@ -1287,12 +1284,6 @@ void GPUDisplayBackendVulkan::clearVertexBuffers() clearBuffer(mIndirectCommandBuffer); mIndirectCommandBuffer.size = 0; } - for (auto& buf : mFontVertexBuffer) { - if (buf.size) { - clearBuffer(buf); - } - buf.size = 0; - } } // ---------------------------- VULKAN TEXTURES ---------------------------- @@ -1468,7 +1459,15 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& mHasDrawnText = false; mFrameCommandBuffers.clear(); const hmm_mat4 modelViewProj = proj * view; - writeToBuffer(mUniformBuffersMat[0][mCurrentImageIndex], sizeof(modelViewProj), &modelViewProj); + static_assert(sizeof(modelViewProj) == sizeof(mLastMVP)); + if (memcmp(&modelViewProj, mLastMVP, sizeof(mLastMVP))) { + memcpy(mLastMVP, &modelViewProj, sizeof(mLastMVP)); + mUpdateUBOdrawMatrix = -1; + } + if (mUpdateUBOdrawMatrix & (1u << mCurrentImageIndex)) { + mUpdateUBOdrawMatrix &= ~(1u << mCurrentImageIndex); + writeToBuffer(mUniformBuffersMat[0][mCurrentImageIndex], sizeof(mLastMVP), mLastMVP); + } } mCurrentCommandBuffer = toMixBuffer ? mCommandBuffersMix[mCurrentImageIndex] : mCommandBuffers[mCurrentImageIndex]; @@ -1577,9 +1576,11 @@ void GPUDisplayBackendVulkan::downsampleToFramebuffer(vk::CommandBuffer& command void GPUDisplayBackendVulkan::prepareText() { - hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); - writeToBuffer(mUniformBuffersMat[1][mCurrentImageIndex], sizeof(proj), &proj); - + if (mUpdateUBOtextMatrix & (1u << mCurrentImageIndex)) { + mUpdateUBOtextMatrix &= ~(1u << mCurrentImageIndex); + hmm_mat4 proj = HMM_Orthographic(0.f, mScreenWidth, 0.f, mScreenHeight, -1, 1); + writeToBuffer(mUniformBuffersMat[1][mCurrentImageIndex], sizeof(proj), &proj); + } mFontVertexBufferHost.clear(); mTextDrawCommands.clear(); } @@ -1598,15 +1599,20 @@ void GPUDisplayBackendVulkan::finishText() cmdBeginRenderingLoad(mCommandBuffersText[mCurrentImageIndex], mSwapChainImages[mCurrentImageIndex], mSwapChainImageViews[mCurrentImageIndex], mScreenWidth, mScreenHeight); - if (mFontVertexBuffer[mCurrentImageIndex].size) { - clearBuffer(mFontVertexBuffer[mCurrentImageIndex]); + VulkanBuffer& fontBuffer = mFontVertexBuffer[mCurrentImageIndex]; + const size_t fontBufferSize = mFontVertexBufferHost.size() * sizeof(mFontVertexBufferHost[0]); + if (fontBuffer.size < fontBufferSize) { // Persistent per image, only grows + if (fontBuffer.size) { + clearBuffer(fontBuffer); + } + fontBuffer = createBuffer(std::max(fontBufferSize, 2 * fontBuffer.size), nullptr, vk::BufferUsageFlagBits::eVertexBuffer, 0); } - mFontVertexBuffer[mCurrentImageIndex] = createBuffer(mFontVertexBufferHost.size() * sizeof(float), mFontVertexBufferHost.data(), vk::BufferUsageFlagBits::eVertexBuffer, 0); + writeToBuffer(fontBuffer, fontBufferSize, mFontVertexBufferHost.data()); mCommandBuffersText[mCurrentImageIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[3]); mCommandBuffersText[mCurrentImageIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[1][mCurrentImageIndex], 0, nullptr); vk::DeviceSize offsets[] = {0}; - mCommandBuffersText[mCurrentImageIndex].bindVertexBuffers(0, 1, &mFontVertexBuffer[mCurrentImageIndex].buffer, offsets); + mCommandBuffersText[mCurrentImageIndex].bindVertexBuffers(0, 1, &fontBuffer.buffer, offsets); for (const auto& cmd : mTextDrawCommands) { mCommandBuffersText[mCurrentImageIndex].pushConstants(mPipelineLayoutTexture, vk::ShaderStageFlagBits::eFragment, 0, sizeof(cmd.color), cmd.color); @@ -1621,9 +1627,11 @@ void GPUDisplayBackendVulkan::finishText() void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float mixSlaveImage) { - hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1, 1); - writeToBuffer(mUniformBuffersMat[2][mCurrentImageIndex], sizeof(proj), &proj); - + if (mUpdateUBOmixMatrix & (1u << mCurrentImageIndex)) { + mUpdateUBOmixMatrix &= ~(1u << mCurrentImageIndex); + hmm_mat4 proj = HMM_Orthographic(0.f, mRenderWidth, 0.f, mRenderHeight, -1, 1); + writeToBuffer(mUniformBuffersMat[2][mCurrentImageIndex], sizeof(proj), &proj); + } commandBuffer.reset({}); vk::CommandBufferBeginInfo beginInfo{}; beginInfo.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit; @@ -1874,8 +1882,7 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout submitSingleTimeCommandBuffer(cmdBuffer); const bool swapRB = format == vk::Format::eR8G8B8A8Unorm || format == vk::Format::eR8G8B8A8Srgb; // Raw copy in swapchain format, the BMP needs BGRA - const char* data; - CHKERR(mDevice.mapMemory(readBuffer.memory, 0, VK_WHOLE_SIZE, {}, (void**)&data)); + const char* data = (const char*)readBuffer.mapped; for (uint32_t i = 0; i < height; i++) { // BMP rows are bottom-up char* dst = pixels.data() + i * width * bytesPerPixel; memcpy(dst, data + (height - i - 1) * width * bytesPerPixel, width * bytesPerPixel); @@ -1885,7 +1892,6 @@ void GPUDisplayBackendVulkan::readImageToPixels(vk::Image image, vk::ImageLayout } } } - mDevice.unmapMemory(readBuffer.memory); clearBuffer(readBuffer); if (mDisplay->cfgR().screenshotScaleFactor != 1) { mDevice.freeMemory(dstImageMemory, nullptr); diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 75423b91c9262..452a630e3546b 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -45,6 +45,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend vk::DeviceMemory memory; size_t size = 0; int32_t deviceMemory; + void* mapped = nullptr; // Persistent mapping of host-visible buffers (deviceMemory 0 / 2) }; struct VulkanImage { vk::Image image; @@ -186,7 +187,10 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mImageTimelineValue; // Per swapchain image: value of the last submit using its resources std::mutex mQueueMutex; // Queue submissions must be externally synchronized std::vector mUniformBuffersMat[3]; - std::vector mUniformBuffersCol[3]; + uint32_t mUpdateUBOdrawMatrix = -1; // Bitmask per swapchain image: UBO must be rewritten when the image is drawn next, set to -1 to update all + uint32_t mUpdateUBOtextMatrix = -1; + uint32_t mUpdateUBOmixMatrix = -1; + float mLastMVP[16] = {}; std::vector mDescriptorSets[3]; vk::DescriptorSetLayout mUniformDescriptor; vk::DescriptorSetLayout mUniformDescriptorTexture; From 27f4b267cdde2bfd4a2ed9b0d53155e3a8061b71 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 19:51:14 +0200 Subject: [PATCH 43/48] GPU Vulkan Display: Simplification: Use Push Descriptors instead of the Descriptor Pool --- .../backend/GPUDisplayBackendVulkan.cxx | 91 ++----------------- .../display/backend/GPUDisplayBackendVulkan.h | 5 +- 2 files changed, 11 insertions(+), 85 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 809fb146b9827..dae404785a408 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -498,6 +498,7 @@ void GPUDisplayBackendVulkan::createDevice() auto& reqFeatures10 = reqDeviceFeatures.get().features; reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = reqFeatures10.multiDrawIndirect = VK_TRUE; reqDeviceFeatures.get().timelineSemaphore = VK_TRUE; + reqDeviceFeatures.get().pushDescriptor = VK_TRUE; reqDeviceFeatures.get().dynamicRendering = reqDeviceFeatures.get().synchronization2 = VK_TRUE; reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; reqDeviceFeatures.get().presentId = VK_TRUE; @@ -644,17 +645,6 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() } } - std::array poolSizes{}; - poolSizes[0].type = vk::DescriptorType::eUniformBuffer; - poolSizes[0].descriptorCount = (uint32_t)mImageCount * 3; - poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler; - poolSizes[1].descriptorCount = (uint32_t)mImageCount * 2; - vk::DescriptorPoolCreateInfo poolInfo{}; - poolInfo.poolSizeCount = poolSizes.size(); - poolInfo.pPoolSizes = poolSizes.data(); - poolInfo.maxSets = (uint32_t)mImageCount * 3; - mDescriptorPool = mDevice.createDescriptorPool(poolInfo, nullptr); - vk::DescriptorSetLayoutBinding uboLayoutBindingMat{}; uboLayoutBindingMat.binding = 0; uboLayoutBindingMat.descriptorType = vk::DescriptorType::eUniformBuffer; @@ -668,69 +658,31 @@ void GPUDisplayBackendVulkan::createUniformLayoutsAndBuffers() vk::DescriptorSetLayoutBinding bindings[2] = {uboLayoutBindingMat, samplerLayoutBinding}; vk::DescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptor; layoutInfo.bindingCount = 1; layoutInfo.pBindings = bindings; mUniformDescriptor = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); layoutInfo.bindingCount = 2; mUniformDescriptorTexture = mDevice.createDescriptorSetLayout(layoutInfo, nullptr); - vk::DescriptorSetAllocateInfo allocInfo{}; - allocInfo.descriptorPool = mDescriptorPool; - allocInfo.descriptorSetCount = (uint32_t)mImageCount; - for (int32_t j = 0; j < 3; j++) { // 0 = Render, 1 = Text, 2 = Texture - std::vector layouts(mImageCount, j ? mUniformDescriptorTexture : mUniformDescriptor); - allocInfo.pSetLayouts = layouts.data(); - mDescriptorSets[j] = mDevice.allocateDescriptorSets(allocInfo); - - for (uint32_t i = 0; i < mImageCount; i++) { - vk::DescriptorBufferInfo bufferInfo{}; - bufferInfo.buffer = mUniformBuffersMat[j][i].buffer; - bufferInfo.offset = 0; - bufferInfo.range = mUniformBuffersMat[j][i].size; - - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[j][i]; - descriptorWrite.dstBinding = 0; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eUniformBuffer; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pBufferInfo = &bufferInfo; - descriptorWrite.pImageInfo = nullptr; - descriptorWrite.pTexelBufferView = nullptr; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); - } - } mUpdateUBOdrawMatrix = mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; - - if (mFontImage.sizex && mFontImage.sizey) { - updateFontTextureDescriptor(); - } } void GPUDisplayBackendVulkan::clearUniformLayoutsAndBuffers() { mDevice.destroyDescriptorSetLayout(mUniformDescriptor, nullptr); mDevice.destroyDescriptorSetLayout(mUniformDescriptorTexture, nullptr); - mDevice.destroyDescriptorPool(mDescriptorPool, nullptr); for (int32_t j = 0; j < 3; j++) { clearVector(mUniformBuffersMat[j], [&](auto& x) { clearBuffer(x); }); } } -void GPUDisplayBackendVulkan::setMixDescriptor(int32_t descriptorIndex, int32_t imageIndex) +void GPUDisplayBackendVulkan::cmdPushDescriptors(vk::CommandBuffer commandBuffer, vk::PipelineLayout layout, const VulkanBuffer& ubo, vk::ImageView textureView) { - vk::DescriptorImageInfo imageInfo{}; - imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - imageInfo.sampler = mTextureSampler; - imageInfo.imageView = *mRenderTargetView[imageIndex + mImageCount]; - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[2][descriptorIndex]; - descriptorWrite.dstBinding = 2; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eCombinedImageSampler; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pImageInfo = &imageInfo; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); + vk::DescriptorBufferInfo bufferInfo{ubo.buffer, 0, ubo.size}; + vk::DescriptorImageInfo imageInfo{mTextureSampler, textureView, vk::ImageLayout::eShaderReadOnlyOptimal}; + const vk::WriteDescriptorSet writes[2] = {{{}, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &bufferInfo}, {{}, 2, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo}}; + commandBuffer.pushDescriptorSet(vk::PipelineBindPoint::eGraphics, layout, 0, textureView ? 2 : 1, writes); } // ---------------------------- VULKAN TEXTURE SAMPLER ---------------------------- @@ -926,10 +878,6 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo {(float)mRenderWidth, 0, 1.0f, 0.0f}, {(float)mRenderWidth, (float)mRenderHeight, 1.0f, 1.0f}}; mMixingTextureVertexArray = createBuffer(sizeof(vertices), &vertices[0][0], vk::BufferUsageFlagBits::eVertexBuffer, 1); - - for (uint32_t i = 0; i < mImageCount; i++) { - setMixDescriptor(i, i); - } } mFontVertexBuffer.resize(mImageCount); @@ -1171,7 +1119,7 @@ void GPUDisplayBackendVulkan::startFillCommandBuffer(vk::CommandBuffer& commandB vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mVBO.buffer, offsets); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayout, 0, 1, &mDescriptorSets[0][mCurrentImageIndex], 0, nullptr); + cmdPushDescriptors(commandBuffer, mPipelineLayout, mUniformBuffersMat[0][imageIndex]); } void GPUDisplayBackendVulkan::endFillCommandBuffer(vk::CommandBuffer& commandBuffer) @@ -1610,7 +1558,7 @@ void GPUDisplayBackendVulkan::finishText() writeToBuffer(fontBuffer, fontBufferSize, mFontVertexBufferHost.data()); mCommandBuffersText[mCurrentImageIndex].bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[3]); - mCommandBuffersText[mCurrentImageIndex].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[1][mCurrentImageIndex], 0, nullptr); + cmdPushDescriptors(mCommandBuffersText[mCurrentImageIndex], mPipelineLayoutTexture, mUniformBuffersMat[1][mCurrentImageIndex], mFontImage.view); vk::DeviceSize offsets[] = {0}; mCommandBuffersText[mCurrentImageIndex].bindVertexBuffers(0, 1, &fontBuffer.buffer, offsets); @@ -1642,7 +1590,7 @@ void GPUDisplayBackendVulkan::mixImages(vk::CommandBuffer commandBuffer, float m cmdBeginRenderingLoad(commandBuffer, mRenderTargetImage[mCurrentImageIndex], *mRenderTargetView[mCurrentImageIndex], mRenderWidth, mRenderHeight); commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[4]); - commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mPipelineLayoutTexture, 0, 1, &mDescriptorSets[2][mCurrentImageIndex], 0, nullptr); + cmdPushDescriptors(commandBuffer, mPipelineLayoutTexture, mUniformBuffersMat[2][mCurrentImageIndex], *mRenderTargetView[mCurrentImageIndex + mImageCount]); vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mMixingTextureVertexArray.buffer, offsets); @@ -1753,7 +1701,6 @@ void GPUDisplayBackendVulkan::initializeTextDrawing() } mFontImage = createImage(sizex, sizey, bigImage.get(), sizex * sizey, vk::Format::eR8Unorm); - updateFontTextureDescriptor(); } void GPUDisplayBackendVulkan::clearFontSymbols() @@ -1766,24 +1713,6 @@ void GPUDisplayBackendVulkan::clearFontSymbols() mFontSymbols.clear(); } -void GPUDisplayBackendVulkan::updateFontTextureDescriptor() -{ - vk::DescriptorImageInfo imageInfo{}; - imageInfo.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; - imageInfo.imageView = mFontImage.view; - imageInfo.sampler = mTextureSampler; - for (uint32_t i = 0; i < mImageCount; i++) { - vk::WriteDescriptorSet descriptorWrite{}; - descriptorWrite.dstSet = mDescriptorSets[1][i]; - descriptorWrite.dstBinding = 2; - descriptorWrite.dstArrayElement = 0; - descriptorWrite.descriptorType = vk::DescriptorType::eCombinedImageSampler; - descriptorWrite.descriptorCount = 1; - descriptorWrite.pImageInfo = &imageInfo; - mDevice.updateDescriptorSets(1, &descriptorWrite, 0, nullptr); - } -} - void GPUDisplayBackendVulkan::OpenGLPrint(const char* s, float x, float y, float* color, float scale) { if (!mFreetypeInitialized || mDisplay->drawTextInCompatMode()) { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 452a630e3546b..93930c44266cf 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -78,7 +78,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void prepareText() override; void finishText() override; void mixImages(vk::CommandBuffer cmdBuffer, float mixSlaveImage); - void setMixDescriptor(int32_t descriptorIndex, int32_t imageIndex); + 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; void resizeScene(uint32_t width, uint32_t height) override; @@ -108,7 +108,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void downsampleToFramebuffer(vk::CommandBuffer& commandBuffer); void updateSwapChainDetails(const vk::PhysicalDevice& device); - void updateFontTextureDescriptor(); void createDevice(); void createShaders(); void createTextureSampler(); @@ -191,10 +190,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend uint32_t mUpdateUBOtextMatrix = -1; uint32_t mUpdateUBOmixMatrix = -1; float mLastMVP[16] = {}; - std::vector mDescriptorSets[3]; vk::DescriptorSetLayout mUniformDescriptor; vk::DescriptorSetLayout mUniformDescriptorTexture; - vk::DescriptorPool mDescriptorPool; VulkanBuffer mVBO; VulkanBuffer mIndirectCommandBuffer; From 31a4ca158055f1330e52d875ea21616866e93038 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 20:07:44 +0200 Subject: [PATCH 44/48] GPU Vulkan Display: Make viewport / scissor dynamic, images are by default read/writeable to allow FSAA and screenshots without swapchain recreation --- .../backend/GPUDisplayBackendVulkan.cxx | 87 ++++++++----------- .../display/backend/GPUDisplayBackendVulkan.h | 3 +- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index dae404785a408..7b69120cccf07 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -142,6 +142,10 @@ static void cmdImageMemoryBarrier(vk::CommandBuffer cmdbuffer, vk::Image image, static void cmdBeginRendering(vk::CommandBuffer cmdbuffer, uint32_t width, uint32_t height, const vk::RenderingAttachmentInfo& color, const vk::RenderingAttachmentInfo* depth = nullptr) { cmdbuffer.beginRendering(vk::RenderingInfo{{}, vk::Rect2D{{0, 0}, {width, height}}, 1, 0, 1, &color, depth}); + vk::Viewport viewport{0.f, 0.f, (float)width, (float)height, 0.f, 1.f}; + vk::Rect2D scissor{{0, 0}, {width, height}}; + cmdbuffer.setViewport(0, 1, &viewport); + cmdbuffer.setScissor(0, 1, &scissor); } static void cmdBeginRenderingLoad(vk::CommandBuffer cmdbuffer, vk::Image image, vk::ImageView view, uint32_t width, uint32_t height) // Render on top of the content of a color attachment written by a previous pass @@ -713,12 +717,8 @@ void GPUDisplayBackendVulkan::clearTextureSampler() // ---------------------------- VULKAN SWAPCHAIN MANAGEMENT ---------------------------- -void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing, vk::SwapchainKHR oldSwapChain) +void GPUDisplayBackendVulkan::createSwapChain(vk::SwapchainKHR oldSwapChain) { - mDownsampleFactor = getDownsampleFactor(forScreenshot); - mDownsampleFSAA = mDownsampleFactor != 1; - mSwapchainImageReadable = forScreenshot; - updateSwapChainDetails(mPhysicalDevice); mSurfaceFormat = chooseSwapSurfaceFormat(mSwapChainDetails.formats); mPresentMode = chooseSwapPresentMode(mSwapChainDetails.presentModes, mDisplay->cfgR().drawQualityVSync ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate); @@ -731,8 +731,6 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing mScreenWidth = extent.width; mScreenHeight = extent.height; - mRenderWidth = mScreenWidth * mDownsampleFactor; - mRenderHeight = mScreenHeight * mDownsampleFactor; vk::SwapchainCreateInfoKHR swapCreateInfo{}; swapCreateInfo.surface = mSurface; @@ -741,7 +739,7 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing swapCreateInfo.imageColorSpace = mSurfaceFormat.colorSpace; swapCreateInfo.imageExtent = extent; swapCreateInfo.imageArrayLayers = 1; - swapCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + swapCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst; // Transfer for screenshots / FSAA downsampling, so that they need no swapchain recreation swapCreateInfo.imageSharingMode = vk::SharingMode::eExclusive; swapCreateInfo.queueFamilyIndexCount = 0; // Optional swapCreateInfo.pQueueFamilyIndices = nullptr; // Optional @@ -750,12 +748,6 @@ void GPUDisplayBackendVulkan::createSwapChain(bool forScreenshot, bool forMixing swapCreateInfo.presentMode = mPresentMode; swapCreateInfo.clipped = true; swapCreateInfo.oldSwapchain = oldSwapChain; - if (mSwapchainImageReadable) { - swapCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferSrc; - } - if (mDownsampleFSAA) { - swapCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferDst; - } mSwapChain = mDevice.createSwapchainKHR(swapCreateInfo, nullptr); if (oldSwapChain) { mDevice.destroySwapchainKHR(oldSwapChain, nullptr); @@ -801,20 +793,26 @@ void GPUDisplayBackendVulkan::clearSwapChain(bool keepSwapChain) void GPUDisplayBackendVulkan::recreateRendering(bool forScreenshot, bool forMixing) { + 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; + if (!needUpdateOffscreenBuffers) { + return; + } mDevice.waitIdle(); - bool needUpdateSwapChain = mMustUpdateSwapChain || mDownsampleFactor != getDownsampleFactor(forScreenshot) || mSwapchainImageReadable != forScreenshot; - bool needUpdateOffscreenBuffers = needUpdateSwapChain || mMSAASampleCount != getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)) || mZActive != (mZSupported && mDisplay->cfgL().depthBuffer) || mMixingSupported != forMixing; - clearPipeline(); - if (needUpdateOffscreenBuffers) { - clearOffscreenBuffers(); - if (needUpdateSwapChain) { - clearSwapChain(true); - createSwapChain(forScreenshot, forMixing, mSwapChain); - } - createOffscreenBuffers(forScreenshot, forMixing); + const vk::Format oldFormat = mSurfaceFormat.format; + const vk::SampleCountFlagBits oldMSAASampleCount = mMSAASampleCount; + const bool oldZActive = mZActive; + clearOffscreenBuffers(); + if (needUpdateSwapChain) { + clearSwapChain(true); + createSwapChain(mSwapChain); + } + createOffscreenBuffers(forScreenshot, forMixing); + if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive) { // Viewport / scissor are dynamic + clearPipeline(); + createPipeline(); } - createPipeline(); - needRecordCommandBuffers(); mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed } @@ -825,6 +823,10 @@ void GPUDisplayBackendVulkan::createOffscreenBuffers(bool forScreenshot, bool fo mMSAASampleCount = getMSAASamplesFlag(std::min(mMaxMSAAsupported, mDisplay->cfgR().drawQualityMSAA)); mZActive = mZSupported && mDisplay->cfgL().depthBuffer; mMixingSupported = forMixing; + mDownsampleFactor = getDownsampleFactor(forScreenshot); + mDownsampleFSAA = mDownsampleFactor != 1; + mRenderWidth = mScreenWidth * mDownsampleFactor; + mRenderHeight = mScreenHeight * mDownsampleFactor; const uint32_t imageCountWithMixImages = mImageCount * (mMixingSupported ? 2 : 1); mRenderTargetView.resize(imageCountWithMixImages); @@ -929,23 +931,9 @@ void GPUDisplayBackendVulkan::createPipeline() // inputAssembly.topology // below inputAssembly.primitiveRestartEnable = false; - vk::Viewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - // viewport.width // below - // viewport.height // below - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - - vk::Rect2D scissor{}; - scissor.offset = vk::Offset2D{0, 0}; - // scissor.extent // below - - vk::PipelineViewportStateCreateInfo viewportState{}; + vk::PipelineViewportStateCreateInfo viewportState{}; // Viewport and scissor are dynamic viewportState.viewportCount = 1; - viewportState.pViewports = &viewport; viewportState.scissorCount = 1; - viewportState.pScissors = &scissor; vk::PipelineRasterizationStateCreateInfo rasterizer{}; rasterizer.depthClampEnable = false; @@ -995,9 +983,9 @@ void GPUDisplayBackendVulkan::createPipeline() depthStencil.depthBoundsTestEnable = false; depthStencil.stencilTestEnable = false; - vk::DynamicState dynamicStates[] = {vk::DynamicState::eLineWidth}; + vk::DynamicState dynamicStates[] = {vk::DynamicState::eViewport, vk::DynamicState::eScissor, vk::DynamicState::eLineWidth}; vk::PipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.dynamicStateCount = 1; + // dynamicState.dynamicStateCount // below: viewport + scissor for text / texture, + line width for the main pipelines dynamicState.pDynamicStates = dynamicStates; vk::PushConstantRange pushConstantRanges[2] = {vk::PushConstantRange{}, vk::PushConstantRange{}}; @@ -1034,7 +1022,7 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional - mPipelines.resize(mMixingSupported ? 5 : 4); + mPipelines.resize(5); 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 == 4) { // Texture rendering @@ -1043,26 +1031,24 @@ void GPUDisplayBackendVulkan::createPipeline() inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; vertShaderStageInfo.module = mShaders["vertexTexture"]; fragShaderStageInfo.module = mShaders["fragmentTexture"]; + dynamicState.dynamicStateCount = 2; pipelineInfo.layout = mPipelineLayoutTexture; renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; pipelineInfo.pDepthStencilState = nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; - viewport.width = scissor.extent.width = mRenderWidth; - viewport.height = scissor.extent.height = mRenderHeight; } else if (i == 3) { // Text rendering bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; vertShaderStageInfo.module = mShaders["vertexTexture"]; fragShaderStageInfo.module = mShaders["fragmentText"]; + dynamicState.dynamicStateCount = 2; pipelineInfo.layout = mPipelineLayoutTexture; renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; pipelineInfo.pDepthStencilState = nullptr; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; - viewport.width = scissor.extent.width = mScreenWidth; - viewport.height = scissor.extent.height = mScreenHeight; } else { // Point / line / line-strip rendering bindingDescription.stride = 3 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32Sfloat; @@ -1072,10 +1058,9 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.layout = mPipelineLayout; renderingInfo.depthAttachmentFormat = mZActive ? vk::Format::eD32Sfloat : vk::Format::eUndefined; pipelineInfo.pDepthStencilState = mZActive ? &depthStencil : nullptr; + dynamicState.dynamicStateCount = 3; colorBlendAttachment.blendEnable = true; multisampling.rasterizationSamples = mMSAASampleCount; - viewport.width = scissor.extent.width = mRenderWidth; - viewport.height = scissor.extent.height = mRenderHeight; } CHKERR(mDevice.createGraphicsPipelines(VkPipelineCache(VK_NULL_HANDLE), 1, &pipelineInfo, nullptr, &mPipelines[i])); // TODO: multiple at once + cache? @@ -1389,7 +1374,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& }; vk::Result retVal = vk::Result::eSuccess; - bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (requestScreenshot && !mSwapchainImageReadable) || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot); + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot); if (!mustUpdateSwapChain) { retVal = getImage(); if (retVal == vk::Result::eErrorOutOfDateKHR) { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 93930c44266cf..ffc54018e71f9 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -114,7 +114,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void createCommandBuffers(); void createSemaphores(); void createUniformLayoutsAndBuffers(); - void createSwapChain(bool forScreenshot = false, bool forMixing = false, vk::SwapchainKHR oldSwapChain = {}); + void createSwapChain(vk::SwapchainKHR oldSwapChain = {}); void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); void createPipeline(); void clearDevice(); @@ -204,7 +204,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend vecpod mFontVertexBufferHost; bool mHasDrawnText = false; - bool mSwapchainImageReadable = false; vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16; uint32_t mMaxMSAAsupported = 0; bool mZActive = false; From 75e3514e08c2f7f5fa58f781783689d23f2efeab Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 20:14:31 +0200 Subject: [PATCH 45/48] GPU Vulkan Display: Add optional support for smooth lines --- .../backend/GPUDisplayBackendVulkan.cxx | 32 +++++++++++++------ .../display/backend/GPUDisplayBackendVulkan.h | 2 ++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 7b69120cccf07..3de1cc9b80a4a 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -537,6 +537,8 @@ 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; updateSwapChainDetails(mPhysicalDevice); vk::PhysicalDeviceProperties deviceProperties = mPhysicalDevice.getProperties(); @@ -548,7 +550,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\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported); + GPUInfo("Max MSAA: %d, 32 bit Z buffer %d, 32 bit Z buffer + stencil buffer %d, Mailbox present mode %d, smooth lines %d\n", (int32_t)mMaxMSAAsupported, (int32_t)mZSupported, (int32_t)mStencilSupported, (int32_t)mailboxSupported, (int32_t)mSmoothLinesSupported); } vk::DeviceQueueCreateInfo queueCreateInfo{}; @@ -796,24 +798,27 @@ 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; - if (!needUpdateOffscreenBuffers) { + const bool smoothLinesChanged = mSmoothLines != (mSmoothLinesSupported && mDisplay->cfgL().smoothLines); + if (!needUpdateOffscreenBuffers && !smoothLinesChanged) { return; } mDevice.waitIdle(); const vk::Format oldFormat = mSurfaceFormat.format; const vk::SampleCountFlagBits oldMSAASampleCount = mMSAASampleCount; const bool oldZActive = mZActive; - clearOffscreenBuffers(); - if (needUpdateSwapChain) { - clearSwapChain(true); - createSwapChain(mSwapChain); + if (needUpdateOffscreenBuffers) { + clearOffscreenBuffers(); + if (needUpdateSwapChain) { + clearSwapChain(true); + createSwapChain(mSwapChain); + } + createOffscreenBuffers(forScreenshot, forMixing); + mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed } - createOffscreenBuffers(forScreenshot, forMixing); - if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive) { // Viewport / scissor are dynamic + if (mSurfaceFormat.format != oldFormat || mMSAASampleCount != oldMSAASampleCount || mZActive != oldZActive || smoothLinesChanged) { // Viewport / scissor are dynamic clearPipeline(); createPipeline(); } - mUpdateUBOtextMatrix = mUpdateUBOmixMatrix = -1; // Screen / render size may have changed } // ---------------------------- VULKAN OFFSCREEN BUFFERS ---------------------------- @@ -1022,10 +1027,15 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional + mSmoothLines = mSmoothLinesSupported && mDisplay->cfgL().smoothLines; + vk::PipelineRasterizationLineStateCreateInfo smoothLineState{}; + smoothLineState.lineRasterizationMode = vk::LineRasterizationMode::eRectangularSmooth; // Coverage goes to alpha, blending is enabled + mPipelines.resize(5); 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 == 4) { // Texture rendering + rasterizer.pNext = nullptr; bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; @@ -1038,6 +1048,7 @@ 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; @@ -1053,6 +1064,7 @@ void GPUDisplayBackendVulkan::createPipeline() bindingDescription.stride = 3 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32Sfloat; inputAssembly.topology = types[i]; + rasterizer.pNext = mSmoothLines && types[i] != vk::PrimitiveTopology::ePointList ? &smoothLineState : nullptr; vertShaderStageInfo.module = mShaders[types[i] == vk::PrimitiveTopology::ePointList ? "vertexPoint" : "vertex"]; fragShaderStageInfo.module = mShaders["fragment"]; pipelineInfo.layout = mPipelineLayout; @@ -1374,7 +1386,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& }; vk::Result retVal = vk::Result::eSuccess; - bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot); + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != (mSmoothLinesSupported && mDisplay->cfgL().smoothLines); if (!mustUpdateSwapChain) { retVal = getImage(); if (retVal == vk::Result::eErrorOutOfDateKHR) { diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index ffc54018e71f9..315b0b432bfc4 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -207,6 +207,8 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend vk::SampleCountFlagBits mMSAASampleCount = vk::SampleCountFlagBits::e16; uint32_t mMaxMSAAsupported = 0; bool mZActive = false; + bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines, enabled if available + bool mSmoothLines = false; bool mZSupported = false; bool mStencilSupported = false; bool mDownsampleFSAA = false; From 971802713cb416c3c78158bb33ea8b1bb18c02d8 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 21:09:54 +0200 Subject: [PATCH 46/48] GPU Display: Add shaders for smooth lines and points for OpenGL and Vulkan --- GPU/GPUTracking/Definitions/GPUSettingsList.h | 2 +- GPU/GPUTracking/display/CMakeLists.txt | 4 + GPU/GPUTracking/display/GPUDisplay.cxx | 2 +- .../display/backend/GPUDisplayBackend.h | 2 + .../backend/GPUDisplayBackendOpenGL.cxx | 87 ++++++++++++++++--- .../display/backend/GPUDisplayBackendOpenGL.h | 11 +++ .../backend/GPUDisplayBackendVulkan.cxx | 54 +++++++++--- .../display/backend/GPUDisplayBackendVulkan.h | 3 + .../display/frontend/GPUDisplayKeys.cxx | 13 ++- .../display/shaders/fragmentSmoothLine.frag | 35 ++++++++ .../display/shaders/fragmentSmoothPoint.frag | 34 ++++++++ .../display/shaders/vertexSmoothLine.vert | 39 +++++++++ .../display/shaders/vertexSmoothPoint.vert | 35 ++++++++ 13 files changed, 285 insertions(+), 36 deletions(-) create mode 100644 GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag create mode 100644 GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag create mode 100644 GPU/GPUTracking/display/shaders/vertexSmoothLine.vert create mode 100644 GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert diff --git a/GPU/GPUTracking/Definitions/GPUSettingsList.h b/GPU/GPUTracking/Definitions/GPUSettingsList.h index f1fdd7f70cdda..f2a7927f9c1d2 100644 --- a/GPU/GPUTracking/Definitions/GPUSettingsList.h +++ b/GPU/GPUTracking/Definitions/GPUSettingsList.h @@ -500,7 +500,7 @@ AddOption(camYUp, bool, false, "", 0, "Orient the camera such that the y-axis is AddOption(cameraMode, int32_t, 0, "", 0, "Camera mode") AddOption(fullScreen, bool, false, "", 0, "Full Screen") AddOption(maximized, bool, true, "", 0, "Maximized window") -AddOption(openGLCore, bool, false, "", 0, "Use renderer path for OpenGL core profile") +AddOption(rendererCompatSmoothing, bool, false, "", 0, "Use compat smoothing (if available) of instad of shader for smooth lines / points") AddOption(drawQualityMSAA, int32_t, 0, "", 0, "MultiSample Anti Aliasing") AddOption(drawQualityDownsampleFSAA, int32_t, 0, "", 0, "Downsampling Anti Aliasing") AddOption(drawQualityVSync, bool, true, "", 0, "Enable Vertical Sync") diff --git a/GPU/GPUTracking/display/CMakeLists.txt b/GPU/GPUTracking/display/CMakeLists.txt index ae27bd1395dd5..73f4c7da99d6a 100644 --- a/GPU/GPUTracking/display/CMakeLists.txt +++ b/GPU/GPUTracking/display/CMakeLists.txt @@ -257,9 +257,13 @@ if(GPUCA_EVENT_DISPLAY_VULKAN OR GPUCA_EVENT_DISPLAY_OPENGL) include(shaders.cmake) set(SHADERS shaders/vertex.vert shaders/vertexPoint.vert + shaders/vertexSmoothPoint.vert shaders/vertexTexture.vert shaders/vertexPassthrough.vert shaders/fragment.frag + shaders/fragmentSmoothPoint.frag + shaders/vertexSmoothLine.vert + shaders/fragmentSmoothLine.frag shaders/fragmentText.frag shaders/fragmentTexture.frag shaders/field.geom) diff --git a/GPU/GPUTracking/display/GPUDisplay.cxx b/GPU/GPUTracking/display/GPUDisplay.cxx index 3aa6ed07b59a3..2c0e6b447c4ed 100644 --- a/GPU/GPUTracking/display/GPUDisplay.cxx +++ b/GPU/GPUTracking/display/GPUDisplay.cxx @@ -75,7 +75,7 @@ GPUDisplay::GPUDisplay(GPUDisplayFrontend* frontend, GPUChainTracking* chain, GP mBackend->mDisplay = this; frontend->mDisplay = this; frontend->mBackend = mBackend.get(); - mCfgR.openGLCore = mBackend->CoreProfile(); + mCfgR.rendererCompatSmoothing &= !mBackend->CoreProfile(); } void GPUDisplay::calcXYZ(const float* matrix) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h index 5d2a85cafbc1f..9342b8a642f33 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackend.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackend.h @@ -120,6 +120,8 @@ class GPUDisplayBackend bool mFrontendCompatTextDraw = false; std::vector mScreenshotPixels; + static constexpr float SMOOTH_POINT_EXTRA_SIZE = 0.6f; + static constexpr float SMOOTH_LINE_EXTRA_WIDTH = 0.8f; int32_t mDownsampleFactor = 1; uint32_t mRenderWidth = 0; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx index 335b7ef6ddd69..1af8f564d4298 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.cxx @@ -180,12 +180,12 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } if (mDisplay->useMultiVBO()) { - if (mDisplay->cfgR().openGLCore) { + if (!mDisplay->cfgR().rendererCompatSmoothing) { // TODO: Remove OpenGL compat profile completely CHKERR(glBindVertexArray(mVertexArray)); } CHKERR(glBindBuffer(GL_ARRAY_BUFFER, mVBOId[iSector])); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); } else #endif @@ -195,6 +195,10 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType } } + const uint32_t smoothProgram = tt == POINTS ? (mUseSmoothPointShader ? mShaderProgramSmoothPoint : 0) : (mUseSmoothLineShader ? mShaderProgramSmoothLine : 0); + if (smoothProgram) { + CHKERR(glUseProgram(smoothProgram)); + } if (mDisplay->cfgR().useGLIndirectDraw) { CHKERR(glMultiDrawArraysIndirect(t, (void*)(size_t)((mIndirectSectorOffset[iSector] + first) * sizeof(DrawArraysIndirectCommand)), count, 0)); } else if (OPENGL_EMULATE_MULTI_DRAW) { @@ -205,18 +209,27 @@ uint32_t GPUDisplayBackendOpenGL::drawVertices(const vboList& v, const drawType static_assert(sizeof(GLsizei) == sizeof(*mDisplay->vertexBufferCount()[iSector].data()), "Invalid counter size does not match GLsizei"); CHKERR(glMultiDrawArrays(t, mDisplay->vertexBufferStart()[iSector].data() + first, ((const GLsizei*)mDisplay->vertexBufferCount()[iSector].data()) + first, count)); } + if (smoothProgram) { + CHKERR(glUseProgram(mShaderProgram)); + } return count; } void GPUDisplayBackendOpenGL::ActivateColor(std::array& color) { #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + 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])); + } } } @@ -312,7 +325,11 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() CHKERR(mVertexShaderTexture = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexTexture_vert_spv_start, _binary_opengl_vertexTexture_vert_spv_len)); CHKERR(mFragmentShaderTexture = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentTexture_frag_spv_start, _binary_opengl_fragmentTexture_frag_spv_len)); CHKERR(mFragmentShaderText = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentText_frag_spv_start, _binary_opengl_fragmentText_frag_spv_len)); - if (checkShaderStatus(mVertexShader) || checkShaderStatus(mFragmentShader) || checkShaderStatus(mVertexShaderTexture) || checkShaderStatus(mFragmentShaderTexture) || checkShaderStatus(mFragmentShaderText)) { + CHKERR(mVertexShaderSmoothPoint = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexSmoothPoint_vert_spv_start, _binary_opengl_vertexSmoothPoint_vert_spv_len)); + CHKERR(mFragmentShaderSmoothPoint = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentSmoothPoint_frag_spv_start, _binary_opengl_fragmentSmoothPoint_frag_spv_len)); + CHKERR(mVertexShaderSmoothLine = loadShader(GL_VERTEX_SHADER, _binary_opengl_vertexSmoothLine_vert_spv_start, _binary_opengl_vertexSmoothLine_vert_spv_len)); + CHKERR(mFragmentShaderSmoothLine = loadShader(GL_FRAGMENT_SHADER, _binary_opengl_fragmentSmoothLine_frag_spv_start, _binary_opengl_fragmentSmoothLine_frag_spv_len)); + if (checkShaderStatus(mVertexShader) || checkShaderStatus(mFragmentShader) || checkShaderStatus(mVertexShaderSmoothPoint) || checkShaderStatus(mFragmentShaderSmoothPoint) || checkShaderStatus(mVertexShaderSmoothLine) || checkShaderStatus(mFragmentShaderSmoothLine) || checkShaderStatus(mVertexShaderTexture) || checkShaderStatus(mFragmentShaderTexture) || checkShaderStatus(mFragmentShaderText)) { return 1; } CHKERR(mShaderProgram = glCreateProgram()); @@ -322,6 +339,20 @@ int32_t GPUDisplayBackendOpenGL::InitBackendA() if (checkProgramStatus(mShaderProgram)) { return 1; } + CHKERR(mShaderProgramSmoothPoint = glCreateProgram()); + CHKERR(glAttachShader(mShaderProgramSmoothPoint, mVertexShaderSmoothPoint)); + CHKERR(glAttachShader(mShaderProgramSmoothPoint, mFragmentShaderSmoothPoint)); + CHKERR(glLinkProgram(mShaderProgramSmoothPoint)); + if (checkProgramStatus(mShaderProgramSmoothPoint)) { + return 1; + } + CHKERR(mShaderProgramSmoothLine = glCreateProgram()); + CHKERR(glAttachShader(mShaderProgramSmoothLine, mVertexShaderSmoothLine)); + CHKERR(glAttachShader(mShaderProgramSmoothLine, mFragmentShaderSmoothLine)); + CHKERR(glLinkProgram(mShaderProgramSmoothLine)); + if (checkProgramStatus(mShaderProgramSmoothLine)) { + return 1; + } CHKERR(mShaderProgramText = glCreateProgram()); CHKERR(glAttachShader(mShaderProgramText, mVertexShaderTexture)); CHKERR(glAttachShader(mShaderProgramText, mFragmentShaderText)); @@ -359,6 +390,12 @@ void GPUDisplayBackendOpenGL::ExitBackendA() CHKERR(glDeleteProgram(mShaderProgram)); CHKERR(glDeleteShader(mVertexShader)); CHKERR(glDeleteShader(mFragmentShader)); + CHKERR(glDeleteProgram(mShaderProgramSmoothPoint)); + CHKERR(glDeleteShader(mVertexShaderSmoothPoint)); + CHKERR(glDeleteShader(mFragmentShaderSmoothPoint)); + CHKERR(glDeleteProgram(mShaderProgramSmoothLine)); + CHKERR(glDeleteShader(mVertexShaderSmoothLine)); + CHKERR(glDeleteShader(mFragmentShaderSmoothLine)); CHKERR(glDeleteProgram(mShaderProgramText)); CHKERR(glDeleteShader(mFragmentShaderText)); CHKERR(glDeleteProgram(mShaderProgramTexture)); @@ -432,13 +469,20 @@ void 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 (mDisplay->cfgL().smoothPoints && !mDisplay->cfgR().openGLCore) { + 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().openGLCore) { + if (mDisplay->cfgL().smoothLines && mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glEnable(GL_LINE_SMOOTH)); } else { CHKERR(glDisable(GL_LINE_SMOOTH)); @@ -449,7 +493,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& glViewport(0, 0, mRenderWidth, mRenderHeight); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glEnableClientState(GL_VERTEX_ARRAY)); CHKERR(glVertexPointer(3, GL_FLOAT, 0, nullptr)); } else @@ -461,7 +505,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& CHKERR(glEnableVertexAttribArray(0)); } #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glMatrixMode(GL_PROJECTION)); CHKERR(glLoadMatrixf(&proj.Elements[0][0])); CHKERR(glMatrixMode(GL_MODELVIEW)); @@ -471,6 +515,13 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& { 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)); } @@ -480,7 +531,7 @@ void GPUDisplayBackendOpenGL::prepareDraw(const hmm_mat4& proj, const hmm_mat4& void GPUDisplayBackendOpenGL::finishDraw(bool doScreenshot, bool toMixBuffer, float includeMixImage) { #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { CHKERR(glDisableClientState(GL_VERTEX_ARRAY)); } else #endif @@ -565,7 +616,7 @@ 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().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_PROJECTION); @@ -584,7 +635,7 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) CHKERR(glBindTexture(GL_TEXTURE_2D, mMixBuffer.fbCol_id)); #ifndef GPUCA_DISPLAY_OPENGL_CORE - if (!mDisplay->cfgR().openGLCore) { + if (mDisplay->cfgR().rendererCompatSmoothing) { glColor4f(1, 1, 1, mixSlaveImage); glBegin(GL_QUADS); glTexCoord2f(0, 0); @@ -611,12 +662,22 @@ void GPUDisplayBackendOpenGL::mixImages(float mixSlaveImage) void GPUDisplayBackendOpenGL::pointSizeFactor(float factor) { - CHKERR(glPointSize(mDisplay->cfgL().pointSize * mDownsampleFactor * factor)); + const float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mUseSmoothPointShader ? SMOOTH_POINT_EXTRA_SIZE : 0.f); + if (mUseSmoothPointShader) { + CHKERR(glProgramUniform1f(mShaderProgramSmoothPoint, UNIFORM_LOCATION_POINT_SIZE, size)); + } + CHKERR(glPointSize(size)); } void GPUDisplayBackendOpenGL::lineWidthFactor(float factor) { - CHKERR(glLineWidth(mDisplay->cfgL().lineWidth * mDownsampleFactor * factor)); + float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; + if (mUseSmoothLineShader) { + 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 + } + CHKERR(glLineWidth(width)); } void GPUDisplayBackendOpenGL::addFontSymbol(int32_t symbol, int32_t sizex, int32_t sizey, int32_t offsetx, int32_t offsety, int32_t advance, void* data) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h index 60a65a4498a7f..ee54c1097ad35 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendOpenGL.h @@ -72,6 +72,9 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend static uint32_t loadShader(uint32_t type, const char* spirv, size_t size); // Create shader from SPIR-V static constexpr int32_t UNIFORM_LOCATION_MATRIX = 0; // Explicit uniform locations in the OpenGL variant of the shaders (shaders/*.vert, *.frag) static constexpr int32_t UNIFORM_LOCATION_COLOR = 4; // color, textColor, alpha + static constexpr int32_t UNIFORM_LOCATION_POINT_SIZE = 5; // vertexSmoothPoint.vert + static constexpr int32_t UNIFORM_LOCATION_VIEWPORT = 6; // vertexSmoothLine.vert + static constexpr int32_t UNIFORM_LOCATION_LINE_WIDTH = 7; // vertexSmoothLine.vert 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; @@ -84,12 +87,20 @@ class GPUDisplayBackendOpenGL : public GPUDisplayBackend uint32_t mVertexShader; uint32_t mFragmentShader; + uint32_t mVertexShaderSmoothPoint; + uint32_t mFragmentShaderSmoothPoint; + uint32_t mVertexShaderSmoothLine; + uint32_t mFragmentShaderSmoothLine; uint32_t mVertexShaderTexture; uint32_t mVertexShaderPassthrough; uint32_t mFragmentShaderTexture; uint32_t mFragmentShaderText; uint32_t mGeometryShader; uint32_t mShaderProgram; + uint32_t mShaderProgramSmoothPoint; // Points with fragmentSmoothPoint.frag, for smoothPoints in the core profile (no GL_POINT_SMOOTH) + bool mUseSmoothPointShader = false; + uint32_t mShaderProgramSmoothLine; // Lines with vertexSmoothLine.vert / fragmentSmoothLine.frag, for smoothLines in the core profile (no GL_LINE_SMOOTH) + bool mUseSmoothLineShader = false; uint32_t mShaderProgramText; uint32_t mShaderProgramTexture; uint32_t mShaderProgramField; diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 3de1cc9b80a4a..8fe9613d21a00 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -504,6 +504,7 @@ void GPUDisplayBackendVulkan::createDevice() reqDeviceFeatures.get().timelineSemaphore = VK_TRUE; reqDeviceFeatures.get().pushDescriptor = VK_TRUE; reqDeviceFeatures.get().dynamicRendering = reqDeviceFeatures.get().synchronization2 = VK_TRUE; + reqDeviceFeatures.get().shaderDemoteToHelperInvocation = VK_TRUE; // discard in SPIR-V 1.6 reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; reqDeviceFeatures.get().presentId = VK_TRUE; reqDeviceFeatures.get().presentWait = VK_TRUE; @@ -546,6 +547,7 @@ void GPUDisplayBackendVulkan::createDevice() vk::FormatProperties depth64FormatProperties = mPhysicalDevice.getFormatProperties(vk::Format::eD32SfloatS8Uint); GPUInfo("Using physical Vulkan device %s", &deviceProperties.deviceName[0]); mMaxMSAAsupported = getMaxUsableSampleCount(deviceProperties); + mStrictLines = deviceProperties.limits.strictLines; mZSupported = (bool)(depth32FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); mStencilSupported = (bool)(depth64FormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment); bool mailboxSupported = std::find(mSwapChainDetails.presentModes.begin(), mSwapChainDetails.presentModes.end(), vk::PresentModeKHR::eMailbox) != mSwapChainDetails.presentModes.end(); @@ -798,7 +800,7 @@ 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 != (mSmoothLinesSupported && mDisplay->cfgL().smoothLines); + const bool smoothLinesChanged = mSmoothLines != useNativeSmoothLines(); if (!needUpdateOffscreenBuffers && !smoothLinesChanged) { return; } @@ -999,7 +1001,7 @@ void GPUDisplayBackendVulkan::createPipeline() pushConstantRanges[0].size = sizeof(float) * 4; pushConstantRanges[1].stageFlags = vk::ShaderStageFlagBits::eVertex; pushConstantRanges[1].offset = pushConstantRanges[0].size; - pushConstantRanges[1].size = sizeof(float); + pushConstantRanges[1].size = 4 * sizeof(float); // Point size, line width, viewport vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &mUniformDescriptor; @@ -1027,11 +1029,11 @@ void GPUDisplayBackendVulkan::createPipeline() pipelineInfo.basePipelineHandle = VkPipeline(VK_NULL_HANDLE); // Optional pipelineInfo.basePipelineIndex = -1; // Optional - mSmoothLines = mSmoothLinesSupported && mDisplay->cfgL().smoothLines; + mSmoothLines = useNativeSmoothLines(); vk::PipelineRasterizationLineStateCreateInfo smoothLineState{}; smoothLineState.lineRasterizationMode = vk::LineRasterizationMode::eRectangularSmooth; // Coverage goes to alpha, blending is enabled - mPipelines.resize(5); + mPipelines.resize(8); // 0 points, 1 lines, 2 line strip (drawType index), 3 text, 4 texture, 5-7 smooth (shader-based) points, lines, line strip 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 == 4) { // Texture rendering @@ -1063,10 +1065,11 @@ void GPUDisplayBackendVulkan::createPipeline() } else { // Point / line / line-strip rendering bindingDescription.stride = 3 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32Sfloat; - inputAssembly.topology = types[i]; - rasterizer.pNext = mSmoothLines && types[i] != vk::PrimitiveTopology::ePointList ? &smoothLineState : nullptr; - vertShaderStageInfo.module = mShaders[types[i] == vk::PrimitiveTopology::ePointList ? "vertexPoint" : "vertex"]; - fragShaderStageInfo.module = mShaders["fragment"]; + const uint32_t drawIdx = i >= 5 ? i - 5 : i; + inputAssembly.topology = types[drawIdx]; + rasterizer.pNext = mSmoothLines && (i == 1 || i == 2) ? &smoothLineState : nullptr; + vertShaderStageInfo.module = mShaders[i < 5 ? (drawIdx == 0 ? "vertexPoint" : "vertex") : (drawIdx == 0 ? "vertexSmoothPoint" : "vertexSmoothLine")]; + fragShaderStageInfo.module = mShaders[i < 5 ? "fragment" : (drawIdx == 0 ? "fragmentSmoothPoint" : "fragmentSmoothLine")]; pipelineInfo.layout = mPipelineLayout; renderingInfo.depthAttachmentFormat = mZActive ? vk::Format::eD32Sfloat : vk::Format::eUndefined; pipelineInfo.pDepthStencilState = mZActive ? &depthStencil : nullptr; @@ -1117,6 +1120,8 @@ void GPUDisplayBackendVulkan::startFillCommandBuffer(vk::CommandBuffer& commandB vk::DeviceSize offsets[] = {0}; commandBuffer.bindVertexBuffers(0, 1, &mVBO.buffer, offsets); cmdPushDescriptors(commandBuffer, mPipelineLayout, mUniformBuffersMat[0][imageIndex]); + const float viewport[2] = {(float)mRenderWidth, (float)mRenderHeight}; + commandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, 6 * sizeof(float), sizeof(viewport), viewport); } void GPUDisplayBackendVulkan::endFillCommandBuffer(vk::CommandBuffer& commandBuffer) @@ -1141,6 +1146,10 @@ void GPUDisplayBackendVulkan::createShaders() { LOAD_SHADER(vertex, vert); LOAD_SHADER(fragment, frag); + LOAD_SHADER(fragmentSmoothPoint, frag); + LOAD_SHADER(vertexSmoothLine, vert); + LOAD_SHADER(vertexSmoothPoint, vert); + LOAD_SHADER(fragmentSmoothLine, frag); LOAD_SHADER(vertexPoint, vert); LOAD_SHADER(vertexTexture, vert); LOAD_SHADER(fragmentTexture, frag); @@ -1357,9 +1366,10 @@ uint32_t GPUDisplayBackendVulkan::drawVertices(const vboList& v, const drawType return count; } - if (mCurrentCommandBufferLastPipeline != tt) { - mCurrentCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[tt]); - mCurrentCommandBufferLastPipeline = tt; + const int32_t pipeline = tt + ((tt == POINTS ? mDisplay->cfgL().smoothPoints : useSmoothLineShader()) ? 5 : 0); + if (mCurrentCommandBufferLastPipeline != pipeline) { + mCurrentCommandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipelines[pipeline]); + mCurrentCommandBufferLastPipeline = pipeline; } if (mDisplay->cfgR().useGLIndirectDraw) { mCurrentCommandBuffer.drawIndirect(mIndirectCommandBuffer.buffer, (mIndirectSectorOffset[iSector] + first) * sizeof(DrawArraysIndirectCommand), count, sizeof(DrawArraysIndirectCommand)); @@ -1386,7 +1396,7 @@ void GPUDisplayBackendVulkan::prepareDraw(const hmm_mat4& proj, const hmm_mat4& }; vk::Result retVal = vk::Result::eSuccess; - bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != (mSmoothLinesSupported && mDisplay->cfgL().smoothLines); + bool mustUpdateSwapChain = mMustUpdateSwapChain || mDisplay->updateRenderPipeline() || (toMixBuffer && !mMixingSupported) || mDownsampleFactor != getDownsampleFactor(requestScreenshot) || mSmoothLines != useNativeSmoothLines(); if (!mustUpdateSwapChain) { retVal = getImage(); if (retVal == vk::Result::eErrorOutOfDateKHR) { @@ -1611,16 +1621,32 @@ void GPUDisplayBackendVulkan::pointSizeFactor(float factor) if (mCommandBufferUpToDate[mCurrentImageIndex]) { return; } - float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor; + float size = mDisplay->cfgL().pointSize * mDownsampleFactor * factor + (mDisplay->cfgL().smoothPoints ? SMOOTH_POINT_EXTRA_SIZE : 0.f); 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; } - mCurrentCommandBuffer.setLineWidth(mDisplay->cfgL().lineWidth * mDownsampleFactor * factor); + float width = mDisplay->cfgL().lineWidth * mDownsampleFactor * factor; + if (useSmoothLineShader()) { + width += SMOOTH_LINE_EXTRA_WIDTH; + mCurrentCommandBuffer.pushConstants(mPipelineLayout, vk::ShaderStageFlagBits::eVertex, 5 * sizeof(float), sizeof(width), &width); + width *= mStrictLines ? 1.f : 1.4143f; // Non-strict wide lines may be parallelograms, widen so that the perpendicular width is covered + } + mCurrentCommandBuffer.setLineWidth(width); } void GPUDisplayBackendVulkan::needRecordCommandBuffers() diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 315b0b432bfc4..cf171fc8c6ea7 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -81,6 +81,8 @@ 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; void resizeScene(uint32_t width, uint32_t height) override; float getYFactor() const override { return -1.0f; } int32_t getMaxMSAA() const override { return mMaxMSAAsupported; } @@ -209,6 +211,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mZActive = false; bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines, enabled if available bool mSmoothLines = false; + bool mStrictLines = false; // Wide lines are rectangles, not parallelograms bool mZSupported = false; bool mStencilSupported = false; bool mDownsampleFSAA = false; diff --git a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx index 9056edfc7c18a..fd94451741e85 100644 --- a/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx +++ b/GPU/GPUTracking/display/frontend/GPUDisplayKeys.cxx @@ -54,7 +54,7 @@ const char* HelpText[] = { "[e] / [f] Rotate left / right", "[+] / [-] Increase / decrease point size (Hold SHIFT for lines)", "[b] Change FOV (field of view)", - "['] Switch between OpenGL core / compat code path", + "['] Switch between core / compat renderer path (OpenGL profile, Vulkan smooth lines)", "[MOUSE 1] Look around", "[MOUSE 2] Strafe camera", "[MOUSE 1+2] Zoom / Rotate", @@ -189,15 +189,14 @@ void GPUDisplay::HandleKey(uint8_t key) } SetInfo("Set FOV to %f", mCfgR.fov); } else if (key == 39) { // character = "'" - if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { #ifdef GPUCA_DISPLAY_OPENGL_CORE + if (backend()->backendType() == GPUDisplayBackend::TYPE_OPENGL) { SetInfo("OpenGL compat profile not available, using core profile", 1); -#else - mCfgR.openGLCore ^= 1; - SetInfo("Using renderer path for OpenGL %s profile", mCfgR.openGLCore ? "core" : "compat"); + } else #endif - } else { - SetInfo("OpenGL options only available with OpenGL backend"); + { + mCfgR.rendererCompatSmoothing ^= 1; + SetInfo("Using renderer path for %s profile", mCfgR.rendererCompatSmoothing ? "compat" : "core (shader)"); } } else if (key == 'B') { mCfgH.markAdjacentClusters++; diff --git a/GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag b/GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag new file mode 100644 index 0000000000000..16d6ff0c537e4 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/fragmentSmoothLine.frag @@ -0,0 +1,35 @@ +// 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 fragmentSmoothLine.frag +/// \author David Rohr + +#version 460 core +layout (location = 0) out vec4 outColor; +layout (location = 1) noperspective in vec2 centerPos; +layout (location = 2) flat in float alphaOffset; +#ifdef VULKAN +layout (push_constant) uniform pushColor { vec4 color; } pc; +#define COLOR pc.color +#else +layout (location = 4) uniform vec4 color; +#define COLOR color +#endif + +void main() // Antialiased line edge (1 pixel ramp at the border of the line widened by SMOOTH_LINE_EXTRA_WIDTH), replaces GL_LINE_SMOOTH / Vulkan smoothLines +{ + vec2 p = gl_FragCoord.xy - centerPos; + float alpha = clamp(alphaOffset - 0.5f * dot(p, p), 0.f, 1.f); // half width - (d^2 + 0.5) / 2 in pixels: sqrt(d^2) linearized around d = 1 pixel (no sqrt), 0.25 margin to make it a bit brighter, as for the points + if (alpha <= 0.f) { + discard; // No depth write outside the line + } + outColor = vec4(COLOR.x, COLOR.y, COLOR.z, alpha); +} diff --git a/GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag b/GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag new file mode 100644 index 0000000000000..161ed5ad72470 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/fragmentSmoothPoint.frag @@ -0,0 +1,34 @@ +// 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 fragmentSmoothPoint.frag +/// \author David Rohr + +#version 460 core +layout (location = 0) out vec4 outColor; +layout (location = 1) flat in float pointSize; +#ifdef VULKAN +layout (push_constant) uniform pushColor { vec4 color; } pc; +#define COLOR pc.color +#else +layout (location = 4) uniform vec4 color; +#define COLOR color +#endif + +void main() // Round point with antialiased edge, replaces GL_POINT_SMOOTH. The point size is increased by SMOOTH_POINT_EXTRA_SIZE, the outermost pixel is the alpha ramp +{ + vec2 p = gl_PointCoord - vec2(0.5f); + float alpha = clamp(pointSize * (0.5f - 0.5f * pointSize * dot(p, p)) - 0.25f, 0.f, 1.f); // radius - (d^2 + 1) / 2 in pixels: sqrt(d^2) linearized around d = 1 pixel, avoids the sqrt, adding 0.25 margin to make it a bit brighter + if (alpha <= 0.f) { + discard; // No depth write outside the disc + } + outColor = vec4(COLOR.x, COLOR.y, COLOR.z, alpha); +} diff --git a/GPU/GPUTracking/display/shaders/vertexSmoothLine.vert b/GPU/GPUTracking/display/shaders/vertexSmoothLine.vert new file mode 100644 index 0000000000000..7ab9dba518b51 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/vertexSmoothLine.vert @@ -0,0 +1,39 @@ +// 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 vertexSmoothLine.vert +/// \author David Rohr + +#version 460 core +layout (location = 0) in vec3 pos; +layout (location = 1) noperspective out vec2 centerPos; // Window position, interpolated along the line, i.e. the closest point on the center line for each fragment of a (wide) line +layout (location = 2) flat out float alphaOffset; // Half line width - 0.25, see fragmentSmoothLine.frag +#ifdef VULKAN +layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; +layout (push_constant) uniform pushLine { layout(offset = 20) float lineWidth; layout(offset = 24) vec2 viewport; } pl; +#define MODEL_VIEW_PROJ um.ModelViewProj +#define LINE_WIDTH pl.lineWidth +#define VIEWPORT pl.viewport +#else +layout (location = 0) uniform mat4 ModelViewProj; +layout (location = 6) uniform vec2 viewport; +layout (location = 7) uniform float lineWidth; +#define MODEL_VIEW_PROJ ModelViewProj +#define LINE_WIDTH lineWidth +#define VIEWPORT viewport +#endif + +void main() +{ + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); + centerPos = (gl_Position.xy / gl_Position.w * 0.5f + 0.5f) * VIEWPORT; + alphaOffset = LINE_WIDTH * 0.5f - 0.25f; +} diff --git a/GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert b/GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert new file mode 100644 index 0000000000000..1c7c39f02d166 --- /dev/null +++ b/GPU/GPUTracking/display/shaders/vertexSmoothPoint.vert @@ -0,0 +1,35 @@ +// 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 vertexSmoothPoint.vert +/// \author David Rohr + +#version 460 core +layout (location = 0) in vec3 pos; +layout (location = 1) flat out float pointSize; +#ifdef VULKAN +layout (binding = 0) uniform uniformMatrix { mat4 ModelViewProj; } um; +layout (push_constant) uniform pushSize { layout(offset = 16) float size; } ps; +#define MODEL_VIEW_PROJ um.ModelViewProj +#define POINT_SIZE ps.size +#else +layout (location = 0) uniform mat4 ModelViewProj; +layout (location = 5) uniform float size; +#define MODEL_VIEW_PROJ ModelViewProj +#define POINT_SIZE size +#endif + +void main() +{ + gl_Position = MODEL_VIEW_PROJ * vec4(pos.x, pos.y, pos.z, 1.0); + gl_PointSize = POINT_SIZE; // Ignored in OpenGL (no GL_PROGRAM_POINT_SIZE), there glPointSize applies + pointSize = POINT_SIZE; +} From 79dd2f6992e24c436bb5fb268f0058eb23eb63c2 Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 22:06:35 +0200 Subject: [PATCH 47/48] GPU Vulkan Display: Use in-memory pipeline cache --- GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx | 4 +++- GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index 8fe9613d21a00..ec1be8010b7be 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -575,11 +575,13 @@ void GPUDisplayBackendVulkan::createDevice() poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; poolInfo.queueFamilyIndex = mGraphicsFamily; mCommandPool = mDevice.createCommandPool(poolInfo, nullptr); + mPipelineCache = mDevice.createPipelineCache(vk::PipelineCacheCreateInfo{}, nullptr); } void GPUDisplayBackendVulkan::clearDevice() { mDevice.destroyCommandPool(mCommandPool, nullptr); + mDevice.destroyPipelineCache(mPipelineCache, nullptr); mDevice.destroy(nullptr); mInstance.destroySurfaceKHR(mSurface, nullptr); if (mEnableValidationLayers) { @@ -1078,7 +1080,7 @@ void GPUDisplayBackendVulkan::createPipeline() multisampling.rasterizationSamples = mMSAASampleCount; } - CHKERR(mDevice.createGraphicsPipelines(VkPipelineCache(VK_NULL_HANDLE), 1, &pipelineInfo, nullptr, &mPipelines[i])); // TODO: multiple at once + cache? + CHKERR(mDevice.createGraphicsPipelines(mPipelineCache, 1, &pipelineInfo, nullptr, &mPipelines[i])); } } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index cf171fc8c6ea7..30244d275e24a 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -163,6 +163,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend vk::PipelineLayout mPipelineLayoutTexture; std::vector mPipelines; vk::CommandPool mCommandPool; + vk::PipelineCache mPipelineCache; bool mCommandInfrastructureCreated = false; uint32_t mImageCount = 0; @@ -211,7 +212,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend bool mZActive = false; bool mSmoothLinesSupported = false; // Optional Vulkan 1.4 feature smoothLines, enabled if available bool mSmoothLines = false; - bool mStrictLines = false; // Wide lines are rectangles, not parallelograms + bool mStrictLines = false; bool mZSupported = false; bool mStencilSupported = false; bool mDownsampleFSAA = false; From 8488df26c0c76b31b32672ed9b722795dc376cfc Mon Sep 17 00:00:00 2001 From: David Rohr Date: Wed, 14 Oct 2026 22:10:51 +0200 Subject: [PATCH 48/48] GPU Vulkan Display: Use Vulkan14Features::maintenance5 to load shaders directly --- .../backend/GPUDisplayBackendVulkan.cxx | 38 ++++++------------- .../display/backend/GPUDisplayBackendVulkan.h | 5 +-- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx index ec1be8010b7be..92b3341917a2e 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.cxx @@ -125,14 +125,6 @@ vk::Extent2D GPUDisplayBackendVulkan::chooseSwapExtent(const vk::SurfaceCapabili } } -static vk::ShaderModule createShaderModule(const char* code, size_t size, vk::Device device) -{ - vk::ShaderModuleCreateInfo createInfo{}; - createInfo.codeSize = size; - createInfo.pCode = reinterpret_cast(code); - return device.createShaderModule(createInfo, nullptr); -} - static void cmdImageMemoryBarrier(vk::CommandBuffer cmdbuffer, vk::Image image, vk::PipelineStageFlags2 srcStageMask, vk::AccessFlags2 srcAccessMask, vk::PipelineStageFlags2 dstStageMask, vk::AccessFlags2 dstAccessMask, vk::ImageLayout oldLayout, vk::ImageLayout newLayout, vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor) { vk::ImageMemoryBarrier2 barrier{srcStageMask, srcAccessMask, dstStageMask, dstAccessMask, oldLayout, newLayout, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, image, {aspect, 0, 1, 0, 1}}; @@ -502,7 +494,7 @@ void GPUDisplayBackendVulkan::createDevice() auto& reqFeatures10 = reqDeviceFeatures.get().features; reqFeatures10.geometryShader = reqFeatures10.wideLines = reqFeatures10.largePoints = reqFeatures10.multiDrawIndirect = VK_TRUE; reqDeviceFeatures.get().timelineSemaphore = VK_TRUE; - reqDeviceFeatures.get().pushDescriptor = VK_TRUE; + reqDeviceFeatures.get().pushDescriptor = reqDeviceFeatures.get().maintenance5 = VK_TRUE; reqDeviceFeatures.get().dynamicRendering = reqDeviceFeatures.get().synchronization2 = VK_TRUE; reqDeviceFeatures.get().shaderDemoteToHelperInvocation = VK_TRUE; // discard in SPIR-V 1.6 reqDeviceFeatures.get().swapchainMaintenance1 = VK_TRUE; @@ -913,11 +905,11 @@ void GPUDisplayBackendVulkan::createPipeline() vk::PipelineShaderStageCreateInfo shaderStages[2] = {vk::PipelineShaderStageCreateInfo{}, vk::PipelineShaderStageCreateInfo{}}; vk::PipelineShaderStageCreateInfo& vertShaderStageInfo = shaderStages[0]; vertShaderStageInfo.stage = vk::ShaderStageFlagBits::eVertex; - // vertShaderStageInfo.module // below + // vertShaderStageInfo.pNext // below: SPIR-V vertShaderStageInfo.pName = "main"; vk::PipelineShaderStageCreateInfo& fragShaderStageInfo = shaderStages[1]; fragShaderStageInfo.stage = vk::ShaderStageFlagBits::eFragment; - // fragShaderStageInfo.module // below + // fragShaderStageInfo.pNext // below: SPIR-V fragShaderStageInfo.pName = "main"; vk::VertexInputBindingDescription bindingDescription{}; @@ -1043,8 +1035,8 @@ void GPUDisplayBackendVulkan::createPipeline() bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; - vertShaderStageInfo.module = mShaders["vertexTexture"]; - fragShaderStageInfo.module = mShaders["fragmentTexture"]; + vertShaderStageInfo.pNext = &mShaders.at("vertexTexture"); + fragShaderStageInfo.pNext = &mShaders.at("fragmentTexture"); dynamicState.dynamicStateCount = 2; pipelineInfo.layout = mPipelineLayoutTexture; renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; @@ -1056,8 +1048,8 @@ void GPUDisplayBackendVulkan::createPipeline() bindingDescription.stride = 4 * sizeof(float); attributeDescriptions.format = vk::Format::eR32G32B32A32Sfloat; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; - vertShaderStageInfo.module = mShaders["vertexTexture"]; - fragShaderStageInfo.module = mShaders["fragmentText"]; + vertShaderStageInfo.pNext = &mShaders.at("vertexTexture"); + fragShaderStageInfo.pNext = &mShaders.at("fragmentText"); dynamicState.dynamicStateCount = 2; pipelineInfo.layout = mPipelineLayoutTexture; renderingInfo.depthAttachmentFormat = vk::Format::eUndefined; @@ -1070,8 +1062,8 @@ void GPUDisplayBackendVulkan::createPipeline() const uint32_t drawIdx = i >= 5 ? i - 5 : i; inputAssembly.topology = types[drawIdx]; rasterizer.pNext = mSmoothLines && (i == 1 || i == 2) ? &smoothLineState : nullptr; - vertShaderStageInfo.module = mShaders[i < 5 ? (drawIdx == 0 ? "vertexPoint" : "vertex") : (drawIdx == 0 ? "vertexSmoothPoint" : "vertexSmoothLine")]; - fragShaderStageInfo.module = mShaders[i < 5 ? "fragment" : (drawIdx == 0 ? "fragmentSmoothPoint" : "fragmentSmoothLine")]; + 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; renderingInfo.depthAttachmentFormat = mZActive ? vk::Format::eD32Sfloat : vk::Format::eUndefined; pipelineInfo.pDepthStencilState = mZActive ? &depthStencil : nullptr; @@ -1142,9 +1134,9 @@ void GPUDisplayBackendVulkan::clearPipeline() // ---------------------------- VULKAN SHADERS ---------------------------- #define LOAD_SHADER(file, ext) \ - mShaders[#file] = createShaderModule(_binary_vulkan_##file##_##ext##_spv_start, _binary_vulkan_##file##_##ext##_spv_len, mDevice) + mShaders[#file] = vk::ShaderModuleCreateInfo { {}, _binary_vulkan_##file##_##ext##_spv_len, reinterpret_cast(_binary_vulkan_##file##_##ext##_spv_start) } -void GPUDisplayBackendVulkan::createShaders() +void GPUDisplayBackendVulkan::loadShaders() { LOAD_SHADER(vertex, vert); LOAD_SHADER(fragment, frag); @@ -1158,11 +1150,6 @@ void GPUDisplayBackendVulkan::createShaders() LOAD_SHADER(fragmentText, frag); } -void GPUDisplayBackendVulkan::clearShaders() -{ - clearVector(mShaders, [&](auto& x) { mDevice.destroyShaderModule(x.second, nullptr); }); -} - // ---------------------------- VULKAN BUFFERS ---------------------------- void GPUDisplayBackendVulkan::writeToBuffer(VulkanBuffer& buffer, size_t size, const void* srcData) @@ -1299,7 +1286,7 @@ int32_t GPUDisplayBackendVulkan::InitBackendA() mFramesInFlight = mDisplay->cfg().vulkan.nFramesInFlight; createDevice(); - createShaders(); + loadShaders(); createTextureSampler(); createSwapChain(); createOffscreenBuffers(); @@ -1325,7 +1312,6 @@ void GPUDisplayBackendVulkan::ExitBackendA() clearUniformLayoutsAndBuffers(); } clearTextureSampler(); - clearShaders(); clearDevice(); } diff --git a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h index 30244d275e24a..29d90d4e1e496 100644 --- a/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h +++ b/GPU/GPUTracking/display/backend/GPUDisplayBackendVulkan.h @@ -111,7 +111,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void updateSwapChainDetails(const vk::PhysicalDevice& device); void createDevice(); - void createShaders(); + void loadShaders(); void createTextureSampler(); void createCommandBuffers(); void createSemaphores(); @@ -120,7 +120,6 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend void createOffscreenBuffers(bool forScreenshot = false, bool forMixing = false); void createPipeline(); void clearDevice(); - void clearShaders(); void clearTextureSampler(); void clearCommandBuffers(); void clearSemaphores(); @@ -158,7 +157,7 @@ class GPUDisplayBackendVulkan : public GPUDisplayBackend std::vector mDownsampleImages; std::vector mZImages; std::vector mMixImages; - std::unordered_map mShaders; + std::unordered_map mShaders; // Embedded SPIR-V, chained into the pipeline shader stages (maintenance5, no shader modules) vk::PipelineLayout mPipelineLayout; vk::PipelineLayout mPipelineLayoutTexture; std::vector mPipelines;