1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the plugins of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE)
44 #include "qxcbsharedbuffermanager.h"
46 #include <QtCore/quuid.h>
47 #include <QtGui/qimage.h>
51 #if !defined(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED)
52 # define SHAREDGRAPHICSCACHE_MAX_MEMORY_USED 16 * 1024 * 1024 // 16 MB limit
55 #if !defined(SHAREDGRAPHICSCACHE_MAX_TEXTURES_PER_CACHE)
56 # define SHAREDGRAPHICSCACHE_MAX_TEXTURES_PER_CACHE 1
59 #if !defined(SHAREDGRAPHICSCACHE_TEXTURE_SIZE)
60 # define SHAREDGRAPHICSCACHE_TEXTURE_SIZE 2048
63 #define SHAREDBUFFERMANAGER_DEBUG 1
67 QXcbSharedBufferManager::QXcbSharedBufferManager()
69 , m_mostRecentlyUsed(0)
70 , m_leastRecentlyUsed(0)
74 QXcbSharedBufferManager::~QXcbSharedBufferManager()
77 QHash<QByteArray, Buffer *>::const_iterator it = m_buffers.constBegin();
78 while (it != m_buffers.constEnd()) {
79 Buffer *buffer = it.value();
86 QHash<QByteArray, Items *>::const_iterator it = m_items.constBegin();
87 while (it != m_items.constEnd()) {
88 Items *items = it.value();
89 QHash<quint32, Item *>::const_iterator itemIt = items->items.constBegin();
90 while (itemIt != items->items.constEnd()) {
91 delete itemIt.value();
100 void QXcbSharedBufferManager::getBufferForItem(const QByteArray &cacheId, quint32 itemId,
101 Buffer **buffer, int *x, int *y) const
103 Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO,
104 "Call endSharedBufferAction before accessing data");
106 Q_ASSERT(buffer != 0);
110 Items *items = itemsForCache(cacheId);
111 Item *item = items->items.value(itemId);
113 *buffer = item->buffer;
123 QPair<QByteArray, int> QXcbSharedBufferManager::serializeBuffer(QSharedMemory *buffer) const
125 Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO,
126 "Call endSharedBufferAction before accessing data");
128 return qMakePair(buffer->key().toLatin1(), 0);
131 void QXcbSharedBufferManager::beginSharedBufferAction(const QByteArray &cacheId)
133 #if defined(SHAREDBUFFERMANAGER_DEBUG)
134 qDebug("QXcbSharedBufferManager::beginSharedBufferAction() called for %s", cacheId.constData());
137 Q_ASSERT(m_currentCacheId.isEmpty());
138 Q_ASSERT(!cacheId.isEmpty());
140 m_pendingInvalidatedItems.clear();
141 m_pendingReadyItems.clear();
142 m_pendingMissingItems.clear();
144 m_currentCacheId = cacheId;
147 void QXcbSharedBufferManager::requestItems(const QSet<quint32> &itemIds)
149 #if defined(SHAREDBUFFERMANAGER_DEBUG)
150 qDebug("QXcbSharedBufferManager::requestItems for %d items", itemIds.size());
153 Q_ASSERT_X(!m_currentCacheId.isEmpty(), Q_FUNC_INFO,
154 "Call beginSharedBufferAction before requesting items");
155 Items *items = itemsForCache(m_currentCacheId);
157 QSet<quint32>::const_iterator it = itemIds.constBegin();
158 while (it != itemIds.constEnd()) {
159 if (items->items.contains(*it))
160 m_pendingReadyItems[m_currentCacheId].insert(*it);
162 m_pendingMissingItems[m_currentCacheId].insert(*it);
167 void QXcbSharedBufferManager::releaseItems(const QSet<quint32> &itemIds)
169 #if defined(SHAREDBUFFERMANAGER_DEBUG)
170 qDebug("QXcbSharedBufferManager::releaseItems for %d items", itemIds.size());
173 Items *items = itemsForCache(m_currentCacheId);
175 QSet<quint32>::const_iterator it;
176 for (it = itemIds.constBegin(); it != itemIds.constEnd(); ++it) {
177 Item *item = items->items.value(*it);
179 pushItemToBack(items, item);
181 m_pendingReadyItems[m_currentCacheId].remove(*it);
182 m_pendingMissingItems[m_currentCacheId].remove(*it);
186 void QXcbSharedBufferManager::insertItem(quint32 itemId, uchar *data,
187 int itemWidth, int itemHeight)
189 Q_ASSERT_X(!m_currentCacheId.isEmpty(), Q_FUNC_INFO,
190 "Call beginSharedBufferAction before inserting items");
191 Items *items = itemsForCache(m_currentCacheId);
193 if (!items->items.contains(itemId)) {
194 Buffer *sharedBuffer = 0;
198 findAvailableBuffer(itemWidth, itemHeight, &sharedBuffer, &x, &y);
199 copyIntoBuffer(sharedBuffer, x, y, itemWidth, itemHeight, data);
201 // static int counter=0;
202 // QString fileName = QString::fromLatin1("buffer%1.png").arg(counter++);
203 // saveBuffer(sharedBuffer, fileName);
205 Item *item = new Item;
206 item->itemId = itemId;
207 item->buffer = sharedBuffer;
211 items->items[itemId] = item;
213 touchItem(items, item);
217 void QXcbSharedBufferManager::endSharedBufferAction()
219 #if defined(SHAREDBUFFERMANAGER_DEBUG)
220 qDebug("QXcbSharedBufferManager::endSharedBufferAction() called for %s",
221 m_currentCacheId.constData());
224 Q_ASSERT(!m_currentCacheId.isEmpty());
226 // Do an extra validation pass on the invalidated items since they may have been re-inserted
227 // after they were invalidated
228 if (m_pendingInvalidatedItems.contains(m_currentCacheId)) {
229 QSet<quint32> &invalidatedItems = m_pendingInvalidatedItems[m_currentCacheId];
230 QSet<quint32>::iterator it = invalidatedItems.begin();
231 while (it != invalidatedItems.end()) {
232 Items *items = m_items.value(m_currentCacheId);
234 if (items->items.contains(*it)) {
235 m_pendingReadyItems[m_currentCacheId].insert(*it);
236 it = invalidatedItems.erase(it);
243 m_currentCacheId.clear();
246 void QXcbSharedBufferManager::pushItemToBack(Items *items, Item *item)
248 if (items->leastRecentlyUsed == item)
252 item->next->prev = item->prev;
254 item->prev->next = item->next;
256 if (items->mostRecentlyUsed == item)
257 items->mostRecentlyUsed = item->prev;
259 if (items->leastRecentlyUsed != 0)
260 items->leastRecentlyUsed->prev = item;
263 item->next = items->leastRecentlyUsed;
264 items->leastRecentlyUsed = item;
265 if (items->mostRecentlyUsed == 0)
266 items->mostRecentlyUsed = item;
269 void QXcbSharedBufferManager::touchItem(Items *items, Item *item)
271 if (items->mostRecentlyUsed == item)
275 item->next->prev = item->prev;
277 item->prev->next = item->next;
279 if (items->leastRecentlyUsed == item)
280 items->leastRecentlyUsed = item->next;
282 if (items->mostRecentlyUsed != 0)
283 items->mostRecentlyUsed->next = item;
286 item->prev = items->mostRecentlyUsed;
287 items->mostRecentlyUsed = item;
288 if (items->leastRecentlyUsed == 0)
289 items->leastRecentlyUsed = item;
292 void QXcbSharedBufferManager::deleteItem(Items *items, Item *item)
294 Q_ASSERT(items != 0);
297 if (items->mostRecentlyUsed == item)
298 items->mostRecentlyUsed = item->prev;
299 if (items->leastRecentlyUsed == item)
300 items->leastRecentlyUsed = item->next;
303 item->next->prev = item->prev;
305 item->prev->next = item->next;
307 m_pendingInvalidatedItems[items->cacheId].insert(item->itemId);
310 QHash<quint32, Item *>::iterator it = items->items.find(item->itemId);
311 while (it != items->items.end() && it.value()->itemId == item->itemId)
312 it = items->items.erase(it);
318 void QXcbSharedBufferManager::recycleItem(Buffer **sharedBuffer, int *glyphX, int *glyphY)
320 #if defined(SHAREDBUFFERMANAGER_DEBUG)
321 qDebug("QXcbSharedBufferManager::recycleItem() called for %s", m_currentCacheId.constData());
324 Items *items = itemsForCache(m_currentCacheId);
326 Item *recycledItem = items->leastRecentlyUsed;
327 Q_ASSERT(recycledItem != 0);
329 *sharedBuffer = recycledItem->buffer;
330 *glyphX = recycledItem->x;
331 *glyphY = recycledItem->y;
333 deleteItem(items, recycledItem);
336 void QXcbSharedBufferManager::touchBuffer(Buffer *buffer)
338 #if defined(SHAREDBUFFERMANAGER_DEBUG)
339 qDebug("QXcbSharedBufferManager::touchBuffer() called for %s", buffer->cacheId.constData());
342 if (buffer == m_mostRecentlyUsed)
345 if (buffer->next != 0)
346 buffer->next->prev = buffer->prev;
347 if (buffer->prev != 0)
348 buffer->prev->next = buffer->next;
350 if (m_leastRecentlyUsed == buffer)
351 m_leastRecentlyUsed = buffer->next;
354 buffer->prev = m_mostRecentlyUsed;
355 if (m_mostRecentlyUsed != 0)
356 m_mostRecentlyUsed->next = buffer;
357 if (m_leastRecentlyUsed == 0)
358 m_leastRecentlyUsed = buffer;
359 m_mostRecentlyUsed = buffer;
362 void QXcbSharedBufferManager::deleteLeastRecentlyUsed()
364 #if defined(SHAREDBUFFERMANAGER_DEBUG)
365 qDebug("QXcbSharedBufferManager::deleteLeastRecentlyUsed() called");
368 if (m_leastRecentlyUsed == 0)
371 Buffer *old = m_leastRecentlyUsed;
372 m_leastRecentlyUsed = old->next;
373 m_leastRecentlyUsed->prev = 0;
375 QByteArray cacheId = old->cacheId;
376 Items *items = itemsForCache(cacheId);
378 QHash<quint32, Item *>::iterator it = items->items.begin();
379 while (it != items->items.end()) {
380 Item *item = it.value();
381 if (item->buffer == old) {
382 deleteItem(items, item);
383 it = items->items.erase(it);
389 m_buffers.remove(cacheId, old);
390 m_memoryUsed -= old->width * old->height * old->bytesPerPixel;
392 #if defined(SHAREDBUFFERMANAGER_DEBUG)
393 qDebug("QXcbSharedBufferManager::deleteLeastRecentlyUsed: Memory used: %d / %d (%6.2f %%)",
394 m_memoryUsed, SHAREDGRAPHICSCACHE_MAX_MEMORY_USED,
395 100.0f * float(m_memoryUsed) / float(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED));
401 QXcbSharedBufferManager::Buffer *QXcbSharedBufferManager::createNewBuffer(const QByteArray &cacheId,
404 #if defined(SHAREDBUFFERMANAGER_DEBUG)
405 qDebug("QXcbSharedBufferManager::createNewBuffer() called for %s", cacheId.constData());
409 // if (bufferCount of cacheId == SHAREDGRAPHICACHE_MAX_TEXTURES_PER_CACHE)
410 // deleteLeastRecentlyUsedBufferForCache(cacheId);
412 // ### Take pixel format into account
413 while (m_memoryUsed + SHAREDGRAPHICSCACHE_TEXTURE_SIZE * heightRequired >= SHAREDGRAPHICSCACHE_MAX_MEMORY_USED)
414 deleteLeastRecentlyUsed();
416 Buffer *buffer = allocateBuffer(SHAREDGRAPHICSCACHE_TEXTURE_SIZE, heightRequired);
417 buffer->cacheId = cacheId;
419 buffer->currentLineMaxHeight = 0;
420 m_buffers.insert(cacheId, buffer);
425 static inline int qt_next_power_of_two(int v)
437 QXcbSharedBufferManager::Buffer *QXcbSharedBufferManager::resizeBuffer(Buffer *oldBuffer, const QSize &newSize)
439 #if defined(SHAREDBUFFERMANAGER_DEBUG)
440 qDebug("QXcbSharedBufferManager::resizeBuffer() called for %s (current size: %dx%d, new size: %dx%d)",
441 oldBuffer->cacheId.constData(), oldBuffer->width, oldBuffer->height,
442 newSize.width(), newSize.height());
445 // Remove old buffer from lists to avoid deleting it under our feet
446 if (m_leastRecentlyUsed == oldBuffer)
447 m_leastRecentlyUsed = oldBuffer->next;
448 if (m_mostRecentlyUsed == oldBuffer)
449 m_mostRecentlyUsed = oldBuffer->prev;
451 if (oldBuffer->prev != 0)
452 oldBuffer->prev->next = oldBuffer->next;
453 if (oldBuffer->next != 0)
454 oldBuffer->next->prev = oldBuffer->prev;
456 m_memoryUsed -= oldBuffer->width * oldBuffer->height * oldBuffer->bytesPerPixel;
457 m_buffers.remove(oldBuffer->cacheId, oldBuffer);
459 #if defined(SHAREDBUFFERMANAGER_DEBUG)
460 qDebug("QXcbSharedBufferManager::resizeBuffer: Memory used: %d / %d (%6.2f %%)",
461 m_memoryUsed, SHAREDGRAPHICSCACHE_MAX_MEMORY_USED,
462 100.0f * float(m_memoryUsed) / float(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED));
465 Buffer *resizedBuffer = createNewBuffer(oldBuffer->cacheId, newSize.height());
466 copyIntoBuffer(resizedBuffer, 0, 0, oldBuffer->width, oldBuffer->height,
467 reinterpret_cast<uchar *>(oldBuffer->buffer->data()));
469 resizedBuffer->currentLineMaxHeight = oldBuffer->currentLineMaxHeight;
471 Items *items = itemsForCache(oldBuffer->cacheId);
472 QHash<quint32, Item *>::const_iterator it = items->items.constBegin();
473 while (it != items->items.constEnd()) {
474 Item *item = it.value();
475 if (item->buffer == oldBuffer) {
476 m_pendingReadyItems[oldBuffer->cacheId].insert(item->itemId);
477 item->buffer = resizedBuffer;
482 resizedBuffer->nextX = oldBuffer->nextX;
483 resizedBuffer->nextY = oldBuffer->nextY;
484 resizedBuffer->currentLineMaxHeight = oldBuffer->currentLineMaxHeight;
487 return resizedBuffer;
490 void QXcbSharedBufferManager::findAvailableBuffer(int itemWidth, int itemHeight,
491 Buffer **sharedBuffer, int *glyphX, int *glyphY)
493 Q_ASSERT(sharedBuffer != 0);
494 Q_ASSERT(glyphX != 0);
495 Q_ASSERT(glyphY != 0);
497 QMultiHash<QByteArray, Buffer *>::iterator it = m_buffers.find(m_currentCacheId);
500 while (it != m_buffers.end() && it.key() == m_currentCacheId) {
501 Buffer *buffer = it.value();
503 int x = buffer->nextX;
504 int y = buffer->nextY;
505 int width = buffer->width;
506 int height = buffer->height;
508 if (x + itemWidth <= width && y + itemHeight <= height) {
509 // There is space on the current line, put the item there
510 buffer->currentLineMaxHeight = qMax(buffer->currentLineMaxHeight, itemHeight);
511 *sharedBuffer = buffer;
515 buffer->nextX += itemWidth;
518 } else if (itemWidth <= width && y + buffer->currentLineMaxHeight + itemHeight <= height) {
519 // There is space for a new line, put the item on the new line
521 buffer->nextY += buffer->currentLineMaxHeight;
522 buffer->currentLineMaxHeight = 0;
524 *sharedBuffer = buffer;
525 *glyphX = buffer->nextX;
526 *glyphY = buffer->nextY;
528 buffer->nextX += itemWidth;
531 } else if (y + buffer->currentLineMaxHeight + itemHeight <= SHAREDGRAPHICSCACHE_TEXTURE_SIZE) {
532 // There is space if we resize the buffer, so we do that
533 int newHeight = qt_next_power_of_two(y + buffer->currentLineMaxHeight + itemHeight);
534 buffer = resizeBuffer(buffer, QSize(width, newHeight));
537 buffer->nextY += buffer->currentLineMaxHeight;
538 buffer->currentLineMaxHeight = 0;
540 *sharedBuffer = buffer;
541 *glyphX = buffer->nextX;
542 *glyphY = buffer->nextY;
544 buffer->nextX += itemWidth;
552 if (bufferCount == SHAREDGRAPHICSCACHE_MAX_TEXTURES_PER_CACHE) {
553 // There is no space in any buffer, and there is no space for a new buffer
554 // recycle an old item
555 recycleItem(sharedBuffer, glyphX, glyphY);
557 // Create a new buffer for the item
558 *sharedBuffer = createNewBuffer(m_currentCacheId, qt_next_power_of_two(itemHeight));
559 if (*sharedBuffer == 0) {
564 *glyphX = (*sharedBuffer)->nextX;
565 *glyphY = (*sharedBuffer)->nextY;
567 (*sharedBuffer)->nextX += itemWidth;
571 QXcbSharedBufferManager::Buffer *QXcbSharedBufferManager::allocateBuffer(int width, int height)
573 Buffer *buffer = new Buffer;
576 buffer->width = width;
577 buffer->height = height;
578 buffer->bytesPerPixel = 1; // ### Use pixel format here
580 buffer->buffer = new QSharedMemory(QUuid::createUuid().toString());
581 bool ok = buffer->buffer->create(buffer->width * buffer->height * buffer->bytesPerPixel,
582 QSharedMemory::ReadWrite);
584 qWarning("QXcbSharedBufferManager::findAvailableBuffer: Can't create new buffer (%s)",
585 qPrintable(buffer->buffer->errorString()));
589 qMemSet(buffer->buffer->data(), 0, buffer->buffer->size());
591 m_memoryUsed += buffer->width * buffer->height * buffer->bytesPerPixel;
593 #if defined(SHAREDBUFFERMANAGER_DEBUG)
594 qDebug("QXcbSharedBufferManager::allocateBuffer: Memory used: %d / %d (%6.2f %%)",
595 int(m_memoryUsed), int(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED),
596 100.0f * float(m_memoryUsed) / float(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED));
602 void QXcbSharedBufferManager::copyIntoBuffer(Buffer *buffer,
603 int bufferX, int bufferY, int width, int height,
606 #if defined(SHAREDBUFFERMANAGER_DEBUG)
607 qDebug("QXcbSharedBufferManager::copyIntoBuffer() called for %s (coords: %d, %d)",
608 buffer->cacheId.constData(), bufferX, bufferY);
611 Q_ASSERT(bufferX >= 0);
612 Q_ASSERT(bufferX + width <= buffer->width);
613 Q_ASSERT(bufferY >= 0);
614 Q_ASSERT(bufferY + height <= buffer->height);
616 uchar *dest = reinterpret_cast<uchar *>(buffer->buffer->data());
617 dest += bufferX + bufferY * buffer->width;
618 for (int y=0; y<height; ++y) {
619 qMemCopy(dest, data, width);
622 dest += buffer->width;
626 QXcbSharedBufferManager::Items *QXcbSharedBufferManager::itemsForCache(const QByteArray &cacheId) const
628 Items *items = m_items.value(cacheId);
631 items->cacheId = cacheId;
632 m_items[cacheId] = items;
640 #endif // QT_USE_XCB_SHARED_GRAPHICS_CACHE