https://bugs.kde.org/show_bug.cgi?id=414266
--- Comment #6 from nyanpasu64 <nyanpas...@tuta.io> --- Created attachment 134836 --> https://bugs.kde.org/attachment.cgi?id=134836&action=edit Video of a Qt Quick 2 button glitching when resized, with patched qqc2-desktop-style I've done a deep-dive investigation, looking into multiple issues and Qt Quick design defects causing this bug's symptoms to occur. For reference, the C++ implementation of the theme is at https://invent.kde.org/frameworks/qqc2-desktop-style/-/blob/7cdeaab75171b4126ba9026875539108a834a577/plugin/kquickstyleitem.cpp. ## Background Qt Quick 2 positions and sizes items in device-independent pixels. `KQuickStyleItem` (defined by qqc2-desktop-style in the above link) is a subclass of `QQuickItem` used to render widgets and output them on-screen. In simple QQC2 apps (like System Settings panels), the position and size of each item are integers in device-independent pixels. Perhaps in more complex apps, the widgets could be translated or rotated, making them no longer integers. When `KQuickStyleItem::updatePolish()` is called, it renders the widget using `QPainter`/`QStyle` onto a member-variable `QImage m_image`. (Note that this is an abuse of updatePolish() which is intended to relayout and not redraw.) When `KQuickStyleItem::updatePaintNode()` is called, it uploads the `QImage` to a GPU texture, and outputs a scene graph item (`QSGNinePatchNode *` upcasted to `QSGNode *`) pointing to the texture. Qt Quick 2 turns it into a rectangle (two tris) and sends the mesh/texture to OpenGL. The size and position of the `KQuickStyleItem`/`QQuickItem` is not determined by `KQuickStyleItem::updatePaintNode()` or `KQuickStyleItem::updatePolish()`, but merely consumed. `QQuickItem::boundingRect()` is an undocumented method used in `KQuickStyleItem::updatePaintNode()` (and assumed to match `width()`/`height()` which is what `updatePolish()` uses). Based on my debug prints, `boundingRect()` returns a rectangle where the top-left lies at (0, 0), even if the item does not lie at the window's origin. You need to use `mapToGlobal()` to find the true position in the window. By running RenderDoc on QQC2 apps (kcm_fonts and a single-button test app), I found that the rectangle's coordinates are sent to OpenGL as device-independent pixels (`qt_VertexPosition`), and the vertex shader transforms them into `gl_Position` coordinates. Then it samples the texture using nearest-neighbor interpolation, even if you modify the `createTextureFromImage()` value by calling `setFiltering(QSGTexture::Linear)`. I was unable to get RenderDoc to replay the trace with linear filtering enabled. ## Bug There are multiple layers of bugs: 1. With DPI display scaling enabled, the polygon drawn on the GPU, as well as the region used by mouse handling, no longer lies on integer physical pixels. The visual polygon's size may not match the QImage's size. - I "fixed" this by having `KQuickStyleItem::updatePaintNode()` return a scene-graph node which matches the QImage's size and not the `QQuickItem`'s size (a). 2. Even if the visual polygon's size is an integer number of physical pixels, the x or y coordinates can lie on a 0.5-pixel boundary. This causes rounding errors and sometimes results in the "diagonal seam" effect (and might also cause duplicated/missing rows/columns) where the two triangles of the rectangle round their boundaries and texture coordinates differently. With `QQuickWindow::TextureCanUseAtlas` in place, the diagonal seam effect is inconsistent, doesn't appear in all buttons, and can appear/disappear when you hover a button (because many `KQuickStyleItem`s are placed onto a single texture uploaded to the GPU, resulting in inconsistent rounding). With that parameter removed (b), the effect is consistent and appears in all buttons with the same x or y endpoints, in both hovered/unhovered states. - I recorded and attached a video demonstrating this bug occurring when I resize a window, with a patched version of qqc2-desktop-style which sets the returned node's size to the QImage's size in physical pixels (a) and also disables Qt's texture atlas (b). I saw both duplicated rows/columns of pixels and diagonal seams. - I don't know the best fix for this. I had a branch where `KQuickStyleItem::updatePaintNode()` used `mapToGlobal` and `mapFromGlobal` to quantize the output polygon to global physical pixels (c). However, this method is only called when a widget is hovered, not upon window resize (which also changes the widget's position and affects pixel rounding), so it's not a workable solution. Worse yet, this solution fails completely if you rotate a widget (but I suspect the current qqc2 theme had the same behavior). Also I was not able to find a way to enable linear filtering for `KQuickStyleItem`'s returned paint node, which would cause non-integer nodes/items to be blurry and ugly, but obviously stand out and not have diagonal/horizontal/vertical seams. 3. I made a test QQC2 app, and resized the window interior to 110x42 physical pixels (at 125% display scale). The vertex shader's transformation matrix (`qt_Matrix`) is used to map device-independent pixels (`qt_VertexPosition`) to OpenGL coordinates. However the matrix's vertical transform is wrong because it thinks the output texture size is 110 x 42.5 physical pixels (corresponding to 34 device-independent pixels, an integer). This causes missing and/or duplicate rows of pixels, even on my branch where I implemented a partial fix for Issue 2 (c) . - I think this is a Qt bug, not a KDE theme bug. I have a private Git repo with my attempted bugfixes. Unfortunately it's a `git init` repo based off Arch's `qqc2-desktop-style-5.78.0` package, not a clone of the upstream source. I can publish my repo if anyone is interested. I have no desire to work on a Qt fix for Bug 3 (`qt_Matrix`), since Qt 5.15 is no longer receiving public or open-source updates. I may investigate further if Kirigami or qqc2-desktop-style ports to Qt 6 (which still has source code for the time being), or if KDE/etc. forks Qt 5.15. I don't know how to fix Bug 2 (non-integer positions and rounding errors) because I don't know how to recompute the scene node's position whenever the QML item is moved (eg. by the layout). Neither updatePolish() nor updatePaintNode() are called when I resize the window. Additionally I don't know if there's any interest in fixing this bug. I don't know how many people use fractional scaling on KDE X11 (or Wayland if the bug occurs there as well). Those people are going to run into this bug more and more, as KDE ports system settings panels (and possibly other apps) to QML. -- You are receiving this mail because: You are watching all bug changes.