| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| |
| #include "config.h" |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| |
| #include "LayerTilerChromium.h" |
| |
| #include "GraphicsContext.h" |
| #include "GraphicsContext3D.h" |
| #include "LayerRendererChromium.h" |
| |
| #if PLATFORM(SKIA) |
| #include "NativeImageSkia.h" |
| #include "PlatformContextSkia.h" |
| #elif PLATFORM(CG) |
| #include <CoreGraphics/CGBitmapContext.h> |
| #endif |
| |
| #include <wtf/PassOwnArrayPtr.h> |
| |
| namespace WebCore { |
| |
| PassOwnPtr<LayerTilerChromium> LayerTilerChromium::create(LayerRendererChromium* layerRenderer, const IntSize& tileSize) |
| { |
| if (!layerRenderer || tileSize.isEmpty()) |
| return 0; |
| |
| return adoptPtr(new LayerTilerChromium(layerRenderer, tileSize)); |
| } |
| |
| LayerTilerChromium::LayerTilerChromium(LayerRendererChromium* layerRenderer, const IntSize& tileSize) |
| : m_layerRenderer(layerRenderer) |
| { |
| setTileSize(tileSize); |
| } |
| |
| LayerTilerChromium::~LayerTilerChromium() |
| { |
| reset(); |
| } |
| |
| GraphicsContext3D* LayerTilerChromium::layerRendererContext() const |
| { |
| ASSERT(layerRenderer()); |
| return layerRenderer()->context(); |
| } |
| |
| void LayerTilerChromium::setTileSize(const IntSize& size) |
| { |
| if (m_tileSize == size) |
| return; |
| |
| reset(); |
| |
| m_tileSize = size; |
| m_tilePixels = adoptArrayPtr(new uint8_t[m_tileSize.width() * m_tileSize.height() * 4]); |
| } |
| |
| void LayerTilerChromium::reset() |
| { |
| for (size_t i = 0; i < m_tiles.size(); ++i) { |
| if (!m_tiles[i]) |
| continue; |
| layerRenderer()->deleteLayerTexture(m_tiles[i]->releaseTextureId()); |
| } |
| m_tiles.clear(); |
| for (size_t i = 0; i < m_unusedTiles.size(); ++i) { |
| if (!m_unusedTiles[i]) |
| continue; |
| layerRenderer()->deleteLayerTexture(m_unusedTiles[i]->releaseTextureId()); |
| } |
| m_unusedTiles.clear(); |
| |
| m_layerSize = IntSize(); |
| m_layerTileSize = IntSize(); |
| m_lastUpdateLayerRect = IntRect(); |
| } |
| |
| LayerTilerChromium::Tile* LayerTilerChromium::createTile(int i, int j) |
| { |
| const int index = tileIndex(i, j); |
| ASSERT(!m_tiles[index]); |
| |
| if (m_unusedTiles.size() > 0) { |
| m_tiles[index] = m_unusedTiles.last().release(); |
| m_unusedTiles.removeLast(); |
| } else { |
| const unsigned int textureId = layerRenderer()->createLayerTexture(); |
| OwnPtr<Tile> tile = adoptPtr(new Tile(textureId)); |
| |
| GraphicsContext3D* context = layerRendererContext(); |
| GLC(context, context->texImage2DResourceSafe(GraphicsContext3D::TEXTURE_2D, 0, GraphicsContext3D::RGBA, m_tileSize.width(), m_tileSize.height(), 0, GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE)); |
| |
| m_tiles[index] = tile.release(); |
| } |
| |
| m_tiles[index]->m_dirtyLayerRect = tileLayerRect(i, j); |
| return m_tiles[index].get(); |
| } |
| |
| void LayerTilerChromium::invalidateTiles(const IntRect& oldLayerRect, const IntRect& newLayerRect) |
| { |
| if (!m_tiles.size()) |
| return; |
| |
| IntRect oldContentRect = layerRectToContentRect(oldLayerRect); |
| int oldLeft, oldTop, oldRight, oldBottom; |
| contentRectToTileIndices(oldContentRect, oldLeft, oldTop, oldRight, oldBottom); |
| |
| IntRect newContentRect = layerRectToContentRect(newLayerRect); |
| int newLeft, newTop, newRight, newBottom; |
| contentRectToTileIndices(newContentRect, newLeft, newTop, newRight, newBottom); |
| |
| // Iterating through just the old tile indices is an optimization to avoid |
| // iterating through the entire m_tiles array. |
| for (int j = oldTop; j <= oldBottom; ++j) { |
| for (int i = oldLeft; i <= oldRight; ++i) { |
| if (i >= newLeft && i <= newRight && j >= newTop && j <= newBottom) |
| continue; |
| |
| const int index = tileIndex(i, j); |
| if (m_tiles[index]) |
| m_unusedTiles.append(m_tiles[index].release()); |
| } |
| } |
| } |
| |
| void LayerTilerChromium::contentRectToTileIndices(const IntRect& contentRect, int &left, int &top, int &right, int &bottom) const |
| { |
| const IntRect layerRect = contentRectToLayerRect(contentRect); |
| |
| left = layerRect.x() / m_tileSize.width(); |
| top = layerRect.y() / m_tileSize.height(); |
| right = (layerRect.right() - 1) / m_tileSize.width(); |
| bottom = (layerRect.bottom() - 1) / m_tileSize.height(); |
| } |
| |
| IntRect LayerTilerChromium::contentRectToLayerRect(const IntRect& contentRect) const |
| { |
| IntPoint pos(contentRect.x() - m_layerPosition.x(), contentRect.y() - m_layerPosition.y()); |
| IntRect layerRect(pos, contentRect.size()); |
| |
| // Clip to the position. |
| if (pos.x() < 0 || pos.y() < 0) |
| layerRect = IntRect(IntPoint(0, 0), IntSize(contentRect.width() + pos.x(), contentRect.height() + pos.y())); |
| return layerRect; |
| } |
| |
| IntRect LayerTilerChromium::layerRectToContentRect(const IntRect& layerRect) const |
| { |
| IntRect contentRect = layerRect; |
| contentRect.move(m_layerPosition.x(), m_layerPosition.y()); |
| return contentRect; |
| } |
| |
| int LayerTilerChromium::tileIndex(int i, int j) const |
| { |
| ASSERT(i >= 0 && j >= 0 && i < m_layerTileSize.width() && j < m_layerTileSize.height()); |
| return i + j * m_layerTileSize.width(); |
| } |
| |
| IntRect LayerTilerChromium::tileContentRect(int i, int j) const |
| { |
| IntPoint anchor(m_layerPosition.x() + i * m_tileSize.width(), m_layerPosition.y() + j * m_tileSize.height()); |
| IntRect tile(anchor, m_tileSize); |
| return tile; |
| } |
| |
| IntRect LayerTilerChromium::tileLayerRect(int i, int j) const |
| { |
| IntPoint anchor(i * m_tileSize.width(), j * m_tileSize.height()); |
| IntRect tile(anchor, m_tileSize); |
| return tile; |
| } |
| |
| void LayerTilerChromium::invalidateRect(const IntRect& contentRect) |
| { |
| if (contentRect.isEmpty()) |
| return; |
| |
| growLayerToContain(contentRect); |
| |
| // Dirty rects are always in layer space, as the layer could be repositioned |
| // after invalidation. |
| IntRect layerRect = contentRectToLayerRect(contentRect); |
| |
| int left, top, right, bottom; |
| contentRectToTileIndices(contentRect, left, top, right, bottom); |
| for (int j = top; j <= bottom; ++j) { |
| for (int i = left; i <= right; ++i) { |
| Tile* tile = m_tiles[tileIndex(i, j)].get(); |
| if (!tile) |
| continue; |
| IntRect bound = tileLayerRect(i, j); |
| bound.intersect(layerRect); |
| tile->m_dirtyLayerRect.unite(bound); |
| } |
| } |
| } |
| |
| void LayerTilerChromium::invalidateEntireLayer() |
| { |
| for (size_t i = 0; i < m_tiles.size(); ++i) { |
| if (m_tiles[i]) |
| m_unusedTiles.append(m_tiles[i].release()); |
| } |
| m_tiles.clear(); |
| |
| m_layerSize = IntSize(); |
| m_layerTileSize = IntSize(); |
| m_lastUpdateLayerRect = IntRect(); |
| } |
| |
| void LayerTilerChromium::update(TilePaintInterface& painter, const IntRect& contentRect) |
| { |
| // Invalidate old tiles that were previously used but aren't in use this |
| // frame so that they can get reused for new tiles. |
| IntRect layerRect = contentRectToLayerRect(contentRect); |
| invalidateTiles(m_lastUpdateLayerRect, layerRect); |
| m_lastUpdateLayerRect = layerRect; |
| |
| growLayerToContain(contentRect); |
| |
| // Create tiles as needed, expanding a dirty rect to contain all |
| // the dirty regions currently being drawn. |
| IntRect dirtyLayerRect; |
| int left, top, right, bottom; |
| contentRectToTileIndices(contentRect, left, top, right, bottom); |
| for (int j = top; j <= bottom; ++j) { |
| for (int i = left; i <= right; ++i) { |
| Tile* tile = m_tiles[tileIndex(i, j)].get(); |
| if (!tile) |
| tile = createTile(i, j); |
| dirtyLayerRect.unite(tile->m_dirtyLayerRect); |
| } |
| } |
| |
| if (dirtyLayerRect.isEmpty()) |
| return; |
| |
| const IntRect paintRect = layerRectToContentRect(dirtyLayerRect); |
| GraphicsContext3D* context = layerRendererContext(); |
| #if PLATFORM(SKIA) |
| OwnPtr<skia::PlatformCanvas> canvas(new skia::PlatformCanvas(paintRect.width(), paintRect.height(), false)); |
| OwnPtr<PlatformContextSkia> skiaContext(new PlatformContextSkia(canvas.get())); |
| OwnPtr<GraphicsContext> graphicsContext(new GraphicsContext(reinterpret_cast<PlatformGraphicsContext*>(skiaContext.get()))); |
| |
| // Bring the canvas into the coordinate system of the paint rect. |
| canvas->translate(static_cast<SkScalar>(-paintRect.x()), static_cast<SkScalar>(-paintRect.y())); |
| |
| painter.paint(*graphicsContext, paintRect); |
| |
| // Get the contents of the updated rect. |
| const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(false); |
| ASSERT(bitmap.width() == paintRect.width() && bitmap.height() == paintRect.height()); |
| uint8_t* paintPixels = static_cast<uint8_t*>(bitmap.getPixels()); |
| #elif PLATFORM(CG) |
| Vector<uint8_t> canvasPixels; |
| int rowBytes = 4 * paintRect.width(); |
| canvasPixels.resize(rowBytes * paintRect.height()); |
| memset(canvasPixels.data(), 0, canvasPixels.size()); |
| RetainPtr<CGColorSpaceRef> colorSpace(AdoptCF, CGColorSpaceCreateDeviceRGB()); |
| RetainPtr<CGContextRef> m_cgContext; |
| m_cgContext.adoptCF(CGBitmapContextCreate(canvasPixels.data(), |
| paintRect.width(), paintRect.height(), 8, rowBytes, |
| colorSpace.get(), |
| kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); |
| CGContextTranslateCTM(m_cgContext.get(), 0, paintRect.height()); |
| CGContextScaleCTM(m_cgContext.get(), 1, -1); |
| OwnPtr<GraphicsContext> m_graphicsContext(new GraphicsContext(m_cgContext.get())); |
| |
| // Bring the CoreGraphics context into the coordinate system of the paint rect. |
| CGContextTranslateCTM(m_cgContext.get(), -paintRect.x(), -paintRect.y()); |
| painter.paint(*m_graphicsContext, paintRect); |
| |
| // Get the contents of the updated rect. |
| ASSERT(static_cast<int>(CGBitmapContextGetWidth(m_cgContext.get())) == paintRect.width() && static_cast<int>(CGBitmapContextGetHeight(m_cgContext.get())) == paintRect.height()); |
| uint8_t* paintPixels = static_cast<uint8_t*>(canvasPixels.data()); |
| #else |
| #error "Need to implement for your platform." |
| #endif |
| |
| for (int j = top; j <= bottom; ++j) { |
| for (int i = left; i <= right; ++i) { |
| Tile* tile = m_tiles[tileIndex(i, j)].get(); |
| if (!tile->dirty()) |
| continue; |
| |
| // Calculate page-space rectangle to copy from. |
| IntRect sourceRect = tileContentRect(i, j); |
| const IntPoint anchor = sourceRect.location(); |
| sourceRect.intersect(layerRectToContentRect(tile->m_dirtyLayerRect)); |
| |
| // Calculate tile-space rectangle to upload into. |
| IntRect destRect(IntPoint(sourceRect.x() - anchor.x(), sourceRect.y() - anchor.y()), sourceRect.size()); |
| |
| // Offset from paint rectangle to this tile's dirty rectangle. |
| IntPoint paintOffset(sourceRect.x() - paintRect.x(), sourceRect.y() - paintRect.y()); |
| |
| uint8_t* pixelSource; |
| if (paintRect.width() == sourceRect.width() && !paintOffset.x()) |
| pixelSource = &paintPixels[4 * paintOffset.y() * paintRect.width()]; |
| else { |
| // Strides not equal, so do a row-by-row memcpy from the |
| // paint results into a temp buffer for uploading. |
| for (int row = 0; row < destRect.height(); ++row) |
| memcpy(&m_tilePixels[destRect.width() * 4 * row], |
| &paintPixels[4 * (paintOffset.x() + (paintOffset.y() + row) * paintRect.width())], |
| destRect.width() * 4); |
| |
| pixelSource = &m_tilePixels[0]; |
| } |
| |
| GLC(context, context->bindTexture(GraphicsContext3D::TEXTURE_2D, tile->textureId())); |
| GLC(context, context->texSubImage2D(GraphicsContext3D::TEXTURE_2D, 0, destRect.x(), destRect.y(), destRect.width(), destRect.height(), GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, pixelSource)); |
| |
| tile->clearDirty(); |
| } |
| } |
| } |
| |
| void LayerTilerChromium::setLayerPosition(const IntPoint& layerPosition) |
| { |
| m_layerPosition = layerPosition; |
| } |
| |
| void LayerTilerChromium::draw(const IntRect& contentRect) |
| { |
| // We reuse the shader program used by ContentLayerChromium. |
| GraphicsContext3D* context = layerRendererContext(); |
| const ContentLayerChromium::SharedValues* contentLayerValues = layerRenderer()->contentLayerSharedValues(); |
| layerRenderer()->useShader(contentLayerValues->contentShaderProgram()); |
| GLC(context, context->uniform1i(contentLayerValues->shaderSamplerLocation(), 0)); |
| |
| int left, top, right, bottom; |
| contentRectToTileIndices(contentRect, left, top, right, bottom); |
| for (int j = top; j <= bottom; ++j) { |
| for (int i = left; i <= right; ++i) { |
| Tile* tile = m_tiles[tileIndex(i, j)].get(); |
| ASSERT(tile); |
| |
| GLC(context, context->bindTexture(GraphicsContext3D::TEXTURE_2D, tile->textureId())); |
| |
| TransformationMatrix tileMatrix; |
| IntRect tileRect = tileContentRect(i, j); |
| tileMatrix.translate3d(tileRect.x() - contentRect.x() + tileRect.width() / 2.0, tileRect.y() - contentRect.y() + tileRect.height() / 2.0, 0); |
| |
| LayerChromium::drawTexturedQuad(context, layerRenderer()->projectionMatrix(), tileMatrix, m_tileSize.width(), m_tileSize.height(), 1, contentLayerValues->shaderMatrixLocation(), contentLayerValues->shaderAlphaLocation()); |
| } |
| } |
| } |
| |
| void LayerTilerChromium::resizeLayer(const IntSize& size) |
| { |
| if (m_layerSize == size) |
| return; |
| |
| int width = (size.width() + m_tileSize.width() - 1) / m_tileSize.width(); |
| int height = (size.height() + m_tileSize.height() - 1) / m_tileSize.height(); |
| |
| Vector<OwnPtr<Tile> > newTiles; |
| newTiles.resize(width * height); |
| for (int j = 0; j < m_layerTileSize.height(); ++j) |
| for (int i = 0; i < m_layerTileSize.width(); ++i) |
| newTiles[i + j * width].swap(m_tiles[i + j * m_layerTileSize.width()]); |
| |
| m_tiles.swap(newTiles); |
| m_layerSize = size; |
| m_layerTileSize = IntSize(width, height); |
| } |
| |
| void LayerTilerChromium::growLayerToContain(const IntRect& contentRect) |
| { |
| // Grow the tile array to contain this content rect. |
| IntRect layerRect = contentRectToLayerRect(contentRect); |
| IntSize layerSize = IntSize(layerRect.right(), layerRect.bottom()); |
| |
| IntSize newSize = layerSize.expandedTo(m_layerSize); |
| resizeLayer(newSize); |
| } |
| |
| LayerTilerChromium::Tile::~Tile() |
| { |
| // Each tile doesn't have a reference to the context, so can't clean up |
| // its own texture. If this assert is hit, then the LayerTilerChromium |
| // destructor didn't clean this up. |
| ASSERT(!m_textureId); |
| } |
| |
| unsigned int LayerTilerChromium::Tile::releaseTextureId() |
| { |
| unsigned int id = m_textureId; |
| m_textureId = 0; |
| return id; |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(ACCELERATED_COMPOSITING) |