Details
-
Bug
-
Resolution: Done
-
P1: Critical
-
5.15.2, 6.0.1
-
-
74c458f9fdf0639cd68684b5184bf561166e14cb (qt/qtdeclarative/dev) af29de3efc4e6615123e12a0305565a57e90978e (qt/qtdeclarative/6.0) 8050979c10e485166bd30ccc929140e4b66e84b5 (qt/qtdeclarative/6.1) c7c2512a67 (qt/tqtc-qtdeclarative/5.15-opensource)
Description
I created a custom QQuickItem type to do line drawings using QSGGeometryNode. When lines intersect, the alpha batching in the Renderer class can incorrectly groups the lines and draw them out of order. This results in items with a higher z-order being rendered behind items with a lower z-order. See attached images.
The QSG_RENDERER_DEBUG=render output indicates that batching is the problem here. For the non-intersection case, there are two batches. This makes sense since we have 4 lines with different line widths.
Batch thresholds: nodes: 64 vertices: 1024 Renderer::render() QSGAbstractRenderer(0x204ffa06ba0) "rebuild: full"
Rendering:
-> Opaque: 0 nodes in 0 batches...
-> Alpha: 4 nodes in 2 batches...
- 0x204ffc988e0 [ upload] [noclip] [ alpha] [ merged] Nodes: 2 Vertices: 4 Indices: 4 root: 0x0 opacity: 1
- 0x204ffc974b0 [ upload] [noclip] [ alpha] [ merged] Nodes: 2 Vertices: 4 Indices: 4 root: 0x0 opacity: 1
-> times: build: 0, prepare(opaque/alpha): 0/0, sorting: 0, upload(opaque/alpha): 0/0, record rendering: 4
For the intersection case, there are 3 batches instead of the expected 4. This is because the two red lines get batched together.
Batch thresholds: nodes: 64 vertices: 1024 Renderer::render() QSGAbstractRenderer(0x1a117515f00) "rebuild: full"
Rendering:
-> Opaque: 0 nodes in 0 batches...
-> Alpha: 4 nodes in 3 batches...
- 0x1a1175142d0 [ upload] [noclip] [ alpha] [ merged] Nodes: 1 Vertices: 2 Indices: 2 root: 0x0 opacity: 1
- 0x1a117514660 [ upload] [noclip] [ alpha] [ merged] Nodes: 2 Vertices: 4 Indices: 4 root: 0x0 opacity: 1
- 0x1a117515a90 [ upload] [noclip] [ alpha] [ merged] Nodes: 1 Vertices: 2 Indices: 2 root: 0x0 opacity: 1
-> times: build: 0, prepare(opaque/alpha): 0/0, sorting: 0, upload(opaque/alpha): 0/0, record rendering: 3
Based on reviewing the code in woboq for Qt 5.15, qsgbatchrenderer.cpp in Renderer::prepareAlphaBatches, I think I found the issue in the logic used to detect intersection on ln1941:
if (!overlapBounds.intersects(ej->bounds) || !checkOverlap(i+1, j - 1, ej->bounds)) {
...
} ...
The Rect::intersects function used does not accommodate lines that are on top of each other. If the two lines have the same point1 and point2, the intersection logic will return FALSE instead of TRUE. This is due to the use of <,> rather than <=, >=
bool intersects(const Rect &r) { bool xOverlap = r.tl.x < br.x && r.br.x > tl.x; bool yOverlap = r.tl.y < br.y && r.br.y > tl.y; return xOverlap && yOverlap; }
This results in the following happening:
- The 4 qquickitems are decomposed in to the 4 QSGGeometry nodes
- Alpha batching runs on the 4 nodes
- Node1 has color=black, thickness=10 (vertical)
- Node2 has color=red, thickness=6 (vertical)
- Node3 has color=black, thickness=10 (horizontal)
- Node4 has color=red, thickness=6 (horizontal)
- Batch1 starts with Node1
- Node1 cannot batch with Node2 due to line width different.
- The overlap bounds is set to Node2's bounds
- Node2 and Node3 intersect so the batch is ended.
- Result: Batch1 only has Node1.
- Batch2 starts with Node2
- Node2 cannot batch with Node3 due to line width difference
- The overlap bounds is set to Node3's bounds
- Due to the intersection logic, Node3 and Node4 do not overlap even though they're coincident.
- Node4 is added to the batch.
- Result: Batch2 has Node2 and Node4
- Batch3 starts with Node3
- Node3 is the last available node
- Result: Batch3 has Node3
When the lines don't intersect, the batches end up as:
- Batch1: Node1, Node3
- Batch2: Node2, Node4
I gathered this all from code inspection and without all the context surrounding the code. My apologies if I misunderstood what's occurring and/or the intent.
Problem is reproducible in Qt5 and Qt6. I only tested on windows, but suspect this will be the case on all platforms. For Qt6, I had to use QSG_RHI_BACKEND=opengl to be able to set the line width.