| /* |
| * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. |
| * Copyright (C) 2011 Adobe Systems Incorporated. All rights reserved. |
| * Copyright (C) 2013 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 THE COPYRIGHT HOLDER “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 HOLDER 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/filters/custom/FECustomFilter.h" |
| |
| #include "platform/graphics/Extensions3D.h" |
| #include "platform/graphics/GraphicsContext3D.h" |
| #include "platform/graphics/filters/custom/CustomFilterRenderer.h" |
| #include "platform/graphics/filters/custom/CustomFilterValidatedProgram.h" |
| #include "platform/text/TextStream.h" |
| #include "wtf/Uint8ClampedArray.h" |
| |
| namespace WebCore { |
| |
| FECustomFilter::FECustomFilter(Filter* filter, PassRefPtr<GraphicsContext3D> context, PassRefPtr<CustomFilterValidatedProgram> validatedProgram, const CustomFilterParameterList& parameters, |
| unsigned meshRows, unsigned meshColumns, CustomFilterMeshType meshType) |
| : FilterEffect(filter) |
| , m_context(context) |
| , m_validatedProgram(validatedProgram) |
| , m_inputTexture(0) |
| , m_frameBuffer(0) |
| , m_depthBuffer(0) |
| , m_destTexture(0) |
| , m_triedMultisampleBuffer(false) |
| , m_multisampleFrameBuffer(0) |
| , m_multisampleRenderBuffer(0) |
| , m_multisampleDepthBuffer(0) |
| { |
| // We will not pass a CustomFilterCompiledProgram here, as we only want to compile it when we actually need it in the first paint. |
| m_customFilterRenderer = CustomFilterRenderer::create(m_context, m_validatedProgram->programInfo().programType(), parameters, meshRows, meshColumns, meshType); |
| } |
| |
| PassRefPtr<FECustomFilter> FECustomFilter::create(Filter* filter, PassRefPtr<GraphicsContext3D> context, PassRefPtr<CustomFilterValidatedProgram> validatedProgram, const CustomFilterParameterList& parameters, |
| unsigned meshRows, unsigned meshColumns, CustomFilterMeshType meshType) |
| { |
| return adoptRef(new FECustomFilter(filter, context, validatedProgram, parameters, meshRows, meshColumns, meshType)); |
| } |
| |
| FECustomFilter::~FECustomFilter() |
| { |
| deleteRenderBuffers(); |
| } |
| |
| void FECustomFilter::deleteRenderBuffers() |
| { |
| ASSERT(m_context); |
| m_context->makeContextCurrent(); |
| if (m_inputTexture) { |
| m_context->deleteTexture(m_inputTexture); |
| m_inputTexture = 0; |
| } |
| if (m_frameBuffer) { |
| // Make sure to unbind any framebuffer from the context first, otherwise |
| // some platforms might refuse to bind the same buffer id again. |
| m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0); |
| m_context->deleteFramebuffer(m_frameBuffer); |
| m_frameBuffer = 0; |
| } |
| if (m_depthBuffer) { |
| m_context->deleteRenderbuffer(m_depthBuffer); |
| m_depthBuffer = 0; |
| } |
| if (m_destTexture) { |
| m_context->deleteTexture(m_destTexture); |
| m_destTexture = 0; |
| } |
| deleteMultisampleRenderBuffers(); |
| } |
| |
| void FECustomFilter::deleteMultisampleRenderBuffers() |
| { |
| if (m_multisampleFrameBuffer) { |
| // Make sure to unbind any framebuffer from the context first, otherwise |
| // some platforms might refuse to bind the same buffer id again. |
| m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, 0); |
| m_context->deleteFramebuffer(m_multisampleFrameBuffer); |
| m_multisampleFrameBuffer = 0; |
| } |
| if (m_multisampleRenderBuffer) { |
| m_context->deleteRenderbuffer(m_multisampleRenderBuffer); |
| m_multisampleRenderBuffer = 0; |
| } |
| if (m_multisampleDepthBuffer) { |
| m_context->deleteRenderbuffer(m_multisampleDepthBuffer); |
| m_multisampleDepthBuffer = 0; |
| } |
| } |
| |
| void FECustomFilter::applySoftware() |
| { |
| if (!applyShader()) |
| clearShaderResult(); |
| } |
| |
| void FECustomFilter::clearShaderResult() |
| { |
| clearResult(); |
| Uint8ClampedArray* dstPixelArray = createUnmultipliedImageResult(); |
| if (!dstPixelArray) |
| return; |
| |
| FilterEffect* in = inputEffect(0); |
| setIsAlphaImage(in->isAlphaImage()); |
| IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); |
| in->copyUnmultipliedImage(dstPixelArray, effectDrawingRect); |
| } |
| |
| void FECustomFilter::drawFilterMesh(Platform3DObject inputTexture) |
| { |
| bool multisample = canUseMultisampleBuffers(); |
| m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, multisample ? m_multisampleFrameBuffer : m_frameBuffer); |
| m_context->viewport(0, 0, m_contextSize.width(), m_contextSize.height()); |
| |
| m_context->clearColor(0, 0, 0, 0); |
| m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT | GraphicsContext3D::DEPTH_BUFFER_BIT); |
| |
| m_customFilterRenderer->draw(inputTexture, m_contextSize); |
| |
| if (multisample) |
| resolveMultisampleBuffer(); |
| } |
| |
| bool FECustomFilter::prepareForDrawing() |
| { |
| m_context->makeContextCurrent(); |
| |
| // Lazily inject the compiled program into the CustomFilterRenderer. |
| if (!m_customFilterRenderer->compiledProgram()) |
| m_customFilterRenderer->setCompiledProgram(m_validatedProgram->compiledProgram()); |
| |
| if (!m_customFilterRenderer->prepareForDrawing()) |
| return false; |
| |
| // Only allocate a texture if the program needs one and the caller doesn't allocate one by itself. |
| if ((m_customFilterRenderer->programNeedsInputTexture() && !ensureInputTexture()) || !ensureFrameBuffer()) |
| return false; |
| |
| return true; |
| } |
| |
| bool FECustomFilter::applyShader() |
| { |
| Uint8ClampedArray* dstPixelArray = m_customFilterRenderer->premultipliedAlpha() ? createPremultipliedImageResult() : createUnmultipliedImageResult(); |
| if (!dstPixelArray) |
| return false; |
| |
| if (!prepareForDrawing()) |
| return false; |
| |
| FilterEffect* in = inputEffect(0); |
| IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); |
| IntSize newContextSize(effectDrawingRect.size()); |
| if (!resizeContextIfNeeded(newContextSize)) |
| return false; |
| |
| bool needsInputTexture = m_customFilterRenderer->programNeedsInputTexture(); |
| if (needsInputTexture) { |
| RefPtr<Uint8ClampedArray> srcPixelArray = in->asUnmultipliedImage(effectDrawingRect); |
| uploadInputTexture(srcPixelArray.get()); |
| } |
| drawFilterMesh(needsInputTexture ? m_inputTexture : 0); |
| |
| ASSERT(static_cast<size_t>(newContextSize.width() * newContextSize.height() * 4) == dstPixelArray->length()); |
| m_context->readPixels(0, 0, newContextSize.width(), newContextSize.height(), GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, dstPixelArray->data()); |
| |
| return true; |
| } |
| |
| bool FECustomFilter::ensureInputTexture() |
| { |
| if (!m_inputTexture) |
| m_inputTexture = m_context->createTexture(); |
| return m_inputTexture; |
| } |
| |
| void FECustomFilter::uploadInputTexture(Uint8ClampedArray* srcPixelArray) |
| { |
| m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_inputTexture); |
| m_context->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, GraphicsContext3D::RGBA, m_contextSize.width(), m_contextSize.height(), 0, GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, srcPixelArray->data()); |
| } |
| |
| bool FECustomFilter::ensureFrameBuffer() |
| { |
| if (!m_frameBuffer) |
| m_frameBuffer = m_context->createFramebuffer(); |
| if (!m_depthBuffer) |
| m_depthBuffer = m_context->createRenderbuffer(); |
| if (!m_destTexture) |
| m_destTexture = m_context->createTexture(); |
| return m_frameBuffer && m_depthBuffer && m_destTexture; |
| } |
| |
| bool FECustomFilter::createMultisampleBuffer() |
| { |
| ASSERT(!m_triedMultisampleBuffer); |
| m_triedMultisampleBuffer = true; |
| |
| Extensions3D* extensions = m_context->extensions(); |
| if (!extensions |
| || !extensions->supports("GL_ANGLE_framebuffer_multisample") |
| || !extensions->supports("GL_ANGLE_framebuffer_blit") |
| || !extensions->supports("GL_OES_rgb8_rgba8")) |
| return false; |
| |
| extensions->ensureEnabled("GL_ANGLE_framebuffer_blit"); |
| extensions->ensureEnabled("GL_ANGLE_framebuffer_multisample"); |
| extensions->ensureEnabled("GL_OES_rgb8_rgba8"); |
| |
| if (!m_multisampleFrameBuffer) |
| m_multisampleFrameBuffer = m_context->createFramebuffer(); |
| if (!m_multisampleRenderBuffer) |
| m_multisampleRenderBuffer = m_context->createRenderbuffer(); |
| if (!m_multisampleDepthBuffer) |
| m_multisampleDepthBuffer = m_context->createRenderbuffer(); |
| |
| return true; |
| } |
| |
| void FECustomFilter::resolveMultisampleBuffer() |
| { |
| ASSERT(m_triedMultisampleBuffer && m_multisampleFrameBuffer && m_multisampleRenderBuffer && m_multisampleDepthBuffer); |
| m_context->bindFramebuffer(Extensions3D::READ_FRAMEBUFFER, m_multisampleFrameBuffer); |
| m_context->bindFramebuffer(Extensions3D::DRAW_FRAMEBUFFER, m_frameBuffer); |
| |
| ASSERT(m_context->extensions()); |
| m_context->extensions()->blitFramebuffer(0, 0, m_contextSize.width(), m_contextSize.height(), 0, 0, m_contextSize.width(), m_contextSize.height(), GraphicsContext3D::COLOR_BUFFER_BIT, GraphicsContext3D::NEAREST); |
| |
| m_context->bindFramebuffer(Extensions3D::READ_FRAMEBUFFER, 0); |
| m_context->bindFramebuffer(Extensions3D::DRAW_FRAMEBUFFER, 0); |
| |
| m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_frameBuffer); |
| } |
| |
| bool FECustomFilter::canUseMultisampleBuffers() const |
| { |
| return m_triedMultisampleBuffer && m_multisampleFrameBuffer && m_multisampleRenderBuffer && m_multisampleDepthBuffer; |
| } |
| |
| bool FECustomFilter::resizeMultisampleBuffers(const IntSize& newContextSize) |
| { |
| if (!m_triedMultisampleBuffer && !createMultisampleBuffer()) |
| return false; |
| |
| if (!canUseMultisampleBuffers()) |
| return false; |
| |
| static const int kMaxSampleCount = 4; |
| int maxSupportedSampleCount = 0; |
| m_context->getIntegerv(Extensions3D::MAX_SAMPLES, &maxSupportedSampleCount); |
| int sampleCount = std::min(kMaxSampleCount, maxSupportedSampleCount); |
| if (!sampleCount) { |
| deleteMultisampleRenderBuffers(); |
| return false; |
| } |
| |
| Extensions3D* extensions = m_context->extensions(); |
| ASSERT(extensions); |
| |
| m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_multisampleFrameBuffer); |
| |
| m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_multisampleRenderBuffer); |
| extensions->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, Extensions3D::RGBA8_OES, newContextSize.width(), newContextSize.height()); |
| m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::RENDERBUFFER, m_multisampleRenderBuffer); |
| |
| m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_multisampleDepthBuffer); |
| extensions->renderbufferStorageMultisample(GraphicsContext3D::RENDERBUFFER, sampleCount, GraphicsContext3D::DEPTH_COMPONENT16, newContextSize.width(), newContextSize.height()); |
| m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::DEPTH_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_multisampleDepthBuffer); |
| |
| m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, 0); |
| |
| if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) { |
| deleteMultisampleRenderBuffers(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool FECustomFilter::resizeContextIfNeeded(const IntSize& newContextSize) |
| { |
| if (newContextSize.isEmpty()) |
| return false; |
| if (m_contextSize == newContextSize) |
| return true; |
| |
| int maxTextureSize = 0; |
| m_context->getIntegerv(GraphicsContext3D::MAX_TEXTURE_SIZE, &maxTextureSize); |
| if (newContextSize.height() > maxTextureSize || newContextSize.width() > maxTextureSize) |
| return false; |
| |
| return resizeContext(newContextSize); |
| } |
| |
| bool FECustomFilter::resizeContext(const IntSize& newContextSize) |
| { |
| bool multisample = resizeMultisampleBuffers(newContextSize); |
| |
| m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_frameBuffer); |
| m_context->bindTexture(GraphicsContext3D::TEXTURE_2D, m_destTexture); |
| // We are going to clear the output buffer anyway, so we can safely initialize the destination texture with garbage data. |
| // FIXME: GraphicsContext3D::texImage2DDirect is not implemented on Chromium. |
| m_context->texImage2D(GraphicsContext3D::TEXTURE_2D, 0, GraphicsContext3D::RGBA, newContextSize.width(), newContextSize.height(), 0, GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, 0); |
| m_context->framebufferTexture2D(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::COLOR_ATTACHMENT0, GraphicsContext3D::TEXTURE_2D, m_destTexture, 0); |
| |
| // We don't need the depth buffer for the texture framebuffer, if we already |
| // have a multisample buffer. |
| if (!multisample) { |
| m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, m_depthBuffer); |
| m_context->renderbufferStorage(GraphicsContext3D::RENDERBUFFER, GraphicsContext3D::DEPTH_COMPONENT16, newContextSize.width(), newContextSize.height()); |
| m_context->framebufferRenderbuffer(GraphicsContext3D::FRAMEBUFFER, GraphicsContext3D::DEPTH_ATTACHMENT, GraphicsContext3D::RENDERBUFFER, m_depthBuffer); |
| } |
| |
| if (m_context->checkFramebufferStatus(GraphicsContext3D::FRAMEBUFFER) != GraphicsContext3D::FRAMEBUFFER_COMPLETE) |
| return false; |
| |
| if (multisample) { |
| // Clear the framebuffer first, otherwise the first blit will fail. |
| m_context->clearColor(0, 0, 0, 0); |
| m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT); |
| } |
| |
| m_context->bindRenderbuffer(GraphicsContext3D::RENDERBUFFER, 0); |
| |
| m_contextSize = newContextSize; |
| return true; |
| } |
| |
| TextStream& FECustomFilter::externalRepresentation(TextStream& ts, int indent) const |
| { |
| writeIndent(ts, indent); |
| ts << "[feCustomFilter"; |
| FilterEffect::externalRepresentation(ts); |
| ts << "]\n"; |
| inputEffect(0)->externalRepresentation(ts, indent + 1); |
| return ts; |
| } |
| |
| } // namespace WebCore |