/* * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved. * 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" #if USE(ACCELERATED_COMPOSITING) #include "LayerRenderer.h" #include "LayerCompositingThread.h" #include "PlatformString.h" #include "TextureCacheCompositingThread.h" #include #include #include #define ENABLE_SCISSOR 1 #define DEBUG_SHADER_COMPILATION 0 #define DEBUG_DIRTY_LAYERS 0 // Show dirty layers as red. #define DEBUG_LAYER_ANIMATIONS 0 // Show running animations as green. #define DEBUG_VIDEO_CLIPPING 0 using BlackBerry::Platform::Graphics::GLES2Context; using namespace std; namespace WebCore { static void checkGLError() { #ifndef NDEBUG if (GLenum error = glGetError()) LOG_ERROR("GL Error: 0x%x " , error); #endif } static GLuint loadShader(GLenum type, const char* shaderSource) { GLuint shader = glCreateShader(type); if (!shader) return 0; glShaderSource(shader, 1, &shaderSource, 0); glCompileShader(shader); GLint compiled; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { #if DEBUG_SHADER_COMPILATION char infoLog[2048]; GLsizei length; glGetShaderInfoLog(shader, 2048, &length, infoLog); fprintf(stderr, "Failed to compile shader: %s", infoLog); #endif glDeleteShader(shader); return 0; } return shader; } static GLuint loadShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource) { GLuint vertexShader; GLuint fragmentShader; GLuint programObject; GLint linked; vertexShader = loadShader(GL_VERTEX_SHADER, vertexShaderSource); if (!vertexShader) return 0; fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentShaderSource); if (!fragmentShader) { glDeleteShader(vertexShader); return 0; } programObject = glCreateProgram(); if (programObject) { glAttachShader(programObject, vertexShader); glAttachShader(programObject, fragmentShader); glLinkProgram(programObject); glGetProgramiv(programObject, GL_LINK_STATUS, &linked); if (!linked) { glDeleteProgram(programObject); programObject = 0; } } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); return programObject; } static TransformationMatrix orthoMatrix(float left, float right, float bottom, float top, float nearZ, float farZ) { float deltaX = right - left; float deltaY = top - bottom; float deltaZ = farZ - nearZ; TransformationMatrix ortho; if (!deltaX || !deltaY || !deltaZ) return ortho; ortho.setM11(2.0f / deltaX); ortho.setM41(-(right + left) / deltaX); ortho.setM22(2.0f / deltaY); ortho.setM42(-(top + bottom) / deltaY); ortho.setM33(-2.0f / deltaZ); ortho.setM43(-(nearZ + farZ) / deltaZ); return ortho; } static Vector rawPtrVectorFromRefPtrVector(const Vector >& sublayers) { Vector sublayerList; for (size_t i = 0; i < sublayers.size(); i++) sublayerList.append(sublayers[i].get()); return sublayerList; } PassOwnPtr LayerRenderer::create(GLES2Context* context) { return adoptPtr(new LayerRenderer(context)); } LayerRenderer::LayerRenderer(GLES2Context* context) : m_colorProgramObject(0) , m_checkerProgramObject(0) , m_positionLocation(0) , m_texCoordLocation(1) , m_fbo(0) , m_currentLayerRendererSurface(0) , m_clearSurfaceOnDrawLayers(true) , m_context(context) , m_needsCommit(false) { for (int i = 0; i < LayerData::NumberOfLayerProgramShaders; ++i) m_layerProgramObject[i] = 0; m_hardwareCompositing = initializeSharedGLObjects(); } LayerRenderer::~LayerRenderer() { if (m_hardwareCompositing) { makeContextCurrent(); if (m_fbo) glDeleteFramebuffers(1, &m_fbo); glDeleteProgram(m_colorProgramObject); glDeleteProgram(m_checkerProgramObject); for (int i = 0; i < LayerData::NumberOfLayerProgramShaders; ++i) glDeleteProgram(m_layerProgramObject[i]); // Free up all GL textures. while (m_layers.begin() != m_layers.end()) { LayerSet::iterator iter = m_layers.begin(); (*iter)->deleteTextures(); (*iter)->setLayerRenderer(0); removeLayer(*iter); } textureCacheCompositingThread()->clear(); } } void LayerRenderer::releaseLayerResources() { if (m_hardwareCompositing) { makeContextCurrent(); // Free up all GL textures. for (LayerSet::iterator iter = m_layers.begin(); iter != m_layers.end(); ++iter) (*iter)->deleteTextures(); textureCacheCompositingThread()->clear(); } } static inline bool compareLayerZ(const LayerCompositingThread* a, const LayerCompositingThread* b) { const TransformationMatrix& transformA = a->drawTransform(); const TransformationMatrix& transformB = b->drawTransform(); return transformA.m43() < transformB.m43(); } // Re-composites all sublayers. void LayerRenderer::drawLayers(const FloatRect& visibleRect, const IntRect& layoutRect, const IntSize& contentsSize, const IntRect& dstRect) { ASSERT(m_hardwareCompositing); if (!m_hardwareCompositing) return; bool wasEmpty = m_lastRenderingResults.isEmpty(); m_lastRenderingResults = LayerRenderingResults(); m_lastRenderingResults.wasEmpty = wasEmpty; if (!m_rootLayer) return; // These parameters are used to calculate position of fixed position elements m_visibleRect = visibleRect; m_layoutRect = layoutRect; m_contentsSize = contentsSize; // WebKit uses row vectors which are multiplied by the matrix on the left (i.e. v*M) // Transformations are composed on the left so that M1.xform(M2) means M2*M1 // We therefore start with our (othogonal) projection matrix, which will be applied // as the last transformation. TransformationMatrix matrix = orthoMatrix(0, visibleRect.width(), visibleRect.height(), 0, -1000, 1000); matrix.translate3d(-visibleRect.x(), -visibleRect.y(), 0); // OpenGL window coordinates origin is at the lower left corner of the surface while // WebKit uses upper left as the origin of the window coordinate system. The passed in 'dstRect' // is in WebKit window coordinate system. Here we setup the viewport to the corresponding value // in OpenGL window coordinates. int viewportY = std::max(0, m_context->surfaceSize().height() - dstRect.maxY()); m_viewport = IntRect(dstRect.x(), viewportY, dstRect.width(), dstRect.height()); double animationTime = currentTime(); #if DEBUG_VIDEO_CLIPPING // Invoking updateLayersRecursive() which will call LayerCompositingThread::setDrawTransform(). BlackBerry::Platform::log(BlackBerry::Platform::LogLevelInfo, "LayerRenderer::drawLayers() visible=(x=%.2f,y=%.2f,width=%.2f,height=%.2f), layout=(x=%d,y=%d,width=%d,height=%d), contents=(%dx%d), dst=(x=%d,y=%d,width=%d,height=%d).", visibleRect.x(), visibleRect.y(), visibleRect.width(), visibleRect.height(), layoutRect.x(), layoutRect.y(), layoutRect.width(), layoutRect.height(), contentsSize.width(), contentsSize.height(), dstRect.x(), dstRect.y(), dstRect.width(), dstRect.height()); #endif Vector > surfaceLayers; const Vector >& sublayers = m_rootLayer->getSublayers(); for (size_t i = 0; i < sublayers.size(); i++) { float opacity = 1; FloatRect clipRect(-1, -1, 2, 2); updateLayersRecursive(sublayers[i].get(), matrix, surfaceLayers, opacity, clipRect, animationTime); } // Decompose the dirty rect into a set of non-overlaping rectangles // (they need to not overlap so that the blending code doesn't draw any region twice). for (int i = 0; i < LayerRenderingResults::NumberOfDirtyRects; ++i) { BlackBerry::Platform::IntRectRegion region(BlackBerry::Platform::IntRect(m_lastRenderingResults.dirtyRect(i))); m_lastRenderingResults.dirtyRegion = BlackBerry::Platform::IntRectRegion::unionRegions(m_lastRenderingResults.dirtyRegion, region); } // If we won't draw anything, don't touch the OpenGL APIs. if (m_lastRenderingResults.isEmpty() && wasEmpty) return; // Okay, we're going to do some drawing. if (!makeContextCurrent()) return; // Get rid of any bound buffer that might affect the interpretation of our // glVertexAttribPointer calls. glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glEnableVertexAttribArray(m_positionLocation); glEnableVertexAttribArray(m_texCoordLocation); glActiveTexture(GL_TEXTURE0); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glEnable(GL_STENCIL_TEST); // If culling is enabled then we will cull the backface. glCullFace(GL_BACK); // The orthographic projection is setup such that Y starts at zero and // increases going down the page which flips the winding order of triangles. // The layer quads are drawn in clock-wise order so the front face is CCW. glFrontFace(GL_CCW); // The shader used to render layers returns pre-multiplied alpha colors // so we need to send the blending mode appropriately. glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Update the parameters for the checkerboard drawing. glUseProgram(m_checkerProgramObject); float bitmapScale = static_cast(m_layoutRect.width()) / static_cast(m_visibleRect.width()); glUniform1f(m_checkerScaleLocation, bitmapScale); float scale = static_cast(dstRect.width()) / static_cast(m_visibleRect.width()); glUniform2f(m_checkerOriginLocation, m_visibleRect.x()*scale, m_visibleRect.y()*scale); glUniform1f(m_checkerSurfaceHeightLocation, m_context->surfaceSize().height()); checkGLError(); // If some layers should be drawed on temporary surfaces, we should do it first. drawLayersOnSurfaces(surfaceLayers); #if ENABLE_SCISSOR m_scissorRect = m_viewport; glEnable(GL_SCISSOR_TEST); glScissor(m_scissorRect.x(), m_scissorRect.y(), m_scissorRect.width(), m_scissorRect.height()); #endif glClearStencil(0); glClearColor(0, 0, 0, 0); GLenum buffersToClear = GL_STENCIL_BUFFER_BIT; if (m_clearSurfaceOnDrawLayers) { buffersToClear |= GL_COLOR_BUFFER_BIT; } glClear(buffersToClear); // Don't render the root layer, the BlackBerry port uses the BackingStore to draw the // root layer. for (size_t i = 0; i < sublayers.size(); i++) { int currentStencilValue = 0; FloatRect clipRect(-1, -1, 2, 2); compositeLayersRecursive(sublayers[i].get(), currentStencilValue, clipRect); } // We need to make sure that all texture resource usage is finished before // unlocking the texture resources, so force a glFinish() in that case. if (m_layersLockingTextureResources.size()) glFinish(); m_context->swapBuffers(); #if ENABLE_SCISSOR glDisable(GL_SCISSOR_TEST); #endif glDisable(GL_STENCIL_TEST); // PR 147254, the EGL implementation crashes when the last bound texture // was an EGLImage, and you try to bind another texture and the pixmap // backing the EGLImage was deleted in between. Make this easier for the // driver by unbinding early (when the pixmap is hopefully still around). glBindTexture(GL_TEXTURE_2D, 0); LayerSet::iterator iter = m_layersLockingTextureResources.begin(); for (; iter != m_layersLockingTextureResources.end(); ++iter) (*iter)->releaseTextureResources(); m_layersLockingTextureResources.clear(); if (m_needsCommit) { m_needsCommit = false; m_rootLayer->scheduleCommit(); } textureCacheCompositingThread()->collectGarbage(); } bool LayerRenderer::useSurface(LayerRendererSurface* surface) { if (m_currentLayerRendererSurface == surface) return true; m_currentLayerRendererSurface = surface; if (!surface) { glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(m_viewport.x(), m_viewport.y(), m_viewport.width(), m_viewport.height()); return true; } surface->ensureTexture(); if (!m_fbo) glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, surface->texture()->textureId(), 0); #ifndef NDEBUG GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { fprintf(stderr, "glCheckFramebufferStatus error %x\n", status); return false; } #endif glViewport(0, 0, surface->size().width(), surface->size().height()); return true; } void LayerRenderer::drawLayersOnSurfaces(const Vector >& surfaceLayers) { for (int i = surfaceLayers.size() - 1; i >= 0; i--) { LayerCompositingThread* layer = surfaceLayers[i].get(); LayerRendererSurface* surface = layer->layerRendererSurface(); if (!surface || !useSurface(surface)) continue; glDisable(GL_SCISSOR_TEST); glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); int currentStencilValue = 0; FloatRect clipRect(-1, -1, 2, 2); compositeLayersRecursive(surfaceLayers[i].get(), currentStencilValue, clipRect); } // If there are layers drawed on surfaces, we need to switch to default framebuffer. // Otherwise, we just need to set viewport. if (surfaceLayers.size()) useSurface(0); else glViewport(m_viewport.x(), m_viewport.y(), m_viewport.width(), m_viewport.height()); } void LayerRenderer::setRootLayer(LayerCompositingThread* layer) { m_rootLayer = layer; } void LayerRenderer::addLayer(LayerCompositingThread* layer) { m_layers.add(layer); } bool LayerRenderer::removeLayer(LayerCompositingThread* layer) { LayerSet::iterator iter = m_layers.find(layer); if (iter == m_layers.end()) return false; m_layers.remove(layer); return true; } void LayerRenderer::addLayerToReleaseTextureResourcesList(LayerCompositingThread* layer) { m_layersLockingTextureResources.add(layer); } // Transform normalized device coordinates to window coordinates // as specified in the OpenGL ES 2.0 spec section 2.12.1. IntRect LayerRenderer::toOpenGLWindowCoordinates(const FloatRect& r) const { float vw2 = m_viewport.width() / 2.0; float vh2 = m_viewport.height() / 2.0; float ox = m_viewport.x() + vw2; float oy = m_viewport.y() + vh2; return enclosingIntRect(FloatRect(r.x() * vw2 + ox, r.y() * vh2 + oy, r.width() * vw2, r.height() * vh2)); } // Transform normalized device coordinates to window coordinates as WebKit understands them. // // The OpenGL surface may be larger than the WebKit window, and OpenGL window coordinates // have origin in bottom left while WebKit window coordinates origin is in top left. // The viewport is setup to cover the upper portion of the larger OpenGL surface. IntRect LayerRenderer::toWebKitWindowCoordinates(const FloatRect& r) const { float vw2 = m_viewport.width() / 2.0; float vh2 = m_viewport.height() / 2.0; float ox = m_viewport.x() + vw2; float oy = m_context->surfaceSize().height() - (m_viewport.y() + vh2); return enclosingIntRect(FloatRect(r.x() * vw2 + ox, -(r.y()+r.height()) * vh2 + oy, r.width() * vw2, r.height() * vh2)); } // Similar to toWebKitWindowCoordinates except that this also takes any zoom into account. IntRect LayerRenderer::toWebKitDocumentCoordinates(const FloatRect& r) const { // The zoom is the ratio between visibleRect (or layoutRect) and dstRect parameters which are passed to drawLayers float zoom = m_visibleRect.width() / m_viewport.width(); // Could assert here that it doesn't matter whether we choose width or height in the above statement: // because both rectangles should have very similar shapes (subject only to pixel rounding error). IntRect result = toWebKitWindowCoordinates(r); result.scale(zoom); return result; } // Draws a debug border around the layer's bounds. void LayerRenderer::drawDebugBorder(LayerCompositingThread* layer) { Color borderColor = layer->borderColor(); #if DEBUG_DIRTY_LAYERS if (layer->isDirty()) borderColor = Color(0xFF, 0x00, 0x00, 0xFF); #endif #if DEBUG_LAYER_ANIMATIONS if (layer->hasRunningAnimations()) borderColor = Color(0x00, 0xFF, 0x00, 0xFF); #endif if (!borderColor.alpha()) return; glUseProgram(m_colorProgramObject); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, &layer->getTransformedBounds()); glUniform4f(m_colorColorLocation, borderColor.red() / 255.0, borderColor.green() / 255.0, borderColor.blue() / 255.0, 1); glLineWidth(layer->borderWidth()); glDrawArrays(GL_LINE_LOOP, 0, 4); } // Clears a rectangle inside the layer's bounds. void LayerRenderer::drawHolePunchRect(LayerCompositingThread* layer) { glUseProgram(m_colorProgramObject); glUniform4f(m_colorColorLocation, 0, 0, 0, 0); glBlendFunc(GL_ONE, GL_ZERO); FloatQuad hole = layer->getTransformedHolePunchRect(); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, &hole); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); checkGLError(); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); IntRect holeWC = toWebKitWindowCoordinates(hole.boundingBox()); m_lastRenderingResults.addHolePunchRect(holeWC); } void LayerRenderer::updateLayersRecursive(LayerCompositingThread* layer, const TransformationMatrix& matrix, Vector >& surfaceLayers, float opacity, FloatRect clipRect, double currentTime) { // The contract for LayerCompositingThread::setLayerRenderer is it must be set if the layer has been rendered. // So do it now, before we render it in compositeLayersRecursive. layer->setLayerRenderer(this); if (layer->maskLayer()) layer->maskLayer()->setLayerRenderer(this); if (layer->replicaLayer()) { LayerCompositingThread* replica = layer->replicaLayer(); replica->setLayerRenderer(this); if (replica->maskLayer()) replica->maskLayer()->setLayerRenderer(this); } // This might cause the layer to recompute some attributes. m_lastRenderingResults.needsAnimationFrame |= layer->updateAnimations(currentTime); // Compute the new matrix transformation that will be applied to this layer and // all its sublayers. It's important to remember that the layer's position // is the position of the layer's anchor point. Also, the coordinate system used // assumes that the origin is at the lower left even though the coordinates the browser // gives us for the layers are for the upper left corner. The Y flip happens via // the orthographic projection applied at render time. // The transformation chain for the layer is (using the Matrix x Vector order): // M = M[p] * Tr[l] * M[l] * Tr[c] // Where M[p] is the parent matrix passed down to the function // Tr[l] is the translation matrix locating the layer's anchor point // Tr[c] is the translation offset between the anchor point and the center of the layer // M[l] is the layer's matrix (applied at the anchor point) // This transform creates a coordinate system whose origin is the center of the layer. // Note that the final matrix used by the shader for the layer is P * M * S . This final product // is computed in drawTexturedQuad(). // Where: P is the projection matrix // M is the layer's matrix computed above // S is the scale adjustment (to scale up to the layer size) IntSize bounds = layer->bounds(); FloatPoint anchorPoint = layer->anchorPoint(); FloatPoint position = layer->position(); // Layer whose hasFixedContainer is true will get scrolled relative to // the fixed positioned parent. if (!layer->hasFixedContainer() && (layer->isFixedPosition() || layer->hasFixedAncestorInDOMTree())) { // The basic idea here is to set visible y to the value we want, and // layout y to the value WebCore layouted the fixed element to. float maximumScrollY = m_contentsSize.height() - m_visibleRect.height(); float visibleY = max(0.0f, m_visibleRect.y()); float layoutY = max(0.0f, min(maximumScrollY, (float)m_layoutRect.y())); // For stuff located on the lower half of the screen, we zoom relative to bottom. // This trick allows us to display fixed positioned elements aligned to top or // bottom correctly when panning and zooming, without actually knowing the // numeric values of the top and bottom CSS attributes. // In fact, the position is the location of the anchor, so to find the top left // we have to subtract the anchor times the bounds. The anchor defaults to // (0.5, 0.5) for most layers. if (position.y() - anchorPoint.y() * bounds.height() > layoutY + m_layoutRect.height() / 2) { visibleY = min(m_contentsSize.height(), m_visibleRect.y() + m_visibleRect.height()); layoutY = min(m_contentsSize.height(), max(0, m_layoutRect.y()) + m_layoutRect.height()); } position.setY(position.y() + (visibleY - layoutY)); } // Offset between anchor point and the center of the quad. float centerOffsetX = (0.5 - anchorPoint.x()) * bounds.width(); float centerOffsetY = (0.5 - anchorPoint.y()) * bounds.height(); // M = M[p] TransformationMatrix localMatrix = matrix; // M = M[p] * Tr[l] localMatrix.translate3d(position.x(), position.y(), layer->anchorPointZ()); // M = M[p] * Tr[l] * M[l] localMatrix.multiply(layer->transform()); // M = M[p] * Tr[l] * M[l] * Tr[c] localMatrix.translate3d(centerOffsetX, centerOffsetY, -layer->anchorPointZ()); // Calculate the layer's opacity. opacity *= layer->opacity(); bool useLayerRendererSurface = layer->maskLayer() || layer->replicaLayer(); if (!useLayerRendererSurface) { layer->setDrawOpacity(opacity); layer->clearLayerRendererSurface(); } else { if (!layer->layerRendererSurface()) layer->createLayerRendererSurface(); LayerRendererSurface* surface = layer->layerRendererSurface(); layer->setDrawOpacity(1.0); surface->setDrawOpacity(opacity); surface->setDrawTransform(localMatrix); if (layer->replicaLayer()) { TransformationMatrix replicaMatrix = localMatrix; replicaMatrix.translate3d(-0.5 * bounds.width(), -0.5 * bounds.height(), 0); replicaMatrix.translate3d(layer->replicaLayer()->position().x(), layer->replicaLayer()->position().y(), 0); replicaMatrix.multiply(layer->replicaLayer()->transform()); replicaMatrix.translate3d(centerOffsetX, centerOffsetY, 0); surface->setReplicaDrawTransform(replicaMatrix); } IntRect drawRect = IntRect(IntPoint(), bounds); surface->setContentRect(drawRect); TransformationMatrix projectionMatrix = orthoMatrix(drawRect.x(), drawRect.maxX(), drawRect.y(), drawRect.maxY(), -1000, 1000); // The origin of the new surface is the upper left corner of the layer. TransformationMatrix drawTransform; drawTransform.translate3d(0.5 * bounds.width(), 0.5 * bounds.height(), 0); // This layer will start using new transformation. localMatrix = projectionMatrix * drawTransform; surfaceLayers.append(layer); } layer->setDrawTransform(localMatrix); #if ENABLE(VIDEO) bool layerVisible = clipRect.intersects(layer->getDrawRect()) || layer->mediaPlayer(); #else bool layerVisible = clipRect.intersects(layer->getDrawRect()); #endif if (layer->needsTexture() && layerVisible) { IntRect dirtyRect = toWebKitWindowCoordinates(intersection(layer->getDrawRect(), clipRect)); m_lastRenderingResults.addDirtyRect(dirtyRect); } if (layer->masksToBounds()) clipRect.intersect(layer->getDrawRect()); // Flatten to 2D if the layer doesn't preserve 3D. if (!layer->preserves3D()) { localMatrix.setM13(0); localMatrix.setM23(0); localMatrix.setM31(0); localMatrix.setM32(0); // This corresponds to the depth range specified in the original orthographic projection matrix localMatrix.setM33(0.001); localMatrix.setM34(0); localMatrix.setM43(0); } // Apply the sublayer transform. localMatrix.multiply(layer->sublayerTransform()); // The origin of the sublayers is actually the bottom left corner of the layer // (or top left when looking it it from the browser's pespective) instead of the center. // The matrix passed down to the sublayers is therefore: // M[s] = M * Tr[-center] localMatrix.translate3d(-bounds.width() * 0.5, -bounds.height() * 0.5, 0); const Vector >& sublayers = layer->getSublayers(); for (size_t i = 0; i < sublayers.size(); i++) updateLayersRecursive(sublayers[i].get(), localMatrix, surfaceLayers, opacity, clipRect, currentTime); } static bool hasRotationalComponent(const TransformationMatrix& m) { return m.m12() || m.m13() || m.m23() || m.m21() || m.m31() || m.m32(); } bool LayerRenderer::layerAlreadyOnSurface(LayerCompositingThread* layer) const { return layer->layerRendererSurface() && layer->layerRendererSurface() != m_currentLayerRendererSurface; } static void collect3DPreservingLayers(Vector& layers) { for (size_t i = 0; i < layers.size(); ++i) { LayerCompositingThread* layer = layers[i]; if (!layer->preserves3D() || !layer->getSublayers().size()) continue; Vector sublayers = rawPtrVectorFromRefPtrVector(layer->getSublayers()); collect3DPreservingLayers(sublayers); layers.insert(i+1, sublayers); i += sublayers.size(); } } void LayerRenderer::compositeLayersRecursive(LayerCompositingThread* layer, int stencilValue, FloatRect clipRect) { FloatRect rect; if (layerAlreadyOnSurface(layer)) rect = layer->layerRendererSurface()->drawRect(); else rect = layer->getDrawRect(); #if ENABLE(VIDEO) bool layerVisible = clipRect.intersects(rect) || layer->mediaPlayer(); #else bool layerVisible = clipRect.intersects(rect); #endif layer->setVisible(layerVisible); glStencilFunc(GL_EQUAL, stencilValue, 0xff); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Note that there are two types of layers: // 1. Layers that have their own GraphicsContext and can draw their contents on demand (layer->drawsContent() == true). // 2. Layers that are just containers of images/video/etc that don't own a GraphicsContext (layer->contents() == true). // Even non-visible layers need to perform their texture jobs, or they will // pile up and waste memory. if (layer->needsTexture()) layer->updateTextureContentsIfNeeded(); if (layer->maskLayer() && layer->maskLayer()->needsTexture()) layer->maskLayer()->updateTextureContentsIfNeeded(); if (layer->replicaLayer()) { LayerCompositingThread* replica = layer->replicaLayer(); if (replica->needsTexture()) replica->updateTextureContentsIfNeeded(); if (replica->maskLayer() && replica->maskLayer()->needsTexture()) replica->maskLayer()->updateTextureContentsIfNeeded(); } if ((layer->needsTexture() || layer->layerRendererSurface()) && layerVisible) { updateScissorIfNeeded(clipRect); if (layer->doubleSided()) glDisable(GL_CULL_FACE); else glEnable(GL_CULL_FACE); if (layer->hasVisibleHolePunchRect()) drawHolePunchRect(layer); // Draw the surface onto another surface or screen. bool drawSurface = layerAlreadyOnSurface(layer); // The texture format for the surface is RGBA. LayerData::LayerProgramShader shader = drawSurface ? LayerData::LayerProgramShaderRGBA : layer->layerProgramShader(); if (!drawSurface) { glUseProgram(m_layerProgramObject[shader]); glUniform1f(m_alphaLocation[shader], layer->drawOpacity()); layer->drawTextures(m_positionLocation, m_texCoordLocation, m_visibleRect); } else { // Draw the reflection if it exists. if (layer->replicaLayer()) { // If this layer and its reflection both have mask, we need another temporary surface. // Since this use case should be rare, currently it's not handled and the mask for // the reflection is applied only when this layer has no mask. LayerCompositingThread* mask = layer->maskLayer(); if (!mask && layer->replicaLayer()) mask = layer->replicaLayer()->maskLayer(); glUseProgram(mask ? m_layerMaskProgramObject[shader] : m_layerProgramObject[shader]); glUniform1f(mask ? m_maskAlphaLocation[shader] : m_alphaLocation[shader], layer->layerRendererSurface()->drawOpacity()); layer->drawSurface(layer->layerRendererSurface()->replicaDrawTransform(), mask, m_positionLocation, m_texCoordLocation); } glUseProgram(layer->maskLayer() ? m_layerMaskProgramObject[shader] : m_layerProgramObject[shader]); glUniform1f(layer->maskLayer() ? m_maskAlphaLocation[shader] : m_alphaLocation[shader], layer->layerRendererSurface()->drawOpacity()); layer->drawSurface(layer->layerRendererSurface()->drawTransform(), layer->maskLayer(), m_positionLocation, m_texCoordLocation); } if (layer->hasMissingTextures()) { glDisable(GL_BLEND); glUseProgram(m_checkerProgramObject); layer->drawMissingTextures(m_positionLocation, m_texCoordLocation, m_visibleRect); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } } // Draw the debug border if there is one. drawDebugBorder(layer); // The texture for the LayerRendererSurface can be released after the surface was drawed on another surface. if (layerAlreadyOnSurface(layer)) { layer->layerRendererSurface()->releaseTexture(); return; } // If we need to mask to bounds but the transformation has a rotational component // to it, scissoring is not enough and we need to use the stencil buffer for clipping. #if ENABLE_SCISSOR bool stencilClip = layer->masksToBounds() && hasRotationalComponent(layer->drawTransform()); #else bool stencilClip = layer->masksToBounds(); #endif if (stencilClip) { glStencilFunc(GL_EQUAL, stencilValue, 0xff); glStencilOp(GL_KEEP, GL_INCR, GL_INCR); updateScissorIfNeeded(clipRect); glUseProgram(m_colorProgramObject); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, &layer->getTransformedBounds()); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } if (layer->masksToBounds()) clipRect.intersect(layer->getDrawRect()); // Here, we need to sort the whole subtree of layers with preserve-3d. It // affects all children, and the children of any children with preserve-3d, // and so on. Vector sublayers = rawPtrVectorFromRefPtrVector(layer->getSublayers()); bool preserves3D = layer->preserves3D(); bool superlayerPreserves3D = layer->superlayer() && layer->superlayer()->preserves3D(); // Collect and render all sublayers with preserves-3D. // If the superlayer preserves 3D, we've already collected and rendered its // children, so bail. if (preserves3D && !superlayerPreserves3D) { collect3DPreservingLayers(sublayers); std::stable_sort(sublayers.begin(), sublayers.end(), compareLayerZ); } int newStencilValue = stencilClip ? stencilValue+1 : stencilValue; for (size_t i = 0; i < sublayers.size(); i++) { LayerCompositingThread* sublayer = sublayers[i]; // The root of the 3d-preserving subtree has collected all // 3d-preserving layers and their children and will render them all in // the right order. if (preserves3D && superlayerPreserves3D) continue; compositeLayersRecursive(sublayer, newStencilValue, clipRect); } if (stencilClip) { glStencilFunc(GL_LEQUAL, stencilValue, 0xff); glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); updateScissorIfNeeded(clipRect); glUseProgram(m_colorProgramObject); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, &layer->getTransformedBounds()); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } } void LayerRenderer::updateScissorIfNeeded(const FloatRect& clipRect) { #if ENABLE_SCISSOR IntRect clipRectWC = toOpenGLWindowCoordinates(clipRect); if (m_scissorRect == clipRectWC) return; m_scissorRect = clipRectWC; glScissor(m_scissorRect.x(), m_scissorRect.y(), m_scissorRect.width(), m_scissorRect.height()); #endif } bool LayerRenderer::makeContextCurrent() { return m_context->makeCurrent(); } // Binds the given attribute name to a common location across all three programs // used by the compositor. This allows the code to bind the attributes only once // even when switching between programs. void LayerRenderer::bindCommonAttribLocation(int location, const char* attribName) { for (int i = 0; i < LayerData::NumberOfLayerProgramShaders; ++i) { glBindAttribLocation(m_layerProgramObject[i], location, attribName); glBindAttribLocation(m_layerMaskProgramObject[i], location, attribName); } glBindAttribLocation(m_colorProgramObject, location, attribName); glBindAttribLocation(m_checkerProgramObject, location, attribName); } bool LayerRenderer::initializeSharedGLObjects() { // Shaders for drawing the layer contents. char vertexShaderString[] = "attribute vec4 a_position; \n" "attribute vec2 a_texCoord; \n" "varying vec2 v_texCoord; \n" "void main() \n" "{ \n" " gl_Position = a_position; \n" " v_texCoord = a_texCoord; \n" "} \n"; char fragmentShaderStringRGBA[] = "varying mediump vec2 v_texCoord; \n" "uniform lowp sampler2D s_texture; \n" "uniform lowp float alpha; \n" "void main() \n" "{ \n" " gl_FragColor = texture2D(s_texture, v_texCoord) * alpha; \n" "} \n"; char fragmentShaderStringBGRA[] = "varying mediump vec2 v_texCoord; \n" "uniform lowp sampler2D s_texture; \n" "uniform lowp float alpha; \n" "void main() \n" "{ \n" " gl_FragColor = texture2D(s_texture, v_texCoord).bgra * alpha; \n" "} \n"; char fragmentShaderStringMaskRGBA[] = "varying mediump vec2 v_texCoord; \n" "uniform lowp sampler2D s_texture; \n" "uniform lowp sampler2D s_mask; \n" "uniform lowp float alpha; \n" "void main() \n" "{ \n" " lowp vec4 texColor = texture2D(s_texture, v_texCoord); \n" " lowp vec4 maskColor = texture2D(s_mask, v_texCoord); \n" " gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w) * alpha * maskColor.w; \n" "} \n"; char fragmentShaderStringMaskBGRA[] = "varying mediump vec2 v_texCoord; \n" "uniform lowp sampler2D s_texture; \n" "uniform lowp sampler2D s_mask; \n" "uniform lowp float alpha; \n" "void main() \n" "{ \n" " lowp vec4 texColor = texture2D(s_texture, v_texCoord).bgra; \n" " lowp vec4 maskColor = texture2D(s_mask, v_texCoord).bgra; \n" " gl_FragColor = vec4(texColor.x, texColor.y, texColor.z, texColor.w) * alpha * maskColor.w; \n" "} \n"; // Shaders for drawing the debug borders around the layers. char colorVertexShaderString[] = "attribute vec4 a_position; \n" "void main() \n" "{ \n" " gl_Position = a_position; \n" "} \n"; char colorFragmentShaderString[] = "uniform lowp vec4 color; \n" "void main() \n" "{ \n" " gl_FragColor = color; \n" "} \n"; // FIXME: get screen size, get light/dark color, use // string manipulation methods to insert those constants into // the shader source. static Color lightColor(0xfb, 0xfd, 0xff); // checkerboardColorDark() static Color darkColor(0xe8, 0xee, 0xf7); int checkerSize = 20; String tmp( "uniform mediump float scale; \n" "uniform mediump vec2 origin; \n" "uniform mediump float surfaceHeight; \n" "void main() \n" "{ \n" " const mediump float grid = GRID; \n" " const lowp vec4 lightColor = LIGHT_COLOR; \n" " const lowp vec4 darkColor = DARK_COLOR; \n" " mediump float tmp = grid * scale; \n" " gl_FragColor = mod(floor((gl_FragCoord.x + origin.x) / tmp) + floor((surfaceHeight - gl_FragCoord.y + origin.y) / tmp), 2.0) > 0.99 \n" " ? lightColor : darkColor; \n" "} \n"); // Let WTF::String be our preprocessor tmp.replace("LIGHT_COLOR", String::format("vec4(%.4f, %.4f, %.4f, 1.0)", lightColor.red() / 255.0, lightColor.green() / 255.0, lightColor.blue() / 255.0)); tmp.replace("DARK_COLOR", String::format("vec4(%.4f, %.4f, %.4f, 1.0)", darkColor.red() / 255.0, darkColor.green() / 255.0, darkColor.blue() / 255.0)); tmp.replace("GRID", String::format("%.3f", (float)checkerSize)); CString checkerFragmentShaderString = tmp.latin1(); if (!makeContextCurrent()) return false; m_layerProgramObject[LayerData::LayerProgramShaderRGBA] = loadShaderProgram(vertexShaderString, fragmentShaderStringRGBA); if (!m_layerProgramObject[LayerData::LayerProgramShaderRGBA]) { LOG_ERROR("Failed to create shader program for RGBA layers"); return false; } m_layerProgramObject[LayerData::LayerProgramShaderBGRA] = loadShaderProgram(vertexShaderString, fragmentShaderStringBGRA); if (!m_layerProgramObject[LayerData::LayerProgramShaderBGRA]) { LOG_ERROR("Failed to create shader program for BGRA layers"); return false; } m_layerMaskProgramObject[LayerData::LayerProgramShaderRGBA] = loadShaderProgram(vertexShaderString, fragmentShaderStringMaskRGBA); if (!m_layerMaskProgramObject[LayerData::LayerProgramShaderRGBA]) { LOG_ERROR("Failed to create shader mask program for RGBA layers"); return false; } m_layerMaskProgramObject[LayerData::LayerProgramShaderBGRA] = loadShaderProgram(vertexShaderString, fragmentShaderStringMaskBGRA); if (!m_layerMaskProgramObject[LayerData::LayerProgramShaderBGRA]) { LOG_ERROR("Failed to create shader mask program for BGRA layers"); return false; } m_colorProgramObject = loadShaderProgram(colorVertexShaderString, colorFragmentShaderString); if (!m_colorProgramObject) { LOG_ERROR("Failed to create shader program for debug borders"); return false; } m_checkerProgramObject = loadShaderProgram(colorVertexShaderString, checkerFragmentShaderString.data()); if (!m_checkerProgramObject) { LOG_ERROR("Failed to create shader program for checkerboard pattern"); return false; } // Specify the attrib location for the position and make it the same for all three programs to // avoid binding re-binding the vertex attributes. bindCommonAttribLocation(m_positionLocation, "a_position"); bindCommonAttribLocation(m_texCoordLocation, "a_texCoord"); checkGLError(); // Re-link the shaders to get the new attrib location to take effect. for (int i = 0; i < LayerData::NumberOfLayerProgramShaders; ++i) { glLinkProgram(m_layerProgramObject[i]); glLinkProgram(m_layerMaskProgramObject[i]); } glLinkProgram(m_colorProgramObject); glLinkProgram(m_checkerProgramObject); checkGLError(); // Get locations of uniforms for the layer content shader program. for (int i = 0; i < LayerData::NumberOfLayerProgramShaders; ++i) { m_samplerLocation[i] = glGetUniformLocation(m_layerProgramObject[i], "s_texture"); m_alphaLocation[i] = glGetUniformLocation(m_layerProgramObject[i], "alpha"); glUseProgram(m_layerProgramObject[i]); glUniform1i(m_samplerLocation[i], 0); m_maskSamplerLocation[i] = glGetUniformLocation(m_layerMaskProgramObject[i], "s_texture"); m_maskSamplerLocationMask[i] = glGetUniformLocation(m_layerMaskProgramObject[i], "s_mask"); m_maskAlphaLocation[i] = glGetUniformLocation(m_layerMaskProgramObject[i], "alpha"); glUseProgram(m_layerMaskProgramObject[i]); glUniform1i(m_maskSamplerLocation[i], 0); glUniform1i(m_maskSamplerLocationMask[i], 1); } // Get locations of uniforms for the debug border shader program. m_colorColorLocation = glGetUniformLocation(m_colorProgramObject, "color"); // Get locations of uniforms for the checkerboard shader program. m_checkerScaleLocation = glGetUniformLocation(m_checkerProgramObject, "scale"); m_checkerOriginLocation = glGetUniformLocation(m_checkerProgramObject, "origin"); m_checkerSurfaceHeightLocation = glGetUniformLocation(m_checkerProgramObject, "surfaceHeight"); return true; } IntRect LayerRenderingResults::holePunchRect(unsigned index) const { if (index >= m_holePunchRects.size()) return IntRect(); return m_holePunchRects.at(index); } void LayerRenderingResults::addHolePunchRect(const IntRect& rect) { #if DEBUG_VIDEO_CLIPPING BlackBerry::Platform::log(BlackBerry::Platform::LogLevelInfo, "LayerRenderingResults::addHolePunchRect (x=%d,y=%d,width=%d,height=%d).", rect.x(), rect.y(), rect.width(), rect.height()); #endif if (!rect.isEmpty()) m_holePunchRects.append(rect); } void LayerRenderingResults::addDirtyRect(const IntRect& rect) { IntRect dirtyUnion[NumberOfDirtyRects]; int smallestIncrease = INT_MAX; int modifiedRect = 0; for (int i = 0; i < NumberOfDirtyRects; ++i) { dirtyUnion[i] = m_dirtyRects[i]; dirtyUnion[i].unite(rect); int increase = dirtyUnion[i].width()*dirtyUnion[i].height() - m_dirtyRects[i].width()*m_dirtyRects[i].height(); if (increase < smallestIncrease) { smallestIncrease = increase; modifiedRect = i; } } m_dirtyRects[modifiedRect] = dirtyUnion[modifiedRect]; } bool LayerRenderingResults::isEmpty() const { for (int i = 0; i < NumberOfDirtyRects; ++i) { if (!m_dirtyRects[i].isEmpty()) return false; } return true; } } // namespace WebCore #endif // USE(ACCELERATED_COMPOSITING)