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
41using namespace std;
42
43static int minTextureSize = 16;
44
45namespace WebCore {
46
47PassOwnPtr<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
55LayerTilerChromium::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
63void LayerTilerChromium::setLayerRenderer(LayerRendererChromium* layerRenderer)
64{
65    if (m_layerRenderer != layerRenderer)
66        reset();
67    m_layerRenderer = layerRenderer;
68}
69
70LayerTilerChromium::~LayerTilerChromium()
71{
72    reset();
73}
74
75GraphicsContext3D* LayerTilerChromium::layerRendererContext() const
76{
77    ASSERT(layerRenderer());
78    return layerRenderer()->context();
79}
80
81void 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
95LayerTexture* LayerTilerChromium::getSingleTexture()
96{
97    Tile* tile = tileAt(0, 0);
98    return tile ? tile->texture() : 0;
99}
100
101void LayerTilerChromium::reset()
102{
103    m_tiles.clear();
104    m_unusedTiles.clear();
105    m_tilingData.setTotalSize(0, 0);
106}
107
108LayerTilerChromium::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
130void 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
148void 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
158IntRect 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
169IntRect 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
176LayerTilerChromium::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
183IntRect 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
190IntRect 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
198void 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
223void 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
234void 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
285void 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
294void 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
375void LayerTilerChromium::setLayerPosition(const IntPoint& layerPosition)
376{
377    m_layerPosition = layerPosition;
378}
379
380void 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
421void 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
432void 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