blob: d8f5af2e3a56f996eeb413b0c9dee2b8fc267eda [file] [log] [blame]
/*
* Copyright (c) 2010, 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:
*
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 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 "platform/graphics/gpu/DrawingBuffer.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/common/capabilities.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/TraceEvent.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/ImageBuffer.h"
#include "platform/graphics/gpu/Extensions3DUtil.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebExternalBitmap.h"
#include "public/platform/WebExternalTextureLayer.h"
#include "public/platform/WebGraphicsContext3DProvider.h"
#include "wtf/CheckedNumeric.h"
#include "wtf/PtrUtil.h"
#include "wtf/typed_arrays/ArrayBufferContents.h"
#include <algorithm>
#include <memory>
namespace blink {
namespace {
const float s_resourceAdjustedRatio = 0.5;
class ScopedTextureUnit0BindingRestorer {
STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(ScopedTextureUnit0BindingRestorer);
public:
ScopedTextureUnit0BindingRestorer(gpu::gles2::GLES2Interface* gl, GLenum activeTextureUnit, GLuint textureUnitZeroId)
: m_gl(gl)
, m_oldActiveTextureUnit(activeTextureUnit)
, m_oldTextureUnitZeroId(textureUnitZeroId)
{
m_gl->ActiveTexture(GL_TEXTURE0);
}
~ScopedTextureUnit0BindingRestorer()
{
m_gl->BindTexture(GL_TEXTURE_2D, m_oldTextureUnitZeroId);
m_gl->ActiveTexture(m_oldActiveTextureUnit);
}
private:
gpu::gles2::GLES2Interface* m_gl;
GLenum m_oldActiveTextureUnit;
GLuint m_oldTextureUnitZeroId;
};
static bool shouldFailDrawingBufferCreationForTesting = false;
} // namespace
PassRefPtr<DrawingBuffer> DrawingBuffer::create(std::unique_ptr<WebGraphicsContext3DProvider> contextProvider,
const IntSize& size, bool premultipliedAlpha, bool wantAlphaChannel,
bool wantDepthBuffer, bool wantStencilBuffer, bool wantAntialiasing,
PreserveDrawingBuffer preserve, WebGLVersion webGLVersion)
{
ASSERT(contextProvider);
if (shouldFailDrawingBufferCreationForTesting) {
shouldFailDrawingBufferCreationForTesting = false;
return nullptr;
}
std::unique_ptr<Extensions3DUtil> extensionsUtil = Extensions3DUtil::create(contextProvider->contextGL());
if (!extensionsUtil->isValid()) {
// This might be the first time we notice that the GL context is lost.
return nullptr;
}
ASSERT(extensionsUtil->supportsExtension("GL_OES_packed_depth_stencil"));
extensionsUtil->ensureExtensionEnabled("GL_OES_packed_depth_stencil");
bool multisampleSupported = wantAntialiasing
&& (extensionsUtil->supportsExtension("GL_CHROMIUM_framebuffer_multisample")
|| extensionsUtil->supportsExtension("GL_EXT_multisampled_render_to_texture"))
&& extensionsUtil->supportsExtension("GL_OES_rgb8_rgba8");
if (multisampleSupported) {
extensionsUtil->ensureExtensionEnabled("GL_OES_rgb8_rgba8");
if (extensionsUtil->supportsExtension("GL_CHROMIUM_framebuffer_multisample"))
extensionsUtil->ensureExtensionEnabled("GL_CHROMIUM_framebuffer_multisample");
else
extensionsUtil->ensureExtensionEnabled("GL_EXT_multisampled_render_to_texture");
}
bool discardFramebufferSupported = extensionsUtil->supportsExtension("GL_EXT_discard_framebuffer");
if (discardFramebufferSupported)
extensionsUtil->ensureExtensionEnabled("GL_EXT_discard_framebuffer");
RefPtr<DrawingBuffer> drawingBuffer = adoptRef(new DrawingBuffer(std::move(contextProvider), std::move(extensionsUtil),
discardFramebufferSupported, wantAlphaChannel, premultipliedAlpha,
preserve, webGLVersion, wantDepthBuffer, wantStencilBuffer));
if (!drawingBuffer->initialize(size, multisampleSupported)) {
drawingBuffer->beginDestruction();
return PassRefPtr<DrawingBuffer>();
}
return drawingBuffer.release();
}
void DrawingBuffer::forceNextDrawingBufferCreationToFail()
{
shouldFailDrawingBufferCreationForTesting = true;
}
DrawingBuffer::DrawingBuffer(
std::unique_ptr<WebGraphicsContext3DProvider> contextProvider,
std::unique_ptr<Extensions3DUtil> extensionsUtil,
bool discardFramebufferSupported,
bool wantAlphaChannel,
bool premultipliedAlpha,
PreserveDrawingBuffer preserve,
WebGLVersion webGLVersion,
bool wantDepth,
bool wantStencil)
: m_preserveDrawingBuffer(preserve)
, m_webGLVersion(webGLVersion)
, m_contextProvider(std::move(contextProvider))
, m_gl(m_contextProvider->contextGL())
, m_extensionsUtil(std::move(extensionsUtil))
, m_discardFramebufferSupported(discardFramebufferSupported)
, m_wantAlphaChannel(wantAlphaChannel)
, m_premultipliedAlpha(premultipliedAlpha)
, m_wantDepth(wantDepth)
, m_wantStencil(wantStencil)
{
memset(m_colorMask, 0, 4 * sizeof(GLboolean));
memset(m_clearColor, 0, 4 * sizeof(GLfloat));
// Used by browser tests to detect the use of a DrawingBuffer.
TRACE_EVENT_INSTANT0("test_gpu", "DrawingBufferCreation", TRACE_EVENT_SCOPE_GLOBAL);
}
DrawingBuffer::~DrawingBuffer()
{
ASSERT(m_destructionInProgress);
ASSERT(m_textureMailboxes.isEmpty());
m_layer.reset();
m_contextProvider.reset();
}
void DrawingBuffer::markContentsChanged()
{
m_contentsChanged = true;
m_contentsChangeCommitted = false;
}
bool DrawingBuffer::bufferClearNeeded() const
{
return m_bufferClearNeeded;
}
void DrawingBuffer::setBufferClearNeeded(bool flag)
{
if (m_preserveDrawingBuffer == Discard) {
m_bufferClearNeeded = flag;
} else {
ASSERT(!m_bufferClearNeeded);
}
}
gpu::gles2::GLES2Interface* DrawingBuffer::contextGL()
{
return m_gl;
}
WebGraphicsContext3DProvider* DrawingBuffer::contextProvider()
{
return m_contextProvider.get();
}
void DrawingBuffer::setIsHidden(bool hidden)
{
if (m_isHidden == hidden)
return;
m_isHidden = hidden;
if (m_isHidden)
freeRecycledMailboxes();
}
void DrawingBuffer::setFilterQuality(SkFilterQuality filterQuality)
{
if (m_filterQuality != filterQuality) {
m_filterQuality = filterQuality;
if (m_layer)
m_layer->setNearestNeighbor(filterQuality == kNone_SkFilterQuality);
}
}
bool DrawingBuffer::requiresAlphaChannelToBePreserved()
{
return !m_drawFramebufferBinding && defaultBufferRequiresAlphaChannelToBePreserved();
}
bool DrawingBuffer::defaultBufferRequiresAlphaChannelToBePreserved()
{
if (wantExplicitResolve()) {
return !m_wantAlphaChannel && getMultisampledRenderbufferFormat() == GL_RGBA8_OES;
}
bool rgbEmulation = contextProvider()->getCapabilities().emulate_rgb_buffer_with_rgba
|| (RuntimeEnabledFeatures::webGLImageChromiumEnabled() && contextProvider()->getCapabilities().chromium_image_rgb_emulation);
return !m_wantAlphaChannel && rgbEmulation;
}
void DrawingBuffer::freeRecycledMailboxes()
{
if (m_recycledMailboxQueue.isEmpty())
return;
while (!m_recycledMailboxQueue.isEmpty())
deleteMailbox(m_recycledMailboxQueue.takeLast());
}
bool DrawingBuffer::prepareMailbox(WebExternalTextureMailbox* outMailbox, WebExternalBitmap* bitmap)
{
if (m_destructionInProgress) {
// It can be hit in the following sequence.
// 1. WebGL draws something.
// 2. The compositor begins the frame.
// 3. Javascript makes a context lost using WEBGL_lose_context extension.
// 4. Here.
return false;
}
ASSERT(!m_isHidden);
if (!m_contentsChanged)
return false;
if (m_newMailboxCallback)
(*m_newMailboxCallback)();
// Resolve the multisampled buffer into m_colorBuffer texture.
if (m_antiAliasingMode != None)
commit();
if (bitmap) {
bitmap->setSize(size());
unsigned char* pixels = bitmap->pixels();
if (!pixels)
return false;
bool needPremultiply = m_wantAlphaChannel && !m_premultipliedAlpha;
WebGLImageConversion::AlphaOp op = needPremultiply ? WebGLImageConversion::AlphaDoPremultiply : WebGLImageConversion::AlphaDoNothing;
readBackFramebuffer(pixels, size().width(), size().height(), ReadbackSkia, op);
}
// We must restore the texture binding since creating new textures,
// consuming and producing mailboxes changes it.
ScopedTextureUnit0BindingRestorer restorer(m_gl, m_activeTextureUnit, m_texture2DBinding);
// First try to recycle an old buffer.
RefPtr<MailboxInfo> frontColorBufferMailbox = recycledMailbox();
// No buffer available to recycle, create a new one.
if (!frontColorBufferMailbox)
frontColorBufferMailbox = createNewMailbox(createTextureAndAllocateMemory(m_size));
if (m_preserveDrawingBuffer == Discard) {
std::swap(frontColorBufferMailbox->textureInfo, m_colorBuffer);
attachColorBufferToReadFramebuffer();
if (m_discardFramebufferSupported) {
// Explicitly discard framebuffer to save GPU memory bandwidth for tile-based GPU arch.
const GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT };
m_gl->DiscardFramebufferEXT(GL_FRAMEBUFFER, 3, attachments);
}
} else {
m_gl->CopySubTextureCHROMIUM(m_colorBuffer.textureId, frontColorBufferMailbox->textureInfo.textureId,
0, 0, 0, 0, m_size.width(), m_size.height(), GL_FALSE, GL_FALSE, GL_FALSE);
}
restoreFramebufferBindings();
m_contentsChanged = false;
m_gl->ProduceTextureDirectCHROMIUM(frontColorBufferMailbox->textureInfo.textureId, frontColorBufferMailbox->textureInfo.parameters.target, frontColorBufferMailbox->mailbox.name);
const GLuint64 fenceSync = m_gl->InsertFenceSyncCHROMIUM();
if (RuntimeEnabledFeatures::webGLImageChromiumEnabled())
m_gl->DescheduleUntilFinishedCHROMIUM();
m_gl->Flush();
m_gl->GenSyncTokenCHROMIUM(fenceSync, frontColorBufferMailbox->mailbox.syncToken);
frontColorBufferMailbox->mailbox.validSyncToken = true;
frontColorBufferMailbox->mailbox.allowOverlay = frontColorBufferMailbox->textureInfo.imageId != 0;
frontColorBufferMailbox->mailbox.textureTarget = frontColorBufferMailbox->textureInfo.parameters.target;
frontColorBufferMailbox->mailbox.textureSize = WebSize(m_size.width(), m_size.height());
frontColorBufferMailbox->mailbox.gpuMemoryBufferId = frontColorBufferMailbox->textureInfo.gpuMemoryBufferId;
setBufferClearNeeded(true);
// set m_parentDrawingBuffer to make sure 'this' stays alive as long as it has live mailboxes
ASSERT(!frontColorBufferMailbox->m_parentDrawingBuffer);
frontColorBufferMailbox->m_parentDrawingBuffer = this;
*outMailbox = frontColorBufferMailbox->mailbox;
m_frontColorBuffer = { frontColorBufferMailbox->textureInfo, frontColorBufferMailbox->mailbox };
return true;
}
void DrawingBuffer::mailboxReleased(const WebExternalTextureMailbox& mailbox, bool lostResource)
{
if (m_destructionInProgress || m_gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR || lostResource || m_isHidden) {
mailboxReleasedWithoutRecycling(mailbox);
return;
}
for (size_t i = 0; i < m_textureMailboxes.size(); i++) {
RefPtr<MailboxInfo> mailboxInfo = m_textureMailboxes[i];
if (nameEquals(mailboxInfo->mailbox, mailbox)) {
memcpy(mailboxInfo->mailbox.syncToken, mailbox.syncToken,
sizeof(mailboxInfo->mailbox.syncToken));
mailboxInfo->mailbox.validSyncToken = mailbox.validSyncToken;
ASSERT(mailboxInfo->m_parentDrawingBuffer.get() == this);
mailboxInfo->m_parentDrawingBuffer.clear();
m_recycledMailboxQueue.prepend(mailboxInfo->mailbox);
return;
}
}
ASSERT_NOT_REACHED();
}
DrawingBuffer::TextureParameters DrawingBuffer::chromiumImageTextureParameters()
{
#if OS(MACOSX)
// A CHROMIUM_image backed texture requires a specialized set of parameters
// on OSX.
TextureParameters parameters;
parameters.target = GC3D_TEXTURE_RECTANGLE_ARB;
if (m_wantAlphaChannel) {
parameters.creationInternalColorFormat = GL_RGBA;
parameters.internalColorFormat = GL_RGBA;
} else if (contextProvider()->getCapabilities().chromium_image_rgb_emulation) {
parameters.creationInternalColorFormat = GL_RGB;
parameters.internalColorFormat = GL_RGBA;
} else {
GLenum format = defaultBufferRequiresAlphaChannelToBePreserved() ? GL_RGBA : GL_RGB;
parameters.creationInternalColorFormat = format;
parameters.internalColorFormat = format;
}
// Unused when CHROMIUM_image is being used.
parameters.colorFormat = 0;
return parameters;
#else
return defaultTextureParameters();
#endif
}
DrawingBuffer::TextureParameters DrawingBuffer::defaultTextureParameters()
{
TextureParameters parameters;
parameters.target = GL_TEXTURE_2D;
if (m_wantAlphaChannel) {
parameters.internalColorFormat = GL_RGBA;
parameters.creationInternalColorFormat = GL_RGBA;
parameters.colorFormat = GL_RGBA;
} else if (contextProvider()->getCapabilities().emulate_rgb_buffer_with_rgba) {
parameters.internalColorFormat = GL_RGBA;
parameters.creationInternalColorFormat = GL_RGBA;
parameters.colorFormat = GL_RGBA;
} else {
GLenum format = defaultBufferRequiresAlphaChannelToBePreserved() ? GL_RGBA : GL_RGB;
parameters.creationInternalColorFormat = format;
parameters.internalColorFormat = format;
parameters.colorFormat = format;
}
return parameters;
}
void DrawingBuffer::mailboxReleasedWithoutRecycling(const WebExternalTextureMailbox& mailbox)
{
ASSERT(m_textureMailboxes.size());
// Ensure not to call the destructor until deleteMailbox() is completed.
RefPtr<DrawingBuffer> self = this;
deleteMailbox(mailbox);
}
PassRefPtr<DrawingBuffer::MailboxInfo> DrawingBuffer::recycledMailbox()
{
if (m_recycledMailboxQueue.isEmpty())
return PassRefPtr<MailboxInfo>();
// Creation of image backed mailboxes is very expensive, so be less
// aggressive about pruning them.
size_t cacheLimit = 1;
if (RuntimeEnabledFeatures::webGLImageChromiumEnabled())
cacheLimit = 4;
WebExternalTextureMailbox mailbox;
while (m_recycledMailboxQueue.size() > cacheLimit) {
mailbox = m_recycledMailboxQueue.takeLast();
deleteMailbox(mailbox);
}
mailbox = m_recycledMailboxQueue.takeLast();
RefPtr<MailboxInfo> mailboxInfo;
for (size_t i = 0; i < m_textureMailboxes.size(); i++) {
if (nameEquals(m_textureMailboxes[i]->mailbox, mailbox)) {
mailboxInfo = m_textureMailboxes[i];
break;
}
}
ASSERT(mailboxInfo);
if (mailboxInfo->mailbox.validSyncToken) {
m_gl->WaitSyncTokenCHROMIUM(mailboxInfo->mailbox.syncToken);
mailboxInfo->mailbox.validSyncToken = false;
}
if (mailboxInfo->size != m_size) {
resizeTextureMemory(&mailboxInfo->textureInfo, m_size);
mailboxInfo->size = m_size;
}
return mailboxInfo.release();
}
PassRefPtr<DrawingBuffer::MailboxInfo> DrawingBuffer::createNewMailbox(const TextureInfo& info)
{
RefPtr<MailboxInfo> returnMailbox = adoptRef(new MailboxInfo());
m_gl->GenMailboxCHROMIUM(returnMailbox->mailbox.name);
returnMailbox->textureInfo = info;
returnMailbox->size = m_size;
m_textureMailboxes.append(returnMailbox);
return returnMailbox.release();
}
void DrawingBuffer::deleteMailbox(const WebExternalTextureMailbox& mailbox)
{
for (size_t i = 0; i < m_textureMailboxes.size(); i++) {
if (nameEquals(m_textureMailboxes[i]->mailbox, mailbox)) {
if (mailbox.validSyncToken)
m_gl->WaitSyncTokenCHROMIUM(mailbox.syncToken);
deleteChromiumImageForTexture(&m_textureMailboxes[i]->textureInfo);
m_gl->DeleteTextures(1, &m_textureMailboxes[i]->textureInfo.textureId);
m_textureMailboxes.remove(i);
return;
}
}
ASSERT_NOT_REACHED();
}
bool DrawingBuffer::initialize(const IntSize& size, bool useMultisampling)
{
if (m_gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR) {
// Need to try to restore the context again later.
return false;
}
m_gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
int maxSampleCount = 0;
m_antiAliasingMode = None;
if (useMultisampling) {
m_gl->GetIntegerv(GL_MAX_SAMPLES_ANGLE, &maxSampleCount);
m_antiAliasingMode = MSAAExplicitResolve;
if (m_extensionsUtil->supportsExtension("GL_EXT_multisampled_render_to_texture")) {
m_antiAliasingMode = MSAAImplicitResolve;
} else if (m_extensionsUtil->supportsExtension("GL_CHROMIUM_screen_space_antialiasing")) {
m_antiAliasingMode = ScreenSpaceAntialiasing;
}
}
m_storageTextureSupported = m_webGLVersion > WebGL1 || m_extensionsUtil->supportsExtension("GL_EXT_texture_storage");
m_sampleCount = std::min(4, maxSampleCount);
m_gl->GenFramebuffers(1, &m_fbo);
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_fbo);
if (wantExplicitResolve()) {
m_gl->GenFramebuffers(1, &m_multisampleFBO);
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO);
m_gl->GenRenderbuffers(1, &m_multisampleRenderbuffer);
}
if (!reset(size))
return false;
if (m_depthStencilBuffer) {
DCHECK(wantDepthOrStencil());
m_hasImplicitStencilBuffer = !m_wantStencil;
}
if (m_gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR) {
// It's possible that the drawing buffer allocation provokes a context loss, so check again just in case. http://crbug.com/512302
return false;
}
return true;
}
bool DrawingBuffer::copyToPlatformTexture(gpu::gles2::GLES2Interface* gl, GLuint texture, GLenum internalFormat,
GLenum destType, GLint level, bool premultiplyAlpha, bool flipY, SourceDrawingBuffer sourceBuffer)
{
if (m_contentsChanged) {
if (m_antiAliasingMode != None) {
commit();
restoreFramebufferBindings();
}
m_gl->Flush();
}
// Assume that the destination target is GL_TEXTURE_2D.
if (!Extensions3DUtil::canUseCopyTextureCHROMIUM(GL_TEXTURE_2D, internalFormat, destType, level))
return false;
// Contexts may be in a different share group. We must transfer the texture through a mailbox first
WebExternalTextureMailbox mailbox;
GLint textureId = 0;
GLenum target = 0;
if (sourceBuffer == FrontBuffer && m_frontColorBuffer.texInfo.textureId) {
textureId = m_frontColorBuffer.texInfo.textureId;
mailbox = m_frontColorBuffer.mailbox;
target = m_frontColorBuffer.texInfo.parameters.target;
} else {
textureId = m_colorBuffer.textureId;
target = m_colorBuffer.parameters.target;
m_gl->GenMailboxCHROMIUM(mailbox.name);
m_gl->ProduceTextureDirectCHROMIUM(textureId, target, mailbox.name);
const GLuint64 fenceSync = m_gl->InsertFenceSyncCHROMIUM();
m_gl->Flush();
m_gl->GenSyncTokenCHROMIUM(fenceSync, mailbox.syncToken);
mailbox.validSyncToken = true;
}
if (mailbox.validSyncToken)
gl->WaitSyncTokenCHROMIUM(mailbox.syncToken);
GLuint sourceTexture = gl->CreateAndConsumeTextureCHROMIUM(target, mailbox.name);
GLboolean unpackPremultiplyAlphaNeeded = GL_FALSE;
GLboolean unpackUnpremultiplyAlphaNeeded = GL_FALSE;
if (m_wantAlphaChannel && m_premultipliedAlpha && !premultiplyAlpha)
unpackUnpremultiplyAlphaNeeded = GL_TRUE;
else if (m_wantAlphaChannel && !m_premultipliedAlpha && premultiplyAlpha)
unpackPremultiplyAlphaNeeded = GL_TRUE;
gl->CopyTextureCHROMIUM(sourceTexture, texture, internalFormat, destType, flipY, unpackPremultiplyAlphaNeeded, unpackUnpremultiplyAlphaNeeded);
gl->DeleteTextures(1, &sourceTexture);
const GLuint64 fenceSync = gl->InsertFenceSyncCHROMIUM();
gl->Flush();
GLbyte syncToken[24];
gl->GenSyncTokenCHROMIUM(fenceSync, syncToken);
m_gl->WaitSyncTokenCHROMIUM(syncToken);
return true;
}
GLuint DrawingBuffer::framebuffer() const
{
return m_fbo;
}
WebLayer* DrawingBuffer::platformLayer()
{
if (!m_layer) {
m_layer = wrapUnique(Platform::current()->compositorSupport()->createExternalTextureLayer(this));
m_layer->setOpaque(!m_wantAlphaChannel);
m_layer->setBlendBackgroundColor(m_wantAlphaChannel);
m_layer->setPremultipliedAlpha(m_premultipliedAlpha);
m_layer->setNearestNeighbor(m_filterQuality == kNone_SkFilterQuality);
GraphicsLayer::registerContentsLayer(m_layer->layer());
}
return m_layer->layer();
}
void DrawingBuffer::clearPlatformLayer()
{
if (m_layer)
m_layer->clearTexture();
m_gl->Flush();
}
void DrawingBuffer::beginDestruction()
{
ASSERT(!m_destructionInProgress);
m_destructionInProgress = true;
clearPlatformLayer();
while (!m_recycledMailboxQueue.isEmpty())
deleteMailbox(m_recycledMailboxQueue.takeLast());
if (m_multisampleFBO)
m_gl->DeleteFramebuffers(1, &m_multisampleFBO);
if (m_fbo)
m_gl->DeleteFramebuffers(1, &m_fbo);
if (m_multisampleRenderbuffer)
m_gl->DeleteRenderbuffers(1, &m_multisampleRenderbuffer);
if (m_depthStencilBuffer)
m_gl->DeleteRenderbuffers(1, &m_depthStencilBuffer);
if (m_colorBuffer.textureId) {
deleteChromiumImageForTexture(&m_colorBuffer);
m_gl->DeleteTextures(1, &m_colorBuffer.textureId);
}
setSize(IntSize());
m_colorBuffer = TextureInfo();
m_frontColorBuffer = FrontBufferInfo();
m_multisampleRenderbuffer = 0;
m_depthStencilBuffer = 0;
m_multisampleFBO = 0;
m_fbo = 0;
if (m_layer)
GraphicsLayer::unregisterContentsLayer(m_layer->layer());
}
GLuint DrawingBuffer::createColorTexture(const TextureParameters& parameters)
{
GLuint offscreenColorTexture;
m_gl->GenTextures(1, &offscreenColorTexture);
m_gl->BindTexture(parameters.target, offscreenColorTexture);
m_gl->TexParameteri(parameters.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
m_gl->TexParameteri(parameters.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
m_gl->TexParameteri(parameters.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
m_gl->TexParameteri(parameters.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return offscreenColorTexture;
}
bool DrawingBuffer::resizeMultisampleFramebuffer(const IntSize& size)
{
DCHECK(wantExplicitResolve());
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO);
m_gl->BindRenderbuffer(GL_RENDERBUFFER, m_multisampleRenderbuffer);
m_gl->RenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, m_sampleCount, getMultisampledRenderbufferFormat(), size.width(), size.height());
if (m_gl->GetError() == GL_OUT_OF_MEMORY)
return false;
m_gl->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_multisampleRenderbuffer);
return true;
}
void DrawingBuffer::resizeDepthStencil(const IntSize& size)
{
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo);
if (!m_depthStencilBuffer)
m_gl->GenRenderbuffers(1, &m_depthStencilBuffer);
m_gl->BindRenderbuffer(GL_RENDERBUFFER, m_depthStencilBuffer);
if (m_antiAliasingMode == MSAAImplicitResolve)
m_gl->RenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, m_sampleCount, GL_DEPTH24_STENCIL8_OES, size.width(), size.height());
else if (m_antiAliasingMode == MSAAExplicitResolve)
m_gl->RenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, m_sampleCount, GL_DEPTH24_STENCIL8_OES, size.width(), size.height());
else
m_gl->RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, size.width(), size.height());
// For ES 2.0 contexts DEPTH_STENCIL is not available natively, so we emulate it
// at the command buffer level for WebGL contexts.
m_gl->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer);
m_gl->BindRenderbuffer(GL_RENDERBUFFER, 0);
}
bool DrawingBuffer::resizeDefaultFramebuffer(const IntSize& size)
{
// Resize or create m_colorBuffer.
if (m_colorBuffer.textureId) {
resizeTextureMemory(&m_colorBuffer, size);
} else {
m_colorBuffer = createTextureAndAllocateMemory(size);
}
attachColorBufferToReadFramebuffer();
if (wantExplicitResolve()) {
if (!resizeMultisampleFramebuffer(size))
return false;
}
if (wantDepthOrStencil())
resizeDepthStencil(size);
if (wantExplicitResolve()) {
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO);
if (m_gl->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
return false;
}
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_fbo);
return m_gl->CheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
}
void DrawingBuffer::clearFramebuffers(GLbitfield clearMask)
{
// We will clear the multisample FBO, but we also need to clear the non-multisampled buffer.
if (m_multisampleFBO) {
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_fbo);
m_gl->Clear(GL_COLOR_BUFFER_BIT);
}
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo);
m_gl->Clear(clearMask);
}
void DrawingBuffer::setSize(const IntSize& size)
{
if (m_size == size)
return;
m_size = size;
}
IntSize DrawingBuffer::adjustSize(const IntSize& desiredSize, const IntSize& curSize, int maxTextureSize)
{
IntSize adjustedSize = desiredSize;
// Clamp if the desired size is greater than the maximum texture size for the device.
if (adjustedSize.height() > maxTextureSize)
adjustedSize.setHeight(maxTextureSize);
if (adjustedSize.width() > maxTextureSize)
adjustedSize.setWidth(maxTextureSize);
return adjustedSize;
}
bool DrawingBuffer::reset(const IntSize& newSize)
{
CHECK(!newSize.isEmpty());
IntSize adjustedSize = adjustSize(newSize, m_size, m_maxTextureSize);
if (adjustedSize.isEmpty())
return false;
if (adjustedSize != m_size) {
do {
if (!resizeDefaultFramebuffer(adjustedSize)) {
adjustedSize.scale(s_resourceAdjustedRatio);
continue;
}
break;
} while (!adjustedSize.isEmpty());
setSize(adjustedSize);
if (adjustedSize.isEmpty())
return false;
}
m_gl->Disable(GL_SCISSOR_TEST);
m_gl->ClearColor(0, 0, 0, defaultBufferRequiresAlphaChannelToBePreserved() ? 1 : 0);
m_gl->ColorMask(true, true, true, true);
GLbitfield clearMask = GL_COLOR_BUFFER_BIT;
if (!!m_depthStencilBuffer) {
m_gl->ClearDepthf(1.0f);
clearMask |= GL_DEPTH_BUFFER_BIT;
m_gl->DepthMask(true);
}
if (!!m_depthStencilBuffer) {
m_gl->ClearStencil(0);
clearMask |= GL_STENCIL_BUFFER_BIT;
m_gl->StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF);
}
clearFramebuffers(clearMask);
return true;
}
void DrawingBuffer::commit()
{
if (wantExplicitResolve() && !m_contentsChangeCommitted) {
m_gl->BindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, m_multisampleFBO);
m_gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, m_fbo);
if (m_scissorEnabled)
m_gl->Disable(GL_SCISSOR_TEST);
int width = m_size.width();
int height = m_size.height();
// Use NEAREST, because there is no scale performed during the blit.
GLuint filter = GL_NEAREST;
m_gl->BlitFramebufferCHROMIUM(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, filter);
// On old AMD GPUs on OS X, glColorMask doesn't work correctly for
// multisampled renderbuffers and the alpha channel can be overwritten.
// Clear the alpha channel of |m_fbo|.
if (defaultBufferRequiresAlphaChannelToBePreserved()
&& contextProvider()->getCapabilities().disable_multisampling_color_mask_usage) {
m_gl->ClearColor(0, 0, 0, 1);
m_gl->ColorMask(false, false, false, true);
m_gl->Clear(GL_COLOR_BUFFER_BIT);
m_gl->ClearColor(m_clearColor[0], m_clearColor[1], m_clearColor[2], m_clearColor[3]);
m_gl->ColorMask(m_colorMask[0], m_colorMask[1], m_colorMask[2], m_colorMask[3]);
}
if (m_scissorEnabled)
m_gl->Enable(GL_SCISSOR_TEST);
}
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_fbo);
if (m_antiAliasingMode == ScreenSpaceAntialiasing) {
m_gl->ApplyScreenSpaceAntialiasingCHROMIUM();
}
m_contentsChangeCommitted = true;
}
void DrawingBuffer::restoreFramebufferBindings()
{
if (m_drawFramebufferBinding && m_readFramebufferBinding) {
if (m_drawFramebufferBinding == m_readFramebufferBinding) {
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_readFramebufferBinding);
} else {
m_gl->BindFramebuffer(GL_READ_FRAMEBUFFER, m_readFramebufferBinding);
m_gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, m_drawFramebufferBinding);
}
return;
}
if (!m_drawFramebufferBinding && !m_readFramebufferBinding) {
bind(GL_FRAMEBUFFER);
return;
}
if (!m_drawFramebufferBinding) {
bind(GL_DRAW_FRAMEBUFFER);
m_gl->BindFramebuffer(GL_READ_FRAMEBUFFER, m_readFramebufferBinding);
} else {
bind(GL_READ_FRAMEBUFFER);
m_gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, m_drawFramebufferBinding);
}
}
bool DrawingBuffer::multisample() const
{
return m_antiAliasingMode != None;
}
void DrawingBuffer::bind(GLenum target)
{
m_gl->BindFramebuffer(target, wantExplicitResolve() ? m_multisampleFBO : m_fbo);
}
void DrawingBuffer::setPackAlignment(GLint param)
{
m_packAlignment = param;
}
bool DrawingBuffer::paintRenderingResultsToImageData(int& width, int& height, SourceDrawingBuffer sourceBuffer, WTF::ArrayBufferContents& contents)
{
ASSERT(!m_premultipliedAlpha);
width = size().width();
height = size().height();
CheckedNumeric<int> dataSize = 4;
dataSize *= width;
dataSize *= height;
if (!dataSize.IsValid())
return false;
WTF::ArrayBufferContents pixels(width * height, 4, WTF::ArrayBufferContents::NotShared, WTF::ArrayBufferContents::DontInitialize);
GLuint fbo = 0;
if (sourceBuffer == FrontBuffer && m_frontColorBuffer.texInfo.textureId) {
m_gl->GenFramebuffers(1, &fbo);
m_gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
m_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_frontColorBuffer.texInfo.parameters.target, m_frontColorBuffer.texInfo.textureId, 0);
} else {
m_gl->BindFramebuffer(GL_FRAMEBUFFER, framebuffer());
}
readBackFramebuffer(static_cast<unsigned char*>(pixels.data()), width, height, ReadbackRGBA, WebGLImageConversion::AlphaDoNothing);
flipVertically(static_cast<uint8_t*>(pixels.data()), width, height);
if (fbo) {
m_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_frontColorBuffer.texInfo.parameters.target, 0, 0);
m_gl->DeleteFramebuffers(1, &fbo);
}
restoreFramebufferBindings();
pixels.transfer(contents);
return true;
}
void DrawingBuffer::readBackFramebuffer(unsigned char* pixels, int width, int height, ReadbackOrder readbackOrder, WebGLImageConversion::AlphaOp op)
{
if (m_packAlignment > 4)
m_gl->PixelStorei(GL_PACK_ALIGNMENT, 1);
m_gl->ReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
if (m_packAlignment > 4)
m_gl->PixelStorei(GL_PACK_ALIGNMENT, m_packAlignment);
size_t bufferSize = 4 * width * height;
if (readbackOrder == ReadbackSkia) {
#if (SK_R32_SHIFT == 16) && !SK_B32_SHIFT
// Swizzle red and blue channels to match SkBitmap's byte ordering.
// TODO(kbr): expose GL_BGRA as extension.
for (size_t i = 0; i < bufferSize; i += 4) {
std::swap(pixels[i], pixels[i + 2]);
}
#endif
}
if (op == WebGLImageConversion::AlphaDoPremultiply) {
for (size_t i = 0; i < bufferSize; i += 4) {
pixels[i + 0] = std::min(255, pixels[i + 0] * pixels[i + 3] / 255);
pixels[i + 1] = std::min(255, pixels[i + 1] * pixels[i + 3] / 255);
pixels[i + 2] = std::min(255, pixels[i + 2] * pixels[i + 3] / 255);
}
} else if (op != WebGLImageConversion::AlphaDoNothing) {
ASSERT_NOT_REACHED();
}
}
void DrawingBuffer::flipVertically(uint8_t* framebuffer, int width, int height)
{
m_scanline.resize(width * 4);
uint8_t* scanline = &m_scanline[0];
unsigned rowBytes = width * 4;
unsigned count = height / 2;
for (unsigned i = 0; i < count; i++) {
uint8_t* rowA = framebuffer + i * rowBytes;
uint8_t* rowB = framebuffer + (height - i - 1) * rowBytes;
memcpy(scanline, rowB, rowBytes);
memcpy(rowB, rowA, rowBytes);
memcpy(rowA, scanline, rowBytes);
}
}
void DrawingBuffer::allocateConditionallyImmutableTexture(GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type)
{
if (m_storageTextureSupported) {
GLenum internalStorageFormat = GL_NONE;
if (internalformat == GL_RGB) {
internalStorageFormat = GL_RGB8;
} else if (internalformat == GL_RGBA) {
internalStorageFormat = GL_RGBA8;
} else {
NOTREACHED();
}
m_gl->TexStorage2DEXT(GL_TEXTURE_2D, 1, internalStorageFormat, width, height);
return;
}
m_gl->TexImage2D(target, 0, internalformat, width, height, border, format, type, 0);
}
void DrawingBuffer::deleteChromiumImageForTexture(TextureInfo* info)
{
if (info->imageId) {
m_gl->BindTexture(info->parameters.target, info->textureId);
m_gl->ReleaseTexImage2DCHROMIUM(info->parameters.target, info->imageId);
m_gl->DestroyImageCHROMIUM(info->imageId);
info->imageId = 0;
info->gpuMemoryBufferId = -1;
}
}
void DrawingBuffer::clearChromiumImageAlpha(const TextureInfo& info)
{
if (m_wantAlphaChannel)
return;
if (!contextProvider()->getCapabilities().chromium_image_rgb_emulation)
return;
GLuint fbo = 0;
m_gl->GenFramebuffers(1, &fbo);
m_gl->BindFramebuffer(GL_FRAMEBUFFER, fbo);
m_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, info.parameters.target, info.textureId, 0);
m_gl->ClearColor(0, 0, 0, 1);
m_gl->ColorMask(false, false, false, true);
m_gl->Clear(GL_COLOR_BUFFER_BIT);
m_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, info.parameters.target, 0, 0);
m_gl->DeleteFramebuffers(1, &fbo);
restoreFramebufferBindings();
m_gl->ClearColor(m_clearColor[0], m_clearColor[1], m_clearColor[2], m_clearColor[3]);
m_gl->ColorMask(m_colorMask[0], m_colorMask[1], m_colorMask[2], m_colorMask[3]);
}
DrawingBuffer::TextureInfo DrawingBuffer::createTextureAndAllocateMemory(const IntSize& size)
{
if (!RuntimeEnabledFeatures::webGLImageChromiumEnabled())
return createDefaultTextureAndAllocateMemory(size);
TextureParameters parameters = chromiumImageTextureParameters();
GLuint imageId = m_gl->CreateGpuMemoryBufferImageCHROMIUM(size.width(), size.height(), parameters.creationInternalColorFormat, GC3D_SCANOUT_CHROMIUM);
GLint gpuMemoryBufferId = -1;
GLuint textureId = createColorTexture(parameters);
if (imageId) {
m_gl->BindTexImage2DCHROMIUM(parameters.target, imageId);
m_gl->GetImageivCHROMIUM(imageId, GC3D_GPU_MEMORY_BUFFER_ID, &gpuMemoryBufferId);
DCHECK_NE(-1, gpuMemoryBufferId);
}
TextureInfo info;
info.textureId = textureId;
info.imageId = imageId;
info.gpuMemoryBufferId = gpuMemoryBufferId;
info.parameters = parameters;
clearChromiumImageAlpha(info);
return info;
}
DrawingBuffer::TextureInfo DrawingBuffer::createDefaultTextureAndAllocateMemory(const IntSize& size)
{
DrawingBuffer::TextureInfo info;
TextureParameters parameters = defaultTextureParameters();
info.parameters = parameters;
info.textureId = createColorTexture(parameters);
allocateConditionallyImmutableTexture(parameters.target, parameters.creationInternalColorFormat,
size.width(), size.height(), 0, parameters.colorFormat, GL_UNSIGNED_BYTE);
info.immutable = m_storageTextureSupported;
return info;
}
void DrawingBuffer::resizeTextureMemory(TextureInfo* info, const IntSize& size)
{
ASSERT(info->textureId);
if (!RuntimeEnabledFeatures::webGLImageChromiumEnabled()) {
if (info->immutable) {
DCHECK(m_storageTextureSupported);
m_gl->DeleteTextures(1, &info->textureId);
info->textureId = createColorTexture(info->parameters);
}
m_gl->BindTexture(info->parameters.target, info->textureId);
allocateConditionallyImmutableTexture(info->parameters.target, info->parameters.creationInternalColorFormat,
size.width(), size.height(), 0, info->parameters.colorFormat, GL_UNSIGNED_BYTE);
info->immutable = m_storageTextureSupported;
return;
}
DCHECK(!info->immutable);
deleteChromiumImageForTexture(info);
info->imageId = m_gl->CreateGpuMemoryBufferImageCHROMIUM(size.width(), size.height(), info->parameters.creationInternalColorFormat, GC3D_SCANOUT_CHROMIUM);
if (info->imageId) {
m_gl->BindTexture(info->parameters.target, info->textureId);
m_gl->BindTexImage2DCHROMIUM(info->parameters.target, info->imageId);
GLint gpuMemoryBufferId = -1;
m_gl->GetImageivCHROMIUM(info->imageId, GC3D_GPU_MEMORY_BUFFER_ID, &gpuMemoryBufferId);
DCHECK_NE(-1, gpuMemoryBufferId);
info->gpuMemoryBufferId = gpuMemoryBufferId;
clearChromiumImageAlpha(*info);
} else {
info->gpuMemoryBufferId = -1;
// At this point, the texture still exists, but has no allocated
// storage. This is intentional, and mimics the behavior of a texImage2D
// failure.
}
}
void DrawingBuffer::attachColorBufferToReadFramebuffer()
{
m_gl->BindFramebuffer(GL_FRAMEBUFFER, m_fbo);
GLenum target = m_colorBuffer.parameters.target;
GLenum id = m_colorBuffer.textureId;
m_gl->BindTexture(target, id);
if (m_antiAliasingMode == MSAAImplicitResolve)
m_gl->FramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, id, 0, m_sampleCount);
else
m_gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, id, 0);
restoreTextureBindings();
restoreFramebufferBindings();
}
bool DrawingBuffer::wantExplicitResolve()
{
return m_antiAliasingMode == MSAAExplicitResolve;
}
bool DrawingBuffer::wantDepthOrStencil()
{
return m_wantDepth || m_wantStencil;
}
GLenum DrawingBuffer::getMultisampledRenderbufferFormat()
{
DCHECK(wantExplicitResolve());
if (m_wantAlphaChannel)
return GL_RGBA8_OES;
if (RuntimeEnabledFeatures::webGLImageChromiumEnabled() && contextProvider()->getCapabilities().chromium_image_rgb_emulation)
return GL_RGBA8_OES;
if (contextProvider()->getCapabilities().disable_webgl_rgb_multisampling_usage)
return GL_RGBA8_OES;
return GL_RGB8_OES;
}
void DrawingBuffer::restoreTextureBindings()
{
// This class potentially modifies the bindings for GL_TEXTURE_2D and
// GL_TEXTURE_RECTANGLE. Only GL_TEXTURE_2D needs to be restored since
// the public interface for WebGL does not support GL_TEXTURE_RECTANGLE.
m_gl->BindTexture(GL_TEXTURE_2D, m_texture2DBinding);
}
} // namespace blink