1 /*
2 * Copyright (C) 2010 Google 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26
27 #include "config.h"
28
29 #if USE(ACCELERATED_COMPOSITING)
30
31 #include "LayerTilerChromium.h"
32
33 #include "GraphicsContext.h"
34 #include "GraphicsContext3D.h"
35 #include "LayerRendererChromium.h"
36 #include "LayerTexture.h"
37 #include "TraceEvent.h"
38
39 #include <wtf/PassOwnArrayPtr.h>
40
41 using namespace std;
42
43 static int minTextureSize = 16;
44
45 namespace WebCore {
46
create(LayerRendererChromium * layerRenderer,const IntSize & tileSize,BorderTexelOption border)47 PassOwnPtr<LayerTilerChromium> LayerTilerChromium::create(LayerRendererChromium* layerRenderer, const IntSize& tileSize, BorderTexelOption border)
48 {
49 if (!layerRenderer || tileSize.isEmpty())
50 return 0;
51
52 return adoptPtr(new LayerTilerChromium(layerRenderer, tileSize, border));
53 }
54
LayerTilerChromium(LayerRendererChromium * layerRenderer,const IntSize & tileSize,BorderTexelOption border)55 LayerTilerChromium::LayerTilerChromium(LayerRendererChromium* layerRenderer, const IntSize& tileSize, BorderTexelOption border)
56 : m_skipsDraw(false)
57 , m_tilingData(max(tileSize.width(), tileSize.height()), 0, 0, border == HasBorderTexels)
58 , m_layerRenderer(layerRenderer)
59 {
60 setTileSize(tileSize);
61 }
62
setLayerRenderer(LayerRendererChromium * layerRenderer)63 void LayerTilerChromium::setLayerRenderer(LayerRendererChromium* layerRenderer)
64 {
65 if (m_layerRenderer != layerRenderer)
66 reset();
67 m_layerRenderer = layerRenderer;
68 }
69
~LayerTilerChromium()70 LayerTilerChromium::~LayerTilerChromium()
71 {
72 reset();
73 }
74
layerRendererContext() const75 GraphicsContext3D* LayerTilerChromium::layerRendererContext() const
76 {
77 ASSERT(layerRenderer());
78 return layerRenderer()->context();
79 }
80
setTileSize(const IntSize & requestedSize)81 void LayerTilerChromium::setTileSize(const IntSize& requestedSize)
82 {
83 IntSize size(max(minTextureSize, requestedSize.width()), max(minTextureSize, requestedSize.height()));
84
85 if (m_tileSize == size)
86 return;
87
88 reset();
89
90 m_tileSize = size;
91 m_tilePixels = adoptArrayPtr(new uint8_t[m_tileSize.width() * m_tileSize.height() * 4]);
92 m_tilingData.setMaxTextureSize(max(size.width(), size.height()));
93 }
94
getSingleTexture()95 LayerTexture* LayerTilerChromium::getSingleTexture()
96 {
97 Tile* tile = tileAt(0, 0);
98 return tile ? tile->texture() : 0;
99 }
100
reset()101 void LayerTilerChromium::reset()
102 {
103 m_tiles.clear();
104 m_unusedTiles.clear();
105 m_tilingData.setTotalSize(0, 0);
106 }
107
createTile(int i,int j)108 LayerTilerChromium::Tile* LayerTilerChromium::createTile(int i, int j)
109 {
110 ASSERT(!tileAt(i, j));
111
112 RefPtr<Tile> tile;
113 if (m_unusedTiles.size() > 0) {
114 tile = m_unusedTiles.last().release();
115 m_unusedTiles.removeLast();
116 ASSERT(tile->refCount() == 1);
117 } else {
118 GraphicsContext3D* context = layerRendererContext();
119 TextureManager* manager = layerRenderer()->textureManager();
120 tile = adoptRef(new Tile(LayerTexture::create(context, manager)));
121 }
122 m_tiles.add(make_pair(i, j), tile);
123
124 tile->moveTo(i, j);
125 tile->m_dirtyLayerRect = tileLayerRect(tile.get());
126
127 return tile.get();
128 }
129
invalidateTiles(const IntRect & contentRect)130 void LayerTilerChromium::invalidateTiles(const IntRect& contentRect)
131 {
132 if (!m_tiles.size())
133 return;
134
135 Vector<TileMapKey> removeKeys;
136 for (TileMap::iterator iter = m_tiles.begin(); iter != m_tiles.end(); ++iter) {
137 Tile* tile = iter->second.get();
138 IntRect tileRect = tileContentRect(tile);
139 if (tileRect.intersects(contentRect))
140 continue;
141 removeKeys.append(iter->first);
142 }
143
144 for (size_t i = 0; i < removeKeys.size(); ++i)
145 m_unusedTiles.append(m_tiles.take(removeKeys[i]));
146 }
147
contentRectToTileIndices(const IntRect & contentRect,int & left,int & top,int & right,int & bottom) const148 void LayerTilerChromium::contentRectToTileIndices(const IntRect& contentRect, int& left, int& top, int& right, int& bottom) const
149 {
150 const IntRect layerRect = contentRectToLayerRect(contentRect);
151
152 left = m_tilingData.tileXIndexFromSrcCoord(layerRect.x());
153 top = m_tilingData.tileYIndexFromSrcCoord(layerRect.y());
154 right = m_tilingData.tileXIndexFromSrcCoord(layerRect.maxX() - 1);
155 bottom = m_tilingData.tileYIndexFromSrcCoord(layerRect.maxY() - 1);
156 }
157
contentRectToLayerRect(const IntRect & contentRect) const158 IntRect LayerTilerChromium::contentRectToLayerRect(const IntRect& contentRect) const
159 {
160 IntPoint pos(contentRect.x() - m_layerPosition.x(), contentRect.y() - m_layerPosition.y());
161 IntRect layerRect(pos, contentRect.size());
162
163 // Clip to the position.
164 if (pos.x() < 0 || pos.y() < 0)
165 layerRect = IntRect(IntPoint(0, 0), IntSize(contentRect.width() + pos.x(), contentRect.height() + pos.y()));
166 return layerRect;
167 }
168
layerRectToContentRect(const IntRect & layerRect) const169 IntRect LayerTilerChromium::layerRectToContentRect(const IntRect& layerRect) const
170 {
171 IntRect contentRect = layerRect;
172 contentRect.move(m_layerPosition.x(), m_layerPosition.y());
173 return contentRect;
174 }
175
tileAt(int i,int j) const176 LayerTilerChromium::Tile* LayerTilerChromium::tileAt(int i, int j) const
177 {
178 Tile* tile = m_tiles.get(make_pair(i, j)).get();
179 ASSERT(!tile || tile->refCount() == 1);
180 return tile;
181 }
182
tileContentRect(const Tile * tile) const183 IntRect LayerTilerChromium::tileContentRect(const Tile* tile) const
184 {
185 IntRect contentRect = tileLayerRect(tile);
186 contentRect.move(m_layerPosition.x(), m_layerPosition.y());
187 return contentRect;
188 }
189
tileLayerRect(const Tile * tile) const190 IntRect LayerTilerChromium::tileLayerRect(const Tile* tile) const
191 {
192 const int index = m_tilingData.tileIndex(tile->i(), tile->j());
193 IntRect layerRect = m_tilingData.tileBoundsWithBorder(index);
194 layerRect.setSize(m_tileSize);
195 return layerRect;
196 }
197
invalidateRect(const IntRect & contentRect)198 void LayerTilerChromium::invalidateRect(const IntRect& contentRect)
199 {
200 if (contentRect.isEmpty() || m_skipsDraw)
201 return;
202
203 growLayerToContain(contentRect);
204
205 // Dirty rects are always in layer space, as the layer could be repositioned
206 // after invalidation.
207 const IntRect layerRect = contentRectToLayerRect(contentRect);
208
209 int left, top, right, bottom;
210 contentRectToTileIndices(contentRect, left, top, right, bottom);
211 for (int j = top; j <= bottom; ++j) {
212 for (int i = left; i <= right; ++i) {
213 Tile* tile = tileAt(i, j);
214 if (!tile)
215 continue;
216 IntRect bound = tileLayerRect(tile);
217 bound.intersect(layerRect);
218 tile->m_dirtyLayerRect.unite(bound);
219 }
220 }
221 }
222
invalidateEntireLayer()223 void LayerTilerChromium::invalidateEntireLayer()
224 {
225 for (TileMap::iterator iter = m_tiles.begin(); iter != m_tiles.end(); ++iter) {
226 ASSERT(iter->second->refCount() == 1);
227 m_unusedTiles.append(iter->second.release());
228 }
229 m_tiles.clear();
230
231 m_tilingData.setTotalSize(0, 0);
232 }
233
update(TilePaintInterface & painter,const IntRect & contentRect)234 void LayerTilerChromium::update(TilePaintInterface& painter, const IntRect& contentRect)
235 {
236 if (m_skipsDraw)
237 return;
238
239 // Invalidate old tiles that were previously used but aren't in use this
240 // frame so that they can get reused for new tiles.
241 invalidateTiles(contentRect);
242 growLayerToContain(contentRect);
243
244 // Create tiles as needed, expanding a dirty rect to contain all
245 // the dirty regions currently being drawn.
246 IntRect dirtyLayerRect;
247 int left, top, right, bottom;
248 contentRectToTileIndices(contentRect, left, top, right, bottom);
249 for (int j = top; j <= bottom; ++j) {
250 for (int i = left; i <= right; ++i) {
251 Tile* tile = tileAt(i, j);
252 if (!tile)
253 tile = createTile(i, j);
254 if (!tile->texture()->isValid(m_tileSize, GraphicsContext3D::RGBA))
255 tile->m_dirtyLayerRect = tileLayerRect(tile);
256 else
257 tile->texture()->reserve(m_tileSize, GraphicsContext3D::RGBA);
258 dirtyLayerRect.unite(tile->m_dirtyLayerRect);
259 }
260 }
261
262 // Due to borders, when the paint rect is extended to tile boundaries, it
263 // may end up overlapping more tiles than the original content rect. Record
264 // that original rect so we don't upload more tiles than necessary.
265 m_updateRect = contentRect;
266
267 m_paintRect = layerRectToContentRect(dirtyLayerRect);
268 if (dirtyLayerRect.isEmpty())
269 return;
270
271 m_canvas.resize(m_paintRect.size());
272
273 // Assumption: if a tiler is using border texels, then it is because the
274 // layer is likely to be filtered or transformed. Because of it might be
275 // transformed, draw the text in grayscale instead of subpixel antialiasing.
276 PlatformCanvas::Painter::TextOption textOption = m_tilingData.borderTexels() ? PlatformCanvas::Painter::GrayscaleText : PlatformCanvas::Painter::SubpixelText;
277 PlatformCanvas::Painter canvasPainter(&m_canvas, textOption);
278 canvasPainter.context()->translate(-m_paintRect.x(), -m_paintRect.y());
279 {
280 TRACE_EVENT("LayerTilerChromium::update::paint", this, 0);
281 painter.paint(*canvasPainter.context(), m_paintRect);
282 }
283 }
284
uploadCanvas()285 void LayerTilerChromium::uploadCanvas()
286 {
287 PlatformCanvas::AutoLocker locker(&m_canvas);
288 {
289 TRACE_EVENT("LayerTilerChromium::updateFromPixels", this, 0);
290 updateFromPixels(m_updateRect, m_paintRect, locker.pixels());
291 }
292 }
293
updateFromPixels(const IntRect & contentRect,const IntRect & paintRect,const uint8_t * paintPixels)294 void LayerTilerChromium::updateFromPixels(const IntRect& contentRect, const IntRect& paintRect, const uint8_t* paintPixels)
295 {
296 // Painting could cause compositing to get turned off, which may cause the tiler to become invalidated mid-update.
297 if (!m_tilingData.totalSizeX() || !m_tilingData.totalSizeY())
298 return;
299
300 GraphicsContext3D* context = layerRendererContext();
301
302 int left, top, right, bottom;
303 contentRectToTileIndices(contentRect, left, top, right, bottom);
304 for (int j = top; j <= bottom; ++j) {
305 for (int i = left; i <= right; ++i) {
306 Tile* tile = tileAt(i, j);
307 if (!tile)
308 tile = createTile(i, j);
309 else if (!tile->dirty())
310 continue;
311
312 // Calculate page-space rectangle to copy from.
313 IntRect sourceRect = tileContentRect(tile);
314 const IntPoint anchor = sourceRect.location();
315 sourceRect.intersect(layerRectToContentRect(tile->m_dirtyLayerRect));
316 // Paint rect not guaranteed to line up on tile boundaries, so
317 // make sure that sourceRect doesn't extend outside of it.
318 sourceRect.intersect(paintRect);
319 if (sourceRect.isEmpty())
320 continue;
321
322 if (!tile->texture()->isReserved()) {
323 if (!tile->texture()->reserve(m_tileSize, GraphicsContext3D::RGBA)) {
324 m_skipsDraw = true;
325 reset();
326 return;
327 }
328 }
329
330 // Calculate tile-space rectangle to upload into.
331 IntRect destRect(IntPoint(sourceRect.x() - anchor.x(), sourceRect.y() - anchor.y()), sourceRect.size());
332 if (destRect.x() < 0)
333 CRASH();
334 if (destRect.y() < 0)
335 CRASH();
336
337 // Offset from paint rectangle to this tile's dirty rectangle.
338 IntPoint paintOffset(sourceRect.x() - paintRect.x(), sourceRect.y() - paintRect.y());
339 if (paintOffset.x() < 0)
340 CRASH();
341 if (paintOffset.y() < 0)
342 CRASH();
343 if (paintOffset.x() + destRect.width() > paintRect.width())
344 CRASH();
345 if (paintOffset.y() + destRect.height() > paintRect.height())
346 CRASH();
347
348 const uint8_t* pixelSource;
349 if (paintRect.width() == sourceRect.width() && !paintOffset.x())
350 pixelSource = &paintPixels[4 * paintOffset.y() * paintRect.width()];
351 else {
352 // Strides not equal, so do a row-by-row memcpy from the
353 // paint results into a temp buffer for uploading.
354 for (int row = 0; row < destRect.height(); ++row)
355 memcpy(&m_tilePixels[destRect.width() * 4 * row],
356 &paintPixels[4 * (paintOffset.x() + (paintOffset.y() + row) * paintRect.width())],
357 destRect.width() * 4);
358
359 pixelSource = &m_tilePixels[0];
360 }
361
362 tile->texture()->bindTexture();
363
364 const GC3Dint filter = m_tilingData.borderTexels() ? GraphicsContext3D::LINEAR : GraphicsContext3D::NEAREST;
365 GLC(context, context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, filter));
366 GLC(context, context->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, filter));
367
368 GLC(context, context->texSubImage2D(GraphicsContext3D::TEXTURE_2D, 0, destRect.x(), destRect.y(), destRect.width(), destRect.height(), GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, pixelSource));
369
370 tile->clearDirty();
371 }
372 }
373 }
374
setLayerPosition(const IntPoint & layerPosition)375 void LayerTilerChromium::setLayerPosition(const IntPoint& layerPosition)
376 {
377 m_layerPosition = layerPosition;
378 }
379
draw(const IntRect & contentRect,const TransformationMatrix & globalTransform,float opacity)380 void LayerTilerChromium::draw(const IntRect& contentRect, const TransformationMatrix& globalTransform, float opacity)
381 {
382 if (m_skipsDraw || !m_tiles.size())
383 return;
384
385 GraphicsContext3D* context = layerRendererContext();
386 const LayerTilerChromium::Program* program = layerRenderer()->tilerProgram();
387 layerRenderer()->useShader(program->program());
388 GLC(context, context->uniform1i(program->fragmentShader().samplerLocation(), 0));
389
390 int left, top, right, bottom;
391 contentRectToTileIndices(contentRect, left, top, right, bottom);
392 for (int j = top; j <= bottom; ++j) {
393 for (int i = left; i <= right; ++i) {
394 Tile* tile = tileAt(i, j);
395 if (!tile)
396 continue;
397
398 tile->texture()->bindTexture();
399
400 TransformationMatrix tileMatrix(globalTransform);
401
402 // Don't use tileContentRect here, as that contains the full
403 // rect with border texels which shouldn't be drawn.
404 IntRect tileRect = m_tilingData.tileBounds(m_tilingData.tileIndex(tile->i(), tile->j()));
405 tileRect.move(m_layerPosition.x(), m_layerPosition.y());
406 tileMatrix.translate3d(tileRect.x() + tileRect.width() / 2.0, tileRect.y() + tileRect.height() / 2.0, 0);
407
408 IntPoint texOffset = m_tilingData.textureOffset(tile->i(), tile->j());
409 float tileWidth = static_cast<float>(m_tileSize.width());
410 float tileHeight = static_cast<float>(m_tileSize.height());
411 float texTranslateX = texOffset.x() / tileWidth;
412 float texTranslateY = texOffset.y() / tileHeight;
413 float texScaleX = tileRect.width() / tileWidth;
414 float texScaleY = tileRect.height() / tileHeight;
415
416 drawTexturedQuad(context, layerRenderer()->projectionMatrix(), tileMatrix, tileRect.width(), tileRect.height(), opacity, texTranslateX, texTranslateY, texScaleX, texScaleY, program);
417 }
418 }
419 }
420
growLayerToContain(const IntRect & contentRect)421 void LayerTilerChromium::growLayerToContain(const IntRect& contentRect)
422 {
423 // Grow the tile array to contain this content rect.
424 IntRect layerRect = contentRectToLayerRect(contentRect);
425 IntSize rectSize = IntSize(layerRect.maxX(), layerRect.maxY());
426
427 IntSize oldLayerSize(m_tilingData.totalSizeX(), m_tilingData.totalSizeY());
428 IntSize newSize = rectSize.expandedTo(oldLayerSize);
429 m_tilingData.setTotalSize(newSize.width(), newSize.height());
430 }
431
drawTexturedQuad(GraphicsContext3D * context,const TransformationMatrix & projectionMatrix,const TransformationMatrix & drawMatrix,float width,float height,float opacity,float texTranslateX,float texTranslateY,float texScaleX,float texScaleY,const LayerTilerChromium::Program * program)432 void LayerTilerChromium::drawTexturedQuad(GraphicsContext3D* context, const TransformationMatrix& projectionMatrix, const TransformationMatrix& drawMatrix,
433 float width, float height, float opacity,
434 float texTranslateX, float texTranslateY,
435 float texScaleX, float texScaleY,
436 const LayerTilerChromium::Program* program)
437 {
438 static float glMatrix[16];
439
440 TransformationMatrix renderMatrix = drawMatrix;
441
442 // Apply a scaling factor to size the quad from 1x1 to its intended size.
443 renderMatrix.scale3d(width, height, 1);
444
445 // Apply the projection matrix before sending the transform over to the shader.
446 LayerChromium::toGLMatrix(&glMatrix[0], projectionMatrix * renderMatrix);
447
448 GLC(context, context->uniformMatrix4fv(program->vertexShader().matrixLocation(), false, &glMatrix[0], 1));
449
450 GLC(context, context->uniform1f(program->fragmentShader().alphaLocation(), opacity));
451
452 GLC(context, context->uniform4f(program->vertexShader().texTransformLocation(),
453 texTranslateX, texTranslateY, texScaleX, texScaleY));
454
455 GLC(context, context->drawElements(GraphicsContext3D::TRIANGLES, 6, GraphicsContext3D::UNSIGNED_SHORT, 0));
456 }
457
458 } // namespace WebCore
459
460 #endif // USE(ACCELERATED_COMPOSITING)
461