blob: 15275b3ae5fe5c1c6a4f8f84fd6690d21fcf58db [file] [log] [blame]
/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#if USE(ACCELERATED_COMPOSITING)
#include "PlatformCALayerWinInternal.h"
#include "Font.h"
#include "FontCache.h"
#include "PlatformCALayer.h"
#include "TextRun.h"
#include <QuartzCore/CACFLayer.h>
#include <wtf/MainThread.h>
using namespace std;
using namespace WebCore;
// The width and height of a single tile in a tiled layer. Should be large enough to
// avoid lots of small tiles (and therefore lots of drawing callbacks), but small enough
// to keep the overall tile cost low.
static const int cTiledLayerTileSize = 512;
PlatformCALayerWinInternal::PlatformCALayerWinInternal(PlatformCALayer* owner)
: m_tileSize(CGSizeMake(cTiledLayerTileSize, cTiledLayerTileSize))
, m_constrainedSize(constrainedSize(owner->bounds().size()))
, m_owner(owner)
{
if (m_owner->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Tiled layers are placed in a child layer that is always the first child of the TiledLayer
m_tileParent.adoptCF(CACFLayerCreate(kCACFLayer));
CACFLayerInsertSublayer(m_owner->platformLayer(), m_tileParent.get(), 0);
updateTiles();
}
}
PlatformCALayerWinInternal::~PlatformCALayerWinInternal()
{
}
void PlatformCALayerWinInternal::displayCallback(CACFLayerRef caLayer, CGContextRef context)
{
ASSERT(isMainThread());
if (!owner() || !owner()->owner())
return;
CGContextSaveGState(context);
CGRect layerBounds = owner()->bounds();
if (owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) {
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -layerBounds.size.height);
}
if (owner()->owner()) {
GraphicsContext graphicsContext(context);
// It's important to get the clip from the context, because it may be significantly
// smaller than the layer bounds (e.g. tiled layers)
CGRect clipBounds = CGContextGetClipBoundingBox(context);
IntRect clip(enclosingIntRect(clipBounds));
owner()->owner()->platformCALayerPaintContents(graphicsContext, clip);
}
#ifndef NDEBUG
else {
ASSERT_NOT_REACHED();
// FIXME: ideally we'd avoid calling -setNeedsDisplay on a layer that is a plain color,
// so CA never makes backing store for it (which is what -setNeedsDisplay will do above).
CGContextSetRGBFillColor(context, 0.0f, 1.0f, 0.0f, 1.0f);
CGContextFillRect(context, layerBounds);
}
#endif
if (owner()->owner()->platformCALayerShowRepaintCounter(owner())) {
FontCachePurgePreventer fontCachePurgePreventer;
String text = String::number(owner()->owner()->platformCALayerIncrementRepaintCount());
CGContextSaveGState(context);
// Make the background of the counter the same as the border color,
// unless there is no border, then make it red
float borderWidth = CACFLayerGetBorderWidth(caLayer);
if (borderWidth > 0) {
CGColorRef borderColor = CACFLayerGetBorderColor(caLayer);
const CGFloat* colors = CGColorGetComponents(borderColor);
CGContextSetRGBFillColor(context, colors[0], colors[1], colors[2], colors[3]);
} else
CGContextSetRGBFillColor(context, 1.0f, 0.0f, 0.0f, 0.8f);
CGRect aBounds = layerBounds;
aBounds.size.width = 10 + 10 * text.length();
aBounds.size.height = 22;
CGContextFillRect(context, aBounds);
FontDescription desc;
NONCLIENTMETRICS metrics;
metrics.cbSize = sizeof(metrics);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
FontFamily family;
family.setFamily(metrics.lfSmCaptionFont.lfFaceName);
desc.setFamily(family);
desc.setComputedSize(18);
Font font = Font(desc, 0, 0);
font.update(0);
GraphicsContext cg(context);
cg.setFillColor(Color::black, ColorSpaceDeviceRGB);
cg.drawText(font, TextRun(text), IntPoint(aBounds.origin.x + 5, aBounds.origin.y + 17));
CGContextRestoreGState(context);
}
CGContextRestoreGState(context);
owner()->owner()->platformCALayerLayerDidDisplay(caLayer);
}
void PlatformCALayerWinInternal::internalSetNeedsDisplay(const FloatRect* dirtyRect)
{
if (dirtyRect) {
CGRect rect = *dirtyRect;
CACFLayerSetNeedsDisplay(owner()->platformLayer(), &rect);
} else
CACFLayerSetNeedsDisplay(owner()->platformLayer(), 0);
}
void PlatformCALayerWinInternal::setNeedsDisplay(const FloatRect* dirtyRect)
{
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// FIXME: Only setNeedsDisplay for tiles that are currently visible
int numTileLayers = tileCount();
CGRect rect;
if (dirtyRect)
rect = *dirtyRect;
for (int i = 0; i < numTileLayers; ++i)
CACFLayerSetNeedsDisplay(tileAtIndex(i), dirtyRect ? &rect : 0);
if (m_owner->owner() && m_owner->owner()->platformCALayerShowRepaintCounter(m_owner)) {
CGRect layerBounds = m_owner->bounds();
CGRect indicatorRect = CGRectMake(layerBounds.origin.x, layerBounds.origin.y, 80, 25);
CACFLayerSetNeedsDisplay(tileAtIndex(0), &indicatorRect);
}
} else if (owner()->layerType() == PlatformCALayer::LayerTypeWebLayer) {
if (owner() && owner()->owner()) {
if (owner()->owner()->platformCALayerShowRepaintCounter(owner())) {
FloatRect layerBounds = owner()->bounds();
FloatRect repaintCounterRect = layerBounds;
// We assume a maximum of 4 digits and a font size of 18.
repaintCounterRect.setWidth(80);
repaintCounterRect.setHeight(22);
if (owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown)
repaintCounterRect.setY(layerBounds.height() - (layerBounds.y() + repaintCounterRect.height()));
internalSetNeedsDisplay(&repaintCounterRect);
}
if (dirtyRect && owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) {
FloatRect flippedDirtyRect = *dirtyRect;
flippedDirtyRect.setY(owner()->bounds().height() - (flippedDirtyRect.y() + flippedDirtyRect.height()));
internalSetNeedsDisplay(&flippedDirtyRect);
return;
}
}
internalSetNeedsDisplay(dirtyRect);
}
owner()->setNeedsCommit();
}
void PlatformCALayerWinInternal::setSublayers(const PlatformCALayerList& list)
{
// Remove all the current sublayers and add the passed layers
CACFLayerSetSublayers(owner()->platformLayer(), 0);
// Perform removeFromSuperLayer in a separate pass. CACF requires superlayer to
// be null or CACFLayerInsertSublayer silently fails.
for (size_t i = 0; i < list.size(); i++)
CACFLayerRemoveFromSuperlayer(list[i]->platformLayer());
for (size_t i = 0; i < list.size(); i++)
CACFLayerInsertSublayer(owner()->platformLayer(), list[i]->platformLayer(), i);
owner()->setNeedsCommit();
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Preserve the tile parent after set
CACFLayerInsertSublayer(owner()->platformLayer(), m_tileParent.get(), 0);
}
}
void PlatformCALayerWinInternal::getSublayers(PlatformCALayerList& list) const
{
CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
if (!sublayers) {
list.clear();
return;
}
size_t count = CFArrayGetCount(sublayers);
size_t layersToSkip = 0;
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Exclude the tile parent layer.
layersToSkip = 1;
}
list.resize(count - layersToSkip);
for (size_t arrayIndex = layersToSkip; arrayIndex < count; ++arrayIndex)
list[arrayIndex - layersToSkip] = PlatformCALayer::platformCALayer(const_cast<void*>(CFArrayGetValueAtIndex(sublayers, arrayIndex)));
}
void PlatformCALayerWinInternal::removeAllSublayers()
{
CACFLayerSetSublayers(owner()->platformLayer(), 0);
owner()->setNeedsCommit();
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Restore the tile parent after removal
CACFLayerInsertSublayer(owner()->platformLayer(), m_tileParent.get(), 0);
}
}
void PlatformCALayerWinInternal::insertSublayer(PlatformCALayer* layer, size_t index)
{
index = min(index, sublayerCount());
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Add 1 to account for the tile parent layer
index++;
}
layer->removeFromSuperlayer();
CACFLayerInsertSublayer(owner()->platformLayer(), layer->platformLayer(), index);
owner()->setNeedsCommit();
}
size_t PlatformCALayerWinInternal::sublayerCount() const
{
CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
size_t count = sublayers ? CFArrayGetCount(sublayers) : 0;
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Subtract 1 to account for the tile parent layer
ASSERT(count > 0);
count--;
}
return count;
}
int PlatformCALayerWinInternal::indexOfSublayer(const PlatformCALayer* reference)
{
CACFLayerRef ref = reference->platformLayer();
if (!ref)
return -1;
CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
if (!sublayers)
return -1;
size_t n = CFArrayGetCount(sublayers);
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
for (size_t i = 1; i < n; ++i) {
if (CFArrayGetValueAtIndex(sublayers, i) == ref)
return i - 1;
}
} else {
for (size_t i = 0; i < n; ++i) {
if (CFArrayGetValueAtIndex(sublayers, i) == ref)
return i;
}
}
return -1;
}
PlatformCALayer* PlatformCALayerWinInternal::sublayerAtIndex(int index) const
{
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
// Add 1 to account for the tile parent layer
index++;
}
CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
if (!sublayers || index < 0 || CFArrayGetCount(sublayers) <= index)
return 0;
return PlatformCALayer::platformCALayer(static_cast<CACFLayerRef>(const_cast<void*>(CFArrayGetValueAtIndex(sublayers, index))));
}
void PlatformCALayerWinInternal::setBounds(const FloatRect& rect)
{
if (CGRectEqualToRect(rect, owner()->bounds()))
return;
CACFLayerSetBounds(owner()->platformLayer(), rect);
owner()->setNeedsCommit();
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer) {
m_constrainedSize = constrainedSize(rect.size());
updateTiles();
}
}
void PlatformCALayerWinInternal::setFrame(const FloatRect& rect)
{
CGRect oldFrame = owner()->frame();
if (CGRectEqualToRect(rect, oldFrame))
return;
CACFLayerSetFrame(owner()->platformLayer(), rect);
owner()->setNeedsCommit();
if (owner()->layerType() == PlatformCALayer::LayerTypeWebTiledLayer)
updateTiles();
}
CGSize PlatformCALayerWinInternal::constrainedSize(const CGSize& size) const
{
const int cMaxTileCount = 512;
const float cSqrtMaxTileCount = sqrtf(cMaxTileCount);
CGSize constrainedSize = size;
int tileColumns = ceilf(constrainedSize.width / m_tileSize.width);
int tileRows = ceilf(constrainedSize.height / m_tileSize.height);
bool tooManyTiles = tileColumns && numeric_limits<int>::max() / tileColumns < tileRows || tileColumns * tileRows > cMaxTileCount;
// If number of tiles vertically or horizontally is < sqrt(cMaxTileCount)
// just shorten the longer dimension. Otherwise shorten both dimensions
// according to the ratio of width to height
if (tooManyTiles) {
if (tileRows < cSqrtMaxTileCount)
tileColumns = floorf(cMaxTileCount / tileRows);
else if (tileColumns < cSqrtMaxTileCount)
tileRows = floorf(cMaxTileCount / tileColumns);
else {
tileRows = ceilf(sqrtf(cMaxTileCount * constrainedSize.height / constrainedSize.width));
tileColumns = floorf(cMaxTileCount / tileRows);
}
constrainedSize.width = tileColumns * m_tileSize.width;
constrainedSize.height = tileRows * m_tileSize.height;
}
return constrainedSize;
}
void PlatformCALayerWinInternal::tileDisplayCallback(CACFLayerRef layer, CGContextRef context)
{
static_cast<PlatformCALayerWinInternal*>(CACFLayerGetUserData(layer))->drawTile(layer, context);
}
void PlatformCALayerWinInternal::addTile()
{
RetainPtr<CACFLayerRef> newLayer(AdoptCF, CACFLayerCreate(kCACFLayer));
CACFLayerSetAnchorPoint(newLayer.get(), CGPointMake(0, 1));
CACFLayerSetUserData(newLayer.get(), this);
CACFLayerSetDisplayCallback(newLayer.get(), tileDisplayCallback);
CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
CACFLayerInsertSublayer(m_tileParent.get(), newLayer.get(), sublayers ? CFArrayGetCount(sublayers) : 0);
if (owner()->owner()->platformCALayerShowDebugBorders()) {
CGColorRef borderColor = CGColorCreateGenericRGB(0.5, 0, 0.5, 0.7);
CACFLayerSetBorderColor(newLayer.get(), borderColor);
CGColorRelease(borderColor);
CACFLayerSetBorderWidth(newLayer.get(), 2);
}
}
void PlatformCALayerWinInternal::removeTile()
{
CACFLayerRemoveFromSuperlayer(tileAtIndex(tileCount() - 1));
}
CACFLayerRef PlatformCALayerWinInternal::tileAtIndex(int index)
{
CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
if (!sublayers || index < 0 || index >= tileCount())
return 0;
return static_cast<CACFLayerRef>(const_cast<void*>(CFArrayGetValueAtIndex(sublayers, index)));
}
int PlatformCALayerWinInternal::tileCount() const
{
CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
return sublayers ? CFArrayGetCount(sublayers) : 0;
}
void PlatformCALayerWinInternal::updateTiles()
{
// FIXME: In addition to redoing the number of tiles, we need to only render and have backing
// store for visible layers
int numTilesHorizontal = ceil(m_constrainedSize.width / m_tileSize.width);
int numTilesVertical = ceil(m_constrainedSize.height / m_tileSize.height);
int numTilesTotal = numTilesHorizontal * numTilesVertical;
ASSERT(!m_constrainedSize.height || !m_constrainedSize.width || numTilesTotal > 0);
int numTilesToChange = numTilesTotal - tileCount();
if (numTilesToChange >= 0) {
// Add new tiles
for (int i = 0; i < numTilesToChange; ++i)
addTile();
} else {
// Remove old tiles
numTilesToChange = -numTilesToChange;
for (int i = 0; i < numTilesToChange; ++i)
removeTile();
}
// Set coordinates for all tiles
CFArrayRef tileArray = CACFLayerGetSublayers(m_tileParent.get());
for (int i = 0; i < numTilesHorizontal; ++i) {
for (int j = 0; j < numTilesVertical; ++j) {
CACFLayerRef tile = static_cast<CACFLayerRef>(const_cast<void*>(CFArrayGetValueAtIndex(tileArray, i * numTilesVertical + j)));
CACFLayerSetPosition(tile, CGPointMake(i * m_tileSize.width, j * m_tileSize.height));
int width = min(m_tileSize.width, m_constrainedSize.width - i * m_tileSize.width);
int height = min(m_tileSize.height, m_constrainedSize.height - j * m_tileSize.height);
CACFLayerSetBounds(tile, CGRectMake(i * m_tileSize.width, j * m_tileSize.height, width, height));
// Flip Y to compensate for the flipping that happens during render to match the CG context coordinate space
CATransform3D transform = CATransform3DMakeScale(1, -1, 1);
CATransform3DTranslate(transform, 0, height, 0);
CACFLayerSetTransform(tile, transform);
#ifndef NDEBUG
String name = "Tile (" + String::number(i) + "," + String::number(j) + ")";
CACFLayerSetName(tile, name.createCFString().get());
#endif
}
}
}
void PlatformCALayerWinInternal::drawTile(CACFLayerRef tile, CGContextRef context)
{
CGPoint tilePosition = CACFLayerGetPosition(tile);
CGRect tileBounds = CACFLayerGetBounds(tile);
CGContextSaveGState(context);
// Transform context to be at the origin of the parent layer
CGContextTranslateCTM(context, -tilePosition.x, -tilePosition.y);
// Set the context clipping rectangle to the current tile
CGContextClipToRect(context, CGRectMake(tilePosition.x, tilePosition.y, tileBounds.size.width, tileBounds.size.height));
if (owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) {
// If the layer is rendering top-down, it will flip the coordinates in y. Tiled layers are
// already flipping, so we need to undo that here.
CGContextTranslateCTM(context, 0, owner()->bounds().height());
CGContextScaleCTM(context, 1, -1);
}
// Draw the tile
displayCallback(owner()->platformLayer(), context);
CGContextRestoreGState(context);
}
#endif // USE(ACCELERATED_COMPOSITING)