From 354c119c983a7becc2fac4778e194bd47e7bff8d Mon Sep 17 00:00:00 2001 From: a-muralev-rogii Date: Mon, 20 Oct 2025 15:42:47 +0400 Subject: [PATCH 1/3] Add patches: 1. Skip GL cleanup on context loss (QTBUG-120138) 2. QWindowsScreenManager::removeScreen() - don't leave stale screen behind (QTBUG-135337) 3. Ensure validity of GL context --- rogii/apply_patches.cmake | 168 ++++++++++++++++++ rogii/build_common.cmake | 3 + rogii/patches/qtbase/0001-QTBUG-120138.patch | 43 +++++ rogii/patches/qtbase/0002-QTBUG-135337.patch | 25 +++ ...Ensure-valid-OpenGL-context-from-RHI.patch | 18 ++ 5 files changed, 257 insertions(+) create mode 100644 rogii/apply_patches.cmake create mode 100644 rogii/patches/qtbase/0001-QTBUG-120138.patch create mode 100644 rogii/patches/qtbase/0002-QTBUG-135337.patch create mode 100644 rogii/patches/qtbase/0003-Ensure-valid-OpenGL-context-from-RHI.patch diff --git a/rogii/apply_patches.cmake b/rogii/apply_patches.cmake new file mode 100644 index 00000000000..9b04bdd44e1 --- /dev/null +++ b/rogii/apply_patches.cmake @@ -0,0 +1,168 @@ +if(NOT COMMAND apply_patches) +function(apply_patches PATCH_DIR) + if(NOT PATCH_DIR) + message(FATAL_ERROR "apply_patches: first argument PATCH_DIR is required") + endif() + + # Ensure Git is available + if(NOT GIT_EXECUTABLE) + find_package(Git REQUIRED) + endif() + + if(NOT EXISTS "${PATCH_DIR}") + message(STATUS "apply_patches: patch directory not found: ${PATCH_DIR} — skipping") + return() + endif() + + # Detect repository root (top-level) using this script's location + execute_process( + COMMAND ${GIT_EXECUTABLE} -C "${CMAKE_CURRENT_LIST_DIR}" rev-parse --show-toplevel + RESULT_VARIABLE _TOPLVL_RES + OUTPUT_VARIABLE _REPO_ROOT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(NOT _TOPLVL_RES EQUAL 0 OR NOT EXISTS "${_REPO_ROOT}") + # Fallback for atypical layouts + set(_REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") + message(WARNING "apply_patches: failed to auto-detect repo root, falling back to: ${_REPO_ROOT}") + endif() + + # Gather submodules (paths relative to repo root) + execute_process( + COMMAND ${GIT_EXECUTABLE} -C "${_REPO_ROOT}" submodule status --recursive + RESULT_VARIABLE _SM_RES + OUTPUT_VARIABLE _SM_OUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + set(_SUBMODULES "") + if(_SM_RES EQUAL 0 AND _SM_OUT) + string(REPLACE "\n" ";" _SM_LINES "${_SM_OUT}") + foreach(_L IN LISTS _SM_LINES) + string(STRIP "${_L}" _LSTR) + if(_LSTR STREQUAL "") + continue() + endif() + # Format: " (branch)" + string(REGEX REPLACE "^[^ ]+ +([^ ]+).*$" "\\1" _SM_PATH "${_LSTR}") + list(APPEND _SUBMODULES "${_SM_PATH}") + endforeach() + endif() + + # Collect patches: top-level and one subdir level (e.g., qtbase/*.patch) + file(GLOB _PATCHES LIST_DIRECTORIES FALSE + "${PATCH_DIR}/*.patch" "${PATCH_DIR}/*.diff" + "${PATCH_DIR}/*/*.patch" "${PATCH_DIR}/*/*.diff" + ) + list(SORT _PATCHES) + if(NOT _PATCHES) + message(STATUS "apply_patches: no patches found in ${PATCH_DIR}") + return() + endif() + + message(STATUS "apply_patches: applying patches from ${PATCH_DIR}") + foreach(_P IN LISTS _PATCHES) + # Default: apply at repo root with -p0 + set(_TARGET_DIR "${_REPO_ROOT}") + set(_STRIP_P 0) + set(_ROUTED_BY_DIR FALSE) + + # Heuristic 1: directory-based routing (PATCH_DIR//...) + if(_SUBMODULES) + file(RELATIVE_PATH _REL_UNDER_PATCHDIR "${PATCH_DIR}" "${_P}") + string(REGEX MATCH "^[^/\\]+" _FIRST_COMP "${_REL_UNDER_PATCHDIR}") + foreach(_SM IN LISTS _SUBMODULES) + if(_FIRST_COMP STREQUAL "${_SM}") + set(_TARGET_DIR "${_REPO_ROOT}/${_SM}") + # Usually patch files placed under / are already relative to that submodule (no extra leading '/'), + # so -p0 is typically correct. Leave _STRIP_P=0 here. + set(_ROUTED_BY_DIR TRUE) + break() + endif() + endforeach() + endif() + + # Heuristic 2: parse patch header to detect submodule and decide strip + if(NOT _ROUTED_BY_DIR AND _SUBMODULES) + file(READ "${_P}" _PATCH_CONTENT LIMIT 8192) # header is small + # Try to capture '+++ b//' (or '--- a//') + set(_HDR_MATCH "") + string(REGEX MATCH "^[+-]{3} [ab]/([^/\n\r]+)/" _HDR_MATCH "${_PATCH_CONTENT}") + if(NOT _HDR_MATCH STREQUAL "") + string(REGEX REPLACE "^[+-]{3} [ab]/([^/\n\r]+)/.*$" "\\1" _HDR_TOP "${_HDR_MATCH}") + foreach(_SM IN LISTS _SUBMODULES) + if(_HDR_TOP STREQUAL "${_SM}") + set(_TARGET_DIR "${_REPO_ROOT}/${_SM}") + # Header path starts with '/', so when applying *inside* the submodule, + # we need to drop that leading component -> use -p1. + set(_STRIP_P 1) + break() + endif() + endforeach() + endif() + endif() + + message(STATUS "→ ${_P} (target: ${_TARGET_DIR}, -p${_STRIP_P})") + + # Try two strip levels for robustness: primary then fallback + set(_TRY_P_LIST "${_STRIP_P}") + if(_STRIP_P EQUAL 0) + list(APPEND _TRY_P_LIST 1) + else() + list(APPEND _TRY_P_LIST 0) + endif() + + set(_APPLIED FALSE) + foreach(_TRY_P IN LISTS _TRY_P_LIST) + if(_TRY_P GREATER 0) + execute_process( + COMMAND ${GIT_EXECUTABLE} -C "${_TARGET_DIR}" apply -p${_TRY_P} --3way --whitespace=fix --ignore-space-change "${_P}" + RESULT_VARIABLE _APPLY_RES + OUTPUT_VARIABLE _APPLY_OUT + ERROR_VARIABLE _APPLY_ERR + ) + else() + execute_process( + COMMAND ${GIT_EXECUTABLE} -C "${_TARGET_DIR}" apply --3way --whitespace=fix --ignore-space-change "${_P}" + RESULT_VARIABLE _APPLY_RES + OUTPUT_VARIABLE _APPLY_OUT + ERROR_VARIABLE _APPLY_ERR + ) + endif() + + if(_APPLY_RES EQUAL 0) + set(_APPLIED TRUE) + message(STATUS " applied with -p${_TRY_P}") + break() + endif() + + # Idempotency check: already applied? + if(_TRY_P GREATER 0) + execute_process( + COMMAND ${GIT_EXECUTABLE} -C "${_TARGET_DIR}" apply -p${_TRY_P} --reverse --check "${_P}" + RESULT_VARIABLE _REV_OK + OUTPUT_QUIET ERROR_QUIET + ) + else() + execute_process( + COMMAND ${GIT_EXECUTABLE} -C "${_TARGET_DIR}" apply --reverse --check "${_P}" + RESULT_VARIABLE _REV_OK + OUTPUT_QUIET ERROR_QUIET + ) + endif() + + if(_REV_OK EQUAL 0) + set(_APPLIED TRUE) + message(STATUS " (already applied) skipping (detected with -p${_TRY_P})") + break() + endif() + endforeach() + + if(NOT _APPLIED) + message(FATAL_ERROR + "apply_patches: failed to apply:\n ${_P}\n${_APPLY_ERR}\n(target: ${_TARGET_DIR})") + endif() + endforeach() +endfunction() +endif() diff --git a/rogii/build_common.cmake b/rogii/build_common.cmake index 482919c943f..c3ff227b167 100644 --- a/rogii/build_common.cmake +++ b/rogii/build_common.cmake @@ -124,6 +124,9 @@ if(NOT INIT_REPOSITORY_RESULT EQUAL 0) ) endif() +include("${CMAKE_CURRENT_LIST_DIR}/../rogii/apply_patches.cmake") +apply_patches("${CMAKE_CURRENT_LIST_DIR}/../rogii/patches") + set( QT_SUFFIX "Rogii" diff --git a/rogii/patches/qtbase/0001-QTBUG-120138.patch b/rogii/patches/qtbase/0001-QTBUG-120138.patch new file mode 100644 index 00000000000..ff4b2ac04a2 --- /dev/null +++ b/rogii/patches/qtbase/0001-QTBUG-120138.patch @@ -0,0 +1,43 @@ + + +diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp +index ea6dfdac3b0..b0bae35d920 100644 +--- a/src/gui/rhi/qrhigles2.cpp ++++ b/src/gui/rhi/qrhigles2.cpp +@@ -1116,22 +1116,23 @@ void QRhiGles2::destroy() + if (!f) + return; + +- ensureContext(); +- executeDeferredReleases(); ++ if (ensureContext()) { ++ executeDeferredReleases(); + +- if (ofr.tsQueries[0]) { +- f->glDeleteQueries(2, ofr.tsQueries); +- ofr.tsQueries[0] = ofr.tsQueries[1] = 0; +- } ++ if (ofr.tsQueries[0]) { ++ f->glDeleteQueries(2, ofr.tsQueries); ++ ofr.tsQueries[0] = ofr.tsQueries[1] = 0; ++ } + +- if (vao) { +- f->glDeleteVertexArrays(1, &vao); +- vao = 0; +- } ++ if (vao) { ++ f->glDeleteVertexArrays(1, &vao); ++ vao = 0; ++ } + +- for (uint shader : m_shaderCache) +- f->glDeleteShader(shader); +- m_shaderCache.clear(); ++ for (uint shader : m_shaderCache) ++ f->glDeleteShader(shader); ++ m_shaderCache.clear(); ++ } + + if (!importedContext) { + delete ctx; diff --git a/rogii/patches/qtbase/0002-QTBUG-135337.patch b/rogii/patches/qtbase/0002-QTBUG-135337.patch new file mode 100644 index 00000000000..45e348f878e --- /dev/null +++ b/rogii/patches/qtbase/0002-QTBUG-135337.patch @@ -0,0 +1,25 @@ + + +diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp +index 6c86c47caac..ddc1114485e 100644 +--- a/src/plugins/platforms/windows/qwindowsscreen.cpp ++++ b/src/plugins/platforms/windows/qwindowsscreen.cpp +@@ -765,7 +765,8 @@ static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen) + void QWindowsScreenManager::removeScreen(int index) + { + qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data(); +- QScreen *screen = m_screens.at(index)->screen(); ++ QPlatformScreen *platformScreen = m_screens.takeAt(index); ++ QScreen *screen = platformScreen->screen(); + QScreen *primaryScreen = QGuiApplication::primaryScreen(); + // QTBUG-38650: When a screen is disconnected, Windows will automatically + // move the Window to another screen. This will trigger a geometry change +@@ -791,7 +792,7 @@ void QWindowsScreenManager::removeScreen(int index) + if (movedWindowCount) + QWindowSystemInterface::flushWindowSystemEvents(); + } +- QWindowSystemInterface::handleScreenRemoved(m_screens.takeAt(index)); ++ QWindowSystemInterface::handleScreenRemoved(platformScreen); + } + + /*! diff --git a/rogii/patches/qtbase/0003-Ensure-valid-OpenGL-context-from-RHI.patch b/rogii/patches/qtbase/0003-Ensure-valid-OpenGL-context-from-RHI.patch new file mode 100644 index 00000000000..b87e09dd1ca --- /dev/null +++ b/rogii/patches/qtbase/0003-Ensure-valid-OpenGL-context-from-RHI.patch @@ -0,0 +1,18 @@ + + +diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp +index ea6dfdac3b0..826cbe9839b 100644 +--- a/src/gui/rhi/qrhigles2.cpp ++++ b/src/gui/rhi/qrhigles2.cpp +@@ -627,6 +627,11 @@ QSurface *QRhiGles2::evaluateFallbackSurface() const + + bool QRhiGles2::ensureContext(QSurface *surface) const + { ++ if (!ctx->isValid()) { ++ contextLost = true; ++ return false; ++ } ++ + if (!surface) { + // null means any surface is good because not going to render + if (currentSurfaceForCurrentContext(ctx)) From b96015cca96f1d043e3be5ceafb195d1ea9b867c Mon Sep 17 00:00:00 2001 From: a-muralev-rogii Date: Fri, 24 Oct 2025 22:07:38 +0400 Subject: [PATCH 2/3] Patch to fix crash at QQuickWidget by changing screen --- ...004-Fix-iterate-on-destroyed-windows.patch | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch diff --git a/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch b/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch new file mode 100644 index 00000000000..52caa35890b --- /dev/null +++ b/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch @@ -0,0 +1,35 @@ + + +diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp +index 8e14ee09efc..14e6e5c93d1 100644 +--- a/src/gui/kernel/qwindowsysteminterface.cpp ++++ b/src/gui/kernel/qwindowsysteminterface.cpp +@@ -16,6 +16,8 @@ + #include + #include + ++#include ++ + #if QT_CONFIG(draganddrop) + #include + #endif +@@ -760,9 +762,16 @@ void QWindowSystemInterface::handleScreenRemoved(QPlatformScreen *platformScreen + && newPrimaryScreen->handle()->virtualSiblings().contains(platformScreen); + + // Move any leftover windows to the primary screen +- const auto allWindows = QGuiApplication::allWindows(); +- for (QWindow *window : allWindows) { +- if (!window->isTopLevel() || window->screen() != screen) ++ const auto & allWindowRawPtrs = QGuiApplication::allWindows(); ++ ++ std::vector> allWindowPointers; ++ allWindowPointers.reserve(allWindowRawPtrs.size()); ++ ++ for (QWindow *rawWindowPtr : allWindowRawPtrs) ++ allWindowPointers.emplace_back(rawWindowPtr); ++ ++ for (auto window : allWindowPointers) { ++ if (!window || !window->isTopLevel() || window->screen() != screen) + continue; + + const bool wasVisible = window->isVisible(); From 97ff8c648e2b5262469cfbee9dedaecda873375c Mon Sep 17 00:00:00 2001 From: a-muralev-rogii Date: Mon, 27 Oct 2025 14:03:49 +0400 Subject: [PATCH 3/3] Extend patch --- ...004-Fix-iterate-on-destroyed-windows.patch | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch b/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch index 52caa35890b..7bcb54be228 100644 --- a/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch +++ b/rogii/patches/qtbase/0004-Fix-iterate-on-destroyed-windows.patch @@ -1,7 +1,5 @@ - - diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp -index 8e14ee09efc..14e6e5c93d1 100644 +index 8e14ee09efc..1e83c71284d 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -16,6 +16,8 @@ @@ -28,8 +26,39 @@ index 8e14ee09efc..14e6e5c93d1 100644 + for (QWindow *rawWindowPtr : allWindowRawPtrs) + allWindowPointers.emplace_back(rawWindowPtr); + -+ for (auto window : allWindowPointers) { ++ for (const auto & window : allWindowPointers) { + if (!window || !window->isTopLevel() || window->screen() != screen) continue; const bool wasVisible = window->isVisible(); +diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp +index 277c234e339..fe3767fff62 100644 +--- a/src/plugins/platforms/windows/qwindowsscreen.cpp ++++ b/src/plugins/platforms/windows/qwindowsscreen.cpp +@@ -27,6 +27,7 @@ + + #include + #include ++#include + + #include + #include +@@ -778,7 +779,17 @@ void QWindowsScreenManager::removeScreen(int index) + if (screen != primaryScreen) { + unsigned movedWindowCount = 0; + const QWindowList tlws = QGuiApplication::topLevelWindows(); +- for (QWindow *w : tlws) { ++ ++ std::vector> tlwPointers; ++ tlwPointers.reserve(tlws.size()); ++ ++ for (QWindow *rawWindowPtr : tlws) ++ tlwPointers.emplace_back(rawWindowPtr); ++ ++ for (const auto & w : tlwPointers) { ++ if (!w) ++ continue; ++ + if (w->screen() == screen && w->handle() && w->type() != Qt::Desktop) { + if (w->isVisible() && w->windowState() != Qt::WindowMinimized + && (QWindowsWindow::baseWindowOf(w)->exStyle() & WS_EX_TOOLWINDOW)) {