// Copyright 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "config.h" #include "CCRendererGL.h" #include "third_party/khronos/GLES2/gl2.h" #include "third_party/khronos/GLES2/gl2ext.h" #include "CCDamageTracker.h" #include "CCLayerQuad.h" #include "CCMathUtil.h" #include "CCProxy.h" #include "CCRenderPass.h" #include "CCRenderSurfaceFilters.h" #include "CCScopedTexture.h" #include "CCSettings.h" #include "CCSingleThreadProxy.h" #include "CCVideoLayerImpl.h" #include "FloatQuad.h" #include "GrTexture.h" #include "NotImplemented.h" #include "base/debug/trace_event.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/logging.h" #include "cc/geometry_binding.h" #include "cc/platform_color.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColor.h" #include #include #include #include #include #include #include using namespace std; using WebKit::WebGraphicsContext3D; using WebKit::WebGraphicsMemoryAllocation; using WebKit::WebSharedGraphicsContext3D; using WebKit::WebTransformationMatrix; namespace cc { namespace { bool needsIOSurfaceReadbackWorkaround() { #if OS(DARWIN) return true; #else return false; #endif } } // anonymous namespace scoped_ptr CCRendererGL::create(CCRendererClient* client, CCResourceProvider* resourceProvider) { scoped_ptr renderer(make_scoped_ptr(new CCRendererGL(client, resourceProvider))); if (!renderer->initialize()) return scoped_ptr(); return renderer.Pass(); } CCRendererGL::CCRendererGL(CCRendererClient* client, CCResourceProvider* resourceProvider) : CCDirectRenderer(client, resourceProvider) , m_offscreenFramebufferId(0) , m_sharedGeometryQuad(FloatRect(-0.5f, -0.5f, 1.0f, 1.0f)) , m_context(resourceProvider->graphicsContext3D()) , m_isViewportChanged(false) , m_isFramebufferDiscarded(false) , m_discardFramebufferWhenNotVisible(false) , m_isUsingBindUniform(false) , m_visible(true) { DCHECK(m_context); } bool CCRendererGL::initialize() { if (!m_context->makeContextCurrent()) return false; m_context->setContextLostCallback(this); m_context->pushGroupMarkerEXT("CompositorContext"); std::string extensionsString = UTF16ToASCII(m_context->getString(GL_EXTENSIONS)); std::vector extensionsList; base::SplitString(extensionsString, ' ', &extensionsList); std::set extensions(extensionsList.begin(), extensionsList.end()); if (settings().acceleratePainting && extensions.count("GL_EXT_texture_format_BGRA8888") && extensions.count("GL_EXT_read_format_bgra")) m_capabilities.usingAcceleratedPainting = true; else m_capabilities.usingAcceleratedPainting = false; m_capabilities.contextHasCachedFrontBuffer = extensions.count("GL_CHROMIUM_front_buffer_cached"); m_capabilities.usingPartialSwap = CCSettings::partialSwapEnabled() && extensions.count("GL_CHROMIUM_post_sub_buffer"); // Use the swapBuffers callback only with the threaded proxy. if (CCProxy::hasImplThread()) m_capabilities.usingSwapCompleteCallback = extensions.count("GL_CHROMIUM_swapbuffers_complete_callback"); if (m_capabilities.usingSwapCompleteCallback) m_context->setSwapBuffersCompleteCallbackCHROMIUM(this); m_capabilities.usingSetVisibility = extensions.count("GL_CHROMIUM_set_visibility"); if (extensions.count("GL_CHROMIUM_iosurface")) DCHECK(extensions.count("GL_ARB_texture_rectangle")); m_capabilities.usingGpuMemoryManager = extensions.count("GL_CHROMIUM_gpu_memory_manager"); if (m_capabilities.usingGpuMemoryManager) m_context->setMemoryAllocationChangedCallbackCHROMIUM(this); m_capabilities.usingDiscardFramebuffer = extensions.count("GL_CHROMIUM_discard_framebuffer"); m_capabilities.usingEglImage = extensions.count("GL_OES_EGL_image_external"); GLC(m_context, m_context->getIntegerv(GL_MAX_TEXTURE_SIZE, &m_capabilities.maxTextureSize)); m_capabilities.bestTextureFormat = PlatformColor::bestTextureFormat(m_context, extensions.count("GL_EXT_texture_format_BGRA8888")); m_isUsingBindUniform = extensions.count("GL_CHROMIUM_bind_uniform_location"); if (!initializeSharedObjects()) return false; // Make sure the viewport and context gets initialized, even if it is to zero. viewportChanged(); return true; } CCRendererGL::~CCRendererGL() { DCHECK(CCProxy::isImplThread()); m_context->setSwapBuffersCompleteCallbackCHROMIUM(0); m_context->setMemoryAllocationChangedCallbackCHROMIUM(0); m_context->setContextLostCallback(0); cleanupSharedObjects(); } const RendererCapabilities& CCRendererGL::capabilities() const { return m_capabilities; } WebGraphicsContext3D* CCRendererGL::context() { return m_context; } void CCRendererGL::debugGLCall(WebGraphicsContext3D* context, const char* command, const char* file, int line) { unsigned long error = context->getError(); if (error != GL_NO_ERROR) LOG(ERROR) << "GL command failed: File: " << file << "\n\tLine " << line << "\n\tcommand: " << command << ", error " << static_cast(error) << "\n"; } void CCRendererGL::setVisible(bool visible) { if (m_visible == visible) return; m_visible = visible; // TODO: Replace setVisibilityCHROMIUM with an extension to explicitly manage front/backbuffers // crbug.com/116049 if (m_capabilities.usingSetVisibility) m_context->setVisibilityCHROMIUM(visible); enforceMemoryPolicy(); } void CCRendererGL::releaseRenderPassTextures() { m_renderPassTextures.clear(); } void CCRendererGL::viewportChanged() { m_isViewportChanged = true; } void CCRendererGL::clearFramebuffer(DrawingFrame& frame) { // On DEBUG builds, opaque render passes are cleared to blue to easily see regions that were not drawn on the screen. if (frame.currentRenderPass->hasTransparentBackground()) GLC(m_context, m_context->clearColor(0, 0, 0, 0)); else GLC(m_context, m_context->clearColor(0, 0, 1, 1)); #ifdef NDEBUG if (frame.currentRenderPass->hasTransparentBackground()) #endif m_context->clear(GL_COLOR_BUFFER_BIT); } void CCRendererGL::beginDrawingFrame(DrawingFrame& frame) { // FIXME: Remove this once framebuffer is automatically recreated on first use ensureFramebuffer(); if (viewportSize().isEmpty()) return; TRACE_EVENT0("cc", "CCRendererGL::drawLayers"); if (m_isViewportChanged) { // Only reshape when we know we are going to draw. Otherwise, the reshape // can leave the window at the wrong size if we never draw and the proper // viewport size is never set. m_isViewportChanged = false; m_context->reshape(viewportWidth(), viewportHeight()); } makeContextCurrent(); // Bind the common vertex attributes used for drawing all the layers. m_sharedGeometry->prepareForDraw(); GLC(m_context, m_context->disable(GL_DEPTH_TEST)); GLC(m_context, m_context->disable(GL_CULL_FACE)); GLC(m_context, m_context->colorMask(true, true, true, true)); GLC(m_context, m_context->enable(GL_BLEND)); GLC(m_context, m_context->blendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); } void CCRendererGL::doNoOp() { GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, 0)); GLC(m_context, m_context->flush()); } void CCRendererGL::drawQuad(DrawingFrame& frame, const CCDrawQuad* quad) { if (quad->needsBlending()) GLC(m_context, m_context->enable(GL_BLEND)); else GLC(m_context, m_context->disable(GL_BLEND)); switch (quad->material()) { case CCDrawQuad::Invalid: NOTREACHED(); break; case CCDrawQuad::Checkerboard: drawCheckerboardQuad(frame, CCCheckerboardDrawQuad::materialCast(quad)); break; case CCDrawQuad::DebugBorder: drawDebugBorderQuad(frame, CCDebugBorderDrawQuad::materialCast(quad)); break; case CCDrawQuad::IOSurfaceContent: drawIOSurfaceQuad(frame, CCIOSurfaceDrawQuad::materialCast(quad)); break; case CCDrawQuad::RenderPass: drawRenderPassQuad(frame, CCRenderPassDrawQuad::materialCast(quad)); break; case CCDrawQuad::SolidColor: drawSolidColorQuad(frame, CCSolidColorDrawQuad::materialCast(quad)); break; case CCDrawQuad::StreamVideoContent: drawStreamVideoQuad(frame, CCStreamVideoDrawQuad::materialCast(quad)); break; case CCDrawQuad::TextureContent: drawTextureQuad(frame, CCTextureDrawQuad::materialCast(quad)); break; case CCDrawQuad::TiledContent: drawTileQuad(frame, CCTileDrawQuad::materialCast(quad)); break; case CCDrawQuad::YUVVideoContent: drawYUVVideoQuad(frame, CCYUVVideoDrawQuad::materialCast(quad)); break; } } void CCRendererGL::drawCheckerboardQuad(const DrawingFrame& frame, const CCCheckerboardDrawQuad* quad) { const TileCheckerboardProgram* program = tileCheckerboardProgram(); DCHECK(program && program->initialized()); GLC(context(), context()->useProgram(program->program())); SkColor color = quad->color(); GLC(context(), context()->uniform4f(program->fragmentShader().colorLocation(), SkColorGetR(color) / 255.0, SkColorGetG(color) / 255.0, SkColorGetB(color) / 255.0, 1)); const int checkerboardWidth = 16; float frequency = 1.0 / checkerboardWidth; IntRect tileRect = quad->quadRect(); float texOffsetX = tileRect.x() % checkerboardWidth; float texOffsetY = tileRect.y() % checkerboardWidth; float texScaleX = tileRect.width(); float texScaleY = tileRect.height(); GLC(context(), context()->uniform4f(program->fragmentShader().texTransformLocation(), texOffsetX, texOffsetY, texScaleX, texScaleY)); GLC(context(), context()->uniform1f(program->fragmentShader().frequencyLocation(), frequency)); setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); } void CCRendererGL::drawDebugBorderQuad(const DrawingFrame& frame, const CCDebugBorderDrawQuad* quad) { static float glMatrix[16]; const SolidColorProgram* program = solidColorProgram(); DCHECK(program && program->initialized()); GLC(context(), context()->useProgram(program->program())); // Use the full quadRect for debug quads to not move the edges based on partial swaps. const IntRect& layerRect = quad->quadRect(); WebTransformationMatrix renderMatrix = quad->quadTransform(); renderMatrix.translate(0.5 * layerRect.width() + layerRect.x(), 0.5 * layerRect.height() + layerRect.y()); renderMatrix.scaleNonUniform(layerRect.width(), layerRect.height()); CCRendererGL::toGLMatrix(&glMatrix[0], frame.projectionMatrix * renderMatrix); GLC(context(), context()->uniformMatrix4fv(program->vertexShader().matrixLocation(), 1, false, &glMatrix[0])); SkColor color = quad->color(); float alpha = SkColorGetA(color) / 255.0; GLC(context(), context()->uniform4f(program->fragmentShader().colorLocation(), (SkColorGetR(color) / 255.0) * alpha, (SkColorGetG(color) / 255.0) * alpha, (SkColorGetB(color) / 255.0) * alpha, alpha)); GLC(context(), context()->lineWidth(quad->width())); // The indices for the line are stored in the same array as the triangle indices. GLC(context(), context()->drawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_SHORT, 6 * sizeof(unsigned short))); } static inline SkBitmap applyFilters(CCRendererGL* renderer, const WebKit::WebFilterOperations& filters, CCScopedTexture* sourceTexture) { if (filters.isEmpty()) return SkBitmap(); WebGraphicsContext3D* filterContext = CCProxy::hasImplThread() ? WebSharedGraphicsContext3D::compositorThreadContext() : WebSharedGraphicsContext3D::mainThreadContext(); GrContext* filterGrContext = CCProxy::hasImplThread() ? WebSharedGraphicsContext3D::compositorThreadGrContext() : WebSharedGraphicsContext3D::mainThreadGrContext(); if (!filterContext || !filterGrContext) return SkBitmap(); renderer->context()->flush(); CCResourceProvider::ScopedWriteLockGL lock(renderer->resourceProvider(), sourceTexture->id()); SkBitmap source = CCRenderSurfaceFilters::apply(filters, lock.textureId(), sourceTexture->size(), filterContext, filterGrContext); return source; } scoped_ptr CCRendererGL::drawBackgroundFilters(DrawingFrame& frame, const CCRenderPassDrawQuad* quad, const WebKit::WebFilterOperations& filters, const WebTransformationMatrix& contentsDeviceTransform) { // This method draws a background filter, which applies a filter to any pixels behind the quad and seen through its background. // The algorithm works as follows: // 1. Compute a bounding box around the pixels that will be visible through the quad. // 2. Read the pixels in the bounding box into a buffer R. // 3. Apply the background filter to R, so that it is applied in the pixels' coordinate space. // 4. Apply the quad's inverse transform to map the pixels in R into the quad's content space. This implicitly // clips R by the content bounds of the quad since the destination texture has bounds matching the quad's content. // 5. Draw the background texture for the contents using the same transform as used to draw the contents itself. This is done // without blending to replace the current background pixels with the new filtered background. // 6. Draw the contents of the quad over drop of the new background with blending, as per usual. The filtered background // pixels will show through any non-opaque pixels in this draws. // // Pixel copies in this algorithm occur at steps 2, 3, 4, and 5. // FIXME: When this algorithm changes, update CCLayerTreeHost::prioritizeTextures() accordingly. if (filters.isEmpty()) return scoped_ptr(); // FIXME: We only allow background filters on an opaque render surface because other surfaces may contain // translucent pixels, and the contents behind those translucent pixels wouldn't have the filter applied. if (frame.currentRenderPass->hasTransparentBackground()) return scoped_ptr(); DCHECK(!frame.currentTexture); // FIXME: Do a single readback for both the surface and replica and cache the filtered results (once filter textures are not reused). IntRect deviceRect = enclosingIntRect(CCMathUtil::mapClippedRect(contentsDeviceTransform, sharedGeometryQuad().boundingBox())); int top, right, bottom, left; filters.getOutsets(top, right, bottom, left); deviceRect.move(-left, -top); deviceRect.expand(left + right, top + bottom); deviceRect.intersect(frame.currentRenderPass->outputRect()); scoped_ptr deviceBackgroundTexture = CCScopedTexture::create(m_resourceProvider); if (!getFramebufferTexture(deviceBackgroundTexture.get(), deviceRect)) return scoped_ptr(); SkBitmap filteredDeviceBackground = applyFilters(this, filters, deviceBackgroundTexture.get()); if (!filteredDeviceBackground.getTexture()) return scoped_ptr(); GrTexture* texture = reinterpret_cast(filteredDeviceBackground.getTexture()); int filteredDeviceBackgroundTextureId = texture->getTextureHandle(); scoped_ptr backgroundTexture = CCScopedTexture::create(m_resourceProvider); if (!backgroundTexture->allocate(CCRenderer::ImplPool, quad->quadRect().size(), GL_RGBA, CCResourceProvider::TextureUsageFramebuffer)) return scoped_ptr(); const CCRenderPass* targetRenderPass = frame.currentRenderPass; bool usingBackgroundTexture = useScopedTexture(frame, backgroundTexture.get(), quad->quadRect()); if (usingBackgroundTexture) { // Copy the readback pixels from device to the background texture for the surface. WebTransformationMatrix deviceToFramebufferTransform; deviceToFramebufferTransform.translate(quad->quadRect().width() / 2.0, quad->quadRect().height() / 2.0); deviceToFramebufferTransform.scale3d(quad->quadRect().width(), quad->quadRect().height(), 1); deviceToFramebufferTransform.multiply(contentsDeviceTransform.inverse()); copyTextureToFramebuffer(frame, filteredDeviceBackgroundTextureId, deviceRect, deviceToFramebufferTransform); } useRenderPass(frame, targetRenderPass); if (!usingBackgroundTexture) return scoped_ptr(); return backgroundTexture.Pass(); } void CCRendererGL::drawRenderPassQuad(DrawingFrame& frame, const CCRenderPassDrawQuad* quad) { CachedTexture* contentsTexture = m_renderPassTextures.get(quad->renderPassId()); if (!contentsTexture || !contentsTexture->id()) return; const CCRenderPass* renderPass = frame.renderPassesById->get(quad->renderPassId()); DCHECK(renderPass); if (!renderPass) return; WebTransformationMatrix quadRectMatrix; quadRectTransform(&quadRectMatrix, quad->quadTransform(), quad->quadRect()); WebTransformationMatrix contentsDeviceTransform = (frame.windowMatrix * frame.projectionMatrix * quadRectMatrix).to2dTransform(); // Can only draw surface if device matrix is invertible. if (!contentsDeviceTransform.isInvertible()) return; scoped_ptr backgroundTexture = drawBackgroundFilters(frame, quad, renderPass->backgroundFilters(), contentsDeviceTransform); // FIXME: Cache this value so that we don't have to do it for both the surface and its replica. // Apply filters to the contents texture. SkBitmap filterBitmap = applyFilters(this, renderPass->filters(), contentsTexture); scoped_ptr contentsResourceLock; unsigned contentsTextureId = 0; if (filterBitmap.getTexture()) { GrTexture* texture = reinterpret_cast(filterBitmap.getTexture()); contentsTextureId = texture->getTextureHandle(); } else { contentsResourceLock = make_scoped_ptr(new CCResourceProvider::ScopedReadLockGL(m_resourceProvider, contentsTexture->id())); contentsTextureId = contentsResourceLock->textureId(); } // Draw the background texture if there is one. if (backgroundTexture) { DCHECK(backgroundTexture->size() == quad->quadRect().size()); CCResourceProvider::ScopedReadLockGL lock(m_resourceProvider, backgroundTexture->id()); copyTextureToFramebuffer(frame, lock.textureId(), quad->quadRect(), quad->quadTransform()); } bool clipped = false; FloatQuad deviceQuad = CCMathUtil::mapQuad(contentsDeviceTransform, sharedGeometryQuad(), clipped); DCHECK(!clipped); CCLayerQuad deviceLayerBounds = CCLayerQuad(FloatQuad(deviceQuad.boundingBox())); CCLayerQuad deviceLayerEdges = CCLayerQuad(deviceQuad); // Use anti-aliasing programs only when necessary. bool useAA = (!deviceQuad.isRectilinear() || !deviceQuad.boundingBox().isExpressibleAsIntRect()); if (useAA) { deviceLayerBounds.inflateAntiAliasingDistance(); deviceLayerEdges.inflateAntiAliasingDistance(); } scoped_ptr maskResourceLock; unsigned maskTextureId = 0; if (quad->maskResourceId()) { maskResourceLock.reset(new CCResourceProvider::ScopedReadLockGL(m_resourceProvider, quad->maskResourceId())); maskTextureId = maskResourceLock->textureId(); } // FIXME: use the backgroundTexture and blend the background in with this draw instead of having a separate copy of the background texture. GLC(context(), context()->activeTexture(GL_TEXTURE0)); context()->bindTexture(GL_TEXTURE_2D, contentsTextureId); int shaderQuadLocation = -1; int shaderEdgeLocation = -1; int shaderMaskSamplerLocation = -1; int shaderMaskTexCoordScaleLocation = -1; int shaderMaskTexCoordOffsetLocation = -1; int shaderMatrixLocation = -1; int shaderAlphaLocation = -1; if (useAA && maskTextureId) { const RenderPassMaskProgramAA* program = renderPassMaskProgramAA(); GLC(context(), context()->useProgram(program->program())); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); shaderQuadLocation = program->vertexShader().pointLocation(); shaderEdgeLocation = program->fragmentShader().edgeLocation(); shaderMaskSamplerLocation = program->fragmentShader().maskSamplerLocation(); shaderMaskTexCoordScaleLocation = program->fragmentShader().maskTexCoordScaleLocation(); shaderMaskTexCoordOffsetLocation = program->fragmentShader().maskTexCoordOffsetLocation(); shaderMatrixLocation = program->vertexShader().matrixLocation(); shaderAlphaLocation = program->fragmentShader().alphaLocation(); } else if (!useAA && maskTextureId) { const RenderPassMaskProgram* program = renderPassMaskProgram(); GLC(context(), context()->useProgram(program->program())); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); shaderMaskSamplerLocation = program->fragmentShader().maskSamplerLocation(); shaderMaskTexCoordScaleLocation = program->fragmentShader().maskTexCoordScaleLocation(); shaderMaskTexCoordOffsetLocation = program->fragmentShader().maskTexCoordOffsetLocation(); shaderMatrixLocation = program->vertexShader().matrixLocation(); shaderAlphaLocation = program->fragmentShader().alphaLocation(); } else if (useAA && !maskTextureId) { const RenderPassProgramAA* program = renderPassProgramAA(); GLC(context(), context()->useProgram(program->program())); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); shaderQuadLocation = program->vertexShader().pointLocation(); shaderEdgeLocation = program->fragmentShader().edgeLocation(); shaderMatrixLocation = program->vertexShader().matrixLocation(); shaderAlphaLocation = program->fragmentShader().alphaLocation(); } else { const RenderPassProgram* program = renderPassProgram(); GLC(context(), context()->useProgram(program->program())); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); shaderMatrixLocation = program->vertexShader().matrixLocation(); shaderAlphaLocation = program->fragmentShader().alphaLocation(); } if (shaderMaskSamplerLocation != -1) { DCHECK(shaderMaskTexCoordScaleLocation != 1); DCHECK(shaderMaskTexCoordOffsetLocation != 1); GLC(context(), context()->activeTexture(GL_TEXTURE1)); GLC(context(), context()->uniform1i(shaderMaskSamplerLocation, 1)); GLC(context(), context()->uniform2f(shaderMaskTexCoordScaleLocation, quad->maskTexCoordScaleX(), quad->maskTexCoordScaleY())); GLC(context(), context()->uniform2f(shaderMaskTexCoordOffsetLocation, quad->maskTexCoordOffsetX(), quad->maskTexCoordOffsetY())); context()->bindTexture(GL_TEXTURE_2D, maskTextureId); GLC(context(), context()->activeTexture(GL_TEXTURE0)); } if (shaderEdgeLocation != -1) { float edge[24]; deviceLayerEdges.toFloatArray(edge); deviceLayerBounds.toFloatArray(&edge[12]); GLC(context(), context()->uniform3fv(shaderEdgeLocation, 8, edge)); } // Map device space quad to surface space. contentsDeviceTransform has no 3d component since it was generated with to2dTransform() so we don't need to project. FloatQuad surfaceQuad = CCMathUtil::mapQuad(contentsDeviceTransform.inverse(), deviceLayerEdges.floatQuad(), clipped); DCHECK(!clipped); setShaderOpacity(quad->opacity(), shaderAlphaLocation); setShaderFloatQuad(surfaceQuad, shaderQuadLocation); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), shaderMatrixLocation); } void CCRendererGL::drawSolidColorQuad(const DrawingFrame& frame, const CCSolidColorDrawQuad* quad) { const SolidColorProgram* program = solidColorProgram(); GLC(context(), context()->useProgram(program->program())); SkColor color = quad->color(); float opacity = quad->opacity(); float alpha = (SkColorGetA(color) / 255.0) * opacity; GLC(context(), context()->uniform4f(program->fragmentShader().colorLocation(), (SkColorGetR(color) / 255.0) * alpha, (SkColorGetG(color) / 255.0) * alpha, (SkColorGetB(color) / 255.0) * alpha, alpha)); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); } struct TileProgramUniforms { unsigned program; unsigned samplerLocation; unsigned vertexTexTransformLocation; unsigned fragmentTexTransformLocation; unsigned edgeLocation; unsigned matrixLocation; unsigned alphaLocation; unsigned pointLocation; }; template static void tileUniformLocation(T program, TileProgramUniforms& uniforms) { uniforms.program = program->program(); uniforms.vertexTexTransformLocation = program->vertexShader().vertexTexTransformLocation(); uniforms.matrixLocation = program->vertexShader().matrixLocation(); uniforms.pointLocation = program->vertexShader().pointLocation(); uniforms.samplerLocation = program->fragmentShader().samplerLocation(); uniforms.alphaLocation = program->fragmentShader().alphaLocation(); uniforms.fragmentTexTransformLocation = program->fragmentShader().fragmentTexTransformLocation(); uniforms.edgeLocation = program->fragmentShader().edgeLocation(); } void CCRendererGL::drawTileQuad(const DrawingFrame& frame, const CCTileDrawQuad* quad) { IntRect tileRect = quad->quadVisibleRect(); FloatRect clampRect(tileRect); // Clamp texture coordinates to avoid sampling outside the layer // by deflating the tile region half a texel or half a texel // minus epsilon for one pixel layers. The resulting clamp region // is mapped to the unit square by the vertex shader and mapped // back to normalized texture coordinates by the fragment shader // after being clamped to 0-1 range. const float epsilon = 1 / 1024.0f; float clampX = min(0.5, clampRect.width() / 2.0 - epsilon); float clampY = min(0.5, clampRect.height() / 2.0 - epsilon); clampRect.inflateX(-clampX); clampRect.inflateY(-clampY); FloatSize clampOffset = clampRect.minXMinYCorner() - FloatRect(tileRect).minXMinYCorner(); FloatPoint textureOffset = quad->textureOffset() + clampOffset + IntPoint(tileRect.location() - quad->quadRect().location()); // Map clamping rectangle to unit square. float vertexTexTranslateX = -clampRect.x() / clampRect.width(); float vertexTexTranslateY = -clampRect.y() / clampRect.height(); float vertexTexScaleX = tileRect.width() / clampRect.width(); float vertexTexScaleY = tileRect.height() / clampRect.height(); // Map to normalized texture coordinates. const IntSize& textureSize = quad->textureSize(); float fragmentTexTranslateX = textureOffset.x() / textureSize.width(); float fragmentTexTranslateY = textureOffset.y() / textureSize.height(); float fragmentTexScaleX = clampRect.width() / textureSize.width(); float fragmentTexScaleY = clampRect.height() / textureSize.height(); FloatQuad localQuad; WebTransformationMatrix deviceTransform = WebTransformationMatrix(frame.windowMatrix * frame.projectionMatrix * quad->quadTransform()).to2dTransform(); if (!deviceTransform.isInvertible()) return; bool clipped = false; FloatQuad deviceLayerQuad = CCMathUtil::mapQuad(deviceTransform, FloatQuad(quad->visibleContentRect()), clipped); DCHECK(!clipped); TileProgramUniforms uniforms; // For now, we simply skip anti-aliasing with the quad is clipped. This only happens // on perspective transformed layers that go partially behind the camera. if (quad->isAntialiased() && !clipped) { if (quad->swizzleContents()) tileUniformLocation(tileProgramSwizzleAA(), uniforms); else tileUniformLocation(tileProgramAA(), uniforms); } else { if (quad->needsBlending()) { if (quad->swizzleContents()) tileUniformLocation(tileProgramSwizzle(), uniforms); else tileUniformLocation(tileProgram(), uniforms); } else { if (quad->swizzleContents()) tileUniformLocation(tileProgramSwizzleOpaque(), uniforms); else tileUniformLocation(tileProgramOpaque(), uniforms); } } GLC(context(), context()->useProgram(uniforms.program)); GLC(context(), context()->uniform1i(uniforms.samplerLocation, 0)); GLC(context(), context()->activeTexture(GL_TEXTURE0)); CCResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resourceId()); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, quadResourceLock.textureId())); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, quad->textureFilter())); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, quad->textureFilter())); bool useAA = !clipped && quad->isAntialiased(); if (useAA) { CCLayerQuad deviceLayerBounds = CCLayerQuad(FloatQuad(deviceLayerQuad.boundingBox())); deviceLayerBounds.inflateAntiAliasingDistance(); CCLayerQuad deviceLayerEdges = CCLayerQuad(deviceLayerQuad); deviceLayerEdges.inflateAntiAliasingDistance(); float edge[24]; deviceLayerEdges.toFloatArray(edge); deviceLayerBounds.toFloatArray(&edge[12]); GLC(context(), context()->uniform3fv(uniforms.edgeLocation, 8, edge)); GLC(context(), context()->uniform4f(uniforms.vertexTexTransformLocation, vertexTexTranslateX, vertexTexTranslateY, vertexTexScaleX, vertexTexScaleY)); GLC(context(), context()->uniform4f(uniforms.fragmentTexTransformLocation, fragmentTexTranslateX, fragmentTexTranslateY, fragmentTexScaleX, fragmentTexScaleY)); FloatPoint bottomRight(tileRect.maxX(), tileRect.maxY()); FloatPoint bottomLeft(tileRect.x(), tileRect.maxY()); FloatPoint topLeft(tileRect.x(), tileRect.y()); FloatPoint topRight(tileRect.maxX(), tileRect.y()); // Map points to device space. bottomRight = CCMathUtil::mapPoint(deviceTransform, bottomRight, clipped); DCHECK(!clipped); bottomLeft = CCMathUtil::mapPoint(deviceTransform, bottomLeft, clipped); DCHECK(!clipped); topLeft = CCMathUtil::mapPoint(deviceTransform, topLeft, clipped); DCHECK(!clipped); topRight = CCMathUtil::mapPoint(deviceTransform, topRight, clipped); DCHECK(!clipped); CCLayerQuad::Edge bottomEdge(bottomRight, bottomLeft); CCLayerQuad::Edge leftEdge(bottomLeft, topLeft); CCLayerQuad::Edge topEdge(topLeft, topRight); CCLayerQuad::Edge rightEdge(topRight, bottomRight); // Only apply anti-aliasing to edges not clipped by culling or scissoring. if (quad->topEdgeAA() && tileRect.y() == quad->quadRect().y()) topEdge = deviceLayerEdges.top(); if (quad->leftEdgeAA() && tileRect.x() == quad->quadRect().x()) leftEdge = deviceLayerEdges.left(); if (quad->rightEdgeAA() && tileRect.maxX() == quad->quadRect().maxX()) rightEdge = deviceLayerEdges.right(); if (quad->bottomEdgeAA() && tileRect.maxY() == quad->quadRect().maxY()) bottomEdge = deviceLayerEdges.bottom(); float sign = FloatQuad(tileRect).isCounterclockwise() ? -1 : 1; bottomEdge.scale(sign); leftEdge.scale(sign); topEdge.scale(sign); rightEdge.scale(sign); // Create device space quad. CCLayerQuad deviceQuad(leftEdge, topEdge, rightEdge, bottomEdge); // Map device space quad to local space. contentsDeviceTransform has no 3d component since it was generated with to2dTransform() so we don't need to project. WebTransformationMatrix inverseDeviceTransform = deviceTransform.inverse(); localQuad = CCMathUtil::mapQuad(inverseDeviceTransform, deviceQuad.floatQuad(), clipped); // We should not DCHECK(!clipped) here, because anti-aliasing inflation may cause deviceQuad to become // clipped. To our knowledge this scenario does not need to be handled differently than the unclipped case. } else { // Move fragment shader transform to vertex shader. We can do this while // still producing correct results as fragmentTexTransformLocation // should always be non-negative when tiles are transformed in a way // that could result in sampling outside the layer. vertexTexScaleX *= fragmentTexScaleX; vertexTexScaleY *= fragmentTexScaleY; vertexTexTranslateX *= fragmentTexScaleX; vertexTexTranslateY *= fragmentTexScaleY; vertexTexTranslateX += fragmentTexTranslateX; vertexTexTranslateY += fragmentTexTranslateY; GLC(context(), context()->uniform4f(uniforms.vertexTexTransformLocation, vertexTexTranslateX, vertexTexTranslateY, vertexTexScaleX, vertexTexScaleY)); localQuad = FloatRect(tileRect); } // Normalize to tileRect. localQuad.scale(1.0f / tileRect.width(), 1.0f / tileRect.height()); setShaderOpacity(quad->opacity(), uniforms.alphaLocation); setShaderFloatQuad(localQuad, uniforms.pointLocation); // The tile quad shader behaves differently compared to all other shaders. // The transform and vertex data are used to figure out the extents that the // un-antialiased quad should have and which vertex this is and the float // quad passed in via uniform is the actual geometry that gets used to draw // it. This is why this centered rect is used and not the original quadRect. FloatRect centeredRect(FloatPoint(-0.5 * tileRect.width(), -0.5 * tileRect.height()), tileRect.size()); drawQuadGeometry(frame, quad->quadTransform(), centeredRect, uniforms.matrixLocation); } void CCRendererGL::drawYUVVideoQuad(const DrawingFrame& frame, const CCYUVVideoDrawQuad* quad) { const VideoYUVProgram* program = videoYUVProgram(); DCHECK(program && program->initialized()); const CCVideoLayerImpl::FramePlane& yPlane = quad->yPlane(); const CCVideoLayerImpl::FramePlane& uPlane = quad->uPlane(); const CCVideoLayerImpl::FramePlane& vPlane = quad->vPlane(); CCResourceProvider::ScopedReadLockGL yPlaneLock(m_resourceProvider, yPlane.resourceId); CCResourceProvider::ScopedReadLockGL uPlaneLock(m_resourceProvider, uPlane.resourceId); CCResourceProvider::ScopedReadLockGL vPlaneLock(m_resourceProvider, vPlane.resourceId); GLC(context(), context()->activeTexture(GL_TEXTURE1)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, yPlaneLock.textureId())); GLC(context(), context()->activeTexture(GL_TEXTURE2)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, uPlaneLock.textureId())); GLC(context(), context()->activeTexture(GL_TEXTURE3)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, vPlaneLock.textureId())); GLC(context(), context()->useProgram(program->program())); float yWidthScaleFactor = static_cast(yPlane.visibleSize.width()) / yPlane.size.width(); // Arbitrarily take the u sizes because u and v dimensions are identical. float uvWidthScaleFactor = static_cast(uPlane.visibleSize.width()) / uPlane.size.width(); GLC(context(), context()->uniform1f(program->vertexShader().yWidthScaleFactorLocation(), yWidthScaleFactor)); GLC(context(), context()->uniform1f(program->vertexShader().uvWidthScaleFactorLocation(), uvWidthScaleFactor)); GLC(context(), context()->uniform1i(program->fragmentShader().yTextureLocation(), 1)); GLC(context(), context()->uniform1i(program->fragmentShader().uTextureLocation(), 2)); GLC(context(), context()->uniform1i(program->fragmentShader().vTextureLocation(), 3)); // These values are magic numbers that are used in the transformation from YUV to RGB color values. // They are taken from the following webpage: http://www.fourcc.org/fccyvrgb.php float yuv2RGB[9] = { 1.164f, 1.164f, 1.164f, 0.f, -.391f, 2.018f, 1.596f, -.813f, 0.f, }; GLC(context(), context()->uniformMatrix3fv(program->fragmentShader().ccMatrixLocation(), 1, 0, yuv2RGB)); // These values map to 16, 128, and 128 respectively, and are computed // as a fraction over 256 (e.g. 16 / 256 = 0.0625). // They are used in the YUV to RGBA conversion formula: // Y - 16 : Gives 16 values of head and footroom for overshooting // U - 128 : Turns unsigned U into signed U [-128,127] // V - 128 : Turns unsigned V into signed V [-128,127] float yuvAdjust[3] = { -0.0625f, -0.5f, -0.5f, }; GLC(context(), context()->uniform3fv(program->fragmentShader().yuvAdjLocation(), 1, yuvAdjust)); setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); // Reset active texture back to texture 0. GLC(context(), context()->activeTexture(GL_TEXTURE0)); } void CCRendererGL::drawStreamVideoQuad(const DrawingFrame& frame, const CCStreamVideoDrawQuad* quad) { static float glMatrix[16]; DCHECK(m_capabilities.usingEglImage); const VideoStreamTextureProgram* program = videoStreamTextureProgram(); GLC(context(), context()->useProgram(program->program())); toGLMatrix(&glMatrix[0], quad->matrix()); GLC(context(), context()->uniformMatrix4fv(program->vertexShader().texMatrixLocation(), 1, false, glMatrix)); GLC(context(), context()->activeTexture(GL_TEXTURE0)); GLC(context(), context()->bindTexture(GL_TEXTURE_EXTERNAL_OES, quad->textureId())); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); setShaderOpacity(quad->opacity(), program->fragmentShader().alphaLocation()); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), program->vertexShader().matrixLocation()); } struct TextureProgramBinding { template void set(Program* program) { DCHECK(program && program->initialized()); programId = program->program(); samplerLocation = program->fragmentShader().samplerLocation(); matrixLocation = program->vertexShader().matrixLocation(); alphaLocation = program->fragmentShader().alphaLocation(); } int programId; int samplerLocation; int matrixLocation; int alphaLocation; }; struct TexTransformTextureProgramBinding : TextureProgramBinding { template void set(Program* program) { TextureProgramBinding::set(program); texTransformLocation = program->vertexShader().texTransformLocation(); } int texTransformLocation; }; void CCRendererGL::drawTextureQuad(const DrawingFrame& frame, const CCTextureDrawQuad* quad) { DCHECK(CCProxy::isImplThread()); TexTransformTextureProgramBinding binding; if (quad->flipped()) binding.set(textureProgramFlip()); else binding.set(textureProgram()); GLC(context(), context()->useProgram(binding.programId)); GLC(context(), context()->uniform1i(binding.samplerLocation, 0)); const FloatRect& uvRect = quad->uvRect(); GLC(context(), context()->uniform4f(binding.texTransformLocation, uvRect.x(), uvRect.y(), uvRect.width(), uvRect.height())); GLC(context(), context()->activeTexture(GL_TEXTURE0)); CCResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resourceId()); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, quadResourceLock.textureId())); // FIXME: setting the texture parameters every time is redundant. Move this code somewhere // where it will only happen once per texture. GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); if (!quad->premultipliedAlpha()) { // As it turns out, the premultiplied alpha blending function (ONE, ONE_MINUS_SRC_ALPHA) // will never cause the alpha channel to be set to anything less than 1.0 if it is // initialized to that value! Therefore, premultipliedAlpha being false is the first // situation we can generally see an alpha channel less than 1.0 coming out of the // compositor. This is causing platform differences in some layout tests (see // https://bugs.webkit.org/show_bug.cgi?id=82412), so in this situation, use a separate // blend function for the alpha channel to avoid modifying it. Don't use colorMask for this // as it has performance implications on some platforms. GLC(context(), context()->blendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)); } setShaderOpacity(quad->opacity(), binding.alphaLocation); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), binding.matrixLocation); if (!quad->premultipliedAlpha()) GLC(m_context, m_context->blendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); } void CCRendererGL::drawIOSurfaceQuad(const DrawingFrame& frame, const CCIOSurfaceDrawQuad* quad) { DCHECK(CCProxy::isImplThread()); TexTransformTextureProgramBinding binding; binding.set(textureIOSurfaceProgram()); GLC(context(), context()->useProgram(binding.programId)); GLC(context(), context()->uniform1i(binding.samplerLocation, 0)); if (quad->orientation() == CCIOSurfaceDrawQuad::Flipped) GLC(context(), context()->uniform4f(binding.texTransformLocation, 0, quad->ioSurfaceSize().height(), quad->ioSurfaceSize().width(), quad->ioSurfaceSize().height() * -1.0)); else GLC(context(), context()->uniform4f(binding.texTransformLocation, 0, 0, quad->ioSurfaceSize().width(), quad->ioSurfaceSize().height())); GLC(context(), context()->activeTexture(GL_TEXTURE0)); GLC(context(), context()->bindTexture(GL_TEXTURE_RECTANGLE_ARB, quad->ioSurfaceTextureId())); setShaderOpacity(quad->opacity(), binding.alphaLocation); drawQuadGeometry(frame, quad->quadTransform(), quad->quadRect(), binding.matrixLocation); GLC(context(), context()->bindTexture(GL_TEXTURE_RECTANGLE_ARB, 0)); } void CCRendererGL::finishDrawingFrame(DrawingFrame& frame) { m_currentFramebufferLock.reset(); m_swapBufferRect.unite(enclosingIntRect(frame.rootDamageRect)); GLC(m_context, m_context->disable(GL_SCISSOR_TEST)); GLC(m_context, m_context->disable(GL_BLEND)); } bool CCRendererGL::flippedFramebuffer() const { return true; } void CCRendererGL::toGLMatrix(float* flattened, const WebTransformationMatrix& m) { flattened[0] = m.m11(); flattened[1] = m.m12(); flattened[2] = m.m13(); flattened[3] = m.m14(); flattened[4] = m.m21(); flattened[5] = m.m22(); flattened[6] = m.m23(); flattened[7] = m.m24(); flattened[8] = m.m31(); flattened[9] = m.m32(); flattened[10] = m.m33(); flattened[11] = m.m34(); flattened[12] = m.m41(); flattened[13] = m.m42(); flattened[14] = m.m43(); flattened[15] = m.m44(); } void CCRendererGL::setShaderFloatQuad(const FloatQuad& quad, int quadLocation) { if (quadLocation == -1) return; float point[8]; point[0] = quad.p1().x(); point[1] = quad.p1().y(); point[2] = quad.p2().x(); point[3] = quad.p2().y(); point[4] = quad.p3().x(); point[5] = quad.p3().y(); point[6] = quad.p4().x(); point[7] = quad.p4().y(); GLC(m_context, m_context->uniform2fv(quadLocation, 4, point)); } void CCRendererGL::setShaderOpacity(float opacity, int alphaLocation) { if (alphaLocation != -1) GLC(m_context, m_context->uniform1f(alphaLocation, opacity)); } void CCRendererGL::drawQuadGeometry(const DrawingFrame& frame, const WebKit::WebTransformationMatrix& drawTransform, const FloatRect& quadRect, int matrixLocation) { WebTransformationMatrix quadRectMatrix; quadRectTransform(&quadRectMatrix, drawTransform, quadRect); static float glMatrix[16]; toGLMatrix(&glMatrix[0], frame.projectionMatrix * quadRectMatrix); GLC(m_context, m_context->uniformMatrix4fv(matrixLocation, 1, false, &glMatrix[0])); GLC(m_context, m_context->drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0)); } void CCRendererGL::copyTextureToFramebuffer(const DrawingFrame& frame, int textureId, const IntRect& rect, const WebTransformationMatrix& drawMatrix) { const RenderPassProgram* program = renderPassProgram(); GLC(context(), context()->activeTexture(GL_TEXTURE0)); GLC(context(), context()->bindTexture(GL_TEXTURE_2D, textureId)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLC(context(), context()->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GLC(context(), context()->useProgram(program->program())); GLC(context(), context()->uniform1i(program->fragmentShader().samplerLocation(), 0)); setShaderOpacity(1, program->fragmentShader().alphaLocation()); drawQuadGeometry(frame, drawMatrix, rect, program->vertexShader().matrixLocation()); } void CCRendererGL::finish() { TRACE_EVENT0("cc", "CCRendererGL::finish"); m_context->finish(); } bool CCRendererGL::swapBuffers() { DCHECK(m_visible); DCHECK(!m_isFramebufferDiscarded); TRACE_EVENT0("cc", "CCRendererGL::swapBuffers"); // We're done! Time to swapbuffers! if (m_capabilities.usingPartialSwap) { // If supported, we can save significant bandwidth by only swapping the damaged/scissored region (clamped to the viewport) m_swapBufferRect.intersect(IntRect(IntPoint(), viewportSize())); int flippedYPosOfRectBottom = viewportHeight() - m_swapBufferRect.y() - m_swapBufferRect.height(); m_context->postSubBufferCHROMIUM(m_swapBufferRect.x(), flippedYPosOfRectBottom, m_swapBufferRect.width(), m_swapBufferRect.height()); } else { // Note that currently this has the same effect as swapBuffers; we should // consider exposing a different entry point on WebGraphicsContext3D. m_context->prepareTexture(); } m_swapBufferRect = IntRect(); return true; } void CCRendererGL::onSwapBuffersComplete() { m_client->onSwapBuffersComplete(); } void CCRendererGL::onMemoryAllocationChanged(WebGraphicsMemoryAllocation allocation) { // FIXME: This is called on the main thread in single threaded mode, but we expect it on the impl thread. if (!CCProxy::hasImplThread()) { DCHECK(CCProxy::isMainThread()); DebugScopedSetImplThread impl; onMemoryAllocationChangedOnImplThread(allocation); } else { DCHECK(CCProxy::isImplThread()); onMemoryAllocationChangedOnImplThread(allocation); } } void CCRendererGL::onMemoryAllocationChangedOnImplThread(WebKit::WebGraphicsMemoryAllocation allocation) { m_discardFramebufferWhenNotVisible = !allocation.suggestHaveBackbuffer; // Just ignore the memory manager when it says to set the limit to zero // bytes. This will happen when the memory manager thinks that the renderer // is not visible (which the renderer knows better). if (allocation.gpuResourceSizeInBytes) m_client->setMemoryAllocationLimitBytes(allocation.gpuResourceSizeInBytes); enforceMemoryPolicy(); } void CCRendererGL::enforceMemoryPolicy() { if (!m_visible) { TRACE_EVENT0("cc", "CCRendererGL::enforceMemoryPolicy dropping resources"); releaseRenderPassTextures(); if (m_discardFramebufferWhenNotVisible) discardFramebuffer(); GLC(m_context, m_context->flush()); } } void CCRendererGL::discardFramebuffer() { if (m_isFramebufferDiscarded) return; if (!m_capabilities.usingDiscardFramebuffer) return; // FIXME: Update attachments argument to appropriate values once they are no longer ignored. m_context->discardFramebufferEXT(GL_TEXTURE_2D, 0, 0); m_isFramebufferDiscarded = true; // Damage tracker needs a full reset every time framebuffer is discarded. m_client->setFullRootLayerDamage(); } void CCRendererGL::ensureFramebuffer() { if (!m_isFramebufferDiscarded) return; if (!m_capabilities.usingDiscardFramebuffer) return; m_context->ensureFramebufferCHROMIUM(); m_isFramebufferDiscarded = false; } void CCRendererGL::onContextLost() { m_client->didLoseContext(); } void CCRendererGL::getFramebufferPixels(void *pixels, const IntRect& rect) { DCHECK(rect.maxX() <= viewportWidth()); DCHECK(rect.maxY() <= viewportHeight()); if (!pixels) return; makeContextCurrent(); bool doWorkaround = needsIOSurfaceReadbackWorkaround(); GLuint temporaryTexture = 0; GLuint temporaryFBO = 0; if (doWorkaround) { // On Mac OS X, calling glReadPixels against an FBO whose color attachment is an // IOSurface-backed texture causes corruption of future glReadPixels calls, even those on // different OpenGL contexts. It is believed that this is the root cause of top crasher // http://crbug.com/99393. temporaryTexture = m_context->createTexture(); GLC(m_context, m_context->bindTexture(GL_TEXTURE_2D, temporaryTexture)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLC(m_context, m_context->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); // Copy the contents of the current (IOSurface-backed) framebuffer into a temporary texture. GLC(m_context, m_context->copyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, viewportSize().width(), viewportSize().height(), 0)); temporaryFBO = m_context->createFramebuffer(); // Attach this texture to an FBO, and perform the readback from that FBO. GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, temporaryFBO)); GLC(m_context, m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, temporaryTexture, 0)); DCHECK(m_context->checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); } scoped_array srcPixels(new uint8_t[rect.width() * rect.height() * 4]); GLC(m_context, m_context->readPixels(rect.x(), viewportSize().height() - rect.maxY(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE, srcPixels.get())); uint8_t* destPixels = static_cast(pixels); size_t rowBytes = rect.width() * 4; int numRows = rect.height(); size_t totalBytes = numRows * rowBytes; for (size_t destY = 0; destY < totalBytes; destY += rowBytes) { // Flip Y axis. size_t srcY = totalBytes - destY - rowBytes; // Swizzle BGRA -> RGBA. for (size_t x = 0; x < rowBytes; x += 4) { destPixels[destY + (x+0)] = srcPixels.get()[srcY + (x+2)]; destPixels[destY + (x+1)] = srcPixels.get()[srcY + (x+1)]; destPixels[destY + (x+2)] = srcPixels.get()[srcY + (x+0)]; destPixels[destY + (x+3)] = srcPixels.get()[srcY + (x+3)]; } } if (doWorkaround) { // Clean up. GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, 0)); GLC(m_context, m_context->bindTexture(GL_TEXTURE_2D, 0)); GLC(m_context, m_context->deleteFramebuffer(temporaryFBO)); GLC(m_context, m_context->deleteTexture(temporaryTexture)); } enforceMemoryPolicy(); } bool CCRendererGL::getFramebufferTexture(CCScopedTexture* texture, const IntRect& deviceRect) { DCHECK(!texture->id() || (texture->size() == deviceRect.size() && texture->format() == GL_RGB)); if (!texture->id() && !texture->allocate(CCRenderer::ImplPool, deviceRect.size(), GL_RGB, CCResourceProvider::TextureUsageAny)) return false; CCResourceProvider::ScopedWriteLockGL lock(m_resourceProvider, texture->id()); GLC(m_context, m_context->bindTexture(GL_TEXTURE_2D, lock.textureId())); GLC(m_context, m_context->copyTexImage2D(GL_TEXTURE_2D, 0, texture->format(), deviceRect.x(), deviceRect.y(), deviceRect.width(), deviceRect.height(), 0)); return true; } bool CCRendererGL::useScopedTexture(DrawingFrame& frame, const CCScopedTexture* texture, const IntRect& viewportRect) { DCHECK(texture->id()); frame.currentRenderPass = 0; frame.currentTexture = texture; return bindFramebufferToTexture(frame, texture, viewportRect); } void CCRendererGL::bindFramebufferToOutputSurface(DrawingFrame& frame) { m_currentFramebufferLock.reset(); GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, 0)); } bool CCRendererGL::bindFramebufferToTexture(DrawingFrame& frame, const CCScopedTexture* texture, const IntRect& framebufferRect) { DCHECK(texture->id()); GLC(m_context, m_context->bindFramebuffer(GL_FRAMEBUFFER, m_offscreenFramebufferId)); m_currentFramebufferLock = make_scoped_ptr(new CCResourceProvider::ScopedWriteLockGL(m_resourceProvider, texture->id())); unsigned textureId = m_currentFramebufferLock->textureId(); GLC(m_context, m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0)); DCHECK(m_context->checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); initializeMatrices(frame, framebufferRect, false); setDrawViewportSize(framebufferRect.size()); return true; } void CCRendererGL::enableScissorTestRect(const IntRect& scissorRect) { GLC(m_context, m_context->enable(GL_SCISSOR_TEST)); GLC(m_context, m_context->scissor(scissorRect.x(), scissorRect.y(), scissorRect.width(), scissorRect.height())); } void CCRendererGL::disableScissorTest() { GLC(m_context, m_context->disable(GL_SCISSOR_TEST)); } void CCRendererGL::setDrawViewportSize(const IntSize& viewportSize) { GLC(m_context, m_context->viewport(0, 0, viewportSize.width(), viewportSize.height())); } bool CCRendererGL::makeContextCurrent() { return m_context->makeContextCurrent(); } bool CCRendererGL::initializeSharedObjects() { TRACE_EVENT0("cc", "CCRendererGL::initializeSharedObjects"); makeContextCurrent(); // Create an FBO for doing offscreen rendering. GLC(m_context, m_offscreenFramebufferId = m_context->createFramebuffer()); // We will always need these programs to render, so create the programs eagerly so that the shader compilation can // start while we do other work. Other programs are created lazily on first access. m_sharedGeometry = make_scoped_ptr(new GeometryBinding(m_context, quadVertexRect())); m_renderPassProgram = make_scoped_ptr(new RenderPassProgram(m_context)); m_tileProgram = make_scoped_ptr(new TileProgram(m_context)); m_tileProgramOpaque = make_scoped_ptr(new TileProgramOpaque(m_context)); GLC(m_context, m_context->flush()); return true; } const CCRendererGL::TileCheckerboardProgram* CCRendererGL::tileCheckerboardProgram() { if (!m_tileCheckerboardProgram) m_tileCheckerboardProgram = make_scoped_ptr(new TileCheckerboardProgram(m_context)); if (!m_tileCheckerboardProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::checkerboardProgram::initalize"); m_tileCheckerboardProgram->initialize(m_context, m_isUsingBindUniform); } return m_tileCheckerboardProgram.get(); } const CCRendererGL::SolidColorProgram* CCRendererGL::solidColorProgram() { if (!m_solidColorProgram) m_solidColorProgram = make_scoped_ptr(new SolidColorProgram(m_context)); if (!m_solidColorProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::solidColorProgram::initialize"); m_solidColorProgram->initialize(m_context, m_isUsingBindUniform); } return m_solidColorProgram.get(); } const CCRendererGL::RenderPassProgram* CCRendererGL::renderPassProgram() { DCHECK(m_renderPassProgram); if (!m_renderPassProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::renderPassProgram::initialize"); m_renderPassProgram->initialize(m_context, m_isUsingBindUniform); } return m_renderPassProgram.get(); } const CCRendererGL::RenderPassProgramAA* CCRendererGL::renderPassProgramAA() { if (!m_renderPassProgramAA) m_renderPassProgramAA = make_scoped_ptr(new RenderPassProgramAA(m_context)); if (!m_renderPassProgramAA->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::renderPassProgramAA::initialize"); m_renderPassProgramAA->initialize(m_context, m_isUsingBindUniform); } return m_renderPassProgramAA.get(); } const CCRendererGL::RenderPassMaskProgram* CCRendererGL::renderPassMaskProgram() { if (!m_renderPassMaskProgram) m_renderPassMaskProgram = make_scoped_ptr(new RenderPassMaskProgram(m_context)); if (!m_renderPassMaskProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::renderPassMaskProgram::initialize"); m_renderPassMaskProgram->initialize(m_context, m_isUsingBindUniform); } return m_renderPassMaskProgram.get(); } const CCRendererGL::RenderPassMaskProgramAA* CCRendererGL::renderPassMaskProgramAA() { if (!m_renderPassMaskProgramAA) m_renderPassMaskProgramAA = make_scoped_ptr(new RenderPassMaskProgramAA(m_context)); if (!m_renderPassMaskProgramAA->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::renderPassMaskProgramAA::initialize"); m_renderPassMaskProgramAA->initialize(m_context, m_isUsingBindUniform); } return m_renderPassMaskProgramAA.get(); } const CCRendererGL::TileProgram* CCRendererGL::tileProgram() { DCHECK(m_tileProgram); if (!m_tileProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::tileProgram::initialize"); m_tileProgram->initialize(m_context, m_isUsingBindUniform); } return m_tileProgram.get(); } const CCRendererGL::TileProgramOpaque* CCRendererGL::tileProgramOpaque() { DCHECK(m_tileProgramOpaque); if (!m_tileProgramOpaque->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::tileProgramOpaque::initialize"); m_tileProgramOpaque->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramOpaque.get(); } const CCRendererGL::TileProgramAA* CCRendererGL::tileProgramAA() { if (!m_tileProgramAA) m_tileProgramAA = make_scoped_ptr(new TileProgramAA(m_context)); if (!m_tileProgramAA->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::tileProgramAA::initialize"); m_tileProgramAA->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramAA.get(); } const CCRendererGL::TileProgramSwizzle* CCRendererGL::tileProgramSwizzle() { if (!m_tileProgramSwizzle) m_tileProgramSwizzle = make_scoped_ptr(new TileProgramSwizzle(m_context)); if (!m_tileProgramSwizzle->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::tileProgramSwizzle::initialize"); m_tileProgramSwizzle->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramSwizzle.get(); } const CCRendererGL::TileProgramSwizzleOpaque* CCRendererGL::tileProgramSwizzleOpaque() { if (!m_tileProgramSwizzleOpaque) m_tileProgramSwizzleOpaque = make_scoped_ptr(new TileProgramSwizzleOpaque(m_context)); if (!m_tileProgramSwizzleOpaque->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::tileProgramSwizzleOpaque::initialize"); m_tileProgramSwizzleOpaque->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramSwizzleOpaque.get(); } const CCRendererGL::TileProgramSwizzleAA* CCRendererGL::tileProgramSwizzleAA() { if (!m_tileProgramSwizzleAA) m_tileProgramSwizzleAA = make_scoped_ptr(new TileProgramSwizzleAA(m_context)); if (!m_tileProgramSwizzleAA->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::tileProgramSwizzleAA::initialize"); m_tileProgramSwizzleAA->initialize(m_context, m_isUsingBindUniform); } return m_tileProgramSwizzleAA.get(); } const CCRendererGL::TextureProgram* CCRendererGL::textureProgram() { if (!m_textureProgram) m_textureProgram = make_scoped_ptr(new TextureProgram(m_context)); if (!m_textureProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::textureProgram::initialize"); m_textureProgram->initialize(m_context, m_isUsingBindUniform); } return m_textureProgram.get(); } const CCRendererGL::TextureProgramFlip* CCRendererGL::textureProgramFlip() { if (!m_textureProgramFlip) m_textureProgramFlip = make_scoped_ptr(new TextureProgramFlip(m_context)); if (!m_textureProgramFlip->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::textureProgramFlip::initialize"); m_textureProgramFlip->initialize(m_context, m_isUsingBindUniform); } return m_textureProgramFlip.get(); } const CCRendererGL::TextureIOSurfaceProgram* CCRendererGL::textureIOSurfaceProgram() { if (!m_textureIOSurfaceProgram) m_textureIOSurfaceProgram = make_scoped_ptr(new TextureIOSurfaceProgram(m_context)); if (!m_textureIOSurfaceProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::textureIOSurfaceProgram::initialize"); m_textureIOSurfaceProgram->initialize(m_context, m_isUsingBindUniform); } return m_textureIOSurfaceProgram.get(); } const CCRendererGL::VideoYUVProgram* CCRendererGL::videoYUVProgram() { if (!m_videoYUVProgram) m_videoYUVProgram = make_scoped_ptr(new VideoYUVProgram(m_context)); if (!m_videoYUVProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::videoYUVProgram::initialize"); m_videoYUVProgram->initialize(m_context, m_isUsingBindUniform); } return m_videoYUVProgram.get(); } const CCRendererGL::VideoStreamTextureProgram* CCRendererGL::videoStreamTextureProgram() { if (!m_videoStreamTextureProgram) m_videoStreamTextureProgram = make_scoped_ptr(new VideoStreamTextureProgram(m_context)); if (!m_videoStreamTextureProgram->initialized()) { TRACE_EVENT0("cc", "CCRendererGL::streamTextureProgram::initialize"); m_videoStreamTextureProgram->initialize(m_context, m_isUsingBindUniform); } return m_videoStreamTextureProgram.get(); } void CCRendererGL::cleanupSharedObjects() { makeContextCurrent(); m_sharedGeometry.reset(); if (m_tileProgram) m_tileProgram->cleanup(m_context); if (m_tileProgramOpaque) m_tileProgramOpaque->cleanup(m_context); if (m_tileProgramSwizzle) m_tileProgramSwizzle->cleanup(m_context); if (m_tileProgramSwizzleOpaque) m_tileProgramSwizzleOpaque->cleanup(m_context); if (m_tileProgramAA) m_tileProgramAA->cleanup(m_context); if (m_tileProgramSwizzleAA) m_tileProgramSwizzleAA->cleanup(m_context); if (m_tileCheckerboardProgram) m_tileCheckerboardProgram->cleanup(m_context); if (m_renderPassMaskProgram) m_renderPassMaskProgram->cleanup(m_context); if (m_renderPassProgram) m_renderPassProgram->cleanup(m_context); if (m_renderPassMaskProgramAA) m_renderPassMaskProgramAA->cleanup(m_context); if (m_renderPassProgramAA) m_renderPassProgramAA->cleanup(m_context); if (m_textureProgram) m_textureProgram->cleanup(m_context); if (m_textureProgramFlip) m_textureProgramFlip->cleanup(m_context); if (m_textureIOSurfaceProgram) m_textureIOSurfaceProgram->cleanup(m_context); if (m_videoYUVProgram) m_videoYUVProgram->cleanup(m_context); if (m_videoStreamTextureProgram) m_videoStreamTextureProgram->cleanup(m_context); if (m_solidColorProgram) m_solidColorProgram->cleanup(m_context); if (m_offscreenFramebufferId) GLC(m_context, m_context->deleteFramebuffer(m_offscreenFramebufferId)); releaseRenderPassTextures(); } bool CCRendererGL::isContextLost() { return (m_context->getGraphicsResetStatusARB() != GL_NO_ERROR); } } // namespace cc