blob: 91f024b0ccba423fc70b78e1f9980ab9ebb2e13f [file] [log] [blame]
/*
* 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<SkSurface> 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> Canvas2DLayerBridge::create(const IntSize& size, OpacityMode opacityMode, int msaaSampleCount)
{
TRACE_EVENT_INSTANT0("test_gpu", "Canvas2DLayerBridgeCreation");
RefPtr<GraphicsContext3D> context = SharedGraphicsContext3D::get();
RefPtr<SkSurface> surface(createSkSurface(context.get(), size, msaaSampleCount));
if (!surface)
return 0;
RefPtr<Canvas2DLayerBridge> layerBridge;
OwnPtr<SkDeferredCanvas> canvas = adoptPtr(SkDeferredCanvas::Create(surface.get()));
layerBridge = adoptRef(new Canvas2DLayerBridge(context, canvas.release(), msaaSampleCount, opacityMode));
return layerBridge.release();
}
Canvas2DLayerBridge::Canvas2DLayerBridge(PassRefPtr<GraphicsContext3D> context, PassOwnPtr<SkDeferredCanvas> 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<MailboxInfo>::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<GraphicsContext3D> 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<SkSurface> 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
// or in Android WebView in software mode. In this case, we do
// not care about producing any results for this canvas.
m_canvas->silentFlush();
m_lastImageId = 0;
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<MailboxInfo>::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<SkImage> 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<MailboxInfo>::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<SkAutoTUnref<SkImage>*>(&other.m_image));
m_status = other.m_status;
}
}