// Copyright 2011 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 "cc/layer_tree_host_impl.h" #include #include "base/basictypes.h" #include "base/debug/trace_event.h" #include "cc/append_quads_data.h" #include "cc/damage_tracker.h" #include "cc/debug_rect_history.h" #include "cc/delay_based_time_source.h" #include "cc/font_atlas.h" #include "cc/frame_rate_counter.h" #include "cc/geometry.h" #include "cc/gl_renderer.h" #include "cc/heads_up_display_layer_impl.h" #include "cc/layer_iterator.h" #include "cc/layer_tree_host.h" #include "cc/layer_tree_host_common.h" #include "cc/math_util.h" #include "cc/overdraw_metrics.h" #include "cc/page_scale_animation.h" #include "cc/prioritized_resource_manager.h" #include "cc/render_pass_draw_quad.h" #include "cc/rendering_stats.h" #include "cc/scrollbar_animation_controller.h" #include "cc/scrollbar_layer_impl.h" #include "cc/single_thread_proxy.h" #include "cc/software_renderer.h" #include "cc/texture_uploader.h" #include "ui/gfx/size_conversions.h" #include "ui/gfx/vector2d_conversions.h" using WebKit::WebTransformationMatrix; namespace { void didVisibilityChange(cc::LayerTreeHostImpl* id, bool visible) { if (visible) { TRACE_EVENT_ASYNC_BEGIN1("webkit", "LayerTreeHostImpl::setVisible", id, "LayerTreeHostImpl", id); return; } TRACE_EVENT_ASYNC_END0("webkit", "LayerTreeHostImpl::setVisible", id); } } // namespace namespace cc { PinchZoomViewport::PinchZoomViewport() : m_pageScaleFactor(1) , m_pageScaleDelta(1) , m_sentPageScaleDelta(1) , m_minPageScaleFactor(0) , m_maxPageScaleFactor(0) { } float PinchZoomViewport::totalPageScaleFactor() const { return m_pageScaleFactor * m_pageScaleDelta; } void PinchZoomViewport::setPageScaleDelta(float delta) { // Clamp to the current min/max limits. float totalPageScaleFactor = m_pageScaleFactor * delta; if (m_minPageScaleFactor && totalPageScaleFactor < m_minPageScaleFactor) delta = m_minPageScaleFactor / m_pageScaleFactor; else if (m_maxPageScaleFactor && totalPageScaleFactor > m_maxPageScaleFactor) delta = m_maxPageScaleFactor / m_pageScaleFactor; if (delta == m_pageScaleDelta) return; m_pageScaleDelta = delta; } bool PinchZoomViewport::setPageScaleFactorAndLimits(float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor) { DCHECK(pageScaleFactor); if (m_sentPageScaleDelta == 1 && pageScaleFactor == m_pageScaleFactor && minPageScaleFactor == m_minPageScaleFactor && maxPageScaleFactor == m_maxPageScaleFactor) return false; m_minPageScaleFactor = minPageScaleFactor; m_maxPageScaleFactor = maxPageScaleFactor; m_pageScaleFactor = pageScaleFactor; return true; } gfx::RectF PinchZoomViewport::bounds() const { gfx::RectF bounds(gfx::PointF(), m_layoutViewportSize); bounds.Scale(1 / totalPageScaleFactor()); bounds += m_pinchViewportScrollDelta; return bounds; } gfx::Vector2dF PinchZoomViewport::applyScroll(const gfx::Vector2dF& delta) { gfx::Vector2dF overflow; gfx::RectF pinchedBounds = bounds() + delta; if (pinchedBounds.x() < 0) { overflow.set_x(pinchedBounds.x()); pinchedBounds.set_x(0); } if (pinchedBounds.y() < 0) { overflow.set_y(pinchedBounds.y()); pinchedBounds.set_y(0); } if (pinchedBounds.right() > m_layoutViewportSize.width()) { overflow.set_x(pinchedBounds.right() - m_layoutViewportSize.width()); pinchedBounds += gfx::Vector2dF(m_layoutViewportSize.width() - pinchedBounds.right(), 0); } if (pinchedBounds.bottom() > m_layoutViewportSize.height()) { overflow.set_y(pinchedBounds.bottom() - m_layoutViewportSize.height()); pinchedBounds += gfx::Vector2dF(0, m_layoutViewportSize.height() - pinchedBounds.bottom()); } m_pinchViewportScrollDelta = pinchedBounds.OffsetFromOrigin(); return overflow; } WebTransformationMatrix PinchZoomViewport::implTransform(bool pageScalePinchZoomEnabled) const { WebTransformationMatrix transform; transform.scale(m_pageScaleDelta); // If the pinch state is applied in the impl, then push it to the // impl transform, otherwise the scale is handled by WebCore. if (pageScalePinchZoomEnabled) { transform.scale(m_pageScaleFactor); transform.translate(-m_pinchViewportScrollDelta.x(), -m_pinchViewportScrollDelta.y()); } return transform; } class LayerTreeHostImplTimeSourceAdapter : public TimeSourceClient { public: static scoped_ptr create(LayerTreeHostImpl* layerTreeHostImpl, scoped_refptr timeSource) { return make_scoped_ptr(new LayerTreeHostImplTimeSourceAdapter(layerTreeHostImpl, timeSource)); } virtual ~LayerTreeHostImplTimeSourceAdapter() { m_timeSource->setClient(0); m_timeSource->setActive(false); } virtual void onTimerTick() OVERRIDE { m_layerTreeHostImpl->animate(base::TimeTicks::Now(), base::Time::Now()); } void setActive(bool active) { if (active != m_timeSource->active()) m_timeSource->setActive(active); } private: LayerTreeHostImplTimeSourceAdapter(LayerTreeHostImpl* layerTreeHostImpl, scoped_refptr timeSource) : m_layerTreeHostImpl(layerTreeHostImpl) , m_timeSource(timeSource) { m_timeSource->setClient(this); } LayerTreeHostImpl* m_layerTreeHostImpl; scoped_refptr m_timeSource; DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImplTimeSourceAdapter); }; LayerTreeHostImpl::FrameData::FrameData() { } LayerTreeHostImpl::FrameData::~FrameData() { } scoped_ptr LayerTreeHostImpl::create(const LayerTreeSettings& settings, LayerTreeHostImplClient* client, Proxy* proxy) { return make_scoped_ptr(new LayerTreeHostImpl(settings, client, proxy)); } LayerTreeHostImpl::LayerTreeHostImpl(const LayerTreeSettings& settings, LayerTreeHostImplClient* client, Proxy* proxy) : m_client(client) , m_proxy(proxy) , m_sourceFrameNumber(-1) , m_rootScrollLayerImpl(0) , m_currentlyScrollingLayerImpl(0) , m_hudLayerImpl(0) , m_scrollingLayerIdFromPreviousTree(-1) , m_scrollDeltaIsInViewportSpace(false) , m_settings(settings) , m_deviceScaleFactor(1) , m_visible(true) , m_contentsTexturesPurged(false) , m_managedMemoryPolicy(PrioritizedResourceManager::defaultMemoryAllocationLimit(), PriorityCalculator::allowEverythingCutoff(), 0, PriorityCalculator::allowNothingCutoff()) , m_backgroundColor(0) , m_hasTransparentBackground(false) , m_needsAnimateLayers(false) , m_pinchGestureActive(false) , m_fpsCounter(FrameRateCounter::create(m_proxy->hasImplThread())) , m_debugRectHistory(DebugRectHistory::create()) , m_numImplThreadScrolls(0) , m_numMainThreadScrolls(0) { DCHECK(m_proxy->isImplThread()); didVisibilityChange(this, m_visible); } LayerTreeHostImpl::~LayerTreeHostImpl() { DCHECK(m_proxy->isImplThread()); TRACE_EVENT0("cc", "LayerTreeHostImpl::~LayerTreeHostImpl()"); if (m_rootLayerImpl) clearRenderSurfaces(); } void LayerTreeHostImpl::beginCommit() { } void LayerTreeHostImpl::commitComplete() { TRACE_EVENT0("cc", "LayerTreeHostImpl::commitComplete"); // Recompute max scroll position; must be after layer content bounds are // updated. updateMaxScrollOffset(); m_client->sendManagedMemoryStats(); } bool LayerTreeHostImpl::canDraw() { // Note: If you are changing this function or any other function that might // affect the result of canDraw, make sure to call m_client->onCanDrawStateChanged // in the proper places and update the notifyIfCanDrawChanged test. if (!m_rootLayerImpl) { TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::canDraw no root layer"); return false; } if (deviceViewportSize().IsEmpty()) { TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::canDraw empty viewport"); return false; } if (!m_renderer) { TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::canDraw no renderer"); return false; } if (m_contentsTexturesPurged) { TRACE_EVENT_INSTANT0("cc", "LayerTreeHostImpl::canDraw contents textures purged"); return false; } return true; } GraphicsContext* LayerTreeHostImpl::context() const { return m_context.get(); } void LayerTreeHostImpl::animate(base::TimeTicks monotonicTime, base::Time wallClockTime) { animatePageScale(monotonicTime); animateLayers(monotonicTime, wallClockTime); animateScrollbars(monotonicTime); } void LayerTreeHostImpl::startPageScaleAnimation(gfx::Vector2d targetOffset, bool anchorPoint, float pageScale, base::TimeTicks startTime, base::TimeDelta duration) { if (!m_rootScrollLayerImpl) return; gfx::Vector2dF scrollTotal = m_rootScrollLayerImpl->scrollOffset() + m_rootScrollLayerImpl->scrollDelta(); gfx::SizeF scaledContentSize = contentSize(); if (!m_settings.pageScalePinchZoomEnabled) { scrollTotal.Scale(1 / m_pinchZoomViewport.pageScaleFactor()); scaledContentSize.Scale(1 / m_pinchZoomViewport.pageScaleFactor()); } gfx::SizeF viewportSize = gfx::ScaleSize(m_deviceViewportSize, 1 / m_deviceScaleFactor); double startTimeSeconds = (startTime - base::TimeTicks()).InSecondsF(); m_pageScaleAnimation = PageScaleAnimation::create(scrollTotal, m_pinchZoomViewport.totalPageScaleFactor(), viewportSize, scaledContentSize, startTimeSeconds); if (anchorPoint) { gfx::Vector2dF anchor(targetOffset); if (!m_settings.pageScalePinchZoomEnabled) anchor.Scale(1 / pageScale); m_pageScaleAnimation->zoomWithAnchor(anchor, pageScale, duration.InSecondsF()); } else { gfx::Vector2dF scaledTargetOffset = targetOffset; if (!m_settings.pageScalePinchZoomEnabled) scaledTargetOffset.Scale(1 / pageScale); m_pageScaleAnimation->zoomTo(scaledTargetOffset, pageScale, duration.InSecondsF()); } m_client->setNeedsRedrawOnImplThread(); m_client->setNeedsCommitOnImplThread(); } void LayerTreeHostImpl::scheduleAnimation() { m_client->setNeedsRedrawOnImplThread(); } void LayerTreeHostImpl::trackDamageForAllSurfaces(LayerImpl* rootDrawLayer, const LayerList& renderSurfaceLayerList) { // For now, we use damage tracking to compute a global scissor. To do this, we must // compute all damage tracking before drawing anything, so that we know the root // damage rect. The root damage rect is then used to scissor each surface. for (int surfaceIndex = renderSurfaceLayerList.size() - 1; surfaceIndex >= 0 ; --surfaceIndex) { LayerImpl* renderSurfaceLayer = renderSurfaceLayerList[surfaceIndex]; RenderSurfaceImpl* renderSurface = renderSurfaceLayer->renderSurface(); DCHECK(renderSurface); renderSurface->damageTracker()->updateDamageTrackingState(renderSurface->layerList(), renderSurfaceLayer->id(), renderSurface->surfacePropertyChangedOnlyFromDescendant(), renderSurface->contentRect(), renderSurfaceLayer->maskLayer(), renderSurfaceLayer->filters(), renderSurfaceLayer->filter()); } } void LayerTreeHostImpl::updateRootScrollLayerImplTransform() { if (m_rootScrollLayerImpl) { m_rootScrollLayerImpl->setImplTransform(implTransform()); } } void LayerTreeHostImpl::calculateRenderSurfaceLayerList(LayerList& renderSurfaceLayerList) { DCHECK(renderSurfaceLayerList.empty()); DCHECK(m_rootLayerImpl); DCHECK(m_renderer); // For maxTextureSize. { updateRootScrollLayerImplTransform(); TRACE_EVENT0("cc", "LayerTreeHostImpl::calcDrawEtc"); float pageScaleFactor = m_pinchZoomViewport.pageScaleFactor(); LayerTreeHostCommon::calculateDrawTransforms(m_rootLayerImpl.get(), deviceViewportSize(), m_deviceScaleFactor, pageScaleFactor, &m_layerSorter, rendererCapabilities().maxTextureSize, renderSurfaceLayerList); trackDamageForAllSurfaces(m_rootLayerImpl.get(), renderSurfaceLayerList); } } void LayerTreeHostImpl::FrameData::appendRenderPass(scoped_ptr renderPass) { RenderPass* pass = renderPass.get(); renderPasses.push_back(pass); renderPassesById.set(pass->id(), renderPass.Pass()); } bool LayerTreeHostImpl::calculateRenderPasses(FrameData& frame) { DCHECK(frame.renderPasses.empty()); calculateRenderSurfaceLayerList(*frame.renderSurfaceLayerList); TRACE_EVENT1("cc", "LayerTreeHostImpl::calculateRenderPasses", "renderSurfaceLayerList.size()", static_cast(frame.renderSurfaceLayerList->size())); // Create the render passes in dependency order. for (int surfaceIndex = frame.renderSurfaceLayerList->size() - 1; surfaceIndex >= 0 ; --surfaceIndex) { LayerImpl* renderSurfaceLayer = (*frame.renderSurfaceLayerList)[surfaceIndex]; renderSurfaceLayer->renderSurface()->appendRenderPasses(frame); } bool recordMetricsForFrame = m_settings.showOverdrawInTracing && base::debug::TraceLog::GetInstance() && base::debug::TraceLog::GetInstance()->IsEnabled(); OcclusionTrackerImpl occlusionTracker(m_rootLayerImpl->renderSurface()->contentRect(), recordMetricsForFrame); occlusionTracker.setMinimumTrackingSize(m_settings.minimumOcclusionTrackingSize); if (settings().showOccludingRects) occlusionTracker.setOccludingScreenSpaceRectsContainer(&frame.occludingScreenSpaceRects); // Add quads to the Render passes in FrontToBack order to allow for testing occlusion and performing culling during the tree walk. typedef LayerIterator, RenderSurfaceImpl, LayerIteratorActions::FrontToBack> LayerIteratorType; // Typically when we are missing a texture and use a checkerboard quad, we still draw the frame. However when the layer being // checkerboarded is moving due to an impl-animation, we drop the frame to avoid flashing due to the texture suddenly appearing // in the future. bool drawFrame = true; LayerIteratorType end = LayerIteratorType::end(frame.renderSurfaceLayerList); for (LayerIteratorType it = LayerIteratorType::begin(frame.renderSurfaceLayerList); it != end; ++it) { RenderPass::Id targetRenderPassId = it.targetRenderSurfaceLayer()->renderSurface()->renderPassId(); RenderPass* targetRenderPass = frame.renderPassesById.get(targetRenderPassId); occlusionTracker.enterLayer(it); AppendQuadsData appendQuadsData(targetRenderPass->id()); if (it.representsContributingRenderSurface()) { RenderPass::Id contributingRenderPassId = it->renderSurface()->renderPassId(); RenderPass* contributingRenderPass = frame.renderPassesById.get(contributingRenderPassId); targetRenderPass->appendQuadsForRenderSurfaceLayer(*it, contributingRenderPass, &occlusionTracker, appendQuadsData); } else if (it.representsItself() && !it->visibleContentRect().IsEmpty()) { bool hasOcclusionFromOutsideTargetSurface; bool implDrawTransformIsUnknown = false; if (occlusionTracker.occluded(it->renderTarget(), it->visibleContentRect(), it->drawTransform(), implDrawTransformIsUnknown, it->drawableContentRect(), &hasOcclusionFromOutsideTargetSurface)) appendQuadsData.hadOcclusionFromOutsideTargetSurface |= hasOcclusionFromOutsideTargetSurface; else { it->willDraw(m_resourceProvider.get()); frame.willDrawLayers.push_back(*it); if (it->hasContributingDelegatedRenderPasses()) { RenderPass::Id contributingRenderPassId = it->firstContributingRenderPassId(); while (frame.renderPassesById.contains(contributingRenderPassId)) { RenderPass* renderPass = frame.renderPassesById.get(contributingRenderPassId); AppendQuadsData appendQuadsData(renderPass->id()); renderPass->appendQuadsForLayer(*it, &occlusionTracker, appendQuadsData); contributingRenderPassId = it->nextContributingRenderPassId(contributingRenderPassId); } } targetRenderPass->appendQuadsForLayer(*it, &occlusionTracker, appendQuadsData); } } if (appendQuadsData.hadOcclusionFromOutsideTargetSurface) targetRenderPass->setHasOcclusionFromOutsideTargetSurface(true); if (appendQuadsData.hadMissingTiles) { bool layerHasAnimatingTransform = it->screenSpaceTransformIsAnimating() || it->drawTransformIsAnimating(); if (layerHasAnimatingTransform) drawFrame = false; } occlusionTracker.leaveLayer(it); } #ifndef NDEBUG for (size_t i = 0; i < frame.renderPasses.size(); ++i) { for (size_t j = 0; j < frame.renderPasses[i]->quadList().size(); ++j) DCHECK(frame.renderPasses[i]->quadList()[j]->sharedQuadStateId() >= 0); DCHECK(frame.renderPassesById.contains(frame.renderPasses[i]->id())); } #endif if (!m_hasTransparentBackground) { frame.renderPasses.back()->setHasTransparentBackground(false); frame.renderPasses.back()->appendQuadsToFillScreen(m_rootLayerImpl.get(), m_backgroundColor, occlusionTracker); } if (drawFrame) occlusionTracker.overdrawMetrics().recordMetrics(this); removeRenderPasses(CullRenderPassesWithNoQuads(), frame); m_renderer->decideRenderPassAllocationsForFrame(frame.renderPasses); removeRenderPasses(CullRenderPassesWithCachedTextures(*m_renderer), frame); return drawFrame; } void LayerTreeHostImpl::animateLayersRecursive(LayerImpl* current, base::TimeTicks monotonicTime, base::Time wallClockTime, AnimationEventsVector* events, bool& didAnimate, bool& needsAnimateLayers) { bool subtreeNeedsAnimateLayers = false; LayerAnimationController* currentController = current->layerAnimationController(); bool hadActiveAnimation = currentController->hasActiveAnimation(); double monotonicTimeSeconds = (monotonicTime - base::TimeTicks()).InSecondsF(); currentController->animate(monotonicTimeSeconds, events); bool startedAnimation = events->size() > 0; // We animated if we either ticked a running animation, or started a new animation. if (hadActiveAnimation || startedAnimation) didAnimate = true; // If the current controller still has an active animation, we must continue animating layers. if (currentController->hasActiveAnimation()) subtreeNeedsAnimateLayers = true; for (size_t i = 0; i < current->children().size(); ++i) { bool childNeedsAnimateLayers = false; animateLayersRecursive(current->children()[i], monotonicTime, wallClockTime, events, didAnimate, childNeedsAnimateLayers); if (childNeedsAnimateLayers) subtreeNeedsAnimateLayers = true; } needsAnimateLayers = subtreeNeedsAnimateLayers; } void LayerTreeHostImpl::setBackgroundTickingEnabled(bool enabled) { // Lazily create the timeSource adapter so that we can vary the interval for testing. if (!m_timeSourceClientAdapter) m_timeSourceClientAdapter = LayerTreeHostImplTimeSourceAdapter::create(this, DelayBasedTimeSource::create(lowFrequencyAnimationInterval(), m_proxy->currentThread())); m_timeSourceClientAdapter->setActive(enabled); } gfx::Size LayerTreeHostImpl::contentSize() const { // TODO(aelias): Hardcoding the first child here is weird. Think of // a cleaner way to get the contentBounds on the Impl side. if (!m_rootScrollLayerImpl || m_rootScrollLayerImpl->children().isEmpty()) return gfx::Size(); return m_rootScrollLayerImpl->children()[0]->contentBounds(); } static inline RenderPass* findRenderPassById(RenderPass::Id renderPassId, const LayerTreeHostImpl::FrameData& frame) { RenderPassIdHashMap::const_iterator it = frame.renderPassesById.find(renderPassId); DCHECK(it != frame.renderPassesById.end()); return it->second; } static void removeRenderPassesRecursive(RenderPass::Id removeRenderPassId, LayerTreeHostImpl::FrameData& frame) { RenderPass* removeRenderPass = findRenderPassById(removeRenderPassId, frame); RenderPassList& renderPasses = frame.renderPasses; RenderPassList::iterator toRemove = std::find(renderPasses.begin(), renderPasses.end(), removeRenderPass); // The pass was already removed by another quad - probably the original, and we are the replica. if (toRemove == renderPasses.end()) return; const RenderPass* removedPass = *toRemove; frame.renderPasses.erase(toRemove); // Now follow up for all RenderPass quads and remove their RenderPasses recursively. const QuadList& quadList = removedPass->quadList(); QuadList::constBackToFrontIterator quadListIterator = quadList.backToFrontBegin(); for (; quadListIterator != quadList.backToFrontEnd(); ++quadListIterator) { DrawQuad* currentQuad = (*quadListIterator); if (currentQuad->material() != DrawQuad::RenderPass) continue; RenderPass::Id nextRemoveRenderPassId = RenderPassDrawQuad::materialCast(currentQuad)->renderPassId(); removeRenderPassesRecursive(nextRemoveRenderPassId, frame); } } bool LayerTreeHostImpl::CullRenderPassesWithCachedTextures::shouldRemoveRenderPass(const RenderPassDrawQuad& quad, const FrameData&) const { return quad.contentsChangedSinceLastFrame().IsEmpty() && m_renderer.haveCachedResourcesForRenderPassId(quad.renderPassId()); } bool LayerTreeHostImpl::CullRenderPassesWithNoQuads::shouldRemoveRenderPass(const RenderPassDrawQuad& quad, const FrameData& frame) const { const RenderPass* renderPass = findRenderPassById(quad.renderPassId(), frame); const RenderPassList& renderPasses = frame.renderPasses; RenderPassList::const_iterator foundPass = std::find(renderPasses.begin(), renderPasses.end(), renderPass); bool renderPassAlreadyRemoved = foundPass == renderPasses.end(); if (renderPassAlreadyRemoved) return false; // If any quad or RenderPass draws into this RenderPass, then keep it. const QuadList& quadList = (*foundPass)->quadList(); for (QuadList::constBackToFrontIterator quadListIterator = quadList.backToFrontBegin(); quadListIterator != quadList.backToFrontEnd(); ++quadListIterator) { DrawQuad* currentQuad = *quadListIterator; if (currentQuad->material() != DrawQuad::RenderPass) return false; const RenderPass* contributingPass = findRenderPassById(RenderPassDrawQuad::materialCast(currentQuad)->renderPassId(), frame); RenderPassList::const_iterator foundContributingPass = std::find(renderPasses.begin(), renderPasses.end(), contributingPass); if (foundContributingPass != renderPasses.end()) return false; } return true; } // Defined for linking tests. template CC_EXPORT void LayerTreeHostImpl::removeRenderPasses(CullRenderPassesWithCachedTextures, FrameData&); template CC_EXPORT void LayerTreeHostImpl::removeRenderPasses(CullRenderPassesWithNoQuads, FrameData&); // static template void LayerTreeHostImpl::removeRenderPasses(RenderPassCuller culler, FrameData& frame) { for (size_t it = culler.renderPassListBegin(frame.renderPasses); it != culler.renderPassListEnd(frame.renderPasses); it = culler.renderPassListNext(it)) { const RenderPass* currentPass = frame.renderPasses[it]; const QuadList& quadList = currentPass->quadList(); QuadList::constBackToFrontIterator quadListIterator = quadList.backToFrontBegin(); for (; quadListIterator != quadList.backToFrontEnd(); ++quadListIterator) { DrawQuad* currentQuad = *quadListIterator; if (currentQuad->material() != DrawQuad::RenderPass) continue; RenderPassDrawQuad* renderPassQuad = static_cast(currentQuad); if (!culler.shouldRemoveRenderPass(*renderPassQuad, frame)) continue; // We are changing the vector in the middle of iteration. Because we // delete render passes that draw into the current pass, we are // guaranteed that any data from the iterator to the end will not // change. So, capture the iterator position from the end of the // list, and restore it after the change. int positionFromEnd = frame.renderPasses.size() - it; removeRenderPassesRecursive(renderPassQuad->renderPassId(), frame); it = frame.renderPasses.size() - positionFromEnd; DCHECK(it >= 0); } } } bool LayerTreeHostImpl::prepareToDraw(FrameData& frame) { TRACE_EVENT0("cc", "LayerTreeHostImpl::prepareToDraw"); DCHECK(canDraw()); frame.renderSurfaceLayerList = &m_renderSurfaceLayerList; frame.renderPasses.clear(); frame.renderPassesById.clear(); frame.renderSurfaceLayerList->clear(); frame.willDrawLayers.clear(); if (!calculateRenderPasses(frame)) return false; // If we return true, then we expect drawLayers() to be called before this function is called again. return true; } void LayerTreeHostImpl::enforceManagedMemoryPolicy(const ManagedMemoryPolicy& policy) { bool evictedResources = m_client->reduceContentsTextureMemoryOnImplThread( m_visible ? policy.bytesLimitWhenVisible : policy.bytesLimitWhenNotVisible, m_visible ? policy.priorityCutoffWhenVisible : policy.priorityCutoffWhenNotVisible); if (evictedResources) { setContentsTexturesPurged(); m_client->setNeedsCommitOnImplThread(); m_client->onCanDrawStateChanged(canDraw()); } m_client->sendManagedMemoryStats(); } bool LayerTreeHostImpl::hasImplThread() const { return m_proxy->hasImplThread(); } void LayerTreeHostImpl::setManagedMemoryPolicy(const ManagedMemoryPolicy& policy) { if (m_managedMemoryPolicy == policy) return; m_managedMemoryPolicy = policy; if (!m_proxy->hasImplThread()) { // FIXME: In single-thread mode, this can be called on the main thread // by GLRenderer::onMemoryAllocationChanged. DebugScopedSetImplThread implThread(m_proxy); enforceManagedMemoryPolicy(m_managedMemoryPolicy); } else { DCHECK(m_proxy->isImplThread()); enforceManagedMemoryPolicy(m_managedMemoryPolicy); } // We always need to commit after changing the memory policy because the new // limit can result in more or less content having texture allocated for it. m_client->setNeedsCommitOnImplThread(); } void LayerTreeHostImpl::onVSyncParametersChanged(double monotonicTimebase, double intervalInSeconds) { base::TimeTicks timebase = base::TimeTicks::FromInternalValue(monotonicTimebase * base::Time::kMicrosecondsPerSecond); base::TimeDelta interval = base::TimeDelta::FromMicroseconds(intervalInSeconds * base::Time::kMicrosecondsPerSecond); m_client->onVSyncParametersChanged(timebase, interval); } void LayerTreeHostImpl::drawLayers(const FrameData& frame) { TRACE_EVENT0("cc", "LayerTreeHostImpl::drawLayers"); DCHECK(canDraw()); DCHECK(!frame.renderPasses.empty()); // FIXME: use the frame begin time from the overall compositor scheduler. // This value is currently inaccessible because it is up in Chromium's // RenderWidget. m_fpsCounter->markBeginningOfFrame(base::TimeTicks::Now()); if (m_settings.showDebugRects()) m_debugRectHistory->saveDebugRectsForCurrentFrame(m_rootLayerImpl.get(), *frame.renderSurfaceLayerList, frame.occludingScreenSpaceRects, settings()); // Because the contents of the HUD depend on everything else in the frame, the contents // of its texture are updated as the last thing before the frame is drawn. if (m_hudLayerImpl) m_hudLayerImpl->updateHudTexture(m_resourceProvider.get()); m_renderer->drawFrame(frame.renderPasses, frame.renderPassesById); // Once a RenderPass has been drawn, its damage should be cleared in // case the RenderPass will be reused next frame. for (unsigned int i = 0; i < frame.renderPasses.size(); i++) frame.renderPasses[i]->setDamageRect(gfx::RectF()); // The next frame should start by assuming nothing has changed, and changes are noted as they occur. for (unsigned int i = 0; i < frame.renderSurfaceLayerList->size(); i++) (*frame.renderSurfaceLayerList)[i]->renderSurface()->damageTracker()->didDrawDamagedArea(); m_rootLayerImpl->resetAllChangeTrackingForSubtree(); } void LayerTreeHostImpl::didDrawAllLayers(const FrameData& frame) { for (size_t i = 0; i < frame.willDrawLayers.size(); ++i) frame.willDrawLayers[i]->didDraw(m_resourceProvider.get()); // Once all layers have been drawn, pending texture uploads should no // longer block future uploads. m_resourceProvider->markPendingUploadsAsNonBlocking(); } void LayerTreeHostImpl::finishAllRendering() { if (m_renderer) m_renderer->finish(); } bool LayerTreeHostImpl::isContextLost() { return m_renderer && m_renderer->isContextLost(); } const RendererCapabilities& LayerTreeHostImpl::rendererCapabilities() const { return m_renderer->capabilities(); } bool LayerTreeHostImpl::swapBuffers() { DCHECK(m_renderer); m_fpsCounter->markEndOfFrame(); return m_renderer->swapBuffers(); } const gfx::Size& LayerTreeHostImpl::deviceViewportSize() const { return m_deviceViewportSize; } const LayerTreeSettings& LayerTreeHostImpl::settings() const { return m_settings; } void LayerTreeHostImpl::didLoseContext() { m_client->didLoseContextOnImplThread(); } void LayerTreeHostImpl::onSwapBuffersComplete() { m_client->onSwapBuffersCompleteOnImplThread(); } void LayerTreeHostImpl::readback(void* pixels, const gfx::Rect& rect) { DCHECK(m_renderer); m_renderer->getFramebufferPixels(pixels, rect); } static LayerImpl* findRootScrollLayer(LayerImpl* layer) { if (!layer) return 0; if (layer->scrollable()) return layer; for (size_t i = 0; i < layer->children().size(); ++i) { LayerImpl* found = findRootScrollLayer(layer->children()[i]); if (found) return found; } return 0; } // Content layers can be either directly scrollable or contained in an outer // scrolling layer which applies the scroll transform. Given a content layer, // this function returns the associated scroll layer if any. static LayerImpl* findScrollLayerForContentLayer(LayerImpl* layerImpl) { if (!layerImpl) return 0; if (layerImpl->scrollable()) return layerImpl; if (layerImpl->drawsContent() && layerImpl->parent() && layerImpl->parent()->scrollable()) return layerImpl->parent(); return 0; } void LayerTreeHostImpl::setRootLayer(scoped_ptr layer) { m_rootLayerImpl = layer.Pass(); m_rootScrollLayerImpl = findRootScrollLayer(m_rootLayerImpl.get()); m_currentlyScrollingLayerImpl = 0; if (m_rootLayerImpl && m_scrollingLayerIdFromPreviousTree != -1) m_currentlyScrollingLayerImpl = LayerTreeHostCommon::findLayerInSubtree(m_rootLayerImpl.get(), m_scrollingLayerIdFromPreviousTree); m_scrollingLayerIdFromPreviousTree = -1; m_client->onCanDrawStateChanged(canDraw()); } scoped_ptr LayerTreeHostImpl::detachLayerTree() { // Clear all data structures that have direct references to the layer tree. m_scrollingLayerIdFromPreviousTree = m_currentlyScrollingLayerImpl ? m_currentlyScrollingLayerImpl->id() : -1; m_currentlyScrollingLayerImpl = 0; m_renderSurfaceLayerList.clear(); return m_rootLayerImpl.Pass(); } void LayerTreeHostImpl::setVisible(bool visible) { DCHECK(m_proxy->isImplThread()); if (m_visible == visible) return; m_visible = visible; didVisibilityChange(this, m_visible); enforceManagedMemoryPolicy(m_managedMemoryPolicy); if (!m_renderer) return; m_renderer->setVisible(visible); setBackgroundTickingEnabled(!m_visible && m_needsAnimateLayers); } bool LayerTreeHostImpl::initializeRenderer(scoped_ptr context) { // Since we will create a new resource provider, we cannot continue to use // the old resources (i.e. renderSurfaces and texture IDs). Clear them // before we destroy the old resource provider. if (m_rootLayerImpl) { clearRenderSurfaces(); sendDidLoseContextRecursive(m_rootLayerImpl.get()); } // Note: order is important here. m_renderer.reset(); m_resourceProvider.reset(); m_context.reset(); if (!context->bindToClient(this)) return false; scoped_ptr resourceProvider = ResourceProvider::create(context.get()); if (!resourceProvider) return false; if (context->context3D()) m_renderer = GLRenderer::create(this, resourceProvider.get()); else if (context->softwareDevice()) m_renderer = SoftwareRenderer::create(this, resourceProvider.get(), context->softwareDevice()); if (!m_renderer) return false; m_resourceProvider = resourceProvider.Pass(); m_context = context.Pass(); if (!m_visible) m_renderer->setVisible(m_visible); m_client->onCanDrawStateChanged(canDraw()); return true; } void LayerTreeHostImpl::setContentsTexturesPurged() { m_contentsTexturesPurged = true; m_client->onCanDrawStateChanged(canDraw()); } void LayerTreeHostImpl::resetContentsTexturesPurged() { m_contentsTexturesPurged = false; m_client->onCanDrawStateChanged(canDraw()); } void LayerTreeHostImpl::setViewportSize(const gfx::Size& layoutViewportSize, const gfx::Size& deviceViewportSize) { if (layoutViewportSize == m_layoutViewportSize && deviceViewportSize == m_deviceViewportSize) return; m_layoutViewportSize = layoutViewportSize; m_deviceViewportSize = deviceViewportSize; m_pinchZoomViewport.setLayoutViewportSize(layoutViewportSize); updateMaxScrollOffset(); if (m_renderer) m_renderer->viewportChanged(); m_client->onCanDrawStateChanged(canDraw()); } static void adjustScrollsForPageScaleChange(LayerImpl* layerImpl, float pageScaleChange) { if (!layerImpl) return; if (layerImpl->scrollable()) { // We need to convert impl-side scroll deltas to pageScale space. gfx::Vector2dF scrollDelta = layerImpl->scrollDelta(); scrollDelta.Scale(pageScaleChange); layerImpl->setScrollDelta(scrollDelta); } for (size_t i = 0; i < layerImpl->children().size(); ++i) adjustScrollsForPageScaleChange(layerImpl->children()[i], pageScaleChange); } void LayerTreeHostImpl::setDeviceScaleFactor(float deviceScaleFactor) { if (deviceScaleFactor == m_deviceScaleFactor) return; m_deviceScaleFactor = deviceScaleFactor; updateMaxScrollOffset(); } float LayerTreeHostImpl::pageScaleFactor() const { return m_pinchZoomViewport.pageScaleFactor(); } void LayerTreeHostImpl::setPageScaleFactorAndLimits(float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor) { if (!pageScaleFactor) return; float pageScaleChange = pageScaleFactor / m_pinchZoomViewport.pageScaleFactor(); m_pinchZoomViewport.setPageScaleFactorAndLimits(pageScaleFactor, minPageScaleFactor, maxPageScaleFactor); if (!m_settings.pageScalePinchZoomEnabled) { if (pageScaleChange != 1) adjustScrollsForPageScaleChange(m_rootScrollLayerImpl, pageScaleChange); } // Clamp delta to limits and refresh display matrix. setPageScaleDelta(m_pinchZoomViewport.pageScaleDelta() / m_pinchZoomViewport.sentPageScaleDelta()); m_pinchZoomViewport.setSentPageScaleDelta(1); } void LayerTreeHostImpl::setPageScaleDelta(float delta) { m_pinchZoomViewport.setPageScaleDelta(delta); updateMaxScrollOffset(); } void LayerTreeHostImpl::updateMaxScrollOffset() { if (!m_rootScrollLayerImpl || !m_rootScrollLayerImpl->children().size()) return; gfx::SizeF viewBounds = m_deviceViewportSize; if (LayerImpl* clipLayer = m_rootScrollLayerImpl->parent()) { // Compensate for non-overlay scrollbars. if (clipLayer->masksToBounds()) viewBounds = gfx::ScaleSize(clipLayer->bounds(), m_deviceScaleFactor); } gfx::Size contentBounds = contentSize(); if (m_settings.pageScalePinchZoomEnabled) { // Pinch with pageScale scrolls entirely in layout space. contentSize // returns the bounds including the page scale factor, so calculate the // pre page-scale layout size here. float pageScaleFactor = m_pinchZoomViewport.pageScaleFactor(); contentBounds.set_width(contentBounds.width() / pageScaleFactor); contentBounds.set_height(contentBounds.height() / pageScaleFactor); } else { viewBounds.Scale(1 / m_pinchZoomViewport.pageScaleDelta()); } gfx::Vector2dF maxScroll = BottomRight(gfx::Rect(contentBounds)) - BottomRight(gfx::RectF(viewBounds)); maxScroll.Scale(1 / m_deviceScaleFactor); // The viewport may be larger than the contents in some cases, such as // having a vertical scrollbar but no horizontal overflow. maxScroll.ClampToMin(gfx::Vector2dF()); m_rootScrollLayerImpl->setMaxScrollOffset(gfx::ToFlooredVector2d(maxScroll)); } void LayerTreeHostImpl::setNeedsRedraw() { m_client->setNeedsRedrawOnImplThread(); } bool LayerTreeHostImpl::ensureRenderSurfaceLayerList() { if (!m_rootLayerImpl) return false; if (!m_renderer) return false; // We need both a non-empty render surface layer list and a root render // surface to be able to iterate over the visible layers. if (m_renderSurfaceLayerList.size() && m_rootLayerImpl->renderSurface()) return true; // If we are called after setRootLayer() but before prepareToDraw(), we need // to recalculate the visible layers. This prevents being unable to scroll // during part of a commit. m_renderSurfaceLayerList.clear(); calculateRenderSurfaceLayerList(m_renderSurfaceLayerList); return m_renderSurfaceLayerList.size(); } InputHandlerClient::ScrollStatus LayerTreeHostImpl::scrollBegin(gfx::Point viewportPoint, InputHandlerClient::ScrollInputType type) { TRACE_EVENT0("cc", "LayerTreeHostImpl::scrollBegin"); DCHECK(!m_currentlyScrollingLayerImpl); clearCurrentlyScrollingLayer(); if (!ensureRenderSurfaceLayerList()) return ScrollIgnored; gfx::PointF deviceViewportPoint = gfx::ScalePoint(viewportPoint, m_deviceScaleFactor); // First find out which layer was hit from the saved list of visible layers // in the most recent frame. LayerImpl* layerImpl = LayerTreeHostCommon::findLayerThatIsHitByPoint(deviceViewportPoint, m_renderSurfaceLayerList); // Walk up the hierarchy and look for a scrollable layer. LayerImpl* potentiallyScrollingLayerImpl = 0; for (; layerImpl; layerImpl = layerImpl->parent()) { // The content layer can also block attempts to scroll outside the main thread. if (layerImpl->tryScroll(deviceViewportPoint, type) == ScrollOnMainThread) { m_numMainThreadScrolls++; return ScrollOnMainThread; } LayerImpl* scrollLayerImpl = findScrollLayerForContentLayer(layerImpl); if (!scrollLayerImpl) continue; ScrollStatus status = scrollLayerImpl->tryScroll(deviceViewportPoint, type); // If any layer wants to divert the scroll event to the main thread, abort. if (status == ScrollOnMainThread) { m_numMainThreadScrolls++; return ScrollOnMainThread; } if (status == ScrollStarted && !potentiallyScrollingLayerImpl) potentiallyScrollingLayerImpl = scrollLayerImpl; } if (potentiallyScrollingLayerImpl) { m_currentlyScrollingLayerImpl = potentiallyScrollingLayerImpl; // Gesture events need to be transformed from viewport coordinates to local layer coordinates // so that the scrolling contents exactly follow the user's finger. In contrast, wheel // events are already in local layer coordinates so we can just apply them directly. m_scrollDeltaIsInViewportSpace = (type == Gesture); m_numImplThreadScrolls++; return ScrollStarted; } return ScrollIgnored; } static gfx::Vector2dF scrollLayerWithViewportSpaceDelta(PinchZoomViewport* viewport, LayerImpl& layerImpl, float scaleFromViewportToScreenSpace, gfx::PointF viewportPoint, gfx::Vector2dF viewportDelta) { // Layers with non-invertible screen space transforms should not have passed the scroll hit // test in the first place. DCHECK(layerImpl.screenSpaceTransform().isInvertible()); WebTransformationMatrix inverseScreenSpaceTransform = layerImpl.screenSpaceTransform().inverse(); gfx::PointF screenSpacePoint = gfx::ScalePoint(viewportPoint, scaleFromViewportToScreenSpace); gfx::Vector2dF screenSpaceDelta = viewportDelta; screenSpaceDelta.Scale(scaleFromViewportToScreenSpace); // First project the scroll start and end points to local layer space to find the scroll delta // in layer coordinates. bool startClipped, endClipped; gfx::PointF screenSpaceEndPoint = screenSpacePoint + screenSpaceDelta; gfx::PointF localStartPoint = MathUtil::projectPoint(inverseScreenSpaceTransform, screenSpacePoint, startClipped); gfx::PointF localEndPoint = MathUtil::projectPoint(inverseScreenSpaceTransform, screenSpaceEndPoint, endClipped); // In general scroll point coordinates should not get clipped. DCHECK(!startClipped); DCHECK(!endClipped); if (startClipped || endClipped) return gfx::Vector2dF(); // localStartPoint and localEndPoint are in content space but we want to move them to layer space for scrolling. float widthScale = 1 / layerImpl.contentsScaleX(); float heightScale = 1 / layerImpl.contentsScaleY(); localStartPoint.Scale(widthScale, heightScale); localEndPoint.Scale(widthScale, heightScale); // Apply the scroll delta. gfx::Vector2dF previousDelta = layerImpl.scrollDelta(); gfx::Vector2dF unscrolled = layerImpl.scrollBy(localEndPoint - localStartPoint); if (viewport) viewport->applyScroll(unscrolled); // Get the end point in the layer's content space so we can apply its screenSpaceTransform. gfx::PointF actualLocalEndPoint = localStartPoint + layerImpl.scrollDelta() - previousDelta; gfx::PointF actualLocalContentEndPoint = gfx::ScalePoint(actualLocalEndPoint, 1 / widthScale, 1 / heightScale); // Calculate the applied scroll delta in viewport space coordinates. gfx::PointF actualScreenSpaceEndPoint = MathUtil::mapPoint(layerImpl.screenSpaceTransform(), actualLocalContentEndPoint, endClipped); DCHECK(!endClipped); if (endClipped) return gfx::Vector2dF(); gfx::PointF actualViewportEndPoint = gfx::ScalePoint(actualScreenSpaceEndPoint, 1 / scaleFromViewportToScreenSpace); return actualViewportEndPoint - viewportPoint; } static gfx::Vector2dF scrollLayerWithLocalDelta(LayerImpl& layerImpl, gfx::Vector2dF localDelta) { gfx::Vector2dF previousDelta(layerImpl.scrollDelta()); layerImpl.scrollBy(localDelta); return layerImpl.scrollDelta() - previousDelta; } bool LayerTreeHostImpl::scrollBy(const gfx::Point& viewportPoint, const gfx::Vector2d& scrollDelta) { TRACE_EVENT0("cc", "LayerTreeHostImpl::scrollBy"); if (!m_currentlyScrollingLayerImpl) return false; gfx::Vector2dF pendingDelta = scrollDelta; for (LayerImpl* layerImpl = m_currentlyScrollingLayerImpl; layerImpl; layerImpl = layerImpl->parent()) { if (!layerImpl->scrollable()) continue; PinchZoomViewport* viewport = layerImpl == m_rootScrollLayerImpl ? &m_pinchZoomViewport : 0; gfx::Vector2dF appliedDelta; if (m_scrollDeltaIsInViewportSpace) { float scaleFromViewportToScreenSpace = m_deviceScaleFactor; appliedDelta = scrollLayerWithViewportSpaceDelta(viewport, *layerImpl, scaleFromViewportToScreenSpace, viewportPoint, pendingDelta); } else appliedDelta = scrollLayerWithLocalDelta(*layerImpl, pendingDelta); // If the layer wasn't able to move, try the next one in the hierarchy. float moveThresholdSquared = 0.1f * 0.1f; if (appliedDelta.LengthSquared() < moveThresholdSquared) continue; // If the applied delta is within 45 degrees of the input delta, bail out to make it easier // to scroll just one layer in one direction without affecting any of its parents. float angleThreshold = 45; if (MathUtil::smallestAngleBetweenVectors(appliedDelta, pendingDelta) < angleThreshold) { pendingDelta = gfx::Vector2d(); break; } // Allow further movement only on an axis perpendicular to the direction in which the layer // moved. gfx::Vector2dF perpendicularAxis(-appliedDelta.y(), appliedDelta.x()); pendingDelta = MathUtil::projectVector(pendingDelta, perpendicularAxis); if (gfx::ToFlooredVector2d(pendingDelta).IsZero()) break; } if (!scrollDelta.IsZero() && gfx::ToFlooredVector2d(pendingDelta).IsZero()) { m_client->setNeedsCommitOnImplThread(); m_client->setNeedsRedrawOnImplThread(); return true; } return false; } void LayerTreeHostImpl::clearCurrentlyScrollingLayer() { m_currentlyScrollingLayerImpl = 0; m_scrollingLayerIdFromPreviousTree = -1; } void LayerTreeHostImpl::scrollEnd() { clearCurrentlyScrollingLayer(); } void LayerTreeHostImpl::pinchGestureBegin() { m_pinchGestureActive = true; m_previousPinchAnchor = gfx::Point(); if (m_rootScrollLayerImpl && m_rootScrollLayerImpl->scrollbarAnimationController()) m_rootScrollLayerImpl->scrollbarAnimationController()->didPinchGestureBegin(); } void LayerTreeHostImpl::pinchGestureUpdate(float magnifyDelta, gfx::Point anchor) { TRACE_EVENT0("cc", "LayerTreeHostImpl::pinchGestureUpdate"); if (!m_rootScrollLayerImpl) return; if (m_previousPinchAnchor == gfx::Point()) m_previousPinchAnchor = anchor; // Keep the center-of-pinch anchor specified by (x, y) in a stable // position over the course of the magnify. float pageScaleDelta = m_pinchZoomViewport.pageScaleDelta(); gfx::PointF previousScaleAnchor = gfx::ScalePoint(m_previousPinchAnchor, 1 / pageScaleDelta); setPageScaleDelta(pageScaleDelta * magnifyDelta); pageScaleDelta = m_pinchZoomViewport.pageScaleDelta(); gfx::PointF newScaleAnchor = gfx::ScalePoint(anchor, 1 / pageScaleDelta); gfx::Vector2dF move = previousScaleAnchor - newScaleAnchor; m_previousPinchAnchor = anchor; if (m_settings.pageScalePinchZoomEnabled) { // Compute the application of the delta with respect to the current page zoom of the page. move.Scale(1 / (m_pinchZoomViewport.pageScaleFactor() * m_deviceScaleFactor)); } gfx::Vector2dF scrollOverflow = m_settings.pageScalePinchZoomEnabled ? m_pinchZoomViewport.applyScroll(move) : move; m_rootScrollLayerImpl->scrollBy(scrollOverflow); if (m_rootScrollLayerImpl->scrollbarAnimationController()) m_rootScrollLayerImpl->scrollbarAnimationController()->didPinchGestureUpdate(); m_client->setNeedsCommitOnImplThread(); m_client->setNeedsRedrawOnImplThread(); } void LayerTreeHostImpl::pinchGestureEnd() { m_pinchGestureActive = false; if (m_rootScrollLayerImpl && m_rootScrollLayerImpl->scrollbarAnimationController()) m_rootScrollLayerImpl->scrollbarAnimationController()->didPinchGestureEnd(); m_client->setNeedsCommitOnImplThread(); } void LayerTreeHostImpl::computeDoubleTapZoomDeltas(ScrollAndScaleSet* scrollInfo) { gfx::Vector2dF scaledScrollOffset = m_pageScaleAnimation->targetScrollOffset(); if (!m_settings.pageScalePinchZoomEnabled) scaledScrollOffset.Scale(m_pinchZoomViewport.pageScaleFactor()); makeScrollAndScaleSet(scrollInfo, ToFlooredVector2d(scaledScrollOffset), m_pageScaleAnimation->targetPageScaleFactor()); } void LayerTreeHostImpl::computePinchZoomDeltas(ScrollAndScaleSet* scrollInfo) { if (!m_rootScrollLayerImpl) return; // Only send fake scroll/zoom deltas if we're pinch zooming out by a // significant amount. This also ensures only one fake delta set will be // sent. const float pinchZoomOutSensitivity = 0.95f; if (m_pinchZoomViewport.pageScaleDelta() > pinchZoomOutSensitivity) return; // Compute where the scroll offset/page scale would be if fully pinch-zoomed // out from the anchor point. gfx::Vector2dF scrollBegin = m_rootScrollLayerImpl->scrollOffset() + m_rootScrollLayerImpl->scrollDelta(); scrollBegin.Scale(m_pinchZoomViewport.pageScaleDelta()); float scaleBegin = m_pinchZoomViewport.totalPageScaleFactor(); float pageScaleDeltaToSend = m_pinchZoomViewport.minPageScaleFactor() / m_pinchZoomViewport.pageScaleFactor(); gfx::SizeF scaledContentsSize = gfx::ScaleSize(contentSize(), pageScaleDeltaToSend); gfx::Vector2d anchorOffset = m_previousPinchAnchor.OffsetFromOrigin(); gfx::Vector2dF scrollEnd = scrollBegin + anchorOffset; scrollEnd.Scale(m_pinchZoomViewport.minPageScaleFactor() / scaleBegin); scrollEnd -= anchorOffset; scrollEnd.ClampToMax(BottomRight(gfx::RectF(scaledContentsSize)) - BottomRight(gfx::Rect(m_deviceViewportSize))); scrollEnd.ClampToMin(gfx::Vector2d()); scrollEnd.Scale(1 / pageScaleDeltaToSend); scrollEnd.Scale(m_deviceScaleFactor); makeScrollAndScaleSet(scrollInfo, gfx::ToRoundedVector2d(scrollEnd), m_pinchZoomViewport.minPageScaleFactor()); } void LayerTreeHostImpl::makeScrollAndScaleSet(ScrollAndScaleSet* scrollInfo, gfx::Vector2d scrollOffset, float pageScale) { if (!m_rootScrollLayerImpl) return; LayerTreeHostCommon::ScrollUpdateInfo scroll; scroll.layerId = m_rootScrollLayerImpl->id(); scroll.scrollDelta = scrollOffset - m_rootScrollLayerImpl->scrollOffset(); scrollInfo->scrolls.push_back(scroll); m_rootScrollLayerImpl->setSentScrollDelta(scroll.scrollDelta); scrollInfo->pageScaleDelta = pageScale / m_pinchZoomViewport.pageScaleFactor(); m_pinchZoomViewport.setSentPageScaleDelta(scrollInfo->pageScaleDelta); } static void collectScrollDeltas(ScrollAndScaleSet* scrollInfo, LayerImpl* layerImpl) { if (!layerImpl) return; if (!layerImpl->scrollDelta().IsZero()) { gfx::Vector2d scrollDelta = gfx::ToFlooredVector2d(layerImpl->scrollDelta()); LayerTreeHostCommon::ScrollUpdateInfo scroll; scroll.layerId = layerImpl->id(); scroll.scrollDelta = scrollDelta; scrollInfo->scrolls.push_back(scroll); layerImpl->setSentScrollDelta(scrollDelta); } for (size_t i = 0; i < layerImpl->children().size(); ++i) collectScrollDeltas(scrollInfo, layerImpl->children()[i]); } scoped_ptr LayerTreeHostImpl::processScrollDeltas() { scoped_ptr scrollInfo(new ScrollAndScaleSet()); if (m_pinchGestureActive || m_pageScaleAnimation) { scrollInfo->pageScaleDelta = 1; m_pinchZoomViewport.setSentPageScaleDelta(1); // FIXME(aelias): Make pinch-zoom painting optimization compatible with // compositor-side scaling. if (!m_settings.pageScalePinchZoomEnabled && m_pinchGestureActive) computePinchZoomDeltas(scrollInfo.get()); else if (m_pageScaleAnimation.get()) computeDoubleTapZoomDeltas(scrollInfo.get()); return scrollInfo.Pass(); } collectScrollDeltas(scrollInfo.get(), m_rootLayerImpl.get()); scrollInfo->pageScaleDelta = m_pinchZoomViewport.pageScaleDelta(); m_pinchZoomViewport.setSentPageScaleDelta(scrollInfo->pageScaleDelta); return scrollInfo.Pass(); } WebTransformationMatrix LayerTreeHostImpl::implTransform() const { return m_pinchZoomViewport.implTransform(m_settings.pageScalePinchZoomEnabled); } void LayerTreeHostImpl::setFullRootLayerDamage() { if (m_rootLayerImpl) { RenderSurfaceImpl* renderSurface = m_rootLayerImpl->renderSurface(); if (renderSurface) renderSurface->damageTracker()->forceFullDamageNextUpdate(); } } void LayerTreeHostImpl::animatePageScale(base::TimeTicks time) { if (!m_pageScaleAnimation || !m_rootScrollLayerImpl) return; double monotonicTime = (time - base::TimeTicks()).InSecondsF(); gfx::Vector2dF scrollTotal = m_rootScrollLayerImpl->scrollOffset() + m_rootScrollLayerImpl->scrollDelta(); setPageScaleDelta(m_pageScaleAnimation->pageScaleFactorAtTime(monotonicTime) / m_pinchZoomViewport.pageScaleFactor()); gfx::Vector2dF nextScroll = m_pageScaleAnimation->scrollOffsetAtTime(monotonicTime); if (!m_settings.pageScalePinchZoomEnabled) nextScroll.Scale(m_pinchZoomViewport.pageScaleFactor()); m_rootScrollLayerImpl->scrollBy(nextScroll - scrollTotal); m_client->setNeedsRedrawOnImplThread(); if (m_pageScaleAnimation->isAnimationCompleteAtTime(monotonicTime)) { m_pageScaleAnimation.reset(); m_client->setNeedsCommitOnImplThread(); } } void LayerTreeHostImpl::animateLayers(base::TimeTicks monotonicTime, base::Time wallClockTime) { if (!m_settings.acceleratedAnimationEnabled || !m_needsAnimateLayers || !m_rootLayerImpl) return; TRACE_EVENT0("cc", "LayerTreeHostImpl::animateLayers"); scoped_ptr events(make_scoped_ptr(new AnimationEventsVector)); bool didAnimate = false; animateLayersRecursive(m_rootLayerImpl.get(), monotonicTime, wallClockTime, events.get(), didAnimate, m_needsAnimateLayers); if (!events->empty()) m_client->postAnimationEventsToMainThreadOnImplThread(events.Pass(), wallClockTime); if (didAnimate) m_client->setNeedsRedrawOnImplThread(); setBackgroundTickingEnabled(!m_visible && m_needsAnimateLayers); } base::TimeDelta LayerTreeHostImpl::lowFrequencyAnimationInterval() const { return base::TimeDelta::FromSeconds(1); } void LayerTreeHostImpl::sendDidLoseContextRecursive(LayerImpl* current) { DCHECK(current); current->didLoseContext(); if (current->maskLayer()) sendDidLoseContextRecursive(current->maskLayer()); if (current->replicaLayer()) sendDidLoseContextRecursive(current->replicaLayer()); for (size_t i = 0; i < current->children().size(); ++i) sendDidLoseContextRecursive(current->children()[i]); } static void clearRenderSurfacesOnLayerImplRecursive(LayerImpl* current) { DCHECK(current); for (size_t i = 0; i < current->children().size(); ++i) clearRenderSurfacesOnLayerImplRecursive(current->children()[i]); current->clearRenderSurface(); } void LayerTreeHostImpl::clearRenderSurfaces() { clearRenderSurfacesOnLayerImplRecursive(m_rootLayerImpl.get()); m_renderSurfaceLayerList.clear(); } std::string LayerTreeHostImpl::layerTreeAsText() const { std::string str; if (m_rootLayerImpl) { str = m_rootLayerImpl->layerTreeAsText(); str += "RenderSurfaces:\n"; dumpRenderSurfaces(&str, 1, m_rootLayerImpl.get()); } return str; } void LayerTreeHostImpl::dumpRenderSurfaces(std::string* str, int indent, const LayerImpl* layer) const { if (layer->renderSurface()) layer->renderSurface()->dumpSurface(str, indent); for (size_t i = 0; i < layer->children().size(); ++i) dumpRenderSurfaces(str, indent, layer->children()[i]); } int LayerTreeHostImpl::sourceAnimationFrameNumber() const { return fpsCounter()->currentFrameNumber(); } void LayerTreeHostImpl::renderingStats(RenderingStats* stats) const { stats->numFramesSentToScreen = fpsCounter()->currentFrameNumber(); stats->droppedFrameCount = fpsCounter()->droppedFrameCount(); stats->numImplThreadScrolls = m_numImplThreadScrolls; stats->numMainThreadScrolls = m_numMainThreadScrolls; } void LayerTreeHostImpl::animateScrollbars(base::TimeTicks time) { animateScrollbarsRecursive(m_rootLayerImpl.get(), time); } void LayerTreeHostImpl::animateScrollbarsRecursive(LayerImpl* layer, base::TimeTicks time) { if (!layer) return; ScrollbarAnimationController* scrollbarController = layer->scrollbarAnimationController(); double monotonicTime = (time - base::TimeTicks()).InSecondsF(); if (scrollbarController && scrollbarController->animate(monotonicTime)) m_client->setNeedsRedrawOnImplThread(); for (size_t i = 0; i < layer->children().size(); ++i) animateScrollbarsRecursive(layer->children()[i], time); } } // namespace cc