diff options
Diffstat (limited to 'src/threed/textures')
-rw-r--r-- | src/threed/textures/qareaallocator.cpp | 877 | ||||
-rw-r--r-- | src/threed/textures/qareaallocator.h | 169 | ||||
-rw-r--r-- | src/threed/textures/qglsharedresource.cpp | 236 | ||||
-rw-r--r-- | src/threed/textures/qglsharedresource_p.h | 96 | ||||
-rw-r--r-- | src/threed/textures/qgltexture2d.cpp | 697 | ||||
-rw-r--r-- | src/threed/textures/qgltexture2d.h | 110 | ||||
-rw-r--r-- | src/threed/textures/qgltexture2d_p.h | 116 | ||||
-rw-r--r-- | src/threed/textures/qgltexturecube.cpp | 549 | ||||
-rw-r--r-- | src/threed/textures/qgltexturecube.h | 112 | ||||
-rw-r--r-- | src/threed/textures/qgltextureutils.cpp | 785 | ||||
-rw-r--r-- | src/threed/textures/qgltextureutils_p.h | 164 | ||||
-rw-r--r-- | src/threed/textures/textures.pri | 15 |
12 files changed, 3926 insertions, 0 deletions
diff --git a/src/threed/textures/qareaallocator.cpp b/src/threed/textures/qareaallocator.cpp new file mode 100644 index 000000000..d6d92de88 --- /dev/null +++ b/src/threed/textures/qareaallocator.cpp @@ -0,0 +1,877 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qareaallocator.h" +#include "qglnamespace.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QAreaAllocator + \brief The QAreaAllocator class provides facilities for allocating sub-regions from a rectangular image. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::textures + \internal + + Performance on a system can sometimes be improved by storing + multiple small images in a single large image. This reduces + memory allocation overhead and GPU state switching costs. + + QAreaAllocator and its subclasses implement standard strategies + for sub-region allocation in images without tying those strategies + to specific technologies such as raster, OpenGL, etc. + + Allocations are managed in a virtual two-dimensional space. + The caller performs the actual texture upload based on the sub-region + that allocate() returns. + + The caller can return a sub-region to the allocation pool with + release(). Note that not all strategies support release(). + + \sa QSimpleAreaAllocator, QGeneralAreaAllocator, QUniformAreaAllocator +*/ + +/*! + \class QSimpleAreaAllocator + \brief The QSimpleAreaAllocator class provides a simple allocation policy for simple-sized sub-allocations. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::textures + \internal + + QSimpleAreaAllocator uses a trivial allocation strategy whereby + sub-regions are allocated in rows, with a new row started each + time the previous row fills up. Space is never reclaimed by + release(). + + This allocator is suitable for use when the allocations will all + be of a similar size and all regions will be discarded at + the same time when the allocator is destroyed. An example would + be a font glyph manager. + + \sa QAreaAllocator, QGeneralAreaAllocator +*/ + +/*! + \class QGeneralAreaAllocator + \brief The QGeneralAreaAllocator class provides a general allocation policy for sub-regions in an image. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::textures + \internal + + QGeneralAreaAllocator can handle arbitrary-sized allocations up to + size(), in any order, and can release() previously allocated regions. + It uses a binary subdivision algorithm on the image, which may result + in fragmentation under heavy load. + + While technically any size sub-region up to size() can be allocated, + once subdivision begins, the sizes that can be allocated will reduce + substantially. It is recommended that incoming requests be size() / 4 + or less for best performance. + + If the sub-region sizes to be allocated are very similar, and release() + is not necessary, then QSimpleAreaAllocator may work better than + QGeneralAreaAllocator. If the sizes are very similar, and + release() is necessary, then QUniformAreaAllocator may work better + than QGeneralAreaAllocator. + + \sa QAreaAllocator, QSimpleAreaAllocator, QUniformAreaAllocator +*/ + +/*! + \class QUniformAreaAllocator + \brief The QUniformAreaAllocator class provides an allocation policy for uniform-sized areas. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::textures + \internal + + QUniformAreaAllocator allocates any size up to uniformSize() + by dividing size() up into a grid of uniformSize() areas. + Areas can be deallocated with release() and returned to the pool. + + This allocator is suitable for use when the allocations will all + be of a similar size. Unlike QSimpleAreaAllocator, this class + can release allocations. + + \sa QAreaAllocator, QSimpleAreaAllocator, QGeneralAreaAllocator +*/ + +/*! + \internal + + Constructs a new area allocator that is initially \a size pixels + in size. + + \sa expand() +*/ +QAreaAllocator::QAreaAllocator(const QSize &size) + : m_size(size) + , m_minAlloc(1, 1) + , m_margin(0, 0) +{ +} + +/*! + \internal + + Destroys this area allocator. +*/ +QAreaAllocator::~QAreaAllocator() +{ +} + +/*! + \fn QSize QAreaAllocator::size() const + \internal + + Returns the current size of the area being used by this allocator. +*/ + +/*! + \fn QSize QAreaAllocator::minimumAllocation() const + \internal + + Returns the minimum allocation size in the x and y directions + for areas returned by allocate(). The default is (1, 1). + + \sa setMinimumAllocation() +*/ + +/*! + \fn void QAreaAllocator::setMinimumAllocation(const QSize &size) + \internal + + Sets the minimum allocation \a size in the x and y directions + for areas returned by allocate(). + + For example, setting the minimum allocation to (8, 8) will force + all allocations to be aligned on an 8-pixel boundary. + + \sa minimumAllocation() +*/ + +/*! + \fn QSize QAreaAllocator::margin() const + \internal + + Returns the margin that should be left between allocated items + in the x and y directions. The default is (0, 0). + + This may be needed when using OpenGL textures because of + rounding errors in the floating-point representation of + texture co-ordinates. Leaving a small margin between allocations + can help avoid adjacent images from bleeding into each other. + + \sa setMargin() +*/ + +/*! + \fn void QAreaAllocator::setMargin(const QSize &margin) + \internal + + Sets the \a margin that should be left between allocated items + in the x and y directions. + + \sa margin() +*/ + +/*! + \internal + + Expands this allocator to encompass the width and height of \a size. + If the area is already larger, this function does nothing. + + The rectangles that were returned for previous allocations will + remain valid. + + \sa expandBy() +*/ +void QAreaAllocator::expand(const QSize &size) +{ + int newWidth = qMax(m_size.width(), size.width()); + int newHeight = qMax(m_size.height(), size.height()); + m_size = QSize(newWidth, newHeight); +} + +/*! + \internal + + Expands this allocator by \a size pixels in the x and y directions. + + For example, expanding by (0, 128) will add 128 additional pixels + of height but will leave the width unchanged. + + \sa expand() +*/ +void QAreaAllocator::expandBy(const QSize &size) +{ + expand(m_size + size); +} + +/*! + \fn QRect QAreaAllocator::allocate(const QSize &size) + \internal + + Allocates \a size pixels from this allocator and returns the rectangle + that should be used by the caller. Returns a null rectangle if + this allocator does not have sufficient space to accommodate \a size. + + \sa release() +*/ + +/*! + \internal + + Allocates and returns a list of rectangles corresponding to the + elements of \a sizes. The returned list will have less elements + than \a sizes if there is insufficient space to accommodate + all of the allocation requests. The values that are in the returned + list will be allocated and need to be passed to release() to + deallocate them. + + The default implementation will call the subclass allocate() once + for each size until all \a sizes have been allocated or an + allocation fails. Subclasses may override this method to + reorder the allocations for best-fit. + + \sa release() +*/ +QList<QRect> QAreaAllocator::allocate(const QList<QSize> &sizes) +{ + QList<QRect> rects; + QRect rect; + for (int index = 0; index < sizes.count(); ++index) { + rect = allocate(sizes[index]); + if (rect.isNull()) + break; + rects.append(rect); + } + return rects; +} + +/*! + \internal + + Releases the space occupied by \a rect back to the allocator. + The default implementation does nothing. + + The \a rect must have been returned by a previous call to allocate(). + Otherwise the behaviour is undefined. + + \sa allocate() +*/ +void QAreaAllocator::release(const QRect &rect) +{ + Q_UNUSED(rect); +} + +/*! + \internal + + Releases the space occupied by the members of \a rects back to + the allocator. The default implementation calls release() for + each rectangle in the list. + + The members of \a rects must have been returned by previous calls + to allocate(). Otherwise the behaviour is undefined. + + \sa allocate() +*/ +void QAreaAllocator::release(const QList<QRect> &rects) +{ + for (int index = 0; index < rects.count(); ++index) + release(rects[index]); +} + +/*! + \internal + + Returns a rough estimate of the number of bytes of overhead that + are currently in use to store the house-keeping data structures + for this area allocator. The default implementation returns zero. +*/ +int QAreaAllocator::overhead() const +{ + return 0; +} + +/*! + \internal + + Returns \a size, after rounding it up to account for + minimumAllocation() and margin(). + + This is a convenience function, provided for subclass overrides + of allocate(). + + \sa allocate() +*/ +QSize QAreaAllocator::roundAllocation(const QSize &size) const +{ + int width = size.width() + m_margin.width(); + int height = size.height() + m_margin.height(); + int extra = width % m_minAlloc.width(); + if (extra) + width += m_minAlloc.width() - extra; + extra = height % m_minAlloc.height(); + if (extra) + height += m_minAlloc.height() - extra; + return QSize(width, height); +} + +/*! + \internal + + Constructs a simple area allocator that is initially \a size pixels + in size. +*/ +QSimpleAreaAllocator::QSimpleAreaAllocator(const QSize &size) + : QAreaAllocator(size) + , m_row(0) + , m_column(0) + , m_rowHeight(0) +{ +} + +/*! + \internal + + Destroys this simple area allocator. +*/ +QSimpleAreaAllocator::~QSimpleAreaAllocator() +{ +} + +/*! + \internal +*/ +QRect QSimpleAreaAllocator::allocate(const QSize &size) +{ + // Round up the allocation size to account for the margin and + // minimum allocation parameters. + QSize rounded = roundAllocation(size); + int width = rounded.width(); + int height = rounded.height(); + + // Bail out if the size is obviously too small or too big. + if (width <= 0 || width > m_size.width()) + return QRect(); + if (height <= 0 || height > (m_size.height() - m_row)) + return QRect(); + + // Do we need to place this allocation on a new row? + int row = m_row; + int column = m_column; + int rowHeight = m_rowHeight; + if ((column + width) > m_size.width()) { + row += m_rowHeight; + column = 0; + rowHeight = 0; + if (height > (m_size.height() - row)) + return QRect(); + } + + // Update the current allocation position. + m_row = row; + m_column = column + width; + m_rowHeight = qMax(rowHeight, height); + + // Return the allocation, using the original size without rounding. + return QRect(column, row, size.width(), size.height()); +} + +/*! + \internal + + Constructs a general area allocator that is initially \a size pixels + in size. The \a size will be rounded up to the next power of two, + to simplify the internal allocation policy. + + This constructor sets minimumAllocation() to (8, 8) to reduce the + housekeeping overhead of the internal data structures. +*/ +QGeneralAreaAllocator::QGeneralAreaAllocator(const QSize &size) + : QAreaAllocator(QGL::nextPowerOfTwo(size)) +{ + m_root = new Node(); + m_root->rect = QRect(0, 0, m_size.width(), m_size.height()); + m_root->largestFree = m_size; + m_root->parent = 0; + m_root->left = 0; + m_root->right = 0; + m_nodeCount = 1; + setMinimumAllocation(QSize(8, 8)); +} + +/*! + \internal + + Destroys this general area allocator. +*/ +QGeneralAreaAllocator::~QGeneralAreaAllocator() +{ + freeNode(m_root); +} + +/*! + \internal +*/ +void QGeneralAreaAllocator::freeNode(Node *node) +{ + if (node) { + freeNode(node->left); + freeNode(node->right); + } + delete node; +} + +/*! + \internal + + The \a size will be rounded up to the next power of two. + Use size() to determine the actual size after expansion. +*/ +void QGeneralAreaAllocator::expand(const QSize &size) +{ + QAreaAllocator::expand(QGL::nextPowerOfTwo(size)); + + if (m_root->rect.size() == m_size) + return; // No change. + if (!m_root->left && m_root->largestFree.width() > 0) { + // No allocations have occurred, so just adjust the root size. + m_root->rect = QRect(0, 0, m_size.width(), m_size.height()); + m_root->largestFree = m_size; + return; + } + + // Add extra nodes above the current root to expand the tree. + Node *oldRoot = m_root; + Split split; + if (m_size.width() >= m_size.height()) + split = SplitOnX; + else + split = SplitOnY; + while (m_root->rect.size() != m_size) { + if (m_root->rect.width() == m_size.width()) + split = SplitOnY; + else if (m_root->rect.height() == m_size.height()) + split = SplitOnX; + Node *parent = new Node(); + Node *right = new Node(); + m_nodeCount += 2; + m_root->parent = parent; + parent->parent = 0; + parent->left = m_root; + parent->right = right; + parent->largestFree = m_root->rect.size(); + right->parent = parent; + right->left = 0; + right->right = 0; + right->largestFree = m_root->rect.size(); + if (split == SplitOnX) { + parent->rect = QRect(m_root->rect.x(), m_root->rect.y(), + m_root->rect.width() * 2, + m_root->rect.height()); + right->rect = QRect(m_root->rect.x() + m_root->rect.width(), + m_root->rect.y(), + m_root->rect.width(), m_root->rect.height()); + } else { + parent->rect = QRect(m_root->rect.x(), m_root->rect.y(), + m_root->rect.width(), + m_root->rect.height() * 2); + right->rect = QRect(m_root->rect.x(), + m_root->rect.y() + m_root->rect.width(), + m_root->rect.width(), m_root->rect.height()); + } + split = (split == SplitOnX ? SplitOnY : SplitOnX); + m_root = parent; + } + updateLargestFree(oldRoot); +} + +static inline bool fitsWithin(const QSize &size1, const QSize &size2) +{ + return size1.width() <= size2.width() && size1.height() <= size2.height(); +} + +/*! + \internal +*/ +QRect QGeneralAreaAllocator::allocate(const QSize &size) +{ + QSize rounded = roundAllocation(size); + rounded = QGL::nextPowerOfTwo(rounded); + if (rounded.width() <= 0 || rounded.width() > m_size.width() || + rounded.height() <= 0 || rounded.height() > m_size.height()) + return QRect(); + QPoint point = allocateFromNode(rounded, m_root); + if (point.x() >= 0) + return QRect(point, size); + else + return QRect(); +} + +/*! + \internal +*/ +QPoint QGeneralAreaAllocator::allocateFromNode(const QSize &size, Node *node) +{ + // Find the best node to insert into, which should be + // a node with the least amount of unused space that is + // big enough to contain the requested size. + while (node != 0) { + // Go down a level and determine if the left or right + // sub-tree contains the best chance of allocation. + Node *left = node->left; + Node *right = node->right; + if (left && fitsWithin(size, left->largestFree)) { + if (right && fitsWithin(size, right->largestFree)) { + if (left->largestFree.width() < right->largestFree.width() || + left->largestFree.height() < right->largestFree.height()) { + // The largestFree values may be a little oversized, + // so try the left sub-tree and then the right sub-tree. + QPoint point = allocateFromNode(size, left); + if (point.x() >= 0) + return point; + else + return allocateFromNode(size, right); + } else { + node = right; + } + } else { + node = left; + } + } else if (right && fitsWithin(size, right->largestFree)) { + node = right; + } else if (left || right) { + // Neither sub-node has enough space to allocate from. + return QPoint(-1, -1); + } else if (fitsWithin(size, node->largestFree)) { + // Do we need to split this node into smaller pieces? + Split split; + if (fitsWithin(QSize(size.width() * 2, size.height() * 2), + node->largestFree)) { + // Split in either direction: choose the inverse of + // the parent node's split direction to try to balance + // out the wasted space as further subdivisions happen. + if (node->parent && + node->parent->left->rect.x() == + node->parent->right->rect.x()) + split = SplitOnX; + else if (node->parent) + split = SplitOnY; + else if (node->rect.width() >= node->rect.height()) + split = SplitOnX; + else + split = SplitOnY; + } else if (fitsWithin(QSize(size.width() * 2, size.height()), + node->largestFree)) { + // Split along the X direction. + split = SplitOnX; + } else if (fitsWithin(QSize(size.width(), size.height() * 2), + node->largestFree)) { + // Split along the Y direction. + split = SplitOnY; + } else { + // Cannot split further - allocate this node. + node->largestFree = QSize(0, 0); + updateLargestFree(node); + return node->rect.topLeft(); + } + + // Split the node, then go around again using the left sub-tree. + node = splitNode(node, split); + } else { + // Cannot possibly fit into this node. + break; + } + } + return QPoint(-1, -1); +} + +/*! + \internal +*/ +QGeneralAreaAllocator::Node *QGeneralAreaAllocator::splitNode + (Node *node, Split split) +{ + Node *left = new Node(); + Node *right = new Node(); + m_nodeCount += 2; + left->parent = node; + left->left = 0; + left->right = 0; + right->parent = node; + right->left = 0; + right->right = 0; + node->left = left; + node->right = right; + if (split == SplitOnX) { + left->rect = QRect(node->rect.x(), node->rect.y(), + node->rect.width() / 2, + node->rect.height()); + right->rect = QRect(left->rect.right() + 1, node->rect.y(), + node->rect.width() / 2, + node->rect.height()); + } else { + left->rect = QRect(node->rect.x(), node->rect.y(), + node->rect.width(), + node->rect.height() / 2); + right->rect = QRect(node->rect.x(), left->rect.bottom() + 1, + node->rect.width(), + node->rect.height() / 2); + } + left->largestFree = left->rect.size(); + right->largestFree = right->rect.size(); + node->largestFree = right->largestFree; + return left; +} + +/*! + \internal +*/ +void QGeneralAreaAllocator::updateLargestFree(Node *node) +{ + while ((node = node->parent) != 0) { + node->largestFree = + QSize(qMax(node->left->largestFree.width(), + node->right->largestFree.width()), + qMax(node->left->largestFree.height(), + node->right->largestFree.height())); + } +} + +/*! + \internal +*/ +void QGeneralAreaAllocator::release(const QRect &rect) +{ + // Locate the node that contains the allocated region. + Node *node = m_root; + QPoint point = rect.topLeft(); + while (node != 0) { + if (node->left && node->left->rect.contains(point)) + node = node->left; + else if (node->right && node->right->rect.contains(point)) + node = node->right; + else if (node->rect.contains(point)) + break; + else + return; // Point is completely outside the tree. + } + if (!node) + return; + + // Mark the node as free and then work upwards through the tree + // recombining and deleting nodes until we reach a sibling + // that is still allocated. + node->largestFree = node->rect.size(); + while (node->parent) { + if (node->parent->left == node) { + if (node->parent->right->largestFree != + node->parent->right->rect.size()) + break; + } else { + if (node->parent->left->largestFree != + node->parent->left->rect.size()) + break; + } + node = node->parent; + freeNode(node->left); + freeNode(node->right); + m_nodeCount -= 2; + node->left = 0; + node->right = 0; + node->largestFree = node->rect.size(); + } + + // Make the rest of our ancestors have the correct "largest free size". + updateLargestFree(node); +} + +/*! + \internal +*/ +int QGeneralAreaAllocator::overhead() const +{ + return m_nodeCount * sizeof(Node); +} + +/*! + \internal + + Constructs a uniform area allocator that is initially \a size pixels + in size. The \a uniformSize specifies the single allocation size + that is supported. All allocate() requests must be \a uniformSize + or less. +*/ +QUniformAreaAllocator::QUniformAreaAllocator + (const QSize &size, const QSize &uniformSize) + : QAreaAllocator(size), m_uniformSize(uniformSize), m_firstFree(0) +{ + Q_ASSERT(uniformSize.width() > 0 && uniformSize.height() > 0); + Q_ASSERT(size.width() >= uniformSize.width() && + size.height() >= uniformSize.height()); + m_gridSize = QSize(size.width() / uniformSize.width(), + size.height() / uniformSize.height()); + int count = m_gridSize.width() * m_gridSize.height(); + m_grid = new int [count]; + for (int index = 0; index < (count - 1); ++index) + m_grid[index] = index + 1; + m_grid[count - 1] = -1; +} + +/*! + \internal + + Destroys this uniform area allocator. +*/ +QUniformAreaAllocator::~QUniformAreaAllocator() +{ + delete [] m_grid; +} + +/*! + \fn QSize QUniformAreaAllocator::uniformSize() const + \internal + + Returns the uniform size of all allocations. + + \sa allocate() +*/ + +/*! + \internal +*/ +void QUniformAreaAllocator::expand(const QSize &size) +{ + QAreaAllocator::expand(size); + + QSize newGridSize = QSize(m_size.width() / m_uniformSize.width(), + m_size.height() / m_uniformSize.height()); + if (m_gridSize == newGridSize) + return; + + // Create a new grid. + int newCount = newGridSize.width() * newGridSize.height(); + int *newGrid = new int [newCount]; + + // Copy across the free blocks from the old grid. + int posn = m_firstFree; + int newFirstFree = -1; + while (posn != -1) { + int x = posn % m_gridSize.width(); + int y = posn / m_gridSize.width(); + int newPosn = x + y * newGridSize.width(); + newGrid[newPosn] = newFirstFree; + newFirstFree = newPosn; + posn = m_grid[posn]; + } + + // Add free blocks within the expanded area of the new grid. + for (int y = 0; y < m_gridSize.height(); ++y) { + int newPosn = y * newGridSize.width() + m_gridSize.width(); + for (int x = m_gridSize.width(); x < newGridSize.width(); ++x) { + newGrid[newPosn] = newFirstFree; + newFirstFree = newPosn; + ++newPosn; + } + } + for (int y = m_gridSize.height(); y < newGridSize.height(); ++y) { + int newPosn = y * newGridSize.width(); + for (int x = 0; x < newGridSize.width(); ++x) { + newGrid[newPosn] = newFirstFree; + newFirstFree = newPosn; + ++newPosn; + } + } + + // Replace the old grid. + delete [] m_grid; + m_grid = newGrid; + m_gridSize = newGridSize; + m_firstFree = newFirstFree; +} + +/*! + \internal +*/ +QRect QUniformAreaAllocator::allocate(const QSize &size) +{ + QSize rounded = roundAllocation(size); + if (rounded.width() > m_uniformSize.width() || + rounded.height() > m_uniformSize.height()) + return QRect(); + int posn = m_firstFree; + if (posn == -1) + return QRect(); + m_firstFree = m_grid[posn]; + int x = posn % m_gridSize.width(); + int y = posn / m_gridSize.width(); + return QRect(x * m_uniformSize.width(), y * m_uniformSize.height(), + size.width(), size.height()); +} + +/*! + \internal +*/ +void QUniformAreaAllocator::release(const QRect &rect) +{ + int x = rect.x() / m_uniformSize.width(); + int y = rect.y() / m_uniformSize.height(); + int posn = x + y * m_gridSize.width(); + Q_ASSERT(posn >= 0 && posn < m_gridSize.width() * m_gridSize.height()); + m_grid[posn] = m_firstFree; + m_firstFree = posn; +} + +/*! + \internal +*/ +int QUniformAreaAllocator::overhead() const +{ + return sizeof(int) * m_gridSize.width() * m_gridSize.height(); +} + +QT_END_NAMESPACE diff --git a/src/threed/textures/qareaallocator.h b/src/threed/textures/qareaallocator.h new file mode 100644 index 000000000..a4d4f3dff --- /dev/null +++ b/src/threed/textures/qareaallocator.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QAREAALLOCATOR_P_H +#define QAREAALLOCATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qt3dglobal.h" +#include <QtCore/qsize.h> +#include <QtCore/qrect.h> +#include <QtCore/qlist.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class Q_QT3D_EXPORT QAreaAllocator +{ +public: + QAreaAllocator(const QSize &size); + virtual ~QAreaAllocator(); + + QSize size() const { return m_size; } + + QSize minimumAllocation() const { return m_minAlloc; } + void setMinimumAllocation(const QSize &size) { m_minAlloc = size; } + + QSize margin() const { return m_margin; } + void setMargin(const QSize &margin) { m_margin = margin; } + + virtual void expand(const QSize &size); + void expandBy(const QSize &size); + + virtual QRect allocate(const QSize &size) = 0; + virtual QList<QRect> allocate(const QList<QSize> &sizes); + virtual void release(const QRect &rect); + virtual void release(const QList<QRect> &rects); + + virtual int overhead() const; + +protected: + QSize m_size; + QSize m_minAlloc; + QSize m_margin; + + QSize roundAllocation(const QSize &size) const; +}; + +class Q_QT3D_EXPORT QSimpleAreaAllocator : public QAreaAllocator +{ +public: + QSimpleAreaAllocator(const QSize &size); + virtual ~QSimpleAreaAllocator(); + + QRect allocate(const QSize &size); + +private: + int m_row; + int m_column; + int m_rowHeight; +}; + +class Q_QT3D_EXPORT QGeneralAreaAllocator : public QAreaAllocator +{ +public: + QGeneralAreaAllocator(const QSize &size); + virtual ~QGeneralAreaAllocator(); + + void expand(const QSize &size); + QRect allocate(const QSize &size); + void release(const QRect &rect); + int overhead() const; + +private: + enum Split { SplitOnX, SplitOnY }; + + struct Node + { + QRect rect; + QSize largestFree; + Node *parent; + Node *left; + Node *right; + }; + + Node *m_root; + int m_nodeCount; + + static void freeNode(Node *node); + QPoint allocateFromNode(const QSize &size, Node *node); + Node *splitNode(Node *node, Split split); + static void updateLargestFree(Node *node); +}; + +class Q_QT3D_EXPORT QUniformAreaAllocator : public QAreaAllocator +{ +public: + QUniformAreaAllocator(const QSize &size, const QSize &uniformSize); + virtual ~QUniformAreaAllocator(); + + QSize uniformSize() const { return m_uniformSize; } + + void expand(const QSize &size); + QRect allocate(const QSize &size); + void release(const QRect &rect); + int overhead() const; + +private: + QSize m_uniformSize; + QSize m_gridSize; + int *m_grid; + int m_firstFree; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/textures/qglsharedresource.cpp b/src/threed/textures/qglsharedresource.cpp new file mode 100644 index 000000000..91bef8972 --- /dev/null +++ b/src/threed/textures/qglsharedresource.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsharedresource_p.h" +#include <QtCore/qmutex.h> +#include <QtCore/qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +#if !defined(Q_MOC_RUN) + +class Q_OPENGL_EXPORT QGLSignalProxy : public QObject +{ + Q_OBJECT +public: + QGLSignalProxy() : QObject() {} + void emitAboutToDestroyContext(const QGLContext *context) { + emit aboutToDestroyContext(context); + } + static QGLSignalProxy *instance(); +Q_SIGNALS: + void aboutToDestroyContext(const QGLContext *context); +}; + +#endif + +class QGLContextInfo +{ +public: + QGLContextInfo(const QGLContext *ctx) : m_context(ctx), m_resources(0) {} + ~QGLContextInfo(); + + const QGLContext *m_context; + QGLSharedResource *m_resources; +}; + +QGLContextInfo::~QGLContextInfo() +{ + // Detach this information block from all of the shared resources + // that used to be owned by it. + QGLSharedResource *resource = m_resources; + while (resource != 0) { + resource->m_contextInfo = 0; + resource->m_id = 0; + resource = resource->m_next; + } +} + +class QGLContextManager : public QObject +{ + Q_OBJECT +public: + QGLContextManager(QObject *parent = 0); + ~QGLContextManager(); + + QMutex managerLock; + + QGLContextInfo *contextInfo(const QGLContext *ctx); + +private Q_SLOTS: + void aboutToDestroyContext(const QGLContext *ctx); + +private: + QList<QGLContextInfo *> m_contexts; +}; + +Q_GLOBAL_STATIC(QGLContextManager, qt_gl_context_manager) + +QGLContextManager::QGLContextManager(QObject *parent) + : QObject(parent) +{ + QGLSignalProxy *proxy = QGLSignalProxy::instance(); + QThread *mainThread = qApp->thread(); + if (thread() != mainThread) { + // The manager and signal proxy have been created for the first + // time in a background thread. For safety, move both objects + // to the main thread. + moveToThread(mainThread); + proxy->moveToThread(mainThread); + } + connect(proxy, SIGNAL(aboutToDestroyContext(const QGLContext *)), + this, SLOT(aboutToDestroyContext(const QGLContext *))); +} + +QGLContextManager::~QGLContextManager() +{ + QMutexLocker locker(&managerLock); + qDeleteAll(m_contexts); +} + +QGLContextInfo *QGLContextManager::contextInfo(const QGLContext *ctx) +{ + QGLContextInfo *info; + for (int index = 0; index < m_contexts.size(); ++index) { + info = m_contexts[index]; + if (info->m_context == ctx) + return info; + } + info = new QGLContextInfo(ctx); + m_contexts.append(info); + return info; +} + +Q_OPENGL_EXPORT const QGLContext *qt_gl_transfer_context(const QGLContext *); + +void QGLContextManager::aboutToDestroyContext(const QGLContext *ctx) +{ + QMutexLocker locker(&managerLock); + int index = 0; + while (index < m_contexts.size()) { + QGLContextInfo *info = m_contexts[index]; + if (info->m_context == ctx) { + const QGLContext *transfer = qt_gl_transfer_context(ctx); + if (transfer) { + // Transfer ownership to another context in the same sharing + // group. This may result in multiple QGLContextInfo objects + // for the same context, which is ok. + info->m_context = transfer; + } else { + // All contexts in the sharing group have been deleted, + // so detach all of the shared resources. + m_contexts.removeAt(index); + delete info; + continue; + } + } + ++index; + } +} + +const QGLContext *QGLSharedResource::context() const +{ + // Hope that the context will not be destroyed in another thread + // while we are doing this so we don't have to acquire the lock. + return m_contextInfo ? m_contextInfo->m_context : 0; +} + +void QGLSharedResource::attach(const QGLContext *context, GLuint id) +{ + Q_ASSERT(!m_contextInfo); + QGLContextManager *manager = qt_gl_context_manager(); + QMutexLocker locker(&(manager->managerLock)); + m_contextInfo = manager->contextInfo(context); + m_id = id; + m_next = m_contextInfo->m_resources; + m_prev = 0; + if (m_contextInfo->m_resources) + m_contextInfo->m_resources->m_prev = this; + m_contextInfo->m_resources = this; +} + +void QGLSharedResource::destroy() +{ + // Detach this resource from the context information block. + QGLContextManager *manager = qt_gl_context_manager(); + const QGLContext *owner = 0; + GLuint id = 0; + manager->managerLock.lock(); + if (m_contextInfo) { + if (m_next) + m_next->m_prev = m_prev; + if (m_prev) + m_prev->m_next = m_next; + else + m_contextInfo->m_resources = m_next; + owner = m_contextInfo->m_context; + id = m_id; + } + m_contextInfo = 0; + m_id = 0; + m_next = 0; + m_prev = 0; + manager->managerLock.unlock(); + + // Switch back to the owning context temporarily and delete the id. + if (owner && id) { + QGLContext *currentContext = const_cast<QGLContext *>(QGLContext::currentContext()); + QGLContext *oldContext; + QGLContext *doneContext; + if (currentContext != owner && !QGLContext::areSharing(owner, currentContext)) { + oldContext = currentContext; + doneContext = const_cast<QGLContext *>(owner); + doneContext->makeCurrent(); + } else { + oldContext = 0; + doneContext = 0; + } + m_destroyFunc(id); + if (oldContext) + oldContext->makeCurrent(); + else if (!currentContext && doneContext) + doneContext->doneCurrent(); + } +} + +QT_END_NAMESPACE + +#include "qglsharedresource.moc" diff --git a/src/threed/textures/qglsharedresource_p.h b/src/threed/textures/qglsharedresource_p.h new file mode 100644 index 000000000..9aafe70a8 --- /dev/null +++ b/src/threed/textures/qglsharedresource_p.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLSHAREDRESOURCE_P_H +#define QGLSHAREDRESOURCE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtOpenGL/qgl.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QGLContextManager; +class QGLContextInfo; + +class QGLSharedResource +{ +public: + typedef void (*DestroyResourceFunc)(GLuint id); + QGLSharedResource(DestroyResourceFunc destroyFunc) + : m_destroyFunc(destroyFunc), m_contextInfo(0), m_id(0) + , m_next(0), m_prev(0) {} + ~QGLSharedResource() { destroy(); } + + const QGLContext *context() const; + GLuint id() const { return m_id; } + void clearId() { m_id = 0; } + + void attach(const QGLContext *context, GLuint id); + void destroy(); + +private: + DestroyResourceFunc m_destroyFunc; + QGLContextInfo *m_contextInfo; + GLuint m_id; + QGLSharedResource *m_next; + QGLSharedResource *m_prev; + + friend class QGLContextManager; + friend class QGLContextInfo; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/textures/qgltexture2d.cpp b/src/threed/textures/qgltexture2d.cpp new file mode 100644 index 000000000..d45a1dc04 --- /dev/null +++ b/src/threed/textures/qgltexture2d.cpp @@ -0,0 +1,697 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgltexture2d.h" +#include "qgltexture2d_p.h" +#include "qgltextureutils_p.h" +#include "qglpainter_p.h" +#include "qglext_p.h" + +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QGLTexture2D + \brief The QGLTexture2D class represents a 2D texture object for GL painting operations. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::textures + + QGLTexture2D contains a QImage and settings for texture filters, + wrap modes, and mipmap generation. When bind() is called, this + information is uploaded to the GL server if it has changed since + the last time bind() was called. + + Once a QGLTexture2D object is created, it can be bound to multiple + GL contexts. Internally, a separate texture identifier is created + for each context. This makes QGLTexture2D easier to use than + raw GL texture identifiers because the application does not need + to be as concerned with whether the texture identifier is valid + in the current context. The application merely calls bind() and + QGLTexture2D will create a new texture identifier for the context + if necessary. + + QGLTexture2D internally points to a reference-counted object that + represents the current texture state. If the QGLTexture2D is copied, + the internal pointer is the same. Modifications to one QGLTexture2D + copy will affect all of the other copies in the system. + + The texture identifiers will be destroyed when the last QGLTexture2D + reference is destroyed, or when a context is destroyed that contained a + texture identifier that was created by QGLTexture2D. + + QGLTexture2D can also be used for uploading 1D textures into the + GL server by specifying an image() with a height of 1. + + \sa QGLTextureCube +*/ + +QGLTexture2DPrivate::QGLTexture2DPrivate() +{ + horizontalWrap = QGL::Repeat; + verticalWrap = QGL::Repeat; + bindOptions = QGLContext::DefaultBindOption; +#if !defined(QT_OPENGL_ES) + mipmapSupported = false; + mipmapSupportedKnown = false; +#endif + imageGeneration = 0; + parameterGeneration = 0; + infos = 0; +} + +QGLTexture2DPrivate::~QGLTexture2DPrivate() +{ + // Destroy the texture id's in the GL server in their original contexts. + QGLTexture2DTextureInfo *current = infos; + QGLTexture2DTextureInfo *next; + const QGLContext *currentContext = + const_cast<QGLContext *>(QGLContext::currentContext()); + const QGLContext *firstContext = currentContext; + while (current != 0) { + next = current->next; + if (current->isLiteral) + current->tex.clearId(); // Don't delete literal id's. + delete current; + current = next; + } + if (firstContext != currentContext) { + if (firstContext) + const_cast<QGLContext *>(firstContext)->makeCurrent(); + else if (currentContext) + const_cast<QGLContext *>(currentContext)->doneCurrent(); + } +} + +/*! + Constructs a null texture object and attaches it to \a parent. + + \sa isNull() +*/ +QGLTexture2D::QGLTexture2D(QObject *parent) + : QObject(parent), d_ptr(new QGLTexture2DPrivate()) +{ +} + +/*! + Destroys this texture object. If this object is the last + reference to the underlying GL texture, then the underlying + GL texture will also be deleted. +*/ +QGLTexture2D::~QGLTexture2D() +{ +} + +/*! + Returns true if this texture object is null; that is, image() + is null and textureId() is zero. +*/ +bool QGLTexture2D::isNull() const +{ + Q_D(const QGLTexture2D); + return d->image.isNull() && !d->infos; +} + +/*! + Returns true if this texture has an alpha channel; false if the + texture is fully opaque. +*/ +bool QGLTexture2D::hasAlphaChannel() const +{ + Q_D(const QGLTexture2D); + if (!d->image.isNull()) + return d->image.hasAlphaChannel(); + QGLTexture2DTextureInfo *info = d->infos; + if (info) + return info->tex.hasAlpha(); + return false; +} + +/*! + Returns the size of this texture. If the underlying OpenGL + implementation requires texture sizes to be a power of two, + then this function will return the next power of two equal + to or greater than requestedSize() + + \sa setSize(), requestedSize() +*/ +QSize QGLTexture2D::size() const +{ + Q_D(const QGLTexture2D); + return d->size; +} + +/*! + Sets the size of this texture to \a value. If the underlying + OpenGL implementation requires texture sizes to be a power of + two, then requestedSize() will be set to \a value, and the + actual size will be set to the next power of two equal + to or greater than \a value. Otherwise both size() and + requestedSize() will be set to \a value. + + \sa size(), requestedSize() +*/ +void QGLTexture2D::setSize(const QSize& value) +{ + Q_D(QGLTexture2D); + if (d->requestedSize == value) + return; + if (!(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) + && !(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0)) + d->size = QGL::nextPowerOfTwo(value); + else + d->size = value; + d->requestedSize = value; + ++(d->imageGeneration); +} + +/*! + Returns the size that was previously set with setSize() before + it was rounded to a power of two. + + \sa size(), setSize() +*/ +QSize QGLTexture2D::requestedSize() const +{ + Q_D(const QGLTexture2D); + return d->requestedSize; +} + +/*! + Returns the image that is currently associated with this texture. + The image may not have been uploaded into the GL server yet. + Uploads occur upon the next call to bind(). + + \sa setImage() +*/ +QImage QGLTexture2D::image() const +{ + Q_D(const QGLTexture2D); + return d->image; +} + +/*! + Sets the \a image that is associated with this texture. The image + will be uploaded into the GL server the next time bind() is called. + + If setSize() or setImage() has been called previously, then \a image + will be scaled to size() when it is uploaded. + + If \a image is null, then this function is equivalent to clearImage(). + + \sa image(), setSize(), copyImage(), setPixmap() +*/ +void QGLTexture2D::setImage(const QImage& image) +{ + Q_D(QGLTexture2D); + d->compressedData = QByteArray(); // Clear compressed file data. + if (image.isNull()) { + // Don't change the imageGeneration, because we aren't actually + // changing the image in the GL server, only the client copy. + d->image = image; + } else { + if (!d->size.isValid()) + setSize(image.size()); + d->image = image; + ++(d->imageGeneration); + } +} + +/*! + Sets the image that is associated with this texture to \a pixmap. + + This is a convenience that calls setImage() after converting + \a pixmap into a QImage. It may be more efficient on some + platforms than the application calling QPixmap::toImage(). + + \sa setImage() +*/ +void QGLTexture2D::setPixmap(const QPixmap& pixmap) +{ + QImage image = pixmap.toImage(); + if (pixmap.depth() == 16 && !image.hasAlphaChannel()) { + // If the system depth is 16 and the pixmap doesn't have an alpha channel + // then we convert it to RGB16 in the hope that it gets uploaded as a 16 + // bit texture which is much faster to access than a 32-bit one. + image = image.convertToFormat(QImage::Format_RGB16); + } + setImage(image); +} + +/*! + Clears the image() that is associated with this texture, but the + GL texture will retain its current value. This can be used to + release client-side memory that is no longer required once the + image has been uploaded into the GL server. + + The following code will queue \c image to be uploaded, immediately + force it to be uploaded into the current GL context, and then + clear the client copy: + + \code + texture.setImage(image); + texture.bind(); + texture.clearImage() + \endcode + + \sa image(), setImage() +*/ +void QGLTexture2D::clearImage() +{ + Q_D(QGLTexture2D); + d->image = QImage(); +} + +#ifndef GL_GENERATE_MIPMAP_SGIS +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +/*! + Sets this texture to the contents of a compressed image file + at \a path. Returns true if the file exists and has a supported + compressed format; false otherwise. + + The DDS, ETC1, PVRTC2, and PVRTC4 compression formats are + supported, assuming that the GL implementation has the + appropriate extension. + + \sa setImage(), setSize() +*/ +bool QGLTexture2D::setCompressedFile(const QString &path) +{ + Q_D(QGLTexture2D); + d->image = QImage(); + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) + { + qWarning("QGLTexture2D::setCompressedFile(%s): File could not be read", + qPrintable(path)); + return false; + } + QByteArray data = f.readAll(); + f.close(); + + bool hasAlpha, isFlipped; + if (!QGLBoundTexture::canBindCompressedTexture + (data.constData(), data.size(), 0, &hasAlpha, &isFlipped)) { + qWarning("QGLTexture2D::setCompressedFile(%s): Format is not supported", + path.toLocal8Bit().constData()); + return false; + } + + QFileInfo fi(path); + d->url = QUrl::fromLocalFile(fi.absoluteFilePath()); + + // The 3DS loader expects the flip state to be set before bind(). + if (isFlipped) + d->bindOptions &= ~QGLContext::InvertedYBindOption; + else + d->bindOptions |= QGLContext::InvertedYBindOption; + + d->compressedData = data; + ++(d->imageGeneration); + return true; +} + +/*! + Returns the url that was last set with setUrl. +*/ +QUrl QGLTexture2D::url() const +{ + Q_D(const QGLTexture2D); + return d->url; +} + +/*! + Sets this texture to have the contents of the image stored at \a url. +*/ +void QGLTexture2D::setUrl(const QUrl &url) +{ + Q_D(QGLTexture2D); + if (d->url == url) + return; + d->url = url; + + if (url.isEmpty()) + { + d->image = QImage(); + } + else + { + if (url.scheme() == QLatin1String("file")) + { + QString fileName = url.toLocalFile(); + if (fileName.endsWith(QLatin1String(".dds"), Qt::CaseInsensitive)) + { + setCompressedFile(fileName); + } + else + { + QImage im(fileName); + if (im.isNull()) + qWarning("Could not load texture: %s", qPrintable(fileName)); + setImage(im); + } + } + else + { + qWarning("Network URLs not yet supported"); + /* + if (d->textureReply) + d->textureReply->deleteLater(); + QNetworkRequest req(d->textureUrl); + req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + d->textureReply = qmlEngine(this)->networkAccessManager()->get(req); + QObject::connect(d->textureReply, SIGNAL(finished()), + this, SLOT(textureRequestFinished())); + */ + } + } +} + +/*! + Copies the contents of \a image to \a offset in this texture + within the current GL context. + + Unlike setImage(), this function copies the image data to the + GL server immediately using \c{glTexSubImage2D()}. This is typically + used to update the contents of a texture after it has been created. + + It is assumed that the application has already called bind() on + this texture to bind it to the current GL context. + + If the texture has been created in multiple contexts, only the + texture identifier for the current context will be updated. + + \sa setImage(), bind() +*/ +void QGLTexture2D::copyImage(const QImage& image, const QPoint& offset) +{ + QImage img = QGLWidget::convertToGLFormat(image); + glTexSubImage2D(GL_TEXTURE_2D, 0, offset.x(), offset.y(), + img.width(), img.height(), GL_RGBA, + GL_UNSIGNED_BYTE, img.bits()); +#if defined(QT_OPENGL_ES_2) + Q_D(QGLTexture2D); + if (d->bindOptions & QGLContext::MipmapBindOption) + glGenerateMipmap(GL_TEXTURE_2D); +#endif +} + +/*! + Returns the options to use when binding the image() to an OpenGL + context for the first time. The default options are + QGLContext::LinearFilteringBindOption | + QGLContext::InvertedYBindOption | QGLContext::MipmapBindOption. + + \sa setBindOptions() +*/ +QGLContext::BindOptions QGLTexture2D::bindOptions() const +{ + Q_D(const QGLTexture2D); + return d->bindOptions; +} + +/*! + Sets the \a options to use when binding the image() to an + OpenGL context. If the image() has already been bound, + then changing the options will cause it to be recreated + from image() the next time bind() is called. + + \sa bindOptions(), bind() +*/ +void QGLTexture2D::setBindOptions(QGLContext::BindOptions options) +{ + Q_D(QGLTexture2D); + if (d->bindOptions != options) { + d->bindOptions = options; + ++(d->imageGeneration); + } +} + +/*! + Returns the wrapping mode for horizontal texture co-ordinates. + The default value is QGL::Repeat. + + \sa setHorizontalWrap(), verticalWrap() +*/ +QGL::TextureWrap QGLTexture2D::horizontalWrap() const +{ + Q_D(const QGLTexture2D); + return d->horizontalWrap; +} + +/*! + Sets the wrapping mode for horizontal texture co-ordinates to \a value. + + If \a value is not supported by the OpenGL implementation, it will be + replaced with a value that is supported. If the application desires a + very specific \a value, it can call horizontalWrap() to check that + the specific value was actually set. + + The \a value will not be applied to the texture in the GL + server until the next call to bind(). + + \sa horizontalWrap(), setVerticalWrap() +*/ +void QGLTexture2D::setHorizontalWrap(QGL::TextureWrap value) +{ + Q_D(QGLTexture2D); + value = qt_gl_modify_texture_wrap(value); + if (d->horizontalWrap != value) { + d->horizontalWrap = value; + ++(d->parameterGeneration); + } +} + +/*! + Returns the wrapping mode for vertical texture co-ordinates. + The default value is QGL::Repeat. + + \sa setVerticalWrap(), horizontalWrap() +*/ +QGL::TextureWrap QGLTexture2D::verticalWrap() const +{ + Q_D(const QGLTexture2D); + return d->verticalWrap; +} + +/*! + Sets the wrapping mode for vertical texture co-ordinates to \a value. + + If \a value is not supported by the OpenGL implementation, it will be + replaced with a value that is supported. If the application desires a + very specific \a value, it can call verticalWrap() to check that + the specific value was actually set. + + The \a value will not be applied to the texture in the GL + server until the next call to bind(). + + \sa verticalWrap(), setHorizontalWrap() +*/ +void QGLTexture2D::setVerticalWrap(QGL::TextureWrap value) +{ + Q_D(QGLTexture2D); + value = qt_gl_modify_texture_wrap(value); + if (d->verticalWrap != value) { + d->verticalWrap = value; + ++(d->parameterGeneration); + } +} + +/*! + Binds this texture to the 2D texture target. + + If this texture object is not associated with an identifier in + the current context, then a new identifier will be created, + and image() uploaded into the GL server. + + If setImage() or setSize() was called since the last upload, + then image() will be re-uploaded to the GL server. + + Returns false if the texture could not be bound for some reason. + + \sa release(), textureId(), setImage() +*/ +bool QGLTexture2D::bind() const +{ + Q_D(const QGLTexture2D); + return const_cast<QGLTexture2DPrivate *>(d)->bind(GL_TEXTURE_2D); +} + +bool QGLTexture2DPrivate::bind(GLenum target) +{ + // Get the current context. If we don't have one, then we + // cannot bind the texture. + const QGLContext *ctx = QGLContext::currentContext(); + if (!ctx) + return false; + + // Find the information block for the context, or create one. + QGLTexture2DTextureInfo *info = infos; + QGLTexture2DTextureInfo *prev = 0; + while (info != 0 && !QGLContext::areSharing(info->tex.context(), ctx)) { + if (info->isLiteral) + return false; // Cannot create extra texture id's for literals. + prev = info; + info = info->next; + } + if (!info) { + info = new QGLTexture2DTextureInfo + (ctx, 0, imageGeneration - 1, parameterGeneration - 1); + if (prev) + prev->next = info; + else + infos = info; + } + + if (!info->tex.textureId() || imageGeneration != info->imageGeneration) { + // Create the texture contents and upload a new image. + info->tex.setOptions(bindOptions); + if (!compressedData.isEmpty()) { + info->tex.bindCompressedTexture + (compressedData.constData(), compressedData.size()); + } else { + info->tex.startUpload(ctx, target, image.size()); + bindImages(info); + info->tex.finishUpload(target); + } + info->imageGeneration = imageGeneration; + } else { + // Bind the existing texture to the texture target. + glBindTexture(target, info->tex.textureId()); + } + + // If the parameter generation has changed, then alter the parameters. + if (parameterGeneration != info->parameterGeneration) { + info->parameterGeneration = parameterGeneration; + q_glTexParameteri(target, GL_TEXTURE_WRAP_S, horizontalWrap); + q_glTexParameteri(target, GL_TEXTURE_WRAP_T, verticalWrap); + } + + // Texture is ready to be used. + return true; +} + +void QGLTexture2DPrivate::bindImages(QGLTexture2DTextureInfo *info) +{ + QSize scaledSize(size); +#if defined(QT_OPENGL_ES_2) + if ((bindOptions & QGLContext::MipmapBindOption) || + horizontalWrap != QGL::ClampToEdge || + verticalWrap != QGL::ClampToEdge) { + // ES 2.0 does not support NPOT textures when mipmaps are in use, + // or if the wrap mode isn't ClampToEdge. + scaledSize = QGL::nextPowerOfTwo(scaledSize); + } +#endif + if (!image.isNull()) + info->tex.uploadFace(GL_TEXTURE_2D, image, scaledSize); + else if (size.isValid()) + info->tex.createFace(GL_TEXTURE_2D, scaledSize); +} + +/*! + Releases the texture associated with the 2D texture target. + This is equivalent to \c{glBindTexture(GL_TEXTURE_2D, 0)}. + + \sa bind() +*/ +void QGLTexture2D::release() const +{ + glBindTexture(GL_TEXTURE_2D, 0); +} + +/*! + Returns the identifier associated with this texture object in + the current context. + + Returns zero if the texture has not previously been bound to + the 2D texture target in the current context with bind(). + + \sa bind() +*/ +GLuint QGLTexture2D::textureId() const +{ + Q_D(const QGLTexture2D); + const QGLContext *ctx = QGLContext::currentContext(); + if (!ctx) + return 0; + QGLTexture2DTextureInfo *info = d->infos; + while (info != 0 && !QGLContext::areSharing(info->tex.context(), ctx)) + info = info->next; + return info ? info->tex.textureId() : 0; +} + +/*! + Constructs a QGLTexture2D object that wraps the supplied literal + texture identifier \a id, with the dimensions specified by \a size. + + The \a id is assumed to have been created by the application in + the current GL context, and it will be destroyed by the application + after the returned QGLTexture2D object is destroyed. + + This function is intended for interfacing to existing code that + uses raw GL texture identifiers. The returned QGLTexture2D can + only be used with the current GL context. + + \sa textureId() +*/ +QGLTexture2D *QGLTexture2D::fromTextureId(GLuint id, const QSize& size) +{ + const QGLContext *ctx = QGLContext::currentContext(); + if (!id || !ctx) + return 0; + + QGLTexture2D *texture = new QGLTexture2D(); + if (!size.isNull()) + texture->setSize(size); + QGLTexture2DTextureInfo *info = new QGLTexture2DTextureInfo + (ctx, id, texture->d_ptr->imageGeneration, + texture->d_ptr->parameterGeneration, true); + texture->d_ptr->infos = info; + return texture; +} + +QT_END_NAMESPACE diff --git a/src/threed/textures/qgltexture2d.h b/src/threed/textures/qgltexture2d.h new file mode 100644 index 000000000..5379c6dfa --- /dev/null +++ b/src/threed/textures/qgltexture2d.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLTEXTURE2D_H +#define QGLTEXTURE2D_H + +#include "qglnamespace.h" +#include <QtOpenGL/qgl.h> +#include <QtCore/qscopedpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLTexture2DPrivate; + +class Q_QT3D_EXPORT QGLTexture2D : public QObject +{ + Q_OBJECT +public: + QGLTexture2D(QObject *parent = 0); + ~QGLTexture2D(); + + bool isNull() const; + bool hasAlphaChannel() const; + + QSize size() const; + void setSize(const QSize& value); + QSize requestedSize() const; + + QImage image() const; + void setImage(const QImage& image); + bool setCompressedFile(const QString &path); + QUrl url() const; + void setUrl(const QUrl &url); + + void setPixmap(const QPixmap& pixmap); + + void clearImage(); + + void copyImage(const QImage& image, const QPoint& offset = QPoint(0, 0)); + + QGLContext::BindOptions bindOptions() const; + void setBindOptions(QGLContext::BindOptions options); + + QGL::TextureWrap horizontalWrap() const; + void setHorizontalWrap(QGL::TextureWrap value); + + QGL::TextureWrap verticalWrap() const; + void setVerticalWrap(QGL::TextureWrap value); + + bool bind() const; + void release() const; + + GLuint textureId() const; + + static QGLTexture2D *fromTextureId(GLuint id, const QSize& size); + +private: + QScopedPointer<QGLTexture2DPrivate> d_ptr; + + Q_DISABLE_COPY(QGLTexture2D) + Q_DECLARE_PRIVATE(QGLTexture2D) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/textures/qgltexture2d_p.h b/src/threed/textures/qgltexture2d_p.h new file mode 100644 index 000000000..d226751bd --- /dev/null +++ b/src/threed/textures/qgltexture2d_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLTEXTURE2D_P_H +#define QGLTEXTURE2D_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgltexture2d.h" +#include "qgltextureutils_p.h" +#include "qurl.h" + +#include <QtCore/qatomic.h> + +QT_BEGIN_NAMESPACE + +class QGLTexture2DTextureInfo +{ +public: + QGLTexture2DTextureInfo + (const QGLContext *context, GLuint textureId, uint imageGeneration, + uint parameterGeneration, bool isLiteral = false) + { + if (textureId) + tex.setTextureId(context, textureId); + this->imageGeneration = imageGeneration; + this->parameterGeneration = parameterGeneration; + this->isLiteral = isLiteral; + this->next = 0; + } + + QGLBoundTexture tex; + uint imageGeneration; + uint parameterGeneration; + bool isLiteral; + QGLTexture2DTextureInfo *next; +}; + +class DDSFormat; + +class QGLTexture2DPrivate +{ +public: + QGLTexture2DPrivate(); + ~QGLTexture2DPrivate(); + + QSize size; + QSize requestedSize; + QImage image; + QUrl url; + QByteArray compressedData; + QGLContext::BindOptions bindOptions; + QGL::TextureWrap horizontalWrap; + QGL::TextureWrap verticalWrap; +#if !defined(QT_OPENGL_ES) + bool mipmapSupported; + bool mipmapSupportedKnown; +#endif + uint imageGeneration; + uint parameterGeneration; + QGLTexture2DTextureInfo *infos; + + bool bind(GLenum target); + virtual void bindImages(QGLTexture2DTextureInfo *info); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/threed/textures/qgltexturecube.cpp b/src/threed/textures/qgltexturecube.cpp new file mode 100644 index 000000000..9947d1b65 --- /dev/null +++ b/src/threed/textures/qgltexturecube.cpp @@ -0,0 +1,549 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgltexturecube.h" +#include "qgltexture2d_p.h" +#include "qgltextureutils_p.h" +#include "qglpainter_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGLTextureCube + \brief The QGLTextureCube class represents a cube map texture object for GL painting operations. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::textures + + QGLTextureCube contains six QImage objects for each of the cube + map faces and settings for texture filters, wrap modes, and mipmap + generation. When bind() is called, this information is uploaded to + the GL server if it has changed since the last time bind() was called. + + Once a QGLTextureCube object is created, it can be bound to multiple + GL contexts. Internally, a separate texture identifier is created + for each context. This makes QGLTextureCube easier to use than + raw GL texture identifiers because the application does not need + to be as concerned with whether the texture identifier is valid + in the current context. The application merely calls bind() and + QGLTextureCube will create a new texture identifier for the context + if necessary. + + QGLTextureCube internally points to a reference-counted object that + represents the current texture state. If the QGLTextureCube is copied, + the internal pointer is the same. Modifications to one QGLTextureCube + copy will affect all of the other copies in the system. + + The texture identifiers will be destroyed when the last QGLTextureCube + reference is destroyed, or when a context is destroyed that contained a + texture identifier that was created by QGLTextureCube. + + \sa QGLTexture2D +*/ + +/*! + \enum QGLTextureCube::Face + This enum defines the face of a cube map texture that is affected + by a texture operation on QGLTextureCube instances. + + \value PositiveX The positive X face of the cube map. + \value NegativeX The negative X face of the cube map. + \value PositiveY The positive Y face of the cube map. + \value NegativeY The negative Y face of the cube map. + \value PositiveZ The positive Z face of the cube map. + \value NegativeZ The negative Z face of the cube map. +*/ + +class QGLTextureCubePrivate : public QGLTexture2DPrivate +{ +public: + QGLTextureCubePrivate(); + ~QGLTextureCubePrivate(); + + void bindImages(QGLTexture2DTextureInfo *info); + + QImage otherImages[5]; + uint changedFaces; +}; + +QGLTextureCubePrivate::QGLTextureCubePrivate() +{ + changedFaces = 0; +} + +QGLTextureCubePrivate::~QGLTextureCubePrivate() +{ +} + +void QGLTextureCubePrivate::bindImages(QGLTexture2DTextureInfo *info) +{ + QSize scaledSize(size); +#if defined(QT_OPENGL_ES_2) + if ((bindOptions & QGLContext::MipmapBindOption) || + horizontalWrap != QGL::ClampToEdge || + verticalWrap != QGL::ClampToEdge) { + // ES 2.0 does not support NPOT textures when mipmaps are in use, + // or if the wrap mode isn't ClampToEdge. + scaledSize = QGL::nextPowerOfTwo(scaledSize); + } +#endif + + // Handle the first face. + if (!image.isNull()) + info->tex.uploadFace(GL_TEXTURE_CUBE_MAP_POSITIVE_X, image, scaledSize); + else if (size.isValid()) + info->tex.createFace(GL_TEXTURE_CUBE_MAP_POSITIVE_X, scaledSize); + + // Handle the other faces. + for (int face = 1; face < 6; ++face) { + if (!otherImages[face - 1].isNull()) { + info->tex.uploadFace(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, + otherImages[face - 1], scaledSize); + } else { + info->tex.createFace(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, scaledSize); + } + } +} + +/*! + Constructs a null texture object. + + \sa isNull() +*/ +QGLTextureCube::QGLTextureCube() + : d_ptr(new QGLTextureCubePrivate()) +{ +} + +/*! + Destroys this texture object. If this object is the last + reference to the underlying GL texture, then the underlying + GL texture will also be deleted. +*/ +QGLTextureCube::~QGLTextureCube() +{ +} + +/*! + Returns true if this texture object is null; that is, all image() + values are null and textureId() is zero. +*/ +bool QGLTextureCube::isNull() const +{ + // TODO + Q_D(const QGLTextureCube); + return !d->infos; +} + +/*! + Returns true if this texture has an alpha channel; false if the + texture is fully opaque. +*/ +bool QGLTextureCube::hasAlphaChannel() const +{ + Q_D(const QGLTextureCube); + if (!d->image.isNull() && d->image.hasAlphaChannel()) + return true; + for (int face = 0; face < 5; ++face) { + if (!d->otherImages[face].isNull()) { + if (d->otherImages[face].hasAlphaChannel()) + return true; + } + } + QGLTexture2DTextureInfo *info = d->infos; + if (info) + return info->tex.hasAlpha(); + return false; +} + +/*! + Returns the size of this texture. If the underlying OpenGL + implementation requires texture sizes to be a power of two, + then this function will return the next power of two equal + to or greater than requestedSize() + + \sa setSize(), requestedSize() +*/ +QSize QGLTextureCube::size() const +{ + Q_D(const QGLTextureCube); + return d->size; +} + +/*! + Sets the size of this texture to \a value. If the underlying + OpenGL implementation requires texture sizes to be a power of + two, then requestedSize() will be set to \a value, and the + actual size will be set to the next power of two equal + to or greater than \a value. Otherwise both size() and + requestedSize() will be set to \a value. + + \sa size(), requestedSize() +*/ +void QGLTextureCube::setSize(const QSize& value) +{ + Q_D(QGLTextureCube); + if (d->requestedSize == value) + return; + if (!(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) && + !(QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_ES_Version_2_0)) + d->size = QGL::nextPowerOfTwo(value); + else + d->size = value; + d->requestedSize = value; + ++(d->imageGeneration); +} + +/*! + Returns the size that was previously set with setSize() before + it was rounded to a power of two. + + \sa size(), setSize() +*/ +QSize QGLTextureCube::requestedSize() const +{ + Q_D(const QGLTextureCube); + return d->requestedSize; +} + +/*! + Returns the image that is currently associated with the specified + \a face of this cube map texture. The image may not have been + uploaded into the GL server yet. Uploads occur upon the next + call to bind(). + + \sa setImage() +*/ +QImage QGLTextureCube::image(QGLTextureCube::Face face) const +{ + Q_D(const QGLTextureCube); + if (uint(face) >= 6) + return QImage(); + if (face == 0) + return d->image; + else + return d->otherImages[face - 1]; +} + +/*! + Sets the \a image that is associated with this texture on the + specified \a face of the cube map. The image will be uploaded + into the GL server the next time bind() is called. + + If setSize() or setImage() has been called previously, then \a image + will be scaled to size() when it is uploaded. + + If \a image is null, then this function is equivalent to clearImage(). + + \sa image(), setSize(), copyImage() +*/ +void QGLTextureCube::setImage + (QGLTextureCube::Face face, const QImage& image) +{ + Q_D(QGLTextureCube); + if (uint(face) >= 6) + return; + if (image.isNull()) { + // Don't change the imageGeneration, because we aren't actually + // changing the image in the GL server, only the client copy. + if (face == 0) + d->image = image; + else + d->otherImages[face - 1] = image; + } else { + if (!d->size.isValid()) + setSize(image.size()); + if (face == 0) + d->image = image; + else + d->otherImages[face - 1] = image; + ++(d->imageGeneration); + d->changedFaces |= (1 << face); + } +} + +/*! + Clears the image() that is associated with this texture on the + specified \a face of the cube map. The GL texture will retain + its current value. This can be used to release client-side memory + that is no longer required once the image has been uploaded into + the GL server. + + The following code will queue \c image to be uploaded as the + positive X face of the cube map, immediately force it to + be uploaded into the current GL context, and then clear the + client copy: + + \code + texture.setImage(QGLTextureCube::PositiveX, image); + texture.bind(); + texture.clearImage(QGLTextureCube::PositiveX); + \endcode + + \sa image(), setImage() +*/ +void QGLTextureCube::clearImage(QGLTextureCube::Face face) +{ + Q_D(QGLTextureCube); + if (face == 0) + d->image = QImage(); + else + d->otherImages[face - 1] = QImage(); +} + +/*! + Copies the contents of \a image to \a offset in this texture + within the current GL context. The \a face parameter indicates + which face of the cube map should be altered. + + Unlike setImage(), this function copies the image data to the + GL server immediately using \c{glTexSubImage2D()}. This is typically + used to update the contents of a texture after it has been created. + + It is assumed that the application has already called bind() on + this texture to bind it to the current GL context. + + If the texture has been created in multiple contexts, only the + texture identifier for the current context will be updated. + + \sa setImage(), bind() +*/ +void QGLTextureCube::copyImage + (QGLTextureCube::Face face, const QImage& image, const QPoint& offset) +{ + if (uint(face) >= 6) + return; // Invalid face number. + QImage img = QGLWidget::convertToGLFormat(image); + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + int(face), + 0, offset.x(), offset.y(), + img.width(), img.height(), GL_RGBA, + GL_UNSIGNED_BYTE, img.bits()); +#if defined(QT_OPENGL_ES_2) + Q_D(QGLTextureCube); + if (d->bindOptions & QGLContext::MipmapBindOption) + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); +#endif +} + +/*! + Returns the options to use when binding the image() to an OpenGL + context for the first time. The default options are + QGLContext::LinearFilteringBindOption | + QGLContext::InvertedYBindOption | QGLContext::MipmapBindOption. + + \sa setBindOptions() +*/ +QGLContext::BindOptions QGLTextureCube::bindOptions() const +{ + Q_D(const QGLTextureCube); + return d->bindOptions; +} + +/*! + Sets the \a options to use when binding the image() to an + OpenGL context. If the image() has already been bound, + then changing the options will cause it to be recreated + from image() the next time bind() is called. + + \sa bindOptions(), bind() +*/ +void QGLTextureCube::setBindOptions(QGLContext::BindOptions options) +{ + Q_D(QGLTextureCube); + if (d->bindOptions != options) { + d->bindOptions = options; + ++(d->imageGeneration); + } +} + +/*! + Returns the wrapping mode for horizontal texture co-ordinates. + The default value is QGL::Repeat. + + \sa setHorizontalWrap(), verticalWrap() +*/ +QGL::TextureWrap QGLTextureCube::horizontalWrap() const +{ + Q_D(const QGLTextureCube); + return d->horizontalWrap; +} + +/*! + Sets the wrapping mode for horizontal texture co-ordinates to \a value. + + If \a value is not supported by the OpenGL implementation, it will be + replaced with a value that is supported. If the application desires a + very specific \a value, it can call horizontalWrap() to check that + the specific value was actually set. + + The \a value will not be applied to the texture in the GL + server until the next call to bind(). + + \sa horizontalWrap(), setVerticalWrap() +*/ +void QGLTextureCube::setHorizontalWrap(QGL::TextureWrap value) +{ + Q_D(QGLTextureCube); + value = qt_gl_modify_texture_wrap(value); + if (d->horizontalWrap != value) { + d->horizontalWrap = value; + ++(d->parameterGeneration); + } +} + +/*! + Returns the wrapping mode for vertical texture co-ordinates. + The default value is QGL::Repeat. + + \sa setVerticalWrap(), horizontalWrap() +*/ +QGL::TextureWrap QGLTextureCube::verticalWrap() const +{ + Q_D(const QGLTextureCube); + return d->verticalWrap; +} + +/*! + Sets the wrapping mode for vertical texture co-ordinates to \a value. + + If \a value is not supported by the OpenGL implementation, it will be + replaced with a value that is supported. If the application desires a + very specific \a value, it can call verticalWrap() to check that + the specific value was actually set. + + The \a value will not be applied to the texture in the GL + server until the next call to bind(). + + \sa verticalWrap(), setHorizontalWrap() +*/ +void QGLTextureCube::setVerticalWrap(QGL::TextureWrap value) +{ + Q_D(QGLTextureCube); + value = qt_gl_modify_texture_wrap(value); + if (d->verticalWrap != value) { + d->verticalWrap = value; + ++(d->parameterGeneration); + } +} + +/*! + Binds this texture to the cube map texture target. + + If this texture object is not associated with an identifier in + the current context, then a new identifier will be created, + and the face images will be uploaded into the GL server. + + If setImage() or setSize() was called since the last upload, + then the face images will be re-uploaded to the GL server. + + Returns false if the texture could not be bound for some reason. + + \sa release(), textureId(), setImage() +*/ +bool QGLTextureCube::bind() const +{ + Q_D(const QGLTextureCube); + return const_cast<QGLTextureCubePrivate *>(d)->bind(GL_TEXTURE_CUBE_MAP); +} + +/*! + Releases the texture associated with the cube map texture target. + This is equivalent to \c{glBindTexture(GL_TEXTURE_CUBE_MAP, 0)}. + + \sa bind() +*/ +void QGLTextureCube::release() +{ + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} + +/*! + Returns the identifier associated with this texture object in + the current context. + + Returns zero if the texture has not previously been bound to + the 2D texture target in the current context with bind(). + + \sa bind() +*/ +GLuint QGLTextureCube::textureId() const +{ + Q_D(const QGLTextureCube); + const QGLContext *ctx = QGLContext::currentContext(); + if (!ctx) + return 0; + QGLTexture2DTextureInfo *info = d->infos; + while (info != 0 && info->tex.context() != ctx) + info = info->next; + return info ? info->tex.textureId() : 0; +} + +/*! + Constructs a QGLTextureCube object that wraps the supplied literal + texture identifier \a id, with the dimensions specified by \a size. + + The \a id is assumed to have been created by the application in + the current GL context, and it will be destroyed by the application + after the returned QGLTextureCube object is destroyed. + + This function is intended for interfacing to existing code that + uses raw GL texture identifiers. The returned QGLTextureCube can + only be used with the current GL context. + + \sa textureId() +*/ +QGLTextureCube *QGLTextureCube::fromTextureId(GLuint id, const QSize& size) +{ + const QGLContext *ctx = QGLContext::currentContext(); + if (!id || !ctx) + return 0; + + QGLTextureCube *texture = new QGLTextureCube(); + if (!size.isNull()) + texture->setSize(size); + QGLTexture2DTextureInfo *info = new QGLTexture2DTextureInfo + (ctx, id, texture->d_ptr->imageGeneration, + texture->d_ptr->parameterGeneration, true); + texture->d_ptr->infos = info; + return texture; +} + +QT_END_NAMESPACE diff --git a/src/threed/textures/qgltexturecube.h b/src/threed/textures/qgltexturecube.h new file mode 100644 index 000000000..dfa445abc --- /dev/null +++ b/src/threed/textures/qgltexturecube.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLTEXTURECUBEMAP_H +#define QGLTEXTURECUBEMAP_H + +#include "qglnamespace.h" +#include <QtOpenGL/qgl.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLTextureCubePrivate; + +class Q_QT3D_EXPORT QGLTextureCube +{ +public: + QGLTextureCube(); + ~QGLTextureCube(); + + enum Face + { + PositiveX, + NegativeX, + PositiveY, + NegativeY, + PositiveZ, + NegativeZ + }; + + bool isNull() const; + bool hasAlphaChannel() const; + + QSize size() const; + void setSize(const QSize& value); + QSize requestedSize() const; + + QImage image(QGLTextureCube::Face face) const; + void setImage(QGLTextureCube::Face face, const QImage& image); + void clearImage(QGLTextureCube::Face face); + + void copyImage(QGLTextureCube::Face face, const QImage& image, const QPoint& offset = QPoint(0, 0)); + + QGLContext::BindOptions bindOptions() const; + void setBindOptions(QGLContext::BindOptions options); + + QGL::TextureWrap horizontalWrap() const; + void setHorizontalWrap(QGL::TextureWrap value); + + QGL::TextureWrap verticalWrap() const; + void setVerticalWrap(QGL::TextureWrap value); + + bool bind() const; + static void release(); + + GLuint textureId() const; + + static QGLTextureCube *fromTextureId(GLuint id, const QSize& size); + +private: + QScopedPointer<QGLTextureCubePrivate> d_ptr; + + Q_DISABLE_COPY(QGLTextureCube) + Q_DECLARE_PRIVATE(QGLTextureCube) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/textures/qgltextureutils.cpp b/src/threed/textures/qgltextureutils.cpp new file mode 100644 index 000000000..47c5de489 --- /dev/null +++ b/src/threed/textures/qgltextureutils.cpp @@ -0,0 +1,785 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgltextureutils_p.h" +#include "qglext_p.h" +#include <QtCore/qfile.h> + +QT_BEGIN_NAMESPACE + +QGL::TextureWrap qt_gl_modify_texture_wrap(QGL::TextureWrap value) +{ + switch (value) { +#if defined(QT_OPENGL_ES) + case QGL::Clamp: + value = QGL::ClampToEdge; + break; +#endif +#if !defined(QT_OPENGL_ES) + case QGL::ClampToBorder: + if ((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_3) + == 0) + value = QGL::Clamp; + break; +#else + case QGL::ClampToBorder: + value = QGL::ClampToEdge; + break; +#endif +#if !defined(QT_OPENGL_ES) + case QGL::ClampToEdge: + if ((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_2) + == 0) + value = QGL::Clamp; + break; +#endif +#if !defined(QT_OPENGL_ES) + case QGL::MirroredRepeat: + if ((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_4) + == 0) + value = QGL::Repeat; + break; +#elif !defined(QT_OPENGL_ES_2) + case QGL::MirroredRepeat: + value = QGL::Repeat; + break; +#endif + default: break; + } + return value; +} + +QGLTextureExtensions::QGLTextureExtensions(const QGLContext *ctx) + : npotTextures(false) + , generateMipmap(false) + , bgraTextureFormat(false) + , ddsTextureCompression(false) + , etc1TextureCompression(false) + , pvrtcTextureCompression(false) + , compressedTexImage2D(0) +{ + Q_UNUSED(ctx); + QGLExtensionChecker extensions(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); + if (extensions.match("GL_ARB_texture_non_power_of_two")) + npotTextures = true; + if (extensions.match("GL_SGIS_generate_mipmap")) + generateMipmap = true; + if (extensions.match("GL_EXT_bgra")) + bgraTextureFormat = true; + if (extensions.match("GL_EXT_texture_compression_s3tc")) + ddsTextureCompression = true; + if (extensions.match("GL_OES_compressed_ETC1_RGB8_texture")) + etc1TextureCompression = true; + if (extensions.match("GL_IMG_texture_compression_pvrtc")) + pvrtcTextureCompression = true; +#if defined(QT_OPENGL_ES_2) + npotTextures = true; + generateMipmap = true; +#endif +#if !defined(QT_OPENGL_ES) + if (extensions.match("GL_ARB_texture_compression")) { + compressedTexImage2D = (q_glCompressedTexImage2DARB) + ctx->getProcAddress(QLatin1String("glCompressedTexImage2DARB")); + } +#else + compressedTexImage2D = glCompressedTexImage2D; +#endif +} + +QGLTextureExtensions::~QGLTextureExtensions() +{ +} + +Q_GLOBAL_STATIC(QGLResource<QGLTextureExtensions>, qt_gl_texture_extensions) + +QGLTextureExtensions *QGLTextureExtensions::extensions() +{ + const QGLContext *ctx = QGLContext::currentContext(); + if (!ctx) + return 0; + return qt_gl_texture_extensions()->value(ctx); +} + +static void qt_gl_destroyTextureId(GLuint id) +{ + glDeleteTextures(1, &id); +} + +QGLBoundTexture::QGLBoundTexture() + : m_resource(qt_gl_destroyTextureId) + , m_options(QGLContext::DefaultBindOption) + , m_hasAlpha(false) +{ +} + +QGLBoundTexture::~QGLBoundTexture() +{ +} + +// #define QGL_BIND_TEXTURE_DEBUG + +void QGLBoundTexture::startUpload(const QGLContext *ctx, GLenum target, const QSize &imageSize) +{ + Q_UNUSED(imageSize); + + QGLTextureExtensions *extensions = QGLTextureExtensions::extensions(); + if (!extensions) + return; + +#ifdef QGL_BIND_TEXTURE_DEBUG + printf("QGLBoundTexture::startUpload(), imageSize=(%d,%d), options=%x\n", + imageSize.width(), imageSize.height(), int(m_options)); + time.start(); +#endif + +#ifndef QT_NO_DEBUG + // Reset the gl error stack... + while (glGetError() != GL_NO_ERROR) ; +#endif + + // Create the texture id for the target, which should be one of + // GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP. + GLuint id = m_resource.id(); + if (id) { + glBindTexture(target, 0); // Just in case texture is bound. + m_resource.destroy(); + } + id = 0; + glGenTextures(1, &id); + glBindTexture(target, id); + m_resource.attach(ctx, id); + + GLuint filtering = m_options & QGLContext::LinearFilteringBindOption ? GL_LINEAR : GL_NEAREST; + +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - setting options (%d ms)\n", time.elapsed()); +#endif + q_glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filtering); + + if (QGLContext::currentContext()->format().directRendering() + && extensions->generateMipmap + && (m_options & QGLContext::MipmapBindOption)) + { +#if !defined(QT_OPENGL_ES_2) + glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST); + q_glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); +#else + glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); +#endif + q_glTexParameteri(target, GL_TEXTURE_MIN_FILTER, + m_options & QGLContext::LinearFilteringBindOption + ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST); + } else { + q_glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filtering); + m_options &= ~QGLContext::MipmapBindOption; + } +} + +// map from Qt's ARGB endianness-dependent format to GL's big-endian RGBA layout +static inline void qt_gl_byteSwapImage(QImage &img, GLenum pixel_type) +{ + const int width = img.width(); + const int height = img.height(); + + if (pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV + || (pixel_type == GL_UNSIGNED_BYTE && QSysInfo::ByteOrder == QSysInfo::LittleEndian)) + { + for (int i = 0; i < height; ++i) { + uint *p = (uint *) img.scanLine(i); + for (int x = 0; x < width; ++x) + p[x] = ((p[x] << 16) & 0xff0000) | ((p[x] >> 16) & 0xff) | (p[x] & 0xff00ff00); + } + } else { + for (int i = 0; i < height; ++i) { + uint *p = (uint *) img.scanLine(i); + for (int x = 0; x < width; ++x) + p[x] = (p[x] << 8) | ((p[x] >> 24) & 0xff); + } + } +} + +// #define QGL_BIND_TEXTURE_DEBUG + +void QGLBoundTexture::uploadFace + (GLenum target, const QImage &image, const QSize &scaleSize, GLenum format) +{ + GLenum internalFormat(format); + + // Resolve the texture-related extensions for the current context. + QGLTextureExtensions *extensions = QGLTextureExtensions::extensions(); + if (!extensions) + return; + + // Adjust the image size for scaling and power of two. + QSize size = (!scaleSize.isEmpty() ? scaleSize : image.size()); + if (!extensions->npotTextures) + size = QGL::nextPowerOfTwo(size); + QImage img(image); + if (size != image.size()) { +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - scaling up to %dx%d (%d ms) \n", size.width(), size.height(), time.elapsed()); +#endif + img = img.scaled(size); + } + m_size = size; + + QImage::Format target_format = img.format(); + bool premul = m_options & QGLContext::PremultipliedAlphaBindOption; + GLenum externalFormat; + GLuint pixel_type; + if (extensions->bgraTextureFormat) { + externalFormat = GL_BGRA; + if (QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_1_2) + pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV; + else + pixel_type = GL_UNSIGNED_BYTE; + } else { + externalFormat = GL_RGBA; + pixel_type = GL_UNSIGNED_BYTE; + } + + switch (target_format) { + case QImage::Format_ARGB32: + if (premul) { + img = img.convertToFormat(target_format = QImage::Format_ARGB32_Premultiplied); +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - converting ARGB32 -> ARGB32_Premultiplied (%d ms) \n", time.elapsed()); +#endif + } + break; + case QImage::Format_ARGB32_Premultiplied: + if (!premul) { + img = img.convertToFormat(target_format = QImage::Format_ARGB32); +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - converting ARGB32_Premultiplied -> ARGB32 (%d ms)\n", time.elapsed()); +#endif + } + break; + case QImage::Format_RGB16: + pixel_type = GL_UNSIGNED_SHORT_5_6_5; + externalFormat = GL_RGB; + internalFormat = GL_RGB; + break; + case QImage::Format_RGB32: + break; + default: + if (img.hasAlphaChannel()) { + img = img.convertToFormat(premul + ? QImage::Format_ARGB32_Premultiplied + : QImage::Format_ARGB32); +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - converting to 32-bit alpha format (%d ms)\n", time.elapsed()); +#endif + } else { + img = img.convertToFormat(QImage::Format_RGB32); +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - converting to 32-bit (%d ms)\n", time.elapsed()); +#endif + } + } + + if (m_options & QGLContext::InvertedYBindOption) { +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - flipping bits over y (%d ms)\n", time.elapsed()); +#endif + if (img.isDetached()) { + int ipl = img.bytesPerLine() / 4; + int h = img.height(); + for (int y=0; y<h/2; ++y) { + int *a = (int *) img.scanLine(y); + int *b = (int *) img.scanLine(h - y - 1); + for (int x=0; x<ipl; ++x) + qSwap(a[x], b[x]); + } + } else { + // Create a new image and copy across. If we use the + // above in-place code then a full copy of the image is + // made before the lines are swapped, which processes the + // data twice. This version should only do it once. + img = img.mirrored(); + } + } + + if (externalFormat == GL_RGBA) { +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - doing byte swapping (%d ms)\n", time.elapsed()); +#endif + // The only case where we end up with a depth different from + // 32 in the switch above is for the RGB16 case, where we set + // the format to GL_RGB + Q_ASSERT(img.depth() == 32); + qt_gl_byteSwapImage(img, pixel_type); + } +#ifdef QT_OPENGL_ES + // OpenGL/ES requires that the internal and external formats be + // identical. + internalFormat = externalFormat; +#endif +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - uploading, image.format=%d, externalFormat=0x%x, internalFormat=0x%x, pixel_type=0x%x\n", + img.format(), externalFormat, internalFormat, pixel_type); +#endif + + const QImage &constRef = img; // to avoid detach in bits()... + glTexImage2D(target, 0, internalFormat, img.width(), img.height(), 0, externalFormat, + pixel_type, constRef.bits()); + + m_hasAlpha = (internalFormat != GL_RGB); +} + +void QGLBoundTexture::createFace + (GLenum target, const QSize &size, GLenum format) +{ + glTexImage2D(target, 0, format, size.width(), + size.height(), 0, format, GL_UNSIGNED_BYTE, 0); + m_hasAlpha = (format != GL_RGB); +} + +void QGLBoundTexture::finishUpload(GLenum target) +{ + Q_UNUSED(target); + +#if defined(QT_OPENGL_ES_2) + // OpenGL/ES 2.0 needs to generate mipmaps after all cubemap faces + // have been uploaded. + if (m_options & QGLContext::MipmapBindOption) { +#ifdef QGL_BIND_TEXTURE_DEBUG + printf(" - generating mipmaps (%d ms)\n", time.elapsed()); +#endif + glGenerateMipmap(target); + } +#endif + +#ifndef QT_NO_DEBUG + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + qWarning(" - texture upload failed, error code 0x%x, enum: %d (%x)\n", error, target, target); + } +#endif + +#ifdef QGL_BIND_TEXTURE_DEBUG + static int totalUploadTime = 0; + totalUploadTime += time.elapsed(); + printf(" - upload done in (%d ms) time=%d\n", time.elapsed(), totalUploadTime); +#endif +} + +// DDS format structure +struct DDSFormat { + quint32 dwSize; + quint32 dwFlags; + quint32 dwHeight; + quint32 dwWidth; + quint32 dwLinearSize; + quint32 dummy1; + quint32 dwMipMapCount; + quint32 dummy2[11]; + struct { + quint32 dummy3[2]; + quint32 dwFourCC; + quint32 dummy4[5]; + } ddsPixelFormat; +}; + +// compressed texture pixel formats +#define FOURCC_DXT1 0x31545844 +#define FOURCC_DXT2 0x32545844 +#define FOURCC_DXT3 0x33545844 +#define FOURCC_DXT4 0x34545844 +#define FOURCC_DXT5 0x35545844 + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +// PVR header format for container files that store textures compressed +// with the ETC1, PVRTC2, and PVRTC4 encodings. Format information from the +// PowerVR SDK at http://www.imgtec.com/powervr/insider/powervr-sdk.asp +// "PVRTexTool Reference Manual, version 1.11f". +struct PvrHeader +{ + quint32 headerSize; + quint32 height; + quint32 width; + quint32 mipMapCount; + quint32 flags; + quint32 dataSize; + quint32 bitsPerPixel; + quint32 redMask; + quint32 greenMask; + quint32 blueMask; + quint32 alphaMask; + quint32 magic; + quint32 surfaceCount; +}; + +#define PVR_MAGIC 0x21525650 // "PVR!" in little-endian + +#define PVR_FORMAT_MASK 0x000000FF +#define PVR_FORMAT_PVRTC2 0x00000018 +#define PVR_FORMAT_PVRTC4 0x00000019 +#define PVR_FORMAT_ETC1 0x00000036 + +#define PVR_HAS_MIPMAPS 0x00000100 +#define PVR_TWIDDLED 0x00000200 +#define PVR_NORMAL_MAP 0x00000400 +#define PVR_BORDER_ADDED 0x00000800 +#define PVR_CUBE_MAP 0x00001000 +#define PVR_FALSE_COLOR_MIPMAPS 0x00002000 +#define PVR_VOLUME_TEXTURE 0x00004000 +#define PVR_ALPHA_IN_TEXTURE 0x00008000 +#define PVR_VERTICAL_FLIP 0x00010000 + +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG +#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#endif + +#ifndef GL_ETC1_RGB8_OES +#define GL_ETC1_RGB8_OES 0x8D64 +#endif + +bool QGLBoundTexture::canBindCompressedTexture + (const char *buf, int len, const char *format, bool *hasAlpha, + bool *isFlipped) +{ + if (QSysInfo::ByteOrder != QSysInfo::LittleEndian) { + // Compressed texture loading only supported on little-endian + // systems such as x86 and ARM at the moment. + return false; + } + if (!format) { + // Auto-detect the format from the header. + if (len >= 4 && !qstrncmp(buf, "DDS ", 4)) { + *hasAlpha = true; + *isFlipped = true; + return true; + } else if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4)) { + const PvrHeader *pvrHeader = + reinterpret_cast<const PvrHeader *>(buf); + *hasAlpha = (pvrHeader->alphaMask != 0); + *isFlipped = ((pvrHeader->flags & PVR_VERTICAL_FLIP) != 0); + return true; + } + } else { + // Validate the format against the header. + if (!qstricmp(format, "DDS")) { + if (len >= 4 && !qstrncmp(buf, "DDS ", 4)) { + *hasAlpha = true; + *isFlipped = true; + return true; + } + } else if (!qstricmp(format, "PVR") || !qstricmp(format, "ETC1")) { + if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4)) { + const PvrHeader *pvrHeader = + reinterpret_cast<const PvrHeader *>(buf); + *hasAlpha = (pvrHeader->alphaMask != 0); + *isFlipped = ((pvrHeader->flags & PVR_VERTICAL_FLIP) != 0); + return true; + } + } + } + return false; +} + +bool QGLBoundTexture::bindCompressedTexture + (const char *buf, int len, const char *format) +{ + if (QSysInfo::ByteOrder != QSysInfo::LittleEndian) { + // Compressed texture loading only supported on little-endian + // systems such as x86 and ARM at the moment. + return false; + } +#if !defined(QT_OPENGL_ES) + QGLTextureExtensions *extensions = QGLTextureExtensions::extensions(); + if (!extensions) + return false; + if (!extensions->compressedTexImage2D) { + qWarning("QGLContext::bindTexture(): The GL implementation does " + "not support texture compression extensions."); + return false; + } +#endif + if (!format) { + // Auto-detect the format from the header. + if (len >= 4 && !qstrncmp(buf, "DDS ", 4)) + return bindCompressedTextureDDS(buf, len); + else if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4)) + return bindCompressedTexturePVR(buf, len); + } else { + // Validate the format against the header. + if (!qstricmp(format, "DDS")) { + if (len >= 4 && !qstrncmp(buf, "DDS ", 4)) + return bindCompressedTextureDDS(buf, len); + } else if (!qstricmp(format, "PVR") || !qstricmp(format, "ETC1")) { + if (len >= 52 && !qstrncmp(buf + 44, "PVR!", 4)) + return bindCompressedTexturePVR(buf, len); + } + } + return false; +} + +bool QGLBoundTexture::bindCompressedTexture + (const QString& fileName, const char *format) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + QByteArray contents = file.readAll(); + file.close(); + return bindCompressedTexture + (contents.constData(), contents.size(), format); +} + +bool QGLBoundTexture::bindCompressedTextureDDS(const char *buf, int len) +{ + QGLTextureExtensions *extensions = QGLTextureExtensions::extensions(); + if (!extensions) + return false; + + // Bail out if the necessary extension is not present. + if (!extensions->ddsTextureCompression) { + qWarning("QGLBoundTexture::bindCompressedTextureDDS(): DDS texture compression is not supported."); + return false; + } + + const DDSFormat *ddsHeader = reinterpret_cast<const DDSFormat *>(buf + 4); + if (!ddsHeader->dwLinearSize) { + qWarning("QGLBoundTexture::bindCompressedTextureDDS(): DDS image size is not valid."); + return false; + } + + int blockSize = 16; + GLenum format; + + switch(ddsHeader->ddsPixelFormat.dwFourCC) { + case FOURCC_DXT1: + format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + blockSize = 8; + break; + case FOURCC_DXT3: + format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + case FOURCC_DXT5: + format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + default: + qWarning("QGLBoundTexture::bindCompressedTextureDDS(): DDS image format not supported."); + return false; + } + + const GLubyte *pixels = + reinterpret_cast<const GLubyte *>(buf + ddsHeader->dwSize + 4); + + GLuint id = m_resource.id(); + if (id) { + glBindTexture(GL_TEXTURE_2D, 0); // Just in case it is bound. + m_resource.destroy(); + } + id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + q_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + q_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_resource.attach(QGLContext::currentContext(), id); + + int size; + int offset = 0; + int available = len - int(ddsHeader->dwSize + 4); + int w = ddsHeader->dwWidth; + int h = ddsHeader->dwHeight; + + // load mip-maps + for (int i = 0; i < (int) ddsHeader->dwMipMapCount; ++i) { + if (w == 0) w = 1; + if (h == 0) h = 1; + + size = ((w+3)/4) * ((h+3)/4) * blockSize; + if (size > available) + break; + extensions->compressedTexImage2D + (GL_TEXTURE_2D, i, format, w, h, 0, size, pixels + offset); + offset += size; + available -= size; + + // half size for each mip-map level + w = w/2; + h = h/2; + } + + // DDS images are not inverted. + m_options &= ~QGLContext::InvertedYBindOption; + + m_size = QSize(ddsHeader->dwWidth, ddsHeader->dwHeight); + m_hasAlpha = false; + return true; +} + +bool QGLBoundTexture::bindCompressedTexturePVR(const char *buf, int len) +{ + QGLTextureExtensions *extensions = QGLTextureExtensions::extensions(); + if (!extensions) + return false; + + // Determine which texture format we will be loading. + const PvrHeader *pvrHeader = reinterpret_cast<const PvrHeader *>(buf); + GLenum textureFormat; + quint32 minWidth, minHeight; + switch (pvrHeader->flags & PVR_FORMAT_MASK) { + case PVR_FORMAT_PVRTC2: + if (pvrHeader->alphaMask) + textureFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + else + textureFormat = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + minWidth = 16; + minHeight = 8; + break; + + case PVR_FORMAT_PVRTC4: + if (pvrHeader->alphaMask) + textureFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + else + textureFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + minWidth = 8; + minHeight = 8; + break; + + case PVR_FORMAT_ETC1: + textureFormat = GL_ETC1_RGB8_OES; + minWidth = 4; + minHeight = 4; + break; + + default: + qWarning("QGLBoundTexture::bindCompressedTexturePVR(): PVR image format 0x%x not supported.", int(pvrHeader->flags & PVR_FORMAT_MASK)); + return false; + } + + // Bail out if the necessary extension is not present. + if (textureFormat == GL_ETC1_RGB8_OES) { + if (!extensions->etc1TextureCompression) { + qWarning("QGLBoundTexture::bindCompressedTexturePVR(): ETC1 texture compression is not supported."); + return false; + } + } else { + if (!extensions->pvrtcTextureCompression) { + qWarning("QGLBoundTexture::bindCompressedTexturePVR(): PVRTC texture compression is not supported."); + return false; + } + } + + // Boundary check on the buffer size. + quint32 bufferSize = pvrHeader->headerSize + pvrHeader->dataSize; + if (bufferSize > quint32(len)) { + qWarning("QGLBoundTexture::bindCompressedTexturePVR(): PVR image size is not valid."); + return false; + } + + // Create the texture. + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + GLuint id = m_resource.id(); + if (id) { + glBindTexture(GL_TEXTURE_2D, 0); // Just in case it is bound. + m_resource.destroy(); + } + id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + m_resource.attach(QGLContext::currentContext(), id); + if (pvrHeader->mipMapCount) { + if ((m_options & QGLContext::LinearFilteringBindOption) != 0) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + } + } else if ((m_options & QGLContext::LinearFilteringBindOption) != 0) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } else { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + // Load the compressed mipmap levels. + const GLubyte *buffer = + reinterpret_cast<const GLubyte *>(buf + pvrHeader->headerSize); + bufferSize = pvrHeader->dataSize; + quint32 level = 0; + quint32 width = pvrHeader->width; + quint32 height = pvrHeader->height; + while (bufferSize > 0 && level <= pvrHeader->mipMapCount) { + quint32 size = + (qMax(width, minWidth) * qMax(height, minHeight) * + pvrHeader->bitsPerPixel) / 8; + if (size > bufferSize) + break; + extensions->compressedTexImage2D + (GL_TEXTURE_2D, GLint(level), textureFormat, + GLsizei(width), GLsizei(height), 0, GLsizei(size), buffer); + width /= 2; + height /= 2; + buffer += size; + ++level; + } + + // Restore the default pixel alignment for later texture uploads. + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + + // Set the invert flag for the texture. The "vertical flip" + // flag in PVR is the opposite sense to our sense of inversion. + if ((pvrHeader->flags & PVR_VERTICAL_FLIP) != 0) + m_options &= ~QGLContext::InvertedYBindOption; + else + m_options |= QGLContext::InvertedYBindOption; + + m_size = QSize(pvrHeader->width, pvrHeader->height); + m_hasAlpha = (pvrHeader->alphaMask != 0); + return true; +} + +QT_END_NAMESPACE diff --git a/src/threed/textures/qgltextureutils_p.h b/src/threed/textures/qgltextureutils_p.h new file mode 100644 index 000000000..2dc7ea4c5 --- /dev/null +++ b/src/threed/textures/qgltextureutils_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLTEXTUREUTILS_P_H +#define QGLTEXTUREUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtOpenGL/qgl.h> +#include <QtCore/qdatetime.h> +#include "qglnamespace.h" +#include "qopenglfunctions.h" +#include "qglsharedresource_p.h" + +QT_BEGIN_NAMESPACE + +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif +#ifndef GL_UNSIGNED_SHORT_5_6_5 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#endif +#ifndef GL_UNSIGNED_INT_8_8_8_8_REV +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#endif +#ifndef GL_TEXTURE_CUBE_MAP +#define GL_TEXTURE_CUBE_MAP 0x8513 +#endif +#ifndef GL_TEXTURE_CUBE_MAP_POSITIVE_X +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#endif +#ifndef GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#endif + +#ifndef GL_GENERATE_MIPMAP_SGIS +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#if !defined(QT_OPENGL_ES) +#define q_glTexParameteri(target,name,value) \ + glTexParameteri((target), (name), int(value)) +#else +#define q_glTexParameteri(target,name,value) \ + glTexParameterf((target), (name), GLfloat(int(value))) +#endif + +// Modify a wrapping mode to account for platform differences. +QGL::TextureWrap qt_gl_modify_texture_wrap(QGL::TextureWrap value); + +typedef void (QT3D_GLF_APIENTRYP q_glCompressedTexImage2DARB) + (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); + +class QGLTextureExtensions +{ +public: + QGLTextureExtensions(const QGLContext *ctx); + ~QGLTextureExtensions(); + + int npotTextures : 1; + int generateMipmap : 1; + int bgraTextureFormat : 1; + int ddsTextureCompression : 1; + int etc1TextureCompression : 1; + int pvrtcTextureCompression : 1; + q_glCompressedTexImage2DARB compressedTexImage2D; + + static QGLTextureExtensions *extensions(); +}; + +class QGLBoundTexture +{ +public: + QGLBoundTexture(); + ~QGLBoundTexture(); + + const QGLContext *context() const { return m_resource.context(); } + + GLuint textureId() const { return m_resource.id(); } + void setTextureId(const QGLContext *ctx, GLuint id) + { m_resource.attach(ctx, id); } + void clearId() { m_resource.clearId(); } + + QGLContext::BindOptions options() const { return m_options; } + void setOptions(QGLContext::BindOptions options) { m_options = options; } + + QSize size() const { return m_size; } + bool hasAlpha() const { return m_hasAlpha; } + + void startUpload(const QGLContext *ctx, GLenum target, const QSize &imageSize); + void uploadFace(GLenum target, const QImage &image, const QSize &scaleSize, + GLenum format = GL_RGBA); + void createFace(GLenum target, const QSize &size, GLenum format = GL_RGBA); + void finishUpload(GLenum target); + + static bool canBindCompressedTexture + (const char *buf, int len, const char *format, bool *hasAlpha, + bool *isFlipped); + bool bindCompressedTexture + (const QString& fileName, const char *format = 0); + bool bindCompressedTexture + (const char *buf, int len, const char *format = 0); + bool bindCompressedTextureDDS(const char *buf, int len); + bool bindCompressedTexturePVR(const char *buf, int len); + +private: + QGLSharedResource m_resource; + QGLContext::BindOptions m_options; + QSize m_size; + bool m_hasAlpha; + QTime time; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/threed/textures/textures.pri b/src/threed/textures/textures.pri new file mode 100644 index 000000000..c17c1531e --- /dev/null +++ b/src/threed/textures/textures.pri @@ -0,0 +1,15 @@ +INCLUDEPATH += $$PWD +VPATH += $$PWD +HEADERS += \ + qgltexture2d.h \ + qgltexturecube.h \ + qareaallocator.h +SOURCES += \ + qareaallocator.cpp \ + qglsharedresource.cpp \ + qgltexture2d.cpp \ + qgltexturecube.cpp \ + qgltextureutils.cpp +PRIVATE_HEADERS += \ + qglsharedresource_p.h \ + qgltexture2d_p.h |