/* * Copyright (C) 2012 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" #include "platform/graphics/Canvas2DLayerBridge.h" #include "GrContext.h" #include "SkDevice.h" #include "SkSurface.h" #include "platform/TraceEvent.h" #include "platform/graphics/Canvas2DLayerManager.h" #include "platform/graphics/GraphicsLayer.h" #include "platform/graphics/gpu/SharedGraphicsContext3D.h" #include "public/platform/Platform.h" #include "public/platform/WebCompositorSupport.h" #include "public/platform/WebGraphicsContext3D.h" using blink::WebExternalTextureLayer; using blink::WebGraphicsContext3D; namespace WebCore { static PassRefPtr createSkSurface(GraphicsContext3D* context3D, const IntSize& size, int msaaSampleCount) { ASSERT(!context3D->webContext()->isContextLost()); GrContext* gr = context3D->grContext(); if (!gr) return 0; gr->resetContext(); SkImageInfo info; info.fWidth = size.width(); info.fHeight = size.height(); info.fColorType = kPMColor_SkColorType; info.fAlphaType = kPremul_SkAlphaType; return adoptRef(SkSurface::NewRenderTarget(gr, info, msaaSampleCount)); } PassRefPtr Canvas2DLayerBridge::create(const IntSize& size, OpacityMode opacityMode, int msaaSampleCount) { TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation"); RefPtr context = SharedGraphicsContext3D::get(); RefPtr surface(createSkSurface(context.get(), size, msaaSampleCount)); if (!surface) return 0; RefPtr layerBridge; OwnPtr canvas = adoptPtr(SkDeferredCanvas::Create(surface.get())); layerBridge = adoptRef(new Canvas2DLayerBridge(context, canvas.release(), msaaSampleCount, opacityMode)); return layerBridge.release(); } Canvas2DLayerBridge::Canvas2DLayerBridge(PassRefPtr context, PassOwnPtr canvas, int msaaSampleCount, OpacityMode opacityMode) : m_canvas(canvas) , m_context(context) , m_msaaSampleCount(msaaSampleCount) , m_bytesAllocated(0) , m_didRecordDrawCommand(false) , m_surfaceIsValid(true) , m_framesPending(0) , m_destructionInProgress(false) , m_rateLimitingEnabled(false) , m_next(0) , m_prev(0) , m_lastImageId(0) { ASSERT(m_canvas); // Used by browser tests to detect the use of a Canvas2DLayerBridge. TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation"); m_layer = adoptPtr(blink::Platform::current()->compositorSupport()->createExternalTextureLayer(this)); m_layer->setOpaque(opacityMode == Opaque); m_layer->setBlendBackgroundColor(opacityMode != Opaque); GraphicsLayer::registerContentsLayer(m_layer->layer()); m_layer->setRateLimitContext(m_rateLimitingEnabled); m_canvas->setNotificationClient(this); } Canvas2DLayerBridge::~Canvas2DLayerBridge() { ASSERT(m_destructionInProgress); m_layer.clear(); Vector::iterator mailboxInfo; for (mailboxInfo = m_mailboxes.begin(); mailboxInfo < m_mailboxes.end(); mailboxInfo++) { ASSERT(mailboxInfo->m_status != MailboxInUse); if (mailboxInfo->m_status == MailboxReleased) { if (mailboxInfo->m_mailbox.syncPoint) { context()->waitSyncPoint(mailboxInfo->m_mailbox.syncPoint); mailboxInfo->m_mailbox.syncPoint = 0; } // Invalidate texture state in case the compositor altered it since the copy-on-write. mailboxInfo->m_image->getTexture()->invalidateCachedState(); } } m_mailboxes.clear(); } void Canvas2DLayerBridge::beginDestruction() { ASSERT(!m_destructionInProgress); m_destructionInProgress = true; GraphicsLayer::unregisterContentsLayer(m_layer->layer()); m_canvas->setNotificationClient(0); m_layer->clearTexture(); Canvas2DLayerManager::get().layerToBeDestroyed(this); // Orphaning the layer is required to trigger the recration of a new layer // in the case where destruction is caused by a canvas resize. Test: // virtual/gpu/fast/canvas/canvas-resize-after-paint-without-layout.html m_layer->layer()->removeFromParent(); } void Canvas2DLayerBridge::limitPendingFrames() { ASSERT(!m_destructionInProgress); if (m_didRecordDrawCommand) { m_framesPending++; m_didRecordDrawCommand = false; if (m_framesPending > 1) { // Turn on the rate limiter if this layer tends to accumulate a // non-discardable multi-frame backlog of draw commands. setRateLimitingEnabled(true); } if (m_rateLimitingEnabled) { flush(); } } } void Canvas2DLayerBridge::prepareForDraw() { ASSERT(!m_destructionInProgress); ASSERT(m_layer); if (!isValid()) { if (m_canvas) { // drop pending commands because there is no surface to draw to m_canvas->silentFlush(); } return; } m_context->makeContextCurrent(); } void Canvas2DLayerBridge::storageAllocatedForRecordingChanged(size_t bytesAllocated) { ASSERT(!m_destructionInProgress); intptr_t delta = (intptr_t)bytesAllocated - (intptr_t)m_bytesAllocated; m_bytesAllocated = bytesAllocated; Canvas2DLayerManager::get().layerAllocatedStorageChanged(this, delta); } size_t Canvas2DLayerBridge::storageAllocatedForRecording() { ASSERT(!m_destructionInProgress); return m_canvas->storageAllocatedForRecording(); } void Canvas2DLayerBridge::flushedDrawCommands() { ASSERT(!m_destructionInProgress); storageAllocatedForRecordingChanged(storageAllocatedForRecording()); m_framesPending = 0; } void Canvas2DLayerBridge::skippedPendingDrawCommands() { ASSERT(!m_destructionInProgress); // Stop triggering the rate limiter if SkDeferredCanvas is detecting // and optimizing overdraw. setRateLimitingEnabled(false); flushedDrawCommands(); } void Canvas2DLayerBridge::setRateLimitingEnabled(bool enabled) { ASSERT(!m_destructionInProgress || !enabled); if (m_rateLimitingEnabled != enabled) { m_rateLimitingEnabled = enabled; m_layer->setRateLimitContext(m_rateLimitingEnabled); } } size_t Canvas2DLayerBridge::freeMemoryIfPossible(size_t bytesToFree) { ASSERT(!m_destructionInProgress); size_t bytesFreed = m_canvas->freeMemoryIfPossible(bytesToFree); if (bytesFreed) Canvas2DLayerManager::get().layerAllocatedStorageChanged(this, -((intptr_t)bytesFreed)); m_bytesAllocated -= bytesFreed; return bytesFreed; } void Canvas2DLayerBridge::flush() { ASSERT(!m_destructionInProgress); if (m_canvas->hasPendingCommands()) { TRACE_EVENT0("cc", "Canvas2DLayerBridge::flush"); m_canvas->flush(); } } blink::WebGraphicsContext3D* Canvas2DLayerBridge::context() { // Check on m_layer is necessary because context() may be called during // the destruction of m_layer if (m_layer) { isValid(); // To ensure rate limiter is disabled if context is lost. } return m_context->webContext(); } bool Canvas2DLayerBridge::isValid() { ASSERT(m_layer); if (m_destructionInProgress) return false; if (m_context->webContext()->isContextLost() || !m_surfaceIsValid) { // Attempt to recover. m_layer->clearTexture(); m_mailboxes.clear(); RefPtr sharedContext = SharedGraphicsContext3D::get(); if (!sharedContext || sharedContext->webContext()->isContextLost()) { m_surfaceIsValid = false; } else { m_context = sharedContext; IntSize size(m_canvas->getTopDevice()->width(), m_canvas->getTopDevice()->height()); RefPtr surface(createSkSurface(m_context.get(), size, m_msaaSampleCount)); if (surface.get()) { m_canvas->setSurface(surface.get()); m_surfaceIsValid = true; // FIXME: draw sad canvas picture into new buffer crbug.com/243842 } else { // Surface allocation failed. Set m_surfaceIsValid to false to // trigger subsequent retry. m_surfaceIsValid = false; } } } if (!m_surfaceIsValid) setRateLimitingEnabled(false); return m_surfaceIsValid; } bool Canvas2DLayerBridge::prepareMailbox(blink::WebExternalTextureMailbox* outMailbox, blink::WebExternalBitmap* bitmap) { if (bitmap) { // Using accelerated 2d canvas with software renderer, which // should only happen in tests that use fake graphics contexts. // In this case, we do not care about producing any results for // compositing. m_canvas->silentFlush(); return false; } if (!isValid()) return false; // Release to skia textures that were previouosly released by the // compositor. We do this before acquiring the next snapshot in // order to cap maximum gpu memory consumption. m_context->makeContextCurrent(); flush(); Vector::iterator mailboxInfo; for (mailboxInfo = m_mailboxes.begin(); mailboxInfo < m_mailboxes.end(); mailboxInfo++) { if (mailboxInfo->m_status == MailboxReleased) { if (mailboxInfo->m_mailbox.syncPoint) { context()->waitSyncPoint(mailboxInfo->m_mailbox.syncPoint); mailboxInfo->m_mailbox.syncPoint = 0; } // Invalidate texture state in case the compositor altered it since the copy-on-write. mailboxInfo->m_image->getTexture()->invalidateCachedState(); mailboxInfo->m_image.reset(0); mailboxInfo->m_status = MailboxAvailable; } } SkAutoTUnref image(m_canvas->newImageSnapshot()); // Early exit if canvas was not drawn to since last prepareMailbox if (image->uniqueID() == m_lastImageId) return false; m_lastImageId = image->uniqueID(); mailboxInfo = createMailboxInfo(); mailboxInfo->m_status = MailboxInUse; mailboxInfo->m_image.swap(&image); // Because of texture sharing with the compositor, we must invalidate // the state cached in skia so that the deferred copy on write // in SkSurface_Gpu does not make any false assumptions. mailboxInfo->m_image->getTexture()->invalidateCachedState(); ASSERT(mailboxInfo->m_mailbox.syncPoint == 0); ASSERT(mailboxInfo->m_image.get()); ASSERT(mailboxInfo->m_image->getTexture()); m_context->bindTexture(GL_TEXTURE_2D, mailboxInfo->m_image->getTexture()->getTextureHandle()); m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); context()->produceTextureCHROMIUM(GL_TEXTURE_2D, mailboxInfo->m_mailbox.name); context()->flush(); mailboxInfo->m_mailbox.syncPoint = context()->insertSyncPoint(); m_context->bindTexture(GL_TEXTURE_2D, 0); // Because we are changing the texture binding without going through skia, // we must dirty the context. m_context->grContext()->resetContext(kTextureBinding_GrGLBackendState); // set m_parentLayerBridge to make sure 'this' stays alive as long as it has // live mailboxes ASSERT(!mailboxInfo->m_parentLayerBridge); mailboxInfo->m_parentLayerBridge = this; *outMailbox = mailboxInfo->m_mailbox; return true; } Canvas2DLayerBridge::MailboxInfo* Canvas2DLayerBridge::createMailboxInfo() { ASSERT(!m_destructionInProgress); MailboxInfo* mailboxInfo; for (mailboxInfo = m_mailboxes.begin(); mailboxInfo < m_mailboxes.end(); mailboxInfo++) { if (mailboxInfo->m_status == MailboxAvailable) { return mailboxInfo; } } // No available mailbox: create one. m_mailboxes.grow(m_mailboxes.size() + 1); mailboxInfo = &m_mailboxes.last(); context()->genMailboxCHROMIUM(mailboxInfo->m_mailbox.name); // Worst case, canvas is triple buffered. More than 3 active mailboxes // means there is a problem. // For the single-threaded case, this value needs to be at least // kMaxSwapBuffersPending+1 (in render_widget.h). // Because of crbug.com/247874, it needs to be kMaxSwapBuffersPending+2. // TODO(piman): fix this. ASSERT(m_mailboxes.size() <= 4); ASSERT(mailboxInfo < m_mailboxes.end()); return mailboxInfo; } void Canvas2DLayerBridge::mailboxReleased(const blink::WebExternalTextureMailbox& mailbox) { Vector::iterator mailboxInfo; for (mailboxInfo = m_mailboxes.begin(); mailboxInfo < m_mailboxes.end(); mailboxInfo++) { if (!memcmp(mailboxInfo->m_mailbox.name, mailbox.name, sizeof(mailbox.name))) { mailboxInfo->m_mailbox.syncPoint = mailbox.syncPoint; ASSERT(mailboxInfo->m_status == MailboxInUse); mailboxInfo->m_status = MailboxReleased; // Trigger Canvas2DLayerBridge self-destruction if this is the // last live mailbox and the layer bridge is not externally // referenced. ASSERT(mailboxInfo->m_parentLayerBridge.get() == this); mailboxInfo->m_parentLayerBridge.clear(); return; } } } blink::WebLayer* Canvas2DLayerBridge::layer() const { ASSERT(m_layer); return m_layer->layer(); } void Canvas2DLayerBridge::willUse() { ASSERT(!m_destructionInProgress); Canvas2DLayerManager::get().layerDidDraw(this); m_didRecordDrawCommand = true; } Platform3DObject Canvas2DLayerBridge::getBackingTexture() { ASSERT(!m_destructionInProgress); if (!isValid()) return 0; willUse(); m_canvas->flush(); m_context->flush(); GrRenderTarget* renderTarget = m_canvas->getTopDevice()->accessRenderTarget(); if (renderTarget) { return renderTarget->asTexture()->getTextureHandle(); } return 0; } Canvas2DLayerBridge::MailboxInfo::MailboxInfo(const MailboxInfo& other) { // This copy constructor should only be used for Vector reallocation // Assuming 'other' is to be destroyed, we swap m_image ownership // rather than do a refcount dance. memcpy(&m_mailbox, &other.m_mailbox, sizeof(m_mailbox)); m_image.swap(const_cast*>(&other.m_image)); m_status = other.m_status; } }