/* * 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 #include #include #include #include #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 4 // 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) m_sharedSurfaceTexture->updateTexImage(); 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->requireTexture(); 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) { 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; } if (currentUploadType == GpuUpload) { // a) Dequeue the Surface Texture and write into the buffer if (!m_ANW.get()) { XLOG("ERROR: ANW is null"); return; } ANativeWindow_Buffer buffer; if (ANativeWindow_lock(m_ANW.get(), &buffer, 0)) return; 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(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); } // 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].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) { 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) m_sharedSurfaceTexture->updateTexImage(); m_transferQueue[index].savedBaseTilePtr = 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