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