blob: a9ece129074d545e7c3d4c1a965bcab7f516eefb [file] [log] [blame]
/*
* Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "config.h"
#include "AreaAllocator.h"
#if USE(COORDINATED_GRAPHICS)
using namespace WebCore;
namespace WebKit {
AreaAllocator::AreaAllocator(const IntSize& size)
: m_size(size)
, m_minAlloc(1, 1)
, m_margin(0, 0)
{
}
AreaAllocator::~AreaAllocator()
{
}
void AreaAllocator::expand(const IntSize& size)
{
m_size = m_size.expandedTo(size);
}
void AreaAllocator::expandBy(const IntSize& size)
{
m_size += size;
}
void AreaAllocator::release(const IntRect&)
{
}
int AreaAllocator::overhead() const
{
return 0;
}
IntSize AreaAllocator::roundAllocation(const IntSize& 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 IntSize(width, height);
}
GeneralAreaAllocator::GeneralAreaAllocator(const IntSize& size)
: AreaAllocator(nextPowerOfTwo(size))
{
m_root = new Node();
m_root->rect = IntRect(0, 0, m_size.width(), m_size.height());
m_root->largestFree = m_size;
m_nodeCount = 1;
setMinimumAllocation(IntSize(8, 8));
}
GeneralAreaAllocator::~GeneralAreaAllocator()
{
freeNode(m_root);
}
void GeneralAreaAllocator::freeNode(Node* node)
{
if (node) {
freeNode(node->left);
freeNode(node->right);
}
delete node;
}
void GeneralAreaAllocator::expand(const IntSize& size)
{
AreaAllocator::expand(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 = IntRect(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 = nullptr;
parent->left = m_root;
parent->right = right;
parent->largestFree = m_root->rect.size();
right->parent = parent;
right->left = nullptr;
right->right = nullptr;
right->largestFree = m_root->rect.size();
if (split == SplitOnX) {
parent->rect = IntRect(m_root->rect.x(), m_root->rect.y(),
m_root->rect.width() * 2, m_root->rect.height());
right->rect = IntRect(m_root->rect.x() + m_root->rect.width(), m_root->rect.y(),
m_root->rect.width(), m_root->rect.height());
} else {
parent->rect = IntRect(m_root->rect.x(), m_root->rect.y(),
m_root->rect.width(), m_root->rect.height() * 2);
right->rect = IntRect(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 IntSize& size1, const IntSize& size2)
{
return size1.width() <= size2.width() && size1.height() <= size2.height();
}
IntRect GeneralAreaAllocator::allocate(const IntSize& size)
{
IntSize rounded = roundAllocation(size);
rounded = nextPowerOfTwo(rounded);
if (rounded.width() <= 0 || rounded.width() > m_size.width()
|| rounded.height() <= 0 || rounded.height() > m_size.height())
return IntRect();
IntPoint point = allocateFromNode(rounded, m_root);
if (point.x() >= 0)
return IntRect(point, size);
return IntRect();
}
IntPoint GeneralAreaAllocator::allocateFromNode(const IntSize& 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) {
// 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.
IntPoint point = allocateFromNode(size, left);
if (point.x() >= 0)
return point;
return allocateFromNode(size, right);
}
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 IntPoint(-1, -1);
} else if (fitsWithin(size, node->largestFree)) {
// Do we need to split this node into smaller pieces?
Split split;
if (fitsWithin(IntSize(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(IntSize(size.width() * 2, size.height()), node->largestFree)) {
// Split along the X direction.
split = SplitOnX;
} else if (fitsWithin(IntSize(size.width(), size.height() * 2), node->largestFree)) {
// Split along the Y direction.
split = SplitOnY;
} else {
// Cannot split further - allocate this node.
node->largestFree = IntSize(0, 0);
updateLargestFree(node);
return node->rect.location();
}
// 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 IntPoint(-1, -1);
}
GeneralAreaAllocator::Node* GeneralAreaAllocator::splitNode(Node* node, Split split)
{
Node* left = new Node();
left->parent = node;
Node* right = new Node();
right->parent = node;
node->left = left;
node->right = right;
m_nodeCount += 2;
if (split == SplitOnX) {
left->rect = IntRect(node->rect.x(), node->rect.y(),
node->rect.width() / 2, node->rect.height());
right->rect = IntRect(left->rect.maxX(), node->rect.y(),
node->rect.width() / 2, node->rect.height());
} else {
left->rect = IntRect(node->rect.x(), node->rect.y(),
node->rect.width(), node->rect.height() / 2);
right->rect = IntRect(node->rect.x(), left->rect.maxY(),
node->rect.width(), node->rect.height() / 2);
}
left->largestFree = left->rect.size();
right->largestFree = right->rect.size();
node->largestFree = right->largestFree;
return left;
}
void GeneralAreaAllocator::updateLargestFree(Node* node)
{
while ((node = node->parent)) {
node->largestFree = IntSize(
std::max(node->left->largestFree.width(), node->right->largestFree.width()),
std::max(node->left->largestFree.height(), node->right->largestFree.height())
);
}
}
void GeneralAreaAllocator::release(const IntRect& rect)
{
// Locate the node that contains the allocated region.
Node* node = m_root;
IntPoint point = rect.location();
while (node) {
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 = nullptr;
node->right = nullptr;
node->largestFree = node->rect.size();
}
// Make the rest of our ancestors have the correct "largest free size".
updateLargestFree(node);
}
int GeneralAreaAllocator::overhead() const
{
return m_nodeCount * sizeof(Node);
}
} // namespace WebKit
#endif // USE(COORDINATED_GRAPHICS)