blob: 5206b7a354526a193c4189819235427753d2bcef [file] [log] [blame]
/*
* Copyright 2010, The Android Open Source Project
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER OR
* 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"
#include "GLWebViewState.h"
#if USE(ACCELERATED_COMPOSITING)
#include "BaseLayerAndroid.h"
#include "ClassTracker.h"
#include "GLUtils.h"
#include "ImagesManager.h"
#include "LayerAndroid.h"
#include "ScrollableLayerAndroid.h"
#include "SkPath.h"
#include "TilesManager.h"
#include "TilesTracker.h"
#include "TreeManager.h"
#include <wtf/CurrentTime.h>
#include <pthread.h>
#include <cutils/log.h>
#include <wtf/text/CString.h>
#undef XLOGC
#define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "GLWebViewState", __VA_ARGS__)
#ifdef DEBUG
#undef XLOG
#define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "GLWebViewState", __VA_ARGS__)
#else
#undef XLOG
#define XLOG(...)
#endif // DEBUG
#define FIRST_TILED_PAGE_ID 1
#define SECOND_TILED_PAGE_ID 2
#define FRAMERATE_CAP 0.01666 // We cap at 60 fps
// log warnings if scale goes outside this range
#define MIN_SCALE_WARNING 0.1
#define MAX_SCALE_WARNING 10
namespace WebCore {
using namespace android;
GLWebViewState::GLWebViewState()
: m_zoomManager(this)
, m_currentPictureCounter(0)
, m_usePageA(true)
, m_frameworkInval(0, 0, 0, 0)
, m_frameworkLayersInval(0, 0, 0, 0)
, m_isScrolling(false)
, m_goingDown(true)
, m_goingLeft(false)
, m_expandedTileBoundsX(0)
, m_expandedTileBoundsY(0)
, m_scale(1)
, m_layersRenderingMode(kAllTextures)
, m_highEndGfx(false)
{
m_viewport.setEmpty();
m_futureViewportTileBounds.setEmpty();
m_viewportTileBounds.setEmpty();
m_preZoomBounds.setEmpty();
m_tiledPageA = new TiledPage(FIRST_TILED_PAGE_ID, this);
m_tiledPageB = new TiledPage(SECOND_TILED_PAGE_ID, this);
#ifdef DEBUG_COUNT
ClassTracker::instance()->increment("GLWebViewState");
#endif
#ifdef MEASURES_PERF
m_timeCounter = 0;
m_totalTimeCounter = 0;
m_measurePerfs = false;
#endif
}
GLWebViewState::~GLWebViewState()
{
// Take care of the transfer queue such that Tex Gen thread will not stuck
TilesManager::instance()->unregisterGLWebViewState(this);
// We have to destroy the two tiled pages first as their destructor
// may depend on the existence of this GLWebViewState and some of its
// instance variables in order to complete.
// Explicitely, currently we need to have the m_paintingBaseLayer around
// in order to complete any pending paint operations (the tiled pages
// will remove any pending operations, and wait if one is underway).
delete m_tiledPageA;
delete m_tiledPageB;
#ifdef DEBUG_COUNT
ClassTracker::instance()->decrement("GLWebViewState");
#endif
}
void GLWebViewState::setBaseLayer(BaseLayerAndroid* layer, const SkRegion& inval,
bool showVisualIndicator, bool isPictureAfterFirstLayout)
{
if (!layer || isPictureAfterFirstLayout) {
// TODO: move this into TreeManager
m_tiledPageA->discardTextures();
m_tiledPageB->discardTextures();
}
if (layer) {
XLOG("new base layer %p, (inval region empty %d) with child %p", layer, inval.isEmpty(), layer->getChild(0));
layer->setState(this);
layer->markAsDirty(inval); // TODO: set in webview.cpp
}
m_treeManager.updateWithTree(layer, isPictureAfterFirstLayout);
m_glExtras.setDrawExtra(0);
#ifdef MEASURES_PERF
if (m_measurePerfs && !showVisualIndicator)
dumpMeasures();
m_measurePerfs = showVisualIndicator;
#endif
TilesManager::instance()->setShowVisualIndicator(showVisualIndicator);
}
void GLWebViewState::scrolledLayer(ScrollableLayerAndroid*)
{
// TODO: only inval the area of the scrolled layer instead of
// doing a fullInval()
if (m_layersRenderingMode == kSingleSurfaceRendering)
fullInval();
}
void GLWebViewState::invalRegion(const SkRegion& region)
{
if (m_layersRenderingMode == kSingleSurfaceRendering) {
// TODO: do the union of both layers tree to compute
//the minimum inval instead of doing a fullInval()
fullInval();
return;
}
SkRegion::Iterator iterator(region);
while (!iterator.done()) {
SkIRect r = iterator.rect();
IntRect ir(r.fLeft, r.fTop, r.width(), r.height());
inval(ir);
iterator.next();
}
}
void GLWebViewState::inval(const IntRect& rect)
{
m_currentPictureCounter++;
if (!rect.isEmpty()) {
// find which tiles fall within the invalRect and mark them as dirty
m_tiledPageA->invalidateRect(rect, m_currentPictureCounter);
m_tiledPageB->invalidateRect(rect, m_currentPictureCounter);
if (m_frameworkInval.isEmpty())
m_frameworkInval = rect;
else
m_frameworkInval.unite(rect);
XLOG("intermediate invalRect(%d, %d, %d, %d) after unite with rect %d %d %d %d", m_frameworkInval.x(),
m_frameworkInval.y(), m_frameworkInval.width(), m_frameworkInval.height(),
rect.x(), rect.y(), rect.width(), rect.height());
}
TilesManager::instance()->getProfiler()->nextInval(rect, zoomManager()->currentScale());
}
unsigned int GLWebViewState::paintBaseLayerContent(SkCanvas* canvas)
{
m_treeManager.drawCanvas(canvas, m_layersRenderingMode == kSingleSurfaceRendering);
return m_currentPictureCounter;
}
TiledPage* GLWebViewState::sibling(TiledPage* page)
{
return (page == m_tiledPageA) ? m_tiledPageB : m_tiledPageA;
}
TiledPage* GLWebViewState::frontPage()
{
android::Mutex::Autolock lock(m_tiledPageLock);
return m_usePageA ? m_tiledPageA : m_tiledPageB;
}
TiledPage* GLWebViewState::backPage()
{
android::Mutex::Autolock lock(m_tiledPageLock);
return m_usePageA ? m_tiledPageB : m_tiledPageA;
}
void GLWebViewState::swapPages()
{
android::Mutex::Autolock lock(m_tiledPageLock);
m_usePageA ^= true;
TiledPage* oldPage = m_usePageA ? m_tiledPageB : m_tiledPageA;
zoomManager()->swapPages();
oldPage->discardTextures();
}
int GLWebViewState::baseContentWidth()
{
return m_treeManager.baseContentWidth();
}
int GLWebViewState::baseContentHeight()
{
return m_treeManager.baseContentHeight();
}
void GLWebViewState::setViewport(SkRect& viewport, float scale)
{
if ((m_viewport == viewport) &&
(zoomManager()->futureScale() == scale))
return;
m_goingDown = m_viewport.fTop - viewport.fTop <= 0;
m_goingLeft = m_viewport.fLeft - viewport.fLeft >= 0;
m_viewport = viewport;
XLOG("New VIEWPORT %.2f - %.2f %.2f - %.2f (w: %2.f h: %.2f scale: %.2f currentScale: %.2f futureScale: %.2f)",
m_viewport.fLeft, m_viewport.fTop, m_viewport.fRight, m_viewport.fBottom,
m_viewport.width(), m_viewport.height(), scale,
zoomManager()->currentScale(), zoomManager()->futureScale());
const float invTileContentWidth = scale / TilesManager::tileWidth();
const float invTileContentHeight = scale / TilesManager::tileHeight();
m_viewportTileBounds.set(
static_cast<int>(floorf(viewport.fLeft * invTileContentWidth)),
static_cast<int>(floorf(viewport.fTop * invTileContentHeight)),
static_cast<int>(ceilf(viewport.fRight * invTileContentWidth)),
static_cast<int>(ceilf(viewport.fBottom * invTileContentHeight)));
// allocate max possible number of tiles visible with this viewport
int viewMaxTileX = static_cast<int>(ceilf((viewport.width()-1) * invTileContentWidth)) + 1;
int viewMaxTileY = static_cast<int>(ceilf((viewport.height()-1) * invTileContentHeight)) + 1;
int maxTextureCount = (viewMaxTileX + m_expandedTileBoundsX * 2) *
(viewMaxTileY + m_expandedTileBoundsY * 2) * (m_highEndGfx ? 4 : 2);
TilesManager::instance()->setMaxTextureCount(maxTextureCount);
m_tiledPageA->updateBaseTileSize();
m_tiledPageB->updateBaseTileSize();
}
#ifdef MEASURES_PERF
void GLWebViewState::dumpMeasures()
{
for (int i = 0; i < m_timeCounter; i++) {
XLOGC("%d delay: %d ms", m_totalTimeCounter + i,
static_cast<int>(m_delayTimes[i]*1000));
m_delayTimes[i] = 0;
}
m_totalTimeCounter += m_timeCounter;
m_timeCounter = 0;
}
#endif // MEASURES_PERF
void GLWebViewState::resetFrameworkInval()
{
m_frameworkInval.setX(0);
m_frameworkInval.setY(0);
m_frameworkInval.setWidth(0);
m_frameworkInval.setHeight(0);
}
void GLWebViewState::addDirtyArea(const IntRect& rect)
{
if (rect.isEmpty())
return;
IntRect inflatedRect = rect;
inflatedRect.inflate(8);
if (m_frameworkLayersInval.isEmpty())
m_frameworkLayersInval = inflatedRect;
else
m_frameworkLayersInval.unite(inflatedRect);
}
void GLWebViewState::resetLayersDirtyArea()
{
m_frameworkLayersInval.setX(0);
m_frameworkLayersInval.setY(0);
m_frameworkLayersInval.setWidth(0);
m_frameworkLayersInval.setHeight(0);
}
void GLWebViewState::drawBackground(Color& backgroundColor)
{
if (TilesManager::instance()->invertedScreen()) {
float color = 1.0 - ((((float) backgroundColor.red() / 255.0) +
((float) backgroundColor.green() / 255.0) +
((float) backgroundColor.blue() / 255.0)) / 3.0);
glClearColor(color, color, color, 1);
} else {
glClearColor((float)backgroundColor.red() / 255.0,
(float)backgroundColor.green() / 255.0,
(float)backgroundColor.blue() / 255.0, 1);
}
glClear(GL_COLOR_BUFFER_BIT);
}
double GLWebViewState::setupDrawing(IntRect& viewRect, SkRect& visibleRect,
IntRect& webViewRect, int titleBarHeight,
IntRect& screenClip, float scale)
{
int left = viewRect.x();
int top = viewRect.y();
int width = viewRect.width();
int height = viewRect.height();
glViewport(left, top, width, height);
ShaderProgram* shader = TilesManager::instance()->shader();
if (shader->program() == -1) {
XLOG("Reinit shader");
shader->init();
}
shader->setViewRect(viewRect);
shader->setViewport(visibleRect);
shader->setWebViewRect(webViewRect);
shader->setTitleBarHeight(titleBarHeight);
shader->setScreenClip(screenClip);
shader->resetBlending();
double currentTime = WTF::currentTime();
setViewport(visibleRect, scale);
m_zoomManager.processNewScale(currentTime, scale);
return currentTime;
}
bool GLWebViewState::setLayersRenderingMode(TexturesResult& nbTexturesNeeded)
{
bool invalBase = false;
if (!nbTexturesNeeded.full)
TilesManager::instance()->setMaxLayerTextureCount(0);
else
TilesManager::instance()->setMaxLayerTextureCount((2*nbTexturesNeeded.full)+1);
int maxTextures = TilesManager::instance()->maxLayerTextureCount();
LayersRenderingMode layersRenderingMode = m_layersRenderingMode;
m_layersRenderingMode = kSingleSurfaceRendering;
if (nbTexturesNeeded.fixed < maxTextures)
m_layersRenderingMode = kFixedLayers;
if (nbTexturesNeeded.scrollable < maxTextures)
m_layersRenderingMode = kScrollableAndFixedLayers;
if (nbTexturesNeeded.clipped < maxTextures)
m_layersRenderingMode = kClippedTextures;
if (nbTexturesNeeded.full < maxTextures)
m_layersRenderingMode = kAllTextures;
if (m_layersRenderingMode < layersRenderingMode
&& m_layersRenderingMode != kAllTextures)
invalBase = true;
if (m_layersRenderingMode > layersRenderingMode
&& m_layersRenderingMode != kClippedTextures)
invalBase = true;
#ifdef DEBUG
if (m_layersRenderingMode != layersRenderingMode) {
char* mode[] = { "kAllTextures", "kClippedTextures",
"kScrollableAndFixedLayers", "kFixedLayers", "kSingleSurfaceRendering" };
XLOGC("Change from mode %s to %s -- We need textures: fixed: %d,"
" scrollable: %d, clipped: %d, full: %d, max textures: %d",
static_cast<char*>(mode[layersRenderingMode]),
static_cast<char*>(mode[m_layersRenderingMode]),
nbTexturesNeeded.fixed,
nbTexturesNeeded.scrollable,
nbTexturesNeeded.clipped,
nbTexturesNeeded.full, maxTextures);
}
#endif
// For now, anything below kClippedTextures is equivalent
// to kSingleSurfaceRendering
// TODO: implement the other rendering modes
if (m_layersRenderingMode > kClippedTextures)
m_layersRenderingMode = kSingleSurfaceRendering;
// update the base surface if needed
if (m_layersRenderingMode != layersRenderingMode
&& invalBase) {
m_tiledPageA->discardTextures();
m_tiledPageB->discardTextures();
fullInval();
return true;
}
return false;
}
void GLWebViewState::fullInval()
{
// TODO -- use base layer's size.
IntRect ir(0, 0, 1E6, 1E6);
inval(ir);
}
bool GLWebViewState::drawGL(IntRect& rect, SkRect& viewport, IntRect* invalRect,
IntRect& webViewRect, int titleBarHeight,
IntRect& clip, float scale, bool* buffersSwappedPtr)
{
m_scale = scale;
TilesManager::instance()->getProfiler()->nextFrame(viewport.fLeft,
viewport.fTop,
viewport.fRight,
viewport.fBottom,
scale);
TilesManager::instance()->incDrawGLCount();
#ifdef DEBUG
TilesManager::instance()->getTilesTracker()->clear();
#endif
float viewWidth = (viewport.fRight - viewport.fLeft) * TILE_PREFETCH_RATIO;
float viewHeight = (viewport.fBottom - viewport.fTop) * TILE_PREFETCH_RATIO;
bool useMinimalMemory = TilesManager::instance()->useMinimalMemory();
bool useHorzPrefetch = useMinimalMemory ? 0 : viewWidth < baseContentWidth();
bool useVertPrefetch = useMinimalMemory ? 0 : viewHeight < baseContentHeight();
m_expandedTileBoundsX = (useHorzPrefetch) ? TILE_PREFETCH_DISTANCE : 0;
m_expandedTileBoundsY = (useVertPrefetch) ? TILE_PREFETCH_DISTANCE : 0;
XLOG("drawGL, rect(%d, %d, %d, %d), viewport(%.2f, %.2f, %.2f, %.2f)",
rect.x(), rect.y(), rect.width(), rect.height(),
viewport.fLeft, viewport.fTop, viewport.fRight, viewport.fBottom);
resetLayersDirtyArea();
// when adding or removing layers, use the the paintingBaseLayer's tree so
// that content that moves to the base layer from a layer is synchronized
if (scale < MIN_SCALE_WARNING || scale > MAX_SCALE_WARNING)
XLOGC("WARNING, scale seems corrupted before update: %e", scale);
// Here before we draw, update the BaseTile which has updated content.
// Inside this function, just do GPU blits from the transfer queue into
// the BaseTiles' texture.
TilesManager::instance()->transferQueue()->updateDirtyBaseTiles();
// Upload any pending ImageTexture
// Return true if we still have some images to upload.
// TODO: upload as many textures as possible within a certain time limit
bool ret = ImagesManager::instance()->uploadTextures();
if (scale < MIN_SCALE_WARNING || scale > MAX_SCALE_WARNING)
XLOGC("WARNING, scale seems corrupted after update: %e", scale);
// gather the textures we can use
TilesManager::instance()->gatherLayerTextures();
double currentTime = setupDrawing(rect, viewport, webViewRect, titleBarHeight, clip, scale);
TexturesResult nbTexturesNeeded;
bool fastSwap = isScrolling() || m_layersRenderingMode == kSingleSurfaceRendering;
ret |= m_treeManager.drawGL(currentTime, rect, viewport,
scale, fastSwap,
buffersSwappedPtr, &nbTexturesNeeded);
if (!ret)
resetFrameworkInval();
ret |= setLayersRenderingMode(nbTexturesNeeded);
FloatRect extrasclip(0, 0, rect.width(), rect.height());
TilesManager::instance()->shader()->clip(extrasclip);
m_glExtras.drawGL(webViewRect, viewport, titleBarHeight);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Clean up GL textures for video layer.
TilesManager::instance()->videoLayerManager()->deleteUnusedTextures();
ret |= TilesManager::instance()->invertedScreenSwitch();
if (ret) {
// ret==true && empty inval region means we've inval'd everything,
// but don't have new content. Keep redrawing full view (0,0,0,0)
// until tile generation catches up and we swap pages.
bool fullScreenInval = m_frameworkInval.isEmpty();
if (TilesManager::instance()->invertedScreenSwitch()) {
fullScreenInval = true;
TilesManager::instance()->setInvertedScreenSwitch(false);
}
if (!fullScreenInval) {
FloatRect frameworkInval = TilesManager::instance()->shader()->rectInInvScreenCoord(
m_frameworkInval);
// Inflate the invalidate rect to avoid precision lost.
frameworkInval.inflate(1);
IntRect inval(frameworkInval.x(), frameworkInval.y(),
frameworkInval.width(), frameworkInval.height());
inval.unite(m_frameworkLayersInval);
invalRect->setX(inval.x());
invalRect->setY(inval.y());
invalRect->setWidth(inval.width());
invalRect->setHeight(inval.height());
XLOG("invalRect(%d, %d, %d, %d)", inval.x(),
inval.y(), inval.width(), inval.height());
if (!invalRect->intersects(rect)) {
// invalidate is occurring offscreen, do full inval to guarantee redraw
fullScreenInval = true;
}
}
if (fullScreenInval) {
invalRect->setX(0);
invalRect->setY(0);
invalRect->setWidth(0);
invalRect->setHeight(0);
}
} else {
resetFrameworkInval();
}
#ifdef MEASURES_PERF
if (m_measurePerfs) {
m_delayTimes[m_timeCounter++] = delta;
if (m_timeCounter >= MAX_MEASURES_PERF)
dumpMeasures();
}
#endif
#ifdef DEBUG
TilesManager::instance()->getTilesTracker()->showTrackTextures();
ImagesManager::instance()->showImages();
#endif
return ret;
}
} // namespace WebCore
#endif // USE(ACCELERATED_COMPOSITING)