1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#include "PlatformCALayerWinInternal.h"
29
30#include "Font.h"
31#include "FontCache.h"
32#include "GraphicsContext.h"
33#include "PlatformCALayer.h"
34#include "TextRun.h"
35#include "TileController.h"
36#include "TiledBacking.h"
37#include <QuartzCore/CACFLayer.h>
38#include <wtf/MainThread.h>
39
40using namespace std;
41using namespace WebCore;
42
43// The width and height of a single tile in a tiled layer. Should be large enough to
44// avoid lots of small tiles (and therefore lots of drawing callbacks), but small enough
45// to keep the overall tile cost low.
46static const int cTiledLayerTileSize = 512;
47
48static bool layerTypeIsTiled(const PlatformCALayer::LayerType layerType)
49{
50    return layerType == PlatformCALayer::LayerTypeWebTiledLayer
51        || layerType == PlatformCALayer::LayerTypePageTiledBackingLayer
52        || layerType == PlatformCALayer::LayerTypeTiledBackingLayer;
53}
54
55PlatformCALayerWinInternal::PlatformCALayerWinInternal(PlatformCALayer* owner)
56    : m_tileSize(CGSizeMake(cTiledLayerTileSize, cTiledLayerTileSize))
57    , m_constrainedSize(constrainedSize(owner->bounds().size()))
58    , m_owner(owner)
59{
60    if (layerTypeIsTiled(m_owner->layerType())) {
61        // Tiled layers are placed in a child layer that is always the first child of the TiledLayer
62        m_tileParent = adoptCF(CACFLayerCreate(kCACFLayer));
63        CACFLayerInsertSublayer(m_owner->platformLayer(), m_tileParent.get(), 0);
64        updateTiles();
65    }
66}
67
68PlatformCALayerWinInternal::~PlatformCALayerWinInternal()
69{
70}
71
72void PlatformCALayerWinInternal::displayCallback(CACFLayerRef caLayer, CGContextRef context)
73{
74    ASSERT(isMainThread());
75
76    if (!owner() || !owner()->owner())
77        return;
78
79    CGContextSaveGState(context);
80
81    CGRect layerBounds = owner()->bounds();
82    if (owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) {
83        CGContextScaleCTM(context, 1, -1);
84        CGContextTranslateCTM(context, 0, -layerBounds.size.height);
85    }
86
87    if (owner()->owner()) {
88        GraphicsContext graphicsContext(context);
89
90        // It's important to get the clip from the context, because it may be significantly
91        // smaller than the layer bounds (e.g. tiled layers)
92        CGRect clipBounds = CGContextGetClipBoundingBox(context);
93        IntRect clip(enclosingIntRect(clipBounds));
94        owner()->owner()->platformCALayerPaintContents(owner(), graphicsContext, clip);
95    }
96#ifndef NDEBUG
97    else {
98        ASSERT_NOT_REACHED();
99
100        // FIXME: ideally we'd avoid calling -setNeedsDisplay on a layer that is a plain color,
101        // so CA never makes backing store for it (which is what -setNeedsDisplay will do above).
102        CGContextSetRGBFillColor(context, 0.0f, 1.0f, 0.0f, 1.0f);
103        CGContextFillRect(context, layerBounds);
104    }
105#endif
106
107    if (owner()->owner()->platformCALayerShowRepaintCounter(owner())) {
108        FontCachePurgePreventer fontCachePurgePreventer;
109
110        String text = String::number(owner()->owner()->platformCALayerIncrementRepaintCount(owner()));
111
112        CGContextSaveGState(context);
113
114        // Make the background of the counter the same as the border color,
115        // unless there is no border, then make it red
116        float borderWidth = CACFLayerGetBorderWidth(caLayer);
117        if (borderWidth > 0) {
118            CGColorRef borderColor = CACFLayerGetBorderColor(caLayer);
119            const CGFloat* colors = CGColorGetComponents(borderColor);
120            CGContextSetRGBFillColor(context, colors[0], colors[1], colors[2], colors[3]);
121        } else
122            CGContextSetRGBFillColor(context, 1.0f, 0.0f, 0.0f, 0.8f);
123
124        CGRect aBounds = layerBounds;
125
126        aBounds.size.width = 10 + 10 * text.length();
127        aBounds.size.height = 22;
128        CGContextFillRect(context, aBounds);
129
130        FontDescription desc;
131
132        NONCLIENTMETRICS metrics;
133        metrics.cbSize = sizeof(metrics);
134        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, metrics.cbSize, &metrics, 0);
135        desc.setOneFamily(metrics.lfSmCaptionFont.lfFaceName);
136
137        desc.setComputedSize(18);
138
139        Font font = Font(desc, 0, 0);
140        font.update(0);
141
142        GraphicsContext cg(context);
143        cg.setFillColor(Color::black, ColorSpaceDeviceRGB);
144        cg.drawText(font, TextRun(text), IntPoint(aBounds.origin.x + 5, aBounds.origin.y + 17));
145
146        CGContextRestoreGState(context);
147    }
148
149    CGContextRestoreGState(context);
150
151    owner()->owner()->platformCALayerLayerDidDisplay(owner());
152}
153
154void PlatformCALayerWinInternal::internalSetNeedsDisplay(const FloatRect* dirtyRect)
155{
156    if (dirtyRect) {
157        CGRect rect = *dirtyRect;
158        CACFLayerSetNeedsDisplay(owner()->platformLayer(), &rect);
159    } else
160        CACFLayerSetNeedsDisplay(owner()->platformLayer(), 0);
161}
162
163void PlatformCALayerWinInternal::setNeedsDisplay(const FloatRect* dirtyRect)
164{
165    if (layerTypeIsTiled(m_owner->layerType())) {
166        // FIXME: Only setNeedsDisplay for tiles that are currently visible
167        int numTileLayers = tileCount();
168        CGRect rect;
169        if (dirtyRect)
170            rect = *dirtyRect;
171        for (int i = 0; i < numTileLayers; ++i)
172            CACFLayerSetNeedsDisplay(tileAtIndex(i), dirtyRect ? &rect : 0);
173
174        if (m_owner->owner() && m_owner->owner()->platformCALayerShowRepaintCounter(m_owner)) {
175            CGRect layerBounds = m_owner->bounds();
176            CGRect indicatorRect = CGRectMake(layerBounds.origin.x, layerBounds.origin.y, 80, 25);
177            CACFLayerSetNeedsDisplay(tileAtIndex(0), &indicatorRect);
178        }
179    } else if (owner()->layerType() == PlatformCALayer::LayerTypeWebLayer) {
180        if (owner() && owner()->owner()) {
181            if (owner()->owner()->platformCALayerShowRepaintCounter(owner())) {
182                FloatRect layerBounds = owner()->bounds();
183                FloatRect repaintCounterRect = layerBounds;
184
185                // We assume a maximum of 4 digits and a font size of 18.
186                repaintCounterRect.setWidth(80);
187                repaintCounterRect.setHeight(22);
188                if (owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown)
189                    repaintCounterRect.setY(layerBounds.height() - (layerBounds.y() + repaintCounterRect.height()));
190                internalSetNeedsDisplay(&repaintCounterRect);
191            }
192            if (dirtyRect && owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) {
193                FloatRect flippedDirtyRect = *dirtyRect;
194                flippedDirtyRect.setY(owner()->bounds().height() - (flippedDirtyRect.y() + flippedDirtyRect.height()));
195                internalSetNeedsDisplay(&flippedDirtyRect);
196                return;
197            }
198        }
199
200        internalSetNeedsDisplay(dirtyRect);
201    }
202    owner()->setNeedsCommit();
203}
204
205void PlatformCALayerWinInternal::setSublayers(const PlatformCALayerList& list)
206{
207    // Remove all the current sublayers and add the passed layers
208    CACFLayerSetSublayers(owner()->platformLayer(), 0);
209
210    // Perform removeFromSuperLayer in a separate pass. CACF requires superlayer to
211    // be null or CACFLayerInsertSublayer silently fails.
212    for (size_t i = 0; i < list.size(); i++)
213        CACFLayerRemoveFromSuperlayer(list[i]->platformLayer());
214
215    for (size_t i = 0; i < list.size(); i++)
216        CACFLayerInsertSublayer(owner()->platformLayer(), list[i]->platformLayer(), i);
217
218    owner()->setNeedsCommit();
219
220    if (layerTypeIsTiled(m_owner->layerType())) {
221        // Preserve the tile parent after set
222        CACFLayerInsertSublayer(owner()->platformLayer(), m_tileParent.get(), 0);
223    }
224}
225
226void PlatformCALayerWinInternal::getSublayers(PlatformCALayerList& list) const
227{
228    CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
229    if (!sublayers) {
230        list.clear();
231        return;
232    }
233
234    size_t count = CFArrayGetCount(sublayers);
235
236    size_t layersToSkip = 0;
237    if (layerTypeIsTiled(m_owner->layerType())) {
238        // Exclude the tile parent layer.
239        layersToSkip = 1;
240    }
241
242    list.resize(count - layersToSkip);
243    for (size_t arrayIndex = layersToSkip; arrayIndex < count; ++arrayIndex)
244        list[arrayIndex - layersToSkip] = PlatformCALayer::platformCALayer(const_cast<void*>(CFArrayGetValueAtIndex(sublayers, arrayIndex)));
245}
246
247void PlatformCALayerWinInternal::removeAllSublayers()
248{
249    CACFLayerSetSublayers(owner()->platformLayer(), 0);
250    owner()->setNeedsCommit();
251
252    if (layerTypeIsTiled(m_owner->layerType())) {
253        // Restore the tile parent after removal
254        CACFLayerInsertSublayer(owner()->platformLayer(), m_tileParent.get(), 0);
255    }
256}
257
258void PlatformCALayerWinInternal::insertSublayer(PlatformCALayer* layer, size_t index)
259{
260    index = min(index, sublayerCount());
261    if (layerTypeIsTiled(m_owner->layerType())) {
262        // Add 1 to account for the tile parent layer
263        index++;
264    }
265
266    layer->removeFromSuperlayer();
267    CACFLayerInsertSublayer(owner()->platformLayer(), layer->platformLayer(), index);
268    owner()->setNeedsCommit();
269}
270
271size_t PlatformCALayerWinInternal::sublayerCount() const
272{
273    CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
274    size_t count = sublayers ? CFArrayGetCount(sublayers) : 0;
275
276    if (layerTypeIsTiled(m_owner->layerType())) {
277        // Subtract 1 to account for the tile parent layer
278        ASSERT(count > 0);
279        count--;
280    }
281
282    return count;
283}
284
285int PlatformCALayerWinInternal::indexOfSublayer(const PlatformCALayer* reference)
286{
287    CACFLayerRef ref = reference->platformLayer();
288    if (!ref)
289        return -1;
290
291    CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
292    if (!sublayers)
293        return -1;
294
295    size_t n = CFArrayGetCount(sublayers);
296
297    if (layerTypeIsTiled(m_owner->layerType())) {
298        for (size_t i = 1; i < n; ++i) {
299            if (CFArrayGetValueAtIndex(sublayers, i) == ref)
300                return i - 1;
301        }
302    } else {
303        for (size_t i = 0; i < n; ++i) {
304            if (CFArrayGetValueAtIndex(sublayers, i) == ref)
305                return i;
306        }
307    }
308
309    return -1;
310}
311
312PlatformCALayer* PlatformCALayerWinInternal::sublayerAtIndex(int index) const
313{
314    if (layerTypeIsTiled(m_owner->layerType())) {
315        // Add 1 to account for the tile parent layer
316        index++;
317    }
318
319    CFArrayRef sublayers = CACFLayerGetSublayers(owner()->platformLayer());
320    if (!sublayers || index < 0 || CFArrayGetCount(sublayers) <= index)
321        return 0;
322
323    return PlatformCALayer::platformCALayer(static_cast<CACFLayerRef>(const_cast<void*>(CFArrayGetValueAtIndex(sublayers, index))));
324}
325
326void PlatformCALayerWinInternal::setBounds(const FloatRect& rect)
327{
328    if (CGRectEqualToRect(rect, owner()->bounds()))
329        return;
330
331    CACFLayerSetBounds(owner()->platformLayer(), rect);
332    owner()->setNeedsCommit();
333
334    if (layerTypeIsTiled(m_owner->layerType())) {
335        m_constrainedSize = constrainedSize(rect.size());
336        updateTiles();
337    }
338}
339
340void PlatformCALayerWinInternal::setFrame(const FloatRect& rect)
341{
342    CGRect oldFrame = CACFLayerGetFrame(owner()->platformLayer());
343    if (CGRectEqualToRect(rect, oldFrame))
344        return;
345
346    CACFLayerSetFrame(owner()->platformLayer(), rect);
347    owner()->setNeedsCommit();
348
349    if (layerTypeIsTiled(m_owner->layerType()))
350        updateTiles();
351}
352
353CGSize PlatformCALayerWinInternal::constrainedSize(const CGSize& size) const
354{
355    const int cMaxTileCount = 512;
356    const float cSqrtMaxTileCount = sqrtf(cMaxTileCount);
357
358    CGSize constrainedSize = size;
359
360    int tileColumns = ceilf(constrainedSize.width / m_tileSize.width);
361    int tileRows = ceilf(constrainedSize.height / m_tileSize.height);
362
363    bool tooManyTiles = tileColumns && numeric_limits<int>::max() / tileColumns < tileRows || tileColumns * tileRows > cMaxTileCount;
364
365    // If number of tiles vertically or horizontally is < sqrt(cMaxTileCount)
366    // just shorten the longer dimension. Otherwise shorten both dimensions
367    // according to the ratio of width to height
368
369    if (tooManyTiles) {
370        if (tileRows < cSqrtMaxTileCount)
371            tileColumns = floorf(cMaxTileCount / tileRows);
372        else if (tileColumns < cSqrtMaxTileCount)
373            tileRows = floorf(cMaxTileCount / tileColumns);
374        else {
375            tileRows = ceilf(sqrtf(cMaxTileCount * constrainedSize.height / constrainedSize.width));
376            tileColumns = floorf(cMaxTileCount / tileRows);
377        }
378
379        constrainedSize.width = tileColumns * m_tileSize.width;
380        constrainedSize.height = tileRows * m_tileSize.height;
381    }
382
383    return constrainedSize;
384}
385
386void PlatformCALayerWinInternal::tileDisplayCallback(CACFLayerRef layer, CGContextRef context)
387{
388    static_cast<PlatformCALayerWinInternal*>(CACFLayerGetUserData(layer))->drawTile(layer, context);
389}
390
391void PlatformCALayerWinInternal::addTile()
392{
393    RetainPtr<CACFLayerRef> newLayer = adoptCF(CACFLayerCreate(kCACFLayer));
394    CACFLayerSetAnchorPoint(newLayer.get(), CGPointMake(0, 1));
395    CACFLayerSetUserData(newLayer.get(), this);
396    CACFLayerSetDisplayCallback(newLayer.get(), tileDisplayCallback);
397
398    CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
399    CACFLayerInsertSublayer(m_tileParent.get(), newLayer.get(), sublayers ? CFArrayGetCount(sublayers) : 0);
400
401    if (owner()->owner()->platformCALayerShowDebugBorders()) {
402        CGColorRef borderColor = CGColorCreateGenericRGB(0.5, 0, 0.5, 0.7);
403        CACFLayerSetBorderColor(newLayer.get(), borderColor);
404        CGColorRelease(borderColor);
405        CACFLayerSetBorderWidth(newLayer.get(), 2);
406    }
407}
408
409void PlatformCALayerWinInternal::removeTile()
410{
411    CACFLayerRemoveFromSuperlayer(tileAtIndex(tileCount() - 1));
412}
413
414CACFLayerRef PlatformCALayerWinInternal::tileAtIndex(int index)
415{
416    CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
417    if (!sublayers || index < 0 || index >= tileCount())
418        return 0;
419
420    return static_cast<CACFLayerRef>(const_cast<void*>(CFArrayGetValueAtIndex(sublayers, index)));
421}
422
423int PlatformCALayerWinInternal::tileCount() const
424{
425    CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
426    return sublayers ? CFArrayGetCount(sublayers) : 0;
427}
428
429void PlatformCALayerWinInternal::updateTiles()
430{
431    // FIXME: In addition to redoing the number of tiles, we need to only render and have backing
432    // store for visible layers
433    int numTilesHorizontal = ceil(m_constrainedSize.width / m_tileSize.width);
434    int numTilesVertical = ceil(m_constrainedSize.height / m_tileSize.height);
435    int numTilesTotal = numTilesHorizontal * numTilesVertical;
436    ASSERT(!m_constrainedSize.height || !m_constrainedSize.width || numTilesTotal > 0);
437
438    int numTilesToChange = numTilesTotal - tileCount();
439    if (numTilesToChange >= 0) {
440        // Add new tiles
441        for (int i = 0; i < numTilesToChange; ++i)
442            addTile();
443    } else {
444        // Remove old tiles
445        numTilesToChange = -numTilesToChange;
446        for (int i = 0; i < numTilesToChange; ++i)
447            removeTile();
448    }
449
450    // Set coordinates for all tiles
451    CFArrayRef tileArray = CACFLayerGetSublayers(m_tileParent.get());
452
453    for (int i = 0; i < numTilesHorizontal; ++i) {
454        for (int j = 0; j < numTilesVertical; ++j) {
455            CACFLayerRef tile = static_cast<CACFLayerRef>(const_cast<void*>(CFArrayGetValueAtIndex(tileArray, i * numTilesVertical + j)));
456            CACFLayerSetPosition(tile, CGPointMake(i * m_tileSize.width, j * m_tileSize.height));
457            int width = min(m_tileSize.width, m_constrainedSize.width - i * m_tileSize.width);
458            int height = min(m_tileSize.height, m_constrainedSize.height - j * m_tileSize.height);
459            CACFLayerSetBounds(tile, CGRectMake(i * m_tileSize.width, j * m_tileSize.height, width, height));
460
461            // Flip Y to compensate for the flipping that happens during render to match the CG context coordinate space
462            CATransform3D transform = CATransform3DMakeScale(1, -1, 1);
463            CATransform3DTranslate(transform, 0, height, 0);
464            CACFLayerSetTransform(tile, transform);
465
466#ifndef NDEBUG
467            String name = "Tile (" + String::number(i) + "," + String::number(j) + ")";
468            CACFLayerSetName(tile, name.createCFString().get());
469#endif
470        }
471    }
472}
473
474void PlatformCALayerWinInternal::drawTile(CACFLayerRef tile, CGContextRef context)
475{
476    CGPoint tilePosition = CACFLayerGetPosition(tile);
477    CGRect tileBounds = CACFLayerGetBounds(tile);
478
479    CGContextSaveGState(context);
480
481    // Transform context to be at the origin of the parent layer
482    CGContextTranslateCTM(context, -tilePosition.x, -tilePosition.y);
483
484    // Set the context clipping rectangle to the current tile
485    CGContextClipToRect(context, CGRectMake(tilePosition.x, tilePosition.y, tileBounds.size.width, tileBounds.size.height));
486
487    if (owner()->owner()->platformCALayerContentsOrientation() == WebCore::GraphicsLayer::CompositingCoordinatesTopDown) {
488        // If the layer is rendering top-down, it will flip the coordinates in y. Tiled layers are
489        // already flipping, so we need to undo that here.
490        CGContextTranslateCTM(context, 0, owner()->bounds().height());
491        CGContextScaleCTM(context, 1, -1);
492    }
493
494    // Draw the tile
495    displayCallback(owner()->platformLayer(), context);
496
497    CGContextRestoreGState(context);
498}
499
500TileController* PlatformCALayerWinInternal::createTileController(PlatformCALayer* rootLayer)
501{
502    ASSERT(!m_tileController);
503    m_tileController = TileController::create(rootLayer);
504    return m_tileController.get();
505}
506
507TiledBacking* PlatformCALayerWinInternal::tiledBacking()
508{
509    return m_tileController.get();
510}
511