Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsgareaallocator.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/qglobal.h>
7#include <QtCore/qrect.h>
8#include <QtCore/qpoint.h>
9#include <QtCore/qdatastream.h>
10#include <QtCore/qstack.h>
11#include <QtCore/qendian.h>
12#include <QtCore/qvarlengtharray.h>
13
15
21
22static const int maxMargin = 2;
23
37
39 : parent(parent)
40 , left(nullptr)
41 , right(nullptr)
42 , isOccupied(false)
43{
44}
45
47{
48 delete left;
49 delete right;
50}
51
53{
54 Q_ASSERT((left != nullptr) == (right != nullptr));
55 return !left;
56}
57
58
59// If sliceHeight is set, the area is treated as a vertical stack of slices of
60// that height, and no allocation will cross the boundary between two slices.
61QSGAreaAllocator::QSGAreaAllocator(const QSize &size, int sliceHeight)
62 : m_size(size)
63 , m_sliceHeight(sliceHeight)
64{
65 m_root = new QSGAreaAllocatorNode(nullptr);
66}
67
68QSGAreaAllocator::~QSGAreaAllocator()
69{
70 delete m_root;
71}
72
73QRect QSGAreaAllocator::allocate(const QSize &size)
74{
75 QPoint point;
76 bool result = allocateInNode(size, point, QRect(QPoint(0, 0), m_size), m_root);
77 return result ? QRect(point, size) : QRect();
78}
79
80bool QSGAreaAllocator::deallocate(const QRect &rect)
81{
82 return deallocateInNode(rect.topLeft(), m_root);
83}
84
85bool QSGAreaAllocator::allocateInNode(const QSize &size, QPoint &result, const QRect &currentRect, QSGAreaAllocatorNode *node)
86{
87 if (size.width() > currentRect.width() || size.height() > currentRect.height())
88 return false;
89
90 if (node->isLeaf()) {
91 if (node->isOccupied)
92 return false;
93 if (m_sliceHeight > 0) {
94 // Does this leaf contain the slice boundary? In that case, we pre-split it so
95 // that we never get an allocation that crosses two slices.
96 const int boundary = (currentRect.top() / m_sliceHeight + 1) * m_sliceHeight;
97 if (boundary <= currentRect.bottom()) {
98 node->left = new QSGAreaAllocatorNode(node);
99 node->right = new QSGAreaAllocatorNode(node);
100 node->splitType = HorizontalSplit;
101 node->split = boundary;
102
103 // Re-call function with same arguments now that node is no longer leaf to
104 // trigger the search.
105 return allocateInNode(size, result, currentRect, node);
106 }
107 }
108 if (size.width() + maxMargin >= currentRect.width() && size.height() + maxMargin >= currentRect.height()) {
109 //Snug fit, occupy entire rectangle.
110 node->isOccupied = true;
111 result = currentRect.topLeft();
112 return true;
113 }
114 // TODO: Reuse nodes.
115 // Split node.
116 node->left = new QSGAreaAllocatorNode(node);
117 node->right = new QSGAreaAllocatorNode(node);
118 QRect splitRect = currentRect;
119 if ((currentRect.width() - size.width()) * currentRect.height() < (currentRect.height() - size.height()) * currentRect.width()) {
120 node->splitType = HorizontalSplit;
121 node->split = currentRect.top() + size.height();
122 splitRect.setHeight(size.height());
123 } else {
124 node->splitType = VerticalSplit;
125 node->split = currentRect.left() + size.width();
126 splitRect.setWidth(size.width());
127 }
128 return allocateInNode(size, result, splitRect, node->left);
129 } else {
130 // TODO: avoid unnecessary recursion.
131 // has been split.
132 QRect leftRect = currentRect;
133 QRect rightRect = currentRect;
134 if (node->splitType == HorizontalSplit) {
135 leftRect.setHeight(node->split - leftRect.top());
136 rightRect.setTop(node->split);
137 } else {
138 leftRect.setWidth(node->split - leftRect.left());
139 rightRect.setLeft(node->split);
140 }
141 if (allocateInNode(size, result, leftRect, node->left))
142 return true;
143 if (allocateInNode(size, result, rightRect, node->right))
144 return true;
145 return false;
146 }
147}
148
149bool QSGAreaAllocator::deallocateInNode(const QPoint &pos, QSGAreaAllocatorNode *node)
150{
151 while (!node->isLeaf()) {
152 // has been split.
153 int cmp = node->splitType == HorizontalSplit ? pos.y() : pos.x();
154 node = cmp < node->split ? node->left : node->right;
155 }
156 if (!node->isOccupied)
157 return false;
158 node->isOccupied = false;
159 mergeNodeWithNeighbors(node);
160 return true;
161}
162
163void QSGAreaAllocator::mergeNodeWithNeighbors(QSGAreaAllocatorNode *node)
164{
165 bool done = false;
166 QSGAreaAllocatorNode *parent = nullptr;
167 QSGAreaAllocatorNode *current = nullptr;
168 QSGAreaAllocatorNode *sibling;
169 while (!done) {
170 Q_ASSERT(node->isLeaf());
171 Q_ASSERT(!node->isOccupied);
172 if (node->parent == nullptr)
173 return; // No neighbours.
174
175 SplitType splitType = SplitType(node->parent->splitType);
176 done = true;
177
178 /* Special case. Might be faster than going through the general code path.
179 // Merge with sibling.
180 parent = node->parent;
181 sibling = (node == parent->left ? parent->right : parent->left);
182 if (sibling->isLeaf() && !sibling->isOccupied) {
183 Q_ASSERT(!sibling->right);
184 node = parent;
185 parent->isOccupied = false;
186 delete parent->left;
187 delete parent->right;
188 parent->left = parent->right = 0;
189 done = false;
190 continue;
191 }
192 */
193
194 // Merge with left neighbour.
195 current = node;
196 parent = current->parent;
197 while (parent && current == parent->left && parent->splitType == splitType) {
198 current = parent;
199 parent = parent->parent;
200 }
201
202 if (parent && parent->splitType == splitType) {
203 Q_ASSERT(current == parent->right);
204 Q_ASSERT(parent->left);
205
206 QSGAreaAllocatorNode *neighbor = parent->left;
207 while (neighbor->right && neighbor->splitType == splitType)
208 neighbor = neighbor->right;
209
210 if (neighbor->isLeaf() && neighbor->parent->splitType == splitType && !neighbor->isOccupied) {
211 // Left neighbour can be merged.
212 parent->split = neighbor->parent->split;
213
214 parent = neighbor->parent;
215 sibling = neighbor == parent->left ? parent->right : parent->left;
216 QSGAreaAllocatorNode **nodeRef = &m_root;
217 if (parent->parent) {
218 if (parent == parent->parent->left)
219 nodeRef = &parent->parent->left;
220 else
221 nodeRef = &parent->parent->right;
222 }
223 sibling->parent = parent->parent;
224 *nodeRef = sibling;
225 parent->left = parent->right = nullptr;
226 delete parent;
227 delete neighbor;
228 done = false;
229 }
230 }
231
232 // Merge with right neighbour.
233 current = node;
234 parent = current->parent;
235 while (parent && current == parent->right && parent->splitType == splitType) {
236 current = parent;
237 parent = parent->parent;
238 }
239
240 if (parent && parent->splitType == splitType) {
241 Q_ASSERT(current == parent->left);
242 Q_ASSERT(parent->right);
243
244 QSGAreaAllocatorNode *neighbor = parent->right;
245 while (neighbor->left && neighbor->splitType == splitType)
246 neighbor = neighbor->left;
247
248 if (neighbor->isLeaf() && neighbor->parent->splitType == splitType && !neighbor->isOccupied) {
249 // Right neighbour can be merged.
250 parent->split = neighbor->parent->split;
251
252 parent = neighbor->parent;
253 sibling = neighbor == parent->left ? parent->right : parent->left;
254 QSGAreaAllocatorNode **nodeRef = &m_root;
255 if (parent->parent) {
256 if (parent == parent->parent->left)
257 nodeRef = &parent->parent->left;
258 else
259 nodeRef = &parent->parent->right;
260 }
261 sibling->parent = parent->parent;
262 *nodeRef = sibling;
263 parent->left = parent->right = nullptr;
264 delete parent;
265 delete neighbor;
266 done = false;
267 }
268 }
269 } // end while(!done)
270}
271
272namespace {
273 struct AreaAllocatorTable
274 {
275 enum TableSize {
276 HeaderSize = 10,
277 NodeSize = 9
278 };
279
280 enum Offset {
281 // Header
282 majorVersion = 0,
283 minorVersion = 1,
284 width = 2,
285 height = 6,
286
287 // Node
288 split = 0,
289 splitType = 4,
290 flags = 8
291 };
292
293 enum Flags {
294 IsOccupied = 1,
295 HasLeft = 2,
296 HasRight = 4
297 };
298
299 template <typename T>
300 static inline T fetch(const char *data, Offset offset)
301 {
302 return qFromBigEndian<T>(data + int(offset));
303 }
304
305 template <typename T>
306 static inline void put(char *data, Offset offset, T value)
307 {
308 qToBigEndian(value, data + int(offset));
309 }
310 };
311}
312
313QByteArray QSGAreaAllocator::serialize()
314{
315 QVarLengthArray<QSGAreaAllocatorNode *> nodesToProcess;
316
317 QStack<QSGAreaAllocatorNode *> nodes;
318 nodes.push(m_root);
319 while (!nodes.isEmpty()) {
320 QSGAreaAllocatorNode *node = nodes.pop();
321
322 nodesToProcess.append(node);
323 if (node->left != nullptr)
324 nodes.push(node->left);
325 if (node->right != nullptr)
326 nodes.push(node->right);
327 }
328
329 QByteArray ret;
330 ret.resize(AreaAllocatorTable::HeaderSize + AreaAllocatorTable::NodeSize * nodesToProcess.size());
331
332 char *data = ret.data();
333 AreaAllocatorTable::put(data, AreaAllocatorTable::majorVersion, quint8(5));
334 AreaAllocatorTable::put(data, AreaAllocatorTable::minorVersion, quint8(12));
335 AreaAllocatorTable::put(data, AreaAllocatorTable::width, quint32(m_size.width()));
336 AreaAllocatorTable::put(data, AreaAllocatorTable::height, quint32(m_size.height()));
337
338 data += AreaAllocatorTable::HeaderSize;
339 for (QSGAreaAllocatorNode *node : nodesToProcess) {
340 AreaAllocatorTable::put(data, AreaAllocatorTable::split, qint32(node->split));
341 AreaAllocatorTable::put(data, AreaAllocatorTable::splitType, quint32(node->splitType));
342
343 quint8 flags =
344 (node->isOccupied ? AreaAllocatorTable::IsOccupied : 0)
345 | (node->left != nullptr ? AreaAllocatorTable::HasLeft : 0)
346 | (node->right != nullptr ? AreaAllocatorTable::HasRight : 0);
347 AreaAllocatorTable::put(data, AreaAllocatorTable::flags, flags);
348 data += AreaAllocatorTable::NodeSize;
349 }
350
351 return ret;
352}
353
354const char *QSGAreaAllocator::deserialize(const char *data, int size)
355{
356 if (uint(size) < AreaAllocatorTable::HeaderSize) {
357 qWarning("QSGAreaAllocator::deserialize: Data not long enough to fit header");
358 return nullptr;
359 }
360
361 const char *end = data + size;
362
363 quint8 majorVersion = AreaAllocatorTable::fetch<quint8>(data, AreaAllocatorTable::majorVersion);
364 quint8 minorVersion = AreaAllocatorTable::fetch<quint8>(data, AreaAllocatorTable::minorVersion);
365 if (majorVersion != 5 || minorVersion != 12) {
366 qWarning("Unrecognized version %d.%d of QSGAreaAllocator",
367 majorVersion,
368 minorVersion);
369 return nullptr;
370 }
371
372 m_size = QSize(AreaAllocatorTable::fetch<quint32>(data, AreaAllocatorTable::width),
373 AreaAllocatorTable::fetch<quint32>(data, AreaAllocatorTable::height));
374
375 Q_ASSERT(m_root != nullptr);
376 Q_ASSERT(m_root->left == nullptr);
377 Q_ASSERT(m_root->right == nullptr);
378
379 QStack<QSGAreaAllocatorNode *> nodes;
380 nodes.push(m_root);
381
382 data += AreaAllocatorTable::HeaderSize;
383 while (!nodes.isEmpty()) {
384 if (data + AreaAllocatorTable::NodeSize > end) {
385 qWarning("QSGAreaAllocator::deseriable: Data not long enough for nodes");
386 return nullptr;
387 }
388
389 QSGAreaAllocatorNode *node = nodes.pop();
390
391 node->split = AreaAllocatorTable::fetch<qint32>(data, AreaAllocatorTable::split);
392 node->splitType = SplitType(AreaAllocatorTable::fetch<quint32>(data, AreaAllocatorTable::splitType));
393
394 quint8 flags = AreaAllocatorTable::fetch<quint8>(data, AreaAllocatorTable::flags);
395 node->isOccupied = flags & AreaAllocatorTable::IsOccupied;
396
397 if (flags & AreaAllocatorTable::HasLeft) {
398 node->left = new QSGAreaAllocatorNode(node);
399 nodes.push(node->left);
400 }
401
402 if (flags & AreaAllocatorTable::HasRight) {
403 node->right = new QSGAreaAllocatorNode(node);
404 nodes.push(node->right);
405 }
406
407 data += AreaAllocatorTable::NodeSize;
408 }
409
410 return data;
411}
412
413QT_END_NAMESPACE
Combined button and popup list for selecting options.
static const int maxMargin
@ HorizontalSplit
@ VerticalSplit
QSGAreaAllocatorNode(QSGAreaAllocatorNode *parent)
QSGAreaAllocatorNode * parent
QSGAreaAllocatorNode * left
QSGAreaAllocatorNode * right