blob: b20ec7aa54bf42eb075b2c9d8b0e08852f89db5f [file] [log] [blame]
/*
* Copyright 2011, 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 "TransferQueue.h"
#if USE(ACCELERATED_COMPOSITING)
#include "BaseTile.h"
#include "PaintedSurface.h"
#include <android/native_window.h>
#include <gui/SurfaceTexture.h>
#include <gui/SurfaceTextureClient.h>
#include <cutils/log.h>
#include <wtf/text/CString.h>
#define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TransferQueue", __VA_ARGS__)
#ifdef DEBUG
#undef XLOG
#define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TransferQueue", __VA_ARGS__)
#else
#undef XLOG
#define XLOG(...)
#endif // DEBUG
#define ST_BUFFER_NUMBER 6
// Set this to 1 if we would like to take the new GpuUpload approach which
// relied on the glCopyTexSubImage2D instead of a glDraw call
#define GPU_UPLOAD_WITHOUT_DRAW 1
namespace WebCore {
TransferQueue::TransferQueue()
: m_eglSurface(EGL_NO_SURFACE)
, m_transferQueueIndex(0)
, m_fboID(0)
, m_sharedSurfaceTextureId(0)
, m_hasGLContext(true)
, m_interruptedByRemovingOp(false)
, m_currentDisplay(EGL_NO_DISPLAY)
, m_currentUploadType(DEFAULT_UPLOAD_TYPE)
{
memset(&m_GLStateBeforeBlit, 0, sizeof(m_GLStateBeforeBlit));
m_emptyItemCount = ST_BUFFER_NUMBER;
m_transferQueue = new TileTransferData[ST_BUFFER_NUMBER];
}
TransferQueue::~TransferQueue()
{
glDeleteFramebuffers(1, &m_fboID);
m_fboID = 0;
glDeleteTextures(1, &m_sharedSurfaceTextureId);
m_sharedSurfaceTextureId = 0;
delete[] m_transferQueue;
}
void TransferQueue::initSharedSurfaceTextures(int width, int height)
{
if (!m_sharedSurfaceTextureId) {
glGenTextures(1, &m_sharedSurfaceTextureId);
m_sharedSurfaceTexture =
#if GPU_UPLOAD_WITHOUT_DRAW
new android::SurfaceTexture(m_sharedSurfaceTextureId, true, GL_TEXTURE_2D);
#else
new android::SurfaceTexture(m_sharedSurfaceTextureId);
#endif
m_ANW = new android::SurfaceTextureClient(m_sharedSurfaceTexture);
m_sharedSurfaceTexture->setSynchronousMode(true);
m_sharedSurfaceTexture->setBufferCount(ST_BUFFER_NUMBER+1);
int result = native_window_set_buffers_geometry(m_ANW.get(),
width, height, HAL_PIXEL_FORMAT_RGBA_8888);
GLUtils::checkSurfaceTextureError("native_window_set_buffers_geometry", result);
result = native_window_set_usage(m_ANW.get(),
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
GLUtils::checkSurfaceTextureError("native_window_set_usage", result);
}
if (!m_fboID)
glGenFramebuffers(1, &m_fboID);
}
// When bliting, if the item from the transfer queue is mismatching b/t the
// BaseTile and the content, then the item is considered as obsolete, and
// the content is discarded.
bool TransferQueue::checkObsolete(int index)
{
BaseTile* baseTilePtr = m_transferQueue[index].savedBaseTilePtr;
if (!baseTilePtr) {
XLOG("Invalid savedBaseTilePtr , such that the tile is obsolete");
return true;
}
BaseTileTexture* baseTileTexture = baseTilePtr->backTexture();
if (!baseTileTexture) {
XLOG("Invalid baseTileTexture , such that the tile is obsolete");
return true;
}
const TextureTileInfo* tileInfo = &m_transferQueue[index].tileInfo;
if (tileInfo->m_x != baseTilePtr->x()
|| tileInfo->m_y != baseTilePtr->y()
|| tileInfo->m_scale != baseTilePtr->scale()
|| tileInfo->m_painter != baseTilePtr->painter()) {
XLOG("Mismatching x, y, scale or painter , such that the tile is obsolete");
return true;
}
return false;
}
void TransferQueue::blitTileFromQueue(GLuint fboID, BaseTileTexture* destTex,
GLuint srcTexId, GLenum srcTexTarget,
int index)
{
#if GPU_UPLOAD_WITHOUT_DRAW
glBindFramebuffer(GL_FRAMEBUFFER, fboID);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
srcTexId,
0);
glBindTexture(GL_TEXTURE_2D, destTex->m_ownTextureId);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,
destTex->getSize().width(),
destTex->getSize().height());
#else
// Then set up the FBO and copy the SurfTex content in.
glBindFramebuffer(GL_FRAMEBUFFER, fboID);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
destTex->m_ownTextureId,
0);
setGLStateForCopy(destTex->getSize().width(),
destTex->getSize().height());
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
XLOG("Error: glCheckFramebufferStatus failed");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return;
}
// Use empty rect to set up the special matrix to draw.
SkRect rect = SkRect::MakeEmpty();
TilesManager::instance()->shader()->drawQuad(rect, srcTexId, 1.0,
srcTexTarget, GL_NEAREST);
// To workaround a sync issue on some platforms, we should insert the sync
// here while in the current FBO.
// This will essentially kick off the GPU command buffer, and the Tex Gen
// thread will then have to wait for this buffer to finish before writing
// into the same memory.
EGLDisplay dpy = eglGetCurrentDisplay();
if (m_currentDisplay != dpy)
m_currentDisplay = dpy;
if (m_currentDisplay != EGL_NO_DISPLAY) {
if (m_transferQueue[index].m_syncKHR != EGL_NO_SYNC_KHR)
eglDestroySyncKHR(m_currentDisplay, m_transferQueue[index].m_syncKHR);
m_transferQueue[index].m_syncKHR = eglCreateSyncKHR(m_currentDisplay,
EGL_SYNC_FENCE_KHR,
0);
}
GLUtils::checkEglError("CreateSyncKHR");
#endif
}
void TransferQueue::interruptTransferQueue(bool interrupt)
{
m_transferQueueItemLocks.lock();
m_interruptedByRemovingOp = interrupt;
if (m_interruptedByRemovingOp)
m_transferQueueItemCond.signal();
m_transferQueueItemLocks.unlock();
}
// This function must be called inside the m_transferQueueItemLocks, for the
// wait, m_interruptedByRemovingOp and getHasGLContext().
// Only called by updateQueueWithBitmap() for now.
bool TransferQueue::readyForUpdate()
{
if (!getHasGLContext())
return false;
// Don't use a while loop since when the WebView tear down, the emptyCount
// will still be 0, and we bailed out b/c of GL context lost.
if (!m_emptyItemCount) {
if (m_interruptedByRemovingOp)
return false;
m_transferQueueItemCond.wait(m_transferQueueItemLocks);
if (m_interruptedByRemovingOp)
return false;
}
if (!getHasGLContext())
return false;
// Disable this wait until we figure out why this didn't work on some
// drivers b/5332112.
#if 0
if (m_currentUploadType == GpuUpload
&& m_currentDisplay != EGL_NO_DISPLAY) {
// Check the GPU fence
EGLSyncKHR syncKHR = m_transferQueue[getNextTransferQueueIndex()].m_syncKHR;
if (syncKHR != EGL_NO_SYNC_KHR)
eglClientWaitSyncKHR(m_currentDisplay,
syncKHR,
EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
EGL_FOREVER_KHR);
}
GLUtils::checkEglError("WaitSyncKHR");
#endif
return true;
}
// Both getHasGLContext and setHasGLContext should be called within the lock.
bool TransferQueue::getHasGLContext()
{
return m_hasGLContext;
}
void TransferQueue::setHasGLContext(bool hasContext)
{
m_hasGLContext = hasContext;
}
// Only called when WebView is destroyed or switching the uploadType.
void TransferQueue::discardQueue()
{
android::Mutex::Autolock lock(m_transferQueueItemLocks);
for (int i = 0 ; i < ST_BUFFER_NUMBER; i++)
if (m_transferQueue[i].status == pendingBlit)
m_transferQueue[i].status = pendingDiscard;
bool GLContextExisted = getHasGLContext();
// Unblock the Tex Gen thread first before Tile Page deletion.
// Otherwise, there will be a deadlock while removing operations.
setHasGLContext(false);
// Only signal once when GL context lost.
if (GLContextExisted)
m_transferQueueItemCond.signal();
}
// Call on UI thread to copy from the shared Surface Texture to the BaseTile's texture.
void TransferQueue::updateDirtyBaseTiles()
{
android::Mutex::Autolock lock(m_transferQueueItemLocks);
cleanupTransportQueue();
if (!getHasGLContext())
setHasGLContext(true);
// Start from the oldest item, we call the updateTexImage to retrive
// the texture and blit that into each BaseTile's texture.
const int nextItemIndex = getNextTransferQueueIndex();
int index = nextItemIndex;
bool usedFboForUpload = false;
for (int k = 0; k < ST_BUFFER_NUMBER ; k++) {
if (m_transferQueue[index].status == pendingBlit) {
bool obsoleteBaseTile = checkObsolete(index);
// Save the needed info, update the Surf Tex, clean up the item in
// the queue. Then either move on to next item or copy the content.
BaseTileTexture* destTexture = 0;
if (!obsoleteBaseTile)
destTexture = m_transferQueue[index].savedBaseTilePtr->backTexture();
if (m_transferQueue[index].uploadType == GpuUpload) {
status_t result = m_sharedSurfaceTexture->updateTexImage();
if (result != OK)
XLOGC("unexpected error: updateTexImage return %d", result);
}
m_transferQueue[index].savedBaseTilePtr = 0;
m_transferQueue[index].status = emptyItem;
if (obsoleteBaseTile) {
XLOG("Warning: the texture is obsolete for this baseTile");
index = (index + 1) % ST_BUFFER_NUMBER;
continue;
}
// guarantee that we have a texture to blit into
destTexture->requireGLTexture();
if (m_transferQueue[index].uploadType == CpuUpload) {
// Here we just need to upload the bitmap content to the GL Texture
GLUtils::updateTextureWithBitmap(destTexture->m_ownTextureId, 0, 0,
*m_transferQueue[index].bitmap);
} else {
if (!usedFboForUpload) {
saveGLState();
usedFboForUpload = true;
}
blitTileFromQueue(m_fboID, destTexture,
m_sharedSurfaceTextureId,
m_sharedSurfaceTexture->getCurrentTextureTarget(),
index);
}
// After the base tile copied into the GL texture, we need to
// update the texture's info such that at draw time, readyFor
// will find the latest texture's info
// We don't need a map any more, each texture contains its own
// texturesTileInfo.
destTexture->setOwnTextureTileInfoFromQueue(&m_transferQueue[index].tileInfo);
XLOG("Blit tile x, y %d %d with dest texture %p to destTexture->m_ownTextureId %d",
m_transferQueue[index].tileInfo.m_x,
m_transferQueue[index].tileInfo.m_y,
destTexture,
destTexture->m_ownTextureId);
}
index = (index + 1) % ST_BUFFER_NUMBER;
}
// Clean up FBO setup. Doing this for both CPU/GPU upload can make the
// dynamic switch possible. Moving this out from the loop can save some
// milli-seconds.
if (usedFboForUpload) {
glBindFramebuffer(GL_FRAMEBUFFER, 0); // rebind the standard FBO
restoreGLState();
GLUtils::checkGlError("updateDirtyBaseTiles");
}
m_emptyItemCount = ST_BUFFER_NUMBER;
m_transferQueueItemCond.signal();
}
void TransferQueue::updateQueueWithBitmap(const TileRenderInfo* renderInfo,
int x, int y, const SkBitmap& bitmap)
{
if (!tryUpdateQueueWithBitmap(renderInfo, x, y, bitmap)) {
// failed placing bitmap in queue, discard tile's texture so it will be
// re-enqueued (and repainted)
BaseTile* tile = renderInfo->baseTile;
if (tile)
tile->backTextureTransferFail();
}
}
bool TransferQueue::tryUpdateQueueWithBitmap(const TileRenderInfo* renderInfo,
int x, int y, const SkBitmap& bitmap)
{
m_transferQueueItemLocks.lock();
bool ready = readyForUpdate();
TextureUploadType currentUploadType = m_currentUploadType;
m_transferQueueItemLocks.unlock();
if (!ready) {
XLOG("Quit bitmap update: not ready! for tile x y %d %d",
renderInfo->x, renderInfo->y);
return false;
}
if (currentUploadType == GpuUpload) {
// a) Dequeue the Surface Texture and write into the buffer
if (!m_ANW.get()) {
XLOG("ERROR: ANW is null");
return false;
}
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(m_ANW.get(), &buffer, 0))
return false;
uint8_t* img = (uint8_t*)buffer.bits;
int row, col;
int bpp = 4; // Now we only deal with RGBA8888 format.
int width = TilesManager::instance()->tileWidth();
int height = TilesManager::instance()->tileHeight();
if (!x && !y && bitmap.width() == width && bitmap.height() == height) {
bitmap.lockPixels();
uint8_t* bitmapOrigin = static_cast<uint8_t*>(bitmap.getPixels());
if (buffer.stride != bitmap.width())
// Copied line by line since we need to handle the offsets and stride.
for (row = 0 ; row < bitmap.height(); row ++) {
uint8_t* dst = &(img[buffer.stride * row * bpp]);
uint8_t* src = &(bitmapOrigin[bitmap.width() * row * bpp]);
memcpy(dst, src, bpp * bitmap.width());
}
else
memcpy(img, bitmapOrigin, bpp * bitmap.width() * bitmap.height());
bitmap.unlockPixels();
} else {
// TODO: implement the partial invalidate here!
XLOG("ERROR: don't expect to get here yet before we support partial inval");
}
ANativeWindow_unlockAndPost(m_ANW.get());
}
m_transferQueueItemLocks.lock();
// b) After update the Surface Texture, now udpate the transfer queue info.
addItemInTransferQueue(renderInfo, currentUploadType, &bitmap);
m_transferQueueItemLocks.unlock();
XLOG("Bitmap updated x, y %d %d, baseTile %p",
renderInfo->x, renderInfo->y, renderInfo->baseTile);
return true;
}
// Note that there should be lock/unlock around this function call.
// Currently only called by GLUtils::updateSharedSurfaceTextureWithBitmap.
void TransferQueue::addItemInTransferQueue(const TileRenderInfo* renderInfo,
TextureUploadType type,
const SkBitmap* bitmap)
{
m_transferQueueIndex = (m_transferQueueIndex + 1) % ST_BUFFER_NUMBER;
int index = m_transferQueueIndex;
if (m_transferQueue[index].savedBaseTilePtr
|| m_transferQueue[index].status != emptyItem) {
XLOG("ERROR update a tile which is dirty already @ index %d", index);
}
m_transferQueue[index].savedBaseTileTexturePtr = renderInfo->baseTile->backTexture();
m_transferQueue[index].savedBaseTilePtr = renderInfo->baseTile;
m_transferQueue[index].status = pendingBlit;
m_transferQueue[index].uploadType = type;
if (type == CpuUpload && bitmap) {
// Lazily create the bitmap
if (!m_transferQueue[index].bitmap) {
m_transferQueue[index].bitmap = new SkBitmap();
int w = bitmap->width();
int h = bitmap->height();
m_transferQueue[index].bitmap->setConfig(bitmap->config(), w, h);
}
bitmap->copyTo(m_transferQueue[index].bitmap, bitmap->config());
}
// Now fill the tileInfo.
TextureTileInfo* textureInfo = &m_transferQueue[index].tileInfo;
textureInfo->m_x = renderInfo->x;
textureInfo->m_y = renderInfo->y;
textureInfo->m_scale = renderInfo->scale;
textureInfo->m_painter = renderInfo->tilePainter;
textureInfo->m_picture = renderInfo->textureInfo->m_pictureCount;
m_emptyItemCount--;
}
void TransferQueue::setTextureUploadType(TextureUploadType type)
{
if (m_currentUploadType == type)
return;
discardQueue();
android::Mutex::Autolock lock(m_transferQueueItemLocks);
m_currentUploadType = type;
XLOGC("Now we set the upload to %s", m_currentUploadType == GpuUpload ? "GpuUpload" : "CpuUpload");
}
// Note: this need to be called within th lock.
// Only called by updateDirtyBaseTiles() for now
void TransferQueue::cleanupTransportQueue()
{
int index = getNextTransferQueueIndex();
for (int i = 0 ; i < ST_BUFFER_NUMBER; i++) {
if (m_transferQueue[index].status == pendingDiscard) {
// No matter what the current upload type is, as long as there has
// been a Surf Tex enqueue operation, this updateTexImage need to
// be called to keep things in sync.
if (m_transferQueue[index].uploadType == GpuUpload) {
status_t result = m_sharedSurfaceTexture->updateTexImage();
if (result != OK)
XLOGC("unexpected error: updateTexImage return %d", result);
}
// since tiles in the queue may be from another webview, remove
// their textures so that they will be repainted / retransferred
BaseTile* tile = m_transferQueue[index].savedBaseTilePtr;
BaseTileTexture* texture = m_transferQueue[index].savedBaseTileTexturePtr;
if (tile && texture && texture->owner() == tile) {
// since tile destruction removes textures on the UI thread, the
// texture->owner ptr guarantees the tile is valid
tile->discardBackTexture();
XLOG("transfer queue discarded tile %p, removed texture", tile);
}
m_transferQueue[index].savedBaseTilePtr = 0;
m_transferQueue[index].savedBaseTileTexturePtr = 0;
m_transferQueue[index].status = emptyItem;
}
index = (index + 1) % ST_BUFFER_NUMBER;
}
}
void TransferQueue::saveGLState()
{
glGetIntegerv(GL_VIEWPORT, m_GLStateBeforeBlit.viewport);
glGetBooleanv(GL_SCISSOR_TEST, m_GLStateBeforeBlit.scissor);
glGetBooleanv(GL_DEPTH_TEST, m_GLStateBeforeBlit.depth);
#if DEBUG
glGetFloatv(GL_COLOR_CLEAR_VALUE, m_GLStateBeforeBlit.clearColor);
#endif
}
void TransferQueue::setGLStateForCopy(int width, int height)
{
// Need to match the texture size.
glViewport(0, 0, width, height);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
// Clear the content is only for debug purpose.
#if DEBUG
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
#endif
}
void TransferQueue::restoreGLState()
{
glViewport(m_GLStateBeforeBlit.viewport[0],
m_GLStateBeforeBlit.viewport[1],
m_GLStateBeforeBlit.viewport[2],
m_GLStateBeforeBlit.viewport[3]);
if (m_GLStateBeforeBlit.scissor[0])
glEnable(GL_SCISSOR_TEST);
if (m_GLStateBeforeBlit.depth[0])
glEnable(GL_DEPTH_TEST);
#if DEBUG
glClearColor(m_GLStateBeforeBlit.clearColor[0],
m_GLStateBeforeBlit.clearColor[1],
m_GLStateBeforeBlit.clearColor[2],
m_GLStateBeforeBlit.clearColor[3]);
#endif
}
int TransferQueue::getNextTransferQueueIndex()
{
return (m_transferQueueIndex + 1) % ST_BUFFER_NUMBER;
}
} // namespace WebCore
#endif // USE(ACCELERATED_COMPOSITING