/* * 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. */ #define LOG_TAG "TransferQueue" #define LOG_NDEBUG 1 #include "config.h" #include "TransferQueue.h" #if USE(ACCELERATED_COMPOSITING) #include "AndroidLog.h" #include "BaseRenderer.h" #include "DrawQuadData.h" #include "GLUtils.h" #include "Tile.h" #include "TileTexture.h" #include "TilesManager.h" #include #include #include // For simple webView usage, MINIMAL_SIZE is recommended for memory saving. // In browser case, EFFICIENT_SIZE is preferred. #define MINIMAL_SIZE 1 #define EFFICIENT_SIZE 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(bool useMinimalMem) : m_eglSurface(EGL_NO_SURFACE) , m_transferQueueIndex(0) , m_fboID(0) , m_sharedSurfaceTextureId(0) , m_hasGLContext(true) , m_currentDisplay(EGL_NO_DISPLAY) , m_currentUploadType(DEFAULT_UPLOAD_TYPE) { memset(&m_GLStateBeforeBlit, 0, sizeof(m_GLStateBeforeBlit)); m_transferQueueSize = useMinimalMem ? MINIMAL_SIZE : EFFICIENT_SIZE; m_emptyItemCount = m_transferQueueSize; m_transferQueue = new TileTransferData[m_transferQueueSize]; } TransferQueue::~TransferQueue() { android::Mutex::Autolock lock(m_transferQueueItemLocks); cleanupGLResources(); delete[] m_transferQueue; } // Set the queue to be totally empty, abandon the Surface Texture. This should // be called only when we hit a wrong EGL Context in an error situation. void TransferQueue::resetQueue() { android::Mutex::Autolock lock(m_transferQueueItemLocks); emptyAndAbandonQueue(); m_sharedSurfaceTextureId = 0; } // This should be called within the m_transferQueueItemLocks. // Now only called by emptyQueue() and destructor. void TransferQueue::cleanupGLResources() { if (m_fboID) { glDeleteFramebuffers(1, &m_fboID); m_fboID = 0; } if (m_sharedSurfaceTextureId) { glDeleteTextures(1, &m_sharedSurfaceTextureId); m_sharedSurfaceTextureId = 0; } } void TransferQueue::initGLResources(int width, int height) { android::Mutex::Autolock lock(m_transferQueueItemLocks); if (!m_sharedSurfaceTextureId) { glGenTextures(1, &m_sharedSurfaceTextureId); sp bufferQueue(new BufferQueue(true)); m_sharedSurfaceTexture = #if GPU_UPLOAD_WITHOUT_DRAW new android::SurfaceTexture(m_sharedSurfaceTextureId, true, GL_TEXTURE_2D, true, bufferQueue); #else new android::SurfaceTexture(m_sharedSurfaceTextureId, true, GL_TEXTURE_EXTERNAL_OES, true, bufferQueue); #endif m_ANW = new android::SurfaceTextureClient(m_sharedSurfaceTexture); m_sharedSurfaceTexture->setSynchronousMode(true); int extraBuffersNeeded = 0; int extraHackyBuffersNeeded = 0; if (m_transferQueueSize == EFFICIENT_SIZE) extraHackyBuffersNeeded = 13; m_ANW->query(m_ANW.get(), NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &extraBuffersNeeded); bufferQueue->setBufferCount(m_transferQueueSize + extraBuffersNeeded + extraHackyBuffersNeeded); 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 // Tile and the content, then the item is considered as obsolete, and // the content is discarded. bool TransferQueue::checkObsolete(const TileTransferData* data) { Tile* baseTilePtr = data->savedTilePtr; if (!baseTilePtr) { ALOGV("Invalid savedTilePtr , such that the tile is obsolete"); return true; } TileTexture* baseTileTexture = baseTilePtr->backTexture(); if (!baseTileTexture || baseTileTexture != data->savedTileTexturePtr) { ALOGV("Invalid baseTileTexture %p (vs expected %p), such that the tile is obsolete", baseTileTexture, data->savedTileTexturePtr); return true; } return false; } void TransferQueue::blitTileFromQueue(GLuint fboID, TileTexture* destTex, GLuint srcTexId, GLenum srcTexTarget, int index) { #if GPU_UPLOAD_WITHOUT_DRAW glBindFramebuffer(GL_FRAMEBUFFER, fboID); glBindTexture(GL_TEXTURE_2D, destTex->m_ownTextureId); int textureWidth = destTex->getSize().width(); int textureHeight = destTex->getSize().height(); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexId, 0); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, textureWidth, textureHeight); if (GLUtils::checkGlError("At the end of blitTileFromQueue()")) { #ifndef DEBUG if (GLUtils::allowGLLog()) #endif ALOGE("blitTileFromQueue ERROR: fboId %d, destTexId %d, srcTexId %d," " textureWidth %d, textureHeight %d", fboID, destTex->m_ownTextureId, srcTexId, textureWidth, textureHeight); } #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) { ALOGV("Error: glCheckFramebufferStatus failed"); return; } // Use empty rect to set up the special matrix to draw. SkRect rect = SkRect::MakeEmpty(); TextureQuadData data(srcTexId, GL_NEAREST, srcTexTarget, Blit, 0, 0, 1.0, false); TilesManager::instance()->shader()->drawQuad(&data); #endif } // This function must be called inside the m_transferQueueItemLocks, for the // wait 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) m_transferQueueItemCond.wait(m_transferQueueItemLocks); if (!getHasGLContext()) return false; 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; } // Call within a m_transferQueueItemLocks, now called by resetQueue() and // cleanupGLResoucesAndQueue() void TransferQueue::emptyAndAbandonQueue() { for (int i = 0 ; i < m_transferQueueSize; i++) clearItemInTranferQueue(i); m_emptyItemCount = m_transferQueueSize; clearPureColorQueue(); if (m_sharedSurfaceTexture.get()) { m_sharedSurfaceTexture->abandon(); m_sharedSurfaceTexture.clear(); } // This can prevent the tex gen thread to produce, until next incoming draw. setHasGLContext(false); } void TransferQueue::cleanupGLResourcesAndQueue() { android::Mutex::Autolock lock(m_transferQueueItemLocks); emptyAndAbandonQueue(); cleanupGLResources(); } // Set all the content in the queue to pendingDiscard, after this, there will // be nothing added to the queue, and this can be called in any thread. // However, in order to discard the content in the Surface Texture using // updateTexImage, cleanupPendingDiscard need to be called on the UI thread. // Must be called within a m_transferQueueItemLocks. void TransferQueue::setPendingDiscard() { for (int i = 0 ; i < m_transferQueueSize; i++) if (m_transferQueue[i].status == pendingBlit) m_transferQueue[i].status = pendingDiscard; clearPureColorQueue(); 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(); } void TransferQueue::clearPureColorQueue() { for (unsigned int i = 0 ; i < m_pureColorTileQueue.size(); i++) { SkSafeUnref(m_pureColorTileQueue[i].savedTilePainter); m_pureColorTileQueue[i].savedTilePainter = 0; } m_pureColorTileQueue.clear(); } void TransferQueue::updatePureColorTiles() { for (unsigned int i = 0 ; i < m_pureColorTileQueue.size(); i++) { TileTransferData* data = &m_pureColorTileQueue[i]; if (data->status == pendingBlit) { TileTexture* destTexture = 0; bool obsoleteTile = checkObsolete(data); if (!obsoleteTile) { destTexture = data->savedTilePtr->backTexture(); destTexture->setPureColor(data->pureColor); destTexture->transferComplete(); } } else if (data->status == emptyItem || data->status == pendingDiscard) { // The queue should be clear instead of setting to different status. ALOGV("Warning: Don't expect an emptyItem here."); } } clearPureColorQueue(); } // Call on UI thread to copy from the shared Surface Texture to the Tile's texture. void TransferQueue::updateDirtyTiles() { android::Mutex::Autolock lock(m_transferQueueItemLocks); cleanupPendingDiscard(); if (!getHasGLContext()) setHasGLContext(true); // Check the pure color tile first, since it is simpler. updatePureColorTiles(); // Start from the oldest item, we call the updateTexImage to retrive // the texture and blit that into each Tile's texture. const int nextItemIndex = getNextTransferQueueIndex(); int index = nextItemIndex; bool usedFboForUpload = false; for (int k = 0; k < m_transferQueueSize ; k++) { if (m_transferQueue[index].status == pendingBlit) { bool obsoleteTile = checkObsolete(&m_transferQueue[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. TileTexture* destTexture = 0; if (!obsoleteTile) destTexture = m_transferQueue[index].savedTilePtr->backTexture(); if (m_transferQueue[index].uploadType == GpuUpload) { status_t result = m_sharedSurfaceTexture->updateTexImage(); if (result != OK) ALOGE("unexpected error: updateTexImage return %d", result); } if (obsoleteTile) { ALOGV("Warning: the texture is obsolete for this baseTile"); clearItemInTranferQueue(index); index = (index + 1) % m_transferQueueSize; continue; } // guarantee that we have a texture to blit into destTexture->requireGLTexture(); GLUtils::checkGlError("before blitTileFromQueue"); if (m_transferQueue[index].uploadType == CpuUpload) { // Here we just need to upload the bitmap content to the GL Texture GLUtils::updateTextureWithBitmap(destTexture->m_ownTextureId, *m_transferQueue[index].bitmap); } else { if (!usedFboForUpload) { saveGLState(); usedFboForUpload = true; } blitTileFromQueue(m_fboID, destTexture, m_sharedSurfaceTextureId, m_sharedSurfaceTexture->getCurrentTextureTarget(), index); } destTexture->setPure(false); destTexture->transferComplete(); clearItemInTranferQueue(index); ALOGV("Blit tile x, y %d %d with dest texture %p to destTexture->m_ownTextureId %d", m_transferQueue[index].savedTilePtr, destTexture, destTexture->m_ownTextureId); } index = (index + 1) % m_transferQueueSize; } // 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) { restoreGLState(); GLUtils::checkGlError("updateDirtyTiles"); } m_emptyItemCount = m_transferQueueSize; m_transferQueueItemCond.signal(); } void TransferQueue::updateQueueWithBitmap(const TileRenderInfo* renderInfo, SkBitmap& bitmap) { TRACE_METHOD(); if (!tryUpdateQueueWithBitmap(renderInfo, bitmap)) { // failed placing bitmap in queue, discard tile's texture so it will be // re-enqueued (and repainted) Tile* tile = renderInfo->baseTile; if (tile) tile->backTextureTransferFail(); } } bool TransferQueue::tryUpdateQueueWithBitmap(const TileRenderInfo* renderInfo, SkBitmap& bitmap) { // This lock need to cover the full update since it is possible that queue // will be cleaned up in the middle of this update without the lock. // The Surface Texture will not block us since the readyForUpdate will check // availability of the slots in the queue first. android::Mutex::Autolock lock(m_transferQueueItemLocks); bool ready = readyForUpdate(); TextureUploadType currentUploadType = m_currentUploadType; if (!ready) { ALOGV("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()) { ALOGV("ERROR: ANW is null"); return false; } if (!GLUtils::updateSharedSurfaceTextureWithBitmap(m_ANW.get(), bitmap)) return false; } // b) After update the Surface Texture, now udpate the transfer queue info. addItemInTransferQueue(renderInfo, currentUploadType, bitmap); ALOGV("Bitmap updated x, y %d %d, baseTile %p", renderInfo->x, renderInfo->y, renderInfo->baseTile); return true; } void TransferQueue::addItemInPureColorQueue(const TileRenderInfo* renderInfo) { // The pure color tiles' queue will be read from UI thread and written in // Tex Gen thread, thus we need to have a lock here. android::Mutex::Autolock lock(m_transferQueueItemLocks); TileTransferData data; addItemCommon(renderInfo, GpuUpload, &data); data.pureColor = renderInfo->pureColor; m_pureColorTileQueue.append(data); } void TransferQueue::clearItemInTranferQueue(int index) { m_transferQueue[index].savedTilePtr = 0; SkSafeUnref(m_transferQueue[index].savedTilePainter); m_transferQueue[index].savedTilePainter = 0; m_transferQueue[index].status = emptyItem; } // Translates the info from TileRenderInfo and others to TileTransferData. // This is used by pure color tiles and normal tiles. void TransferQueue::addItemCommon(const TileRenderInfo* renderInfo, TextureUploadType type, TileTransferData* data) { data->savedTileTexturePtr = renderInfo->baseTile->backTexture(); data->savedTilePainter = renderInfo->tilePainter; SkSafeRef(data->savedTilePainter); data->savedTilePtr = renderInfo->baseTile; data->status = pendingBlit; data->uploadType = type; IntRect inval(0, 0, 0, 0); } // 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, SkBitmap& bitmap) { m_transferQueueIndex = (m_transferQueueIndex + 1) % m_transferQueueSize; int index = m_transferQueueIndex; if (m_transferQueue[index].savedTilePtr || m_transferQueue[index].status != emptyItem) { ALOGV("ERROR update a tile which is dirty already @ index %d", index); } TileTransferData* data = &m_transferQueue[index]; addItemCommon(renderInfo, type, data); if (type == CpuUpload) { // 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); m_transferQueue[index].bitmap->allocPixels(); } SkBitmap temp = (*m_transferQueue[index].bitmap); (*m_transferQueue[index].bitmap) = bitmap; bitmap = temp; } m_emptyItemCount--; } void TransferQueue::setTextureUploadType(TextureUploadType type) { android::Mutex::Autolock lock(m_transferQueueItemLocks); if (m_currentUploadType == type) return; setPendingDiscard(); m_currentUploadType = type; ALOGD("Now we set the upload to %s", m_currentUploadType == GpuUpload ? "GpuUpload" : "CpuUpload"); } // Note: this need to be called within the lock and on the UI thread. // Only called by updateDirtyTiles() and emptyQueue() for now void TransferQueue::cleanupPendingDiscard() { int index = getNextTransferQueueIndex(); for (int i = 0 ; i < m_transferQueueSize; 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) ALOGE("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 Tile* tile = m_transferQueue[index].savedTilePtr; TileTexture* texture = m_transferQueue[index].savedTileTexturePtr; 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(); ALOGV("transfer queue discarded tile %p, removed texture", tile); } clearItemInTranferQueue(index); } index = (index + 1) % m_transferQueueSize; } } void TransferQueue::saveGLState() { glGetIntegerv(GL_FRAMEBUFFER_BINDING, m_GLStateBeforeBlit.bufferId); glGetIntegerv(GL_VIEWPORT, m_GLStateBeforeBlit.viewport); glGetBooleanv(GL_SCISSOR_TEST, m_GLStateBeforeBlit.scissor); glGetBooleanv(GL_DEPTH_TEST, m_GLStateBeforeBlit.depth); #ifdef 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. #ifdef DEBUG glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); #endif } void TransferQueue::restoreGLState() { glBindFramebuffer(GL_FRAMEBUFFER, m_GLStateBeforeBlit.bufferId[0]); 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); #ifdef 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) % m_transferQueueSize; } } // namespace WebCore #endif // USE(ACCELERATED_COMPOSITING