/* * 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 #include 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(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(const_cast(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::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(CACFLayerGetUserData(layer))->drawTile(layer, context); } void PlatformCALayerWinInternal::addTile() { RetainPtr 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(const_cast(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(const_cast(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, RetainPtr(AdoptCF, 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)