From c274ea7cf5e11ade0a5812e3080baa671d012524 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Fri, 18 Nov 2011 10:24:32 +0100 Subject: Introducing QPlatformSharedGraphicsCache Interface to provide cross-process caching mechanisms in a platform plugin. Can be used for shared glyph caches and icon caches etc. Change-Id: If0d89a0a50bbd6eee05daf908448262ff270fc5b Reviewed-by: Jiang Jiang --- src/plugins/platforms/xcb/qxcbintegration.cpp | 29 + src/plugins/platforms/xcb/qxcbintegration.h | 8 + .../platforms/xcb/qxcbsharedbuffermanager.cpp | 640 +++++++++++++++++++++ .../platforms/xcb/qxcbsharedbuffermanager.h | 215 +++++++ .../platforms/xcb/qxcbsharedgraphicscache.cpp | 290 ++++++++++ .../platforms/xcb/qxcbsharedgraphicscache.h | 91 +++ src/plugins/platforms/xcb/xcb.pro | 9 +- 7 files changed, 1280 insertions(+), 2 deletions(-) create mode 100644 src/plugins/platforms/xcb/qxcbsharedbuffermanager.cpp create mode 100644 src/plugins/platforms/xcb/qxcbsharedbuffermanager.h create mode 100644 src/plugins/platforms/xcb/qxcbsharedgraphicscache.cpp create mode 100644 src/plugins/platforms/xcb/qxcbsharedgraphicscache.h (limited to 'src/plugins') diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index 418915bd15..0bed8c6fbb 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -47,6 +47,7 @@ #include "qxcbnativeinterface.h" #include "qxcbclipboard.h" #include "qxcbdrag.h" +#include "qxcbsharedgraphicscache.h" #include @@ -107,6 +108,10 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters) m_fontDatabase.reset(new QGenericUnixFontDatabase()); m_inputContext.reset(QPlatformInputContextFactory::create()); m_accessibility.reset(new QPlatformAccessibility()); + +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + m_sharedGraphicsCache.reset(new QXcbSharedGraphicsCache); +#endif } QXcbIntegration::~QXcbIntegration() @@ -186,6 +191,10 @@ QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *wind bool QXcbIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + case SharedGraphicsCache: return true; +#endif + case ThreadedPixmaps: return true; case OpenGL: return true; case ThreadedOpenGL: @@ -239,4 +248,24 @@ QPlatformAccessibility *QXcbIntegration::accessibility() const return m_accessibility.data(); } +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) +static bool sharedGraphicsCacheDisabled() +{ + static const char *environmentVariable = "QT_DISABLE_SHARED_CACHE"; + static bool cacheDisabled = !qgetenv(environmentVariable).isEmpty() + && qgetenv(environmentVariable).toInt() != 0; + return cacheDisabled; +} + +QPlatformSharedGraphicsCache *QXcbIntegration::createPlatformSharedGraphicsCache(const char *cacheId) const +{ + Q_UNUSED(cacheId); + + if (sharedGraphicsCacheDisabled()) + return 0; + + return m_sharedGraphicsCache.data(); +} +#endif + QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h index 2bb5f1e65e..fc769429cb 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.h +++ b/src/plugins/platforms/xcb/qxcbintegration.h @@ -77,6 +77,10 @@ public: QPlatformAccessibility *accessibility() const; +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + QPlatformSharedGraphicsCache *createPlatformSharedGraphicsCache(const char *cacheId) const; +#endif + private: QList m_connections; @@ -87,6 +91,10 @@ private: QAbstractEventDispatcher *m_eventDispatcher; QScopedPointer m_accessibility; + +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + QScopedPointer m_sharedGraphicsCache; +#endif }; QT_END_NAMESPACE 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 +#include + +#include + +#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::const_iterator it = m_buffers.constBegin(); + while (it != m_buffers.constEnd()) { + Buffer *buffer = it.value(); + delete buffer; + ++it; + } + } + + { + QHash::const_iterator it = m_items.constBegin(); + while (it != m_items.constEnd()) { + Items *items = it.value(); + QHash::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 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 &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::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 &itemIds) +{ +#if defined(SHAREDBUFFERMANAGER_DEBUG) + qDebug("QXcbSharedBufferManager::releaseItems for %d items", itemIds.size()); +#endif + + Items *items = itemsForCache(m_currentCacheId); + + QSet::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 &invalidatedItems = m_pendingInvalidatedItems[m_currentCacheId]; + QSet::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::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::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(oldBuffer->buffer->data())); + + resizedBuffer->currentLineMaxHeight = oldBuffer->currentLineMaxHeight; + + Items *items = itemsForCache(oldBuffer->cacheId); + QHash::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::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(buffer->buffer->data()); + dest += bufferX + bufferY * buffer->width; + for (int y=0; ywidth; + } +} + +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 diff --git a/src/plugins/platforms/xcb/qxcbsharedbuffermanager.h b/src/plugins/platforms/xcb/qxcbsharedbuffermanager.h new file mode 100644 index 0000000000..c815336c05 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbsharedbuffermanager.h @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef XCBSHAREDBUFFERMANAGER_H +#define XCBSHAREDBUFFERMANAGER_H + +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + +#include +#include +#include + +#include + +#include + +#include + +class wl_resource; + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QXcbSharedBufferManager +{ +public: + struct Buffer { + Buffer() + : width(-1) + , height(-1) + , bytesPerPixel(1) + , nextX(-1) + , nextY(-1) + , currentLineMaxHeight(0) + , next(0) + , prev(0) + , buffer(0) + , textureId(0) + { + } + + ~Buffer() + { + delete buffer; + + if (textureId != 0) + glDeleteTextures(1, &textureId); + } + + QByteArray cacheId; + int width; + int height; + int bytesPerPixel; + int nextX; + int nextY; + int currentLineMaxHeight; + + Buffer *next; + Buffer *prev; + + QSharedMemory *buffer; + + GLuint textureId; + + QAtomicInt ref; + }; + + typedef QHash > PendingItemIds; + + QXcbSharedBufferManager(); + ~QXcbSharedBufferManager(); + + void beginSharedBufferAction(const QByteArray &cacheId); + void insertItem(quint32 itemId, uchar *data, int itemWidth, int itemHeight); + void requestItems(const QSet &itemIds); + void releaseItems(const QSet &itemIds); + void endSharedBufferAction(); + + void getBufferForItem(const QByteArray &cacheId, quint32 itemId, Buffer **buffer, + int *x, int *y) const; + QPair serializeBuffer(QSharedMemory *buffer) const; + + PendingItemIds pendingItemsInvalidated() const + { + Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call endSharedBufferAction() before accessing data"); + return m_pendingInvalidatedItems; + } + + PendingItemIds pendingItemsReady() const + { + Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call endSharedBufferAction() before accessing data"); + return m_pendingReadyItems; + } + + PendingItemIds pendingItemsMissing() const + { + Q_ASSERT_X(m_currentCacheId.isEmpty(), Q_FUNC_INFO, + "Call endSharedBufferAction() before accessing data"); + return m_pendingMissingItems; + } + +private: + struct Item { + Item() + : next(0) + , prev(0) + , buffer(0) + , itemId(0) + , x(-1) + , y(-1) + , width(-1) + , height(-1) + { + } + + Item *next; + Item *prev; + + Buffer *buffer; + quint32 itemId; + int x; + int y; + int width; + int height; + }; + + struct Items + { + Items() : leastRecentlyUsed(0), mostRecentlyUsed(0) {} + + Item *leastRecentlyUsed; + Item *mostRecentlyUsed; + + QByteArray cacheId; + QHash items; + }; + + void findAvailableBuffer(int itemWidth, int itemHeight, Buffer **buffer, int *x, int *y); + void recycleItem(Buffer **buffer, int *x, int *y); + void copyIntoBuffer(Buffer *buffer, int x, int y, int itemWidth, int itemHeight, uchar *data); + void touchBuffer(Buffer *buffer); + void deleteLeastRecentlyUsed(); + + Buffer *createNewBuffer(const QByteArray &cacheId, int heightRequired); + Buffer *resizeBuffer(Buffer *buffer, const QSize &newSize); + Buffer *allocateBuffer(int width, int height); + + Items *itemsForCache(const QByteArray &cacheId) const; + void pushItemToBack(Items *items, Item *item); + void touchItem(Items *items, Item *item); + void deleteItem(Items *items, Item *item); + void recycleItem(const QByteArray &cacheId, Buffer **sharedBuffer, int *glyphX, int *glyphY); + + QByteArray m_currentCacheId; + + quint32 m_memoryUsed; + Buffer *m_mostRecentlyUsed; + Buffer *m_leastRecentlyUsed; + + mutable QHash m_items; + QMultiHash m_buffers; + + PendingItemIds m_pendingInvalidatedItems; + PendingItemIds m_pendingReadyItems; + PendingItemIds m_pendingMissingItems; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_USE_XCB_SHARED_GRAPHICS_CACHE + +#endif // XCBSHAREDBUFFERMANAGER_H diff --git a/src/plugins/platforms/xcb/qxcbsharedgraphicscache.cpp b/src/plugins/platforms/xcb/qxcbsharedgraphicscache.cpp new file mode 100644 index 0000000000..aa13138ee9 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbsharedgraphicscache.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** 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 "qxcbsharedgraphicscache.h" +#include "qxcbsharedbuffermanager.h" + +#include + +#include +#include + +#define GL_GLEXT_PROTOTYPES +#include + +#define SHAREDGRAPHICSCACHE_DEBUG 1 + +QT_BEGIN_NAMESPACE + +QXcbSharedGraphicsCache::QXcbSharedGraphicsCache(QObject *parent) + : QPlatformSharedGraphicsCache(parent) + , m_bufferManager(new QXcbSharedBufferManager) +{ +} + +void QXcbSharedGraphicsCache::requestItems(const QByteArray &cacheId, + const QVector &itemIds) +{ + m_bufferManager->beginSharedBufferAction(cacheId); + + QSet itemsForRequest; + for (int i=0; irequestItems(itemsForRequest); + m_bufferManager->endSharedBufferAction(); + + processPendingItems(); +} + +void QXcbSharedGraphicsCache::insertItems(const QByteArray &cacheId, + const QVector &itemIds, + const QVector &items) +{ + m_bufferManager->beginSharedBufferAction(cacheId); + + QSet itemsForRequest; + for (int i=0; iinsertItem(itemIds.at(i), image.bits(), image.width(), image.height()); + itemsForRequest.insert(itemIds.at(i)); + } + + // ### To avoid loops, we could check missing items here and notify the client + m_bufferManager->requestItems(itemsForRequest); + + m_bufferManager->endSharedBufferAction(); + + processPendingItems(); +} + +void QXcbSharedGraphicsCache::ensureCacheInitialized(const QByteArray &cacheId, + BufferType bufferType, + PixelFormat pixelFormat) +{ + Q_UNUSED(cacheId); + Q_UNUSED(bufferType); + Q_UNUSED(pixelFormat); +} + + +void QXcbSharedGraphicsCache::releaseItems(const QByteArray &cacheId, + const QVector &itemIds) +{ + m_bufferManager->beginSharedBufferAction(cacheId); + + QSet itemsToRelease; + for (int i=0; ireleaseItems(itemsToRelease); + + m_bufferManager->endSharedBufferAction(); + + processPendingItems(); +} + +void QXcbSharedGraphicsCache::serializeBuffer(void *bufferId, + QByteArray *serializedData, + int *fileDescriptor) const +{ + QXcbSharedBufferManager::Buffer *buffer = + reinterpret_cast(bufferId); + + QPair bufferName = m_bufferManager->serializeBuffer(buffer->buffer); + + *serializedData = bufferName.first; + *fileDescriptor = bufferName.second; +} + +GLuint QXcbSharedGraphicsCache::textureIdForBuffer(void *bufferId) +{ +# if defined(SHAREDGRAPHICSCACHE_DEBUG) + qDebug("QXcbSharedGraphicsCache::textureIdForBuffer"); +# endif + + QXcbSharedBufferManager::Buffer *buffer = + reinterpret_cast(bufferId); + + if (buffer->textureId == 0) { + glGenTextures(1, &buffer->textureId); + if (buffer->textureId == 0) { + qWarning("QXcbSharedGraphicsCache::textureIdForBuffer: Failed to generate texture (gl error: 0x%x)", + glGetError()); + return 0; + } + + glBindTexture(GL_TEXTURE_2D, buffer->textureId); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glBindTexture(GL_TEXTURE_2D, buffer->textureId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, buffer->width, buffer->height, 0, GL_ALPHA, + GL_UNSIGNED_BYTE, buffer->buffer->data()); + glBindTexture(GL_TEXTURE_2D, 0); + + return buffer->textureId; +} + +void QXcbSharedGraphicsCache::referenceBuffer(void *bufferId) +{ + QXcbSharedBufferManager::Buffer *buffer = + reinterpret_cast(bufferId); + + buffer->ref.ref(); +} + +bool QXcbSharedGraphicsCache::dereferenceBuffer(void *bufferId) +{ + QXcbSharedBufferManager::Buffer *buffer = + reinterpret_cast(bufferId); + + if (buffer->ref.deref()) + return true; + + if (buffer->textureId != 0) { + glDeleteTextures(1, &buffer->textureId); + buffer->textureId = 0; + } + + return false; +} + +void QXcbSharedGraphicsCache::processPendingItems() +{ +# if defined(SHAREDGRAPHICSCACHE_DEBUG) + qDebug("QXcbSharedGraphicsCache::processPendingItems"); +# endif + + { + QXcbSharedBufferManager::PendingItemIds pendingMissingItems = m_bufferManager->pendingItemsMissing(); + QXcbSharedBufferManager::PendingItemIds::const_iterator it; + + + for (it = pendingMissingItems.constBegin(); it != pendingMissingItems.constEnd(); ++it) { + QVector missingItems; + + const QSet &items = it.value(); + QSet::const_iterator itemIt; + for (itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) + missingItems.append(*itemIt); + +# if defined(SHAREDGRAPHICSCACHE_DEBUG) + qDebug("QXcbSharedGraphicsCache::processPendingItems: %d missing items", + missingItems.size()); +# endif + + if (!missingItems.isEmpty()) + emit itemsMissing(it.key(), missingItems); + } + } + + { + QXcbSharedBufferManager::PendingItemIds pendingInvalidatedItems = m_bufferManager->pendingItemsInvalidated(); + QXcbSharedBufferManager::PendingItemIds::const_iterator it; + + for (it = pendingInvalidatedItems.constBegin(); it != pendingInvalidatedItems.constEnd(); ++it) { + QVector invalidatedItems; + + const QSet &items = it.value(); + QSet::const_iterator itemIt; + for (itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) + invalidatedItems.append(*itemIt); + +# if defined(SHAREDGRAPHICSCACHE_DEBUG) + qDebug("QXcbSharedGraphicsCache::processPendingItems: %d invalidated items", + invalidatedItems.size()); +# endif + + if (!invalidatedItems.isEmpty()) + emit itemsInvalidated(it.key(), invalidatedItems); + } + } + + { + QXcbSharedBufferManager::PendingItemIds pendingReadyItems = m_bufferManager->pendingItemsReady(); + QXcbSharedBufferManager::PendingItemIds::const_iterator it; + + for (it = pendingReadyItems.constBegin(); it != pendingReadyItems.constEnd(); ++it) { + QHash readyItemsForBuffer; + const QSet &items = it.value(); + + QByteArray cacheId = it.key(); + + QSet::const_iterator itemIt; + for (itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) { + QXcbSharedBufferManager::Buffer *buffer; + int x = -1; + int y = -1; + + m_bufferManager->getBufferForItem(cacheId, *itemIt, &buffer, &x, &y); + + readyItemsForBuffer[buffer].itemIds.append(*itemIt); + readyItemsForBuffer[buffer].positions.append(QPoint(x, y)); + } + + QHash::iterator readyItemIt + = readyItemsForBuffer.begin(); + while (readyItemIt != readyItemsForBuffer.end()) { + QXcbSharedBufferManager::Buffer *buffer = readyItemIt.key(); + if (!readyItemIt.value().itemIds.isEmpty()) { +# if defined(SHAREDGRAPHICSCACHE_DEBUG) + qDebug("QXcbSharedGraphicsCache::processPendingItems: %d ready items", + readyItemIt.value().itemIds.size()); +# endif + + emit itemsAvailable(cacheId, buffer, QSize(buffer->width, buffer->height), + readyItemIt.value().itemIds, readyItemIt.value().positions); + } + ++readyItemIt; + } + } + } +} + +QT_END_NAMESPACE + +#endif // QT_USE_XCB_SHARED_GRAPHICS_CACHE diff --git a/src/plugins/platforms/xcb/qxcbsharedgraphicscache.h b/src/plugins/platforms/xcb/qxcbsharedgraphicscache.h new file mode 100644 index 0000000000..2b37c334b7 --- /dev/null +++ b/src/plugins/platforms/xcb/qxcbsharedgraphicscache.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QXCBSHAREDGRAPHICSCACHE +#define QXCBSHAREDGRAPHICSCACHE + +#if defined(QT_USE_XCB_SHARED_GRAPHICS_CACHE) + +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QXcbSharedBufferManager; +class QXcbSharedGraphicsCache : public QPlatformSharedGraphicsCache +{ + Q_OBJECT +public: + explicit QXcbSharedGraphicsCache(QObject *parent = 0); + + virtual void ensureCacheInitialized(const QByteArray &cacheId, BufferType bufferType, + PixelFormat pixelFormat); + + virtual void requestItems(const QByteArray &cacheId, const QVector &itemIds); + virtual void insertItems(const QByteArray &cacheId, + const QVector &itemIds, + const QVector &items); + virtual void releaseItems(const QByteArray &cacheId, const QVector &itemIds); + + virtual void serializeBuffer(void *bufferId, QByteArray *serializedData, int *fileDescriptor) const; + virtual uint textureIdForBuffer(void *bufferId); + virtual void referenceBuffer(void *bufferId); + virtual bool dereferenceBuffer(void *bufferId); + +private: + struct ReadyItem { + QVector itemIds; + QVector positions; + }; + + void processPendingItems(); + + QXcbSharedBufferManager *m_bufferManager; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QT_USE_XCB_SHARED_GRAPHICS_CACHE + +#endif // QXCBSHAREDGRAPHICSCACHE diff --git a/src/plugins/platforms/xcb/xcb.pro b/src/plugins/platforms/xcb/xcb.pro index 823a12b0f4..d80a6df0b6 100644 --- a/src/plugins/platforms/xcb/xcb.pro +++ b/src/plugins/platforms/xcb/xcb.pro @@ -5,6 +5,7 @@ QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/platforms QT += core-private gui-private platformsupport-private + SOURCES = \ qxcbclipboard.cpp \ qxcbconnection.cpp \ @@ -19,7 +20,9 @@ SOURCES = \ main.cpp \ qxcbnativeinterface.cpp \ qxcbcursor.cpp \ - qxcbimage.cpp + qxcbimage.cpp \ + qxcbsharedbuffermanager.cpp \ + qxcbsharedgraphicscache.cpp HEADERS = \ qxcbclipboard.h \ @@ -35,7 +38,9 @@ HEADERS = \ qxcbwmsupport.h \ qxcbnativeinterface.h \ qxcbcursor.h \ - qxcbimage.h + qxcbimage.h \ + qxcbsharedbuffermanager.h \ + qxcbsharedgraphicscache.h contains(QT_CONFIG, xcb-poll-for-queued-event) { DEFINES += XCB_POLL_FOR_QUEUED_EVENT -- cgit v1.2.3