diff options
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbsharedbuffermanager.cpp')
-rw-r--r-- | src/plugins/platforms/xcb/qxcbsharedbuffermanager.cpp | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/src/plugins/platforms/xcb/qxcbsharedbuffermanager.cpp b/src/plugins/platforms/xcb/qxcbsharedbuffermanager.cpp new file mode 100644 index 0000000000..51e3e85f10 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbsharedbuffermanager.cpp @@ -0,0 +1,640 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + +#include "qxcbsharedbuffermanager.h" + +#include <QtCore/quuid.h> +#include <QtGui/qimage.h> + +#include <stdio.h> + +#if !defined(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED) +# define SHAREDGRAPHICSCACHE_MAX_MEMORY_USED 16 * 1024 * 1024 // 16 MB limit +#endif + +#if !defined(SHAREDGRAPHICSCACHE_MAX_TEXTURES_PER_CACHE) +# define SHAREDGRAPHICSCACHE_MAX_TEXTURES_PER_CACHE 1 +#endif + +#if !defined(SHAREDGRAPHICSCACHE_TEXTURE_SIZE) +# define SHAREDGRAPHICSCACHE_TEXTURE_SIZE 2048 +#endif + +#define SHAREDBUFFERMANAGER_DEBUG 1 + +QT_BEGIN_NAMESPACE + +QXcbSharedBufferManager::QXcbSharedBufferManager() + : m_memoryUsed(0) + , m_mostRecentlyUsed(0) + , m_leastRecentlyUsed(0) +{ +} + +QXcbSharedBufferManager::~QXcbSharedBufferManager() +{ + { + QHash<QByteArray, Buffer *>::const_iterator it = m_buffers.constBegin(); + while (it != m_buffers.constEnd()) { + Buffer *buffer = it.value(); + delete buffer; + ++it; + } + } + + { + QHash<QByteArray, Items *>::const_iterator it = m_items.constBegin(); + while (it != m_items.constEnd()) { + Items *items = it.value(); + QHash<quint32, Item *>::const_iterator itemIt = items->items.constBegin(); + while (itemIt != items->items.constEnd()) { + delete itemIt.value(); + ++itemIt; + } + delete it.value(); + ++it; + } + } +} + +void QXcbSharedBufferManager::getBufferForItem(const QByteArray &cacheId, quint32 itemId, + Buffer **buffer, int *x, int *y) const +{ + Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call endSharedBufferAction before accessing data"); + + Q_ASSERT(buffer != 0); + Q_ASSERT(x != 0); + Q_ASSERT(y != 0); + + Items *items = itemsForCache(cacheId); + Item *item = items->items.value(itemId); + if (item != 0) { + *buffer = item->buffer; + *x = item->x; + *y = item->y; + } else { + *buffer = 0; + *x = -1; + *y = -1; + } +} + +QPair<QByteArray, int> QXcbSharedBufferManager::serializeBuffer(QSharedMemory *buffer) const +{ + Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call endSharedBufferAction before accessing data"); + + return qMakePair(buffer->key().toLatin1(), 0); +} + +void QXcbSharedBufferManager::beginSharedBufferAction(const QByteArray &cacheId) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::beginSharedBufferAction() called for %s", cacheId.constData()); +#endif + + Q_ASSERT(m_currentCacheId.isEmpty()); + Q_ASSERT(!cacheId.isEmpty()); + + m_pendingInvalidatedItems.clear(); + m_pendingReadyItems.clear(); + m_pendingMissingItems.clear(); + + m_currentCacheId = cacheId; +} + +void QXcbSharedBufferManager::requestItems(const QSet<quint32> &itemIds) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::requestItems for %d items", itemIds.size()); +#endif + + Q_ASSERT_X(!m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call beginSharedBufferAction before requesting items"); + Items *items = itemsForCache(m_currentCacheId); + + QSet<quint32>::const_iterator it = itemIds.constBegin(); + while (it != itemIds.constEnd()) { + if (items->items.contains(*it)) + m_pendingReadyItems[m_currentCacheId].insert(*it); + else + m_pendingMissingItems[m_currentCacheId].insert(*it); + ++it; + } +} + +void QXcbSharedBufferManager::releaseItems(const QSet<quint32> &itemIds) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::releaseItems for %d items", itemIds.size()); +#endif + + Items *items = itemsForCache(m_currentCacheId); + + QSet<quint32>::const_iterator it; + for (it = itemIds.constBegin(); it != itemIds.constEnd(); ++it) { + Item *item = items->items.value(*it); + if (item != 0) + pushItemToBack(items, item); + + m_pendingReadyItems[m_currentCacheId].remove(*it); + m_pendingMissingItems[m_currentCacheId].remove(*it); + } +} + +void QXcbSharedBufferManager::insertItem(quint32 itemId, uchar *data, + int itemWidth, int itemHeight) +{ + Q_ASSERT_X(!m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call beginSharedBufferAction before inserting items"); + Items *items = itemsForCache(m_currentCacheId); + + if (!items->items.contains(itemId)) { + Buffer *sharedBuffer = 0; + int x = 0; + int y = 0; + + findAvailableBuffer(itemWidth, itemHeight, &sharedBuffer, &x, &y); + copyIntoBuffer(sharedBuffer, x, y, itemWidth, itemHeight, data); + +// static int counter=0; +// QString fileName = QString::fromLatin1("buffer%1.png").arg(counter++); +// saveBuffer(sharedBuffer, fileName); + + Item *item = new Item; + item->itemId = itemId; + item->buffer = sharedBuffer; + item->x = x; + item->y = y; + + items->items[itemId] = item; + + touchItem(items, item); + } +} + +void QXcbSharedBufferManager::endSharedBufferAction() +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::endSharedBufferAction() called for %s", + m_currentCacheId.constData()); +#endif + + Q_ASSERT(!m_currentCacheId.isEmpty()); + + // Do an extra validation pass on the invalidated items since they may have been re-inserted + // after they were invalidated + if (m_pendingInvalidatedItems.contains(m_currentCacheId)) { + QSet<quint32> &invalidatedItems = m_pendingInvalidatedItems[m_currentCacheId]; + QSet<quint32>::iterator it = invalidatedItems.begin(); + while (it != invalidatedItems.end()) { + Items *items = m_items.value(m_currentCacheId); + + if (items->items.contains(*it)) { + m_pendingReadyItems[m_currentCacheId].insert(*it); + it = invalidatedItems.erase(it); + } else { + ++it; + } + } + } + + m_currentCacheId.clear(); +} + +void QXcbSharedBufferManager::pushItemToBack(Items *items, Item *item) +{ + if (items->leastRecentlyUsed == item) + return; + + if (item->next != 0) + item->next->prev = item->prev; + if (item->prev != 0) + item->prev->next = item->next; + + if (items->mostRecentlyUsed == item) + items->mostRecentlyUsed = item->prev; + + if (items->leastRecentlyUsed != 0) + items->leastRecentlyUsed->prev = item; + + item->prev = 0; + item->next = items->leastRecentlyUsed; + items->leastRecentlyUsed = item; + if (items->mostRecentlyUsed == 0) + items->mostRecentlyUsed = item; +} + +void QXcbSharedBufferManager::touchItem(Items *items, Item *item) +{ + if (items->mostRecentlyUsed == item) + return; + + if (item->next != 0) + item->next->prev = item->prev; + if (item->prev != 0) + item->prev->next = item->next; + + if (items->leastRecentlyUsed == item) + items->leastRecentlyUsed = item->next; + + if (items->mostRecentlyUsed != 0) + items->mostRecentlyUsed->next = item; + + item->next = 0; + item->prev = items->mostRecentlyUsed; + items->mostRecentlyUsed = item; + if (items->leastRecentlyUsed == 0) + items->leastRecentlyUsed = item; +} + +void QXcbSharedBufferManager::deleteItem(Items *items, Item *item) +{ + Q_ASSERT(items != 0); + Q_ASSERT(item != 0); + + if (items->mostRecentlyUsed == item) + items->mostRecentlyUsed = item->prev; + if (items->leastRecentlyUsed == item) + items->leastRecentlyUsed = item->next; + + if (item->next != 0) + item->next->prev = item->prev; + if (item->prev != 0) + item->prev->next = item->next; + + m_pendingInvalidatedItems[items->cacheId].insert(item->itemId); + + { + QHash<quint32, Item *>::iterator it = items->items.find(item->itemId); + while (it != items->items.end() && it.value()->itemId == item->itemId) + it = items->items.erase(it); + } + + delete item; +} + +void QXcbSharedBufferManager::recycleItem(Buffer **sharedBuffer, int *glyphX, int *glyphY) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::recycleItem() called for %s", m_currentCacheId.constData()); +#endif + + Items *items = itemsForCache(m_currentCacheId); + + Item *recycledItem = items->leastRecentlyUsed; + Q_ASSERT(recycledItem != 0); + + *sharedBuffer = recycledItem->buffer; + *glyphX = recycledItem->x; + *glyphY = recycledItem->y; + + deleteItem(items, recycledItem); +} + +void QXcbSharedBufferManager::touchBuffer(Buffer *buffer) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::touchBuffer() called for %s", buffer->cacheId.constData()); +#endif + + if (buffer == m_mostRecentlyUsed) + return; + + if (buffer->next != 0) + buffer->next->prev = buffer->prev; + if (buffer->prev != 0) + buffer->prev->next = buffer->next; + + if (m_leastRecentlyUsed == buffer) + m_leastRecentlyUsed = buffer->next; + + buffer->next = 0; + buffer->prev = m_mostRecentlyUsed; + if (m_mostRecentlyUsed != 0) + m_mostRecentlyUsed->next = buffer; + if (m_leastRecentlyUsed == 0) + m_leastRecentlyUsed = buffer; + m_mostRecentlyUsed = buffer; +} + +void QXcbSharedBufferManager::deleteLeastRecentlyUsed() +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::deleteLeastRecentlyUsed() called"); +#endif + + if (m_leastRecentlyUsed == 0) + return; + + Buffer *old = m_leastRecentlyUsed; + m_leastRecentlyUsed = old->next; + m_leastRecentlyUsed->prev = 0; + + QByteArray cacheId = old->cacheId; + Items *items = itemsForCache(cacheId); + + QHash<quint32, Item *>::iterator it = items->items.begin(); + while (it != items->items.end()) { + Item *item = it.value(); + if (item->buffer == old) { + deleteItem(items, item); + it = items->items.erase(it); + } else { + ++it; + } + } + + m_buffers.remove(cacheId, old); + m_memoryUsed -= old->width * old->height * old->bytesPerPixel; + +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::deleteLeastRecentlyUsed: Memory used: %d / %d (%6.2f %%)", + m_memoryUsed, SHAREDGRAPHICSCACHE_MAX_MEMORY_USED, + 100.0f * float(m_memoryUsed) / float(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED)); +#endif + + delete old; +} + +QXcbSharedBufferManager::Buffer *QXcbSharedBufferManager::createNewBuffer(const QByteArray &cacheId, + int heightRequired) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::createNewBuffer() called for %s", cacheId.constData()); +#endif + + // ### + // if (bufferCount of cacheId == SHAREDGRAPHICACHE_MAX_TEXTURES_PER_CACHE) + // deleteLeastRecentlyUsedBufferForCache(cacheId); + + // ### Take pixel format into account + while (m_memoryUsed + SHAREDGRAPHICSCACHE_TEXTURE_SIZE * heightRequired >= SHAREDGRAPHICSCACHE_MAX_MEMORY_USED) + deleteLeastRecentlyUsed(); + + Buffer *buffer = allocateBuffer(SHAREDGRAPHICSCACHE_TEXTURE_SIZE, heightRequired); + buffer->cacheId = cacheId; + + buffer->currentLineMaxHeight = 0; + m_buffers.insert(cacheId, buffer); + + return buffer; +} + +static inline int qt_next_power_of_two(int v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + ++v; + return v; +} + +QXcbSharedBufferManager::Buffer *QXcbSharedBufferManager::resizeBuffer(Buffer *oldBuffer, const QSize &newSize) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::resizeBuffer() called for %s (current size: %dx%d, new size: %dx%d)", + oldBuffer->cacheId.constData(), oldBuffer->width, oldBuffer->height, + newSize.width(), newSize.height()); +#endif + + // Remove old buffer from lists to avoid deleting it under our feet + if (m_leastRecentlyUsed == oldBuffer) + m_leastRecentlyUsed = oldBuffer->next; + if (m_mostRecentlyUsed == oldBuffer) + m_mostRecentlyUsed = oldBuffer->prev; + + if (oldBuffer->prev != 0) + oldBuffer->prev->next = oldBuffer->next; + if (oldBuffer->next != 0) + oldBuffer->next->prev = oldBuffer->prev; + + m_memoryUsed -= oldBuffer->width * oldBuffer->height * oldBuffer->bytesPerPixel; + m_buffers.remove(oldBuffer->cacheId, oldBuffer); + +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::resizeBuffer: Memory used: %d / %d (%6.2f %%)", + m_memoryUsed, SHAREDGRAPHICSCACHE_MAX_MEMORY_USED, + 100.0f * float(m_memoryUsed) / float(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED)); +#endif + + Buffer *resizedBuffer = createNewBuffer(oldBuffer->cacheId, newSize.height()); + copyIntoBuffer(resizedBuffer, 0, 0, oldBuffer->width, oldBuffer->height, + reinterpret_cast<uchar *>(oldBuffer->buffer->data())); + + resizedBuffer->currentLineMaxHeight = oldBuffer->currentLineMaxHeight; + + Items *items = itemsForCache(oldBuffer->cacheId); + QHash<quint32, Item *>::const_iterator it = items->items.constBegin(); + while (it != items->items.constEnd()) { + Item *item = it.value(); + if (item->buffer == oldBuffer) { + m_pendingReadyItems[oldBuffer->cacheId].insert(item->itemId); + item->buffer = resizedBuffer; + } + ++it; + } + + resizedBuffer->nextX = oldBuffer->nextX; + resizedBuffer->nextY = oldBuffer->nextY; + resizedBuffer->currentLineMaxHeight = oldBuffer->currentLineMaxHeight; + + delete oldBuffer; + return resizedBuffer; +} + +void QXcbSharedBufferManager::findAvailableBuffer(int itemWidth, int itemHeight, + Buffer **sharedBuffer, int *glyphX, int *glyphY) +{ + Q_ASSERT(sharedBuffer != 0); + Q_ASSERT(glyphX != 0); + Q_ASSERT(glyphY != 0); + + QMultiHash<QByteArray, Buffer *>::iterator it = m_buffers.find(m_currentCacheId); + + int bufferCount = 0; + while (it != m_buffers.end() && it.key() == m_currentCacheId) { + Buffer *buffer = it.value(); + + int x = buffer->nextX; + int y = buffer->nextY; + int width = buffer->width; + int height = buffer->height; + + if (x + itemWidth <= width && y + itemHeight <= height) { + // There is space on the current line, put the item there + buffer->currentLineMaxHeight = qMax(buffer->currentLineMaxHeight, itemHeight); + *sharedBuffer = buffer; + *glyphX = x; + *glyphY = y; + + buffer->nextX += itemWidth; + + return; + } else if (itemWidth <= width && y + buffer->currentLineMaxHeight + itemHeight <= height) { + // There is space for a new line, put the item on the new line + buffer->nextX = 0; + buffer->nextY += buffer->currentLineMaxHeight; + buffer->currentLineMaxHeight = 0; + + *sharedBuffer = buffer; + *glyphX = buffer->nextX; + *glyphY = buffer->nextY; + + buffer->nextX += itemWidth; + + return; + } else if (y + buffer->currentLineMaxHeight + itemHeight <= SHAREDGRAPHICSCACHE_TEXTURE_SIZE) { + // There is space if we resize the buffer, so we do that + int newHeight = qt_next_power_of_two(y + buffer->currentLineMaxHeight + itemHeight); + buffer = resizeBuffer(buffer, QSize(width, newHeight)); + + buffer->nextX = 0; + buffer->nextY += buffer->currentLineMaxHeight; + buffer->currentLineMaxHeight = 0; + + *sharedBuffer = buffer; + *glyphX = buffer->nextX; + *glyphY = buffer->nextY; + + buffer->nextX += itemWidth; + return; + } + + bufferCount++; + ++it; + } + + if (bufferCount == SHAREDGRAPHICSCACHE_MAX_TEXTURES_PER_CACHE) { + // There is no space in any buffer, and there is no space for a new buffer + // recycle an old item + recycleItem(sharedBuffer, glyphX, glyphY); + } else { + // Create a new buffer for the item + *sharedBuffer = createNewBuffer(m_currentCacheId, qt_next_power_of_two(itemHeight)); + if (*sharedBuffer == 0) { + Q_ASSERT(false); + return; + } + + *glyphX = (*sharedBuffer)->nextX; + *glyphY = (*sharedBuffer)->nextY; + + (*sharedBuffer)->nextX += itemWidth; + } +} + +QXcbSharedBufferManager::Buffer *QXcbSharedBufferManager::allocateBuffer(int width, int height) +{ + Buffer *buffer = new Buffer; + buffer->nextX = 0; + buffer->nextY = 0; + buffer->width = width; + buffer->height = height; + buffer->bytesPerPixel = 1; // ### Use pixel format here + + buffer->buffer = new QSharedMemory(QUuid::createUuid().toString()); + bool ok = buffer->buffer->create(buffer->width * buffer->height * buffer->bytesPerPixel, + QSharedMemory::ReadWrite); + if (!ok) { + qWarning("SharedBufferManager::findAvailableBuffer: Can't create new buffer (%s)", + qPrintable(buffer->buffer->errorString())); + delete buffer; + return 0; + } + qMemSet(buffer->buffer->data(), 0, buffer->buffer->size()); + + m_memoryUsed += buffer->width * buffer->height * buffer->bytesPerPixel; + +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::allocateBuffer: Memory used: %d / %d (%6.2f %%)", + int(m_memoryUsed), int(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED), + 100.0f * float(m_memoryUsed) / float(SHAREDGRAPHICSCACHE_MAX_MEMORY_USED)); +#endif + + return buffer; +} + +void QXcbSharedBufferManager::copyIntoBuffer(Buffer *buffer, + int bufferX, int bufferY, int width, int height, + uchar *data) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::copyIntoBuffer() called for %s (coords: %d, %d)", + buffer->cacheId.constData(), bufferX, bufferY); +#endif + + Q_ASSERT(bufferX >= 0); + Q_ASSERT(bufferX + width <= buffer->width); + Q_ASSERT(bufferY >= 0); + Q_ASSERT(bufferY + height <= buffer->height); + + uchar *dest = reinterpret_cast<uchar *>(buffer->buffer->data()); + dest += bufferX + bufferY * buffer->width; + for (int y=0; y<height; ++y) { + qMemCopy(dest, data, width); + + data += width; + dest += buffer->width; + } +} + +QXcbSharedBufferManager::Items *QXcbSharedBufferManager::itemsForCache(const QByteArray &cacheId) const +{ + Items *items = m_items.value(cacheId); + if (items == 0) { + items = new Items; + items->cacheId = cacheId; + m_items[cacheId] = items; + } + + return items; +} + +QT_END_NAMESPACE + +#endif // QT_USE_XCB_SHARED_GRAPHICS_CACHE |