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 
37 using namespace std;
38 using 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.
43 static const int cTiledLayerTileSize = 512;
44 
PlatformCALayerWinInternal(PlatformCALayer * owner)45 PlatformCALayerWinInternal::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 
~PlatformCALayerWinInternal()58 PlatformCALayerWinInternal::~PlatformCALayerWinInternal()
59 {
60 }
61 
displayCallback(CACFLayerRef caLayer,CGContextRef context)62 void 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 
internalSetNeedsDisplay(const FloatRect * dirtyRect)142 void 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 
setNeedsDisplay(const FloatRect * dirtyRect)151 void 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 
setSublayers(const PlatformCALayerList & list)193 void 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 
getSublayers(PlatformCALayerList & list) const214 void 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 
removeAllSublayers()235 void 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 
insertSublayer(PlatformCALayer * layer,size_t index)246 void 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 
sublayerCount() const259 size_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 
indexOfSublayer(const PlatformCALayer * reference)273 int 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 
sublayerAtIndex(int index) const300 PlatformCALayer* 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 
setBounds(const FloatRect & rect)314 void 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 
setFrame(const FloatRect & rect)328 void 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 
constrainedSize(const CGSize & size) const341 CGSize 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 
tileDisplayCallback(CACFLayerRef layer,CGContextRef context)373 void PlatformCALayerWinInternal::tileDisplayCallback(CACFLayerRef layer, CGContextRef context)
374 {
375     static_cast<PlatformCALayerWinInternal*>(CACFLayerGetUserData(layer))->drawTile(layer, context);
376 }
377 
addTile()378 void 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 
removeTile()396 void PlatformCALayerWinInternal::removeTile()
397 {
398     CACFLayerRemoveFromSuperlayer(tileAtIndex(tileCount() - 1));
399 }
400 
tileAtIndex(int index)401 CACFLayerRef 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 
tileCount() const410 int PlatformCALayerWinInternal::tileCount() const
411 {
412     CFArrayRef sublayers = CACFLayerGetSublayers(m_tileParent.get());
413     return sublayers ? CFArrayGetCount(sublayers) : 0;
414 }
415 
updateTiles()416 void 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 
drawTile(CACFLayerRef tile,CGContextRef context)460 void 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