/* * Copyright (C) 2009 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 "WKCACFLayer.h" #include "CString.h" #include "WKCACFContextFlusher.h" #include "WKCACFLayerRenderer.h" #include #include #include #include #ifndef NDEBUG #include #endif #ifdef DEBUG_ALL #pragma comment(lib, "QuartzCore_debug") #pragma comment(lib, "QuartzCoreInterface_debug") #else #pragma comment(lib, "QuartzCore") #pragma comment(lib, "QuartzCoreInterface") #endif namespace WebCore { using namespace std; static void displayInContext(CACFLayerRef layer, CGContextRef context) { ASSERT_ARG(layer, WKCACFLayer::layer(layer)); WKCACFLayer::layer(layer)->display(context); } #define STATIC_CACF_STRING(name) \ static CFStringRef name() \ { \ static CFStringRef name = wkqcCFStringRef(wkqc##name); \ return name; \ } STATIC_CACF_STRING(kCACFLayer) STATIC_CACF_STRING(kCACFTransformLayer) STATIC_CACF_STRING(kCACFGravityCenter) STATIC_CACF_STRING(kCACFGravityTop) STATIC_CACF_STRING(kCACFGravityBottom) STATIC_CACF_STRING(kCACFGravityLeft) STATIC_CACF_STRING(kCACFGravityRight) STATIC_CACF_STRING(kCACFGravityTopLeft) STATIC_CACF_STRING(kCACFGravityTopRight) STATIC_CACF_STRING(kCACFGravityBottomLeft) STATIC_CACF_STRING(kCACFGravityBottomRight) STATIC_CACF_STRING(kCACFGravityResize) STATIC_CACF_STRING(kCACFGravityResizeAspect) STATIC_CACF_STRING(kCACFGravityResizeAspectFill) STATIC_CACF_STRING(kCACFFilterLinear) STATIC_CACF_STRING(kCACFFilterNearest) STATIC_CACF_STRING(kCACFFilterTrilinear) STATIC_CACF_STRING(kCACFFilterLanczos) static CFStringRef toCACFLayerType(WKCACFLayer::LayerType type) { switch (type) { case WKCACFLayer::Layer: return kCACFLayer(); case WKCACFLayer::TransformLayer: return kCACFTransformLayer(); default: return 0; } } static CFStringRef toCACFContentsGravityType(WKCACFLayer::ContentsGravityType type) { switch (type) { case WKCACFLayer::Center: return kCACFGravityCenter(); case WKCACFLayer::Top: return kCACFGravityTop(); case WKCACFLayer::Bottom: return kCACFGravityBottom(); case WKCACFLayer::Left: return kCACFGravityLeft(); case WKCACFLayer::Right: return kCACFGravityRight(); case WKCACFLayer::TopLeft: return kCACFGravityTopLeft(); case WKCACFLayer::TopRight: return kCACFGravityTopRight(); case WKCACFLayer::BottomLeft: return kCACFGravityBottomLeft(); case WKCACFLayer::BottomRight: return kCACFGravityBottomRight(); case WKCACFLayer::Resize: return kCACFGravityResize(); case WKCACFLayer::ResizeAspect: return kCACFGravityResizeAspect(); case WKCACFLayer::ResizeAspectFill: return kCACFGravityResizeAspectFill(); default: return 0; } } static WKCACFLayer::ContentsGravityType fromCACFContentsGravityType(CFStringRef string) { if (CFEqual(string, kCACFGravityTop())) return WKCACFLayer::Top; if (CFEqual(string, kCACFGravityBottom())) return WKCACFLayer::Bottom; if (CFEqual(string, kCACFGravityLeft())) return WKCACFLayer::Left; if (CFEqual(string, kCACFGravityRight())) return WKCACFLayer::Right; if (CFEqual(string, kCACFGravityTopLeft())) return WKCACFLayer::TopLeft; if (CFEqual(string, kCACFGravityTopRight())) return WKCACFLayer::TopRight; if (CFEqual(string, kCACFGravityBottomLeft())) return WKCACFLayer::BottomLeft; if (CFEqual(string, kCACFGravityBottomRight())) return WKCACFLayer::BottomRight; if (CFEqual(string, kCACFGravityResize())) return WKCACFLayer::Resize; if (CFEqual(string, kCACFGravityResizeAspect())) return WKCACFLayer::ResizeAspect; if (CFEqual(string, kCACFGravityResizeAspectFill())) return WKCACFLayer::ResizeAspectFill; return WKCACFLayer::Center; } static CFStringRef toCACFFilterType(WKCACFLayer::FilterType type) { switch (type) { case WKCACFLayer::Linear: return kCACFFilterLinear(); case WKCACFLayer::Nearest: return kCACFFilterNearest(); case WKCACFLayer::Trilinear: return kCACFFilterTrilinear(); case WKCACFLayer::Lanczos: return kCACFFilterLanczos(); default: return 0; } } static WKCACFLayer::FilterType fromCACFFilterType(CFStringRef string) { if (CFEqual(string, kCACFFilterNearest())) return WKCACFLayer::Nearest; if (CFEqual(string, kCACFFilterTrilinear())) return WKCACFLayer::Trilinear; if (CFEqual(string, kCACFFilterLanczos())) return WKCACFLayer::Lanczos; return WKCACFLayer::Linear; } PassRefPtr WKCACFLayer::create(LayerType type, GraphicsLayerCACF* owner) { if (!WKCACFLayerRenderer::acceleratedCompositingAvailable()) return 0; return adoptRef(new WKCACFLayer(type, owner)); } // FIXME: It might be good to have a way of ensuring that all WKCACFLayers eventually // get destroyed in debug builds. A static counter could accomplish this pretty easily. WKCACFLayer::WKCACFLayer(LayerType type, GraphicsLayerCACF* owner) : m_layer(AdoptCF, CACFLayerCreate(toCACFLayerType(type))) , m_needsDisplayOnBoundsChange(false) , m_owner(owner) { CACFLayerSetUserData(layer(), this); CACFLayerSetDisplayCallback(layer(), displayInContext); } WKCACFLayer::~WKCACFLayer() { // Our superlayer should be holding a reference to us, so there should be no way for us to be destroyed while we still have a superlayer. ASSERT(!superlayer()); CACFLayerSetUserData(layer(), 0); CACFLayerSetDisplayCallback(layer(), 0); } void WKCACFLayer::display(PlatformGraphicsContext* context) { if (!m_owner) return; CGContextSaveGState(context); CGRect layerBounds = bounds(); if (m_owner->contentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) { CGContextScaleCTM(context, 1, -1); CGContextTranslateCTM(context, 0, -layerBounds.size.height); } if (m_owner->client()) { 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)); m_owner->paintGraphicsLayerContents(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 (m_owner->showRepaintCounter()) { char text[16]; // that's a lot of repaints _snprintf(text, sizeof(text), "%d", m_owner->incrementRepaintCount()); CGContextSaveGState(context); CGContextSetRGBFillColor(context, 1.0f, 0.0f, 0.0f, 0.8f); CGRect aBounds = layerBounds; aBounds.size.width = 10 + 12 * strlen(text); aBounds.size.height = 25; CGContextFillRect(context, aBounds); CGContextSetRGBFillColor(context, 0.0f, 0.0f, 0.0f, 1.0f); CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0f, -1.0f)); CGContextSelectFont(context, "Helvetica", 25, kCGEncodingMacRoman); CGContextShowTextAtPoint(context, aBounds.origin.x + 3.0f, aBounds.origin.y + 20.0f, text, strlen(text)); CGContextRestoreGState(context); } CGContextRestoreGState(context); } void WKCACFLayer::becomeRootLayerForContext(CACFContextRef context) { CACFContextSetLayer(context, layer()); setNeedsCommit(); } void WKCACFLayer::setNeedsCommit() { CACFContextRef context = CACFLayerGetContext(rootLayer()->layer()); // The context might now be set yet. This happens if a property gets set // before placing the layer in the tree. In this case we don't need to // worry about remembering the context because we will when the layer is // added to the tree. if (context) WKCACFContextFlusher::shared().addContext(context); // Call notifySyncRequired(), which in this implementation plumbs through to // call setRootLayerNeedsDisplay() on the WebView, which causes the CACFRenderer // to render a frame. if (m_owner) m_owner->notifySyncRequired(); } bool WKCACFLayer::isTransformLayer() const { return CACFLayerGetClass(layer()) == kCACFTransformLayer(); } void WKCACFLayer::addSublayer(PassRefPtr sublayer) { insertSublayer(sublayer, numSublayers()); } void WKCACFLayer::insertSublayer(PassRefPtr sublayer, size_t index) { index = min(index, numSublayers()); CACFLayerInsertSublayer(layer(), sublayer->layer(), index); setNeedsCommit(); } void WKCACFLayer::insertSublayerAboveLayer(PassRefPtr sublayer, const WKCACFLayer* reference) { if (!reference) { insertSublayer(sublayer, 0); return; } int referenceIndex = indexOfSublayer(reference); if (referenceIndex == -1) { addSublayer(sublayer); return; } insertSublayer(sublayer, referenceIndex + 1); } void WKCACFLayer::insertSublayerBelowLayer(PassRefPtr sublayer, const WKCACFLayer* reference) { if (!reference) { insertSublayer(sublayer, 0); return; } int referenceIndex = indexOfSublayer(reference); if (referenceIndex == -1) { addSublayer(sublayer); return; } insertSublayer(sublayer, referenceIndex); } void WKCACFLayer::replaceSublayer(WKCACFLayer* reference, PassRefPtr newLayer) { ASSERT_ARG(reference, reference); ASSERT_ARG(reference, reference->superlayer() == this); if (reference == newLayer) return; if (!newLayer) { removeSublayer(reference); return; } newLayer->removeFromSuperlayer(); int referenceIndex = indexOfSublayer(reference); ASSERT(referenceIndex != -1); if (referenceIndex == -1) return; // FIXME: Can we make this more efficient? The current CACF API doesn't seem to give us a way to do so. reference->removeFromSuperlayer(); insertSublayer(newLayer, referenceIndex); } void WKCACFLayer::removeFromSuperlayer() { WKCACFLayer* superlayer = this->superlayer(); if (!superlayer) return; superlayer->removeSublayer(this); CACFLayerRemoveFromSuperlayer(layer()); superlayer->setNeedsCommit(); } void WKCACFLayer::removeSublayer(const WKCACFLayer* sublayer) { int foundIndex = indexOfSublayer(sublayer); if (foundIndex == -1) return; CACFLayerRemoveFromSuperlayer(sublayer->layer()); setNeedsCommit(); } const WKCACFLayer* WKCACFLayer::sublayerAtIndex(int index) const { CFArrayRef sublayers = CACFLayerGetSublayers(layer()); if (index < 0 || CFArrayGetCount(sublayers) <= index) return 0; return layer(static_cast(const_cast(CFArrayGetValueAtIndex(sublayers, index)))); } int WKCACFLayer::indexOfSublayer(const WKCACFLayer* reference) { CACFLayerRef ref = reference->layer(); if (!ref) return -1; CFArrayRef sublayers = CACFLayerGetSublayers(layer()); size_t n = CFArrayGetCount(sublayers); for (size_t i = 0; i < n; ++i) if (CFArrayGetValueAtIndex(sublayers, i) == ref) return i; return -1; } WKCACFLayer* WKCACFLayer::ancestorOrSelfWithSuperlayer(WKCACFLayer* superlayer) const { WKCACFLayer* layer = const_cast(this); for (WKCACFLayer* ancestor = this->superlayer(); ancestor; layer = ancestor, ancestor = ancestor->superlayer()) { if (ancestor == superlayer) return layer; } return 0; } void WKCACFLayer::setBounds(const CGRect& rect) { if (CGRectEqualToRect(rect, bounds())) return; CACFLayerSetBounds(layer(), rect); setNeedsCommit(); if (m_needsDisplayOnBoundsChange) setNeedsDisplay(); } void WKCACFLayer::setFrame(const CGRect& rect) { CGRect oldFrame = frame(); if (CGRectEqualToRect(rect, oldFrame)) return; CACFLayerSetFrame(layer(), rect); setNeedsCommit(); if (m_needsDisplayOnBoundsChange) setNeedsDisplay(); } void WKCACFLayer::setContentsGravity(ContentsGravityType type) { CACFLayerSetContentsGravity(layer(), toCACFContentsGravityType(type)); setNeedsCommit(); } WKCACFLayer::ContentsGravityType WKCACFLayer::contentsGravity() const { return fromCACFContentsGravityType(CACFLayerGetContentsGravity(layer())); } void WKCACFLayer::setMagnificationFilter(FilterType type) { CACFLayerSetMagnificationFilter(layer(), toCACFFilterType(type)); setNeedsCommit(); } WKCACFLayer::FilterType WKCACFLayer::magnificationFilter() const { return fromCACFFilterType(CACFLayerGetMagnificationFilter(layer())); } void WKCACFLayer::setMinificationFilter(FilterType type) { CACFLayerSetMinificationFilter(layer(), toCACFFilterType(type)); setNeedsCommit(); } WKCACFLayer::FilterType WKCACFLayer::minificationFilter() const { return fromCACFFilterType(CACFLayerGetMinificationFilter(layer())); } WKCACFLayer* WKCACFLayer::rootLayer() const { WKCACFLayer* layer = const_cast(this); for (WKCACFLayer* superlayer = layer->superlayer(); superlayer; layer = superlayer, superlayer = superlayer->superlayer()) { } return layer; } void WKCACFLayer::removeAllSublayers() { CACFLayerSetSublayers(layer(), 0); setNeedsCommit(); } void WKCACFLayer::setSublayers(const Vector >& sublayers) { if (sublayers.isEmpty()) CACFLayerSetSublayers(layer(), 0); else { // Create a vector of CACFLayers. Vector layers; for (size_t i = 0; i < sublayers.size(); i++) layers.append(sublayers[i]->layer()); RetainPtr layersArray(AdoptCF, CFArrayCreate(0, layers.data(), layers.size(), 0)); CACFLayerSetSublayers(layer(), layersArray.get()); } setNeedsCommit(); } void WKCACFLayer::moveSublayers(WKCACFLayer* fromLayer, WKCACFLayer* toLayer) { if (!fromLayer || !toLayer) return; CACFLayerSetSublayers(toLayer->layer(), CACFLayerGetSublayers(fromLayer->layer())); fromLayer->setNeedsCommit(); toLayer->setNeedsCommit(); } WKCACFLayer* WKCACFLayer::superlayer() const { CACFLayerRef super = CACFLayerGetSuperlayer(layer()); if (!super) return 0; return WKCACFLayer::layer(super); } void WKCACFLayer::setNeedsDisplay(const CGRect& dirtyRect) { if (m_owner) CACFLayerSetNeedsDisplay(layer(), &dirtyRect); setNeedsCommit(); } void WKCACFLayer::setNeedsDisplay() { if (m_owner) CACFLayerSetNeedsDisplay(layer(), 0); setNeedsCommit(); } #ifndef NDEBUG static void printIndent(int indent) { for ( ; indent > 0; --indent) fprintf(stderr, " "); } static void printTransform(const CATransform3D& transform) { fprintf(stderr, "[%g %g %g %g; %g %g %g %g; %g %g %g %g; %g %g %g %g]", transform.m11, transform.m12, transform.m13, transform.m14, transform.m21, transform.m22, transform.m23, transform.m24, transform.m31, transform.m32, transform.m33, transform.m34, transform.m41, transform.m42, transform.m43, transform.m44); } void WKCACFLayer::printTree() const { // Print heading info CGRect rootBounds = bounds(); fprintf(stderr, "\n\n** Render tree at time %g (bounds %g, %g %gx%g) **\n\n", currentTime(), rootBounds.origin.x, rootBounds.origin.y, rootBounds.size.width, rootBounds.size.height); // Print layer tree from the root printLayer(0); } void WKCACFLayer::printLayer(int indent) const { CGPoint layerPosition = position(); CGPoint layerAnchorPoint = anchorPoint(); CGRect layerBounds = bounds(); printIndent(indent); fprintf(stderr, "(%s [%g %g %g] [%g %g %g %g] [%g %g %g]\n", isTransformLayer() ? "transform-layer" : "layer", layerPosition.x, layerPosition.y, zPosition(), layerBounds.origin.x, layerBounds.origin.y, layerBounds.size.width, layerBounds.size.height, layerAnchorPoint.x, layerAnchorPoint.y, anchorPointZ()); // Print name if needed String layerName = name(); if (!layerName.isEmpty()) { printIndent(indent + 1); fprintf(stderr, "(name %s)\n", layerName.utf8().data()); } // Print masksToBounds if needed bool layerMasksToBounds = masksToBounds(); if (layerMasksToBounds) { printIndent(indent + 1); fprintf(stderr, "(masksToBounds true)\n"); } // Print opacity if needed float layerOpacity = opacity(); if (layerOpacity != 1) { printIndent(indent + 1); fprintf(stderr, "(opacity %hf)\n", layerOpacity); } // Print sublayerTransform if needed CATransform3D layerTransform = sublayerTransform(); if (!CATransform3DIsIdentity(layerTransform)) { printIndent(indent + 1); fprintf(stderr, "(sublayerTransform "); printTransform(layerTransform); fprintf(stderr, ")\n"); } // Print transform if needed layerTransform = transform(); if (!CATransform3DIsIdentity(layerTransform)) { printIndent(indent + 1); fprintf(stderr, "(transform "); printTransform(layerTransform); fprintf(stderr, ")\n"); } // Print contents if needed CGImageRef layerContents = contents(); if (layerContents) { printIndent(indent + 1); fprintf(stderr, "(contents (image [%d %d]))\n", CGImageGetWidth(layerContents), CGImageGetHeight(layerContents)); } // Print sublayers if needed int n = numSublayers(); if (n > 0) { printIndent(indent + 1); fprintf(stderr, "(sublayers\n"); for (int i = 0; i < n; ++i) sublayerAtIndex(i)->printLayer(indent + 2); printIndent(indent + 1); fprintf(stderr, ")\n"); } printIndent(indent); fprintf(stderr, ")\n"); } #endif // #ifndef NDEBUG } #endif // USE(ACCELERATED_COMPOSITING)