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