| /* |
| * 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 "config.h" |
| |
| #include "platform/graphics/gpu/DrawingBuffer.h" |
| |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include <algorithm> |
| #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/WebGraphicsContext3D.h" |
| #include "public/platform/WebGraphicsContext3DProvider.h" |
| #ifndef NDEBUG |
| #include "wtf/RefCountedLeakCounter.h" |
| #endif |
| |
| namespace blink { |
| |
| namespace { |
| // Global resource ceiling (expressed in terms of pixels) for DrawingBuffer creation and resize. |
| // When this limit is set, DrawingBuffer::create() and DrawingBuffer::reset() calls that would |
| // exceed the global cap will instead clear the buffer. |
| const int s_maximumResourceUsePixels = 16 * 1024 * 1024; |
| int s_currentResourceUsePixels = 0; |
| const float s_resourceAdjustedRatio = 0.5; |
| |
| const bool s_allowContextEvictionOnCreate = true; |
| const int s_maxScaleAttempts = 3; |
| |
| DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, drawingBufferCounter, ("DrawingBuffer")); |
| |
| class ScopedTextureUnit0BindingRestorer { |
| public: |
| ScopedTextureUnit0BindingRestorer(WebGraphicsContext3D* context, GLenum activeTextureUnit, Platform3DObject textureUnitZeroId) |
| : m_context(context) |
| , m_oldActiveTextureUnit(activeTextureUnit) |
| , m_oldTextureUnitZeroId(textureUnitZeroId) |
| { |
| m_context->activeTexture(GL_TEXTURE0); |
| } |
| ~ScopedTextureUnit0BindingRestorer() |
| { |
| m_context->bindTexture(GL_TEXTURE_2D, m_oldTextureUnitZeroId); |
| m_context->activeTexture(m_oldActiveTextureUnit); |
| } |
| |
| private: |
| WebGraphicsContext3D* m_context; |
| GLenum m_oldActiveTextureUnit; |
| Platform3DObject m_oldTextureUnitZeroId; |
| }; |
| |
| } // namespace |
| |
| PassRefPtr<DrawingBuffer> DrawingBuffer::create(PassOwnPtr<WebGraphicsContext3D> context, const IntSize& size, PreserveDrawingBuffer preserve, WebGraphicsContext3D::Attributes requestedAttributes, PassRefPtr<ContextEvictionManager> contextEvictionManager) |
| { |
| ASSERT(context); |
| OwnPtr<Extensions3DUtil> extensionsUtil = Extensions3DUtil::create(context.get()); |
| if (!extensionsUtil) { |
| // This might be the first time we notice that the WebGraphicsContext3D is lost. |
| return nullptr; |
| } |
| bool multisampleSupported = (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 packedDepthStencilSupported = extensionsUtil->supportsExtension("GL_OES_packed_depth_stencil"); |
| if (packedDepthStencilSupported) |
| extensionsUtil->ensureExtensionEnabled("GL_OES_packed_depth_stencil"); |
| bool discardFramebufferSupported = extensionsUtil->supportsExtension("GL_EXT_discard_framebuffer"); |
| if (discardFramebufferSupported) |
| extensionsUtil->ensureExtensionEnabled("GL_EXT_discard_framebuffer"); |
| |
| RefPtr<DrawingBuffer> drawingBuffer = adoptRef(new DrawingBuffer(context, extensionsUtil.release(), multisampleSupported, packedDepthStencilSupported, discardFramebufferSupported, preserve, requestedAttributes, contextEvictionManager)); |
| if (!drawingBuffer->initialize(size)) { |
| drawingBuffer->beginDestruction(); |
| return PassRefPtr<DrawingBuffer>(); |
| } |
| return drawingBuffer.release(); |
| } |
| |
| DrawingBuffer::DrawingBuffer(PassOwnPtr<WebGraphicsContext3D> context, |
| PassOwnPtr<Extensions3DUtil> extensionsUtil, |
| bool multisampleExtensionSupported, |
| bool packedDepthStencilExtensionSupported, |
| bool discardFramebufferSupported, |
| PreserveDrawingBuffer preserve, |
| WebGraphicsContext3D::Attributes requestedAttributes, |
| PassRefPtr<ContextEvictionManager> contextEvictionManager) |
| : m_preserveDrawingBuffer(preserve) |
| , m_scissorEnabled(false) |
| , m_texture2DBinding(0) |
| , m_framebufferBinding(0) |
| , m_activeTextureUnit(GL_TEXTURE0) |
| , m_context(context) |
| , m_extensionsUtil(extensionsUtil) |
| , m_size(-1, -1) |
| , m_requestedAttributes(requestedAttributes) |
| , m_multisampleExtensionSupported(multisampleExtensionSupported) |
| , m_packedDepthStencilExtensionSupported(packedDepthStencilExtensionSupported) |
| , m_discardFramebufferSupported(discardFramebufferSupported) |
| , m_fbo(0) |
| , m_depthStencilBuffer(0) |
| , m_depthBuffer(0) |
| , m_stencilBuffer(0) |
| , m_multisampleFBO(0) |
| , m_multisampleColorBuffer(0) |
| , m_contentsChanged(true) |
| , m_contentsChangeCommitted(false) |
| , m_layerComposited(false) |
| , m_multisampleMode(None) |
| , m_internalColorFormat(0) |
| , m_colorFormat(0) |
| , m_internalRenderbufferFormat(0) |
| , m_maxTextureSize(0) |
| , m_sampleCount(0) |
| , m_packAlignment(4) |
| , m_destructionInProgress(false) |
| , m_isHidden(false) |
| , m_contextEvictionManager(contextEvictionManager) |
| { |
| // Used by browser tests to detect the use of a DrawingBuffer. |
| TRACE_EVENT_INSTANT0("test_gpu", "DrawingBufferCreation"); |
| #ifndef NDEBUG |
| drawingBufferCounter.increment(); |
| #endif |
| } |
| |
| DrawingBuffer::~DrawingBuffer() |
| { |
| ASSERT(m_destructionInProgress); |
| ASSERT(m_textureMailboxes.isEmpty()); |
| m_layer.clear(); |
| m_context.clear(); |
| #ifndef NDEBUG |
| drawingBufferCounter.decrement(); |
| #endif |
| } |
| |
| void DrawingBuffer::markContentsChanged() |
| { |
| m_contentsChanged = true; |
| m_contentsChangeCommitted = false; |
| m_layerComposited = false; |
| } |
| |
| bool DrawingBuffer::layerComposited() const |
| { |
| return m_layerComposited; |
| } |
| |
| void DrawingBuffer::markLayerComposited() |
| { |
| m_layerComposited = true; |
| } |
| |
| WebGraphicsContext3D* DrawingBuffer::context() |
| { |
| return m_context.get(); |
| } |
| |
| void DrawingBuffer::setIsHidden(bool hidden) |
| { |
| if (m_isHidden == hidden) |
| return; |
| m_isHidden = hidden; |
| if (m_isHidden) |
| freeRecycledMailboxes(); |
| } |
| |
| 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; |
| |
| // Resolve the multisampled buffer into m_colorBuffer texture. |
| if (m_multisampleMode != None) |
| commit(); |
| |
| if (bitmap) { |
| bitmap->setSize(size()); |
| |
| unsigned char* pixels = bitmap->pixels(); |
| bool needPremultiply = m_actualAttributes.alpha && !m_actualAttributes.premultipliedAlpha; |
| WebGLImageConversion::AlphaOp op = needPremultiply ? WebGLImageConversion::AlphaDoPremultiply : WebGLImageConversion::AlphaDoNothing; |
| if (pixels) |
| 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_context.get(), 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) { |
| TextureInfo newTexture; |
| newTexture.textureId = createColorTexture(); |
| allocateTextureMemory(&newTexture, m_size); |
| // Bad things happened, abandon ship. |
| if (!newTexture.textureId) |
| return false; |
| |
| frontColorBufferMailbox = createNewMailbox(newTexture); |
| } |
| |
| if (m_preserveDrawingBuffer == Discard) { |
| std::swap(frontColorBufferMailbox->textureInfo, m_colorBuffer); |
| // It appears safe to overwrite the context's framebuffer binding in the Discard case since there will always be a |
| // WebGLRenderingContext::clearIfComposited() call made before the next draw call which restores the framebuffer binding. |
| // If this stops being true at some point, we should track the current framebuffer binding in the DrawingBuffer and restore |
| // it after attaching the new back buffer here. |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo); |
| if (m_multisampleMode == ImplicitResolve) |
| m_context->framebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0, m_sampleCount); |
| else |
| m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0); |
| |
| if (m_discardFramebufferSupported) { |
| // Explicitly discard framebuffer to save GPU memory bandwidth for tile-based GPU arch. |
| const WGC3Denum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}; |
| m_context->discardFramebufferEXT(GL_FRAMEBUFFER, 3, attachments); |
| } |
| } else { |
| m_context->copyTextureCHROMIUM(GL_TEXTURE_2D, m_colorBuffer.textureId, frontColorBufferMailbox->textureInfo.textureId, 0, GL_RGBA, GL_UNSIGNED_BYTE); |
| } |
| |
| if (m_multisampleMode != None && !m_framebufferBinding) |
| bind(); |
| else |
| restoreFramebufferBinding(); |
| |
| m_contentsChanged = false; |
| |
| m_context->produceTextureDirectCHROMIUM(frontColorBufferMailbox->textureInfo.textureId, GL_TEXTURE_2D, frontColorBufferMailbox->mailbox.name); |
| m_context->flush(); |
| frontColorBufferMailbox->mailbox.syncPoint = m_context->insertSyncPoint(); |
| frontColorBufferMailbox->mailbox.allowOverlay = frontColorBufferMailbox->textureInfo.imageId != 0; |
| markLayerComposited(); |
| |
| // 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_context->isContextLost() || 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)) { |
| mailboxInfo->mailbox.syncPoint = mailbox.syncPoint; |
| ASSERT(mailboxInfo->m_parentDrawingBuffer.get() == this); |
| mailboxInfo->m_parentDrawingBuffer.clear(); |
| m_recycledMailboxQueue.prepend(mailboxInfo->mailbox); |
| return; |
| } |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| 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>(); |
| |
| WebExternalTextureMailbox mailbox; |
| while (!m_recycledMailboxQueue.isEmpty()) { |
| mailbox = m_recycledMailboxQueue.takeLast(); |
| // Never have more than one mailbox in the released state. |
| if (!m_recycledMailboxQueue.isEmpty()) |
| deleteMailbox(mailbox); |
| } |
| |
| 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.syncPoint) { |
| m_context->waitSyncPoint(mailboxInfo->mailbox.syncPoint); |
| mailboxInfo->mailbox.syncPoint = 0; |
| } |
| |
| if (mailboxInfo->size != m_size) { |
| m_context->bindTexture(GL_TEXTURE_2D, mailboxInfo->textureInfo.textureId); |
| allocateTextureMemory(&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_context->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.syncPoint) |
| m_context->waitSyncPoint(mailbox.syncPoint); |
| |
| deleteChromiumImageForTexture(&m_textureMailboxes[i]->textureInfo); |
| |
| m_context->deleteTexture(m_textureMailboxes[i]->textureInfo.textureId); |
| m_textureMailboxes.remove(i); |
| return; |
| } |
| } |
| ASSERT_NOT_REACHED(); |
| } |
| |
| bool DrawingBuffer::initialize(const IntSize& size) |
| { |
| if (m_context->isContextLost()) { |
| // Need to try to restore the context again later. |
| return false; |
| } |
| |
| if (m_requestedAttributes.alpha) { |
| m_internalColorFormat = GL_RGBA; |
| m_colorFormat = GL_RGBA; |
| m_internalRenderbufferFormat = GL_RGBA8_OES; |
| } else { |
| m_internalColorFormat = GL_RGB; |
| m_colorFormat = GL_RGB; |
| m_internalRenderbufferFormat = GL_RGB8_OES; |
| } |
| |
| m_context->getIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize); |
| |
| int maxSampleCount = 0; |
| m_multisampleMode = None; |
| if (m_requestedAttributes.antialias && m_multisampleExtensionSupported) { |
| m_context->getIntegerv(GL_MAX_SAMPLES_ANGLE, &maxSampleCount); |
| m_multisampleMode = ExplicitResolve; |
| if (m_extensionsUtil->supportsExtension("GL_EXT_multisampled_render_to_texture")) |
| m_multisampleMode = ImplicitResolve; |
| } |
| m_sampleCount = std::min(4, maxSampleCount); |
| |
| m_fbo = m_context->createFramebuffer(); |
| |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo); |
| m_colorBuffer.textureId = createColorTexture(); |
| if (m_multisampleMode == ImplicitResolve) |
| m_context->framebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0, m_sampleCount); |
| else |
| m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0); |
| createSecondaryBuffers(); |
| // We first try to initialize everything with the requested attributes. |
| if (!reset(size)) |
| return false; |
| // If that succeeds, we then see what we actually got and update our actual attributes to reflect that. |
| m_actualAttributes = m_requestedAttributes; |
| if (m_requestedAttributes.alpha) { |
| WGC3Dint alphaBits = 0; |
| m_context->getIntegerv(GL_ALPHA_BITS, &alphaBits); |
| m_actualAttributes.alpha = alphaBits > 0; |
| } |
| if (m_requestedAttributes.depth) { |
| WGC3Dint depthBits = 0; |
| m_context->getIntegerv(GL_DEPTH_BITS, &depthBits); |
| m_actualAttributes.depth = depthBits > 0; |
| } |
| if (m_requestedAttributes.stencil) { |
| WGC3Dint stencilBits = 0; |
| m_context->getIntegerv(GL_STENCIL_BITS, &stencilBits); |
| m_actualAttributes.stencil = stencilBits > 0; |
| } |
| m_actualAttributes.antialias = multisample(); |
| return true; |
| } |
| |
| bool DrawingBuffer::copyToPlatformTexture(WebGraphicsContext3D* context, Platform3DObject texture, GLenum internalFormat, |
| GLenum destType, GLint level, bool premultiplyAlpha, bool flipY, SourceBuffer source) |
| { |
| if (m_contentsChanged) { |
| if (m_multisampleMode != None) { |
| commit(); |
| if (!m_framebufferBinding) |
| bind(); |
| else |
| restoreFramebufferBinding(); |
| } |
| m_context->flush(); |
| } |
| |
| if (!Extensions3DUtil::canUseCopyTextureCHROMIUM(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; |
| if (source == Front && m_frontColorBuffer.texInfo.textureId) { |
| textureId = m_frontColorBuffer.texInfo.textureId; |
| mailbox = m_frontColorBuffer.mailbox; |
| } else { |
| textureId = m_colorBuffer.textureId; |
| m_context->genMailboxCHROMIUM(mailbox.name); |
| m_context->produceTextureDirectCHROMIUM(textureId, GL_TEXTURE_2D, mailbox.name); |
| m_context->flush(); |
| mailbox.syncPoint = m_context->insertSyncPoint(); |
| } |
| |
| context->waitSyncPoint(mailbox.syncPoint); |
| Platform3DObject sourceTexture = context->createAndConsumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name); |
| |
| bool unpackPremultiplyAlphaNeeded = false; |
| bool unpackUnpremultiplyAlphaNeeded = false; |
| if (m_actualAttributes.alpha && m_actualAttributes.premultipliedAlpha && !premultiplyAlpha) |
| unpackUnpremultiplyAlphaNeeded = true; |
| else if (m_actualAttributes.alpha && !m_actualAttributes.premultipliedAlpha && premultiplyAlpha) |
| unpackPremultiplyAlphaNeeded = true; |
| |
| context->pixelStorei(GC3D_UNPACK_UNPREMULTIPLY_ALPHA_CHROMIUM, unpackUnpremultiplyAlphaNeeded); |
| context->pixelStorei(GC3D_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM, unpackPremultiplyAlphaNeeded); |
| context->pixelStorei(GC3D_UNPACK_FLIP_Y_CHROMIUM, flipY); |
| context->copyTextureCHROMIUM(GL_TEXTURE_2D, sourceTexture, texture, level, internalFormat, destType); |
| context->pixelStorei(GC3D_UNPACK_FLIP_Y_CHROMIUM, false); |
| context->pixelStorei(GC3D_UNPACK_UNPREMULTIPLY_ALPHA_CHROMIUM, false); |
| context->pixelStorei(GC3D_UNPACK_PREMULTIPLY_ALPHA_CHROMIUM, false); |
| |
| context->deleteTexture(sourceTexture); |
| |
| context->flush(); |
| m_context->waitSyncPoint(context->insertSyncPoint()); |
| |
| return true; |
| } |
| |
| Platform3DObject DrawingBuffer::framebuffer() const |
| { |
| return m_fbo; |
| } |
| |
| WebLayer* DrawingBuffer::platformLayer() |
| { |
| if (!m_layer) { |
| m_layer = adoptPtr(Platform::current()->compositorSupport()->createExternalTextureLayer(this)); |
| |
| m_layer->setOpaque(!m_actualAttributes.alpha); |
| m_layer->setBlendBackgroundColor(m_actualAttributes.alpha); |
| m_layer->setPremultipliedAlpha(m_actualAttributes.premultipliedAlpha); |
| GraphicsLayer::registerContentsLayer(m_layer->layer()); |
| } |
| |
| return m_layer->layer(); |
| } |
| |
| void DrawingBuffer::clearPlatformLayer() |
| { |
| if (m_layer) |
| m_layer->clearTexture(); |
| |
| m_context->flush(); |
| } |
| |
| void DrawingBuffer::beginDestruction() |
| { |
| ASSERT(!m_destructionInProgress); |
| m_destructionInProgress = true; |
| |
| clearPlatformLayer(); |
| |
| while (!m_recycledMailboxQueue.isEmpty()) |
| deleteMailbox(m_recycledMailboxQueue.takeLast()); |
| |
| if (m_multisampleFBO) |
| m_context->deleteFramebuffer(m_multisampleFBO); |
| |
| if (m_fbo) |
| m_context->deleteFramebuffer(m_fbo); |
| |
| if (m_multisampleColorBuffer) |
| m_context->deleteRenderbuffer(m_multisampleColorBuffer); |
| |
| if (m_depthStencilBuffer) |
| m_context->deleteRenderbuffer(m_depthStencilBuffer); |
| |
| if (m_depthBuffer) |
| m_context->deleteRenderbuffer(m_depthBuffer); |
| |
| if (m_stencilBuffer) |
| m_context->deleteRenderbuffer(m_stencilBuffer); |
| |
| if (m_colorBuffer.textureId) { |
| deleteChromiumImageForTexture(&m_colorBuffer); |
| m_context->deleteTexture(m_colorBuffer.textureId); |
| } |
| |
| setSize(IntSize()); |
| |
| m_colorBuffer = TextureInfo(); |
| m_frontColorBuffer = FrontBufferInfo(); |
| m_multisampleColorBuffer = 0; |
| m_depthStencilBuffer = 0; |
| m_depthBuffer = 0; |
| m_stencilBuffer = 0; |
| m_multisampleFBO = 0; |
| m_fbo = 0; |
| m_contextEvictionManager.clear(); |
| |
| if (m_layer) |
| GraphicsLayer::unregisterContentsLayer(m_layer->layer()); |
| } |
| |
| unsigned DrawingBuffer::createColorTexture() |
| { |
| unsigned offscreenColorTexture = m_context->createTexture(); |
| if (!offscreenColorTexture) |
| return 0; |
| |
| m_context->bindTexture(GL_TEXTURE_2D, offscreenColorTexture); |
| 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); |
| |
| return offscreenColorTexture; |
| } |
| |
| void DrawingBuffer::createSecondaryBuffers() |
| { |
| // create a multisample FBO |
| if (m_multisampleMode == ExplicitResolve) { |
| m_multisampleFBO = m_context->createFramebuffer(); |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO); |
| m_multisampleColorBuffer = m_context->createRenderbuffer(); |
| } |
| } |
| |
| bool DrawingBuffer::resizeFramebuffer(const IntSize& size) |
| { |
| // resize regular FBO |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo); |
| |
| m_context->bindTexture(GL_TEXTURE_2D, m_colorBuffer.textureId); |
| |
| allocateTextureMemory(&m_colorBuffer, size); |
| |
| if (m_multisampleMode == ImplicitResolve) |
| m_context->framebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0, m_sampleCount); |
| else |
| m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0); |
| |
| m_context->bindTexture(GL_TEXTURE_2D, 0); |
| |
| if (m_multisampleMode != ExplicitResolve) |
| resizeDepthStencil(size); |
| if (m_context->checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) |
| return false; |
| |
| return true; |
| } |
| |
| bool DrawingBuffer::resizeMultisampleFramebuffer(const IntSize& size) |
| { |
| if (m_multisampleMode == ExplicitResolve) { |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO); |
| |
| m_context->bindRenderbuffer(GL_RENDERBUFFER, m_multisampleColorBuffer); |
| m_context->renderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, m_sampleCount, m_internalRenderbufferFormat, size.width(), size.height()); |
| |
| if (m_context->getError() == GL_OUT_OF_MEMORY) |
| return false; |
| |
| m_context->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_multisampleColorBuffer); |
| resizeDepthStencil(size); |
| if (m_context->checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void DrawingBuffer::resizeDepthStencil(const IntSize& size) |
| { |
| if (!m_requestedAttributes.depth && !m_requestedAttributes.stencil) |
| return; |
| |
| if (m_packedDepthStencilExtensionSupported) { |
| if (!m_depthStencilBuffer) |
| m_depthStencilBuffer = m_context->createRenderbuffer(); |
| m_context->bindRenderbuffer(GL_RENDERBUFFER, m_depthStencilBuffer); |
| if (m_multisampleMode == ImplicitResolve) |
| m_context->renderbufferStorageMultisampleEXT(GL_RENDERBUFFER, m_sampleCount, GL_DEPTH24_STENCIL8_OES, size.width(), size.height()); |
| else if (m_multisampleMode == ExplicitResolve) |
| m_context->renderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, m_sampleCount, GL_DEPTH24_STENCIL8_OES, size.width(), size.height()); |
| else |
| m_context->renderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, size.width(), size.height()); |
| m_context->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer); |
| m_context->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthStencilBuffer); |
| } else { |
| if (m_requestedAttributes.depth) { |
| if (!m_depthBuffer) |
| m_depthBuffer = m_context->createRenderbuffer(); |
| m_context->bindRenderbuffer(GL_RENDERBUFFER, m_depthBuffer); |
| if (m_multisampleMode == ImplicitResolve) |
| m_context->renderbufferStorageMultisampleEXT(GL_RENDERBUFFER, m_sampleCount, GL_DEPTH_COMPONENT16, size.width(), size.height()); |
| else if (m_multisampleMode == ExplicitResolve) |
| m_context->renderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, m_sampleCount, GL_DEPTH_COMPONENT16, size.width(), size.height()); |
| else |
| m_context->renderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width(), size.height()); |
| m_context->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBuffer); |
| } |
| if (m_requestedAttributes.stencil) { |
| if (!m_stencilBuffer) |
| m_stencilBuffer = m_context->createRenderbuffer(); |
| m_context->bindRenderbuffer(GL_RENDERBUFFER, m_stencilBuffer); |
| if (m_multisampleMode == ImplicitResolve) |
| m_context->renderbufferStorageMultisampleEXT(GL_RENDERBUFFER, m_sampleCount, GL_STENCIL_INDEX8, size.width(), size.height()); |
| else if (m_multisampleMode == ExplicitResolve) |
| m_context->renderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, m_sampleCount, GL_STENCIL_INDEX8, size.width(), size.height()); |
| else |
| m_context->renderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, size.width(), size.height()); |
| m_context->framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_stencilBuffer); |
| } |
| } |
| m_context->bindRenderbuffer(GL_RENDERBUFFER, 0); |
| } |
| |
| |
| |
| 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_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo); |
| m_context->clear(GL_COLOR_BUFFER_BIT); |
| } |
| |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo); |
| m_context->clear(clearMask); |
| } |
| |
| void DrawingBuffer::setSize(const IntSize& size) |
| { |
| if (m_size == size) |
| return; |
| |
| s_currentResourceUsePixels += pixelDelta(size, m_size); |
| m_size = size; |
| } |
| |
| int DrawingBuffer::pixelDelta(const IntSize& newSize, const IntSize& curSize) |
| { |
| return (std::max(0, newSize.width()) * std::max(0, newSize.height())) - (std::max(0, curSize.width()) * std::max(0, curSize.height())); |
| } |
| |
| 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); |
| |
| // Try progressively smaller sizes until we find a size that fits or reach a scale limit. |
| int scaleAttempts = 0; |
| while ((s_currentResourceUsePixels + pixelDelta(adjustedSize, curSize)) > s_maximumResourceUsePixels) { |
| scaleAttempts++; |
| if (scaleAttempts > s_maxScaleAttempts) |
| return IntSize(); |
| |
| adjustedSize.scale(s_resourceAdjustedRatio); |
| |
| if (adjustedSize.isEmpty()) |
| return IntSize(); |
| } |
| |
| return adjustedSize; |
| } |
| |
| IntSize DrawingBuffer::adjustSizeWithContextEviction(const IntSize& size, bool& evictContext) |
| { |
| IntSize adjustedSize = adjustSize(size, m_size, m_maxTextureSize); |
| if (!adjustedSize.isEmpty()) { |
| evictContext = false; |
| return adjustedSize; // Buffer fits without evicting a context. |
| } |
| |
| // Speculatively adjust the pixel budget to see if the buffer would fit should the oldest context be evicted. |
| IntSize oldestSize = m_contextEvictionManager->oldestContextSize(); |
| int pixelDelta = oldestSize.width() * oldestSize.height(); |
| |
| s_currentResourceUsePixels -= pixelDelta; |
| adjustedSize = adjustSize(size, m_size, m_maxTextureSize); |
| s_currentResourceUsePixels += pixelDelta; |
| |
| evictContext = !adjustedSize.isEmpty(); |
| return adjustedSize; |
| } |
| |
| bool DrawingBuffer::reset(const IntSize& newSize) |
| { |
| ASSERT(!newSize.isEmpty()); |
| IntSize adjustedSize; |
| bool evictContext = false; |
| bool isNewContext = m_size.isEmpty(); |
| if (s_allowContextEvictionOnCreate && isNewContext) |
| adjustedSize = adjustSizeWithContextEviction(newSize, evictContext); |
| else |
| adjustedSize = adjustSize(newSize, m_size, m_maxTextureSize); |
| |
| if (adjustedSize.isEmpty()) |
| return false; |
| |
| if (evictContext) |
| m_contextEvictionManager->forciblyLoseOldestContext("WARNING: WebGL contexts have exceeded the maximum allowed backbuffer area. Oldest context will be lost."); |
| |
| if (adjustedSize != m_size) { |
| do { |
| // resize multisample FBO |
| if (!resizeMultisampleFramebuffer(adjustedSize) || !resizeFramebuffer(adjustedSize)) { |
| adjustedSize.scale(s_resourceAdjustedRatio); |
| continue; |
| } |
| break; |
| } while (!adjustedSize.isEmpty()); |
| |
| setSize(adjustedSize); |
| |
| if (adjustedSize.isEmpty()) |
| return false; |
| } |
| |
| m_context->disable(GL_SCISSOR_TEST); |
| m_context->clearColor(0, 0, 0, 0); |
| m_context->colorMask(true, true, true, true); |
| |
| GLbitfield clearMask = GL_COLOR_BUFFER_BIT; |
| if (m_actualAttributes.depth) { |
| m_context->clearDepth(1.0f); |
| clearMask |= GL_DEPTH_BUFFER_BIT; |
| m_context->depthMask(true); |
| } |
| if (m_actualAttributes.stencil) { |
| m_context->clearStencil(0); |
| clearMask |= GL_STENCIL_BUFFER_BIT; |
| m_context->stencilMaskSeparate(GL_FRONT, 0xFFFFFFFF); |
| } |
| |
| clearFramebuffers(clearMask); |
| return true; |
| } |
| |
| void DrawingBuffer::commit(long x, long y, long width, long height) |
| { |
| if (width < 0) |
| width = m_size.width(); |
| if (height < 0) |
| height = m_size.height(); |
| |
| if (m_multisampleFBO && !m_contentsChangeCommitted) { |
| m_context->bindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, m_multisampleFBO); |
| m_context->bindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, m_fbo); |
| |
| if (m_scissorEnabled) |
| m_context->disable(GL_SCISSOR_TEST); |
| |
| // Use NEAREST, because there is no scale performed during the blit. |
| m_context->blitFramebufferCHROMIUM(x, y, width, height, x, y, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| |
| if (m_scissorEnabled) |
| m_context->enable(GL_SCISSOR_TEST); |
| } |
| |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo); |
| m_contentsChangeCommitted = true; |
| } |
| |
| void DrawingBuffer::restoreFramebufferBinding() |
| { |
| if (!m_framebufferBinding) |
| return; |
| |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_framebufferBinding); |
| } |
| |
| bool DrawingBuffer::multisample() const |
| { |
| return m_multisampleMode != None; |
| } |
| |
| void DrawingBuffer::bind() |
| { |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, m_multisampleFBO ? m_multisampleFBO : m_fbo); |
| } |
| |
| void DrawingBuffer::setPackAlignment(GLint param) |
| { |
| m_packAlignment = param; |
| } |
| |
| void DrawingBuffer::paintRenderingResultsToCanvas(ImageBuffer* imageBuffer) |
| { |
| paintFramebufferToCanvas(framebuffer(), size().width(), size().height(), !m_actualAttributes.premultipliedAlpha, imageBuffer); |
| } |
| |
| PassRefPtr<Uint8ClampedArray> DrawingBuffer::paintRenderingResultsToImageData(int& width, int& height) |
| { |
| if (m_actualAttributes.premultipliedAlpha) |
| return nullptr; |
| |
| width = size().width(); |
| height = size().height(); |
| |
| Checked<int, RecordOverflow> dataSize = 4; |
| dataSize *= width; |
| dataSize *= height; |
| if (dataSize.hasOverflowed()) |
| return nullptr; |
| |
| RefPtr<Uint8ClampedArray> pixels = Uint8ClampedArray::createUninitialized(width * height * 4); |
| |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, framebuffer()); |
| readBackFramebuffer(pixels->data(), width, height, ReadbackRGBA, WebGLImageConversion::AlphaDoNothing); |
| flipVertically(pixels->data(), width, height); |
| |
| return pixels.release(); |
| } |
| |
| void DrawingBuffer::paintFramebufferToCanvas(int framebuffer, int width, int height, bool premultiplyAlpha, ImageBuffer* imageBuffer) |
| { |
| unsigned char* pixels = 0; |
| |
| const SkBitmap& canvasBitmap = imageBuffer->bitmap(); |
| const SkBitmap* readbackBitmap = 0; |
| ASSERT(canvasBitmap.colorType() == kN32_SkColorType); |
| if (canvasBitmap.width() == width && canvasBitmap.height() == height) { |
| // This is the fastest and most common case. We read back |
| // directly into the canvas's backing store. |
| readbackBitmap = &canvasBitmap; |
| m_resizingBitmap.reset(); |
| } else { |
| // We need to allocate a temporary bitmap for reading back the |
| // pixel data. We will then use Skia to rescale this bitmap to |
| // the size of the canvas's backing store. |
| if (m_resizingBitmap.width() != width || m_resizingBitmap.height() != height) { |
| if (!m_resizingBitmap.tryAllocN32Pixels(width, height)) |
| return; |
| } |
| readbackBitmap = &m_resizingBitmap; |
| } |
| |
| // Read back the frame buffer. |
| SkAutoLockPixels bitmapLock(*readbackBitmap); |
| pixels = static_cast<unsigned char*>(readbackBitmap->getPixels()); |
| |
| m_context->bindFramebuffer(GL_FRAMEBUFFER, framebuffer); |
| readBackFramebuffer(pixels, width, height, ReadbackSkia, premultiplyAlpha ? WebGLImageConversion::AlphaDoPremultiply : WebGLImageConversion::AlphaDoNothing); |
| flipVertically(pixels, width, height); |
| |
| readbackBitmap->notifyPixelsChanged(); |
| if (m_resizingBitmap.readyToDraw()) { |
| // We need to draw the resizing bitmap into the canvas's backing store. |
| SkCanvas canvas(canvasBitmap); |
| SkRect dst; |
| dst.set(SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(canvasBitmap.width()), SkIntToScalar(canvasBitmap.height())); |
| canvas.drawBitmapRect(m_resizingBitmap, 0, dst); |
| } |
| } |
| |
| void DrawingBuffer::readBackFramebuffer(unsigned char* pixels, int width, int height, ReadbackOrder readbackOrder, WebGLImageConversion::AlphaOp op) |
| { |
| if (m_packAlignment > 4) |
| m_context->pixelStorei(GL_PACK_ALIGNMENT, 1); |
| m_context->readPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); |
| if (m_packAlignment > 4) |
| m_context->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* scanline = &m_scanline[0]; |
| unsigned rowBytes = width * 4; |
| unsigned count = height / 2; |
| for (unsigned i = 0; i < count; i++) { |
| uint8* rowA = framebuffer + i * rowBytes; |
| uint8* rowB = framebuffer + (height - i - 1) * rowBytes; |
| memcpy(scanline, rowB, rowBytes); |
| memcpy(rowB, rowA, rowBytes); |
| memcpy(rowA, scanline, rowBytes); |
| } |
| } |
| |
| void DrawingBuffer::texImage2DResourceSafe(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, GLint unpackAlignment) |
| { |
| ASSERT(unpackAlignment == 1 || unpackAlignment == 2 || unpackAlignment == 4 || unpackAlignment == 8); |
| m_context->texImage2D(target, level, internalformat, width, height, border, format, type, 0); |
| } |
| |
| void DrawingBuffer::allocateTextureMemory(TextureInfo* info, const IntSize& size) |
| { |
| if (RuntimeEnabledFeatures::webGLImageChromiumEnabled()) { |
| deleteChromiumImageForTexture(info); |
| |
| info->imageId = m_context->createGpuMemoryBufferImageCHROMIUM(size.width(), size.height(), GL_RGBA, GC3D_SCANOUT_CHROMIUM); |
| if (info->imageId) { |
| m_context->bindTexImage2DCHROMIUM(GL_TEXTURE_2D, info->imageId); |
| return; |
| } |
| } |
| |
| texImage2DResourceSafe(GL_TEXTURE_2D, 0, m_internalColorFormat, size.width(), size.height(), 0, m_colorFormat, GL_UNSIGNED_BYTE); |
| } |
| |
| void DrawingBuffer::deleteChromiumImageForTexture(TextureInfo* info) |
| { |
| if (info->imageId) { |
| m_context->releaseTexImage2DCHROMIUM(GL_TEXTURE_2D, info->imageId); |
| m_context->destroyImageCHROMIUM(info->imageId); |
| info->imageId = 0; |
| } |
| } |
| |
| } // namespace blink |